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