tickprocessor.js 28.3 KB
Newer Older
1
// Copyright 2012 the V8 project authors. All rights reserved.
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
// 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 TickProcessor(
74 75 76 77 78
    cppEntriesProvider,
    separateIc,
    callGraphSize,
    ignoreUnknown,
    stateFilter,
79
    distortion,
80
    range,
jkummerow's avatar
jkummerow committed
81
    sourceMap,
82
    timedRange,
83
    pairwiseTimedRange,
84 85
    onlySummary,
    runtimeTimerFilter) {
86
  LogReader.call(this, {
87
      'shared-library': { parsers: [null, parseInt, parseInt, parseInt],
88 89
          processor: this.processSharedLibrary },
      'code-creation': {
90
          parsers: [null, parseInt, parseInt, parseInt, null, 'var-args'],
91 92 93
          processor: this.processCodeCreation },
      'code-move': { parsers: [parseInt, parseInt],
          processor: this.processCodeMove },
94 95
      'code-delete': { parsers: [parseInt],
          processor: this.processCodeDelete },
96
      'sfi-move': { parsers: [parseInt, parseInt],
97
          processor: this.processFunctionMove },
98 99 100
      'active-runtime-timer': {
        parsers: [null],
        processor: this.processRuntimeTimerEvent },
101
      'tick': {
102
          parsers: [parseInt, parseInt, parseInt,
103
                    parseInt, parseInt, 'var-args'],
104
          processor: this.processTick },
105 106 107 108
      'heap-sample-begin': { parsers: [null, null, parseInt],
          processor: this.processHeapSampleBegin },
      'heap-sample-end': { parsers: [null, null],
          processor: this.processHeapSampleEnd },
109 110 111 112
      'timer-event-start' : { parsers: [null, null, null],
                              processor: this.advanceDistortion },
      'timer-event-end' : { parsers: [null, null, null],
                            processor: this.advanceDistortion },
113
      // Ignored events.
114
      'profiler': null,
115 116 117
      'function-creation': null,
      'function-move': null,
      'function-delete': null,
118
      'heap-sample-item': null,
jkummerow's avatar
jkummerow committed
119
      'current-time': null,  // Handled specially, not parsed.
120 121 122
      // Obsolete row types.
      'code-allocate': null,
      'begin-code-region': null,
jkummerow's avatar
jkummerow committed
123
      'end-code-region': null },
124 125
      timedRange,
      pairwiseTimedRange);
126

127
  this.cppEntriesProvider_ = cppEntriesProvider;
128
  this.callGraphSize_ = callGraphSize;
129 130
  this.ignoreUnknown_ = ignoreUnknown;
  this.stateFilter_ = stateFilter;
131
  this.runtimeTimerFilter_ = runtimeTimerFilter;
132
  this.sourceMap = sourceMap;
133
  this.deserializedEntriesNames_ = [];
134 135 136
  var ticks = this.ticks_ =
    { total: 0, unaccounted: 0, excluded: 0, gc: 0 };

137 138 139 140
  distortion = parseInt(distortion);
  // Convert picoseconds to nanoseconds.
  this.distortion_per_entry = isNaN(distortion) ? 0 : (distortion / 1000);
  this.distortion = 0;
141
  var rangelimits = range ? range.split(",") : [];
142 143 144 145 146 147
  var range_start = parseInt(rangelimits[0]);
  var range_end = parseInt(rangelimits[1]);
  // Convert milliseconds to nanoseconds.
  this.range_start = isNaN(range_start) ? -Infinity : (range_start * 1000);
  this.range_end = isNaN(range_end) ? Infinity : (range_end * 1000)

148
  V8Profile.prototype.handleUnknownCode = function(
149
      operation, addr, opt_stackPos) {
150
    var op = Profile.Operation;
151 152 153 154
    switch (operation) {
      case op.MOVE:
        print('Code move event for unknown code: 0x' + addr.toString(16));
        break;
155 156 157
      case op.DELETE:
        print('Code delete event for unknown code: 0x' + addr.toString(16));
        break;
158 159 160 161 162 163 164 165 166 167 168
      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;
    }
  };

169
  this.profile_ = new V8Profile(separateIc);
170 171
  this.codeTypes_ = {};
  // Count each tick as a time unit.
172
  this.viewBuilder_ = new ViewBuilder(1);
173
  this.lastLogFileName_ = null;
174 175 176

  this.generation_ = 1;
  this.currentProducerProfile_ = null;
177
  this.onlySummary_ = onlySummary;
178
};
179
inherits(TickProcessor, LogReader);
180 181 182 183 184 185


TickProcessor.VmStates = {
  JS: 0,
  GC: 1,
  COMPILER: 2,
186
  OTHER: 3,
187 188
  EXTERNAL: 4,
  IDLE: 5
189 190 191 192
};


TickProcessor.CodeTypes = {
193 194
  CPP: 0,
  SHARED_LIB: 1
195
};
196 197
// Otherwise, this is JS-related code. We are not adding it to
// codeTypes_ map because there can be zillions of them.
198 199 200 201


TickProcessor.CALL_PROFILE_CUTOFF_PCT = 2.0;

202
TickProcessor.CALL_GRAPH_SIZE = 5;
203

204 205 206 207 208 209 210 211
/**
 * @override
 */
TickProcessor.prototype.printError = function(str) {
  print(str);
};


212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
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) {
jkummerow's avatar
jkummerow committed
228
  return name !== "UNKNOWN" && !(name in this.codeTypes_);
229 230 231 232 233
};


TickProcessor.prototype.processLogFile = function(fileName) {
  this.lastLogFileName_ = fileName;
234 235 236 237
  var line;
  while (line = readline()) {
    this.processLogLine(line);
  }
238 239 240
};


241 242 243 244 245 246 247 248
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);
};


249
TickProcessor.prototype.processSharedLibrary = function(
250 251
    name, startAddr, endAddr, aslrSlide) {
  var entry = this.profile_.addLibrary(name, startAddr, endAddr, aslrSlide);
252 253 254 255
  this.setCodeType(entry.getName(), 'SHARED_LIB');

  var self = this;
  var libFuncs = this.cppEntriesProvider_.parseVmSymbols(
256
      name, startAddr, endAddr, aslrSlide, function(fName, fStart, fEnd) {
257 258 259 260 261 262 263
    self.profile_.addStaticCode(fName, fStart, fEnd);
    self.setCodeType(fName, 'CPP');
  });
};


TickProcessor.prototype.processCodeCreation = function(
264
    type, kind, start, size, name, maybe_func) {
265
  name = this.deserializedEntriesNames_[start] || name;
266 267 268 269 270 271 272
  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);
  }
273 274 275 276 277 278 279 280
};


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


281 282 283 284 285
TickProcessor.prototype.processCodeDelete = function(start) {
  this.profile_.deleteCode(start);
};


286
TickProcessor.prototype.processFunctionMove = function(from, to) {
287
  this.profile_.moveFunc(from, to);
288 289 290
};


291
TickProcessor.prototype.includeTick = function(vmState) {
292 293 294 295 296 297
  if (this.stateFilter_ !== null) {
    return this.stateFilter_ == vmState;
  } else if (this.runtimeTimerFilter_ !== null) {
    return this.currentRuntimeTimer == this.runtimeTimerFilter_;
  }
  return true;
298 299
};

300 301 302 303
TickProcessor.prototype.processRuntimeTimerEvent = function(name) {
  this.currentRuntimeTimer = name;
}

304
TickProcessor.prototype.processTick = function(pc,
305
                                               ns_since_start,
306 307
                                               is_external_callback,
                                               tos_or_external_callback,
308 309
                                               vmState,
                                               stack) {
310 311 312 313 314
  this.distortion += this.distortion_per_entry;
  ns_since_start -= this.distortion;
  if (ns_since_start < this.range_start || ns_since_start > this.range_end) {
    return;
  }
315 316 317 318 319 320
  this.ticks_.total++;
  if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++;
  if (!this.includeTick(vmState)) {
    this.ticks_.excluded++;
    return;
  }
321
  if (is_external_callback) {
322 323
    // Don't use PC when in external callback code, as it can point
    // inside callback's code, and we will erroneously report
324 325
    // that a callback calls itself. Instead we use tos_or_external_callback,
    // as simply resetting PC will produce unaccounted ticks.
326 327 328 329 330 331 332 333 334 335
    pc = tos_or_external_callback;
    tos_or_external_callback = 0;
  } 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);
    if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction()) {
      tos_or_external_callback = 0;
    }
  }
336

337
  this.profile_.recordTick(this.processStack(pc, tos_or_external_callback, stack));
338 339 340
};


341 342 343 344 345
TickProcessor.prototype.advanceDistortion = function() {
  this.distortion += this.distortion_per_entry;
}


346 347
TickProcessor.prototype.processHeapSampleBegin = function(space, state, ticks) {
  if (space != 'Heap') return;
348
  this.currentProducerProfile_ = new CallTree();
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
};


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_++;
};


370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
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;

  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;
  }
388
  var printAllTicks = !this.onlySummary_;
389 390 391 392

  // Count library ticks
  var flatViewNodes = flatView.head.children;
  var self = this;
393

394
  var libraryTicks = 0;
395
  if(printAllTicks) this.printHeader('Shared libraries');
396
  this.printEntries(flatViewNodes, totalTicks, null,
397
      function(name) { return self.isSharedLibrary(name); },
398
      function(rec) { libraryTicks += rec.selfTime; }, printAllTicks);
399 400
  var nonLibraryTicks = totalTicks - libraryTicks;

401
  var jsTicks = 0;
402
  if(printAllTicks) this.printHeader('JavaScript');
403 404
  this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks,
      function(name) { return self.isJsCode(name); },
405
      function(rec) { jsTicks += rec.selfTime; }, printAllTicks);
406

407
  var cppTicks = 0;
408
  if(printAllTicks) this.printHeader('C++');
409 410
  this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks,
      function(name) { return self.isCppCode(name); },
411
      function(rec) { cppTicks += rec.selfTime; }, printAllTicks);
412 413 414 415 416 417 418 419 420 421

  this.printHeader('Summary');
  this.printLine('JavaScript', jsTicks, totalTicks, nonLibraryTicks);
  this.printLine('C++', cppTicks, totalTicks, nonLibraryTicks);
  this.printLine('GC', this.ticks_.gc, totalTicks, nonLibraryTicks);
  this.printLine('Shared libraries', libraryTicks, totalTicks, null);
  if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) {
    this.printLine('Unaccounted', this.ticks_.unaccounted,
                   this.ticks_.total, null);
  }
422

423 424 425 426 427 428 429 430 431
  if(printAllTicks) {
    print('\n [C++ entry points]:');
    print('   ticks    cpp   total   name');
    var c_entry_functions = this.profile_.getCEntryProfile();
    var total_c_entry = c_entry_functions[0].ticks;
    for (var i = 1; i < c_entry_functions.length; i++) {
      c = c_entry_functions[i];
      this.printLine(c.name, c.ticks, total_c_entry, totalTicks);
    }
432

433 434 435 436 437 438 439 440 441 442 443
    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);
  }
444 445 446 447 448 449
};


function padLeft(s, len) {
  s = s.toString();
  if (s.length < len) {
450 451 452 453 454
    var padLength = len - s.length;
    if (!(padLength in padLeft)) {
      padLeft[padLength] = new Array(padLength + 1).join(' ');
    }
    s = padLeft[padLength] + s;
455 456 457 458 459 460 461 462 463 464 465
  }
  return s;
};


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


466 467 468 469 470 471 472 473 474 475 476 477
TickProcessor.prototype.printLine = function(
    entry, ticks, totalTicks, nonLibTicks) {
  var pct = ticks * 100 / totalTicks;
  var nonLibPct = nonLibTicks != null
      ? padLeft((ticks * 100 / nonLibTicks).toFixed(1), 5) + '%  '
      : '        ';
  print('  ' + padLeft(ticks, 5) + '  ' +
        padLeft(pct.toFixed(1), 5) + '%  ' +
        nonLibPct +
        entry);
}

478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
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.processProfile = function(
    profile, filterP, func) {
  for (var i = 0, n = profile.length; i < n; ++i) {
    var rec = profile[i];
494
    if (!filterP(rec.internalFuncName)) {
495 496 497 498 499 500
      continue;
    }
    func(rec);
  }
};

501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
TickProcessor.prototype.getLineAndColumn = function(name) {
  var re = /:([0-9]+):([0-9]+)$/;
  var array = re.exec(name);
  if (!array) {
    return null;
  }
  return {line: array[1], column: array[2]};
}

TickProcessor.prototype.hasSourceMap = function() {
  return this.sourceMap != null;
};


TickProcessor.prototype.formatFunctionName = function(funcName) {
  if (!this.hasSourceMap()) {
    return funcName;
  }
  var lc = this.getLineAndColumn(funcName);
  if (lc == null) {
    return funcName;
  }
  // in source maps lines and columns are zero based
  var lineNumber = lc.line - 1;
  var column = lc.column - 1;
  var entry = this.sourceMap.findEntry(lineNumber, column);
  var sourceFile = entry[2];
  var sourceLine = entry[3] + 1;
  var sourceColumn = entry[4] + 1;

  return sourceFile + ':' + sourceLine + ':' + sourceColumn + ' -> ' + funcName;
};
533 534

TickProcessor.prototype.printEntries = function(
535
    profile, totalTicks, nonLibTicks, filterP, callback, printAllTicks) {
536
  var that = this;
537 538
  this.processProfile(profile, filterP, function (rec) {
    if (rec.selfTime == 0) return;
539
    callback(rec);
540
    var funcName = that.formatFunctionName(rec.internalFuncName);
541 542 543
    if(printAllTicks) {
      that.printLine(funcName, rec.selfTime, totalTicks, nonLibTicks);
    }
544 545 546 547 548 549 550 551 552 553 554
  });
};


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;
555
    var funcName = self.formatFunctionName(rec.internalFuncName);
556 557
    print('  ' + padLeft(rec.totalTime, 5) + '  ' +
          padLeft(rec.parentTotalPercent.toFixed(1), 5) + '%  ' +
558
          indentStr + funcName);
559
    // Limit backtrace depth.
560
    if (indent < 2 * self.callGraphSize_) {
561 562 563 564 565 566 567 568 569 570 571 572 573 574 575
      self.printHeavyProfile(rec.children, indent + 2);
    }
    // Delimit top-level functions.
    if (indent == 0) {
      print('');
    }
  });
};


function CppEntriesProvider() {
};


CppEntriesProvider.prototype.parseVmSymbols = function(
576
    libName, libStart, libEnd, libASLRSlide, processorFunc) {
577
  this.loadSymbols(libName);
578 579 580

  var prevEntry;

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

598 599 600
  while (true) {
    var funcInfo = this.parseNextLine();
    if (funcInfo === null) {
601
      continue;
602 603
    } else if (funcInfo === false) {
      break;
604
    }
605
    funcInfo.start += libASLRSlide;
606 607 608
    if (funcInfo.start < libStart && funcInfo.start < libEnd - libStart) {
      funcInfo.start += libStart;
    }
609 610 611 612
    if (funcInfo.size) {
      funcInfo.end = funcInfo.start + funcInfo.size;
    }
    addEntry(funcInfo);
613
  }
614
  addEntry({name: '', start: libEnd});
615 616 617 618 619 620 621
};


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


622 623
CppEntriesProvider.prototype.parseNextLine = function() {
  return false;
624 625 626
};


627
function UnixCppEntriesProvider(nmExec, targetRootFS) {
628 629
  this.symbols = [];
  this.parsePos = 0;
630
  this.nmExec = nmExec;
631
  this.targetRootFS = targetRootFS;
632
  this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/;
633 634 635 636 637
};
inherits(UnixCppEntriesProvider, CppEntriesProvider);


UnixCppEntriesProvider.prototype.loadSymbols = function(libName) {
638
  this.parsePos = 0;
639
  libName = this.targetRootFS + libName;
640 641
  try {
    this.symbols = [
642 643
      os.system(this.nmExec, ['-C', '-n', '-S', libName], -1, -1),
      os.system(this.nmExec, ['-C', '-n', '-S', '-D', libName], -1, -1)
644 645 646
    ];
  } catch (e) {
    // If the library cannot be found on this system let's not panic.
647
    this.symbols = ['', ''];
648
  }
649 650 651
};


652 653 654 655 656 657 658 659 660 661 662 663 664
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;
665
  var fields = line.match(this.FUNC_RE);
666 667 668 669 670 671 672 673
  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;
674 675 676
};


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


MacCppEntriesProvider.prototype.loadSymbols = function(libName) {
  this.parsePos = 0;
687
  libName = this.targetRootFS + libName;
688 689 690

  // It seems that in OS X `nm` thinks that `-f` is a format option, not a
  // "flat" display option flag.
691
  try {
692
    this.symbols = [os.system(this.nmExec, ['-n', libName], -1, -1), ''];
693 694 695 696 697 698 699
  } catch (e) {
    // If the library cannot be found on this system let's not panic.
    this.symbols = '';
  }
};


700 701
function WindowsCppEntriesProvider(_ignored_nmExec, targetRootFS) {
  this.targetRootFS = targetRootFS;
702 703
  this.symbols = '';
  this.parsePos = 0;
704 705 706 707
};
inherits(WindowsCppEntriesProvider, CppEntriesProvider);


708
WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.([^.]+)$/;
709 710 711


WindowsCppEntriesProvider.FUNC_RE =
712 713 714 715 716 717 718 719 720
    /^\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;
721 722 723


WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) {
724
  libName = this.targetRootFS + libName;
725
  var fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE);
726
  if (!fileNameFields) return;
727
  var mapFileName = fileNameFields[1] + '.map';
728 729 730 731 732 733 734
  this.moduleType_ = fileNameFields[2].toLowerCase();
  try {
    this.symbols = read(mapFileName);
  } catch (e) {
    // If .map file cannot be found let's not panic.
    this.symbols = '';
  }
735 736 737
};


738 739 740 741 742 743 744 745
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;
746 747 748 749 750 751 752 753 754 755 756 757

  // 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;
    }
  }

758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
  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('::');
};


783 784 785
function ArgumentsProcessor(args) {
  this.args_ = args;
  this.result_ = ArgumentsProcessor.DEFAULTS;
786

787
  this.argsDispatch_ = {
788 789 790 791 792 793 794 795 796 797
    '-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'],
798 799
    '--filter-runtime-timer': ['runtimeTimerFilter', null,
            'Show only ticks matching the given runtime timer scope'],
800 801
    '--call-graph-size': ['callGraphSize', TickProcessor.CALL_GRAPH_SIZE,
        'Set the call graph size'],
802 803 804 805 806 807 808
    '--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',
809
        'Specify that we are running on Windows platform'],
810 811
    '--mac': ['platform', 'mac',
        'Specify that we are running on Mac OS X platform'],
812
    '--nm': ['nm', 'nm',
813
        'Specify the \'nm\' executable to use (e.g. --nm=/my_dir/nm)'],
814 815
    '--target': ['targetRootFS', '',
        'Specify the target root directory for cross environment'],
816
    '--range': ['range', 'auto,auto',
817
        'Specify the range limit as [start],[end]'],
818
    '--distortion': ['distortion', 0,
819 820
        'Specify the logging overhead in picoseconds'],
    '--source-map': ['sourceMap', null,
jkummerow's avatar
jkummerow committed
821 822
        'Specify the source map that should be used for output'],
    '--timed-range': ['timedRange', true,
823 824
        'Ignore ticks before first and after last Date.now() call'],
    '--pairwise-timed-range': ['pairwiseTimedRange', true,
825 826 827
        'Ignore ticks outside pairs of Date.now() calls'],
    '--only-summary': ['onlySummary', true,
        'Print only tick summary, exclude other information']
828
  };
829 830 831 832 833
  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'];
834
  this.argsDispatch_['--ptr'] = this.argsDispatch_['--pairwise-timed-range'];
835 836
};

837

838 839 840 841
ArgumentsProcessor.DEFAULTS = {
  logFileName: 'v8.log',
  platform: 'unix',
  stateFilter: null,
842
  callGraphSize: 5,
843 844
  ignoreUnknown: false,
  separateIc: false,
845
  targetRootFS: '',
846 847
  nm: 'nm',
  range: 'auto,auto',
jkummerow's avatar
jkummerow committed
848
  distortion: 0,
849
  timedRange: false,
850
  pairwiseTimedRange: false,
851 852
  onlySummary: false,
  runtimeTimerFilter: null,
853 854 855 856 857
};


ArgumentsProcessor.prototype.parse = function() {
  while (this.args_.length) {
858
    var arg = this.args_.shift();
859
    if (arg.charAt(0) != '-') {
860 861
      this.result_.logFileName = arg;
      continue;
862
    }
863 864 865 866 867 868
    var userValue = null;
    var eqPos = arg.indexOf('=');
    if (eqPos != -1) {
      userValue = arg.substr(eqPos + 1);
      arg = arg.substr(0, eqPos);
    }
869 870 871
    if (arg in this.argsDispatch_) {
      var dispatch = this.argsDispatch_[arg];
      this.result_[dispatch[0]] = userValue == null ? dispatch[1] : userValue;
872
    } else {
873
      return false;
874 875
    }
  }
876
  return true;
877 878 879
};


880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899
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_) {
900
    var synonyms = [arg];
901 902 903
    var dispatch = this.argsDispatch_[arg];
    for (var synArg in this.argsDispatch_) {
      if (arg !== synArg && dispatch === this.argsDispatch_[synArg]) {
904
        synonyms.push(synArg);
905 906 907
        delete this.argsDispatch_[synArg];
      }
    }
908
    print('  ' + padRight(synonyms.join(', '), 20) + " " + dispatch[2]);
909 910 911
  }
  quit(2);
};