v8/tools/heap-stats/trace-file-reader.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

306 lines
11 KiB
JavaScript
Raw Normal View History

// 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 reader = new FileReader();
if (file.type === 'application/gzip') {
reader.onload = (e) => {
try {
const textResult = pako.inflate(e.target.result, {to: 'string'});
this.processRawText(file, textResult);
} catch (err) {
console.error(err);
}
};
reader.readAsArrayBuffer(file);
} else {
reader.onload = (e) => this.processRawText(file, e.target.result);
reader.readAsText(file);
}
}
processRawText(file, result) {
let contents = result.split('\n');
const return_data = (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}));
}
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];
// 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;
});
// Check that a lower bound for histogram memory does not exceed the
// overall counter.
const 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})`);
}
};
Object.entries(data_set.instance_type_data).forEach(([
name, entry
]) => {
checkHistogram(
name, entry, data_set.bucket_sizes, 'histogram', ' overall');
checkHistogram(
name, entry, data_set.bucket_sizes, 'over_allocated_histogram',
' over_allocated');
});
}
}
}
}
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.error('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);