• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
28function inherits(childCtor, parentCtor) {
29  childCtor.prototype.__proto__ = parentCtor.prototype;
30};
31
32
33function 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};
50inherits(V8Profile, Profile);
51
52
53V8Profile.IC_RE =
54    /^(LoadGlobalIC: )|(Handler: )|(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Load|Store)IC_)/;
55V8Profile.BYTECODES_RE = /^(BytecodeHandler: )/
56V8Profile.BUILTINS_RE = /^(Builtin: )/
57V8Profile.STUBS_RE = /^(Stub: )/
58
59
60/**
61 * A thin wrapper around shell's 'read' function showing a file name on error.
62 */
63function 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 */
76function 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
86function 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};
213inherits(TickProcessor, LogReader);
214
215
216TickProcessor.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
228TickProcessor.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
236TickProcessor.CALL_PROFILE_CUTOFF_PCT = 1.0;
237
238TickProcessor.CALL_GRAPH_SIZE = 5;
239
240/**
241 * @override
242 */
243TickProcessor.prototype.printError = function(str) {
244  printErr(str);
245};
246
247
248TickProcessor.prototype.setCodeType = function(name, type) {
249  this.codeTypes_[name] = TickProcessor.CodeTypes[type];
250};
251
252
253TickProcessor.prototype.isSharedLibrary = function(name) {
254  return this.codeTypes_[name] == TickProcessor.CodeTypes.SHARED_LIB;
255};
256
257
258TickProcessor.prototype.isCppCode = function(name) {
259  return this.codeTypes_[name] == TickProcessor.CodeTypes.CPP;
260};
261
262
263TickProcessor.prototype.isJsCode = function(name) {
264  return name !== "UNKNOWN" && !(name in this.codeTypes_);
265};
266
267
268TickProcessor.prototype.processLogFile = function(fileName) {
269  this.lastLogFileName_ = fileName;
270  var line;
271  while (line = readline()) {
272    this.processLogLine(line);
273  }
274};
275
276
277TickProcessor.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
285TickProcessor.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
299TickProcessor.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
311TickProcessor.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
319TickProcessor.prototype.processCodeMove = function(from, to) {
320  this.profile_.moveCode(from, to);
321};
322
323TickProcessor.prototype.processCodeDelete = function(start) {
324  this.profile_.deleteCode(start);
325};
326
327TickProcessor.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
334TickProcessor.prototype.processScriptSource = function(script, url, source) {
335  this.profile_.addScriptSource(script, url, source);
336};
337
338TickProcessor.prototype.processFunctionMove = function(from, to) {
339  this.profile_.moveFunc(from, to);
340};
341
342
343TickProcessor.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
352TickProcessor.prototype.processRuntimeTimerEvent = function(name) {
353  this.currentRuntimeTimer = name;
354}
355
356TickProcessor.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
395TickProcessor.prototype.advanceDistortion = function() {
396  this.distortion += this.distortion_per_entry;
397}
398
399
400TickProcessor.prototype.processHeapSampleBegin = function(space, state, ticks) {
401  if (space != 'Heap') return;
402  this.currentProducerProfile_ = new CallTree();
403};
404
405
406TickProcessor.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
424TickProcessor.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
506function 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
519TickProcessor.prototype.printHeader = function(headerTitle) {
520  print('\n [' + headerTitle + ']:');
521  print('   ticks  total  nonlib   name');
522};
523
524
525TickProcessor.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
537TickProcessor.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
549TickProcessor.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
560TickProcessor.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
569TickProcessor.prototype.hasSourceMap = function() {
570  return this.sourceMap != null;
571};
572
573
574TickProcessor.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
593TickProcessor.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
607TickProcessor.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
630function CppEntriesProvider() {
631};
632
633
634CppEntriesProvider.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
679CppEntriesProvider.prototype.loadSymbols = function(libName) {
680};
681
682
683CppEntriesProvider.prototype.parseNextLine = function() {
684  return false;
685};
686
687
688function 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};
695inherits(UnixCppEntriesProvider, CppEntriesProvider);
696
697
698UnixCppEntriesProvider.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
713UnixCppEntriesProvider.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
738function 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};
743inherits(MacCppEntriesProvider, UnixCppEntriesProvider);
744
745
746MacCppEntriesProvider.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
761function WindowsCppEntriesProvider(_ignored_nmExec, targetRootFS) {
762  this.targetRootFS = targetRootFS;
763  this.symbols = '';
764  this.parsePos = 0;
765};
766inherits(WindowsCppEntriesProvider, CppEntriesProvider);
767
768
769WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.([^.]+)$/;
770
771
772WindowsCppEntriesProvider.FUNC_RE =
773    /^\s+0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/;
774
775
776WindowsCppEntriesProvider.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.
781WindowsCppEntriesProvider.EXE_IMAGE_BASE = 0x00400000;
782
783
784WindowsCppEntriesProvider.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
799WindowsCppEntriesProvider.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 */
834WindowsCppEntriesProvider.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
844class 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