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