• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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