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 82/** 83 * Creates a parser for an address entry. 84 * 85 * @param {string} addressTag Address tag to perform offset decoding. 86 * @return {function(string):number} Address parser. 87 */ 88devtools.profiler.LogReader.prototype.createAddressParser = function( 89 addressTag) { 90 var self = this; 91 return (function (str) { 92 var value = parseInt(str, 16); 93 var firstChar = str.charAt(0); 94 if (firstChar == '+' || firstChar == '-') { 95 var addr = self.prevAddresses_[addressTag]; 96 addr += value; 97 self.prevAddresses_[addressTag] = addr; 98 return addr; 99 } else if (firstChar != '0' || str.charAt(1) != 'x') { 100 self.prevAddresses_[addressTag] = value; 101 } 102 return value; 103 }); 104}; 105 106 107/** 108 * Expands an alias symbol, if applicable. 109 * 110 * @param {string} symbol Symbol to expand. 111 * @return {string} Expanded symbol, or the input symbol itself. 112 */ 113devtools.profiler.LogReader.prototype.expandAlias = function(symbol) { 114 return symbol in this.aliases_ ? this.aliases_[symbol] : symbol; 115}; 116 117 118/** 119 * Used for printing error messages. 120 * 121 * @param {string} str Error message. 122 */ 123devtools.profiler.LogReader.prototype.printError = function(str) { 124 // Do nothing. 125}; 126 127 128/** 129 * Processes a portion of V8 profiler event log. 130 * 131 * @param {string} chunk A portion of log. 132 */ 133devtools.profiler.LogReader.prototype.processLogChunk = function(chunk) { 134 this.processLog_(chunk.split('\n')); 135}; 136 137 138/** 139 * Processes stack record. 140 * 141 * @param {number} pc Program counter. 142 * @param {Array.<string>} stack String representation of a stack. 143 * @return {Array.<number>} Processed stack. 144 */ 145devtools.profiler.LogReader.prototype.processStack = function(pc, stack) { 146 var fullStack = [pc]; 147 var prevFrame = pc; 148 for (var i = 0, n = stack.length; i < n; ++i) { 149 var frame = stack[i]; 150 var firstChar = frame.charAt(0); 151 if (firstChar == '+' || firstChar == '-') { 152 // An offset from the previous frame. 153 prevFrame += parseInt(frame, 16); 154 fullStack.push(prevFrame); 155 // Filter out possible 'overflow' string. 156 } else if (firstChar != 'o') { 157 fullStack.push(parseInt(frame, 16)); 158 } 159 } 160 return fullStack; 161}; 162 163 164/** 165 * Returns whether a particular dispatch must be skipped. 166 * 167 * @param {!Object} dispatch Dispatch record. 168 * @return {boolean} True if dispatch must be skipped. 169 */ 170devtools.profiler.LogReader.prototype.skipDispatch = function(dispatch) { 171 return false; 172}; 173 174 175/** 176 * Does a dispatch of a log record. 177 * 178 * @param {Array.<string>} fields Log record. 179 * @private 180 */ 181devtools.profiler.LogReader.prototype.dispatchLogRow_ = function(fields) { 182 // Obtain the dispatch. 183 var command = fields[0]; 184 if (!(command in this.dispatchTable_)) { 185 throw new Error('unknown command: ' + command); 186 } 187 var dispatch = this.dispatchTable_[command]; 188 189 if (dispatch === null || this.skipDispatch(dispatch)) { 190 return; 191 } 192 193 // Parse fields. 194 var parsedFields = []; 195 for (var i = 0; i < dispatch.parsers.length; ++i) { 196 var parser = dispatch.parsers[i]; 197 if (parser === null) { 198 parsedFields.push(fields[1 + i]); 199 } else if (typeof parser == 'function') { 200 parsedFields.push(parser(fields[1 + i])); 201 } else { 202 // var-args 203 parsedFields.push(fields.slice(1 + i)); 204 break; 205 } 206 } 207 208 // Run the processor. 209 dispatch.processor.apply(this, parsedFields); 210}; 211 212 213/** 214 * Decompresses a line if it was backreference-compressed. 215 * 216 * @param {string} line Possibly compressed line. 217 * @return {string} Decompressed line. 218 * @private 219 */ 220devtools.profiler.LogReader.prototype.expandBackRef_ = function(line) { 221 var backRefPos; 222 // Filter out case when a regexp is created containing '#'. 223 if (line.charAt(line.length - 1) != '"' 224 && (backRefPos = line.lastIndexOf('#')) != -1) { 225 var backRef = line.substr(backRefPos + 1); 226 var backRefIdx = parseInt(backRef, 10) - 1; 227 var colonPos = backRef.indexOf(':'); 228 var backRefStart = 229 colonPos != -1 ? parseInt(backRef.substr(colonPos + 1), 10) : 0; 230 line = line.substr(0, backRefPos) + 231 this.backRefs_[backRefIdx].substr(backRefStart); 232 } 233 this.backRefs_.unshift(line); 234 if (this.backRefs_.length > 10) { 235 this.backRefs_.length = 10; 236 } 237 return line; 238}; 239 240 241/** 242 * Initializes the map of backward reference compressible commands. 243 * @private 244 */ 245devtools.profiler.LogReader.prototype.initBackRefsCommands_ = function() { 246 for (var event in this.dispatchTable_) { 247 var dispatch = this.dispatchTable_[event]; 248 if (dispatch && dispatch.backrefs) { 249 this.backRefsCommands_[event] = true; 250 } 251 } 252}; 253 254 255/** 256 * Processes alias log record. Adds an alias to a corresponding map. 257 * 258 * @param {string} symbol Short name. 259 * @param {string} expansion Long name. 260 * @private 261 */ 262devtools.profiler.LogReader.prototype.processAlias_ = function( 263 symbol, expansion) { 264 if (expansion in this.dispatchTable_) { 265 this.dispatchTable_[symbol] = this.dispatchTable_[expansion]; 266 if (expansion in this.backRefsCommands_) { 267 this.backRefsCommands_[symbol] = true; 268 } 269 } else { 270 this.aliases_[symbol] = expansion; 271 } 272}; 273 274 275/** 276 * Processes log lines. 277 * 278 * @param {Array.<string>} lines Log lines. 279 * @private 280 */ 281devtools.profiler.LogReader.prototype.processLog_ = function(lines) { 282 var csvParser = new devtools.profiler.CsvParser(); 283 try { 284 for (var i = 0, n = lines.length; i < n; ++i) { 285 var line = lines[i]; 286 if (!line) { 287 continue; 288 } 289 if (line.charAt(0) == '#' || 290 line.substr(0, line.indexOf(',')) in this.backRefsCommands_) { 291 line = this.expandBackRef_(line); 292 } 293 var fields = csvParser.parseLine(line); 294 this.dispatchLogRow_(fields); 295 } 296 } catch (e) { 297 // An error on the last line is acceptable since log file can be truncated. 298 if (i < n - 1) { 299 this.printError('line ' + (i + 1) + ': ' + (e.message || e)); 300 throw e; 301 } 302 } 303}; 304 305 306/** 307 * Processes repeat log record. Expands it according to calls count and 308 * invokes processing. 309 * 310 * @param {number} count Count. 311 * @param {Array.<string>} cmd Parsed command. 312 * @private 313 */ 314devtools.profiler.LogReader.prototype.processRepeat_ = function(count, cmd) { 315 // Replace the repeat-prefixed command from backrefs list with a non-prefixed. 316 this.backRefs_[0] = cmd.join(','); 317 for (var i = 0; i < count; ++i) { 318 this.dispatchLogRow_(cmd); 319 } 320}; 321