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