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