Implemented processing of compressed log files.
authormikhail.naganov@gmail.com <mikhail.naganov@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 18 Jun 2009 07:59:13 +0000 (07:59 +0000)
committermikhail.naganov@gmail.com <mikhail.naganov@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 18 Jun 2009 07:59:13 +0000 (07:59 +0000)
- fixed address delta calculation;
- code creations are also compressed to be in sync with other events;
- factored out a base class from TickProcessor to reuse code in DevTools profiler.

Review URL: http://codereview.chromium.org/125256

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@2216 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/log-utils.cc
src/log.cc
test/mjsunit/tools/logreader.js [new file with mode: 0644]
tools/linux-tick-processor
tools/logreader.js [new file with mode: 0644]
tools/tickprocessor.js
tools/windows-tick-processor.bat

index 028eb3a..b31864b 100644 (file)
@@ -261,14 +261,20 @@ void LogMessageBuilder::AppendAddress(Address addr) {
 
 
 void LogMessageBuilder::AppendAddress(Address addr, Address bias) {
-  if (!FLAG_compress_log || bias == NULL) {
+  if (!FLAG_compress_log) {
     Append("0x%" V8PRIxPTR, addr);
+  } else if (bias == NULL) {
+    Append("%" V8PRIxPTR, addr);
   } else {
-    intptr_t delta = addr - bias;
-    // To avoid printing negative offsets in an unsigned form,
-    // we are printing an absolute value with a sign.
-    const char sign = delta >= 0 ? '+' : '-';
-    if (sign == '-') { delta = -delta; }
+    uintptr_t delta;
+    char sign;
+    if (addr >= bias) {
+      delta = addr - bias;
+      sign = '+';
+    } else {
+      delta = bias - addr;
+      sign = '-';
+    }
     Append("%c%" V8PRIxPTR, sign, delta);
   }
 }
index 1777234..9fbea14 100644 (file)
@@ -627,6 +627,42 @@ void Logger::DeleteEvent(const char* name, void* object) {
 }
 
 
+#ifdef ENABLE_LOGGING_AND_PROFILING
+
+// A class that contains all common code dealing with record compression.
+class CompressionHelper {
+ public:
+  explicit CompressionHelper(int window_size)
+      : compressor_(window_size), repeat_count_(0) { }
+
+  // Handles storing message in compressor, retrieving the previous one and
+  // prefixing it with repeat count, if needed.
+  // Returns true if message needs to be written to log.
+  bool HandleMessage(LogMessageBuilder* msg) {
+    if (!msg->StoreInCompressor(&compressor_)) {
+      // Current message repeats the previous one, don't write it.
+      ++repeat_count_;
+      return false;
+    }
+    if (repeat_count_ == 0) {
+      return msg->RetrieveCompressedPrevious(&compressor_);
+    }
+    OS::SNPrintF(prefix_, "%s,%d,",
+                 Logger::log_events_[Logger::REPEAT_META_EVENT],
+                 repeat_count_ + 1);
+    repeat_count_ = 0;
+    return msg->RetrieveCompressedPrevious(&compressor_, prefix_.start());
+  }
+
+ private:
+  LogRecordCompressor compressor_;
+  int repeat_count_;
+  EmbeddedVector<char, 20> prefix_;
+};
+
+#endif  // ENABLE_LOGGING_AND_PROFILING
+
+
 void Logger::CodeCreateEvent(LogEventsAndTags tag,
                              Code* code,
                              const char* comment) {
@@ -643,6 +679,10 @@ void Logger::CodeCreateEvent(LogEventsAndTags tag,
     msg.Append(*p);
   }
   msg.Append('"');
+  if (FLAG_compress_log) {
+    ASSERT(compression_helper_ != NULL);
+    if (!compression_helper_->HandleMessage(&msg)) return;
+  }
   msg.Append('\n');
   msg.WriteToLogFile();
 #endif
@@ -657,7 +697,12 @@ void Logger::CodeCreateEvent(LogEventsAndTags tag, Code* code, String* name) {
       name->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL);
   msg.Append("%s,%s,", log_events_[CODE_CREATION_EVENT], log_events_[tag]);
   msg.AppendAddress(code->address());
-  msg.Append(",%d,\"%s\"\n", code->ExecutableSize(), *str);
+  msg.Append(",%d,\"%s\"", code->ExecutableSize(), *str);
+  if (FLAG_compress_log) {
+    ASSERT(compression_helper_ != NULL);
+    if (!compression_helper_->HandleMessage(&msg)) return;
+  }
+  msg.Append('\n');
   msg.WriteToLogFile();
 #endif
 }
@@ -675,8 +720,13 @@ void Logger::CodeCreateEvent(LogEventsAndTags tag,
       source->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL);
   msg.Append("%s,%s,", log_events_[CODE_CREATION_EVENT], log_events_[tag]);
   msg.AppendAddress(code->address());
-  msg.Append(",%d,\"%s %s:%d\"\n",
+  msg.Append(",%d,\"%s %s:%d\"",
              code->ExecutableSize(), *str, *sourcestr, line);
+  if (FLAG_compress_log) {
+    ASSERT(compression_helper_ != NULL);
+    if (!compression_helper_->HandleMessage(&msg)) return;
+  }
+  msg.Append('\n');
   msg.WriteToLogFile();
 #endif
 }
@@ -688,7 +738,12 @@ void Logger::CodeCreateEvent(LogEventsAndTags tag, Code* code, int args_count) {
   LogMessageBuilder msg;
   msg.Append("%s,%s,", log_events_[CODE_CREATION_EVENT], log_events_[tag]);
   msg.AppendAddress(code->address());
-  msg.Append(",%d,\"args_count: %d\"\n", code->ExecutableSize(), args_count);
+  msg.Append(",%d,\"args_count: %d\"", code->ExecutableSize(), args_count);
+  if (FLAG_compress_log) {
+    ASSERT(compression_helper_ != NULL);
+    if (!compression_helper_->HandleMessage(&msg)) return;
+  }
+  msg.Append('\n');
   msg.WriteToLogFile();
 #endif
 }
@@ -703,48 +758,17 @@ void Logger::RegExpCodeCreateEvent(Code* code, String* source) {
   msg.AppendAddress(code->address());
   msg.Append(",%d,\"", code->ExecutableSize());
   msg.AppendDetailed(source, false);
-  msg.Append("\"\n");
+  msg.Append('\"');
+  if (FLAG_compress_log) {
+    ASSERT(compression_helper_ != NULL);
+    if (!compression_helper_->HandleMessage(&msg)) return;
+  }
+  msg.Append('\n');
   msg.WriteToLogFile();
 #endif
 }
 
 
-#ifdef ENABLE_LOGGING_AND_PROFILING
-
-// A class that contains all common code dealing with record compression.
-class CompressionHelper {
- public:
-  explicit CompressionHelper(int window_size)
-      : compressor_(window_size), repeat_count_(0) { }
-
-  // Handles storing message in compressor, retrieving the previous one and
-  // prefixing it with repeat count, if needed.
-  // Returns true if message needs to be written to log.
-  bool HandleMessage(LogMessageBuilder* msg) {
-    if (!msg->StoreInCompressor(&compressor_)) {
-      // Current message repeats the previous one, don't write it.
-      ++repeat_count_;
-      return false;
-    }
-    if (repeat_count_ == 0) {
-      return msg->RetrieveCompressedPrevious(&compressor_);
-    }
-    OS::SNPrintF(prefix_, "%s,%d,",
-                 Logger::log_events_[Logger::REPEAT_META_EVENT],
-                 repeat_count_ + 1);
-    repeat_count_ = 0;
-    return msg->RetrieveCompressedPrevious(&compressor_, prefix_.start());
-  }
-
- private:
-  LogRecordCompressor compressor_;
-  int repeat_count_;
-  EmbeddedVector<char, 20> prefix_;
-};
-
-#endif  // ENABLE_LOGGING_AND_PROFILING
-
-
 void Logger::CodeMoveEvent(Address from, Address to) {
 #ifdef ENABLE_LOGGING_AND_PROFILING
   static Address prev_to_ = NULL;
diff --git a/test/mjsunit/tools/logreader.js b/test/mjsunit/tools/logreader.js
new file mode 100644 (file)
index 0000000..dfd7f9f
--- /dev/null
@@ -0,0 +1,82 @@
+// 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.
+
+// Load CSV Parser and Log Reader implementations from <project root>/tools.
+// Files: tools/csvparser.js tools/logreader.js
+
+
+(function testAddressParser() {
+  var reader = new devtools.profiler.LogReader({});
+  var parser = reader.createAddressParser('test');
+
+  // Test that 0x values are parsed, and prevAddresses_ are untouched.
+  assertFalse('test' in reader.prevAddresses_);
+  assertEquals(0, parser('0x0'));
+  assertFalse('test' in reader.prevAddresses_);
+  assertEquals(0x100, parser('0x100'));
+  assertFalse('test' in reader.prevAddresses_);
+  assertEquals(0xffffffff, parser('0xffffffff'));
+  assertFalse('test' in reader.prevAddresses_);
+
+  // Test that values that has no '+' or '-' prefix are parsed
+  // and saved to prevAddresses_.
+  assertEquals(0, parser('0'));
+  assertEquals(0, reader.prevAddresses_.test);
+  assertEquals(0x100, parser('100'));
+  assertEquals(0x100, reader.prevAddresses_.test);
+  assertEquals(0xffffffff, parser('ffffffff'));
+  assertEquals(0xffffffff, reader.prevAddresses_.test);
+
+  // Test that values prefixed with '+' or '-' are treated as deltas,
+  // and prevAddresses_ is updated.
+  // Set base value.
+  assertEquals(0x100, parser('100'));
+  assertEquals(0x100, reader.prevAddresses_.test);
+  assertEquals(0x200, parser('+100'));
+  assertEquals(0x200, reader.prevAddresses_.test);
+  assertEquals(0x100, parser('-100'));
+  assertEquals(0x100, reader.prevAddresses_.test);
+})();
+
+
+(function testAddressParser() {
+  var reader = new devtools.profiler.LogReader({});
+
+  assertEquals([0x10000000, 0x10001000, 0xffff000, 0x10000000],
+               reader.processStack(0x10000000, ['overflow',
+                   '+1000', '-2000', '+1000']));
+})();
+
+
+(function testExpandBackRef() {
+  var reader = new devtools.profiler.LogReader({});
+
+  assertEquals('aaaaaaaa', reader.expandBackRef_('aaaaaaaa'));
+  assertEquals('aaaaaaaa', reader.expandBackRef_('#1'));
+  assertEquals('bbbbaaaa', reader.expandBackRef_('bbbb#2:4'));
+  assertEquals('"#1:1"', reader.expandBackRef_('"#1:1"'));
+})();
index 968c241..2de988c 100755 (executable)
@@ -12,4 +12,4 @@ d8_exec=$D8_PATH/d8
 $d8_exec $tools_path/splaytree.js $tools_path/codemap.js \
   $tools_path/csvparser.js $tools_path/consarray.js \
   $tools_path/profile.js $tools_path/profile_view.js \
-  $tools_path/tickprocessor.js -- $@ 2>/dev/null
+  $tools_path/logreader.js $tools_path/tickprocessor.js -- $@ 2>/dev/null
diff --git a/tools/logreader.js b/tools/logreader.js
new file mode 100644 (file)
index 0000000..78085a4
--- /dev/null
@@ -0,0 +1,317 @@
+// 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.
+
+/**
+ * @fileoverview Log Reader is used to process log file produced by V8.
+ */
+
+// Initlialize namespaces
+var devtools = devtools || {};
+devtools.profiler = devtools.profiler || {};
+
+
+/**
+ * Base class for processing log files.
+ *
+ * @param {Array.<Object>} dispatchTable A table used for parsing and processing
+ *     log records.
+ * @constructor
+ */
+devtools.profiler.LogReader = function(dispatchTable) {
+  /**
+   * @type {Array.<Object>}
+   */
+  this.dispatchTable_ = dispatchTable;
+  this.dispatchTable_['alias'] =
+      { parsers: [null, null], processor: this.processAlias_ };
+  this.dispatchTable_['repeat'] =
+      { parsers: [parseInt, 'var-args'], processor: this.processRepeat_,
+        backrefs: true };
+
+  /**
+   * A key-value map for aliases. Translates short name -> full name.
+   * @type {Object}
+   */
+  this.aliases_ = {};
+
+  /**
+   * A key-value map for previous address values.
+   * @type {Object}
+   */
+  this.prevAddresses_ = {};
+
+  /**
+   * A key-value map for events than can be backreference-compressed.
+   * @type {Object}
+   */
+  this.backRefsCommands_ = {};
+  this.initBackRefsCommands_();
+
+  /**
+   * Back references for decompression.
+   * @type {Array.<string>}
+   */
+  this.backRefs_ = [];
+};
+
+
+/**
+ * Creates a parser for an address entry.
+ *
+ * @param {string} addressTag Address tag to perform offset decoding.
+ * @return {function(string):number} Address parser.
+ */
+devtools.profiler.LogReader.prototype.createAddressParser = function(
+    addressTag) {
+  var self = this;
+  return (function (str) {
+    var value = parseInt(str, 16);
+    var firstChar = str.charAt(0);
+    if (firstChar == '+' || firstChar == '-') {
+      var addr = self.prevAddresses_[addressTag];
+      addr += value;
+      self.prevAddresses_[addressTag] = addr;
+      return addr;
+    } else if (firstChar != '0' || str.charAt(1) != 'x') {
+      self.prevAddresses_[addressTag] = value;
+    }
+    return value;
+  });
+};
+
+
+/**
+ * Expands an alias symbol, if applicable.
+ *
+ * @param {string} symbol Symbol to expand.
+ * @return {string} Expanded symbol, or the input symbol itself.
+ */
+devtools.profiler.LogReader.prototype.expandAlias = function(symbol) {
+  return symbol in this.aliases_ ? this.aliases_[symbol] : symbol;
+};
+
+
+/**
+ * Used for printing error messages.
+ *
+ * @param {string} str Error message.
+ */
+devtools.profiler.LogReader.prototype.printError = function(str) {
+  // Do nothing.
+};
+
+
+/**
+ * Processes a portion of V8 profiler event log.
+ *
+ * @param {string} chunk A portion of log.
+ */
+devtools.profiler.LogReader.prototype.processLogChunk = function(chunk) {
+  this.processLog_(chunk.split('\n'));
+};
+
+
+/**
+ * Processes stack record.
+ *
+ * @param {number} pc Program counter.
+ * @param {Array.<string>} stack String representation of a stack.
+ * @return {Array.<number>} Processed stack.
+ */
+devtools.profiler.LogReader.prototype.processStack = function(pc, stack) {
+  var fullStack = [pc];
+  var prevFrame = pc;
+  for (var i = 0, n = stack.length; i < n; ++i) {
+    var frame = stack[i];
+    var firstChar = frame.charAt(0);
+    if (firstChar == '+' || firstChar == '-') {
+      // An offset from the previous frame.
+      prevFrame += parseInt(frame, 16);
+      fullStack.push(prevFrame);
+    // Filter out possible 'overflow' string.
+    } else if (firstChar != 'o') {
+      fullStack.push(parseInt(frame, 16));
+    }
+  }
+  return fullStack;
+};
+
+
+/**
+ * Returns whether a particular dispatch must be skipped.
+ *
+ * @param {!Object} dispatch Dispatch record.
+ * @return {boolean} True if dispatch must be skipped.
+ */
+devtools.profiler.LogReader.prototype.skipDispatch = function(dispatch) {
+  return false;
+};
+
+
+/**
+ * Does a dispatch of a log record.
+ *
+ * @param {Array.<string>} fields Log record.
+ * @private
+ */
+devtools.profiler.LogReader.prototype.dispatchLogRow_ = function(fields) {
+  // Obtain the dispatch.
+  var command = fields[0];
+  if (!(command in this.dispatchTable_)) {
+    throw new Error('unknown command: ' + command);
+  }
+  var dispatch = this.dispatchTable_[command];
+
+  if (dispatch === null || this.skipDispatch(dispatch)) {
+    return;
+  }
+
+  // Parse fields.
+  var parsedFields = [];
+  for (var i = 0; i < dispatch.parsers.length; ++i) {
+    var parser = dispatch.parsers[i];
+    if (parser === null) {
+      parsedFields.push(fields[1 + i]);
+    } else if (typeof parser == 'function') {
+      parsedFields.push(parser(fields[1 + i]));
+    } else {
+      // var-args
+      parsedFields.push(fields.slice(1 + i));
+      break;
+    }
+  }
+
+  // Run the processor.
+  dispatch.processor.apply(this, parsedFields);
+};
+
+
+/**
+ * Decompresses a line if it was backreference-compressed.
+ *
+ * @param {string} line Possibly compressed line.
+ * @return {string} Decompressed line.
+ * @private
+ */
+devtools.profiler.LogReader.prototype.expandBackRef_ = function(line) {
+  var backRefPos;
+  // Filter out case when a regexp is created containing '#'.
+  if (line.charAt(line.length - 1) != '"'
+      && (backRefPos = line.lastIndexOf('#')) != -1) {
+    var backRef = line.substr(backRefPos + 1);
+    var backRefIdx = parseInt(backRef, 10) - 1;
+    var colonPos = backRef.indexOf(':');
+    var backRefStart =
+        colonPos != -1 ? parseInt(backRef.substr(colonPos + 1), 10) : 0;
+    line = line.substr(0, backRefPos) +
+        this.backRefs_[backRefIdx].substr(backRefStart);
+  }
+  this.backRefs_.unshift(line);
+  if (this.backRefs_.length > 10) {
+    this.backRefs_.length = 10;
+  }
+  return line;
+};
+
+
+/**
+ * Initializes the map of backward reference compressible commands.
+ * @private
+ */
+devtools.profiler.LogReader.prototype.initBackRefsCommands_ = function() {
+  for (var event in this.dispatchTable_) {
+    var dispatch = this.dispatchTable_[event];
+    if (dispatch && dispatch.backrefs) {
+      this.backRefsCommands_[event] = true;
+    }
+  }
+};
+
+
+/**
+ * Processes alias log record. Adds an alias to a corresponding map.
+ *
+ * @param {string} symbol Short name.
+ * @param {string} expansion Long name.
+ * @private
+ */
+devtools.profiler.LogReader.prototype.processAlias_ = function(
+    symbol, expansion) {
+  if (expansion in this.dispatchTable_) {
+    this.dispatchTable_[symbol] = this.dispatchTable_[expansion];
+    if (expansion in this.backRefsCommands_) {
+      this.backRefsCommands_[symbol] = true;
+    }
+  } else {
+    this.aliases_[symbol] = expansion;
+  }
+};
+
+
+/**
+ * Processes log lines.
+ *
+ * @param {Array.<string>} lines Log lines.
+ * @private
+ */
+devtools.profiler.LogReader.prototype.processLog_ = function(lines) {
+  var csvParser = new devtools.profiler.CsvParser();
+  try {
+    for (var i = 0, n = lines.length; i < n; ++i) {
+      var line = lines[i];
+      if (!line) {
+        continue;
+      }
+      if (line.charAt(0) == '#' ||
+          line.substr(0, line.indexOf(',')) in this.backRefsCommands_) {
+        line = this.expandBackRef_(line);
+      }
+      var fields = csvParser.parseLine(line);
+      this.dispatchLogRow_(fields);
+    }
+  } catch (e) {
+    this.printError('line ' + (i + 1) + ': ' + (e.message || e));
+    throw e;
+  }
+};
+
+
+/**
+ * Processes repeat log record. Expands it according to calls count and
+ * invokes processing.
+ *
+ * @param {number} count Count.
+ * @param {Array.<string>} cmd Parsed command.
+ * @private
+ */
+devtools.profiler.LogReader.prototype.processRepeat_ = function(count, cmd) {
+  // Replace the repeat-prefixed command from backrefs list with a non-prefixed.
+  this.backRefs_[0] = cmd.join(',');
+  for (var i = 0; i < count; ++i) {
+    this.dispatchLogRow_(cmd);
+  }
+};
index 3c5fab4..72d367f 100644 (file)
@@ -52,8 +52,35 @@ function readFile(fileName) {
 }
 
 
+function inherits(childCtor, parentCtor) {
+  function tempCtor() {};
+  tempCtor.prototype = parentCtor.prototype;
+  childCtor.prototype = new tempCtor();
+};
+
+
 function TickProcessor(
     cppEntriesProvider, separateIc, ignoreUnknown, stateFilter) {
+  devtools.profiler.LogReader.call(this, {
+      'shared-library': { parsers: [null, parseInt, parseInt],
+          processor: this.processSharedLibrary },
+      'code-creation': {
+          parsers: [null, this.createAddressParser('code'), parseInt, null],
+          processor: this.processCodeCreation, backrefs: true },
+      'code-move': { parsers: [this.createAddressParser('code'),
+          this.createAddressParser('code-move-to')],
+          processor: this.processCodeMove, backrefs: true },
+      'code-delete': { parsers: [this.createAddressParser('code')],
+          processor: this.processCodeDelete, backrefs: true },
+      'tick': { parsers: [this.createAddressParser('code'),
+          this.createAddressParser('stack'), parseInt, 'var-args'],
+          processor: this.processTick, backrefs: true },
+      'profiler': null,
+      // Obsolete row types.
+      'code-allocate': null,
+      'begin-code-region': null,
+      'end-code-region': null });
+
   this.cppEntriesProvider_ = cppEntriesProvider;
   this.ignoreUnknown_ = ignoreUnknown;
   this.stateFilter_ = stateFilter;
@@ -86,8 +113,8 @@ function TickProcessor(
   // Count each tick as a time unit.
   this.viewBuilder_ = new devtools.profiler.ViewBuilder(1);
   this.lastLogFileName_ = null;
-  this.aliases_ = {};
 };
+inherits(TickProcessor, devtools.profiler.LogReader);
 
 
 TickProcessor.VmStates = {
@@ -107,27 +134,17 @@ TickProcessor.CodeTypes = {
 // codeTypes_ map because there can be zillions of them.
 
 
-TickProcessor.RecordsDispatch = {
-  'shared-library': { parsers: [null, parseInt, parseInt],
-                      processor: 'processSharedLibrary' },
-  'code-creation': { parsers: [null, parseInt, parseInt, null],
-                   processor: 'processCodeCreation' },
-  'code-move': { parsers: [parseInt, parseInt],
-                 processor: 'processCodeMove' },
-  'code-delete': { parsers: [parseInt], processor: 'processCodeDelete' },
-  'tick': { parsers: [parseInt, parseInt, parseInt, 'var-args'],
-            processor: 'processTick' },
-  'alias': { parsers: [null, null], processor: 'processAlias' },
-  'profiler': null,
-  // Obsolete row types.
-  'code-allocate': null,
-  'begin-code-region': null,
-  'end-code-region': null
-};
-
 TickProcessor.CALL_PROFILE_CUTOFF_PCT = 2.0;
 
 
+/**
+ * @override
+ */
+TickProcessor.prototype.printError = function(str) {
+  print(str);
+};
+
+
 TickProcessor.prototype.setCodeType = function(name, type) {
   this.codeTypes_[name] = TickProcessor.CodeTypes[type];
 };
@@ -151,52 +168,7 @@ TickProcessor.prototype.isJsCode = function(name) {
 TickProcessor.prototype.processLogFile = function(fileName) {
   this.lastLogFileName_ = fileName;
   var contents = readFile(fileName);
-  this.processLog(contents.split('\n'));
-};
-
-
-TickProcessor.prototype.processLog = function(lines) {
-  var csvParser = new devtools.profiler.CsvParser();
-  for (var i = 0, n = lines.length; i < n; ++i) {
-    var line = lines[i];
-    if (!line) {
-      continue;
-    }
-    var fields = csvParser.parseLine(line);
-    this.dispatchLogRow(fields);
-  }
-};
-
-
-TickProcessor.prototype.dispatchLogRow = function(fields) {
-  // Obtain the dispatch.
-  var command = fields[0];
-  if (!(command in TickProcessor.RecordsDispatch)) {
-    throw new Error('unknown command: ' + command);
-  }
-  var dispatch = TickProcessor.RecordsDispatch[command];
-
-  if (dispatch === null) {
-    return;
-  }
-
-  // Parse fields.
-  var parsedFields = [];
-  for (var i = 0; i < dispatch.parsers.length; ++i) {
-    var parser = dispatch.parsers[i];
-    if (parser === null) {
-      parsedFields.push(fields[1 + i]);
-    } else if (typeof parser == 'function') {
-      parsedFields.push(parser(fields[1 + i]));
-    } else {
-      // var-args
-      parsedFields.push(fields.slice(1 + i));
-      break;
-    }
-  }
-
-  // Run the processor.
-  this[dispatch.processor].apply(this, parsedFields);
+  this.processLogChunk(contents);
 };
 
 
@@ -214,22 +186,10 @@ TickProcessor.prototype.processSharedLibrary = function(
 };
 
 
-TickProcessor.prototype.processAlias = function(symbol, expansion) {
-  if (expansion in TickProcessor.RecordsDispatch) {
-    TickProcessor.RecordsDispatch[symbol] =
-      TickProcessor.RecordsDispatch[expansion];
-  } else {
-    this.aliases_[symbol] = expansion;
-  }
-};
-
-
 TickProcessor.prototype.processCodeCreation = function(
     type, start, size, name) {
-  if (type in this.aliases_) {
-    type = this.aliases_[type];
-  }
-  var entry = this.profile_.addCode(type, name, start, size);
+  var entry = this.profile_.addCode(
+      this.expandAlias(type), name, start, size);
 };
 
 
@@ -256,21 +216,7 @@ TickProcessor.prototype.processTick = function(pc, sp, vmState, stack) {
     return;
   }
 
-  var fullStack = [pc];
-  var prevFrame = pc;
-  for (var i = 0, n = stack.length; i < n; ++i) {
-    var frame = stack[i];
-    var firstChar = frame.charAt(0);
-    // Leave only numbers starting with 0x. Filter possible 'overflow' string.
-    if (firstChar == '0') {
-      fullStack.push(parseInt(frame, 16));
-    } else if (firstChar == '+' || firstChar == '-') {
-      // An offset from the previous frame.
-      prevFrame += parseInt(frame, 16);
-      fullStack.push(prevFrame);
-    }
-  }
-  this.profile_.recordTick(fullStack);
+  this.profile_.recordTick(this.processStack(pc, stack));
 };
 
 
@@ -464,13 +410,6 @@ CppEntriesProvider.prototype.parseNextLine = function() {
 };
 
 
-function inherits(childCtor, parentCtor) {
-  function tempCtor() {};
-  tempCtor.prototype = parentCtor.prototype;
-  childCtor.prototype = new tempCtor();
-};
-
-
 function UnixCppEntriesProvider() {
   this.symbols = [];
   this.parsePos = 0;
@@ -490,7 +429,7 @@ UnixCppEntriesProvider.prototype.loadSymbols = function(libName) {
     ];
   } catch (e) {
     // If the library cannot be found on this system let's not panic.
-    this.symbols = [ '', '' ];
+    this.symbols = ['', ''];
   }
 };
 
index 52454e3..67cbe98 100755 (executable)
@@ -2,4 +2,4 @@
 
 SET tools_dir=%~dp0
 
-%tools_dir%..\d8 %tools_dir%splaytree.js %tools_dir%codemap.js %tools_dir%csvparser.js %tools_dir%consarray.js %tools_dir%profile.js %tools_dir%profile_view.js %tools_dir%tickprocessor.js -- --windows %*
+%tools_dir%..\d8 %tools_dir%splaytree.js %tools_dir%codemap.js %tools_dir%csvparser.js %tools_dir%consarray.js %tools_dir%profile.js %tools_dir%profile_view.js %tools_dir%logreader.js %tools_dir%tickprocessor.js -- --windows %*