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