Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

16
deps/v8/tools/heap-stats/README.md vendored Normal file
View File

@ -0,0 +1,16 @@
# 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 can either be processed as gzip or raw text
files.
Hosting requires a web server, e.g.:
cd tools/heap-stats
python -m SimpleHTTPServer 8000

223
deps/v8/tools/heap-stats/categories.js vendored Normal file
View File

@ -0,0 +1,223 @@
// 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.
export const CATEGORIES = new Map([
[
'user', new Set([
'CONS_ONE_BYTE_STRING_TYPE',
'CONS_TWO_BYTE_STRING_TYPE',
'DESCRIPTOR_ARRAY_TYPE',
'DEPRECATED_DESCRIPTOR_ARRAY_TYPE',
'ELEMENTS_TYPE',
'EXTERNAL_INTERNALIZED_TWO_BYTE_STRING_TYPE',
'EXTERNAL_INTERNALIZED_ONE_BYTE_STRING_TYPE',
'EXTERNAL_ONE_BYTE_STRING_TYPE',
'EXTERNAL_TWO_BYTE_STRING_TYPE',
'EXTERNAL_STRING_WITH_ONE_BYTE_DATA_TYPE',
'FIXED_BIGINT64_ARRAY_TYPE',
'FIXED_BIGUINT64_ARRAY_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',
'FUNCTION_CONTEXT_TYPE',
'GLOBAL_ELEMENTS_TYPE',
'GLOBAL_PROPERTIES_TYPE',
'HEAP_NUMBER_TYPE',
'INTERNALIZED_TWO_BYTE_STRING_TYPE',
'JS_ARGUMENTS_OBJECT_TYPE',
'JS_ARRAY_BUFFER_TYPE',
'JS_ARRAY_ITERATOR_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_COLLATOR_TYPE',
'JS_DATE_TIME_FORMAT_TYPE',
'JS_DISPLAY_NAMES_TYPE',
'JS_DURATION_FORMAT_TYPE',
'JS_LIST_FORMAT_TYPE',
'JS_LOCALE_TYPE',
'JS_NUMBER_FORMAT_TYPE',
'JS_PLURAL_RULES_TYPE',
'JS_RELATIVE_TIME_FORMAT_TYPE',
'JS_SEGMENT_ITERATOR_TYPE',
'JS_SEGMENTER_TYPE',
'JS_SEGMENTS_TYPE',
'JS_V8_BREAK_ITERATOR_TYPE',
'JS_MAP_KEY_ITERATOR_TYPE',
'JS_MAP_KEY_VALUE_ITERATOR_TYPE',
'JS_MAP_TYPE',
'JS_MAP_VALUE_ITERATOR_TYPE',
'JS_MESSAGE_OBJECT_TYPE',
'JS_OBJECT_TYPE',
'JS_PRIMITIVE_WRAPPER_TYPE',
'JS_PROMISE_TYPE',
'JS_PROXY_TYPE',
'JS_REG_EXP_TYPE',
'JS_SET_KEY_VALUE_ITERATOR_TYPE',
'JS_SET_TYPE',
'JS_SET_VALUE_ITERATOR_TYPE',
'JS_STRING_ITERATOR_TYPE',
'JS_TO_WASM_FUNCTION',
'JS_TYPED_ARRAY_TYPE',
'JS_WEAK_MAP_TYPE',
'HEAP_NUMBER_TYPE',
'NATIVE_CONTEXT_TYPE',
'OBJECT_PROPERTY_DICTIONARY_TYPE',
'INTERNALIZED_ONE_BYTE_STRING_TYPE',
'SEQ_ONE_BYTE_STRING_TYPE',
'OTHER_CONTEXT_TYPE',
'PROPERTY_ARRAY_TYPE',
'PROTOTYPE_DESCRIPTOR_ARRAY_TYPE',
'SLICED_ONE_BYTE_STRING_TYPE',
'SLICED_TWO_BYTE_STRING_TYPE',
'STRING_EXTERNAL_RESOURCE_ONE_BYTE_TYPE',
'STRING_EXTERNAL_RESOURCE_TWO_BYTE_TYPE',
'STRONG_DESCRIPTOR_ARRAY_TYPE',
'SEQ_TWO_BYTE_STRING_TYPE',
'SYMBOL_TYPE',
'THIN_ONE_BYTE_STRING_TYPE',
'THIN_TWO_BYTE_STRING_TYPE',
'UNCACHED_EXTERNAL_INTERNALIZED_TWO_BYTE_STRING_TYPE',
'UNCACHED_EXTERNAL_INTERNALIZED_ONE_BYTE_STRING_TYPE',
'UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE',
'UNCACHED_EXTERNAL_TWO_BYTE_STRING_TYPE',
'WASM_INSTANCE_OBJECT_TYPE',
'WASM_MEMORY_OBJECT_TYPE',
'WASM_MODULE_OBJECT_TYPE',
])
],
[
'system', new Set([
'ACCESS_CHECK_INFO_TYPE',
'ACCESSOR_INFO_TYPE',
'ACCESSOR_PAIR_TYPE',
'ALLOCATION_MEMENTO_TYPE',
'ALLOCATION_SITE_TYPE',
'ARRAY_BOILERPLATE_DESCRIPTION_TYPE',
'ARRAY_BOILERPLATE_DESCRIPTION_ELEMENTS_TYPE',
'BOILERPLATE_ELEMENTS_TYPE',
'BOILERPLATE_PROPERTY_ARRAY_TYPE',
'BOILERPLATE_PROPERTY_DICTIONARY_TYPE',
'BYTE_ARRAY_TYPE',
'CALL_HANDLER_INFO_TYPE',
'CALL_SITE_INFO_TYPE',
'CELL_TYPE',
'CODE_STUBS_TABLE_TYPE',
'CONTEXT_EXTENSION_TYPE',
'ENUM_CACHE_TYPE',
'ENUM_INDICES_CACHE_TYPE',
'FOREIGN_TYPE',
'FUNCTION_TEMPLATE_INFO_ENTRIES_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',
'NUMBER_STRING_CACHE_TYPE',
'OBJECT_BOILERPLATE_DESCRIPTION_TYPE',
'OBJECT_TEMPLATE_INFO_TYPE',
'OBJECT_TO_CODE_TYPE',
'ODDBALL_TYPE',
'PROMISE_REACTION_JOB_INFO_TYPE',
'PROMISE_RESOLVE_THENABLE_JOB_INFO_TYPE',
'PROPERTY_CELL_TYPE',
'PROTOTYPE_INFO_TYPE',
'PROTOTYPE_USERS_TYPE',
'REGEXP_MULTIPLE_CACHE_TYPE',
'RETAINED_MAPS_TYPE',
'SCOPE_INFO_TYPE',
'SCRIPT_LIST_TYPE',
'SCRIPT_SHARED_FUNCTION_INFOS_TYPE',
'SERIALIZED_OBJECTS_TYPE',
'SINGLE_CHARACTER_STRING_CACHE_TYPE',
'STRING_SPLIT_CACHE_TYPE',
'STRING_TABLE_TYPE',
'TRANSITION_ARRAY_TYPE',
'WEAK_NEW_SPACE_OBJECT_TO_CODE_TYPE',
])
],
[
'code', new Set([
'BUILTIN',
'BYTECODE_ARRAY_CONSTANT_POOL_TYPE',
'BYTECODE_ARRAY_HANDLER_TABLE_TYPE',
'BYTECODE_ARRAY_TYPE',
'BYTECODE_HANDLER',
'CODE_TYPE',
'DEOPTIMIZATION_DATA_TYPE',
'EMBEDDED_OBJECT_TYPE',
'FEEDBACK_CELL_TYPE',
'FEEDBACK_METADATA_TYPE',
'FEEDBACK_VECTOR_ENTRY_TYPE',
'FEEDBACK_VECTOR_HEADER_TYPE',
'FEEDBACK_VECTOR_SLOT_CALL_TYPE',
'FEEDBACK_VECTOR_SLOT_CALL_UNUSED_TYPE',
'FEEDBACK_VECTOR_SLOT_ENUM_TYPE',
'FEEDBACK_VECTOR_SLOT_LOAD_TYPE',
'FEEDBACK_VECTOR_SLOT_LOAD_UNUSED_TYPE',
'FEEDBACK_VECTOR_SLOT_OTHER_TYPE',
'FEEDBACK_VECTOR_SLOT_STORE_TYPE',
'FEEDBACK_VECTOR_SLOT_STORE_UNUSED_TYPE',
'FEEDBACK_VECTOR_TYPE',
'LOAD_HANDLER_TYPE',
'NOSCRIPT_SHARED_FUNCTION_INFOS_TYPE',
'OPTIMIZED_CODE_LITERALS_TYPE',
'OPTIMIZED_FUNCTION',
'PREPARSE_DATA_TYPE',
'REGEXP',
'RELOC_INFO_TYPE',
'SCRIPT_SOURCE_EXTERNAL_ONE_BYTE_TYPE',
'SCRIPT_SOURCE_EXTERNAL_TWO_BYTE_TYPE',
'SCRIPT_SOURCE_EXTERNAL_TYPE',
'SCRIPT_SOURCE_NON_EXTERNAL_ONE_BYTE_TYPE',
'SCRIPT_SOURCE_NON_EXTERNAL_TWO_BYTE_TYPE',
'SCRIPT_TYPE',
'SHARED_FUNCTION_INFO_TYPE',
'SOURCE_POSITION_TABLE_TYPE',
'STORE_HANDLER_TYPE',
'STUB',
'UNCOMPILED_DATA_WITHOUT_PREPARSE_DATA_TYPE',
'UNCOMPILED_DATA_WITH_PREPARSE_DATA_TYPE',
'UNCOMPILED_JS_FUNCTION_TYPE',
'UNCOMPILED_SHARED_FUNCTION_INFO_TYPE'
])
],
['unclassified', new Set()],
['duplicated', new Set([
'WASTED_DESCRIPTOR_ARRAY_DETAILS_TYPE',
'WASTED_DESCRIPTOR_ARRAY_VALUES_TYPE',
])
],
]);
// Maps category to description text that is shown in html.
export const CATEGORY_NAMES = new Map([
['user', 'JS'],
['system', 'Metadata'],
['code', 'Code'],
['unclassified', 'Unclassified'],
['duplicated', 'Some detailed data ALREADY ACCOUNTED (!!!) in other entries'],
]);

View File

@ -0,0 +1,151 @@
<!-- 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. -->
<style>
#dataSelectionSection {
display: none;
}
.box {
border-left: dashed 1px #666666;
border-right: dashed 1px #666666;
border-bottom: dashed 1px #666666;
padding: 10px;
overflow: hidden;
position: relative;
}
.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;
}
.box > ul {
margin: 0px;
padding: 0px;
}
.box > ul > li {
display: inline-block;
}
.box > ul > li:not(:first-child) {
margin-left: 10px;
}
.box > ul > li:first-child {
font-weight: bold;
}
.instanceTypeSelectBox {
position: relative;
overflow: hidden;
float: left;
padding: 0px 5px 2px 0px;
margin: 3px;
border-radius: 3px;
}
.instanceTypeSelectBox > label {
font-size: xx-small;
}
.instanceTypeSelectBox > input {
vertical-align: middle;
}
.percentBackground {
position: absolute;
width: 200%;
height: 100%;
left: 0%;
top: 0px;
margin-left: -100%;
transition: all 1s ease-in-out;
}
.instanceTypeSelectBox > .percentBackground {
background: linear-gradient(90deg, #68b0f7 50%, #b3d9ff 50%);
z-index: -1;
}
.box > .percentBackground {
background: linear-gradient(90deg, #e0edfe 50%, #fff 50%);
z-index: -2;
}
#categories {
margin-top: 10px;
}
#category-filter {
text-align: right;
width: 50px;
}
.categorySelectionButtons {
float: right;
}
.categoryLabels {
float: left;
min-wdith: 200px;
}
.categoryContent {
clear: both;
}
</style>
<section id="dataSelectionSection">
<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="data-view-select">
Data view
</label>
<select id="data-view-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>
<label for="gc-select">
Garbage collection (at a specific time in ms)
</label>
<select id="gc-select">
<option>No data</option>
</select>
</li>
<li>
<input id="category-filter" type="text" value="0" disabled="disabled" />KB
<button id="category-filter-btn" disabled="disabled">
Filter categories with less memory
</button>
<button id="category-auto-filter-btn" disabled="disabled">
Show top 20 categories only
</button>
</li>
<li>
<button id="csv-export-btn" disabled="disabled">Export selection as CSV</button>
</li>
</ul>
<div id="categories"></div>
</section>

View File

@ -0,0 +1,447 @@
// 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';
import {CATEGORIES, CATEGORY_NAMES} from './categories.js';
export const VIEW_BY_INSTANCE_TYPE = 'by-instance-type';
export const VIEW_BY_INSTANCE_CATEGORY = 'by-instance-category';
export const VIEW_BY_FIELD_TYPE = 'by-field-type';
defineCustomElement('details-selection', (templateText) =>
class DetailsSelection extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = templateText;
this.isolateSelect.addEventListener(
'change', e => this.handleIsolateChange(e));
this.dataViewSelect.addEventListener(
'change', e => this.notifySelectionChanged(e));
this.datasetSelect.addEventListener(
'change', e => this.notifySelectionChanged(e));
this.gcSelect.addEventListener(
'change', e => this.notifySelectionChanged(e));
this.$('#csv-export-btn')
.addEventListener('click', e => this.exportCurrentSelection(e));
this.$('#category-filter-btn')
.addEventListener('click', e => this.filterCurrentSelection(e));
this.$('#category-auto-filter-btn')
.addEventListener('click', e => this.filterTop20Categories(e));
this._data = undefined;
this.selection = undefined;
}
connectedCallback() {
for (let category of CATEGORIES.keys()) {
this.$('#categories').appendChild(this.buildCategory(category));
}
}
dataChanged() {
this.selection = {categories: {}};
this.resetUI(true);
this.populateIsolateSelect();
this.handleIsolateChange();
this.$('#dataSelectionSection').style.display = 'block';
}
set data(value) {
this._data = value;
this.dataChanged();
}
get data() {
return this._data;
}
get selectedIsolate() {
return this._data[this.selection.isolate];
}
get selectedData() {
console.assert(this.data, 'invalid data');
console.assert(this.selection, 'invalid selection');
return this.selectedIsolate.gcs[this.selection.gc][this.selection.data_set];
}
$(id) {
return this.shadowRoot.querySelector(id);
}
querySelectorAll(query) {
return this.shadowRoot.querySelectorAll(query);
}
get dataViewSelect() {
return this.$('#data-view-select');
}
get datasetSelect() {
return this.$('#dataset-select');
}
get isolateSelect() {
return this.$('#isolate-select');
}
get gcSelect() {
return this.$('#gc-select');
}
buildCategory(name) {
const div = document.createElement('div');
div.id = name;
div.classList.add('box');
let ul = document.createElement('ul');
ul.className = 'categoryLabels'
{
const name_li = document.createElement('li');
name_li.textContent = CATEGORY_NAMES.get(name);
ul.appendChild(name_li);
const percent_li = document.createElement('li');
percent_li.textContent = '0%';
percent_li.id = name + 'PercentContent';
ul.appendChild(percent_li);
}
div.appendChild(ul);
ul = document.createElement('ul');
ul.className = 'categorySelectionButtons'
{
const all_li = document.createElement('li');
const all_button = document.createElement('button');
all_button.textContent = 'All';
all_button.addEventListener('click', e => this.selectCategory(name));
all_li.appendChild(all_button);
ul.appendChild(all_li);
const top_li = document.createElement('li');
const top_button = document.createElement('button');
top_button.textContent = 'Top 10';
top_button.addEventListener(
'click', e => this.selectCategoryTopEntries(name));
top_li.appendChild(top_button);
ul.appendChild(top_li);
const none_li = document.createElement('li');
const none_button = document.createElement('button');
none_button.textContent = 'None';
none_button.addEventListener('click', e => this.unselectCategory(name));
none_li.appendChild(none_button);
ul.appendChild(none_li);
}
div.appendChild(ul);
const innerDiv = document.createElement('div');
innerDiv.id = name + 'Content';
innerDiv.className = 'categoryContent';
div.appendChild(innerDiv);
const percentDiv = document.createElement('div');
percentDiv.className = 'percentBackground';
percentDiv.id = name + 'PercentBackground';
div.appendChild(percentDiv);
return div;
}
populateIsolateSelect() {
let isolates = Object.entries(this.data);
// Sorty by peak heap memory consumption.
isolates.sort((a, b) => b[1].peakMemory - a[1].peakMemory);
this.populateSelect(
'#isolate-select', isolates, (key, isolate) => isolate.getLabel());
}
resetUI(resetIsolateSelect) {
if (resetIsolateSelect) removeAllChildren(this.isolateSelect);
removeAllChildren(this.dataViewSelect);
removeAllChildren(this.datasetSelect);
removeAllChildren(this.gcSelect);
this.clearCategories();
this.setButtonState('disabled');
}
setButtonState(disabled) {
this.$('#csv-export-btn').disabled = disabled;
this.$('#category-filter').disabled = disabled;
this.$('#category-filter-btn').disabled = disabled;
this.$('#category-auto-filter-btn').disabled = disabled;
}
handleIsolateChange(e) {
this.selection.isolate = this.isolateSelect.value;
if (this.selection.isolate.length === 0) {
this.selection.isolate = null;
return;
}
this.resetUI(false);
this.populateSelect(
'#data-view-select', [
[VIEW_BY_INSTANCE_TYPE, 'Selected instance types'],
[VIEW_BY_INSTANCE_CATEGORY, 'Selected type categories'],
[VIEW_BY_FIELD_TYPE, 'Field type statistics']
],
(key, label) => label, VIEW_BY_INSTANCE_TYPE);
this.populateSelect(
'#dataset-select', this.selectedIsolate.data_sets.entries(), null,
'live');
this.populateSelect(
'#gc-select',
Object.keys(this.selectedIsolate.gcs)
.map(id => [id, this.selectedIsolate.gcs[id].time]),
(key, time, index) => {
return (index + ': ').padStart(4, '0') +
formatSeconds(time).padStart(6, '0') + ' ' +
formatBytes(this.selectedIsolate.gcs[key].live.overall)
.padStart(9, '0');
});
this.populateCategories();
this.notifySelectionChanged();
}
notifySelectionChanged(e) {
if (!this.selection.isolate) return;
this.selection.data_view = this.dataViewSelect.value;
this.selection.categories = {};
if (this.selection.data_view === VIEW_BY_FIELD_TYPE) {
this.$('#categories').style.display = 'none';
} else {
for (let category of CATEGORIES.keys()) {
const selected = this.selectedInCategory(category);
if (selected.length > 0) this.selection.categories[category] = selected;
}
this.$('#categories').style.display = 'block';
}
this.selection.category_names = CATEGORY_NAMES;
this.selection.data_set = this.datasetSelect.value;
this.selection.gc = this.gcSelect.value;
this.setButtonState(false);
this.updatePercentagesInCategory();
this.updatePercentagesInInstanceTypes();
this.dispatchEvent(new CustomEvent(
'change', {bubbles: true, composed: true, detail: this.selection}));
}
filterCurrentSelection(e) {
const minSize = this.$('#category-filter').value * KB;
this.filterCurrentSelectionWithThresold(minSize);
}
filterTop20Categories(e) {
// Limit to show top 20 categories only.
let minSize = 0;
let count = 0;
let sizes = this.selectedIsolate.instanceTypePeakMemory;
for (let key in sizes) {
if (count == 20) break;
minSize = sizes[key];
count++;
}
this.filterCurrentSelectionWithThresold(minSize);
}
filterCurrentSelectionWithThresold(minSize) {
if (minSize === 0) return;
this.selection.category_names.forEach((_, category) => {
for (let checkbox of this.querySelectorAll(
'input[name=' + category + 'Checkbox]')) {
checkbox.checked =
this.selectedData.instance_type_data[checkbox.instance_type]
.overall > minSize;
console.log(
checkbox.instance_type, checkbox.checked,
this.selectedData.instance_type_data[checkbox.instance_type]
.overall);
}
});
this.notifySelectionChanged();
}
updatePercentagesInCategory() {
const overalls = {};
let overall = 0;
// Reset all categories.
this.selection.category_names.forEach((_, category) => {
overalls[category] = 0;
});
// Only update categories that have selections.
Object.entries(this.selection.categories).forEach(([category, value]) => {
overalls[category] =
Object.values(value).reduce(
(accu, current) =>
accu + this.selectedData.instance_type_data[current].overall,
0) /
KB;
overall += overalls[category];
});
Object.entries(overalls).forEach(([category, category_overall]) => {
let percents = category_overall / overall * 100;
this.$(`#${category}PercentContent`).textContent =
`${percents.toFixed(1)}%`;
this.$('#' + category + 'PercentBackground').style.left = percents + '%';
});
}
updatePercentagesInInstanceTypes() {
const instanceTypeData = this.selectedData.instance_type_data;
const maxInstanceType = this.selectedData.singleInstancePeakMemory;
this.querySelectorAll('.instanceTypeSelectBox input').forEach(checkbox => {
let instanceType = checkbox.value;
let instanceTypeSize = instanceTypeData[instanceType].overall;
let percents = instanceTypeSize / maxInstanceType;
let percentDiv = checkbox.parentNode.querySelector('.percentBackground');
percentDiv.style.left = (percents * 100) + '%';
});
}
selectedInCategory(category) {
let tmp = [];
this.querySelectorAll('input[name=' + category + 'Checkbox]:checked')
.forEach(checkbox => tmp.push(checkbox.value));
return tmp;
}
categoryForType(instance_type) {
for (let [key, value] of CATEGORIES.entries()) {
if (value.has(instance_type)) return key;
}
return 'unclassified';
}
createOption(value, text) {
const option = document.createElement('option');
option.value = value;
option.text = text;
return option;
}
populateSelect(id, iterable, labelFn = null, autoselect = null) {
if (labelFn == null) labelFn = e => e;
let index = 0;
for (let [key, value] of iterable) {
index++;
const label = labelFn(key, value, index);
const option = this.createOption(key, label);
if (autoselect === key) {
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 = {__proto__:null};
for (let cat of CATEGORIES.keys()) {
categories[cat] = [];
}
for (let instance_type of this.selectedIsolate.non_empty_instance_types) {
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) {
this.querySelectorAll('input[name=' + category + 'Checkbox]')
.forEach(checkbox => checkbox.checked = false);
this.notifySelectionChanged();
}
selectCategory(category) {
this.querySelectorAll('input[name=' + category + 'Checkbox]')
.forEach(checkbox => checkbox.checked = true);
this.notifySelectionChanged();
}
selectCategoryTopEntries(category) {
// unselect all checkboxes in this category.
this.querySelectorAll('input[name=' + category + 'Checkbox]')
.forEach(checkbox => checkbox.checked = false);
const data = this.selectedData.instance_type_data;
// Get the max values for instance_types in this category
const categoryInstanceTypes = Array.from(CATEGORIES.get(category));
categoryInstanceTypes.filter(each => each in data)
.sort((a,b) => {
return data[b].overall - data[a].overall;
}).slice(0, 10).forEach((category) => {
this.$('#' + category + 'Checkbox').checked = true;
});
this.notifySelectionChanged();
}
createCheckBox(instance_type, category) {
const div = document.createElement('div');
div.classList.add('instanceTypeSelectBox');
const input = document.createElement('input');
div.appendChild(input);
input.type = 'checkbox';
input.name = category + 'Checkbox';
input.checked = 'checked';
input.id = instance_type + 'Checkbox';
input.instance_type = instance_type;
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';
const percentDiv = document.createElement('div');
percentDiv.className = 'percentBackground';
div.appendChild(percentDiv);
return div;
}
exportCurrentSelection(e) {
const data = [];
const selected_data =
this.selectedIsolate.gcs[this.selection.gc][this.selection.data_set]
.instance_type_data;
Object.values(this.selection.categories).forEach(instance_types => {
instance_types.forEach(instance_type => {
data.push([instance_type, selected_data[instance_type].overall / KB]);
});
});
const createInlineContent = arrayOfRows => {
const content = arrayOfRows.reduce(
(accu, rowAsArray) => {return accu + `${rowAsArray.join(',')}\n`},
'');
return `data:text/csv;charset=utf-8,${content}`;
};
const encodedUri = encodeURI(createInlineContent(data));
const link = document.createElement('a');
link.setAttribute('href', encodedUri);
link.setAttribute(
'download',
`heap_objects_data_${this.selection.isolate}_${this.selection.gc}.csv`);
this.shadowRoot.appendChild(link);
link.click();
this.shadowRoot.removeChild(link);
}
});

View File

@ -0,0 +1,13 @@
<!-- 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. -->
<style>
#chart {
width: 100%;
height: 500px;
}
</style>
<div id="container" style="display: none;">
<h2>Timeline</h2>
<div id="chart"></div>
</div>

View File

@ -0,0 +1,224 @@
// 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';
import {
VIEW_BY_INSTANCE_TYPE,
VIEW_BY_INSTANCE_CATEGORY,
VIEW_BY_FIELD_TYPE
} from './details-selection.js';
defineCustomElement('global-timeline', (templateText) =>
class GlobalTimeline extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = templateText;
}
$(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();
}
}
getFieldData() {
const labels = [
{type: 'number', label: 'Time'},
{type: 'number', label: 'Ptr compression benefit'},
{type: 'string', role: 'tooltip'},
{type: 'number', label: 'Embedder fields'},
{type: 'number', label: 'Tagged fields (excl. in-object Smis)'},
{type: 'number', label: 'In-object Smi-only fields'},
{type: 'number', label: 'Other raw fields'},
{type: 'number', label: 'Boxed doubles'},
{type: 'number', label: 'String data'}
];
const chart_data = [labels];
const isolate_data = this.data[this.selection.isolate];
let sum_total = 0;
let sum_ptr_compr_benefit_perc = 0;
let count = 0;
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].field_data;
const data = [];
data.push(gc_data.time * kMillis2Seconds);
const total = data_set.tagged_fields +
data_set.inobject_smi_fields +
data_set.embedder_fields +
data_set.other_raw_fields +
data_set.boxed_double_fields +
data_set.string_data;
const ptr_compr_benefit =
(data_set.inobject_smi_fields + data_set.tagged_fields) / 2;
const ptr_compr_benefit_perc = ptr_compr_benefit / total * 100;
sum_total += total;
sum_ptr_compr_benefit_perc += ptr_compr_benefit_perc;
count++;
const tooltip = "Ptr compression benefit: " +
(ptr_compr_benefit / KB).toFixed(2) + "KB " +
" (" + ptr_compr_benefit_perc.toFixed(2) + "%)";
data.push(ptr_compr_benefit / KB);
data.push(tooltip);
data.push(data_set.embedder_fields / KB);
data.push(data_set.tagged_fields / KB);
data.push(data_set.inobject_smi_fields / KB);
data.push(data_set.other_raw_fields / KB);
data.push(data_set.boxed_double_fields / KB);
data.push(data_set.string_data / KB);
chart_data.push(data);
});
const avg_ptr_compr_benefit_perc =
count ? sum_ptr_compr_benefit_perc / count : 0;
console.log("==================================================");
console.log("= Average ptr compression benefit is " +
avg_ptr_compr_benefit_perc.toFixed(2) + "%");
console.log("= Average V8 heap size " +
(sum_total / count / KB).toFixed(2) + " KB");
console.log("==================================================");
return chart_data;
}
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 * kMillis2Seconds);
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 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 * kMillis2Seconds);
instance_types.forEach(instance_type => {
data.push(data_set[instance_type].overall / KB);
});
chart_data.push(data);
});
return chart_data;
}
getChartData() {
switch (this.selection.data_view) {
case VIEW_BY_FIELD_TYPE:
return this.getFieldData();
case VIEW_BY_INSTANCE_CATEGORY:
return this.getCategoryData();
case VIEW_BY_INSTANCE_TYPE:
default:
return this.getInstanceTypeData();
}
}
getChartOptions() {
const options = {
isStacked: true,
hAxis: {
format: '###.##s',
title: 'Time [s]',
},
vAxis: {
format: '#,###KB',
title: 'Memory consumption [KBytes]'
},
chartArea: {left:100, width: '85%', height: '70%'},
legend: {position: 'top', maxLines: '1'},
pointsVisible: true,
pointSize: 5,
explorer: {},
};
switch (this.selection.data_view) {
case VIEW_BY_FIELD_TYPE:
// Overlay pointer compression benefit on top of the graph
return Object.assign(options, {
series: {0: {type: 'line', lineDashStyle: [13, 13]}},
});
case VIEW_BY_INSTANCE_CATEGORY:
case VIEW_BY_INSTANCE_TYPE:
default:
return options;
}
}
drawChart() {
setTimeout(() => this._drawChart(), 10);
}
_drawChart() {
console.assert(this.data, 'invalid data');
console.assert(this.selection, 'invalid selection');
const chart_data = this.getChartData();
const data = google.visualization.arrayToDataTable(chart_data);
const options = this.getChartOptions();
const chart = new google.visualization.AreaChart(this.$('#chart'));
this.show();
chart.draw(data, google.charts.Line.convertOptions(options));
}
});

30
deps/v8/tools/heap-stats/helper.js vendored Normal file
View File

@ -0,0 +1,30 @@
// 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.
const KB = 1024;
const MB = KB * KB;
const GB = MB * KB;
const kMillis2Seconds = 1 / 1000;
function formatBytes(bytes) {
const units = ['B', 'KiB', 'MiB', 'GiB'];
const divisor = 1024;
let index = 0;
while (index < units.length && bytes >= divisor) {
index++;
bytes /= divisor;
}
return bytes.toFixed(2) + units[index];
}
function formatSeconds(millis) {
return (millis * kMillis2Seconds).toFixed(2) + 's';
}
function defineCustomElement(name, generator) {
let htmlTemplatePath = name + '-template.html';
fetch(htmlTemplatePath)
.then(stream => stream.text())
.then(templateText => customElements.define(name, generator(templateText)));
}

View File

@ -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. -->
<style>
#chart {
width: 100%;
height: 800px;
}
</style>
<div id="container" style="display: none;">
<h2>Details</h2>
<ul>
<li><span id="overall"></span></li>
</ul>
<div id="chart"></div>
</div>

View File

@ -0,0 +1,191 @@
// 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';
import {
VIEW_BY_INSTANCE_TYPE,
VIEW_BY_INSTANCE_CATEGORY,
VIEW_BY_FIELD_TYPE
} from './details-selection.js';
defineCustomElement('histogram-viewer', (templateText) =>
class HistogramViewer extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = templateText;
}
$(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 &&
(this.selection.data_view === VIEW_BY_INSTANCE_CATEGORY ||
this.selection.data_view === VIEW_BY_INSTANCE_TYPE);
;
}
hide() {
this.$('#container').style.display = 'none';
}
show() {
this.$('#container').style.display = 'block';
}
getOverallValue() {
switch (this.selection.data_view) {
case VIEW_BY_FIELD_TYPE:
return NaN;
case VIEW_BY_INSTANCE_CATEGORY:
return this.getPropertyForCategory('overall');
case VIEW_BY_INSTANCE_TYPE:
default:
return this.getPropertyForInstanceTypes('overall');
}
}
stateChanged() {
if (this.isValid()) {
const overall_bytes = this.getOverallValue();
this.$('#overall').innerHTML = `Overall: ${overall_bytes / KB} KB`;
this.drawChart();
} else {
this.hide();
}
}
get selectedData() {
console.assert(this.data, 'invalid data');
console.assert(this.selection, 'invalid selection');
return this.data[this.selection.isolate]
.gcs[this.selection.gc][this.selection.data_set];
}
get selectedInstanceTypes() {
console.assert(this.selection, 'invalid selection');
return Object.values(this.selection.categories)
.reduce((accu, current) => accu.concat(current), []);
}
getPropertyForCategory(property) {
return Object.values(this.selection.categories)
.reduce(
(outer_accu, instance_types) => outer_accu +
instance_types.reduce(
(inner_accu, instance_type) => inner_accu +
this.selectedData
.instance_type_data[instance_type][property],
0),
0);
}
getPropertyForInstanceTypes(property) {
return this.selectedInstanceTypes.reduce(
(accu, instance_type) => accu +
this.selectedData.instance_type_data[instance_type][property],
0);
}
formatBytes(bytes) {
const units = ['B', 'KiB', 'MiB'];
const divisor = 1024;
let index = 0;
while (index < units.length && bytes >= divisor) {
index++;
bytes /= divisor;
}
return bytes + units[index];
}
getCategoryData() {
const labels = [
'Bucket',
...Object.keys(this.selection.categories)
.map(k => this.selection.category_names.get(k))
];
const data = this.selectedData.bucket_sizes.map(
(bucket_size, index) =>
[`<${this.formatBytes(bucket_size)}`,
...Object.values(this.selection.categories)
.map(
instance_types =>
instance_types
.map(
instance_type =>
this.selectedData
.instance_type_data[instance_type]
.histogram[index])
.reduce((accu, current) => accu + current, 0))]);
// Adjust last histogram bucket label.
data[data.length - 1][0] = 'rest';
return [labels, ...data];
}
getInstanceTypeData() {
const instance_types = this.selectedInstanceTypes;
const labels = ['Bucket', ...instance_types];
const data = this.selectedData.bucket_sizes.map(
(bucket_size, index) =>
[`<${bucket_size}`,
...instance_types.map(
instance_type =>
this.selectedData.instance_type_data[instance_type]
.histogram[index])]);
// Adjust last histogram bucket label.
data[data.length - 1][0] = 'rest';
return [labels, ...data];
}
getChartData() {
switch (this.selection.data_view) {
case VIEW_BY_FIELD_TYPE:
return this.getFieldData();
case VIEW_BY_INSTANCE_CATEGORY:
return this.getCategoryData();
case VIEW_BY_INSTANCE_TYPE:
default:
return this.getInstanceTypeData();
}
}
drawChart() {
const chart_data = this.getChartData();
const data = google.visualization.arrayToDataTable(chart_data);
const options = {
legend: {position: 'top', maxLines: '1'},
chartArea: {width: '85%', height: '85%'},
bar: {groupWidth: '80%'},
hAxis: {
title: 'Count',
minValue: 0
},
explorer: {},
};
const chart = new google.visualization.BarChart(this.$('#chart'));
this.show();
chart.draw(data, options);
}
});

111
deps/v8/tools/heap-stats/index.html vendored Normal file
View File

@ -0,0 +1,111 @@
<!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>
<!-- This will be overwritten by the v8.dev/tools exporter -->
<!-- ANALYTICS_PLACEHOLDER -->
<meta charset="UTF-8">
<title>V8 Heap Statistics</title>
<link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet'>
<script
src="https://www.gstatic.com/charts/loader.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/pako/1.0.6/pako_inflate.min.js"
integrity="sha256-N1z6ddQzX83fjw8v7uSNe7/MgOmMKdwFUv1+AJMDqNM="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/oboe.js/2.1.5/oboe-browser.min.js"
crossorigin="anonymous"></script>
<script src="helper.js"></script>
<script type="module" src="details-selection.js"></script>
<script type="module" src="global-timeline.js"></script>
<script type="module" src="histogram-viewer.js"></script>
<script type="module" src="trace-file-reader.js"></script>
<style>
body {
font-family: 'Roboto', sans-serif;
margin-left: 5%;
margin-right: 5%;
}
</style>
<script>
'use strict';
google.charts.load('current', {'packages':['line', 'corechart', 'bar']});
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;
$('#histogram-viewer').selection = state.selection;
$('#histogram-viewer').data = state.data;
$('#details-selection').data = state.data;
}
function globalSelectionChangedA(e) {
state.selection = e.detail;
console.log(state.selection);
$('#global-timeline').selection = state.selection;
$('#histogram-viewer').selection = state.selection;
}
</script>
</head>
<body>
<h1>V8 Heap Statistics</h1>
<trace-file-reader onchange="globalDataChanged(event)"></trace-file-reader>
<details-selection id="details-selection" onchange="globalSelectionChangedA(event)"></details-selection>
<global-timeline id="global-timeline"></global-timeline>
<histogram-viewer id="histogram-viewer"></histogram-viewer>
<p>Visualize object statistics that have been gathered using</p>
<ul>
<li>Use <code>--trace-gc-object-stats</code> for V8 and load the contents of stdout</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>disabled-by-default-v8.gc_stats</code> and directly load the
results.html or trace.json.gzip file.
</li>
</ul>
Additional information:
<ul>
<li>
You only get a data point on major GCs. You can enforce this by
using the <code>--gc-global</code> V8 flag.
</li>
<li>
For more frequent data points you can also the
<code>--gc-interval=$AFTER_N_ALLOCATIONS</code> V8.
</li>
<li>
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>.
</li>
<ul>
</body>
</html>

105
deps/v8/tools/heap-stats/model.js vendored Normal file
View File

@ -0,0 +1,105 @@
// 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';
export class Isolate {
constructor(address) {
this.address = address;
this.start = null;
this.end = null;
this.samples = Object.create(null);
this.non_empty_instance_types = new Set();
this.gcs = Object.create(null);
this.zonetags = [];
this.samples = {zone: {}};
this.data_sets = new Set();
this.peakMemory = 0;
// Maps instance_types to their max memory consumption over all gcs.
this.instanceTypePeakMemory = Object.create(null);
// Peak memory consumed by any single instance type.
this.singleInstanceTypePeakMemory = 0;
}
finalize() {
Object.values(this.gcs).forEach(gc => this.finalizeGC(gc));
this.sortInstanceTypePeakMemory();
}
getLabel() {
let label = `${this.address}: gc=#${Object.keys(this.gcs).length}`;
label += ` peak=${formatBytes(this.peakMemory)}`
return label;
}
finalizeGC(gc_data) {
this.data_sets.forEach(key => this.finalizeDataSet(gc_data[key]));
if (!('live' in gc_data)) return;
let liveData = gc_data.live;
this.peakMemory = Math.max(this.peakMemory, liveData.overall);
let data = liveData.instance_type_data;
for (let name in data) {
let prev = this.instanceTypePeakMemory[name] || 0;
this.instanceTypePeakMemory[name] = Math.max(prev, data[name].overall);
}
}
finalizeDataSet(data_set) {
// Create a ranked instance type array that sorts instance types by
// memory size (overall).
let data = data_set.instance_type_data;
let ranked_instance_types =
[...data_set.non_empty_instance_types].sort((a, b) => {
return data[a].overall - data[b].overall;
});
// Reassemble the instance_type list sorted by size.
let sorted_data = Object.create(null);
let max = 0;
ranked_instance_types.forEach((name) => {
let entry = sorted_data[name] = data[name];
max = Math.max(max, entry.overall);
});
data_set.instance_type_data = data;
data_set.singleInstancePeakMemory = max;
Object.entries(data_set.instance_type_data).forEach(([name, entry]) => {
this.checkHistogram(
name, entry, data_set.bucket_sizes, 'histogram', ' overall');
this.checkHistogram(
name, entry, data_set.bucket_sizes, 'over_allocated_histogram',
' over_allocated');
});
}
// Check that a lower bound for histogram memory does not exceed the
// overall counter.
checkHistogram(type, entry, bucket_sizes, histogram, overallProperty) {
let sum = 0;
for (let i = 1; i < entry[histogram].length; i++) {
sum += entry[histogram][i] * bucket_sizes[i - 1];
}
const overall = entry[overallProperty];
if (sum >= overall) {
console.error(
`${type}: sum('${histogram}') > overall (${sum} > ${overall})`);
}
}
sortInstanceTypePeakMemory() {
let entries = Object.entries(this.instanceTypePeakMemory);
entries.sort((a, b) => {return b[1] - a[1]});
this.instanceTypePeakMemory = Object.create(null);
let max = 0;
for (let [key, value] of entries) {
this.instanceTypePeakMemory[key] = value;
max = Math.max(max, value);
}
this.singleInstanceTypePeakMemory = max;
}
getInstanceTypePeakMemory(type) {
if (!(type in this.instanceTypePeakMemory)) return 0;
return this.instanceTypePeakMemory[type];
}
}

View File

@ -0,0 +1,81 @@
<!-- 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. -->
<style>
#fileReader {
width: 100%;
height: 100px;
line-height: 100px;
text-align: center;
border: solid 1px #000000;
border-radius: 5px;
cursor: pointer;
transition: all 0.5s ease-in-out;
}
#fileReader.done {
height: 20px;
line-height: 20px;
}
#fileReader:hover {
background-color: #e0edfe ;
}
.loading #fileReader {
cursor: wait;
}
#fileReader > input {
display: none;
}
#loader {
display: none;
}
.loading #loader {
display: block;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.5);
}
#spinner {
position: absolute;
width: 100px;
height: 100px;
top: 40%;
left: 50%;
margin-left: -50px;
border: 30px solid #000;
border-top: 30px solid #36E;
border-radius: 50%;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<section id="fileReaderSection">
<div id="fileReader" tabindex=1 >
<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>
<div id="loader">
<div id="spinner"></div>
</div>
</section>

View File

@ -0,0 +1,328 @@
// 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';
import {Isolate} from './model.js';
defineCustomElement('trace-file-reader', (templateText) =>
class TraceFileReader extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = templateText;
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));
this.$('#fileReader').addEventListener('keydown', e => this.handleKeyEvent(e));
}
$(id) {
return this.shadowRoot.querySelector(id);
}
get section() {
return this.$('#fileReaderSection');
}
updateLabel(text) {
this.$('#label').innerText = text;
}
handleKeyEvent(event) {
if (event.key == "Enter") this.handleClick(event);
}
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() {
this.$('#fileReader').focus();
}
readFile(file) {
if (!file) {
this.updateLabel('Failed to load file.');
return;
}
this.$('#fileReader').blur();
this.section.className = 'loading';
const reader = new FileReader();
if (['application/gzip', 'application/x-gzip'].includes(file.type)) {
reader.onload = (e) => {
try {
const textResult = pako.inflate(e.target.result, {to: 'string'});
this.processRawText(file, textResult);
this.section.className = 'success';
this.$('#fileReader').classList.add('done');
} catch (err) {
console.error(err);
this.section.className = 'failure';
}
};
// Delay the loading a bit to allow for CSS animations to happen.
setTimeout(() => reader.readAsArrayBuffer(file), 0);
} else if (file.type == 'text/html') {
// try extracting the data from a results.html file
reader.onload = (e) => {
try {
let html = document.createElement('html');
html.innerHTML = e.target.result;
for (let dataScript of html.querySelectorAll('#viewer-data')) {
const base64 = dataScript.innerText.slice(1,-1);
const binary = globalThis.atob(base64);
const textResult = pako.inflate(binary, {to: 'string'});
this.processRawText(file, textResult);
}
this.section.className = 'success';
this.$('#fileReader').classList.add('done');
} catch (err) {
console.error(err);
this.section.className = 'failure';
}
};
// Delay the loading a bit to allow for CSS animations to happen.
setTimeout(() => reader.readAsText(file), 0);
} else {
reader.onload = (e) => {
try {
this.processRawText(file, e.target.result);
this.section.className = 'success';
this.$('#fileReader').classList.add('done');
} catch (err) {
console.error(err);
this.section.className = 'failure';
}
};
// Delay the loading a bit to allow for CSS animations to happen.
setTimeout(() => reader.readAsText(file), 0);
}
}
processRawText(file, result) {
let return_data;
if (result.includes('V8.GC_Objects_Stats')) {
return_data = this.createModelFromChromeTraceFile(result);
} else {
let contents = result.split('\n');
return_data = this.createModelFromV8TraceFile(contents);
}
this.extendAndSanitizeModel(return_data);
this.updateLabel('Finished loading \'' + file.name + '\'.');
this.dispatchEvent(new CustomEvent(
'change', {bubbles: true, composed: true, detail: return_data}));
}
createOrUpdateEntryIfNeeded(data, entry) {
console.assert(entry.isolate, 'entry should have an isolate');
if (!(entry.isolate in data)) {
data[entry.isolate] = new Isolate(entry.isolate);
}
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, 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);
}
}
addFieldTypeData(data, isolate, gc_id, data_set, tagged_fields,
inobject_smi_fields, embedder_fields, unboxed_double_fields,
boxed_double_fields, string_data, other_raw_fields) {
data[isolate].gcs[gc_id][data_set].field_data = {
tagged_fields,
inobject_smi_fields,
embedder_fields,
unboxed_double_fields,
boxed_double_fields,
string_data,
other_raw_fields
};
}
addInstanceTypeData(data, isolate, gc_id, data_set, instance_type, entry) {
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) {
const checkNonNegativeProperty = (obj, property) => {
console.assert(obj[property] >= 0, 'negative property', obj, property);
};
Object.values(data).forEach(isolate => isolate.finalize());
}
createModelFromChromeTraceFile(contents) {
const data = Object.create(null); // Final data container.
const parseOneGCEvent = (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, entry);
this.createDatasetIfNeeded(data, entry, data_set);
const isolate = entry.isolate;
const time = entry.time;
const gc_id = entry.id;
data[isolate].gcs[gc_id].time = time;
const field_data = entry.field_data;
this.addFieldTypeData(data, isolate, gc_id, data_set,
field_data.tagged_fields,
field_data.inobject_smi_fields,
field_data.embedder_fields,
field_data.unboxed_double_fields,
field_data.boxed_double_fields,
field_data.string_data,
field_data.other_raw_fields);
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, isolate, gc_id, data_set, instance_type, value);
}
} catch (e) {
console.error('Unable to parse data set entry', e);
}
});
};
console.log(`Processing log as chrome trace file.`);
try {
let gc_events_filter = (event) => {
if (event.name == 'V8.GC_Objects_Stats') {
parseOneGCEvent(event.args);
}
return oboe.drop;
};
let oboe_stream = oboe();
// Trace files support two formats.
oboe_stream
// 1) {traceEvents: [ data ]}
.node('traceEvents.*', gc_events_filter)
// 2) [ data ]
.node('!.*', gc_events_filter)
.fail(() => { throw new Error("Trace data parse failed!"); });
oboe_stream.emit('data', contents);
} catch (e) {
console.error('Unable to parse chrome trace file.', e);
}
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.
for (var entry of contents) {
if (entry === null || entry.type === undefined) {
continue;
}
if (entry.type === 'zone') {
this.createOrUpdateEntryIfNeeded(data, 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, entry);
data[entry.isolate].zonetags.push(
Object.assign({opening: entry.type === 'zonecreation'}, entry));
} else if (entry.type === 'gc_descriptor') {
this.createOrUpdateEntryIfNeeded(data, 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 === 'field_data') {
this.createOrUpdateEntryIfNeeded(data, entry);
this.createDatasetIfNeeded(data, entry, entry.key);
this.addFieldTypeData(data, entry.isolate, entry.id, entry.key,
entry.tagged_fields, entry.embedder_fields, entry.inobject_smi_fields,
entry.unboxed_double_fields, entry.boxed_double_fields,
entry.string_data, entry.other_raw_fields);
} else if (entry.type === 'instance_type_data') {
if (entry.id in data[entry.isolate].gcs) {
this.createOrUpdateEntryIfNeeded(data, entry);
this.createDatasetIfNeeded(data, entry, entry.key);
this.addInstanceTypeData(
data, 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, entry);
this.createDatasetIfNeeded(data, entry, entry.key);
data[entry.isolate].gcs[entry.id][entry.key].bucket_sizes =
entry.sizes;
}
} else {
console.log('Unknown entry type: ' + entry.type);
}
}
return data;
}
});