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