• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2012 the V8 project authors. All rights reserved.
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6//     * Redistributions of source code must retain the above copyright
7//       notice, this list of conditions and the following disclaimer.
8//     * Redistributions in binary form must reproduce the above
9//       copyright notice, this list of conditions and the following
10//       disclaimer in the documentation and/or other materials provided
11//       with the distribution.
12//     * Neither the name of Google Inc. nor the names of its
13//       contributors may be used to endorse or promote products derived
14//       from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
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  var flatProfile = this.profile_.getFlatProfile();
445  var flatView = this.viewBuilder_.buildView(flatProfile);
446  // Sort by self time, desc, then by name, desc.
447  flatView.sort(function(rec1, rec2) {
448      return rec2.selfTime - rec1.selfTime ||
449          (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
450  var totalTicks = this.ticks_.total;
451  if (this.ignoreUnknown_) {
452    totalTicks -= this.ticks_.unaccounted;
453  }
454
455  // Count library ticks
456  var flatViewNodes = flatView.head.children;
457  var self = this;
458
459  var libraryTicks = 0;
460  this.printHeader('Shared libraries');
461  this.printEntries(flatViewNodes, totalTicks, null,
462      function(name) { return self.isSharedLibrary(name); },
463      function(rec) { libraryTicks += rec.selfTime; });
464  var nonLibraryTicks = totalTicks - libraryTicks;
465
466  var jsTicks = 0;
467  this.printHeader('JavaScript');
468  this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks,
469      function(name) { return self.isJsCode(name); },
470      function(rec) { jsTicks += rec.selfTime; });
471
472  var cppTicks = 0;
473  this.printHeader('C++');
474  this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks,
475      function(name) { return self.isCppCode(name); },
476      function(rec) { cppTicks += rec.selfTime; });
477
478  this.printHeader('Summary');
479  this.printLine('JavaScript', jsTicks, totalTicks, nonLibraryTicks);
480  this.printLine('C++', cppTicks, totalTicks, nonLibraryTicks);
481  this.printLine('GC', this.ticks_.gc, totalTicks, nonLibraryTicks);
482  this.printLine('Shared libraries', libraryTicks, totalTicks, null);
483  if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) {
484    this.printLine('Unaccounted', this.ticks_.unaccounted,
485                   this.ticks_.total, null);
486  }
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.printLine = function(
521    entry, ticks, totalTicks, nonLibTicks) {
522  var pct = ticks * 100 / totalTicks;
523  var nonLibPct = nonLibTicks != null
524      ? padLeft((ticks * 100 / nonLibTicks).toFixed(1), 5) + '%  '
525      : '        ';
526  print('  ' + padLeft(ticks, 5) + '  ' +
527        padLeft(pct.toFixed(1), 5) + '%  ' +
528        nonLibPct +
529        entry);
530}
531
532TickProcessor.prototype.printHeavyProfHeader = function() {
533  print('\n [Bottom up (heavy) profile]:');
534  print('  Note: percentage shows a share of a particular caller in the ' +
535        'total\n' +
536        '  amount of its parent calls.');
537  print('  Callers occupying less than ' +
538        TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1) +
539        '% are not shown.\n');
540  print('   ticks parent  name');
541};
542
543
544TickProcessor.prototype.processProfile = function(
545    profile, filterP, func) {
546  for (var i = 0, n = profile.length; i < n; ++i) {
547    var rec = profile[i];
548    if (!filterP(rec.internalFuncName)) {
549      continue;
550    }
551    func(rec);
552  }
553};
554
555TickProcessor.prototype.getLineAndColumn = function(name) {
556  var re = /:([0-9]+):([0-9]+)$/;
557  var array = re.exec(name);
558  if (!array) {
559    return null;
560  }
561  return {line: array[1], column: array[2]};
562}
563
564TickProcessor.prototype.hasSourceMap = function() {
565  return this.sourceMap != null;
566};
567
568
569TickProcessor.prototype.formatFunctionName = function(funcName) {
570  if (!this.hasSourceMap()) {
571    return funcName;
572  }
573  var lc = this.getLineAndColumn(funcName);
574  if (lc == null) {
575    return funcName;
576  }
577  // in source maps lines and columns are zero based
578  var lineNumber = lc.line - 1;
579  var column = lc.column - 1;
580  var entry = this.sourceMap.findEntry(lineNumber, column);
581  var sourceFile = entry[2];
582  var sourceLine = entry[3] + 1;
583  var sourceColumn = entry[4] + 1;
584
585  return sourceFile + ':' + sourceLine + ':' + sourceColumn + ' -> ' + funcName;
586};
587
588TickProcessor.prototype.printEntries = function(
589    profile, totalTicks, nonLibTicks, filterP, callback) {
590  var that = this;
591  this.processProfile(profile, filterP, function (rec) {
592    if (rec.selfTime == 0) return;
593    callback(rec);
594    var funcName = that.formatFunctionName(rec.internalFuncName);
595    that.printLine(funcName, rec.selfTime, totalTicks, nonLibTicks);
596  });
597};
598
599
600TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) {
601  var self = this;
602  var indent = opt_indent || 0;
603  var indentStr = padLeft('', indent);
604  this.processProfile(profile, function() { return true; }, function (rec) {
605    // Cut off too infrequent callers.
606    if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return;
607    var funcName = self.formatFunctionName(rec.internalFuncName);
608    print('  ' + padLeft(rec.totalTime, 5) + '  ' +
609          padLeft(rec.parentTotalPercent.toFixed(1), 5) + '%  ' +
610          indentStr + funcName);
611    // Limit backtrace depth.
612    if (indent < 2 * self.callGraphSize_) {
613      self.printHeavyProfile(rec.children, indent + 2);
614    }
615    // Delimit top-level functions.
616    if (indent == 0) {
617      print('');
618    }
619  });
620};
621
622
623function CppEntriesProvider() {
624};
625
626
627CppEntriesProvider.prototype.parseVmSymbols = function(
628    libName, libStart, libEnd, processorFunc) {
629  this.loadSymbols(libName);
630
631  var prevEntry;
632
633  function addEntry(funcInfo) {
634    // Several functions can be mapped onto the same address. To avoid
635    // creating zero-sized entries, skip such duplicates.
636    // Also double-check that function belongs to the library address space.
637    if (prevEntry && !prevEntry.end &&
638        prevEntry.start < funcInfo.start &&
639        prevEntry.start >= libStart && funcInfo.start <= libEnd) {
640      processorFunc(prevEntry.name, prevEntry.start, funcInfo.start);
641    }
642    if (funcInfo.end &&
643        (!prevEntry || prevEntry.start != funcInfo.start) &&
644        funcInfo.start >= libStart && funcInfo.end <= libEnd) {
645      processorFunc(funcInfo.name, funcInfo.start, funcInfo.end);
646    }
647    prevEntry = funcInfo;
648  }
649
650  while (true) {
651    var funcInfo = this.parseNextLine();
652    if (funcInfo === null) {
653      continue;
654    } else if (funcInfo === false) {
655      break;
656    }
657    if (funcInfo.start < libStart && funcInfo.start < libEnd - libStart) {
658      funcInfo.start += libStart;
659    }
660    if (funcInfo.size) {
661      funcInfo.end = funcInfo.start + funcInfo.size;
662    }
663    addEntry(funcInfo);
664  }
665  addEntry({name: '', start: libEnd});
666};
667
668
669CppEntriesProvider.prototype.loadSymbols = function(libName) {
670};
671
672
673CppEntriesProvider.prototype.parseNextLine = function() {
674  return false;
675};
676
677
678function UnixCppEntriesProvider(nmExec, targetRootFS) {
679  this.symbols = [];
680  this.parsePos = 0;
681  this.nmExec = nmExec;
682  this.targetRootFS = targetRootFS;
683  this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/;
684};
685inherits(UnixCppEntriesProvider, CppEntriesProvider);
686
687
688UnixCppEntriesProvider.prototype.loadSymbols = function(libName) {
689  this.parsePos = 0;
690  libName = this.targetRootFS + libName;
691  try {
692    this.symbols = [
693      os.system(this.nmExec, ['-C', '-n', '-S', libName], -1, -1),
694      os.system(this.nmExec, ['-C', '-n', '-S', '-D', libName], -1, -1)
695    ];
696  } catch (e) {
697    // If the library cannot be found on this system let's not panic.
698    this.symbols = ['', ''];
699  }
700};
701
702
703UnixCppEntriesProvider.prototype.parseNextLine = function() {
704  if (this.symbols.length == 0) {
705    return false;
706  }
707  var lineEndPos = this.symbols[0].indexOf('\n', this.parsePos);
708  if (lineEndPos == -1) {
709    this.symbols.shift();
710    this.parsePos = 0;
711    return this.parseNextLine();
712  }
713
714  var line = this.symbols[0].substring(this.parsePos, lineEndPos);
715  this.parsePos = lineEndPos + 1;
716  var fields = line.match(this.FUNC_RE);
717  var funcInfo = null;
718  if (fields) {
719    funcInfo = { name: fields[3], start: parseInt(fields[1], 16) };
720    if (fields[2]) {
721      funcInfo.size = parseInt(fields[2], 16);
722    }
723  }
724  return funcInfo;
725};
726
727
728function MacCppEntriesProvider(nmExec, targetRootFS) {
729  UnixCppEntriesProvider.call(this, nmExec, targetRootFS);
730  // Note an empty group. It is required, as UnixCppEntriesProvider expects 3 groups.
731  this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ()[iItT] (.*)$/;
732};
733inherits(MacCppEntriesProvider, UnixCppEntriesProvider);
734
735
736MacCppEntriesProvider.prototype.loadSymbols = function(libName) {
737  this.parsePos = 0;
738  libName = this.targetRootFS + libName;
739  try {
740    this.symbols = [os.system(this.nmExec, ['-n', '-f', libName], -1, -1), ''];
741  } catch (e) {
742    // If the library cannot be found on this system let's not panic.
743    this.symbols = '';
744  }
745};
746
747
748function WindowsCppEntriesProvider(_ignored_nmExec, targetRootFS) {
749  this.targetRootFS = targetRootFS;
750  this.symbols = '';
751  this.parsePos = 0;
752};
753inherits(WindowsCppEntriesProvider, CppEntriesProvider);
754
755
756WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.([^.]+)$/;
757
758
759WindowsCppEntriesProvider.FUNC_RE =
760    /^\s+0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/;
761
762
763WindowsCppEntriesProvider.IMAGE_BASE_RE =
764    /^\s+0000:00000000\s+___ImageBase\s+([0-9a-fA-F]{8}).*$/;
765
766
767// This is almost a constant on Windows.
768WindowsCppEntriesProvider.EXE_IMAGE_BASE = 0x00400000;
769
770
771WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) {
772  libName = this.targetRootFS + libName;
773  var fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE);
774  if (!fileNameFields) return;
775  var mapFileName = fileNameFields[1] + '.map';
776  this.moduleType_ = fileNameFields[2].toLowerCase();
777  try {
778    this.symbols = read(mapFileName);
779  } catch (e) {
780    // If .map file cannot be found let's not panic.
781    this.symbols = '';
782  }
783};
784
785
786WindowsCppEntriesProvider.prototype.parseNextLine = function() {
787  var lineEndPos = this.symbols.indexOf('\r\n', this.parsePos);
788  if (lineEndPos == -1) {
789    return false;
790  }
791
792  var line = this.symbols.substring(this.parsePos, lineEndPos);
793  this.parsePos = lineEndPos + 2;
794
795  // Image base entry is above all other symbols, so we can just
796  // terminate parsing.
797  var imageBaseFields = line.match(WindowsCppEntriesProvider.IMAGE_BASE_RE);
798  if (imageBaseFields) {
799    var imageBase = parseInt(imageBaseFields[1], 16);
800    if ((this.moduleType_ == 'exe') !=
801        (imageBase == WindowsCppEntriesProvider.EXE_IMAGE_BASE)) {
802      return false;
803    }
804  }
805
806  var fields = line.match(WindowsCppEntriesProvider.FUNC_RE);
807  return fields ?
808      { name: this.unmangleName(fields[1]), start: parseInt(fields[2], 16) } :
809      null;
810};
811
812
813/**
814 * Performs very simple unmangling of C++ names.
815 *
816 * Does not handle arguments and template arguments. The mangled names have
817 * the form:
818 *
819 *   ?LookupInDescriptor@JSObject@internal@v8@@...arguments info...
820 */
821WindowsCppEntriesProvider.prototype.unmangleName = function(name) {
822  // Empty or non-mangled name.
823  if (name.length < 1 || name.charAt(0) != '?') return name;
824  var nameEndPos = name.indexOf('@@');
825  var components = name.substring(1, nameEndPos).split('@');
826  components.reverse();
827  return components.join('::');
828};
829
830
831function ArgumentsProcessor(args) {
832  this.args_ = args;
833  this.result_ = ArgumentsProcessor.DEFAULTS;
834
835  this.argsDispatch_ = {
836    '-j': ['stateFilter', TickProcessor.VmStates.JS,
837        'Show only ticks from JS VM state'],
838    '-g': ['stateFilter', TickProcessor.VmStates.GC,
839        'Show only ticks from GC VM state'],
840    '-c': ['stateFilter', TickProcessor.VmStates.COMPILER,
841        'Show only ticks from COMPILER VM state'],
842    '-o': ['stateFilter', TickProcessor.VmStates.OTHER,
843        'Show only ticks from OTHER VM state'],
844    '-e': ['stateFilter', TickProcessor.VmStates.EXTERNAL,
845        'Show only ticks from EXTERNAL VM state'],
846    '--call-graph-size': ['callGraphSize', TickProcessor.CALL_GRAPH_SIZE,
847        'Set the call graph size'],
848    '--ignore-unknown': ['ignoreUnknown', true,
849        'Exclude ticks of unknown code entries from processing'],
850    '--separate-ic': ['separateIc', true,
851        'Separate IC entries'],
852    '--unix': ['platform', 'unix',
853        'Specify that we are running on *nix platform'],
854    '--windows': ['platform', 'windows',
855        'Specify that we are running on Windows platform'],
856    '--mac': ['platform', 'mac',
857        'Specify that we are running on Mac OS X platform'],
858    '--nm': ['nm', 'nm',
859        'Specify the \'nm\' executable to use (e.g. --nm=/my_dir/nm)'],
860    '--target': ['targetRootFS', '',
861        'Specify the target root directory for cross environment'],
862    '--snapshot-log': ['snapshotLogFileName', 'snapshot.log',
863        'Specify snapshot log file to use (e.g. --snapshot-log=snapshot.log)'],
864    '--range': ['range', 'auto,auto',
865        'Specify the range limit as [start],[end]'],
866    '--distortion': ['distortion', 0,
867        'Specify the logging overhead in picoseconds'],
868    '--source-map': ['sourceMap', null,
869        'Specify the source map that should be used for output']
870  };
871  this.argsDispatch_['--js'] = this.argsDispatch_['-j'];
872  this.argsDispatch_['--gc'] = this.argsDispatch_['-g'];
873  this.argsDispatch_['--compiler'] = this.argsDispatch_['-c'];
874  this.argsDispatch_['--other'] = this.argsDispatch_['-o'];
875  this.argsDispatch_['--external'] = this.argsDispatch_['-e'];
876};
877
878
879ArgumentsProcessor.DEFAULTS = {
880  logFileName: 'v8.log',
881  snapshotLogFileName: null,
882  platform: 'unix',
883  stateFilter: null,
884  callGraphSize: 5,
885  ignoreUnknown: false,
886  separateIc: false,
887  targetRootFS: '',
888  nm: 'nm',
889  range: 'auto,auto',
890  distortion: 0
891};
892
893
894ArgumentsProcessor.prototype.parse = function() {
895  while (this.args_.length) {
896    var arg = this.args_[0];
897    if (arg.charAt(0) != '-') {
898      break;
899    }
900    this.args_.shift();
901    var userValue = null;
902    var eqPos = arg.indexOf('=');
903    if (eqPos != -1) {
904      userValue = arg.substr(eqPos + 1);
905      arg = arg.substr(0, eqPos);
906    }
907    if (arg in this.argsDispatch_) {
908      var dispatch = this.argsDispatch_[arg];
909      this.result_[dispatch[0]] = userValue == null ? dispatch[1] : userValue;
910    } else {
911      return false;
912    }
913  }
914
915  if (this.args_.length >= 1) {
916      this.result_.logFileName = this.args_.shift();
917  }
918  return true;
919};
920
921
922ArgumentsProcessor.prototype.result = function() {
923  return this.result_;
924};
925
926
927ArgumentsProcessor.prototype.printUsageAndExit = function() {
928
929  function padRight(s, len) {
930    s = s.toString();
931    if (s.length < len) {
932      s = s + (new Array(len - s.length + 1).join(' '));
933    }
934    return s;
935  }
936
937  print('Cmdline args: [options] [log-file-name]\n' +
938        'Default log file name is "' +
939        ArgumentsProcessor.DEFAULTS.logFileName + '".\n');
940  print('Options:');
941  for (var arg in this.argsDispatch_) {
942    var synonims = [arg];
943    var dispatch = this.argsDispatch_[arg];
944    for (var synArg in this.argsDispatch_) {
945      if (arg !== synArg && dispatch === this.argsDispatch_[synArg]) {
946        synonims.push(synArg);
947        delete this.argsDispatch_[synArg];
948      }
949    }
950    print('  ' + padRight(synonims.join(', '), 20) + dispatch[2]);
951  }
952  quit(2);
953};
954