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