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 
     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