summaryrefslogtreecommitdiff
path: root/deps/v8/tools/heap-stats/trace-file-reader.js
diff options
context:
space:
mode:
Diffstat (limited to 'deps/v8/tools/heap-stats/trace-file-reader.js')
-rw-r--r--deps/v8/tools/heap-stats/trace-file-reader.js300
1 files changed, 300 insertions, 0 deletions
diff --git a/deps/v8/tools/heap-stats/trace-file-reader.js b/deps/v8/tools/heap-stats/trace-file-reader.js
new file mode 100644
index 0000000000..59825fe514
--- /dev/null
+++ b/deps/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);