summaryrefslogtreecommitdiff
path: root/chromium/v8/tools/heap-stats
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/v8/tools/heap-stats')
-rw-r--r--chromium/v8/tools/heap-stats/README.md15
-rw-r--r--chromium/v8/tools/heap-stats/categories.js167
-rw-r--r--chromium/v8/tools/heap-stats/details-selection.html72
-rw-r--r--chromium/v8/tools/heap-stats/details-selection.js211
-rw-r--r--chromium/v8/tools/heap-stats/global-timeline.html16
-rw-r--r--chromium/v8/tools/heap-stats/global-timeline.js135
-rw-r--r--chromium/v8/tools/heap-stats/index.html88
-rw-r--r--chromium/v8/tools/heap-stats/trace-file-reader.html26
-rw-r--r--chromium/v8/tools/heap-stats/trace-file-reader.js300
9 files changed, 1030 insertions, 0 deletions
diff --git a/chromium/v8/tools/heap-stats/README.md b/chromium/v8/tools/heap-stats/README.md
new file mode 100644
index 00000000000..70083fe2574
--- /dev/null
+++ b/chromium/v8/tools/heap-stats/README.md
@@ -0,0 +1,15 @@
+# Heap Stats
+
+Heap stats is a HTML-based tool for visualizing V8-internal object statistics.
+For example, the tool can be used to visualize how much heap memory is used for
+maintaining internal state versus actually allocated by the user.
+
+The tool consumes log files produced by d8 (or Chromium) by passing
+`--trace-gc-object-stats` or a trace captured using Chrome's tracing
+infrastructure. Chrome trace files need to be unpacked before they can
+be used though.
+
+Hosting requires a web server, e.g.:
+
+ cd tools/heap-stats
+ python -m SimpleHTTPServer 8000
diff --git a/chromium/v8/tools/heap-stats/categories.js b/chromium/v8/tools/heap-stats/categories.js
new file mode 100644
index 00000000000..0a836d5f6c0
--- /dev/null
+++ b/chromium/v8/tools/heap-stats/categories.js
@@ -0,0 +1,167 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Categories for instance types.
+const CATEGORIES = new Map([
+ [
+ 'user', new Set([
+ '*FIXED_ARRAY_CONTEXT_SUB_TYPE',
+ '*FIXED_ARRAY_COPY_ON_WRITE_SUB_TYPE',
+ '*FIXED_ARRAY_DICTIONARY_PROPERTIES_SUB_TYPE',
+ '*FIXED_ARRAY_JS_COLLECTION_SUB_TYPE',
+ '*FIXED_ARRAY_JS_WEAK_COLLECTION_SUB_TYPE',
+ '*FIXED_ARRAY_PACKED_ELEMENTS_SUB_TYPE',
+ 'CONS_ONE_BYTE_STRING_TYPE',
+ 'CONS_STRING_TYPE',
+ 'DESCRIPTOR_ARRAY_TYPE',
+ 'EXTERNAL_INTERNALIZED_STRING_TYPE',
+ 'EXTERNAL_ONE_BYTE_INTERNALIZED_STRING_TYPE',
+ 'EXTERNAL_ONE_BYTE_STRING_TYPE',
+ 'EXTERNAL_STRING_TYPE',
+ 'EXTERNAL_STRING_WITH_ONE_BYTE_DATA_TYPE',
+ 'FIXED_DOUBLE_ARRAY_TYPE',
+ 'FIXED_FLOAT32_ARRAY_TYPE',
+ 'FIXED_FLOAT64_ARRAY_TYPE',
+ 'FIXED_INT16_ARRAY_TYPE',
+ 'FIXED_INT32_ARRAY_TYPE',
+ 'FIXED_INT8_ARRAY_TYPE',
+ 'FIXED_UINT16_ARRAY_TYPE',
+ 'FIXED_UINT32_ARRAY_TYPE',
+ 'FIXED_UINT8_ARRAY_TYPE',
+ 'FIXED_UINT8_CLAMPED_ARRAY_TYPE',
+ 'HEAP_NUMBER_TYPE',
+ 'INTERNALIZED_STRING_TYPE',
+ 'JS_ARGUMENTS_TYPE',
+ 'JS_ARRAY_BUFFER_TYPE',
+ 'JS_ARRAY_TYPE',
+ 'JS_BOUND_FUNCTION_TYPE',
+ 'JS_DATE_TYPE',
+ 'JS_ERROR_TYPE',
+ 'JS_FAST_ARRAY_KEY_ITERATOR_TYPE',
+ 'JS_FAST_ARRAY_VALUE_ITERATOR_TYPE',
+ 'JS_FAST_HOLEY_ARRAY_VALUE_ITERATOR_TYPE',
+ 'JS_FAST_HOLEY_SMI_ARRAY_VALUE_ITERATOR_TYPE',
+ 'JS_FAST_SMI_ARRAY_KEY_VALUE_ITERATOR_TYPE',
+ 'JS_FAST_SMI_ARRAY_VALUE_ITERATOR_TYPE',
+ 'JS_FUNCTION_TYPE',
+ 'JS_GENERATOR_OBJECT_TYPE',
+ 'JS_GENERIC_ARRAY_VALUE_ITERATOR_TYPE',
+ 'JS_GLOBAL_OBJECT_TYPE',
+ 'JS_GLOBAL_PROXY_TYPE',
+ 'JS_MAP_KEY_VALUE_ITERATOR_TYPE',
+ 'JS_MAP_TYPE',
+ 'JS_MESSAGE_OBJECT_TYPE',
+ 'JS_OBJECT_TYPE',
+ 'JS_PROMISE_TYPE',
+ 'JS_REGEXP_TYPE',
+ 'JS_SET_TYPE',
+ 'JS_STRING_ITERATOR_TYPE',
+ 'JS_TYPED_ARRAY_TYPE',
+ 'JS_VALUE_TYPE',
+ 'JS_WEAK_MAP_TYPE',
+ 'MUTABLE_HEAP_NUMBER_TYPE',
+ 'ONE_BYTE_INTERNALIZED_STRING_TYPE',
+ 'ONE_BYTE_STRING_TYPE',
+ 'PROPERTY_ARRAY_TYPE',
+ 'SHORT_EXTERNAL_INTERNALIZED_STRING_TYPE',
+ 'SHORT_EXTERNAL_ONE_BYTE_INTERNALIZED_STRING_TYPE',
+ 'SHORT_EXTERNAL_ONE_BYTE_STRING_TYPE',
+ 'SHORT_EXTERNAL_STRING_TYPE',
+ 'SLICED_ONE_BYTE_STRING_TYPE',
+ 'SLICED_STRING_TYPE',
+ 'STRING_TYPE',
+ 'SYMBOL_TYPE',
+ 'THIN_ONE_BYTE_STRING_TYPE',
+ 'THIN_STRING_TYPE',
+ ])
+ ],
+ [
+ 'system', new Set([
+ 'ACCESS_CHECK_INFO_TYPE',
+ 'ACCESSOR_INFO_TYPE',
+ 'ACCESSOR_PAIR_TYPE',
+ 'ALLOCATION_MEMENTO_TYPE',
+ 'ALLOCATION_SITE_TYPE',
+ 'BOILERPLATE_ELEMENTS_TYPE',
+ 'BOILERPLATE_NAME_DICTIONARY_TYPE',
+ 'BOILERPLATE_PROPERTY_ARRAY_TYPE',
+ 'BYTE_ARRAY_TYPE',
+ 'CELL_TYPE',
+ 'CONTEXT_EXTENSION_TYPE',
+ '*FIXED_ARRAY_DEPENDENT_CODE_SUB_TYPE',
+ '*FIXED_ARRAY_ENUM_CACHE_SUB_TYPE',
+ '*FIXED_ARRAY_ENUM_INDICES_CACHE_SUB_TYPE',
+ '*FIXED_ARRAY_FAST_TEMPLATE_INSTANTIATIONS_CACHE_SUB_TYPE',
+ '*FIXED_ARRAY_NUMBER_STRING_CACHE_SUB_TYPE',
+ '*FIXED_ARRAY_PROTOTYPE_USERS_SUB_TYPE',
+ '*FIXED_ARRAY_REGEXP_MULTIPLE_CACHE_SUB_TYPE',
+ '*FIXED_ARRAY_RETAINED_MAPS_SUB_TYPE',
+ '*FIXED_ARRAY_SCOPE_INFO_SUB_TYPE',
+ '*FIXED_ARRAY_SCRIPT_LIST_SUB_TYPE',
+ '*FIXED_ARRAY_SINGLE_CHARACTER_STRING_CACHE_SUB_TYPE',
+ '*FIXED_ARRAY_STRING_SPLIT_CACHE_SUB_TYPE',
+ '*FIXED_ARRAY_TEMPLATE_INFO_SUB_TYPE',
+ '*FIXED_ARRAY_WEAK_NEW_SPACE_OBJECT_TO_CODE_SUB_TYPE',
+ 'FOREIGN_TYPE',
+ 'FUNCTION_TEMPLATE_INFO_TYPE',
+ 'INTERCEPTOR_INFO_TYPE',
+ 'JS_API_OBJECT_TYPE',
+ 'JS_ARRAY_BOILERPLATE_TYPE',
+ 'JS_OBJECT_BOILERPLATE_TYPE',
+ 'JS_SPECIAL_API_OBJECT_TYPE',
+ 'MAP_TYPE',
+ 'OBJECT_TEMPLATE_INFO_TYPE',
+ 'ODDBALL_TYPE',
+ 'PROMISE_REACTION_JOB_INFO_TYPE',
+ 'PROMISE_RESOLVE_THENABLE_JOB_INFO_TYPE',
+ 'PROPERTY_CELL_TYPE',
+ 'PROTOTYPE_INFO_TYPE',
+ 'STACK_FRAME_INFO_TYPE',
+ 'TRANSITION_ARRAY_TYPE',
+ 'WEAK_CELL_TYPE',
+ ])
+ ],
+ [
+ 'code', new Set([
+ '*CODE_BUILTIN',
+ '*CODE_BYTECODE_HANDLER',
+ '*CODE_OPTIMIZED_FUNCTION',
+ '*CODE_REGEXP',
+ '*CODE_STUB',
+ '*FIXED_ARRAY_BYTECODE_ARRAY_CONSTANT_POOL_SUB_TYPE',
+ '*FIXED_ARRAY_BYTECODE_ARRAY_HANDLER_TABLE_SUB_TYPE',
+ '*FIXED_ARRAY_CODE_STUBS_TABLE_SUB_TYPE',
+ '*FIXED_ARRAY_COMPILATION_CACHE_TABLE_SUB_TYPE',
+ '*FIXED_ARRAY_DEOPTIMIZATION_DATA_SUB_TYPE',
+ '*FIXED_ARRAY_EMBEDDED_OBJECT_SUB_TYPE',
+ '*FIXED_ARRAY_HANDLER_TABLE_SUB_TYPE',
+ '*FIXED_ARRAY_NOSCRIPT_SHARED_FUNCTION_INFOS_SUB_TYPE',
+ '*FIXED_ARRAY_OPTIMIZED_CODE_LITERALS_SUB_TYPE',
+ '*FIXED_ARRAY_SHARED_FUNCTION_INFOS_SUB_TYPE',
+ 'BYTECODE_ARRAY_TYPE',
+ 'CODE_DATA_CONTAINER_TYPE',
+ 'FEEDBACK_VECTOR_TYPE',
+ 'LOAD_HANDLER_TYPE',
+ 'SCRIPT_TYPE',
+ 'SHARED_FUNCTION_INFO_TYPE',
+ 'STORE_HANDLER_TYPE',
+ ])
+ ],
+ ['unclassified', new Set()],
+]);
+
+// Maps category to description text that is shown in html.
+const CATEGORY_NAMES = new Map([
+ ['user', 'JS'],
+ ['system', 'Metadata'],
+ ['code', 'Code'],
+ ['unclassified', 'Unclassified'],
+]);
+
+// Instance types that are constructed from their sub types and
+// should thus be hidden.
+const IGNORED_INSTANCE_TYPES = new Set([
+ 'FIXED_ARRAY_TYPE',
+ 'CODE_TYPE',
+]);
diff --git a/chromium/v8/tools/heap-stats/details-selection.html b/chromium/v8/tools/heap-stats/details-selection.html
new file mode 100644
index 00000000000..d60aef96694
--- /dev/null
+++ b/chromium/v8/tools/heap-stats/details-selection.html
@@ -0,0 +1,72 @@
+<!-- Copyright 2018 the V8 project authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file. -->
+<template id="details-selection-template">
+<style>
+.box {
+ border-left: dashed 1px #666666;
+ border-right: dashed 1px #666666;
+ border-bottom: dashed 1px #666666;
+ padding: 10px;
+ overflow: hidden;
+}
+
+.box:nth-of-type(1) {
+ border-top: dashed 1px #666666;
+ border-radius: 5px 5px 0px 0px;
+}
+
+.box:last-of-type {
+ border-radius: 0px 0px 5px 5px;
+}
+
+span {
+ display: block;
+ padding: 5px;
+ font-weight: bold;
+}
+
+.boxDiv {
+ padding: 3px;
+ float: left;
+}
+
+.boxDiv > label {
+ font-size: xx-small;
+}
+
+#categories {
+ margin-top: 10px;
+}
+</style>
+<h2>Data selection</h2>
+<ul>
+ <li>
+ <label for="isolate-select">
+ Isolate
+ </label>
+ <select id="isolate-select">
+ <option>No data</option>
+ </select>
+ </li>
+ <li>
+ <label for="dataset-select">
+ Data set
+ </label>
+ <select id="dataset-select">
+ <option>No data</option>
+ </select>
+ </li>
+ <li>
+ <input type="checkbox" id="merge-categories" checked=checked />
+ <label for="merge-categories">
+ Merge categories
+ </label>
+ </li>
+</ul>
+
+
+<div id="categories"></div>
+</template>
+<script type="text/javascript" src="categories.js"></script>
+<script type="text/javascript" src="details-selection.js"></script> \ No newline at end of file
diff --git a/chromium/v8/tools/heap-stats/details-selection.js b/chromium/v8/tools/heap-stats/details-selection.js
new file mode 100644
index 00000000000..43c000d3f45
--- /dev/null
+++ b/chromium/v8/tools/heap-stats/details-selection.js
@@ -0,0 +1,211 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+const details_selection_template =
+ document.currentScript.ownerDocument.querySelector(
+ '#details-selection-template');
+
+class DetailsSelection extends HTMLElement {
+ constructor() {
+ super();
+ const shadowRoot = this.attachShadow({mode: 'open'});
+ shadowRoot.appendChild(details_selection_template.content.cloneNode(true));
+ this.isolateSelect.addEventListener(
+ 'change', e => this.handleIsolateChange(e));
+ this.datasetSelect.addEventListener(
+ 'change', e => this.notifySelectionChanged(e));
+ this.$('#merge-categories')
+ .addEventListener('change', e => this.notifySelectionChanged(e));
+ }
+
+ connectedCallback() {
+ for (let category of CATEGORIES.keys()) {
+ this.$('#categories').appendChild(this.buildCategory(category));
+ }
+ }
+
+ set data(value) {
+ this._data = value;
+ this.dataChanged();
+ }
+
+ get data() {
+ return this._data;
+ }
+
+ buildCategory(name) {
+ const div = document.createElement('div');
+ div.id = name;
+ div.classList.add('box');
+ const span = document.createElement('span');
+ div.appendChild(span);
+ span.innerHTML = CATEGORY_NAMES.get(name) + ' ';
+ const all_button = document.createElement('button');
+ span.appendChild(all_button);
+ all_button.innerHTML = 'All';
+ all_button.addEventListener('click', e => this.selectCategory(name));
+ const none_button = document.createElement('button');
+ span.appendChild(none_button);
+ none_button.innerHTML = 'None';
+ none_button.addEventListener('click', e => this.unselectCategory(name));
+ const innerDiv = document.createElement('div');
+ div.appendChild(innerDiv);
+ innerDiv.id = name + 'Content';
+ return div;
+ }
+
+ $(id) {
+ return this.shadowRoot.querySelector(id);
+ }
+
+ get datasetSelect() {
+ return this.$('#dataset-select');
+ }
+
+ get isolateSelect() {
+ return this.$('#isolate-select');
+ }
+
+ dataChanged() {
+ this.clearUI();
+ this.populateSelect('#isolate-select', Object.keys(this.data));
+ this.handleIsolateChange();
+ }
+
+ clearUI() {
+ this.selection = {categories: {}};
+ removeAllChildren(this.isolateSelect);
+ removeAllChildren(this.datasetSelect);
+ this.clearCategories();
+ }
+
+ handleIsolateChange(e) {
+ this.selection.isolate = this.isolateSelect.value;
+ if (this.selection.isolate.length === 0) {
+ this.selection.isolate = null;
+ return;
+ }
+
+ this.populateSelect(
+ '#dataset-select', this.data[this.selection.isolate].data_sets, 'live');
+ this.populateCategories();
+ this.notifySelectionChanged();
+ }
+
+ notifySelectionChanged(e) {
+ if (!this.selection.isolate) return;
+
+ this.selection.categories = {};
+ for (let category of CATEGORIES.keys()) {
+ const selected = this.selectedInCategory(category);
+ if (selected.length > 0) this.selection.categories[category] = selected;
+ }
+ this.selection.category_names = CATEGORY_NAMES;
+ this.selection.data_set = this.datasetSelect.value;
+ this.selection.merge_categories = this.$('#merge-categories').checked;
+ this.dispatchEvent(new CustomEvent(
+ 'change', {bubbles: true, composed: true, detail: this.selection}));
+ }
+
+ selectedInCategory(category) {
+ const selected = this.shadowRoot.querySelectorAll(
+ 'input[name=' + category + 'Checkbox]:checked');
+ var tmp = [];
+ for (var val of selected.values()) tmp.push(val.value);
+ return tmp;
+ }
+
+ categoryForType(instance_type) {
+ for (let [key, value] of CATEGORIES.entries()) {
+ if (value.has(instance_type)) return key;
+ }
+ return 'unclassified';
+ }
+
+ createOption(text) {
+ const option = document.createElement('option');
+ option.value = text;
+ option.text = text;
+ return option;
+ }
+
+ populateSelect(id, iterable, autoselect = null) {
+ for (let option_value of iterable) {
+ const option = this.createOption(option_value);
+ if (autoselect === option_value) {
+ option.selected = 'selected';
+ }
+ this.$(id).appendChild(option);
+ }
+ }
+
+ clearCategories() {
+ for (const category of CATEGORIES.keys()) {
+ let f = this.$('#' + category + 'Content');
+ while (f.firstChild) {
+ f.removeChild(f.firstChild);
+ }
+ }
+ }
+
+ populateCategories() {
+ this.clearCategories();
+ const categories = {};
+ for (let cat of CATEGORIES.keys()) {
+ categories[cat] = [];
+ }
+
+ for (let instance_type of this.data[this.selection.isolate]
+ .non_empty_instance_types) {
+ if (IGNORED_INSTANCE_TYPES.has(instance_type)) continue;
+ const category = this.categoryForType(instance_type);
+ categories[category].push(instance_type);
+ }
+ for (let category of Object.keys(categories)) {
+ categories[category].sort();
+ for (let instance_type of categories[category]) {
+ this.$('#' + category + 'Content')
+ .appendChild(this.createCheckBox(instance_type, category));
+ }
+ }
+ }
+
+ unselectCategory(category) {
+ for (let checkbox of this.shadowRoot.querySelectorAll(
+ 'input[name=' + category + 'Checkbox]')) {
+ checkbox.checked = false;
+ }
+ this.notifySelectionChanged();
+ }
+
+ selectCategory(category) {
+ for (let checkbox of this.shadowRoot.querySelectorAll(
+ 'input[name=' + category + 'Checkbox]')) {
+ checkbox.checked = true;
+ }
+ this.notifySelectionChanged();
+ }
+
+ createCheckBox(instance_type, category) {
+ const div = document.createElement('div');
+ div.classList.add('boxDiv');
+ const input = document.createElement('input');
+ div.appendChild(input);
+ input.type = 'checkbox';
+ input.name = category + 'Checkbox';
+ input.checked = 'checked';
+ input.id = instance_type + 'Checkbox';
+ input.value = instance_type;
+ input.addEventListener('change', e => this.notifySelectionChanged(e));
+ const label = document.createElement('label');
+ div.appendChild(label);
+ label.innerText = instance_type;
+ label.htmlFor = instance_type + 'Checkbox';
+ return div;
+ }
+}
+
+customElements.define('details-selection', DetailsSelection);
diff --git a/chromium/v8/tools/heap-stats/global-timeline.html b/chromium/v8/tools/heap-stats/global-timeline.html
new file mode 100644
index 00000000000..788f966735f
--- /dev/null
+++ b/chromium/v8/tools/heap-stats/global-timeline.html
@@ -0,0 +1,16 @@
+<!-- Copyright 2018 the V8 project authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file. -->
+<template id="global-timeline-template">
+<style>
+#chart {
+ width: 100%;
+ height: 500px;
+}
+</style>
+<div id="container" style="display: none;">
+ <h2>Timeline</h2>
+ <div id="chart"></div>
+</div>
+</template>
+<script type="text/javascript" src="global-timeline.js"></script> \ No newline at end of file
diff --git a/chromium/v8/tools/heap-stats/global-timeline.js b/chromium/v8/tools/heap-stats/global-timeline.js
new file mode 100644
index 00000000000..0533f214322
--- /dev/null
+++ b/chromium/v8/tools/heap-stats/global-timeline.js
@@ -0,0 +1,135 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+const KB = 1024;
+const MB = KB * KB;
+
+const global_timeline_template =
+ document.currentScript.ownerDocument.querySelector(
+ '#global-timeline-template');
+
+class GlobalTimeline extends HTMLElement {
+ constructor() {
+ super();
+ const shadowRoot = this.attachShadow({mode: 'open'});
+ shadowRoot.appendChild(global_timeline_template.content.cloneNode(true));
+ }
+
+ $(id) {
+ return this.shadowRoot.querySelector(id);
+ }
+
+ set data(value) {
+ this._data = value;
+ this.stateChanged();
+ }
+
+ get data() {
+ return this._data;
+ }
+
+ set selection(value) {
+ this._selection = value;
+ this.stateChanged();
+ }
+
+ get selection() {
+ return this._selection;
+ }
+
+ isValid() {
+ return this.data && this.selection;
+ }
+
+ hide() {
+ this.$('#container').style.display = 'none';
+ }
+
+ show() {
+ this.$('#container').style.display = 'block';
+ }
+
+ stateChanged() {
+ if (this.isValid()) {
+ this.drawChart();
+ } else {
+ this.hide();
+ }
+ }
+
+ getCategoryData() {
+ const categories = Object.keys(this.selection.categories)
+ .map(k => this.selection.category_names.get(k));
+ const labels = ['Time', ...categories];
+ const chart_data = [labels];
+ const isolate_data = this.data[this.selection.isolate];
+ Object.keys(isolate_data.gcs).forEach(gc_key => {
+ const gc_data = isolate_data.gcs[gc_key];
+ const data_set = gc_data[this.selection.data_set].instance_type_data;
+ const data = [];
+ data.push(gc_data.time);
+ Object.values(this.selection.categories).forEach(instance_types => {
+ data.push(
+ instance_types
+ .map(instance_type => {
+ return data_set[instance_type].overall;
+ })
+ .reduce((accu, current) => accu + current, 0) /
+ KB);
+ });
+ chart_data.push(data);
+ });
+ return chart_data;
+ }
+
+ getInstanceTypeData() {
+ const categories = Object.keys(this.selection.categories);
+ const instance_types =
+ Object.values(this.selection.categories)
+ .reduce((accu, current) => accu.concat(current), []);
+ const labels = ['Time', ...instance_types];
+ const chart_data = [labels];
+ const isolate_data = this.data[this.selection.isolate];
+ Object.keys(isolate_data.gcs).forEach(gc_key => {
+ const gc_data = isolate_data.gcs[gc_key];
+ const data_set = gc_data[this.selection.data_set].instance_type_data;
+ const data = [];
+ data.push(gc_data.time);
+ instance_types.forEach(instance_type => {
+ data.push(data_set[instance_type].overall / KB);
+ });
+ chart_data.push(data);
+ });
+ return chart_data;
+ }
+
+ drawChart() {
+ console.assert(this.data, 'invalid data');
+ console.assert(this.selection, 'invalid selection');
+
+ const chart_data = (this.selection.merge_categories) ?
+ this.getCategoryData() :
+ this.getInstanceTypeData();
+ const data = google.visualization.arrayToDataTable(chart_data);
+ const options = {
+ isStacked: true,
+ hAxis: {
+ title: 'Time [ms]',
+ },
+ vAxis: {title: 'Memory consumption [KBytes]'},
+ chartArea: {width: '85%', height: '70%'},
+ legend: {position: 'top', maxLines: '1'},
+ pointsVisible: true,
+ pointSize: 5,
+ explorer: {},
+ };
+ const chart = new google.visualization.AreaChart(this.$('#chart'));
+ this.show();
+ chart.draw(data, google.charts.Line.convertOptions(options));
+ }
+}
+
+customElements.define('global-timeline', GlobalTimeline);
diff --git a/chromium/v8/tools/heap-stats/index.html b/chromium/v8/tools/heap-stats/index.html
new file mode 100644
index 00000000000..3c2e62b6d0b
--- /dev/null
+++ b/chromium/v8/tools/heap-stats/index.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<!-- Copyright 2018 the V8 project authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file. -->
+
+<html lang="en">
+
+<head>
+ <meta charset="UTF-8">
+ <title>V8 Heap Statistics</title>
+ <link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>
+ <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
+
+ <link rel="import" href="details-selection.html">
+ <link rel="import" href="global-timeline.html">
+ <link rel="import" href="trace-file-reader.html">
+
+ <style type="text/css">
+
+body {
+ font-family: 'Roboto', sans-serif;
+ margin-left: 5%;
+ margin-right: 5%;
+}
+
+ </style>
+ <script type="text/javascript">
+
+'use strict';
+
+google.charts.load('current', {'packages':['line', 'corechart']});
+
+function $(id) { return document.querySelector(id); }
+
+function removeAllChildren(node) {
+ while (node.firstChild) {
+ node.removeChild(node.firstChild);
+ }
+}
+
+let state = Object.create(null);
+
+function globalDataChanged(e) {
+ state.data = e.detail;
+ // Emit one entry with the whole model for debugging purposes.
+ console.log(state.data);
+ state.selection = null;
+ $('#global-timeline').selection = state.selection;
+ $('#global-timeline').data = state.data;
+ $('#type-details').selection = state.selection;
+ $('#type-details').data = state.data;
+ $('#details-selection').data = state.data;
+}
+
+function globalSelectionChangedA(e) {
+ state.selection = e.detail;
+ $('#global-timeline').selection = state.selection;
+ $('#type-details').selection = state.selection;
+}
+
+ </script>
+</head>
+
+<body>
+ <trace-file-reader onchange="globalDataChanged(event)"></trace-file-reader>
+ <h1>V8 Heap Statistics</h1>
+ <p>Visualize object statistics that have been gathered using</p>
+ <ul>
+ <li><code>--trace-gc-object-stats on V8</code></li>
+ <li>
+ <a
+ href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chrome's
+ tracing infrastructure</a> collecting data for the category
+ <code>v8.gc_stats</code>. The trace file needs to be unpacked (e.g. using
+ <code>gunzip</code>).
+ </li>
+ </ul>
+ <p>
+ Note that the visualizer needs to run on a web server due to HTML imports
+ requiring <a
+ href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing">CORS</a>.
+ </p>
+ <details-selection id="details-selection" onchange="globalSelectionChangedA(event)"></details-selection>
+ <global-timeline id="global-timeline"></global-timeline>
+ <type-details id="type-details"></type-details>
+</body>
+
+</html>
diff --git a/chromium/v8/tools/heap-stats/trace-file-reader.html b/chromium/v8/tools/heap-stats/trace-file-reader.html
new file mode 100644
index 00000000000..98c2ef0c606
--- /dev/null
+++ b/chromium/v8/tools/heap-stats/trace-file-reader.html
@@ -0,0 +1,26 @@
+<!-- Copyright 2018 the V8 project authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file. -->
+<template id="trace-file-reader-template">
+<style>
+#fileReader {
+ width: 100%;
+ height: 100px;
+ line-height: 100px;
+ text-align: center;
+ border: solid 1px #000000;
+ border-radius: 5px;
+}
+
+#fileReader > input {
+ display: none;
+}
+</style>
+<div id="fileReader">
+ <span id="label">
+ Drag and drop a trace file into this area, or click to choose from disk.
+ </span>
+ <input id="file" type="file" name="file" />
+</div>
+</template>
+<script type="text/javascript" src="trace-file-reader.js"></script>
diff --git a/chromium/v8/tools/heap-stats/trace-file-reader.js b/chromium/v8/tools/heap-stats/trace-file-reader.js
new file mode 100644
index 00000000000..59825fe514e
--- /dev/null
+++ b/chromium/v8/tools/heap-stats/trace-file-reader.js
@@ -0,0 +1,300 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+const trace_file_reader_template =
+ document.currentScript.ownerDocument.querySelector(
+ '#trace-file-reader-template');
+
+class TraceFileReader extends HTMLElement {
+ constructor() {
+ super();
+ const shadowRoot = this.attachShadow({mode: 'open'});
+ shadowRoot.appendChild(trace_file_reader_template.content.cloneNode(true));
+ this.addEventListener('click', e => this.handleClick(e));
+ this.addEventListener('dragover', e => this.handleDragOver(e));
+ this.addEventListener('drop', e => this.handleChange(e));
+ this.$('#file').addEventListener('change', e => this.handleChange(e));
+ }
+
+ $(id) {
+ return this.shadowRoot.querySelector(id);
+ }
+
+ updateLabel(text) {
+ this.$('#label').innerText = text;
+ }
+
+ handleClick(event) {
+ this.$('#file').click();
+ }
+
+ handleChange(event) {
+ // Used for drop and file change.
+ event.preventDefault();
+ var host = event.dataTransfer ? event.dataTransfer : event.target;
+ this.readFile(host.files[0]);
+ }
+
+ handleDragOver(event) {
+ event.preventDefault();
+ }
+
+ connectedCallback() {}
+
+ readFile(file) {
+ if (!file) {
+ this.updateLabel('Failed to load file.');
+ return;
+ }
+
+ const result = new FileReader();
+ result.onload = (e) => {
+ let contents = e.target.result.split('\n');
+ const return_data = (e.target.result.includes('V8.GC_Objects_Stats')) ?
+ this.createModelFromChromeTraceFile(contents) :
+ this.createModelFromV8TraceFile(contents);
+ this.updateLabel('Finished loading \'' + file.name + '\'.');
+ this.dispatchEvent(new CustomEvent(
+ 'change', {bubbles: true, composed: true, detail: return_data}));
+ };
+ result.readAsText(file);
+ }
+
+ createOrUpdateEntryIfNeeded(data, keys, entry) {
+ console.assert(entry.isolate, 'entry should have an isolate');
+ if (!(entry.isolate in keys)) {
+ keys[entry.isolate] = new Set();
+ }
+ if (!(entry.isolate in data)) {
+ data[entry.isolate] = {
+ non_empty_instance_types: new Set(),
+ gcs: {},
+ zonetags: [],
+ samples: {zone: {}},
+ start: null,
+ end: null,
+ data_sets: new Set()
+ };
+ }
+ const data_object = data[entry.isolate];
+ if (('id' in entry) && !(entry.id in data_object.gcs)) {
+ data_object.gcs[entry.id] = {non_empty_instance_types: new Set()};
+ }
+ if ('time' in entry) {
+ if (data_object.end === null || data_object.end < entry.time)
+ data_object.end = entry.time;
+ if (data_object.start === null || data_object.start > entry.time)
+ data_object.start = entry.time;
+ }
+ }
+
+ createDatasetIfNeeded(data, keys, entry, data_set) {
+ if (!(data_set in data[entry.isolate].gcs[entry.id])) {
+ data[entry.isolate].gcs[entry.id][data_set] = {
+ instance_type_data: {},
+ non_empty_instance_types: new Set(),
+ overall: 0
+ };
+ data[entry.isolate].data_sets.add(data_set);
+ }
+ }
+
+ addInstanceTypeData(
+ data, keys, isolate, gc_id, data_set, instance_type, entry) {
+ keys[isolate].add(data_set);
+ data[isolate].gcs[gc_id][data_set].instance_type_data[instance_type] = {
+ overall: entry.overall,
+ count: entry.count,
+ histogram: entry.histogram,
+ over_allocated: entry.over_allocated,
+ over_allocated_histogram: entry.over_allocated_histogram
+ };
+ data[isolate].gcs[gc_id][data_set].overall += entry.overall;
+ if (entry.overall !== 0) {
+ data[isolate].gcs[gc_id][data_set].non_empty_instance_types.add(
+ instance_type);
+ data[isolate].gcs[gc_id].non_empty_instance_types.add(instance_type);
+ data[isolate].non_empty_instance_types.add(instance_type);
+ }
+ }
+
+ extendAndSanitizeModel(data, keys) {
+ const checkNonNegativeProperty = (obj, property) => {
+ console.assert(obj[property] >= 0, 'negative property', obj, property);
+ };
+
+ for (const isolate of Object.keys(data)) {
+ for (const gc of Object.keys(data[isolate].gcs)) {
+ for (const data_set_key of keys[isolate]) {
+ const data_set = data[isolate].gcs[gc][data_set_key];
+ // 1. Create a ranked instance type array that sorts instance
+ // types by memory size (overall).
+ data_set.ranked_instance_types =
+ [...data_set.non_empty_instance_types].sort(function(a, b) {
+ if (data_set.instance_type_data[a].overall >
+ data_set.instance_type_data[b].overall) {
+ return 1;
+ } else if (
+ data_set.instance_type_data[a].overall <
+ data_set.instance_type_data[b].overall) {
+ return -1;
+ }
+ return 0;
+ });
+
+ let known_count = 0;
+ let known_overall = 0;
+ let known_histogram =
+ Array(
+ data_set.instance_type_data.FIXED_ARRAY_TYPE.histogram.length)
+ .fill(0);
+ for (const instance_type in data_set.instance_type_data) {
+ if (!instance_type.startsWith('*FIXED_ARRAY')) continue;
+ const subtype = data_set.instance_type_data[instance_type];
+ known_count += subtype.count;
+ known_overall += subtype.count;
+ for (let i = 0; i < subtype.histogram.length; i++) {
+ known_histogram[i] += subtype.histogram[i];
+ }
+ }
+
+ const fixed_array_data = data_set.instance_type_data.FIXED_ARRAY_TYPE;
+ const unknown_entry = {
+ count: fixed_array_data.count - known_count,
+ overall: fixed_array_data.overall - known_overall,
+ histogram: fixed_array_data.histogram.map(
+ (value, index) => value - known_histogram[index])
+ };
+
+ // Check for non-negative values.
+ checkNonNegativeProperty(unknown_entry, 'count');
+ checkNonNegativeProperty(unknown_entry, 'overall');
+ for (let i = 0; i < unknown_entry.histogram.length; i++) {
+ checkNonNegativeProperty(unknown_entry.histogram, i);
+ }
+
+ data_set.instance_type_data['*FIXED_ARRAY_UNKNOWN_SUB_TYPE'] =
+ unknown_entry;
+ data_set.non_empty_instance_types.add(
+ '*FIXED_ARRAY_UNKNOWN_SUB_TYPE');
+ }
+ }
+ }
+ }
+
+ createModelFromChromeTraceFile(contents) {
+ console.log('Processing log as chrome trace file.');
+ const data = Object.create(null); // Final data container.
+ const keys = Object.create(null); // Collecting 'keys' per isolate.
+
+ // Pop last line in log as it might be broken.
+ contents.pop();
+ // Remove trailing comma.
+ contents[contents.length - 1] = contents[contents.length - 1].slice(0, -1);
+ // Terminate JSON.
+ const sanitized_contents = [...contents, ']}'].join('');
+ try {
+ const raw_data = JSON.parse(sanitized_contents);
+ const objects_stats_data =
+ raw_data.traceEvents.filter(e => e.name == 'V8.GC_Objects_Stats');
+ objects_stats_data.forEach(trace_data => {
+ const actual_data = trace_data.args;
+ const data_sets = new Set(Object.keys(actual_data));
+ Object.keys(actual_data).forEach(data_set => {
+ const string_entry = actual_data[data_set];
+ try {
+ const entry = JSON.parse(string_entry);
+ this.createOrUpdateEntryIfNeeded(data, keys, entry);
+ this.createDatasetIfNeeded(data, keys, entry, data_set);
+ const isolate = entry.isolate;
+ const time = entry.time;
+ const gc_id = entry.id;
+ data[isolate].gcs[gc_id].time = time;
+ data[isolate].gcs[gc_id][data_set].bucket_sizes =
+ entry.bucket_sizes;
+ for (let [instance_type, value] of Object.entries(
+ entry.type_data)) {
+ // Trace file format uses markers that do not have actual
+ // properties.
+ if (!('overall' in value)) continue;
+ this.addInstanceTypeData(
+ data, keys, isolate, gc_id, data_set, instance_type, value);
+ }
+ } catch (e) {
+ console.log('Unable to parse data set entry', e);
+ }
+ });
+ });
+ } catch (e) {
+ console.log('Unable to parse chrome trace file.', e);
+ }
+ this.extendAndSanitizeModel(data, keys);
+ return data;
+ }
+
+ createModelFromV8TraceFile(contents) {
+ console.log('Processing log as V8 trace file.');
+ contents = contents.map(function(line) {
+ try {
+ // Strip away a potentially present adb logcat prefix.
+ line = line.replace(/^I\/v8\s*\(\d+\):\s+/g, '');
+ return JSON.parse(line);
+ } catch (e) {
+ console.log('Unable to parse line: \'' + line + '\'\' (' + e + ')');
+ }
+ return null;
+ });
+
+ const data = Object.create(null); // Final data container.
+ const keys = Object.create(null); // Collecting 'keys' per isolate.
+
+ for (var entry of contents) {
+ if (entry === null || entry.type === undefined) {
+ continue;
+ }
+ if (entry.type === 'zone') {
+ this.createOrUpdateEntryIfNeeded(data, keys, entry);
+ const stacktrace = ('stacktrace' in entry) ? entry.stacktrace : [];
+ data[entry.isolate].samples.zone[entry.time] = {
+ allocated: entry.allocated,
+ pooled: entry.pooled,
+ stacktrace: stacktrace
+ };
+ } else if (
+ entry.type === 'zonecreation' || entry.type === 'zonedestruction') {
+ this.createOrUpdateEntryIfNeeded(data, keys, entry);
+ data[entry.isolate].zonetags.push(
+ Object.assign({opening: entry.type === 'zonecreation'}, entry));
+ } else if (entry.type === 'gc_descriptor') {
+ this.createOrUpdateEntryIfNeeded(data, keys, entry);
+ data[entry.isolate].gcs[entry.id].time = entry.time;
+ if ('zone' in entry)
+ data[entry.isolate].gcs[entry.id].malloced = entry.zone;
+ } else if (entry.type === 'instance_type_data') {
+ if (entry.id in data[entry.isolate].gcs) {
+ this.createOrUpdateEntryIfNeeded(data, keys, entry);
+ this.createDatasetIfNeeded(data, keys, entry, entry.key);
+ this.addInstanceTypeData(
+ data, keys, entry.isolate, entry.id, entry.key,
+ entry.instance_type_name, entry);
+ }
+ } else if (entry.type === 'bucket_sizes') {
+ if (entry.id in data[entry.isolate].gcs) {
+ this.createOrUpdateEntryIfNeeded(data, keys, entry);
+ this.createDatasetIfNeeded(data, keys, entry, entry.key);
+ data[entry.isolate].gcs[entry.id][entry.key].bucket_sizes =
+ entry.sizes;
+ }
+ } else {
+ console.log('Unknown entry type: ' + entry.type);
+ }
+ }
+ this.extendAndSanitizeModel(data, keys);
+ return data;
+ }
+}
+
+customElements.define('trace-file-reader', TraceFileReader);