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