diff options
Diffstat (limited to 'deps/v8/tools/heap-stats/trace-file-reader.js')
-rw-r--r-- | deps/v8/tools/heap-stats/trace-file-reader.js | 300 |
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); |