summaryrefslogtreecommitdiff
path: root/chromium/v8/tools/zone-stats/trace-file-reader.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/v8/tools/zone-stats/trace-file-reader.js')
-rw-r--r--chromium/v8/tools/zone-stats/trace-file-reader.js294
1 files changed, 294 insertions, 0 deletions
diff --git a/chromium/v8/tools/zone-stats/trace-file-reader.js b/chromium/v8/tools/zone-stats/trace-file-reader.js
new file mode 100644
index 00000000000..6decfa8f273
--- /dev/null
+++ b/chromium/v8/tools/zone-stats/trace-file-reader.js
@@ -0,0 +1,294 @@
+// Copyright 2020 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 {
+ // Decode data as strings of 64Kb chunks. Bigger chunks may cause
+ // parsing failures in Oboe.js.
+ const chunkedInflate = new pako.Inflate(
+ {to: 'string', chunkSize: 65536}
+ );
+ let processingState = undefined;
+ chunkedInflate.onData = (chunk) => {
+ if (processingState === undefined) {
+ processingState = this.startProcessing(file, chunk);
+ } else {
+ processingState.processChunk(chunk);
+ }
+ };
+ chunkedInflate.onEnd = () => {
+ if (processingState !== undefined) {
+ const result_data = processingState.endProcessing();
+ this.processLoadedData(file, result_data);
+ }
+ };
+ console.log("======");
+ const textResult = chunkedInflate.push(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.readAsArrayBuffer(file), 0);
+ } else {
+ reader.onload = (e) => {
+ try {
+ // Process the whole file in at once.
+ const processingState = this.startProcessing(file, e.target.result);
+ const dataModel = processingState.endProcessing();
+ this.processLoadedData(file, dataModel);
+
+ 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);
+ }
+ }
+
+ processLoadedData(file, dataModel) {
+ console.log("Trace file parsed successfully.");
+ this.extendAndSanitizeModel(dataModel);
+ this.updateLabel('Finished loading \'' + file.name + '\'.');
+ this.dispatchEvent(new CustomEvent(
+ 'change', {bubbles: true, composed: true, detail: dataModel}));
+ }
+
+ createOrUpdateEntryIfNeeded(data, entry) {
+ console.assert(entry.isolate, 'entry should have an isolate');
+ if (!(entry.isolate in data)) {
+ data[entry.isolate] = new Isolate(entry.isolate);
+ }
+ }
+
+ extendAndSanitizeModel(data) {
+ const checkNonNegativeProperty = (obj, property) => {
+ console.assert(obj[property] >= 0, 'negative property', obj, property);
+ };
+
+ Object.values(data).forEach(isolate => isolate.finalize());
+ }
+
+ processOneZoneStatsEntry(data, entry_stats) {
+ this.createOrUpdateEntryIfNeeded(data, entry_stats);
+ const isolate_data = data[entry_stats.isolate];
+ let zones = undefined;
+ const entry_zones = entry_stats.zones;
+ if (entry_zones !== undefined) {
+ zones = new Map();
+ entry_zones.forEach(zone => {
+ // There might be multiple occurrences of the same zone in the set,
+ // combine numbers in this case.
+ const existing_zone_stats = zones.get(zone.name);
+ if (existing_zone_stats !== undefined) {
+ existing_zone_stats.allocated += zone.allocated;
+ existing_zone_stats.used += zone.used;
+ } else {
+ zones.set(zone.name, {allocated: zone.allocated, used: zone.used});
+ }
+ });
+ }
+ const time = entry_stats.time;
+ const sample = {
+ time: time,
+ allocated: entry_stats.allocated,
+ used: entry_stats.used,
+ zones: zones
+ };
+ isolate_data.samples.set(time, sample);
+ }
+
+ startProcessing(file, chunk) {
+ const isV8TraceFile = chunk.includes('v8-zone-trace');
+ const processingState =
+ isV8TraceFile ? this.startProcessingAsV8TraceFile(file)
+ : this.startProcessingAsChromeTraceFile(file);
+
+ processingState.processChunk(chunk);
+ return processingState;
+ }
+
+ startProcessingAsChromeTraceFile(file) {
+ console.log(`Processing log as chrome trace file.`);
+ const data = Object.create(null); // Final data container.
+ const parseOneZoneEvent = (actual_data) => {
+ if ('stats' in actual_data) {
+ try {
+ const entry_stats = JSON.parse(actual_data.stats);
+ this.processOneZoneStatsEntry(data, entry_stats);
+ } catch (e) {
+ console.error('Unable to parse data set entry', e);
+ }
+ }
+ };
+ const zone_events_filter = (event) => {
+ if (event.name == 'V8.Zone_Stats') {
+ parseOneZoneEvent(event.args);
+ }
+ return oboe.drop;
+ };
+
+ const oboe_stream = oboe();
+ // Trace files support two formats.
+ oboe_stream
+ // 1) {traceEvents: [ data ]}
+ .node('traceEvents.*', zone_events_filter)
+ // 2) [ data ]
+ .node('!.*', zone_events_filter)
+ .fail((errorReport) => {
+ throw new Error("Trace data parse failed: " + errorReport.thrown);
+ });
+
+ let failed = false;
+
+ const processingState = {
+ file: file,
+
+ processChunk(chunk) {
+ if (failed) return false;
+ try {
+ oboe_stream.emit('data', chunk);
+ return true;
+ } catch (e) {
+ console.error('Unable to parse chrome trace file.', e);
+ failed = true;
+ return false;
+ }
+ },
+
+ endProcessing() {
+ if (failed) return null;
+ oboe_stream.emit('end');
+ return data;
+ },
+ };
+ return processingState;
+ }
+
+ startProcessingAsV8TraceFile(file) {
+ console.log('Processing log as V8 trace file.');
+ const data = Object.create(null); // Final data container.
+
+ const processOneLine = (line) => {
+ try {
+ // Strip away a potentially present adb logcat prefix.
+ line = line.replace(/^I\/v8\s*\(\d+\):\s+/g, '');
+
+ const entry = JSON.parse(line);
+ if (entry === null || entry.type === undefined) return;
+ if ((entry.type === 'v8-zone-trace') && ('stats' in entry)) {
+ const entry_stats = entry.stats;
+ this.processOneZoneStatsEntry(data, entry_stats);
+ } else {
+ console.log('Unknown entry type: ' + entry.type);
+ }
+ } catch (e) {
+ console.log('Unable to parse line: \'' + line + '\' (' + e + ')');
+ }
+ };
+
+ let prev_chunk_leftover = "";
+
+ const processingState = {
+ file: file,
+
+ processChunk(chunk) {
+ const contents = chunk.split('\n');
+ const last_line = contents.pop();
+ const linesCount = contents.length;
+ if (linesCount == 0) {
+ // There was only one line in the chunk, it may still be unfinished.
+ prev_chunk_leftover += last_line;
+ } else {
+ contents[0] = prev_chunk_leftover + contents[0];
+ prev_chunk_leftover = last_line;
+ for (let line of contents) {
+ processOneLine(line);
+ }
+ }
+ return true;
+ },
+
+ endProcessing() {
+ if (prev_chunk_leftover.length > 0) {
+ processOneLine(prev_chunk_leftover);
+ prev_chunk_leftover = "";
+ }
+ return data;
+ },
+ };
+ return processingState;
+ }
+});