1// Copyright 2011 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 33/** 34 * Base class for processing log files. 35 * 36 * @param {Array.<Object>} dispatchTable A table used for parsing and processing 37 * log records. 38 * @param {boolean} timedRange Ignore ticks outside timed range. 39 * @param {boolean} pairwiseTimedRange Ignore ticks outside pairs of timer 40 * markers. 41 * @constructor 42 */ 43function LogReader(dispatchTable, timedRange, pairwiseTimedRange) { 44 /** 45 * @type {Array.<Object>} 46 */ 47 this.dispatchTable_ = dispatchTable; 48 49 /** 50 * @type {boolean} 51 */ 52 this.timedRange_ = timedRange; 53 54 /** 55 * @type {boolean} 56 */ 57 this.pairwiseTimedRange_ = pairwiseTimedRange; 58 if (pairwiseTimedRange) { 59 this.timedRange_ = true; 60 } 61 62 /** 63 * Current line. 64 * @type {number} 65 */ 66 this.lineNum_ = 0; 67 68 /** 69 * CSV lines parser. 70 * @type {CsvParser} 71 */ 72 this.csvParser_ = new CsvParser(); 73 74 /** 75 * Keeps track of whether we've seen a "current-time" tick yet. 76 * @type {boolean} 77 */ 78 this.hasSeenTimerMarker_ = false; 79 80 /** 81 * List of log lines seen since last "current-time" tick. 82 * @type {Array.<String>} 83 */ 84 this.logLinesSinceLastTimerMarker_ = []; 85}; 86 87 88/** 89 * Used for printing error messages. 90 * 91 * @param {string} str Error message. 92 */ 93LogReader.prototype.printError = function(str) { 94 // Do nothing. 95}; 96 97 98/** 99 * Processes a portion of V8 profiler event log. 100 * 101 * @param {string} chunk A portion of log. 102 */ 103LogReader.prototype.processLogChunk = function(chunk) { 104 this.processLog_(chunk.split('\n')); 105}; 106 107 108/** 109 * Processes a line of V8 profiler event log. 110 * 111 * @param {string} line A line of log. 112 */ 113LogReader.prototype.processLogLine = function(line) { 114 if (!this.timedRange_) { 115 this.processLogLine_(line); 116 return; 117 } 118 if (line.startsWith("current-time")) { 119 if (this.hasSeenTimerMarker_) { 120 this.processLog_(this.logLinesSinceLastTimerMarker_); 121 this.logLinesSinceLastTimerMarker_ = []; 122 // In pairwise mode, a "current-time" line ends the timed range. 123 if (this.pairwiseTimedRange_) { 124 this.hasSeenTimerMarker_ = false; 125 } 126 } else { 127 this.hasSeenTimerMarker_ = true; 128 } 129 } else { 130 if (this.hasSeenTimerMarker_) { 131 this.logLinesSinceLastTimerMarker_.push(line); 132 } else if (!line.startsWith("tick")) { 133 this.processLogLine_(line); 134 } 135 } 136}; 137 138 139/** 140 * Processes stack record. 141 * 142 * @param {number} pc Program counter. 143 * @param {number} func JS Function. 144 * @param {Array.<string>} stack String representation of a stack. 145 * @return {Array.<number>} Processed stack. 146 */ 147LogReader.prototype.processStack = function(pc, func, stack) { 148 var fullStack = func ? [pc, func] : [pc]; 149 var prevFrame = pc; 150 for (var i = 0, n = stack.length; i < n; ++i) { 151 var frame = stack[i]; 152 var firstChar = frame.charAt(0); 153 if (firstChar == '+' || firstChar == '-') { 154 // An offset from the previous frame. 155 prevFrame += parseInt(frame, 16); 156 fullStack.push(prevFrame); 157 // Filter out possible 'overflow' string. 158 } else if (firstChar != 'o') { 159 fullStack.push(parseInt(frame, 16)); 160 } else { 161 this.printError("dropping: " + frame); 162 } 163 } 164 return fullStack; 165}; 166 167 168/** 169 * Returns whether a particular dispatch must be skipped. 170 * 171 * @param {!Object} dispatch Dispatch record. 172 * @return {boolean} True if dispatch must be skipped. 173 */ 174LogReader.prototype.skipDispatch = function(dispatch) { 175 return false; 176}; 177 178// Parses dummy variable for readability; 179const parseString = 'parse-string'; 180const parseVarArgs = 'parse-var-args'; 181 182/** 183 * Does a dispatch of a log record. 184 * 185 * @param {Array.<string>} fields Log record. 186 * @private 187 */ 188LogReader.prototype.dispatchLogRow_ = function(fields) { 189 // Obtain the dispatch. 190 var command = fields[0]; 191 var dispatch = this.dispatchTable_[command]; 192 if (dispatch === undefined) return; 193 194 if (dispatch === null || this.skipDispatch(dispatch)) { 195 return; 196 } 197 198 // Parse fields. 199 var parsedFields = []; 200 for (var i = 0; i < dispatch.parsers.length; ++i) { 201 var parser = dispatch.parsers[i]; 202 if (parser === parseString) { 203 parsedFields.push(fields[1 + i]); 204 } else if (typeof parser == 'function') { 205 parsedFields.push(parser(fields[1 + i])); 206 } else if (parser === parseVarArgs) { 207 // var-args 208 parsedFields.push(fields.slice(1 + i)); 209 break; 210 } else { 211 throw new Error("Invalid log field parser: " + parser); 212 } 213 } 214 215 // Run the processor. 216 dispatch.processor.apply(this, parsedFields); 217}; 218 219 220/** 221 * Processes log lines. 222 * 223 * @param {Array.<string>} lines Log lines. 224 * @private 225 */ 226LogReader.prototype.processLog_ = function(lines) { 227 for (var i = 0, n = lines.length; i < n; ++i) { 228 this.processLogLine_(lines[i]); 229 } 230} 231 232/** 233 * Processes a single log line. 234 * 235 * @param {String} a log line 236 * @private 237 */ 238LogReader.prototype.processLogLine_ = function(line) { 239 if (line.length > 0) { 240 try { 241 var fields = this.csvParser_.parseLine(line); 242 this.dispatchLogRow_(fields); 243 } catch (e) { 244 this.printError('line ' + (this.lineNum_ + 1) + ': ' + (e.message || e)); 245 } 246 } 247 this.lineNum_++; 248}; 249