From f2d079bc975ce6282590dd8d5cc0227e0fdd68ff Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Wed, 30 Jun 2021 13:17:33 +0200 Subject: [PATCH] [tools][system-analyzer] Add local symbol server Start a local symbol server using the local-web-sever node package: ws --stack system-analyzer/lws-middleware.js lws-static cors The system-analyzer will then use it to symbolize profiles. Note: The symbol server will execute `nm` and `objdump` locally. Change-Id: Icff6e9f5af24f214f353c049f5cd13eedccf0f88 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2979591 Commit-Queue: Camillo Bruni Reviewed-by: Victor Gomes Cr-Commit-Position: refs/heads/master@{#75501} --- test/mjsunit/tools/dumpcpp.mjs | 4 +- test/mjsunit/tools/processor.mjs | 4 +- test/mjsunit/tools/tickprocessor.mjs | 88 +++++++---------- tools/dumpcpp-driver.mjs | 2 +- tools/ic-processor-driver.mjs | 2 +- tools/logreader.mjs | 24 ++--- tools/parse-processor-driver.mjs | 2 +- tools/system-analyzer/index.mjs | 13 ++- tools/system-analyzer/lws-middleware.js | 98 +++++++++++++++++++ tools/system-analyzer/processor.mjs | 60 ++++++++++-- .../view/timeline/timeline-track-base.mjs | 2 +- tools/tickprocessor-driver.mjs | 2 +- tools/tickprocessor.mjs | 84 +++++++++++++--- 13 files changed, 283 insertions(+), 102 deletions(-) create mode 100644 tools/system-analyzer/lws-middleware.js diff --git a/test/mjsunit/tools/dumpcpp.mjs b/test/mjsunit/tools/dumpcpp.mjs index d80ada2bf4..3d882b2424 100644 --- a/test/mjsunit/tools/dumpcpp.mjs +++ b/test/mjsunit/tools/dumpcpp.mjs @@ -6,7 +6,7 @@ import { CppProcessor, LinuxCppEntriesProvider } from "../../../tools/dumpcpp.mjs" ; -(function testProcessSharedLibrary() { +await (async function testProcessSharedLibrary() { var oldLoadSymbols = LinuxCppEntriesProvider.prototype.loadSymbols; LinuxCppEntriesProvider.prototype.loadSymbols = function(libName) { @@ -20,7 +20,7 @@ import { var testCppProcessor = new CppProcessor(new LinuxCppEntriesProvider(), false, false); - testCppProcessor.processSharedLibrary( + await testCppProcessor.processSharedLibrary( '/usr/local/google/home/lpy/v8/out/native/d8', 0x00000100, 0x00000400, 0); diff --git a/test/mjsunit/tools/processor.mjs b/test/mjsunit/tools/processor.mjs index 0edb70d8fa..4190108638 100644 --- a/test/mjsunit/tools/processor.mjs +++ b/test/mjsunit/tools/processor.mjs @@ -34,8 +34,8 @@ const result = doWork(); const logString = d8.log.getAndStop(); const processor = new Processor(); -processor.processChunk(logString); -processor.finalize(); +await processor.processChunk(logString); +await processor.finalize(); const maps = processor.mapTimeline; const ics = processor.icTimeline; diff --git a/test/mjsunit/tools/tickprocessor.mjs b/test/mjsunit/tools/tickprocessor.mjs index 16da3452ff..b0ecc99f84 100644 --- a/test/mjsunit/tools/tickprocessor.mjs +++ b/test/mjsunit/tools/tickprocessor.mjs @@ -82,7 +82,7 @@ import { })(); -(function testUnixCppEntriesProvider() { +await (async function testUnixCppEntriesProvider() { var oldLoadSymbols = LinuxCppEntriesProvider.prototype.loadSymbols; // shell executable @@ -99,13 +99,10 @@ import { '081f08a0 00000004 B stdout\n' ].join('\n'), '']; }; - var shell_prov = new LinuxCppEntriesProvider(); var shell_syms = []; - shell_prov.parseVmSymbols('shell', 0x08048000, 0x081ee000, 0, - function (name, start, end) { - shell_syms.push(Array.prototype.slice.apply(arguments, [0])); - }); + await shell_prov.parseVmSymbols('shell', 0x08048000, 0x081ee000, 0, + (...params) => shell_syms.push(params)); assertEquals( [['_init', 0x08049790, 0x08049f50], ['_start', 0x08049f50, 0x08139150], @@ -128,10 +125,8 @@ import { }; var libc_prov = new LinuxCppEntriesProvider(); var libc_syms = []; - libc_prov.parseVmSymbols('libc', 0xf7c5c000, 0xf7da5000, 0, - function (name, start, end) { - libc_syms.push(Array.prototype.slice.apply(arguments, [0])); - }); + await libc_prov.parseVmSymbols('libc', 0xf7c5c000, 0xf7da5000, 0, + (...params) => libc_syms.push(params)); var libc_ref_syms = [['__libc_init_first', 0x000162a0, 0x000162a0 + 0x5], ['__isnan', 0x0002a5f0, 0x0002a5f0 + 0x2d], ['scalblnf', 0x0002aaa0, 0x0002aaa0 + 0xd], @@ -165,10 +160,8 @@ import { }; var android_prov = new LinuxCppEntriesProvider(); var android_syms = []; - android_prov.parseVmSymbols('libmonochrome', 0xf7c5c000, 0xf9c5c000, 0, - function (name, start, end) { - android_syms.push(Array.prototype.slice.apply(arguments, [0])); - }); + await android_prov.parseVmSymbols('libmonochrome', 0xf7c5c000, 0xf9c5c000, 0, + (...params) => android_syms.push(params)); var android_ref_syms = [ ['v8::internal::interpreter::BytecodeGenerator::BytecodeGenerator(v8::internal::UnoptimizedCompilationInfo*)', 0x013a1088, 0x013a1088 + 0x224], ['v8::internal::interpreter::BytecodeGenerator::FinalizeBytecode(v8::internal::Isolate*, v8::internal::Handle)', 0x013a12ac, 0x013a12ac + 0xd0], @@ -187,7 +180,7 @@ import { })(); -(function testMacOSCppEntriesProvider() { +await (async function testMacOSCppEntriesProvider() { var oldLoadSymbols = MacOSCppEntriesProvider.prototype.loadSymbols; // shell executable @@ -203,13 +196,10 @@ import { '00137400 v8::internal::Runtime_DebugGetPropertyDetails\n' ].join('\n'), '']; }; - var shell_prov = new MacOSCppEntriesProvider(); var shell_syms = []; - shell_prov.parseVmSymbols('shell', 0x00001c00, 0x00163256, 0x100, - function (name, start, end) { - shell_syms.push(Array.prototype.slice.apply(arguments, [0])); - }); + await shell_prov.parseVmSymbols('shell', 0x00001c00, 0x00163256, 0x100, + (...params) => shell_syms.push(params)); assertEquals( [['start', 0x00001c00, 0x00001c40], ['dyld_stub_binding_helper', 0x00001c40, 0x0011b810], @@ -229,10 +219,8 @@ import { }; var stdc_prov = new MacOSCppEntriesProvider(); var stdc_syms = []; - stdc_prov.parseVmSymbols('stdc++', 0x95728fb4, 0x95770005, 0, - function (name, start, end) { - stdc_syms.push(Array.prototype.slice.apply(arguments, [0])); - }); + await stdc_prov.parseVmSymbols('stdc++', 0x95728fb4, 0x95770005, 0, + (...params) => stdc_syms.push(params)); var stdc_ref_syms = [['__gnu_cxx::balloc::__mini_vector::_Alloc_block*, __gnu_cxx::bitmap_allocator::_Alloc_block*> >::__mini_vector', 0x0000107a, 0x0002c410], ['std::basic_streambuf >::pubseekoff', 0x0002c410, 0x0002c488], ['std::basic_streambuf >::pubseekpos', 0x0002c488, 0x000466aa], @@ -247,7 +235,7 @@ import { })(); -(function testWindowsCppEntriesProvider() { +await (async function testWindowsCppEntriesProvider() { var oldLoadSymbols = WindowsCppEntriesProvider.prototype.loadSymbols; WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) { @@ -272,10 +260,8 @@ import { }; var shell_prov = new WindowsCppEntriesProvider(); var shell_syms = []; - shell_prov.parseVmSymbols('shell.exe', 0x00400000, 0x0057c000, 0, - function (name, start, end) { - shell_syms.push(Array.prototype.slice.apply(arguments, [0])); - }); + await shell_prov.parseVmSymbols('shell.exe', 0x00400000, 0x0057c000, 0, + (...params) => shell_syms.push(params)); assertEquals( [['ReadFile', 0x00401000, 0x004010a0], ['Print', 0x004010a0, 0x00402230], @@ -289,7 +275,7 @@ import { // http://code.google.com/p/v8/issues/detail?id=427 -(function testWindowsProcessExeAndDllMapFile() { +await (async function testWindowsProcessExeAndDllMapFile() { function exeSymbols(exeName) { return [ ' 0000:00000000 ___ImageBase 00400000 ', @@ -312,11 +298,9 @@ import { read = exeSymbols; var exe_exe_syms = []; - (new WindowsCppEntriesProvider()).parseVmSymbols( + await (new WindowsCppEntriesProvider()).parseVmSymbols( 'chrome.exe', 0x00400000, 0x00472000, 0, - function (name, start, end) { - exe_exe_syms.push(Array.prototype.slice.apply(arguments, [0])); - }); + (...params) => exe_exe_syms.push(params)); assertEquals( [['RunMain', 0x00401780, 0x00401ac0], ['_main', 0x00401ac0, 0x00472000]], @@ -324,22 +308,18 @@ import { read = dllSymbols; var exe_dll_syms = []; - (new WindowsCppEntriesProvider()).parseVmSymbols( + await (new WindowsCppEntriesProvider()).parseVmSymbols( 'chrome.exe', 0x00400000, 0x00472000, 0, - function (name, start, end) { - exe_dll_syms.push(Array.prototype.slice.apply(arguments, [0])); - }); + (...params) => exe_dll_syms.push(params)); assertEquals( [], exe_dll_syms, '.exe with .dll symbols'); read = dllSymbols; var dll_dll_syms = []; - (new WindowsCppEntriesProvider()).parseVmSymbols( + await (new WindowsCppEntriesProvider()).parseVmSymbols( 'chrome.dll', 0x01c30000, 0x02b80000, 0, - function (name, start, end) { - dll_dll_syms.push(Array.prototype.slice.apply(arguments, [0])); - }); + (...params) => dll_dll_syms.push(params)); assertEquals( [['_DllMain@12', 0x01c31780, 0x01c31ac0], ['___DllMainCRTStartup', 0x01c31ac0, 0x02b80000]], @@ -347,11 +327,9 @@ import { read = exeSymbols; var dll_exe_syms = []; - (new WindowsCppEntriesProvider()).parseVmSymbols( + await (new WindowsCppEntriesProvider()).parseVmSymbols( 'chrome.dll', 0x01c30000, 0x02b80000, 0, - function (name, start, end) { - dll_exe_syms.push(Array.prototype.slice.apply(arguments, [0])); - }); + (...params) => dll_exe_syms.push(params)); assertEquals( [], dll_exe_syms, '.dll with .exe symbols'); @@ -431,7 +409,7 @@ class PrintMonitor { } } -(function testProcessing() { +await (async function testProcessing() { const testData = { 'Default': [ 'tickprocessor-test.log', 'tickprocessor-test.default', @@ -462,30 +440,30 @@ class PrintMonitor { }; for (var testName in testData) { console.log('=== testProcessing-' + testName + ' ==='); - testTickProcessor(...testData[testName]); + await testTickProcessor(...testData[testName]); } })(); -function testTickProcessor(logInput, refOutput, args=[]) { +async function testTickProcessor(logInput, refOutput, args=[]) { // /foo/bar/tickprocesser.mjs => /foo/bar/ const dir = import.meta.url.split("/").slice(0, -1).join('/') + '/'; const params = ArgumentsProcessor.process(args); - testExpectations(dir, logInput, refOutput, params); + await testExpectations(dir, logInput, refOutput, params); // TODO(cbruni): enable again after it works on bots - // testEndToEnd(dir, 'tickprocessor-test-large.js', refOutput, params); + // await testEndToEnd(dir, 'tickprocessor-test-large.js', refOutput, params); } -function testExpectations(dir, logInput, refOutput, params) { +async function testExpectations(dir, logInput, refOutput, params) { const symbolsFile = dir + logInput + '.symbols.json'; const cppEntries = new CppEntriesProviderMock(symbolsFile); const tickProcessor = TickProcessor.fromParams(params, cppEntries); const printMonitor = new PrintMonitor(dir + refOutput); - tickProcessor.processLogFileInTest(dir + logInput); + await tickProcessor.processLogFileInTest(dir + logInput); tickProcessor.printStatistics(); printMonitor.finish(); }; -function testEndToEnd(dir, sourceFile, ignoredRefOutput, params) { +async function testEndToEnd(dir, sourceFile, ignoredRefOutput, params) { // This test only works on linux. if (!os?.system) return; if (os.name !== 'linux' && os.name !== 'macos') return; @@ -499,6 +477,6 @@ function testEndToEnd(dir, sourceFile, ignoredRefOutput, params) { // hence we cannot properly compare output expectations. // Let's just use a dummy file and only test whether we don't throw. const printMonitor = new PrintMonitor(dir + ignoredRefOutput); - tickProcessor.processLogFileInTest(tmpLogFile); + await tickProcessor.processLogFileInTest(tmpLogFile); tickProcessor.printStatistics(); } \ No newline at end of file diff --git a/tools/dumpcpp-driver.mjs b/tools/dumpcpp-driver.mjs index 7961ab4733..b6150bdf68 100644 --- a/tools/dumpcpp-driver.mjs +++ b/tools/dumpcpp-driver.mjs @@ -20,5 +20,5 @@ const cppProcessor = new CppProcessor( new (entriesProviders[params.platform])(params.nm, params.targetRootFS, params.apkEmbeddedLibrary), params.timedRange, params.pairwiseTimedRange); -cppProcessor.processLogFile(params.logFileName); +await cppProcessor.processLogFile(params.logFileName); cppProcessor.dumpCppSymbols(); diff --git a/tools/ic-processor-driver.mjs b/tools/ic-processor-driver.mjs index 4de2f37719..dffa11cae5 100644 --- a/tools/ic-processor-driver.mjs +++ b/tools/ic-processor-driver.mjs @@ -22,7 +22,7 @@ class ArgumentsProcessor extends BaseArgumentsProcessor { const params = ArgumentsProcessor.process(arguments); const processor = new Processor(); -processor.processLogFile(params.logFileName); +await processor.processLogFile(params.logFileName); const typeAccumulator = new Map(); diff --git a/tools/logreader.mjs b/tools/logreader.mjs index fcacd7888f..ecd7b573a2 100644 --- a/tools/logreader.mjs +++ b/tools/logreader.mjs @@ -117,8 +117,8 @@ export class LogReader { * * @param {string} chunk A portion of log. */ - processLogChunk(chunk) { - this.processLog_(chunk.split('\n')); + async processLogChunk(chunk) { + await this.processLog_(chunk.split('\n')); } /** @@ -126,14 +126,14 @@ export class LogReader { * * @param {string} line A line of log. */ - processLogLine(line) { + async processLogLine(line) { if (!this.timedRange_) { - this.processLogLine_(line); + await this.processLogLine_(line); return; } if (line.startsWith("current-time")) { if (this.hasSeenTimerMarker_) { - this.processLog_(this.logLinesSinceLastTimerMarker_); + await this.processLog_(this.logLinesSinceLastTimerMarker_); this.logLinesSinceLastTimerMarker_ = []; // In pairwise mode, a "current-time" line ends the timed range. if (this.pairwiseTimedRange_) { @@ -146,7 +146,7 @@ export class LogReader { if (this.hasSeenTimerMarker_) { this.logLinesSinceLastTimerMarker_.push(line); } else if (!line.startsWith("tick")) { - this.processLogLine_(line); + await this.processLogLine_(line); } } } @@ -195,7 +195,7 @@ export class LogReader { * @param {Array.} fields Log record. * @private */ - dispatchLogRow_(fields) { + async dispatchLogRow_(fields) { // Obtain the dispatch. const command = fields[0]; const dispatch = this.dispatchTable_[command]; @@ -222,7 +222,7 @@ export class LogReader { } // Run the processor. - dispatch.processor.apply(this, parsedFields); + await dispatch.processor.apply(this, parsedFields); } /** @@ -231,9 +231,9 @@ export class LogReader { * @param {Array.} lines Log lines. * @private */ - processLog_(lines) { + async processLog_(lines) { for (let i = 0, n = lines.length; i < n; ++i) { - this.processLogLine_(lines[i]); + await this.processLogLine_(lines[i]); } } @@ -243,11 +243,11 @@ export class LogReader { * @param {String} a log line * @private */ - processLogLine_(line) { + async processLogLine_(line) { if (line.length > 0) { try { const fields = this.csvParser_.parseLine(line); - this.dispatchLogRow_(fields); + await this.dispatchLogRow_(fields); } catch (e) { this.printError(`line ${this.lineNum_ + 1}: ${e.message || e}\n${e.stack}`); } diff --git a/tools/parse-processor-driver.mjs b/tools/parse-processor-driver.mjs index 3dc902adaa..bbcb85681e 100644 --- a/tools/parse-processor-driver.mjs +++ b/tools/parse-processor-driver.mjs @@ -6,4 +6,4 @@ import { ParseProcessor, ArgumentsProcessor } from "./parse-processor.mjs"; const params = ArgumentsProcessor.process(arguments); const parseProcessor = new ParseProcessor(); -parseProcessor.processLogFile(params.logFileName); +await parseProcessor.processLogFile(params.logFileName); diff --git a/tools/system-analyzer/index.mjs b/tools/system-analyzer/index.mjs index d5125eadd1..2cae0d3b6d 100644 --- a/tools/system-analyzer/index.mjs +++ b/tools/system-analyzer/index.mjs @@ -56,7 +56,7 @@ class App { 'fileuploadchunk', (e) => this.handleFileUploadChunk(e)); this._view.logFileReader.addEventListener( 'fileuploadend', (e) => this.handleFileUploadEnd(e)); - this._startupPromise = this.runAsyncInitialize(); + this._startupPromise = this._loadCustomElements(); this._view.codeTrack.svg = true; } @@ -74,7 +74,7 @@ class App { ]); } - async runAsyncInitialize() { + async _loadCustomElements() { await Promise.all([ import('./view/list-panel.mjs'), import('./view/timeline-panel.mjs'), @@ -84,6 +84,10 @@ class App { import('./view/property-link-table.mjs'), import('./view/tool-tip.mjs'), ]); + this._addEventListeners(); + } + + _addEventListeners() { document.addEventListener( 'keydown', e => this._navigation?.handleKeyDown(e)); document.addEventListener( @@ -359,15 +363,14 @@ class App { this._processor = new Processor(); } - handleFileUploadChunk(e) { + async handleFileUploadChunk(e) { this._processor.processChunk(e.detail); } async handleFileUploadEnd(e) { try { const processor = this._processor; - processor.finalize(); - + await processor.finalize(); await this._startupPromise; this._state.profile = processor.profile; diff --git a/tools/system-analyzer/lws-middleware.js b/tools/system-analyzer/lws-middleware.js new file mode 100644 index 0000000000..f297c2d786 --- /dev/null +++ b/tools/system-analyzer/lws-middleware.js @@ -0,0 +1,98 @@ +// Copyright 2021 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. + +const util = require('util'); +const execFile = util.promisify(require('child_process').execFile); +const fs = require('fs') + +async function sh(cmd, ...params) { + console.log(cmd, params.join(' ')); + const options = {maxBuffer: 256 * 1024 * 1024}; + const {stdout} = await execFile(cmd, params, options); + return stdout; +} + +class Symbolizer { + constructor() { + this.nmExec = 'nm'; + this.objdumpExec = 'objdump'; + } + + middleware(config) { + return async (ctx, next) => { + if (ctx.path == '/v8/loadVMSymbols') { + await this.parseVMSymbols(ctx) + } + await next() + } + } + + async parseVMSymbols(ctx) { + const query = ctx.request.query; + const result = { + libName: query.libName, + symbols: ['', ''], + error: undefined, + fileOffsetMinusVma: 0, + }; + switch (query.platform) { + case 'macos': + await this.loadVMSymbolsMacOS(query, result); + break; + case 'linux': + await this.loadVMSymbolsLinux(query, result); + break; + default: + ctx.response.status = '500'; + return; + } + ctx.response.type = 'json'; + ctx.response.body = JSON.stringify(result); + } + + async loadVMSymbolsMacOS(query, result) { + let libName = + (query.targetRootFS ? query.targetRootFS : '') + query.libName; + try { + // Fast skip files that don't exist. + if (libName.indexOf('/') === -1 || !fs.existsSync(libName)) return; + result.symbols = [await sh(this.nmExec, '--demangle', '-n', libName), '']; + } catch (e) { + result.error = e.message; + } + } + + async loadVMSymbolsLinux(query, result) { + let libName = query.libName; + if (query.apkEmbeddedLibrary && libName.endsWith('.apk')) { + libName = query.apkEmbeddedLibrary; + } + if (query.targetRootFS) { + libName = libName.substring(libName.lastIndexOf('/') + 1); + libName = query.targetRootFS + libName; + } + try { + // Fast skip files that don't exist. + if (libName.indexOf('/') === -1 || !fs.existsSync(libName)) return; + result.symbols = [ + await sh(this.nmExec, '-C', '-n', '-S', libName), + await sh(this.nmExec, '-C', '-n', '-S', '-D', libName) + ]; + + const objdumpOutput = await sh(this.objdumpExec, '-h', libName); + for (const line of objdumpOutput.split('\n')) { + const [, sectionName, , vma, , fileOffset] = line.trim().split(/\s+/); + if (sectionName === '.text') { + result.fileOffsetMinusVma = + parseInt(fileOffset, 16) - parseInt(vma, 16); + } + } + } catch (e) { + console.log(e); + result.error = e.message; + } + } +} + +module.exports = Symbolizer diff --git a/tools/system-analyzer/processor.mjs b/tools/system-analyzer/processor.mjs index 00757cb86d..5b33117e01 100644 --- a/tools/system-analyzer/processor.mjs +++ b/tools/system-analyzer/processor.mjs @@ -4,6 +4,7 @@ import {LogReader, parseString, parseVarArgs} from '../logreader.mjs'; import {Profile} from '../profile.mjs'; +import {RemoteLinuxCppEntriesProvider, RemoteMacOSCppEntriesProvider} from '../tickprocessor.mjs' import {ApiLogEntry} from './log/api.mjs'; import {CodeLogEntry, DeoptLogEntry, SharedLibLogEntry} from './log/code.mjs'; @@ -15,6 +16,37 @@ import {Timeline} from './timeline.mjs'; // =========================================================================== +class AsyncConsumer { + constructor(consumer_fn) { + this._chunks = []; + this._consumer = consumer_fn; + this._pendingWork = Promise.resolve(); + this._isConsuming = false; + } + + get pendingWork() { + return this._pendingWork; + } + + push(chunk) { + this._chunks.push(chunk); + this.consumeAll(); + } + + async consumeAll() { + if (!this._isConsuming) this._pendingWork = this._consumeAll(); + return await this._pendingWork; + } + + async _consumeAll() { + this._isConsuming = true; + while (this._chunks.length > 0) { + await this._consumer(this._chunks.shift()); + } + this._isConsuming = false; + } +} + export class Processor extends LogReader { _profile = new Profile(); _apiTimeline = new Timeline(); @@ -32,6 +64,8 @@ export class Processor extends LogReader { MINOR_VERSION = 6; constructor() { super(); + this._chunkConsumer = + new AsyncConsumer((chunk) => this._processChunk(chunk)); const propertyICParser = [ parseInt, parseInt, parseInt, parseInt, parseString, parseString, parseString, parseString, parseString, parseString @@ -149,6 +183,8 @@ export class Processor extends LogReader { processor: this.processApiEvent }, }; + // TODO(cbruni): Choose correct cpp entries provider + this._cppEntriesProvider = new RemoteLinuxCppEntriesProvider(); } printError(str) { @@ -157,6 +193,10 @@ export class Processor extends LogReader { } processChunk(chunk) { + this._chunkConsumer.push(chunk) + } + + async _processChunk(chunk) { let end = chunk.length; let current = 0; let next = 0; @@ -174,21 +214,21 @@ export class Processor extends LogReader { this._chunkRemainder = ''; } current = next + 1; - this.processLogLine(line); + await this.processLogLine(line); } } catch (e) { console.error(`Error occurred during parsing, trying to continue: ${e}`); } } - processLogFile(fileName) { + async processLogFile(fileName) { this.collectEntries = true; this.lastLogFileName_ = fileName; let i = 1; let line; try { while (line = readline()) { - this.processLogLine(line); + await this.processLogLine(line); i++; } } catch (e) { @@ -199,7 +239,8 @@ export class Processor extends LogReader { this.finalize(); } - finalize() { + async finalize() { + await this._chunkConsumer.consumeAll(); // TODO(cbruni): print stats; this._mapTimeline.transitions = new Map(); let id = 0; @@ -227,12 +268,16 @@ export class Processor extends LogReader { } } - processSharedLibrary(name, start, end, aslr_slide) { - const entry = this._profile.addLibrary(name, start, end); + async processSharedLibrary(name, startAddr, endAddr, aslrSlide) { + const entry = this._profile.addLibrary(name, startAddr, endAddr); entry.logEntry = new SharedLibLogEntry(entry); // Many events rely on having a script around, creating fake entries for // shared libraries. this._profile.addScriptSource(-1, name, ''); + await this._cppEntriesProvider.parseVmSymbols( + name, startAddr, endAddr, aslrSlide, (fName, fStart, fEnd) => { + this._profile.addStaticCode(fName, fStart, fEnd); + }); } processCodeCreation(type, kind, timestamp, start, size, name, maybe_func) { @@ -329,6 +374,7 @@ export class Processor extends LogReader { this._profile.addSourcePositions( start, scriptId, startPos, endPos, sourcePositions, inliningPositions, inlinedFunctions); + if (this._lastCodeLogEntry === undefined) return; let profileEntry = this._profile.findEntry(start); if (profileEntry !== this._lastCodeLogEntry._entry) return; this.addSourcePosition(profileEntry, this._lastCodeLogEntry); @@ -383,7 +429,7 @@ export class Processor extends LogReader { const script = profileEntry.source?.script; if (script !== undefined) return script; let fileName; - if (profileEntry.type = 'SHARED_LIB') { + if (profileEntry.type === 'SHARED_LIB') { fileName = profileEntry.name; } else { // Slow path, try to get the script from the url: diff --git a/tools/system-analyzer/view/timeline/timeline-track-base.mjs b/tools/system-analyzer/view/timeline/timeline-track-base.mjs index 8760b0223e..fac7c53b40 100644 --- a/tools/system-analyzer/view/timeline/timeline-track-base.mjs +++ b/tools/system-analyzer/view/timeline/timeline-track-base.mjs @@ -297,6 +297,7 @@ export class TimelineTrackBase extends V8CustomElement { } _updateToolTip(event) { + if (!this._focusedEntry) return false; this.dispatchEvent( new ToolTipEvent(this._focusedEntry, this.toolTipTargetNode)); event.stopImmediatePropagation(); @@ -350,7 +351,6 @@ export class TimelineTrackBase extends V8CustomElement { if (chunk === undefined) return [-1, -1]; const xFrom = (chunk.index * kChunkWidth + kChunkVisualWidth / 2) | 0; const yFrom = kTimelineHeight - chunk.yOffset(entry) | 0; - console.log(xFrom, yFrom); return [xFrom, yFrom]; } diff --git a/tools/tickprocessor-driver.mjs b/tools/tickprocessor-driver.mjs index 658ac8e1ba..13b5a2f06c 100644 --- a/tools/tickprocessor-driver.mjs +++ b/tools/tickprocessor-driver.mjs @@ -29,7 +29,7 @@ import { ArgumentsProcessor, TickProcessor } from "./tickprocessor.mjs"; const params = ArgumentsProcessor.process(arguments); const tickProcessor = TickProcessor.fromParams(params); -tickProcessor.processLogFile(params.logFileName); +await tickProcessor.processLogFile(params.logFileName); if (params.serializeVMSymbols) { tickProcessor.printVMSymbols(); } else { diff --git a/tools/tickprocessor.mjs b/tools/tickprocessor.mjs index 75760536a7..b2dee0ce07 100644 --- a/tools/tickprocessor.mjs +++ b/tools/tickprocessor.mjs @@ -60,12 +60,17 @@ class V8Profile extends Profile { } class CppEntriesProvider { + constructor() { + this._isEnabled = true; + } + inRange(funcInfo, start, end) { return funcInfo.start >= start && funcInfo.end <= end; } - parseVmSymbols(libName, libStart, libEnd, libASLRSlide, processorFunc) { - this.loadSymbols(libName); + async parseVmSymbols(libName, libStart, libEnd, libASLRSlide, processorFunc) { + if (!this._isEnabled) return; + await this.loadSymbols(libName); let lastUnknownSize; let lastAdded; @@ -121,12 +126,41 @@ class CppEntriesProvider { addEntry({ name: '', start: libEnd }); } - loadSymbols(libName) {} + async loadSymbols(libName) {} + + async loadSymbolsRemote(platform, libName) { + this.parsePos = 0; + const url = new URL("http://localhost:8000/v8/loadVMSymbols"); + url.searchParams.set('libName', libName); + url.searchParams.set('platform', platform); + this._setRemoteQueryParams(url.searchParams); + let response; + let json; + try { + response = await fetch(url); + json = await response.json(); + if (json.error) console.warn(json.error); + } catch (e) { + if (!response || response.status == 404) { + // Assume that the local symbol server is not reachable. + console.error("Disabling remote symbol loading:", e); + this._isEnabled = false; + } + } + this._handleRemoteSymbolsResult(json); + } + + _setRemoteQueryParams(searchParams) { + // Subclass responsibility. + } + + _handleRemoteSymbolsResult(json) { + this.symbols = json.symbols; + } parseNextLine() { return false } } - export class LinuxCppEntriesProvider extends CppEntriesProvider { constructor(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary) { super(); @@ -142,8 +176,18 @@ export class LinuxCppEntriesProvider extends CppEntriesProvider { this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/; } + _setRemoteQueryParams(searchParams) { + super._setRemoteQueryParams(searchParams); + searchParams.set('targetRootFS', this.targetRootFS ?? ""); + searchParams.set('apkEmbeddedLibrary', this.apkEmbeddedLibrary); + } - loadSymbols(libName) { + _handleRemoteSymbolsResult(json) { + super._handleRemoteSymbolsResult(json); + this.fileOffsetMinusVma = json.fileOffsetMinusVma; + } + + async loadSymbols(libName) { this.parsePos = 0; if (this.apkEmbeddedLibrary && libName.endsWith('.apk')) { libName = this.apkEmbeddedLibrary; @@ -172,9 +216,7 @@ export class LinuxCppEntriesProvider extends CppEntriesProvider { } parseNextLine() { - if (this.symbols.length == 0) { - return false; - } + if (this.symbols.length == 0) return false; const lineEndPos = this.symbols[0].indexOf('\n', this.parsePos); if (lineEndPos == -1) { this.symbols.shift(); @@ -196,6 +238,12 @@ export class LinuxCppEntriesProvider extends CppEntriesProvider { } } +export class RemoteLinuxCppEntriesProvider extends LinuxCppEntriesProvider { + async loadSymbols(libName) { + return this.loadSymbolsRemote('linux', libName); + } +} + export class MacOSCppEntriesProvider extends LinuxCppEntriesProvider { constructor(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary) { super(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary); @@ -203,14 +251,16 @@ export class MacOSCppEntriesProvider extends LinuxCppEntriesProvider { this.FUNC_RE = /^([0-9a-fA-F]{8,16})() (.*)$/; } - loadSymbols(libName) { + async loadSymbols(libName) { this.parsePos = 0; libName = this.targetRootFS + libName; // It seems that in OS X `nm` thinks that `-f` is a format option, not a // "flat" display option flag. try { - this.symbols = [os.system(this.nmExec, ['-n', libName], -1, -1), '']; + this.symbols = [ + os.system(this.nmExec, ['--demangle', '-n', libName], -1, -1), + '']; } catch (e) { // If the library cannot be found on this system let's not panic. this.symbols = ''; @@ -218,6 +268,12 @@ export class MacOSCppEntriesProvider extends LinuxCppEntriesProvider { } } +export class RemoteMacOSCppEntriesProvider extends LinuxCppEntriesProvider { + async loadSymbols(libName) { + return this.loadSymbolsRemote('macos', libName); + } +} + export class WindowsCppEntriesProvider extends CppEntriesProvider { constructor(_ignored_nmExec, _ignored_objdumpExec, targetRootFS, @@ -640,19 +696,19 @@ export class TickProcessor extends LogReader { return name !== "UNKNOWN" && !(name in this.codeTypes_); } - processLogFile(fileName) { + async processLogFile(fileName) { this.lastLogFileName_ = fileName; let line; while (line = readline()) { - this.processLogLine(line); + await this.processLogLine(line); } } - processLogFileInTest(fileName) { + async processLogFileInTest(fileName) { // Hack file name to avoid dealing with platform specifics. this.lastLogFileName_ = 'v8.log'; const contents = d8.file.read(fileName); - this.processLogChunk(contents); + await this.processLogChunk(contents); } processSharedLibrary(name, startAddr, endAddr, aslrSlide) {