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 33var devtools = devtools || {}; 34devtools.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 */ 44devtools.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 */ 100devtools.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 */ 125devtools.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 */ 135devtools.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 */ 145devtools.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 */ 155devtools.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 */ 168devtools.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 */ 193devtools.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 */ 204devtools.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 */ 243devtools.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 */ 268devtools.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 */ 285devtools.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 */ 304devtools.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 */ 332devtools.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