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