tickprocessor.js 26.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


29 30 31 32 33 34 35
function inherits(childCtor, parentCtor) {
  childCtor.prototype.__proto__ = parentCtor.prototype;
};


function V8Profile(separateIc) {
  Profile.call(this);
36
  if (!separateIc) {
37
    this.skipThisFunction = function(name) { return V8Profile.IC_RE.test(name); };
38 39
  }
};
40
inherits(V8Profile, Profile);
41 42


43
V8Profile.IC_RE =
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
    /^(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Call|Load|Store)IC_)/;


/**
 * A thin wrapper around shell's 'read' function showing a file name on error.
 */
function readFile(fileName) {
  try {
    return read(fileName);
  } catch (e) {
    print(fileName + ': ' + (e.message || e));
    throw e;
  }
}


60 61 62 63 64 65 66 67 68 69 70 71 72
/**
 * Parser for dynamic code optimization state.
 */
function parseState(s) {
  switch (s) {
  case "": return Profile.CodeState.COMPILED;
  case "~": return Profile.CodeState.OPTIMIZABLE;
  case "*": return Profile.CodeState.OPTIMIZED;
  }
  throw new Error("unknown code state: " + s);
}


73
function SnapshotLogProcessor() {
74
  LogReader.call(this, {
75
      'code-creation': {
76
          parsers: [null, parseInt, parseInt, null, 'var-args'],
77 78 79 80 81
          processor: this.processCodeCreation },
      'code-move': { parsers: [parseInt, parseInt],
          processor: this.processCodeMove },
      'code-delete': { parsers: [parseInt],
          processor: this.processCodeDelete },
82 83 84
      'function-creation': null,
      'function-move': null,
      'function-delete': null,
85
      'sfi-move': null,
86 87
      'snapshot-pos': { parsers: [parseInt, parseInt],
          processor: this.processSnapshotPosition }});
88

89 90
  V8Profile.prototype.handleUnknownCode = function(operation, addr) {
    var op = Profile.Operation;
91 92 93 94 95 96 97 98 99 100 101 102
    switch (operation) {
      case op.MOVE:
        print('Snapshot: Code move event for unknown code: 0x' +
              addr.toString(16));
        break;
      case op.DELETE:
        print('Snapshot: Code delete event for unknown code: 0x' +
              addr.toString(16));
        break;
    }
  };

103
  this.profile_ = new V8Profile();
104 105
  this.serializedEntries_ = [];
}
106
inherits(SnapshotLogProcessor, LogReader);
107 108 109


SnapshotLogProcessor.prototype.processCodeCreation = function(
110 111 112 113 114 115 116 117
    type, start, size, name, maybe_func) {
  if (maybe_func.length) {
    var funcAddr = parseInt(maybe_func[0]);
    var state = parseState(maybe_func[1]);
    this.profile_.addFuncCode(type, name, start, size, funcAddr, state);
  } else {
    this.profile_.addCode(type, name, start, size);
  }
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
};


SnapshotLogProcessor.prototype.processCodeMove = function(from, to) {
  this.profile_.moveCode(from, to);
};


SnapshotLogProcessor.prototype.processCodeDelete = function(start) {
  this.profile_.deleteCode(start);
};


SnapshotLogProcessor.prototype.processSnapshotPosition = function(addr, pos) {
  this.serializedEntries_[pos] = this.profile_.findEntry(addr);
};


SnapshotLogProcessor.prototype.processLogFile = function(fileName) {
  var contents = readFile(fileName);
  this.processLogChunk(contents);
};


SnapshotLogProcessor.prototype.getSerializedEntryName = function(pos) {
  var entry = this.serializedEntries_[pos];
  return entry ? entry.getRawName() : null;
145 146 147
};


148
function TickProcessor(
149
    cppEntriesProvider, separateIc, ignoreUnknown, stateFilter, snapshotLogProcessor) {
150
  LogReader.call(this, {
151 152 153
      'shared-library': { parsers: [null, parseInt, parseInt],
          processor: this.processSharedLibrary },
      'code-creation': {
154
          parsers: [null, parseInt, parseInt, null, 'var-args'],
155 156 157 158 159
          processor: this.processCodeCreation },
      'code-move': { parsers: [parseInt, parseInt],
          processor: this.processCodeMove },
      'code-delete': { parsers: [parseInt],
          processor: this.processCodeDelete },
160
      'sfi-move': { parsers: [parseInt, parseInt],
161 162 163
          processor: this.processFunctionMove },
      'snapshot-pos': { parsers: [parseInt, parseInt],
          processor: this.processSnapshotPosition },
164 165 166
      'tick': {
          parsers: [parseInt, parseInt, parseInt,
                    parseInt, parseInt, 'var-args'],
167
          processor: this.processTick },
168 169 170 171 172
      'heap-sample-begin': { parsers: [null, null, parseInt],
          processor: this.processHeapSampleBegin },
      'heap-sample-end': { parsers: [null, null],
          processor: this.processHeapSampleEnd },
      'heap-js-prod-item': { parsers: [null, 'var-args'],
173
          processor: this.processJSProducer },
174
      // Ignored events.
175
      'profiler': null,
176 177 178
      'function-creation': null,
      'function-move': null,
      'function-delete': null,
179 180 181 182
      'heap-sample-stats': null,
      'heap-sample-item': null,
      'heap-js-cons-item': null,
      'heap-js-ret-item': null,
183 184 185 186 187
      // Obsolete row types.
      'code-allocate': null,
      'begin-code-region': null,
      'end-code-region': null });

188 189 190
  this.cppEntriesProvider_ = cppEntriesProvider;
  this.ignoreUnknown_ = ignoreUnknown;
  this.stateFilter_ = stateFilter;
191 192
  this.snapshotLogProcessor_ = snapshotLogProcessor;
  this.deserializedEntriesNames_ = [];
193 194 195
  var ticks = this.ticks_ =
    { total: 0, unaccounted: 0, excluded: 0, gc: 0 };

196
  V8Profile.prototype.handleUnknownCode = function(
197
      operation, addr, opt_stackPos) {
198
    var op = Profile.Operation;
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
    switch (operation) {
      case op.MOVE:
        print('Code move event for unknown code: 0x' + addr.toString(16));
        break;
      case op.DELETE:
        print('Code delete event for unknown code: 0x' + addr.toString(16));
        break;
      case op.TICK:
        // Only unknown PCs (the first frame) are reported as unaccounted,
        // otherwise tick balance will be corrupted (this behavior is compatible
        // with the original tickprocessor.py script.)
        if (opt_stackPos == 0) {
          ticks.unaccounted++;
        }
        break;
    }
  };

217
  this.profile_ = new V8Profile(separateIc);
218 219
  this.codeTypes_ = {};
  // Count each tick as a time unit.
220
  this.viewBuilder_ = new ViewBuilder(1);
221
  this.lastLogFileName_ = null;
222 223 224

  this.generation_ = 1;
  this.currentProducerProfile_ = null;
225
};
226
inherits(TickProcessor, LogReader);
227 228 229 230 231 232 233 234 235 236 237 238


TickProcessor.VmStates = {
  JS: 0,
  GC: 1,
  COMPILER: 2,
  OTHER: 3,
  EXTERNAL: 4
};


TickProcessor.CodeTypes = {
239 240
  CPP: 0,
  SHARED_LIB: 1
241
};
242 243
// Otherwise, this is JS-related code. We are not adding it to
// codeTypes_ map because there can be zillions of them.
244 245 246 247 248


TickProcessor.CALL_PROFILE_CUTOFF_PCT = 2.0;


249 250 251 252 253 254 255 256
/**
 * @override
 */
TickProcessor.prototype.printError = function(str) {
  print(str);
};


257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
TickProcessor.prototype.setCodeType = function(name, type) {
  this.codeTypes_[name] = TickProcessor.CodeTypes[type];
};


TickProcessor.prototype.isSharedLibrary = function(name) {
  return this.codeTypes_[name] == TickProcessor.CodeTypes.SHARED_LIB;
};


TickProcessor.prototype.isCppCode = function(name) {
  return this.codeTypes_[name] == TickProcessor.CodeTypes.CPP;
};


TickProcessor.prototype.isJsCode = function(name) {
273
  return !(name in this.codeTypes_);
274 275 276 277 278
};


TickProcessor.prototype.processLogFile = function(fileName) {
  this.lastLogFileName_ = fileName;
279 280 281 282
  var line;
  while (line = readline()) {
    this.processLogLine(line);
  }
283 284 285
};


286 287 288 289 290 291 292 293
TickProcessor.prototype.processLogFileInTest = function(fileName) {
   // Hack file name to avoid dealing with platform specifics.
  this.lastLogFileName_ = 'v8.log';
  var contents = readFile(fileName);
  this.processLogChunk(contents);
};


294 295
TickProcessor.prototype.processSharedLibrary = function(
    name, startAddr, endAddr) {
296
  var entry = this.profile_.addLibrary(name, startAddr, endAddr);
297 298 299 300 301 302 303 304 305 306 307 308
  this.setCodeType(entry.getName(), 'SHARED_LIB');

  var self = this;
  var libFuncs = this.cppEntriesProvider_.parseVmSymbols(
      name, startAddr, endAddr, function(fName, fStart, fEnd) {
    self.profile_.addStaticCode(fName, fStart, fEnd);
    self.setCodeType(fName, 'CPP');
  });
};


TickProcessor.prototype.processCodeCreation = function(
309
    type, start, size, name, maybe_func) {
310
  name = this.deserializedEntriesNames_[start] || name;
311 312 313 314 315 316 317
  if (maybe_func.length) {
    var funcAddr = parseInt(maybe_func[0]);
    var state = parseState(maybe_func[1]);
    this.profile_.addFuncCode(type, name, start, size, funcAddr, state);
  } else {
    this.profile_.addCode(type, name, start, size);
  }
318 319 320 321 322 323 324 325 326 327 328 329 330
};


TickProcessor.prototype.processCodeMove = function(from, to) {
  this.profile_.moveCode(from, to);
};


TickProcessor.prototype.processCodeDelete = function(start) {
  this.profile_.deleteCode(start);
};


331
TickProcessor.prototype.processFunctionMove = function(from, to) {
332
  this.profile_.moveFunc(from, to);
333 334 335
};


336 337 338 339 340 341 342 343
TickProcessor.prototype.processSnapshotPosition = function(addr, pos) {
  if (this.snapshotLogProcessor_) {
    this.deserializedEntriesNames_[addr] =
      this.snapshotLogProcessor_.getSerializedEntryName(pos);
  }
};


344 345 346 347
TickProcessor.prototype.includeTick = function(vmState) {
  return this.stateFilter_ == null || this.stateFilter_ == vmState;
};

348 349 350 351 352 353
TickProcessor.prototype.processTick = function(pc,
                                               sp,
                                               is_external_callback,
                                               tos_or_external_callback,
                                               vmState,
                                               stack) {
354 355 356 357 358 359
  this.ticks_.total++;
  if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++;
  if (!this.includeTick(vmState)) {
    this.ticks_.excluded++;
    return;
  }
360 361 362
  if (is_external_callback) {
    // Don't use PC when in external callback code, as it can point
    // inside callback's code, and we will erroneously report
363 364 365 366
    // that a callback calls itself. Instead we use tos_or_external_callback,
    // as simply resetting PC will produce unaccounted ticks.
    pc = tos_or_external_callback;
    tos_or_external_callback = 0;
367 368 369 370
  } else if (tos_or_external_callback) {
    // Find out, if top of stack was pointing inside a JS function
    // meaning that we have encountered a frameless invocation.
    var funcEntry = this.profile_.findEntry(tos_or_external_callback);
371
    if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction()) {
372
      tos_or_external_callback = 0;
373 374 375
    }
  }

376
  this.profile_.recordTick(this.processStack(pc, tos_or_external_callback, stack));
377 378 379
};


380 381
TickProcessor.prototype.processHeapSampleBegin = function(space, state, ticks) {
  if (space != 'Heap') return;
382
  this.currentProducerProfile_ = new CallTree();
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
};


TickProcessor.prototype.processHeapSampleEnd = function(space, state) {
  if (space != 'Heap' || !this.currentProducerProfile_) return;

  print('Generation ' + this.generation_ + ':');
  var tree = this.currentProducerProfile_;
  tree.computeTotalWeights();
  var producersView = this.viewBuilder_.buildView(tree);
  // Sort by total time, desc, then by name, desc.
  producersView.sort(function(rec1, rec2) {
      return rec2.totalTime - rec1.totalTime ||
          (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
  this.printHeavyProfile(producersView.head.children);

  this.currentProducerProfile_ = null;
  this.generation_++;
};


TickProcessor.prototype.processJSProducer = function(constructor, stack) {
  if (!this.currentProducerProfile_) return;
  if (stack.length == 0) return;
  var first = stack.shift();
  var processedStack =
409
      this.profile_.resolveAndFilterFuncs_(this.processStack(first, 0, stack));
410 411 412 413 414
  processedStack.unshift(constructor);
  this.currentProducerProfile_.addPath(processedStack);
};


415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
TickProcessor.prototype.printStatistics = function() {
  print('Statistical profiling result from ' + this.lastLogFileName_ +
        ', (' + this.ticks_.total +
        ' ticks, ' + this.ticks_.unaccounted + ' unaccounted, ' +
        this.ticks_.excluded + ' excluded).');

  if (this.ticks_.total == 0) return;

  // Print the unknown ticks percentage if they are not ignored.
  if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) {
    this.printHeader('Unknown');
    this.printCounter(this.ticks_.unaccounted, this.ticks_.total);
  }

  var flatProfile = this.profile_.getFlatProfile();
  var flatView = this.viewBuilder_.buildView(flatProfile);
  // Sort by self time, desc, then by name, desc.
  flatView.sort(function(rec1, rec2) {
      return rec2.selfTime - rec1.selfTime ||
          (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
  var totalTicks = this.ticks_.total;
  if (this.ignoreUnknown_) {
    totalTicks -= this.ticks_.unaccounted;
  }
  // Our total time contains all the ticks encountered,
  // while profile only knows about the filtered ticks.
  flatView.head.totalTime = totalTicks;

  // Count library ticks
  var flatViewNodes = flatView.head.children;
  var self = this;
  var libraryTicks = 0;
  this.processProfile(flatViewNodes,
      function(name) { return self.isSharedLibrary(name); },
      function(rec) { libraryTicks += rec.selfTime; });
  var nonLibraryTicks = totalTicks - libraryTicks;

  this.printHeader('Shared libraries');
  this.printEntries(flatViewNodes, null,
      function(name) { return self.isSharedLibrary(name); });

  this.printHeader('JavaScript');
  this.printEntries(flatViewNodes, nonLibraryTicks,
      function(name) { return self.isJsCode(name); });

  this.printHeader('C++');
  this.printEntries(flatViewNodes, nonLibraryTicks,
      function(name) { return self.isCppCode(name); });

  this.printHeader('GC');
  this.printCounter(this.ticks_.gc, totalTicks);

  this.printHeavyProfHeader();
  var heavyProfile = this.profile_.getBottomUpProfile();
  var heavyView = this.viewBuilder_.buildView(heavyProfile);
  // To show the same percentages as in the flat profile.
  heavyView.head.totalTime = totalTicks;
  // Sort by total time, desc, then by name, desc.
  heavyView.sort(function(rec1, rec2) {
      return rec2.totalTime - rec1.totalTime ||
          (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
  this.printHeavyProfile(heavyView.head.children);
};


function padLeft(s, len) {
  s = s.toString();
  if (s.length < len) {
483 484 485 486 487
    var padLength = len - s.length;
    if (!(padLength in padLeft)) {
      padLeft[padLength] = new Array(padLength + 1).join(' ');
    }
    s = padLeft[padLength] + s;
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
  }
  return s;
};


TickProcessor.prototype.printHeader = function(headerTitle) {
  print('\n [' + headerTitle + ']:');
  print('   ticks  total  nonlib   name');
};


TickProcessor.prototype.printHeavyProfHeader = function() {
  print('\n [Bottom up (heavy) profile]:');
  print('  Note: percentage shows a share of a particular caller in the ' +
        'total\n' +
        '  amount of its parent calls.');
  print('  Callers occupying less than ' +
        TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1) +
        '% are not shown.\n');
  print('   ticks parent  name');
};


TickProcessor.prototype.printCounter = function(ticksCount, totalTicksCount) {
  var pct = ticksCount * 100.0 / totalTicksCount;
  print('  ' + padLeft(ticksCount, 5) + '  ' + padLeft(pct.toFixed(1), 5) + '%');
};


TickProcessor.prototype.processProfile = function(
    profile, filterP, func) {
  for (var i = 0, n = profile.length; i < n; ++i) {
    var rec = profile[i];
521
    if (!filterP(rec.internalFuncName)) {
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
      continue;
    }
    func(rec);
  }
};


TickProcessor.prototype.printEntries = function(
    profile, nonLibTicks, filterP) {
  this.processProfile(profile, filterP, function (rec) {
    if (rec.selfTime == 0) return;
    var nonLibPct = nonLibTicks != null ?
        rec.selfTime * 100.0 / nonLibTicks : 0.0;
    print('  ' + padLeft(rec.selfTime, 5) + '  ' +
          padLeft(rec.selfPercent.toFixed(1), 5) + '%  ' +
          padLeft(nonLibPct.toFixed(1), 5) + '%  ' +
          rec.internalFuncName);
  });
};


TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) {
  var self = this;
  var indent = opt_indent || 0;
  var indentStr = padLeft('', indent);
  this.processProfile(profile, function() { return true; }, function (rec) {
    // Cut off too infrequent callers.
    if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return;
    print('  ' + padLeft(rec.totalTime, 5) + '  ' +
          padLeft(rec.parentTotalPercent.toFixed(1), 5) + '%  ' +
          indentStr + rec.internalFuncName);
    // Limit backtrace depth.
    if (indent < 10) {
      self.printHeavyProfile(rec.children, indent + 2);
    }
    // Delimit top-level functions.
    if (indent == 0) {
      print('');
    }
  });
};


function CppEntriesProvider() {
};


CppEntriesProvider.prototype.parseVmSymbols = function(
    libName, libStart, libEnd, processorFunc) {
571
  this.loadSymbols(libName);
572 573 574

  var prevEntry;

575
  function addEntry(funcInfo) {
576 577
    // Several functions can be mapped onto the same address. To avoid
    // creating zero-sized entries, skip such duplicates.
578
    // Also double-check that function belongs to the library address space.
579 580 581 582
    if (prevEntry && !prevEntry.end &&
        prevEntry.start < funcInfo.start &&
        prevEntry.start >= libStart && funcInfo.start <= libEnd) {
      processorFunc(prevEntry.name, prevEntry.start, funcInfo.start);
583
    }
584 585 586 587 588 589
    if (funcInfo.end &&
        (!prevEntry || prevEntry.start != funcInfo.start) &&
        funcInfo.start >= libStart && funcInfo.end <= libEnd) {
      processorFunc(funcInfo.name, funcInfo.start, funcInfo.end);
    }
    prevEntry = funcInfo;
590 591
  }

592 593 594
  while (true) {
    var funcInfo = this.parseNextLine();
    if (funcInfo === null) {
595
      continue;
596 597
    } else if (funcInfo === false) {
      break;
598 599 600 601
    }
    if (funcInfo.start < libStart && funcInfo.start < libEnd - libStart) {
      funcInfo.start += libStart;
    }
602 603 604 605
    if (funcInfo.size) {
      funcInfo.end = funcInfo.start + funcInfo.size;
    }
    addEntry(funcInfo);
606
  }
607
  addEntry({name: '', start: libEnd});
608 609 610 611 612 613 614
};


CppEntriesProvider.prototype.loadSymbols = function(libName) {
};


615 616
CppEntriesProvider.prototype.parseNextLine = function() {
  return false;
617 618 619
};


620
function UnixCppEntriesProvider(nmExec) {
621 622
  this.symbols = [];
  this.parsePos = 0;
623
  this.nmExec = nmExec;
624
  this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/;
625 626 627 628 629
};
inherits(UnixCppEntriesProvider, CppEntriesProvider);


UnixCppEntriesProvider.prototype.loadSymbols = function(libName) {
630
  this.parsePos = 0;
631 632
  try {
    this.symbols = [
633 634
      os.system(this.nmExec, ['-C', '-n', '-S', libName], -1, -1),
      os.system(this.nmExec, ['-C', '-n', '-S', '-D', libName], -1, -1)
635 636 637
    ];
  } catch (e) {
    // If the library cannot be found on this system let's not panic.
638
    this.symbols = ['', ''];
639
  }
640 641 642
};


643 644 645 646 647 648 649 650 651 652 653 654 655
UnixCppEntriesProvider.prototype.parseNextLine = function() {
  if (this.symbols.length == 0) {
    return false;
  }
  var lineEndPos = this.symbols[0].indexOf('\n', this.parsePos);
  if (lineEndPos == -1) {
    this.symbols.shift();
    this.parsePos = 0;
    return this.parseNextLine();
  }

  var line = this.symbols[0].substring(this.parsePos, lineEndPos);
  this.parsePos = lineEndPos + 1;
656
  var fields = line.match(this.FUNC_RE);
657 658 659 660 661 662 663 664
  var funcInfo = null;
  if (fields) {
    funcInfo = { name: fields[3], start: parseInt(fields[1], 16) };
    if (fields[2]) {
      funcInfo.size = parseInt(fields[2], 16);
    }
  }
  return funcInfo;
665 666 667
};


668 669
function MacCppEntriesProvider(nmExec) {
  UnixCppEntriesProvider.call(this, nmExec);
670
  // Note an empty group. It is required, as UnixCppEntriesProvider expects 3 groups.
671
  this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ()[iItT] (.*)$/;
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686
};
inherits(MacCppEntriesProvider, UnixCppEntriesProvider);


MacCppEntriesProvider.prototype.loadSymbols = function(libName) {
  this.parsePos = 0;
  try {
    this.symbols = [os.system(this.nmExec, ['-n', '-f', libName], -1, -1), ''];
  } catch (e) {
    // If the library cannot be found on this system let's not panic.
    this.symbols = '';
  }
};


687
function WindowsCppEntriesProvider() {
688 689
  this.symbols = '';
  this.parsePos = 0;
690 691 692 693
};
inherits(WindowsCppEntriesProvider, CppEntriesProvider);


694
WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.([^.]+)$/;
695 696 697


WindowsCppEntriesProvider.FUNC_RE =
698 699 700 701 702 703 704 705 706
    /^\s+0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/;


WindowsCppEntriesProvider.IMAGE_BASE_RE =
    /^\s+0000:00000000\s+___ImageBase\s+([0-9a-fA-F]{8}).*$/;


// This is almost a constant on Windows.
WindowsCppEntriesProvider.EXE_IMAGE_BASE = 0x00400000;
707 708 709 710


WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) {
  var fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE);
711
  if (!fileNameFields) return;
712
  var mapFileName = fileNameFields[1] + '.map';
713 714 715 716 717 718 719
  this.moduleType_ = fileNameFields[2].toLowerCase();
  try {
    this.symbols = read(mapFileName);
  } catch (e) {
    // If .map file cannot be found let's not panic.
    this.symbols = '';
  }
720 721 722
};


723 724 725 726 727 728 729 730
WindowsCppEntriesProvider.prototype.parseNextLine = function() {
  var lineEndPos = this.symbols.indexOf('\r\n', this.parsePos);
  if (lineEndPos == -1) {
    return false;
  }

  var line = this.symbols.substring(this.parsePos, lineEndPos);
  this.parsePos = lineEndPos + 2;
731 732 733 734 735 736 737 738 739 740 741 742

  // Image base entry is above all other symbols, so we can just
  // terminate parsing.
  var imageBaseFields = line.match(WindowsCppEntriesProvider.IMAGE_BASE_RE);
  if (imageBaseFields) {
    var imageBase = parseInt(imageBaseFields[1], 16);
    if ((this.moduleType_ == 'exe') !=
        (imageBase == WindowsCppEntriesProvider.EXE_IMAGE_BASE)) {
      return false;
    }
  }

743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767
  var fields = line.match(WindowsCppEntriesProvider.FUNC_RE);
  return fields ?
      { name: this.unmangleName(fields[1]), start: parseInt(fields[2], 16) } :
      null;
};


/**
 * Performs very simple unmangling of C++ names.
 *
 * Does not handle arguments and template arguments. The mangled names have
 * the form:
 *
 *   ?LookupInDescriptor@JSObject@internal@v8@@...arguments info...
 */
WindowsCppEntriesProvider.prototype.unmangleName = function(name) {
  // Empty or non-mangled name.
  if (name.length < 1 || name.charAt(0) != '?') return name;
  var nameEndPos = name.indexOf('@@');
  var components = name.substring(1, nameEndPos).split('@');
  components.reverse();
  return components.join('::');
};


768 769 770
function ArgumentsProcessor(args) {
  this.args_ = args;
  this.result_ = ArgumentsProcessor.DEFAULTS;
771

772
  this.argsDispatch_ = {
773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
    '-j': ['stateFilter', TickProcessor.VmStates.JS,
        'Show only ticks from JS VM state'],
    '-g': ['stateFilter', TickProcessor.VmStates.GC,
        'Show only ticks from GC VM state'],
    '-c': ['stateFilter', TickProcessor.VmStates.COMPILER,
        'Show only ticks from COMPILER VM state'],
    '-o': ['stateFilter', TickProcessor.VmStates.OTHER,
        'Show only ticks from OTHER VM state'],
    '-e': ['stateFilter', TickProcessor.VmStates.EXTERNAL,
        'Show only ticks from EXTERNAL VM state'],
    '--ignore-unknown': ['ignoreUnknown', true,
        'Exclude ticks of unknown code entries from processing'],
    '--separate-ic': ['separateIc', true,
        'Separate IC entries'],
    '--unix': ['platform', 'unix',
        'Specify that we are running on *nix platform'],
    '--windows': ['platform', 'windows',
790
        'Specify that we are running on Windows platform'],
791 792
    '--mac': ['platform', 'mac',
        'Specify that we are running on Mac OS X platform'],
793
    '--nm': ['nm', 'nm',
794 795 796
        'Specify the \'nm\' executable to use (e.g. --nm=/my_dir/nm)'],
    '--snapshot-log': ['snapshotLogFileName', 'snapshot.log',
        'Specify snapshot log file to use (e.g. --snapshot-log=snapshot.log)']
797
  };
798 799 800 801 802 803 804
  this.argsDispatch_['--js'] = this.argsDispatch_['-j'];
  this.argsDispatch_['--gc'] = this.argsDispatch_['-g'];
  this.argsDispatch_['--compiler'] = this.argsDispatch_['-c'];
  this.argsDispatch_['--other'] = this.argsDispatch_['-o'];
  this.argsDispatch_['--external'] = this.argsDispatch_['-e'];
};

805

806 807
ArgumentsProcessor.DEFAULTS = {
  logFileName: 'v8.log',
808
  snapshotLogFileName: null,
809 810 811 812 813 814 815 816 817 818 819
  platform: 'unix',
  stateFilter: null,
  ignoreUnknown: false,
  separateIc: false,
  nm: 'nm'
};


ArgumentsProcessor.prototype.parse = function() {
  while (this.args_.length) {
    var arg = this.args_[0];
820 821 822
    if (arg.charAt(0) != '-') {
      break;
    }
823
    this.args_.shift();
824 825 826 827 828 829
    var userValue = null;
    var eqPos = arg.indexOf('=');
    if (eqPos != -1) {
      userValue = arg.substr(eqPos + 1);
      arg = arg.substr(0, eqPos);
    }
830 831 832
    if (arg in this.argsDispatch_) {
      var dispatch = this.argsDispatch_[arg];
      this.result_[dispatch[0]] = userValue == null ? dispatch[1] : userValue;
833
    } else {
834
      return false;
835 836 837
    }
  }

838 839
  if (this.args_.length >= 1) {
      this.result_.logFileName = this.args_.shift();
840
  }
841
  return true;
842 843 844
};


845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877
ArgumentsProcessor.prototype.result = function() {
  return this.result_;
};


ArgumentsProcessor.prototype.printUsageAndExit = function() {

  function padRight(s, len) {
    s = s.toString();
    if (s.length < len) {
      s = s + (new Array(len - s.length + 1).join(' '));
    }
    return s;
  }

  print('Cmdline args: [options] [log-file-name]\n' +
        'Default log file name is "' +
        ArgumentsProcessor.DEFAULTS.logFileName + '".\n');
  print('Options:');
  for (var arg in this.argsDispatch_) {
    var synonims = [arg];
    var dispatch = this.argsDispatch_[arg];
    for (var synArg in this.argsDispatch_) {
      if (arg !== synArg && dispatch === this.argsDispatch_[synArg]) {
        synonims.push(synArg);
        delete this.argsDispatch_[synArg];
      }
    }
    print('  ' + padRight(synonims.join(', '), 20) + dispatch[2]);
  }
  quit(2);
};