959d169c08
Change-Id: I913e36afd76fe0f212e8c0c9b97e5ac52b2342d1 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3437045 Reviewed-by: Victor Gomes <victorgomes@chromium.org> Commit-Queue: Camillo Bruni <cbruni@chromium.org> Cr-Commit-Position: refs/heads/main@{#78926}
329 lines
11 KiB
JavaScript
329 lines
11 KiB
JavaScript
// 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;
|
|
}
|
|
});
|