diff options
Diffstat (limited to 'chromium/v8/tools/zone-stats/trace-file-reader.js')
-rw-r--r-- | chromium/v8/tools/zone-stats/trace-file-reader.js | 294 |
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; + } +}); |