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