1// Copyright 2012 the V8 project authors. All rights reserved. 2// Redistribution and use in source and binary forms, with or without 3// modification, are permitted provided that the following conditions are 4// met: 5// 6// * Redistributions of source code must retain the above copyright 7// notice, this list of conditions and the following disclaimer. 8// * Redistributions in binary form must reproduce the above 9// copyright notice, this list of conditions and the following 10// disclaimer in the documentation and/or other materials provided 11// with the distribution. 12// * Neither the name of Google Inc. nor the names of its 13// contributors may be used to endorse or promote products derived 14// from this software without specific prior written permission. 15// 16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28import { LogReader, parseString, parseVarArgs } from "./logreader.mjs"; 29import { BaseArgumentsProcessor, parseBool } from "./arguments.mjs"; 30import { Profile, JsonProfile } from "./profile.mjs"; 31import { ViewBuilder } from "./profile_view.mjs"; 32import { WebInspector} from "./sourcemap.mjs"; 33 34 35class V8Profile extends Profile { 36 static IC_RE = 37 /^(LoadGlobalIC: )|(Handler: )|(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Load|Store)IC_)/; 38 static BYTECODES_RE = /^(BytecodeHandler: )/; 39 static BASELINE_HANDLERS_RE = /^(Builtin: .*Baseline.*)/; 40 static BUILTINS_RE = /^(Builtin: )/; 41 static STUBS_RE = /^(Stub: )/; 42 43 constructor(separateIc, separateBytecodes, separateBuiltins, separateStubs, 44 separateBaselineHandlers) { 45 super(); 46 const regexps = []; 47 if (!separateIc) regexps.push(V8Profile.IC_RE); 48 if (!separateBytecodes) regexps.push(V8Profile.BYTECODES_RE); 49 if (!separateBuiltins) regexps.push(V8Profile.BUILTINS_RE); 50 if (!separateStubs) regexps.push(V8Profile.STUBS_RE); 51 if (regexps.length > 0) { 52 this.skipThisFunction = function(name) { 53 for (let i = 0; i < regexps.length; i++) { 54 if (regexps[i].test(name)) return true; 55 } 56 return false; 57 }; 58 } 59 } 60} 61 62class CppEntriesProvider { 63 constructor() { 64 this._isEnabled = true; 65 } 66 67 inRange(funcInfo, start, end) { 68 return funcInfo.start >= start && funcInfo.end <= end; 69 } 70 71 async parseVmSymbols(libName, libStart, libEnd, libASLRSlide, processorFunc) { 72 if (!this._isEnabled) return; 73 await this.loadSymbols(libName); 74 75 let lastUnknownSize; 76 let lastAdded; 77 78 let addEntry = (funcInfo) => { 79 // Several functions can be mapped onto the same address. To avoid 80 // creating zero-sized entries, skip such duplicates. 81 // Also double-check that function belongs to the library address space. 82 83 if (lastUnknownSize && 84 lastUnknownSize.start < funcInfo.start) { 85 // Try to update lastUnknownSize based on new entries start position. 86 lastUnknownSize.end = funcInfo.start; 87 if ((!lastAdded || 88 !this.inRange(lastUnknownSize, lastAdded.start, lastAdded.end)) && 89 this.inRange(lastUnknownSize, libStart, libEnd)) { 90 processorFunc( 91 lastUnknownSize.name, lastUnknownSize.start, lastUnknownSize.end); 92 lastAdded = lastUnknownSize; 93 } 94 } 95 lastUnknownSize = undefined; 96 97 if (funcInfo.end) { 98 // Skip duplicates that have the same start address as the last added. 99 if ((!lastAdded || lastAdded.start != funcInfo.start) && 100 this.inRange(funcInfo, libStart, libEnd)) { 101 processorFunc(funcInfo.name, funcInfo.start, funcInfo.end); 102 lastAdded = funcInfo; 103 } 104 } else { 105 // If a funcInfo doesn't have an end, try to match it up with the next 106 // entry. 107 lastUnknownSize = funcInfo; 108 } 109 } 110 111 while (true) { 112 const funcInfo = this.parseNextLine(); 113 if (funcInfo === null) continue; 114 if (funcInfo === false) break; 115 if (funcInfo.start < libStart - libASLRSlide && 116 funcInfo.start < libEnd - libStart) { 117 funcInfo.start += libStart; 118 } else { 119 funcInfo.start += libASLRSlide; 120 } 121 if (funcInfo.size) { 122 funcInfo.end = funcInfo.start + funcInfo.size; 123 } 124 addEntry(funcInfo); 125 } 126 addEntry({ name: '', start: libEnd }); 127 } 128 129 async loadSymbols(libName) {} 130 131 async loadSymbolsRemote(platform, libName) { 132 this.parsePos = 0; 133 const url = new URL("http://localhost:8000/v8/loadVMSymbols"); 134 url.searchParams.set('libName', libName); 135 url.searchParams.set('platform', platform); 136 this._setRemoteQueryParams(url.searchParams); 137 let response; 138 let json; 139 try { 140 response = await fetch(url); 141 if (response.status == 404) { 142 throw new Error( 143 `Local symbol server returned 404: ${await response.text()}`); 144 } 145 json = await response.json(); 146 if (json.error) console.warn(json.error); 147 } catch (e) { 148 if (!response || response.status == 404) { 149 // Assume that the local symbol server is not reachable. 150 console.warn("Disabling remote symbol loading:", e); 151 this._isEnabled = false; 152 return; 153 } 154 } 155 this._handleRemoteSymbolsResult(json); 156 } 157 158 _setRemoteQueryParams(searchParams) { 159 // Subclass responsibility. 160 } 161 162 _handleRemoteSymbolsResult(json) { 163 this.symbols = json.symbols; 164 } 165 166 parseNextLine() { return false } 167} 168 169export class LinuxCppEntriesProvider extends CppEntriesProvider { 170 constructor(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary) { 171 super(); 172 this.symbols = []; 173 // File offset of a symbol minus the virtual address of a symbol found in 174 // the symbol table. 175 this.fileOffsetMinusVma = 0; 176 this.parsePos = 0; 177 this.nmExec = nmExec; 178 this.objdumpExec = objdumpExec; 179 this.targetRootFS = targetRootFS; 180 this.apkEmbeddedLibrary = apkEmbeddedLibrary; 181 this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/; 182 } 183 184 _setRemoteQueryParams(searchParams) { 185 super._setRemoteQueryParams(searchParams); 186 searchParams.set('targetRootFS', this.targetRootFS ?? ""); 187 searchParams.set('apkEmbeddedLibrary', this.apkEmbeddedLibrary); 188 } 189 190 _handleRemoteSymbolsResult(json) { 191 super._handleRemoteSymbolsResult(json); 192 this.fileOffsetMinusVma = json.fileOffsetMinusVma; 193 } 194 195 async loadSymbols(libName) { 196 this.parsePos = 0; 197 if (this.apkEmbeddedLibrary && libName.endsWith('.apk')) { 198 libName = this.apkEmbeddedLibrary; 199 } 200 if (this.targetRootFS) { 201 libName = libName.substring(libName.lastIndexOf('/') + 1); 202 libName = this.targetRootFS + libName; 203 } 204 try { 205 this.symbols = [ 206 os.system(this.nmExec, ['-C', '-n', '-S', libName], -1, -1), 207 os.system(this.nmExec, ['-C', '-n', '-S', '-D', libName], -1, -1) 208 ]; 209 210 const objdumpOutput = os.system(this.objdumpExec, ['-h', libName], -1, -1); 211 for (const line of objdumpOutput.split('\n')) { 212 const [, sectionName, , vma, , fileOffset] = line.trim().split(/\s+/); 213 if (sectionName === ".text") { 214 this.fileOffsetMinusVma = parseInt(fileOffset, 16) - parseInt(vma, 16); 215 } 216 } 217 } catch (e) { 218 // If the library cannot be found on this system let's not panic. 219 this.symbols = ['', '']; 220 } 221 } 222 223 parseNextLine() { 224 if (this.symbols.length == 0) return false; 225 const lineEndPos = this.symbols[0].indexOf('\n', this.parsePos); 226 if (lineEndPos == -1) { 227 this.symbols.shift(); 228 this.parsePos = 0; 229 return this.parseNextLine(); 230 } 231 232 const line = this.symbols[0].substring(this.parsePos, lineEndPos); 233 this.parsePos = lineEndPos + 1; 234 const fields = line.match(this.FUNC_RE); 235 let funcInfo = null; 236 if (fields) { 237 funcInfo = { name: fields[3], start: parseInt(fields[1], 16) + this.fileOffsetMinusVma }; 238 if (fields[2]) { 239 funcInfo.size = parseInt(fields[2], 16); 240 } 241 } 242 return funcInfo; 243 } 244} 245 246export class RemoteLinuxCppEntriesProvider extends LinuxCppEntriesProvider { 247 async loadSymbols(libName) { 248 return this.loadSymbolsRemote('linux', libName); 249 } 250} 251 252export class MacOSCppEntriesProvider extends LinuxCppEntriesProvider { 253 constructor(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary) { 254 super(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary); 255 // Note an empty group. It is required, as LinuxCppEntriesProvider expects 3 groups. 256 this.FUNC_RE = /^([0-9a-fA-F]{8,16})() (.*)$/; 257 } 258 259 async loadSymbols(libName) { 260 this.parsePos = 0; 261 libName = this.targetRootFS + libName; 262 263 // It seems that in OS X `nm` thinks that `-f` is a format option, not a 264 // "flat" display option flag. 265 try { 266 this.symbols = [ 267 os.system(this.nmExec, ['--demangle', '-n', libName], -1, -1), 268 '']; 269 } catch (e) { 270 // If the library cannot be found on this system let's not panic. 271 this.symbols = ''; 272 } 273 } 274} 275 276export class RemoteMacOSCppEntriesProvider extends LinuxCppEntriesProvider { 277 async loadSymbols(libName) { 278 return this.loadSymbolsRemote('macos', libName); 279 } 280} 281 282 283export class WindowsCppEntriesProvider extends CppEntriesProvider { 284 constructor(_ignored_nmExec, _ignored_objdumpExec, targetRootFS, 285 _ignored_apkEmbeddedLibrary) { 286 super(); 287 this.targetRootFS = targetRootFS; 288 this.symbols = ''; 289 this.parsePos = 0; 290 } 291 292 static FILENAME_RE = /^(.*)\.([^.]+)$/; 293 static FUNC_RE = 294 /^\s+0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/; 295 static IMAGE_BASE_RE = 296 /^\s+0000:00000000\s+___ImageBase\s+([0-9a-fA-F]{8}).*$/; 297 // This is almost a constant on Windows. 298 static EXE_IMAGE_BASE = 0x00400000; 299 300 loadSymbols(libName) { 301 libName = this.targetRootFS + libName; 302 const fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE); 303 if (!fileNameFields) return; 304 const mapFileName = `${fileNameFields[1]}.map`; 305 this.moduleType_ = fileNameFields[2].toLowerCase(); 306 try { 307 this.symbols = read(mapFileName); 308 } catch (e) { 309 // If .map file cannot be found let's not panic. 310 this.symbols = ''; 311 } 312 } 313 314 parseNextLine() { 315 const lineEndPos = this.symbols.indexOf('\r\n', this.parsePos); 316 if (lineEndPos == -1) { 317 return false; 318 } 319 320 const line = this.symbols.substring(this.parsePos, lineEndPos); 321 this.parsePos = lineEndPos + 2; 322 323 // Image base entry is above all other symbols, so we can just 324 // terminate parsing. 325 const imageBaseFields = line.match(WindowsCppEntriesProvider.IMAGE_BASE_RE); 326 if (imageBaseFields) { 327 const imageBase = parseInt(imageBaseFields[1], 16); 328 if ((this.moduleType_ == 'exe') != 329 (imageBase == WindowsCppEntriesProvider.EXE_IMAGE_BASE)) { 330 return false; 331 } 332 } 333 334 const fields = line.match(WindowsCppEntriesProvider.FUNC_RE); 335 return fields ? 336 { name: this.unmangleName(fields[1]), start: parseInt(fields[2], 16) } : 337 null; 338 } 339 340 /** 341 * Performs very simple unmangling of C++ names. 342 * 343 * Does not handle arguments and template arguments. The mangled names have 344 * the form: 345 * 346 * ?LookupInDescriptor@JSObject@internal@v8@@...arguments info... 347 */ 348 unmangleName(name) { 349 // Empty or non-mangled name. 350 if (name.length < 1 || name.charAt(0) != '?') return name; 351 const nameEndPos = name.indexOf('@@'); 352 const components = name.substring(1, nameEndPos).split('@'); 353 components.reverse(); 354 return components.join('::'); 355 } 356} 357 358 359export class ArgumentsProcessor extends BaseArgumentsProcessor { 360 getArgsDispatch() { 361 let dispatch = { 362 __proto__:null, 363 '-j': ['stateFilter', TickProcessor.VmStates.JS, 364 'Show only ticks from JS VM state'], 365 '-g': ['stateFilter', TickProcessor.VmStates.GC, 366 'Show only ticks from GC VM state'], 367 '-p': ['stateFilter', TickProcessor.VmStates.PARSER, 368 'Show only ticks from PARSER VM state'], 369 '-b': ['stateFilter', TickProcessor.VmStates.BYTECODE_COMPILER, 370 'Show only ticks from BYTECODE_COMPILER VM state'], 371 '-c': ['stateFilter', TickProcessor.VmStates.COMPILER, 372 'Show only ticks from COMPILER VM state'], 373 '-o': ['stateFilter', TickProcessor.VmStates.OTHER, 374 'Show only ticks from OTHER VM state'], 375 '-e': ['stateFilter', TickProcessor.VmStates.EXTERNAL, 376 'Show only ticks from EXTERNAL VM state'], 377 '--filter-runtime-timer': ['runtimeTimerFilter', null, 378 'Show only ticks matching the given runtime timer scope'], 379 '--call-graph-size': ['callGraphSize', TickProcessor.CALL_GRAPH_SIZE, 380 'Set the call graph size'], 381 '--ignore-unknown': ['ignoreUnknown', true, 382 'Exclude ticks of unknown code entries from processing'], 383 '--separate-ic': ['separateIc', parseBool, 384 'Separate IC entries'], 385 '--separate-bytecodes': ['separateBytecodes', parseBool, 386 'Separate Bytecode entries'], 387 '--separate-builtins': ['separateBuiltins', parseBool, 388 'Separate Builtin entries'], 389 '--separate-stubs': ['separateStubs', parseBool, 390 'Separate Stub entries'], 391 '--separate-baseline-handlers': ['separateBaselineHandlers', parseBool, 392 'Separate Baseline Handler entries'], 393 '--linux': ['platform', 'linux', 394 'Specify that we are running on *nix platform'], 395 '--windows': ['platform', 'windows', 396 'Specify that we are running on Windows platform'], 397 '--mac': ['platform', 'macos', 398 'Specify that we are running on Mac OS X platform'], 399 '--nm': ['nm', 'nm', 400 'Specify the \'nm\' executable to use (e.g. --nm=/my_dir/nm)'], 401 '--objdump': ['objdump', 'objdump', 402 'Specify the \'objdump\' executable to use (e.g. --objdump=/my_dir/objdump)'], 403 '--target': ['targetRootFS', '', 404 'Specify the target root directory for cross environment'], 405 '--apk-embedded-library': ['apkEmbeddedLibrary', '', 406 'Specify the path of the embedded library for Android traces'], 407 '--range': ['range', 'auto,auto', 408 'Specify the range limit as [start],[end]'], 409 '--distortion': ['distortion', 0, 410 'Specify the logging overhead in picoseconds'], 411 '--source-map': ['sourceMap', null, 412 'Specify the source map that should be used for output'], 413 '--timed-range': ['timedRange', true, 414 'Ignore ticks before first and after last Date.now() call'], 415 '--pairwise-timed-range': ['pairwiseTimedRange', true, 416 'Ignore ticks outside pairs of Date.now() calls'], 417 '--only-summary': ['onlySummary', true, 418 'Print only tick summary, exclude other information'], 419 '--serialize-vm-symbols': ['serializeVMSymbols', true, 420 'Print all C++ symbols and library addresses as JSON data'], 421 '--preprocess': ['preprocessJson', true, 422 'Preprocess for consumption with web interface'] 423 }; 424 dispatch['--js'] = dispatch['-j']; 425 dispatch['--gc'] = dispatch['-g']; 426 dispatch['--compiler'] = dispatch['-c']; 427 dispatch['--other'] = dispatch['-o']; 428 dispatch['--external'] = dispatch['-e']; 429 dispatch['--ptr'] = dispatch['--pairwise-timed-range']; 430 return dispatch; 431 } 432 433 getDefaultResults() { 434 return { 435 logFileName: 'v8.log', 436 platform: 'linux', 437 stateFilter: null, 438 callGraphSize: 5, 439 ignoreUnknown: false, 440 separateIc: true, 441 separateBytecodes: false, 442 separateBuiltins: true, 443 separateStubs: true, 444 separateBaselineHandlers: false, 445 preprocessJson: null, 446 sourceMap: null, 447 targetRootFS: '', 448 nm: 'nm', 449 objdump: 'objdump', 450 range: 'auto,auto', 451 distortion: 0, 452 timedRange: false, 453 pairwiseTimedRange: false, 454 onlySummary: false, 455 runtimeTimerFilter: null, 456 serializeVMSymbols: false, 457 }; 458 } 459} 460 461 462export class TickProcessor extends LogReader { 463 static EntriesProvider = { 464 'linux': LinuxCppEntriesProvider, 465 'windows': WindowsCppEntriesProvider, 466 'macos': MacOSCppEntriesProvider 467 }; 468 469 static fromParams(params, entriesProvider) { 470 if (entriesProvider == undefined) { 471 entriesProvider = new this.EntriesProvider[params.platform]( 472 params.nm, params.objdump, params.targetRootFS, 473 params.apkEmbeddedLibrary); 474 } 475 return new TickProcessor( 476 entriesProvider, 477 params.separateIc, 478 params.separateBytecodes, 479 params.separateBuiltins, 480 params.separateStubs, 481 params.separateBaselineHandlers, 482 params.callGraphSize, 483 params.ignoreUnknown, 484 params.stateFilter, 485 params.distortion, 486 params.range, 487 params.sourceMap, 488 params.timedRange, 489 params.pairwiseTimedRange, 490 params.onlySummary, 491 params.runtimeTimerFilter, 492 params.preprocessJson); 493 } 494 495 constructor( 496 cppEntriesProvider, 497 separateIc, 498 separateBytecodes, 499 separateBuiltins, 500 separateStubs, 501 separateBaselineHandlers, 502 callGraphSize, 503 ignoreUnknown, 504 stateFilter, 505 distortion, 506 range, 507 sourceMap, 508 timedRange, 509 pairwiseTimedRange, 510 onlySummary, 511 runtimeTimerFilter, 512 preprocessJson) { 513 super(timedRange, pairwiseTimedRange); 514 this.setDispatchTable({ 515 __proto__: null, 516 'shared-library': { 517 parsers: [parseString, parseInt, parseInt, parseInt], 518 processor: this.processSharedLibrary 519 }, 520 'code-creation': { 521 parsers: [parseString, parseInt, parseInt, parseInt, parseInt, 522 parseString, parseVarArgs], 523 processor: this.processCodeCreation 524 }, 525 'code-deopt': { 526 parsers: [parseInt, parseInt, parseInt, parseInt, parseInt, 527 parseString, parseString, parseString], 528 processor: this.processCodeDeopt 529 }, 530 'code-move': { 531 parsers: [parseInt, parseInt,], 532 processor: this.processCodeMove 533 }, 534 'code-delete': { 535 parsers: [parseInt], 536 processor: this.processCodeDelete 537 }, 538 'code-source-info': { 539 parsers: [parseInt, parseInt, parseInt, parseInt, parseString, 540 parseString, parseString], 541 processor: this.processCodeSourceInfo 542 }, 543 'script-source': { 544 parsers: [parseInt, parseString, parseString], 545 processor: this.processScriptSource 546 }, 547 'sfi-move': { 548 parsers: [parseInt, parseInt], 549 processor: this.processFunctionMove 550 }, 551 'active-runtime-timer': { 552 parsers: [parseString], 553 processor: this.processRuntimeTimerEvent 554 }, 555 'tick': { 556 parsers: [parseInt, parseInt, parseInt, 557 parseInt, parseInt, parseVarArgs], 558 processor: this.processTick 559 }, 560 'heap-sample-begin': { 561 parsers: [parseString, parseString, parseInt], 562 processor: this.processHeapSampleBegin 563 }, 564 'heap-sample-end': { 565 parsers: [parseString, parseString], 566 processor: this.processHeapSampleEnd 567 }, 568 'timer-event-start': { 569 parsers: [parseString, parseString, parseString], 570 processor: this.advanceDistortion 571 }, 572 'timer-event-end': { 573 parsers: [parseString, parseString, parseString], 574 processor: this.advanceDistortion 575 }, 576 // Ignored events. 577 'profiler': undefined, 578 'function-creation': undefined, 579 'function-move': undefined, 580 'function-delete': undefined, 581 'heap-sample-item': undefined, 582 'current-time': undefined, // Handled specially, not parsed. 583 // Obsolete row types. 584 'code-allocate': undefined, 585 'begin-code-region': undefined, 586 'end-code-region': undefined 587 }); 588 589 this.preprocessJson = preprocessJson; 590 this.cppEntriesProvider_ = cppEntriesProvider; 591 this.callGraphSize_ = callGraphSize; 592 this.ignoreUnknown_ = ignoreUnknown; 593 this.stateFilter_ = stateFilter; 594 this.runtimeTimerFilter_ = runtimeTimerFilter; 595 this.sourceMap = this.loadSourceMap(sourceMap); 596 const ticks = this.ticks_ = 597 { total: 0, unaccounted: 0, excluded: 0, gc: 0 }; 598 599 distortion = parseInt(distortion); 600 // Convert picoseconds to nanoseconds. 601 this.distortion_per_entry = isNaN(distortion) ? 0 : (distortion / 1000); 602 this.distortion = 0; 603 const rangelimits = range ? range.split(",") : []; 604 const range_start = parseInt(rangelimits[0]); 605 const range_end = parseInt(rangelimits[1]); 606 // Convert milliseconds to nanoseconds. 607 this.range_start = isNaN(range_start) ? -Infinity : (range_start * 1000); 608 this.range_end = isNaN(range_end) ? Infinity : (range_end * 1000) 609 610 V8Profile.prototype.handleUnknownCode = function ( 611 operation, addr, opt_stackPos) { 612 const op = Profile.Operation; 613 switch (operation) { 614 case op.MOVE: 615 printErr(`Code move event for unknown code: 0x${addr.toString(16)}`); 616 break; 617 case op.DELETE: 618 printErr(`Code delete event for unknown code: 0x${addr.toString(16)}`); 619 break; 620 case op.TICK: 621 // Only unknown PCs (the first frame) are reported as unaccounted, 622 // otherwise tick balance will be corrupted (this behavior is compatible 623 // with the original tickprocessor.py script.) 624 if (opt_stackPos == 0) { 625 ticks.unaccounted++; 626 } 627 break; 628 } 629 }; 630 631 if (preprocessJson) { 632 this.profile_ = new JsonProfile(); 633 } else { 634 this.profile_ = new V8Profile(separateIc, separateBytecodes, 635 separateBuiltins, separateStubs, separateBaselineHandlers); 636 } 637 this.codeTypes_ = {}; 638 // Count each tick as a time unit. 639 this.viewBuilder_ = new ViewBuilder(1); 640 this.lastLogFileName_ = null; 641 642 this.generation_ = 1; 643 this.currentProducerProfile_ = null; 644 this.onlySummary_ = onlySummary; 645 } 646 647 loadSourceMap(sourceMap) { 648 if (!sourceMap) return null; 649 // Overwrite the load function to load scripts synchronously. 650 WebInspector.SourceMap.load = (sourceMapURL) => { 651 const content = d8.file.read(sourceMapURL); 652 const sourceMapObject = JSON.parse(content); 653 return new SourceMap(sourceMapURL, sourceMapObject); 654 }; 655 return WebInspector.SourceMap.load(sourceMap); 656 } 657 658 static VmStates = { 659 JS: 0, 660 GC: 1, 661 PARSER: 2, 662 BYTECODE_COMPILER: 3, 663 // TODO(cbruni): add BASELINE_COMPILER 664 COMPILER: 4, 665 OTHER: 5, 666 EXTERNAL: 6, 667 IDLE: 7, 668 }; 669 670 static CodeTypes = { 671 CPP: 0, 672 SHARED_LIB: 1 673 }; 674 // Otherwise, this is JS-related code. We are not adding it to 675 // codeTypes_ map because there can be zillions of them. 676 677 static CALL_PROFILE_CUTOFF_PCT = 1.0; 678 static CALL_GRAPH_SIZE = 5; 679 680 /** 681 * @override 682 */ 683 printError(str) { 684 printErr(str); 685 } 686 687 setCodeType(name, type) { 688 this.codeTypes_[name] = TickProcessor.CodeTypes[type]; 689 } 690 691 isSharedLibrary(name) { 692 return this.codeTypes_[name] == TickProcessor.CodeTypes.SHARED_LIB; 693 } 694 695 isCppCode(name) { 696 return this.codeTypes_[name] == TickProcessor.CodeTypes.CPP; 697 } 698 699 isJsCode(name) { 700 return name !== "UNKNOWN" && !(name in this.codeTypes_); 701 } 702 703 async processLogFile(fileName) { 704 this.lastLogFileName_ = fileName; 705 let line; 706 while (line = readline()) { 707 await this.processLogLine(line); 708 } 709 } 710 711 async processLogFileInTest(fileName) { 712 // Hack file name to avoid dealing with platform specifics. 713 this.lastLogFileName_ = 'v8.log'; 714 const contents = d8.file.read(fileName); 715 await this.processLogChunk(contents); 716 } 717 718 processSharedLibrary(name, startAddr, endAddr, aslrSlide) { 719 const entry = this.profile_.addLibrary(name, startAddr, endAddr, aslrSlide); 720 this.setCodeType(entry.getName(), 'SHARED_LIB'); 721 this.cppEntriesProvider_.parseVmSymbols( 722 name, startAddr, endAddr, aslrSlide, (fName, fStart, fEnd) => { 723 this.profile_.addStaticCode(fName, fStart, fEnd); 724 this.setCodeType(fName, 'CPP'); 725 }); 726 } 727 728 processCodeCreation(type, kind, timestamp, start, size, name, maybe_func) { 729 if (type != 'RegExp' && maybe_func.length) { 730 const funcAddr = parseInt(maybe_func[0]); 731 const state = Profile.parseState(maybe_func[1]); 732 this.profile_.addFuncCode(type, name, timestamp, start, size, funcAddr, state); 733 } else { 734 this.profile_.addCode(type, name, timestamp, start, size); 735 } 736 } 737 738 processCodeDeopt( 739 timestamp, size, code, inliningId, scriptOffset, bailoutType, 740 sourcePositionText, deoptReasonText) { 741 this.profile_.deoptCode(timestamp, code, inliningId, scriptOffset, 742 bailoutType, sourcePositionText, deoptReasonText); 743 } 744 745 processCodeMove(from, to) { 746 this.profile_.moveCode(from, to); 747 } 748 749 processCodeDelete(start) { 750 this.profile_.deleteCode(start); 751 } 752 753 processCodeSourceInfo( 754 start, script, startPos, endPos, sourcePositions, inliningPositions, 755 inlinedFunctions) { 756 this.profile_.addSourcePositions(start, script, startPos, 757 endPos, sourcePositions, inliningPositions, inlinedFunctions); 758 } 759 760 processScriptSource(script, url, source) { 761 this.profile_.addScriptSource(script, url, source); 762 } 763 764 processFunctionMove(from, to) { 765 this.profile_.moveFunc(from, to); 766 } 767 768 includeTick(vmState) { 769 if (this.stateFilter_ !== null) { 770 return this.stateFilter_ == vmState; 771 } else if (this.runtimeTimerFilter_ !== null) { 772 return this.currentRuntimeTimer == this.runtimeTimerFilter_; 773 } 774 return true; 775 } 776 777 processRuntimeTimerEvent(name) { 778 this.currentRuntimeTimer = name; 779 } 780 781 processTick(pc, 782 ns_since_start, 783 is_external_callback, 784 tos_or_external_callback, 785 vmState, 786 stack) { 787 this.distortion += this.distortion_per_entry; 788 ns_since_start -= this.distortion; 789 if (ns_since_start < this.range_start || ns_since_start > this.range_end) { 790 return; 791 } 792 this.ticks_.total++; 793 if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++; 794 if (!this.includeTick(vmState)) { 795 this.ticks_.excluded++; 796 return; 797 } 798 if (is_external_callback) { 799 // Don't use PC when in external callback code, as it can point 800 // inside callback's code, and we will erroneously report 801 // that a callback calls itself. Instead we use tos_or_external_callback, 802 // as simply resetting PC will produce unaccounted ticks. 803 pc = tos_or_external_callback; 804 tos_or_external_callback = 0; 805 } else if (tos_or_external_callback) { 806 // Find out, if top of stack was pointing inside a JS function 807 // meaning that we have encountered a frameless invocation. 808 const funcEntry = this.profile_.findEntry(tos_or_external_callback); 809 if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction()) { 810 tos_or_external_callback = 0; 811 } 812 } 813 814 this.profile_.recordTick( 815 ns_since_start, vmState, 816 this.processStack(pc, tos_or_external_callback, stack)); 817 } 818 819 advanceDistortion() { 820 this.distortion += this.distortion_per_entry; 821 } 822 823 processHeapSampleBegin(space, state, ticks) { 824 if (space != 'Heap') return; 825 this.currentProducerProfile_ = new CallTree(); 826 } 827 828 processHeapSampleEnd(space, state) { 829 if (space != 'Heap' || !this.currentProducerProfile_) return; 830 831 print(`Generation ${this.generation_}:`); 832 const tree = this.currentProducerProfile_; 833 tree.computeTotalWeights(); 834 const producersView = this.viewBuilder_.buildView(tree); 835 // Sort by total time, desc, then by name, desc. 836 producersView.sort((rec1, rec2) => 837 rec2.totalTime - rec1.totalTime || 838 (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1)); 839 this.printHeavyProfile(producersView.head.children); 840 841 this.currentProducerProfile_ = null; 842 this.generation_++; 843 } 844 845 printVMSymbols() { 846 console.log( 847 JSON.stringify(this.profile_.serializeVMSymbols())); 848 } 849 850 printStatistics() { 851 if (this.preprocessJson) { 852 this.profile_.writeJson(); 853 return; 854 } 855 856 print(`Statistical profiling result from ${this.lastLogFileName_}` + 857 `, (${this.ticks_.total} ticks, ${this.ticks_.unaccounted} unaccounted, ` + 858 `${this.ticks_.excluded} excluded).`); 859 860 861 if (this.ticks_.total == 0) return; 862 863 const flatProfile = this.profile_.getFlatProfile(); 864 const flatView = this.viewBuilder_.buildView(flatProfile); 865 // Sort by self time, desc, then by name, desc. 866 flatView.sort((rec1, rec2) => 867 rec2.selfTime - rec1.selfTime || 868 (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1)); 869 let totalTicks = this.ticks_.total; 870 if (this.ignoreUnknown_) { 871 totalTicks -= this.ticks_.unaccounted; 872 } 873 const printAllTicks = !this.onlySummary_; 874 875 // Count library ticks 876 const flatViewNodes = flatView.head.children; 877 878 let libraryTicks = 0; 879 if (printAllTicks) this.printHeader('Shared libraries'); 880 this.printEntries(flatViewNodes, totalTicks, null, 881 name => this.isSharedLibrary(name), 882 (rec) => { libraryTicks += rec.selfTime; }, printAllTicks); 883 const nonLibraryTicks = totalTicks - libraryTicks; 884 885 let jsTicks = 0; 886 if (printAllTicks) this.printHeader('JavaScript'); 887 this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks, 888 name => this.isJsCode(name), 889 (rec) => { jsTicks += rec.selfTime; }, printAllTicks); 890 891 let cppTicks = 0; 892 if (printAllTicks) this.printHeader('C++'); 893 this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks, 894 name => this.isCppCode(name), 895 (rec) => { cppTicks += rec.selfTime; }, printAllTicks); 896 897 this.printHeader('Summary'); 898 this.printLine('JavaScript', jsTicks, totalTicks, nonLibraryTicks); 899 this.printLine('C++', cppTicks, totalTicks, nonLibraryTicks); 900 this.printLine('GC', this.ticks_.gc, totalTicks, nonLibraryTicks); 901 this.printLine('Shared libraries', libraryTicks, totalTicks, null); 902 if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) { 903 this.printLine('Unaccounted', this.ticks_.unaccounted, 904 this.ticks_.total, null); 905 } 906 907 if (printAllTicks) { 908 print('\n [C++ entry points]:'); 909 print(' ticks cpp total name'); 910 const c_entry_functions = this.profile_.getCEntryProfile(); 911 const total_c_entry = c_entry_functions[0].ticks; 912 for (let i = 1; i < c_entry_functions.length; i++) { 913 const c = c_entry_functions[i]; 914 this.printLine(c.name, c.ticks, total_c_entry, totalTicks); 915 } 916 917 this.printHeavyProfHeader(); 918 const heavyProfile = this.profile_.getBottomUpProfile(); 919 const heavyView = this.viewBuilder_.buildView(heavyProfile); 920 // To show the same percentages as in the flat profile. 921 heavyView.head.totalTime = totalTicks; 922 // Sort by total time, desc, then by name, desc. 923 heavyView.sort((rec1, rec2) => 924 rec2.totalTime - rec1.totalTime || 925 (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1)); 926 this.printHeavyProfile(heavyView.head.children); 927 } 928 } 929 930 printHeader(headerTitle) { 931 print(`\n [${headerTitle}]:`); 932 print(' ticks total nonlib name'); 933 } 934 935 printLine( 936 entry, ticks, totalTicks, nonLibTicks) { 937 const pct = ticks * 100 / totalTicks; 938 const nonLibPct = nonLibTicks != null 939 ? `${(ticks * 100 / nonLibTicks).toFixed(1).toString().padStart(5)}% ` 940 : ' '; 941 print(`${` ${ticks.toString().padStart(5)} ` + 942 pct.toFixed(1).toString().padStart(5)}% ${nonLibPct}${entry}`); 943 } 944 945 printHeavyProfHeader() { 946 print('\n [Bottom up (heavy) profile]:'); 947 print(' Note: percentage shows a share of a particular caller in the ' + 948 'total\n' + 949 ' amount of its parent calls.'); 950 print(` Callers occupying less than ${TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1)}% are not shown.\n`); 951 print(' ticks parent name'); 952 } 953 954 processProfile(profile, filterP, func) { 955 for (let i = 0, n = profile.length; i < n; ++i) { 956 const rec = profile[i]; 957 if (!filterP(rec.internalFuncName)) { 958 continue; 959 } 960 func(rec); 961 } 962 } 963 964 getLineAndColumn(name) { 965 const re = /:([0-9]+):([0-9]+)$/; 966 const array = re.exec(name); 967 if (!array) { 968 return null; 969 } 970 return { line: array[1], column: array[2] }; 971 } 972 973 hasSourceMap() { 974 return this.sourceMap != null; 975 } 976 977 formatFunctionName(funcName) { 978 if (!this.hasSourceMap()) { 979 return funcName; 980 } 981 const lc = this.getLineAndColumn(funcName); 982 if (lc == null) { 983 return funcName; 984 } 985 // in source maps lines and columns are zero based 986 const lineNumber = lc.line - 1; 987 const column = lc.column - 1; 988 const entry = this.sourceMap.findEntry(lineNumber, column); 989 const sourceFile = entry[2]; 990 const sourceLine = entry[3] + 1; 991 const sourceColumn = entry[4] + 1; 992 993 return `${sourceFile}:${sourceLine}:${sourceColumn} -> ${funcName}`; 994 } 995 996 printEntries( 997 profile, totalTicks, nonLibTicks, filterP, callback, printAllTicks) { 998 this.processProfile(profile, filterP, (rec) => { 999 if (rec.selfTime == 0) return; 1000 callback(rec); 1001 const funcName = this.formatFunctionName(rec.internalFuncName); 1002 if (printAllTicks) { 1003 this.printLine(funcName, rec.selfTime, totalTicks, nonLibTicks); 1004 } 1005 }); 1006 } 1007 1008 printHeavyProfile(profile, opt_indent) { 1009 const indent = opt_indent || 0; 1010 const indentStr = ''.padStart(indent); 1011 this.processProfile(profile, () => true, (rec) => { 1012 // Cut off too infrequent callers. 1013 if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return; 1014 const funcName = this.formatFunctionName(rec.internalFuncName); 1015 print(`${` ${rec.totalTime.toString().padStart(5)} ` + 1016 rec.parentTotalPercent.toFixed(1).toString().padStart(5)}% ${indentStr}${funcName}`); 1017 // Limit backtrace depth. 1018 if (indent < 2 * this.callGraphSize_) { 1019 this.printHeavyProfile(rec.children, indent + 2); 1020 } 1021 // Delimit top-level functions. 1022 if (indent == 0) print(''); 1023 }); 1024 } 1025} 1026