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 28function inherits(childCtor, parentCtor) { 29 childCtor.prototype.__proto__ = parentCtor.prototype; 30}; 31 32 33function V8Profile(separateIc) { 34 Profile.call(this); 35 if (!separateIc) { 36 this.skipThisFunction = function(name) { return V8Profile.IC_RE.test(name); }; 37 } 38}; 39inherits(V8Profile, Profile); 40 41 42V8Profile.IC_RE = 43 /^(LoadGlobalIC: )|(Handler: )|(Stub: )|(Builtin: )|(BytecodeHandler: )|(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Load|Store)IC_)/; 44 45 46/** 47 * A thin wrapper around shell's 'read' function showing a file name on error. 48 */ 49function readFile(fileName) { 50 try { 51 return read(fileName); 52 } catch (e) { 53 print(fileName + ': ' + (e.message || e)); 54 throw e; 55 } 56} 57 58 59/** 60 * Parser for dynamic code optimization state. 61 */ 62function parseState(s) { 63 switch (s) { 64 case "": return Profile.CodeState.COMPILED; 65 case "~": return Profile.CodeState.OPTIMIZABLE; 66 case "*": return Profile.CodeState.OPTIMIZED; 67 } 68 throw new Error("unknown code state: " + s); 69} 70 71 72function TickProcessor( 73 cppEntriesProvider, 74 separateIc, 75 callGraphSize, 76 ignoreUnknown, 77 stateFilter, 78 distortion, 79 range, 80 sourceMap, 81 timedRange, 82 pairwiseTimedRange, 83 onlySummary, 84 runtimeTimerFilter) { 85 LogReader.call(this, { 86 'shared-library': { parsers: [null, parseInt, parseInt, parseInt], 87 processor: this.processSharedLibrary }, 88 'code-creation': { 89 parsers: [null, parseInt, parseInt, parseInt, null, 'var-args'], 90 processor: this.processCodeCreation }, 91 'code-move': { parsers: [parseInt, parseInt], 92 processor: this.processCodeMove }, 93 'code-delete': { parsers: [parseInt], 94 processor: this.processCodeDelete }, 95 'sfi-move': { parsers: [parseInt, parseInt], 96 processor: this.processFunctionMove }, 97 'active-runtime-timer': { 98 parsers: [null], 99 processor: this.processRuntimeTimerEvent }, 100 'tick': { 101 parsers: [parseInt, parseInt, parseInt, 102 parseInt, parseInt, 'var-args'], 103 processor: this.processTick }, 104 'heap-sample-begin': { parsers: [null, null, parseInt], 105 processor: this.processHeapSampleBegin }, 106 'heap-sample-end': { parsers: [null, null], 107 processor: this.processHeapSampleEnd }, 108 'timer-event-start' : { parsers: [null, null, null], 109 processor: this.advanceDistortion }, 110 'timer-event-end' : { parsers: [null, null, null], 111 processor: this.advanceDistortion }, 112 // Ignored events. 113 'profiler': null, 114 'function-creation': null, 115 'function-move': null, 116 'function-delete': null, 117 'heap-sample-item': null, 118 'current-time': null, // Handled specially, not parsed. 119 // Obsolete row types. 120 'code-allocate': null, 121 'begin-code-region': null, 122 'end-code-region': null }, 123 timedRange, 124 pairwiseTimedRange); 125 126 this.cppEntriesProvider_ = cppEntriesProvider; 127 this.callGraphSize_ = callGraphSize; 128 this.ignoreUnknown_ = ignoreUnknown; 129 this.stateFilter_ = stateFilter; 130 this.runtimeTimerFilter_ = runtimeTimerFilter; 131 this.sourceMap = sourceMap; 132 this.deserializedEntriesNames_ = []; 133 var ticks = this.ticks_ = 134 { total: 0, unaccounted: 0, excluded: 0, gc: 0 }; 135 136 distortion = parseInt(distortion); 137 // Convert picoseconds to nanoseconds. 138 this.distortion_per_entry = isNaN(distortion) ? 0 : (distortion / 1000); 139 this.distortion = 0; 140 var rangelimits = range ? range.split(",") : []; 141 var range_start = parseInt(rangelimits[0]); 142 var range_end = parseInt(rangelimits[1]); 143 // Convert milliseconds to nanoseconds. 144 this.range_start = isNaN(range_start) ? -Infinity : (range_start * 1000); 145 this.range_end = isNaN(range_end) ? Infinity : (range_end * 1000) 146 147 V8Profile.prototype.handleUnknownCode = function( 148 operation, addr, opt_stackPos) { 149 var op = Profile.Operation; 150 switch (operation) { 151 case op.MOVE: 152 print('Code move event for unknown code: 0x' + addr.toString(16)); 153 break; 154 case op.DELETE: 155 print('Code delete event for unknown code: 0x' + addr.toString(16)); 156 break; 157 case op.TICK: 158 // Only unknown PCs (the first frame) are reported as unaccounted, 159 // otherwise tick balance will be corrupted (this behavior is compatible 160 // with the original tickprocessor.py script.) 161 if (opt_stackPos == 0) { 162 ticks.unaccounted++; 163 } 164 break; 165 } 166 }; 167 168 this.profile_ = new V8Profile(separateIc); 169 this.codeTypes_ = {}; 170 // Count each tick as a time unit. 171 this.viewBuilder_ = new ViewBuilder(1); 172 this.lastLogFileName_ = null; 173 174 this.generation_ = 1; 175 this.currentProducerProfile_ = null; 176 this.onlySummary_ = onlySummary; 177}; 178inherits(TickProcessor, LogReader); 179 180 181TickProcessor.VmStates = { 182 JS: 0, 183 GC: 1, 184 COMPILER: 2, 185 OTHER: 3, 186 EXTERNAL: 4, 187 IDLE: 5 188}; 189 190 191TickProcessor.CodeTypes = { 192 CPP: 0, 193 SHARED_LIB: 1 194}; 195// Otherwise, this is JS-related code. We are not adding it to 196// codeTypes_ map because there can be zillions of them. 197 198 199TickProcessor.CALL_PROFILE_CUTOFF_PCT = 2.0; 200 201TickProcessor.CALL_GRAPH_SIZE = 5; 202 203/** 204 * @override 205 */ 206TickProcessor.prototype.printError = function(str) { 207 print(str); 208}; 209 210 211TickProcessor.prototype.setCodeType = function(name, type) { 212 this.codeTypes_[name] = TickProcessor.CodeTypes[type]; 213}; 214 215 216TickProcessor.prototype.isSharedLibrary = function(name) { 217 return this.codeTypes_[name] == TickProcessor.CodeTypes.SHARED_LIB; 218}; 219 220 221TickProcessor.prototype.isCppCode = function(name) { 222 return this.codeTypes_[name] == TickProcessor.CodeTypes.CPP; 223}; 224 225 226TickProcessor.prototype.isJsCode = function(name) { 227 return name !== "UNKNOWN" && !(name in this.codeTypes_); 228}; 229 230 231TickProcessor.prototype.processLogFile = function(fileName) { 232 this.lastLogFileName_ = fileName; 233 var line; 234 while (line = readline()) { 235 this.processLogLine(line); 236 } 237}; 238 239 240TickProcessor.prototype.processLogFileInTest = function(fileName) { 241 // Hack file name to avoid dealing with platform specifics. 242 this.lastLogFileName_ = 'v8.log'; 243 var contents = readFile(fileName); 244 this.processLogChunk(contents); 245}; 246 247 248TickProcessor.prototype.processSharedLibrary = function( 249 name, startAddr, endAddr, aslrSlide) { 250 var entry = this.profile_.addLibrary(name, startAddr, endAddr, aslrSlide); 251 this.setCodeType(entry.getName(), 'SHARED_LIB'); 252 253 var self = this; 254 var libFuncs = this.cppEntriesProvider_.parseVmSymbols( 255 name, startAddr, endAddr, aslrSlide, function(fName, fStart, fEnd) { 256 self.profile_.addStaticCode(fName, fStart, fEnd); 257 self.setCodeType(fName, 'CPP'); 258 }); 259}; 260 261 262TickProcessor.prototype.processCodeCreation = function( 263 type, kind, start, size, name, maybe_func) { 264 name = this.deserializedEntriesNames_[start] || name; 265 if (maybe_func.length) { 266 var funcAddr = parseInt(maybe_func[0]); 267 var state = parseState(maybe_func[1]); 268 this.profile_.addFuncCode(type, name, start, size, funcAddr, state); 269 } else { 270 this.profile_.addCode(type, name, start, size); 271 } 272}; 273 274 275TickProcessor.prototype.processCodeMove = function(from, to) { 276 this.profile_.moveCode(from, to); 277}; 278 279 280TickProcessor.prototype.processCodeDelete = function(start) { 281 this.profile_.deleteCode(start); 282}; 283 284 285TickProcessor.prototype.processFunctionMove = function(from, to) { 286 this.profile_.moveFunc(from, to); 287}; 288 289 290TickProcessor.prototype.includeTick = function(vmState) { 291 if (this.stateFilter_ !== null) { 292 return this.stateFilter_ == vmState; 293 } else if (this.runtimeTimerFilter_ !== null) { 294 return this.currentRuntimeTimer == this.runtimeTimerFilter_; 295 } 296 return true; 297}; 298 299TickProcessor.prototype.processRuntimeTimerEvent = function(name) { 300 this.currentRuntimeTimer = name; 301} 302 303TickProcessor.prototype.processTick = function(pc, 304 ns_since_start, 305 is_external_callback, 306 tos_or_external_callback, 307 vmState, 308 stack) { 309 this.distortion += this.distortion_per_entry; 310 ns_since_start -= this.distortion; 311 if (ns_since_start < this.range_start || ns_since_start > this.range_end) { 312 return; 313 } 314 this.ticks_.total++; 315 if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++; 316 if (!this.includeTick(vmState)) { 317 this.ticks_.excluded++; 318 return; 319 } 320 if (is_external_callback) { 321 // Don't use PC when in external callback code, as it can point 322 // inside callback's code, and we will erroneously report 323 // that a callback calls itself. Instead we use tos_or_external_callback, 324 // as simply resetting PC will produce unaccounted ticks. 325 pc = tos_or_external_callback; 326 tos_or_external_callback = 0; 327 } else if (tos_or_external_callback) { 328 // Find out, if top of stack was pointing inside a JS function 329 // meaning that we have encountered a frameless invocation. 330 var funcEntry = this.profile_.findEntry(tos_or_external_callback); 331 if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction()) { 332 tos_or_external_callback = 0; 333 } 334 } 335 336 this.profile_.recordTick(this.processStack(pc, tos_or_external_callback, stack)); 337}; 338 339 340TickProcessor.prototype.advanceDistortion = function() { 341 this.distortion += this.distortion_per_entry; 342} 343 344 345TickProcessor.prototype.processHeapSampleBegin = function(space, state, ticks) { 346 if (space != 'Heap') return; 347 this.currentProducerProfile_ = new CallTree(); 348}; 349 350 351TickProcessor.prototype.processHeapSampleEnd = function(space, state) { 352 if (space != 'Heap' || !this.currentProducerProfile_) return; 353 354 print('Generation ' + this.generation_ + ':'); 355 var tree = this.currentProducerProfile_; 356 tree.computeTotalWeights(); 357 var producersView = this.viewBuilder_.buildView(tree); 358 // Sort by total time, desc, then by name, desc. 359 producersView.sort(function(rec1, rec2) { 360 return rec2.totalTime - rec1.totalTime || 361 (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); 362 this.printHeavyProfile(producersView.head.children); 363 364 this.currentProducerProfile_ = null; 365 this.generation_++; 366}; 367 368 369TickProcessor.prototype.printStatistics = function() { 370 print('Statistical profiling result from ' + this.lastLogFileName_ + 371 ', (' + this.ticks_.total + 372 ' ticks, ' + this.ticks_.unaccounted + ' unaccounted, ' + 373 this.ticks_.excluded + ' excluded).'); 374 375 if (this.ticks_.total == 0) return; 376 377 var flatProfile = this.profile_.getFlatProfile(); 378 var flatView = this.viewBuilder_.buildView(flatProfile); 379 // Sort by self time, desc, then by name, desc. 380 flatView.sort(function(rec1, rec2) { 381 return rec2.selfTime - rec1.selfTime || 382 (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); 383 var totalTicks = this.ticks_.total; 384 if (this.ignoreUnknown_) { 385 totalTicks -= this.ticks_.unaccounted; 386 } 387 var printAllTicks = !this.onlySummary_; 388 389 // Count library ticks 390 var flatViewNodes = flatView.head.children; 391 var self = this; 392 393 var libraryTicks = 0; 394 if(printAllTicks) this.printHeader('Shared libraries'); 395 this.printEntries(flatViewNodes, totalTicks, null, 396 function(name) { return self.isSharedLibrary(name); }, 397 function(rec) { libraryTicks += rec.selfTime; }, printAllTicks); 398 var nonLibraryTicks = totalTicks - libraryTicks; 399 400 var jsTicks = 0; 401 if(printAllTicks) this.printHeader('JavaScript'); 402 this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks, 403 function(name) { return self.isJsCode(name); }, 404 function(rec) { jsTicks += rec.selfTime; }, printAllTicks); 405 406 var cppTicks = 0; 407 if(printAllTicks) this.printHeader('C++'); 408 this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks, 409 function(name) { return self.isCppCode(name); }, 410 function(rec) { cppTicks += rec.selfTime; }, printAllTicks); 411 412 this.printHeader('Summary'); 413 this.printLine('JavaScript', jsTicks, totalTicks, nonLibraryTicks); 414 this.printLine('C++', cppTicks, totalTicks, nonLibraryTicks); 415 this.printLine('GC', this.ticks_.gc, totalTicks, nonLibraryTicks); 416 this.printLine('Shared libraries', libraryTicks, totalTicks, null); 417 if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) { 418 this.printLine('Unaccounted', this.ticks_.unaccounted, 419 this.ticks_.total, null); 420 } 421 422 if(printAllTicks) { 423 print('\n [C++ entry points]:'); 424 print(' ticks cpp total name'); 425 var c_entry_functions = this.profile_.getCEntryProfile(); 426 var total_c_entry = c_entry_functions[0].ticks; 427 for (var i = 1; i < c_entry_functions.length; i++) { 428 c = c_entry_functions[i]; 429 this.printLine(c.name, c.ticks, total_c_entry, totalTicks); 430 } 431 432 this.printHeavyProfHeader(); 433 var heavyProfile = this.profile_.getBottomUpProfile(); 434 var heavyView = this.viewBuilder_.buildView(heavyProfile); 435 // To show the same percentages as in the flat profile. 436 heavyView.head.totalTime = totalTicks; 437 // Sort by total time, desc, then by name, desc. 438 heavyView.sort(function(rec1, rec2) { 439 return rec2.totalTime - rec1.totalTime || 440 (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); }); 441 this.printHeavyProfile(heavyView.head.children); 442 } 443}; 444 445 446function padLeft(s, len) { 447 s = s.toString(); 448 if (s.length < len) { 449 var padLength = len - s.length; 450 if (!(padLength in padLeft)) { 451 padLeft[padLength] = new Array(padLength + 1).join(' '); 452 } 453 s = padLeft[padLength] + s; 454 } 455 return s; 456}; 457 458 459TickProcessor.prototype.printHeader = function(headerTitle) { 460 print('\n [' + headerTitle + ']:'); 461 print(' ticks total nonlib name'); 462}; 463 464 465TickProcessor.prototype.printLine = function( 466 entry, ticks, totalTicks, nonLibTicks) { 467 var pct = ticks * 100 / totalTicks; 468 var nonLibPct = nonLibTicks != null 469 ? padLeft((ticks * 100 / nonLibTicks).toFixed(1), 5) + '% ' 470 : ' '; 471 print(' ' + padLeft(ticks, 5) + ' ' + 472 padLeft(pct.toFixed(1), 5) + '% ' + 473 nonLibPct + 474 entry); 475} 476 477TickProcessor.prototype.printHeavyProfHeader = function() { 478 print('\n [Bottom up (heavy) profile]:'); 479 print(' Note: percentage shows a share of a particular caller in the ' + 480 'total\n' + 481 ' amount of its parent calls.'); 482 print(' Callers occupying less than ' + 483 TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1) + 484 '% are not shown.\n'); 485 print(' ticks parent name'); 486}; 487 488 489TickProcessor.prototype.processProfile = function( 490 profile, filterP, func) { 491 for (var i = 0, n = profile.length; i < n; ++i) { 492 var rec = profile[i]; 493 if (!filterP(rec.internalFuncName)) { 494 continue; 495 } 496 func(rec); 497 } 498}; 499 500TickProcessor.prototype.getLineAndColumn = function(name) { 501 var re = /:([0-9]+):([0-9]+)$/; 502 var array = re.exec(name); 503 if (!array) { 504 return null; 505 } 506 return {line: array[1], column: array[2]}; 507} 508 509TickProcessor.prototype.hasSourceMap = function() { 510 return this.sourceMap != null; 511}; 512 513 514TickProcessor.prototype.formatFunctionName = function(funcName) { 515 if (!this.hasSourceMap()) { 516 return funcName; 517 } 518 var lc = this.getLineAndColumn(funcName); 519 if (lc == null) { 520 return funcName; 521 } 522 // in source maps lines and columns are zero based 523 var lineNumber = lc.line - 1; 524 var column = lc.column - 1; 525 var entry = this.sourceMap.findEntry(lineNumber, column); 526 var sourceFile = entry[2]; 527 var sourceLine = entry[3] + 1; 528 var sourceColumn = entry[4] + 1; 529 530 return sourceFile + ':' + sourceLine + ':' + sourceColumn + ' -> ' + funcName; 531}; 532 533TickProcessor.prototype.printEntries = function( 534 profile, totalTicks, nonLibTicks, filterP, callback, printAllTicks) { 535 var that = this; 536 this.processProfile(profile, filterP, function (rec) { 537 if (rec.selfTime == 0) return; 538 callback(rec); 539 var funcName = that.formatFunctionName(rec.internalFuncName); 540 if(printAllTicks) { 541 that.printLine(funcName, rec.selfTime, totalTicks, nonLibTicks); 542 } 543 }); 544}; 545 546 547TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) { 548 var self = this; 549 var indent = opt_indent || 0; 550 var indentStr = padLeft('', indent); 551 this.processProfile(profile, function() { return true; }, function (rec) { 552 // Cut off too infrequent callers. 553 if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return; 554 var funcName = self.formatFunctionName(rec.internalFuncName); 555 print(' ' + padLeft(rec.totalTime, 5) + ' ' + 556 padLeft(rec.parentTotalPercent.toFixed(1), 5) + '% ' + 557 indentStr + funcName); 558 // Limit backtrace depth. 559 if (indent < 2 * self.callGraphSize_) { 560 self.printHeavyProfile(rec.children, indent + 2); 561 } 562 // Delimit top-level functions. 563 if (indent == 0) { 564 print(''); 565 } 566 }); 567}; 568 569 570function CppEntriesProvider() { 571}; 572 573 574CppEntriesProvider.prototype.parseVmSymbols = function( 575 libName, libStart, libEnd, libASLRSlide, processorFunc) { 576 this.loadSymbols(libName); 577 578 var prevEntry; 579 580 function addEntry(funcInfo) { 581 // Several functions can be mapped onto the same address. To avoid 582 // creating zero-sized entries, skip such duplicates. 583 // Also double-check that function belongs to the library address space. 584 if (prevEntry && !prevEntry.end && 585 prevEntry.start < funcInfo.start && 586 prevEntry.start >= libStart && funcInfo.start <= libEnd) { 587 processorFunc(prevEntry.name, prevEntry.start, funcInfo.start); 588 } 589 if (funcInfo.end && 590 (!prevEntry || prevEntry.start != funcInfo.start) && 591 funcInfo.start >= libStart && funcInfo.end <= libEnd) { 592 processorFunc(funcInfo.name, funcInfo.start, funcInfo.end); 593 } 594 prevEntry = funcInfo; 595 } 596 597 while (true) { 598 var funcInfo = this.parseNextLine(); 599 if (funcInfo === null) { 600 continue; 601 } else if (funcInfo === false) { 602 break; 603 } 604 funcInfo.start += libASLRSlide; 605 if (funcInfo.start < libStart && funcInfo.start < libEnd - libStart) { 606 funcInfo.start += libStart; 607 } 608 if (funcInfo.size) { 609 funcInfo.end = funcInfo.start + funcInfo.size; 610 } 611 addEntry(funcInfo); 612 } 613 addEntry({name: '', start: libEnd}); 614}; 615 616 617CppEntriesProvider.prototype.loadSymbols = function(libName) { 618}; 619 620 621CppEntriesProvider.prototype.parseNextLine = function() { 622 return false; 623}; 624 625 626function UnixCppEntriesProvider(nmExec, targetRootFS) { 627 this.symbols = []; 628 this.parsePos = 0; 629 this.nmExec = nmExec; 630 this.targetRootFS = targetRootFS; 631 this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/; 632}; 633inherits(UnixCppEntriesProvider, CppEntriesProvider); 634 635 636UnixCppEntriesProvider.prototype.loadSymbols = function(libName) { 637 this.parsePos = 0; 638 libName = this.targetRootFS + libName; 639 try { 640 this.symbols = [ 641 os.system(this.nmExec, ['-C', '-n', '-S', libName], -1, -1), 642 os.system(this.nmExec, ['-C', '-n', '-S', '-D', libName], -1, -1) 643 ]; 644 } catch (e) { 645 // If the library cannot be found on this system let's not panic. 646 this.symbols = ['', '']; 647 } 648}; 649 650 651UnixCppEntriesProvider.prototype.parseNextLine = function() { 652 if (this.symbols.length == 0) { 653 return false; 654 } 655 var lineEndPos = this.symbols[0].indexOf('\n', this.parsePos); 656 if (lineEndPos == -1) { 657 this.symbols.shift(); 658 this.parsePos = 0; 659 return this.parseNextLine(); 660 } 661 662 var line = this.symbols[0].substring(this.parsePos, lineEndPos); 663 this.parsePos = lineEndPos + 1; 664 var fields = line.match(this.FUNC_RE); 665 var funcInfo = null; 666 if (fields) { 667 funcInfo = { name: fields[3], start: parseInt(fields[1], 16) }; 668 if (fields[2]) { 669 funcInfo.size = parseInt(fields[2], 16); 670 } 671 } 672 return funcInfo; 673}; 674 675 676function MacCppEntriesProvider(nmExec, targetRootFS) { 677 UnixCppEntriesProvider.call(this, nmExec, targetRootFS); 678 // Note an empty group. It is required, as UnixCppEntriesProvider expects 3 groups. 679 this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ()[iItT] (.*)$/; 680}; 681inherits(MacCppEntriesProvider, UnixCppEntriesProvider); 682 683 684MacCppEntriesProvider.prototype.loadSymbols = function(libName) { 685 this.parsePos = 0; 686 libName = this.targetRootFS + libName; 687 688 // It seems that in OS X `nm` thinks that `-f` is a format option, not a 689 // "flat" display option flag. 690 try { 691 this.symbols = [os.system(this.nmExec, ['-n', libName], -1, -1), '']; 692 } catch (e) { 693 // If the library cannot be found on this system let's not panic. 694 this.symbols = ''; 695 } 696}; 697 698 699function WindowsCppEntriesProvider(_ignored_nmExec, targetRootFS) { 700 this.targetRootFS = targetRootFS; 701 this.symbols = ''; 702 this.parsePos = 0; 703}; 704inherits(WindowsCppEntriesProvider, CppEntriesProvider); 705 706 707WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.([^.]+)$/; 708 709 710WindowsCppEntriesProvider.FUNC_RE = 711 /^\s+0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/; 712 713 714WindowsCppEntriesProvider.IMAGE_BASE_RE = 715 /^\s+0000:00000000\s+___ImageBase\s+([0-9a-fA-F]{8}).*$/; 716 717 718// This is almost a constant on Windows. 719WindowsCppEntriesProvider.EXE_IMAGE_BASE = 0x00400000; 720 721 722WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) { 723 libName = this.targetRootFS + libName; 724 var fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE); 725 if (!fileNameFields) return; 726 var mapFileName = fileNameFields[1] + '.map'; 727 this.moduleType_ = fileNameFields[2].toLowerCase(); 728 try { 729 this.symbols = read(mapFileName); 730 } catch (e) { 731 // If .map file cannot be found let's not panic. 732 this.symbols = ''; 733 } 734}; 735 736 737WindowsCppEntriesProvider.prototype.parseNextLine = function() { 738 var lineEndPos = this.symbols.indexOf('\r\n', this.parsePos); 739 if (lineEndPos == -1) { 740 return false; 741 } 742 743 var line = this.symbols.substring(this.parsePos, lineEndPos); 744 this.parsePos = lineEndPos + 2; 745 746 // Image base entry is above all other symbols, so we can just 747 // terminate parsing. 748 var imageBaseFields = line.match(WindowsCppEntriesProvider.IMAGE_BASE_RE); 749 if (imageBaseFields) { 750 var imageBase = parseInt(imageBaseFields[1], 16); 751 if ((this.moduleType_ == 'exe') != 752 (imageBase == WindowsCppEntriesProvider.EXE_IMAGE_BASE)) { 753 return false; 754 } 755 } 756 757 var fields = line.match(WindowsCppEntriesProvider.FUNC_RE); 758 return fields ? 759 { name: this.unmangleName(fields[1]), start: parseInt(fields[2], 16) } : 760 null; 761}; 762 763 764/** 765 * Performs very simple unmangling of C++ names. 766 * 767 * Does not handle arguments and template arguments. The mangled names have 768 * the form: 769 * 770 * ?LookupInDescriptor@JSObject@internal@v8@@...arguments info... 771 */ 772WindowsCppEntriesProvider.prototype.unmangleName = function(name) { 773 // Empty or non-mangled name. 774 if (name.length < 1 || name.charAt(0) != '?') return name; 775 var nameEndPos = name.indexOf('@@'); 776 var components = name.substring(1, nameEndPos).split('@'); 777 components.reverse(); 778 return components.join('::'); 779}; 780 781 782function ArgumentsProcessor(args) { 783 this.args_ = args; 784 this.result_ = ArgumentsProcessor.DEFAULTS; 785 786 this.argsDispatch_ = { 787 '-j': ['stateFilter', TickProcessor.VmStates.JS, 788 'Show only ticks from JS VM state'], 789 '-g': ['stateFilter', TickProcessor.VmStates.GC, 790 'Show only ticks from GC VM state'], 791 '-c': ['stateFilter', TickProcessor.VmStates.COMPILER, 792 'Show only ticks from COMPILER VM state'], 793 '-o': ['stateFilter', TickProcessor.VmStates.OTHER, 794 'Show only ticks from OTHER VM state'], 795 '-e': ['stateFilter', TickProcessor.VmStates.EXTERNAL, 796 'Show only ticks from EXTERNAL VM state'], 797 '--filter-runtime-timer': ['runtimeTimerFilter', null, 798 'Show only ticks matching the given runtime timer scope'], 799 '--call-graph-size': ['callGraphSize', TickProcessor.CALL_GRAPH_SIZE, 800 'Set the call graph size'], 801 '--ignore-unknown': ['ignoreUnknown', true, 802 'Exclude ticks of unknown code entries from processing'], 803 '--separate-ic': ['separateIc', true, 804 'Separate IC entries'], 805 '--unix': ['platform', 'unix', 806 'Specify that we are running on *nix platform'], 807 '--windows': ['platform', 'windows', 808 'Specify that we are running on Windows platform'], 809 '--mac': ['platform', 'mac', 810 'Specify that we are running on Mac OS X platform'], 811 '--nm': ['nm', 'nm', 812 'Specify the \'nm\' executable to use (e.g. --nm=/my_dir/nm)'], 813 '--target': ['targetRootFS', '', 814 'Specify the target root directory for cross environment'], 815 '--range': ['range', 'auto,auto', 816 'Specify the range limit as [start],[end]'], 817 '--distortion': ['distortion', 0, 818 'Specify the logging overhead in picoseconds'], 819 '--source-map': ['sourceMap', null, 820 'Specify the source map that should be used for output'], 821 '--timed-range': ['timedRange', true, 822 'Ignore ticks before first and after last Date.now() call'], 823 '--pairwise-timed-range': ['pairwiseTimedRange', true, 824 'Ignore ticks outside pairs of Date.now() calls'], 825 '--only-summary': ['onlySummary', true, 826 'Print only tick summary, exclude other information'] 827 }; 828 this.argsDispatch_['--js'] = this.argsDispatch_['-j']; 829 this.argsDispatch_['--gc'] = this.argsDispatch_['-g']; 830 this.argsDispatch_['--compiler'] = this.argsDispatch_['-c']; 831 this.argsDispatch_['--other'] = this.argsDispatch_['-o']; 832 this.argsDispatch_['--external'] = this.argsDispatch_['-e']; 833 this.argsDispatch_['--ptr'] = this.argsDispatch_['--pairwise-timed-range']; 834}; 835 836 837ArgumentsProcessor.DEFAULTS = { 838 logFileName: 'v8.log', 839 platform: 'unix', 840 stateFilter: null, 841 callGraphSize: 5, 842 ignoreUnknown: false, 843 separateIc: false, 844 targetRootFS: '', 845 nm: 'nm', 846 range: 'auto,auto', 847 distortion: 0, 848 timedRange: false, 849 pairwiseTimedRange: false, 850 onlySummary: false, 851 runtimeTimerFilter: null, 852}; 853 854 855ArgumentsProcessor.prototype.parse = function() { 856 while (this.args_.length) { 857 var arg = this.args_.shift(); 858 if (arg.charAt(0) != '-') { 859 this.result_.logFileName = arg; 860 continue; 861 } 862 var userValue = null; 863 var eqPos = arg.indexOf('='); 864 if (eqPos != -1) { 865 userValue = arg.substr(eqPos + 1); 866 arg = arg.substr(0, eqPos); 867 } 868 if (arg in this.argsDispatch_) { 869 var dispatch = this.argsDispatch_[arg]; 870 this.result_[dispatch[0]] = userValue == null ? dispatch[1] : userValue; 871 } else { 872 return false; 873 } 874 } 875 return true; 876}; 877 878 879ArgumentsProcessor.prototype.result = function() { 880 return this.result_; 881}; 882 883 884ArgumentsProcessor.prototype.printUsageAndExit = function() { 885 886 function padRight(s, len) { 887 s = s.toString(); 888 if (s.length < len) { 889 s = s + (new Array(len - s.length + 1).join(' ')); 890 } 891 return s; 892 } 893 894 print('Cmdline args: [options] [log-file-name]\n' + 895 'Default log file name is "' + 896 ArgumentsProcessor.DEFAULTS.logFileName + '".\n'); 897 print('Options:'); 898 for (var arg in this.argsDispatch_) { 899 var synonyms = [arg]; 900 var dispatch = this.argsDispatch_[arg]; 901 for (var synArg in this.argsDispatch_) { 902 if (arg !== synArg && dispatch === this.argsDispatch_[synArg]) { 903 synonyms.push(synArg); 904 delete this.argsDispatch_[synArg]; 905 } 906 } 907 print(' ' + padRight(synonyms.join(', '), 20) + " " + dispatch[2]); 908 } 909 quit(2); 910}; 911