| // 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. |
| |
| import {LogReader, parseString, parseVarArgs} from '../logreader.mjs'; |
| import {Profile} from '../profile.mjs'; |
| |
| import {DeoptLogEntry} from './log/deopt.mjs'; |
| import {IcLogEntry} from './log/ic.mjs'; |
| import {Edge, MapLogEntry} from './log/map.mjs'; |
| import {Timeline} from './timeline.mjs'; |
| |
| // =========================================================================== |
| |
| export class Processor extends LogReader { |
| _profile = new Profile(); |
| _mapTimeline = new Timeline(); |
| _icTimeline = new Timeline(); |
| _deoptTimeline = new Timeline(); |
| _formatPCRegexp = /(.*):[0-9]+:[0-9]+$/; |
| MAJOR_VERSION = 7; |
| MINOR_VERSION = 6; |
| constructor(logString) { |
| super(); |
| this.propertyICParser = [ |
| parseInt, parseInt, parseInt, parseInt, parseString, parseString, |
| parseString, parseString, parseString, parseString |
| ]; |
| this.dispatchTable_ = { |
| __proto__: null, |
| 'code-creation': { |
| parsers: [ |
| parseString, parseInt, parseInt, parseInt, parseInt, parseString, |
| parseVarArgs |
| ], |
| processor: this.processCodeCreation |
| }, |
| 'code-deopt': { |
| parsers: [ |
| parseInt, parseInt, parseInt, parseInt, parseInt, parseString, |
| parseString, parseString |
| ], |
| processor: this.processCodeDeopt |
| }, |
| 'v8-version': { |
| parsers: [ |
| parseInt, |
| parseInt, |
| ], |
| processor: this.processV8Version |
| }, |
| 'script-source': { |
| parsers: [parseInt, parseString, parseString], |
| processor: this.processScriptSource |
| }, |
| 'code-move': |
| {parsers: [parseInt, parseInt], processor: this.processCodeMove}, |
| 'code-delete': {parsers: [parseInt], processor: this.processCodeDelete}, |
| 'sfi-move': |
| {parsers: [parseInt, parseInt], processor: this.processFunctionMove}, |
| 'map-create': |
| {parsers: [parseInt, parseString], processor: this.processMapCreate}, |
| 'map': { |
| parsers: [ |
| parseString, parseInt, parseString, parseString, parseInt, parseInt, |
| parseInt, parseString, parseString |
| ], |
| processor: this.processMap |
| }, |
| 'map-details': { |
| parsers: [parseInt, parseString, parseString], |
| processor: this.processMapDetails |
| }, |
| 'LoadGlobalIC': { |
| parsers: this.propertyICParser, |
| processor: this.processPropertyIC.bind(this, 'LoadGlobalIC') |
| }, |
| 'StoreGlobalIC': { |
| parsers: this.propertyICParser, |
| processor: this.processPropertyIC.bind(this, 'StoreGlobalIC') |
| }, |
| 'LoadIC': { |
| parsers: this.propertyICParser, |
| processor: this.processPropertyIC.bind(this, 'LoadIC') |
| }, |
| 'StoreIC': { |
| parsers: this.propertyICParser, |
| processor: this.processPropertyIC.bind(this, 'StoreIC') |
| }, |
| 'KeyedLoadIC': { |
| parsers: this.propertyICParser, |
| processor: this.processPropertyIC.bind(this, 'KeyedLoadIC') |
| }, |
| 'KeyedStoreIC': { |
| parsers: this.propertyICParser, |
| processor: this.processPropertyIC.bind(this, 'KeyedStoreIC') |
| }, |
| 'StoreInArrayLiteralIC': { |
| parsers: this.propertyICParser, |
| processor: this.processPropertyIC.bind(this, 'StoreInArrayLiteralIC') |
| }, |
| }; |
| if (logString) this.processString(logString); |
| } |
| |
| printError(str) { |
| console.error(str); |
| throw str |
| } |
| |
| processString(string) { |
| let end = string.length; |
| let current = 0; |
| let next = 0; |
| let line; |
| let i = 0; |
| let entry; |
| try { |
| while (current < end) { |
| next = string.indexOf('\n', current); |
| if (next === -1) break; |
| i++; |
| line = string.substring(current, next); |
| current = next + 1; |
| this.processLogLine(line); |
| } |
| } catch (e) { |
| console.error(`Error occurred during parsing, trying to continue: ${e}`); |
| } |
| this.finalize(); |
| } |
| |
| processLogFile(fileName) { |
| this.collectEntries = true; |
| this.lastLogFileName_ = fileName; |
| let i = 1; |
| let line; |
| try { |
| while (line = readline()) { |
| this.processLogLine(line); |
| i++; |
| } |
| } catch (e) { |
| console.error( |
| `Error occurred during parsing line ${i}` + |
| ', trying to continue: ' + e); |
| } |
| this.finalize(); |
| } |
| |
| finalize() { |
| // TODO(cbruni): print stats; |
| this._mapTimeline.transitions = new Map(); |
| let id = 0; |
| this._mapTimeline.forEach(map => { |
| if (map.isRoot()) id = map.finalizeRootMap(id + 1); |
| if (map.edge && map.edge.name) { |
| const edge = map.edge; |
| const list = this._mapTimeline.transitions.get(edge.name); |
| if (list === undefined) { |
| this._mapTimeline.transitions.set(edge.name, [edge]); |
| } else { |
| list.push(edge); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Parser for dynamic code optimization state. |
| */ |
| parseState(s) { |
| switch (s) { |
| case '': |
| return Profile.CodeState.COMPILED; |
| case '~': |
| return Profile.CodeState.OPTIMIZABLE; |
| case '*': |
| return Profile.CodeState.OPTIMIZED; |
| } |
| throw new Error(`unknown code state: ${s}`); |
| } |
| |
| processCodeCreation(type, kind, timestamp, start, size, name, maybe_func) { |
| if (maybe_func.length) { |
| const funcAddr = parseInt(maybe_func[0]); |
| const state = this.parseState(maybe_func[1]); |
| this._profile.addFuncCode( |
| type, name, timestamp, start, size, funcAddr, state); |
| } else { |
| this._profile.addCode(type, name, timestamp, start, size); |
| } |
| } |
| |
| processCodeDeopt( |
| timestamp, codeSize, instructionStart, inliningId, scriptOffset, |
| deoptKind, deoptLocation, deoptReason) { |
| this._deoptTimeline.push(new DeoptLogEntry(deoptKind, timestamp)); |
| } |
| |
| processV8Version(majorVersion, minorVersion) { |
| if ((majorVersion == this.MAJOR_VERSION && |
| minorVersion <= this.MINOR_VERSION) || |
| (majorVersion < this.MAJOR_VERSION)) { |
| window.alert( |
| `Unsupported version ${majorVersion}.${minorVersion}. \n` + |
| `Please use the matching tool for given the V8 version.`); |
| } |
| } |
| |
| processScriptSource(scriptId, url, source) { |
| this._profile.addScriptSource(scriptId, url, source); |
| } |
| |
| processCodeMove(from, to) { |
| this._profile.moveCode(from, to); |
| } |
| |
| processCodeDelete(start) { |
| this._profile.deleteCode(start); |
| } |
| |
| processFunctionMove(from, to) { |
| this._profile.moveFunc(from, to); |
| } |
| |
| formatName(entry) { |
| if (!entry) return '<unknown>'; |
| let name = entry.func.getName(); |
| let re = /(.*):[0-9]+:[0-9]+$/; |
| let array = re.exec(name); |
| if (!array) return name; |
| return entry.getState() + array[1]; |
| } |
| |
| processPropertyIC( |
| type, pc, time, line, column, old_state, new_state, map, key, modifier, |
| slow_reason) { |
| let fnName = this.functionName(pc); |
| let parts = fnName.split(' '); |
| let fileName = parts[parts.length - 1]; |
| let script = this.getScript(fileName); |
| // TODO: Use SourcePosition here directly |
| let entry = new IcLogEntry( |
| type, fnName, time, line, column, key, old_state, new_state, map, |
| slow_reason, script, modifier); |
| if (script) { |
| entry.sourcePosition = script.addSourcePosition(line, column, entry); |
| } |
| this._icTimeline.push(entry); |
| } |
| |
| functionName(pc) { |
| let entry = this._profile.findEntry(pc); |
| return this.formatName(entry); |
| } |
| formatPC(pc, line, column) { |
| let entry = this._profile.findEntry(pc); |
| if (!entry) return '<unknown>' |
| if (entry.type === 'Builtin') { |
| return entry.name; |
| } |
| let name = entry.func.getName(); |
| let array = this._formatPCRegexp.exec(name); |
| if (array === null) { |
| entry = name; |
| } else { |
| entry = entry.getState() + array[1]; |
| } |
| return entry + ':' + line + ':' + column; |
| } |
| |
| processFileName(filePositionLine) { |
| if (!filePositionLine.includes(' ')) return; |
| // Try to handle urls with file positions: https://foo.bar.com/:17:330" |
| filePositionLine = filePositionLine.split(' '); |
| let parts = filePositionLine[1].split(':'); |
| if (parts[0].length <= 5) return parts[0] + ':' + parts[1]; |
| return parts[1]; |
| } |
| |
| processMap(type, time, from, to, pc, line, column, reason, name) { |
| let time_ = parseInt(time); |
| if (type === 'Deprecate') return this.deprecateMap(type, time_, from); |
| let from_ = this.getExistingMapEntry(from, time_); |
| let to_ = this.getExistingMapEntry(to, time_); |
| // TODO: use SourcePosition directly. |
| let edge = new Edge(type, name, reason, time, from_, to_); |
| to_.filePosition = this.formatPC(pc, line, column); |
| let fileName = this.processFileName(to_.filePosition); |
| // TODO: avoid undefined source positions. |
| if (fileName !== undefined) { |
| to_.script = this.getScript(fileName); |
| } |
| if (to_.script) { |
| to_.sourcePosition = to_.script.addSourcePosition(line, column, to_) |
| } |
| edge.finishSetup(); |
| } |
| |
| deprecateMap(type, time, id) { |
| this.getExistingMapEntry(id, time).deprecate(); |
| } |
| |
| processMapCreate(time, id) { |
| // map-create events might override existing maps if the addresses get |
| // recycled. Hence we do not check for existing maps. |
| let map = this.createMapEntry(id, time); |
| } |
| |
| processMapDetails(time, id, string) { |
| // TODO(cbruni): fix initial map logging. |
| let map = this.getExistingMapEntry(id, time); |
| map.description = string; |
| } |
| |
| createMapEntry(id, time) { |
| let map = new MapLogEntry(id, time); |
| this._mapTimeline.push(map); |
| return map; |
| } |
| |
| getExistingMapEntry(id, time) { |
| if (id === '0x000000000000') return undefined; |
| let map = MapLogEntry.get(id, time); |
| if (map === undefined) { |
| console.error(`No map details provided: id=${id}`); |
| // Manually patch in a map to continue running. |
| return this.createMapEntry(id, time); |
| }; |
| return map; |
| } |
| |
| getScript(url) { |
| const script = this._profile.getScript(url); |
| // TODO create placeholder script for empty urls. |
| if (script === undefined) { |
| console.error(`Could not find script for url: '${url}'`) |
| } |
| return script; |
| } |
| |
| get icTimeline() { |
| return this._icTimeline; |
| } |
| |
| get mapTimeline() { |
| return this._mapTimeline; |
| } |
| |
| get deoptTimeline() { |
| return this._deoptTimeline; |
| } |
| |
| get scripts() { |
| return this._profile.scripts_.filter(script => script !== undefined); |
| } |
| } |