Home | History | Annotate | Download | only in tools
      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 /**
     29  * @fileoverview Log Reader is used to process log file produced by V8.
     30  */
     31 
     32 // Initlialize namespaces
     33 var devtools = devtools || {};
     34 devtools.profiler = devtools.profiler || {};
     35 
     36 
     37 /**
     38  * Base class for processing log files.
     39  *
     40  * @param {Array.<Object>} dispatchTable A table used for parsing and processing
     41  *     log records.
     42  * @constructor
     43  */
     44 devtools.profiler.LogReader = function(dispatchTable) {
     45   /**
     46    * @type {Array.<Object>}
     47    */
     48   this.dispatchTable_ = dispatchTable;
     49   this.dispatchTable_['alias'] =
     50       { parsers: [null, null], processor: this.processAlias_ };
     51   this.dispatchTable_['repeat'] =
     52       { parsers: [parseInt, 'var-args'], processor: this.processRepeat_,
     53         backrefs: true };
     54 
     55   /**
     56    * A key-value map for aliases. Translates short name -> full name.
     57    * @type {Object}
     58    */
     59   this.aliases_ = {};
     60 
     61   /**
     62    * A key-value map for previous address values.
     63    * @type {Object}
     64    */
     65   this.prevAddresses_ = {};
     66 
     67   /**
     68    * A key-value map for events than can be backreference-compressed.
     69    * @type {Object}
     70    */
     71   this.backRefsCommands_ = {};
     72   this.initBackRefsCommands_();
     73 
     74   /**
     75    * Back references for decompression.
     76    * @type {Array.<string>}
     77    */
     78   this.backRefs_ = [];
     79 
     80   /**
     81    * Current line.
     82    * @type {number}
     83    */
     84   this.lineNum_ = 0;
     85 
     86   /**
     87    * CSV lines parser.
     88    * @type {devtools.profiler.CsvParser}
     89    */
     90   this.csvParser_ = new devtools.profiler.CsvParser();
     91 };
     92 
     93 
     94 /**
     95  * Creates a parser for an address entry.
     96  *
     97  * @param {string} addressTag Address tag to perform offset decoding.
     98  * @return {function(string):number} Address parser.
     99  */
    100 devtools.profiler.LogReader.prototype.createAddressParser = function(
    101     addressTag) {
    102   var self = this;
    103   return (function (str) {
    104     var value = parseInt(str, 16);
    105     var firstChar = str.charAt(0);
    106     if (firstChar == '+' || firstChar == '-') {
    107       var addr = self.prevAddresses_[addressTag];
    108       addr += value;
    109       self.prevAddresses_[addressTag] = addr;
    110       return addr;
    111     } else if (firstChar != '0' || str.charAt(1) != 'x') {
    112       self.prevAddresses_[addressTag] = value;
    113     }
    114     return value;
    115   });
    116 };
    117 
    118 
    119 /**
    120  * Expands an alias symbol, if applicable.
    121  *
    122  * @param {string} symbol Symbol to expand.
    123  * @return {string} Expanded symbol, or the input symbol itself.
    124  */
    125 devtools.profiler.LogReader.prototype.expandAlias = function(symbol) {
    126   return symbol in this.aliases_ ? this.aliases_[symbol] : symbol;
    127 };
    128 
    129 
    130 /**
    131  * Used for printing error messages.
    132  *
    133  * @param {string} str Error message.
    134  */
    135 devtools.profiler.LogReader.prototype.printError = function(str) {
    136   // Do nothing.
    137 };
    138 
    139 
    140 /**
    141  * Processes a portion of V8 profiler event log.
    142  *
    143  * @param {string} chunk A portion of log.
    144  */
    145 devtools.profiler.LogReader.prototype.processLogChunk = function(chunk) {
    146   this.processLog_(chunk.split('\n'));
    147 };
    148 
    149 
    150 /**
    151  * Processes a line of V8 profiler event log.
    152  *
    153  * @param {string} line A line of log.
    154  */
    155 devtools.profiler.LogReader.prototype.processLogLine = function(line) {
    156   this.processLog_([line]);
    157 };
    158 
    159 
    160 /**
    161  * Processes stack record.
    162  *
    163  * @param {number} pc Program counter.
    164  * @param {number} func JS Function.
    165  * @param {Array.<string>} stack String representation of a stack.
    166  * @return {Array.<number>} Processed stack.
    167  */
    168 devtools.profiler.LogReader.prototype.processStack = function(pc, func, stack) {
    169   var fullStack = func ? [pc, func] : [pc];
    170   var prevFrame = pc;
    171   for (var i = 0, n = stack.length; i < n; ++i) {
    172     var frame = stack[i];
    173     var firstChar = frame.charAt(0);
    174     if (firstChar == '+' || firstChar == '-') {
    175       // An offset from the previous frame.
    176       prevFrame += parseInt(frame, 16);
    177       fullStack.push(prevFrame);
    178     // Filter out possible 'overflow' string.
    179     } else if (firstChar != 'o') {
    180       fullStack.push(parseInt(frame, 16));
    181     }
    182   }
    183   return fullStack;
    184 };
    185 
    186 
    187 /**
    188  * Returns whether a particular dispatch must be skipped.
    189  *
    190  * @param {!Object} dispatch Dispatch record.
    191  * @return {boolean} True if dispatch must be skipped.
    192  */
    193 devtools.profiler.LogReader.prototype.skipDispatch = function(dispatch) {
    194   return false;
    195 };
    196 
    197 
    198 /**
    199  * Does a dispatch of a log record.
    200  *
    201  * @param {Array.<string>} fields Log record.
    202  * @private
    203  */
    204 devtools.profiler.LogReader.prototype.dispatchLogRow_ = function(fields) {
    205   // Obtain the dispatch.
    206   var command = fields[0];
    207   if (!(command in this.dispatchTable_)) {
    208     throw new Error('unknown command: ' + command);
    209   }
    210   var dispatch = this.dispatchTable_[command];
    211 
    212   if (dispatch === null || this.skipDispatch(dispatch)) {
    213     return;
    214   }
    215 
    216   // Parse fields.
    217   var parsedFields = [];
    218   for (var i = 0; i < dispatch.parsers.length; ++i) {
    219     var parser = dispatch.parsers[i];
    220     if (parser === null) {
    221       parsedFields.push(fields[1 + i]);
    222     } else if (typeof parser == 'function') {
    223       parsedFields.push(parser(fields[1 + i]));
    224     } else {
    225       // var-args
    226       parsedFields.push(fields.slice(1 + i));
    227       break;
    228     }
    229   }
    230 
    231   // Run the processor.
    232   dispatch.processor.apply(this, parsedFields);
    233 };
    234 
    235 
    236 /**
    237  * Decompresses a line if it was backreference-compressed.
    238  *
    239  * @param {string} line Possibly compressed line.
    240  * @return {string} Decompressed line.
    241  * @private
    242  */
    243 devtools.profiler.LogReader.prototype.expandBackRef_ = function(line) {
    244   var backRefPos;
    245   // Filter out case when a regexp is created containing '#'.
    246   if (line.charAt(line.length - 1) != '"'
    247       && (backRefPos = line.lastIndexOf('#')) != -1) {
    248     var backRef = line.substr(backRefPos + 1);
    249     var backRefIdx = parseInt(backRef, 10) - 1;
    250     var colonPos = backRef.indexOf(':');
    251     var backRefStart =
    252         colonPos != -1 ? parseInt(backRef.substr(colonPos + 1), 10) : 0;
    253     line = line.substr(0, backRefPos) +
    254         this.backRefs_[backRefIdx].substr(backRefStart);
    255   }
    256   this.backRefs_.unshift(line);
    257   if (this.backRefs_.length > 10) {
    258     this.backRefs_.length = 10;
    259   }
    260   return line;
    261 };
    262 
    263 
    264 /**
    265  * Initializes the map of backward reference compressible commands.
    266  * @private
    267  */
    268 devtools.profiler.LogReader.prototype.initBackRefsCommands_ = function() {
    269   for (var event in this.dispatchTable_) {
    270     var dispatch = this.dispatchTable_[event];
    271     if (dispatch && dispatch.backrefs) {
    272       this.backRefsCommands_[event] = true;
    273     }
    274   }
    275 };
    276 
    277 
    278 /**
    279  * Processes alias log record. Adds an alias to a corresponding map.
    280  *
    281  * @param {string} symbol Short name.
    282  * @param {string} expansion Long name.
    283  * @private
    284  */
    285 devtools.profiler.LogReader.prototype.processAlias_ = function(
    286     symbol, expansion) {
    287   if (expansion in this.dispatchTable_) {
    288     this.dispatchTable_[symbol] = this.dispatchTable_[expansion];
    289     if (expansion in this.backRefsCommands_) {
    290       this.backRefsCommands_[symbol] = true;
    291     }
    292   } else {
    293     this.aliases_[symbol] = expansion;
    294   }
    295 };
    296 
    297 
    298 /**
    299  * Processes log lines.
    300  *
    301  * @param {Array.<string>} lines Log lines.
    302  * @private
    303  */
    304 devtools.profiler.LogReader.prototype.processLog_ = function(lines) {
    305   for (var i = 0, n = lines.length; i < n; ++i, ++this.lineNum_) {
    306     var line = lines[i];
    307     if (!line) {
    308       continue;
    309     }
    310     try {
    311       if (line.charAt(0) == '#' ||
    312           line.substr(0, line.indexOf(',')) in this.backRefsCommands_) {
    313         line = this.expandBackRef_(line);
    314       }
    315       var fields = this.csvParser_.parseLine(line);
    316       this.dispatchLogRow_(fields);
    317     } catch (e) {
    318       this.printError('line ' + (this.lineNum_ + 1) + ': ' + (e.message || e));
    319     }
    320   }
    321 };
    322 
    323 
    324 /**
    325  * Processes repeat log record. Expands it according to calls count and
    326  * invokes processing.
    327  *
    328  * @param {number} count Count.
    329  * @param {Array.<string>} cmd Parsed command.
    330  * @private
    331  */
    332 devtools.profiler.LogReader.prototype.processRepeat_ = function(count, cmd) {
    333   // Replace the repeat-prefixed command from backrefs list with a non-prefixed.
    334   this.backRefs_[0] = cmd.join(',');
    335   for (var i = 0; i < count; ++i) {
    336     this.dispatchLogRow_(cmd);
    337   }
    338 };
    339