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