logreader.mjs 7.66 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
// Copyright 2011 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

/**
 * @fileoverview Log Reader is used to process log file produced by V8.
 */
 import { CsvParser } from "./csvparser.mjs";

33 34

// Parses dummy variable for readability;
35
export function parseString(field) { return field };
36 37
export const parseVarArgs = 'parse-var-args';

38 39 40 41 42 43 44 45
/**
 * Base class for processing log files.
 *
 * @param {boolean} timedRange Ignore ticks outside timed range.
 * @param {boolean} pairwiseTimedRange Ignore ticks outside pairs of timer
 *     markers.
 * @constructor
 */
46
export class LogReader {
47
  constructor(timedRange=false, pairwiseTimedRange=false) {
48
    this.dispatchTable_ = new Map();
49 50
    this.timedRange_ = timedRange;
    this.pairwiseTimedRange_ = pairwiseTimedRange;
51
    if (pairwiseTimedRange) this.timedRange_ = true;
52 53
    this.lineNum_ = 0;
    this.csvParser_ = new CsvParser();
54
    // Variables for tracking of 'current-time' log entries:
55 56 57
    this.hasSeenTimerMarker_ = false;
    this.logLinesSinceLastTimerMarker_ = [];
  }
58

59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
/**
 * @param {Object} table A table used for parsing and processing
 *     log records.
 *     exampleDispatchTable = {
 *       "log-entry-XXX": {
 *          parser: [parseString, parseInt, ..., parseVarArgs],
 *          processor: this.processXXX.bind(this)
 *        },
 *        ...
 *      }
 */
  setDispatchTable(table) {
    if (Object.getPrototypeOf(table) !== null) {
      throw new Error("Dispatch expected table.__proto__=null for speedup");
    }
    for (let name in table) {
      const parser = table[name];
      if (parser === undefined) continue;
      if (!parser.isAsync) parser.isAsync = false;
      if (!Array.isArray(parser.parsers)) {
        throw new Error(`Invalid parsers: dispatchTable['${
            name}'].parsers should be an Array.`);
      }
      let type = typeof parser.processor;
      if (type !== 'function') {
       throw new Error(`Invalid processor: typeof dispatchTable['${
          name}'].processor is '${type}' instead of 'function'`);
      }
      if (!parser.processor.name.startsWith('bound ')) {
        parser.processor = parser.processor.bind(this);
      }
      this.dispatchTable_.set(name, parser);
    }
  }

94 95 96 97 98 99 100 101 102 103 104 105 106

  /**
   * A thin wrapper around shell's 'read' function showing a file name on error.
   */
  readFile(fileName) {
    try {
      return read(fileName);
    } catch (e) {
      printErr(`file="${fileName}": ${e.message || e}`);
      throw e;
    }
  }

107
  /**
108 109 110
   * Used for printing error messages.
   *
   * @param {string} str Error message.
111
   */
112 113 114
  printError(str) {
    // Do nothing.
  }
115 116

  /**
117 118 119
   * Processes a portion of V8 profiler event log.
   *
   * @param {string} chunk A portion of log.
120
   */
121
  async processLogChunk(chunk) {
122 123 124 125 126 127 128 129 130 131 132 133
    let end = chunk.length;
    let current = 0;
    // Kept for debugging in case of parsing errors.
    let lineNumber = 0;
    while (current < end) {
      const next = chunk.indexOf("\n", current);
      if (next === -1) break;
      lineNumber++;
      const line = chunk.substring(current, next);
      current = next + 1;
      await this.processLogLine(line);
    }
134 135 136
  }

  /**
137 138 139
   * Processes a line of V8 profiler event log.
   *
   * @param {string} line A line of log.
140
   */
141
  async processLogLine(line) {
142
    if (!this.timedRange_) {
143
      await this.processLogLine_(line);
144 145 146 147
      return;
    }
    if (line.startsWith("current-time")) {
      if (this.hasSeenTimerMarker_) {
148
        await this.processLog_(this.logLinesSinceLastTimerMarker_);
149 150 151 152 153 154 155 156 157 158 159 160
        this.logLinesSinceLastTimerMarker_ = [];
        // In pairwise mode, a "current-time" line ends the timed range.
        if (this.pairwiseTimedRange_) {
          this.hasSeenTimerMarker_ = false;
        }
      } else {
        this.hasSeenTimerMarker_ = true;
      }
    } else {
      if (this.hasSeenTimerMarker_) {
        this.logLinesSinceLastTimerMarker_.push(line);
      } else if (!line.startsWith("tick")) {
161
        await this.processLogLine_(line);
162 163 164
      }
    }
  }
165 166

  /**
167 168 169 170
   * Processes stack record.
   *
   * @param {number} pc Program counter.
   * @param {number} func JS Function.
171 172
   * @param {string[]} stack String representation of a stack.
   * @return {number[]} Processed stack.
173
   */
174 175 176
  processStack(pc, func, stack) {
    const fullStack = func ? [pc, func] : [pc];
    let prevFrame = pc;
177 178
    const length = stack.length;
    for (let i = 0, n = length; i < n; ++i) {
179
      const frame = stack[i];
180 181
      const firstChar = frame[0];
      if (firstChar === '+' || firstChar === '-') {
182 183 184 185
        // An offset from the previous frame.
        prevFrame += parseInt(frame, 16);
        fullStack.push(prevFrame);
      // Filter out possible 'overflow' string.
186
      } else if (firstChar !== 'o') {
187 188
        fullStack.push(parseInt(frame, 16));
      } else {
189
        console.error(`Dropping unknown tick frame: ${frame}`);
190 191 192 193
      }
    }
    return fullStack;
  }
194 195

  /**
196 197
   * Does a dispatch of a log record.
   *
198
   * @param {string[]} fields Log record.
199
   * @private
200
   */
201
  async dispatchLogRow_(fields) {
202 203
    // Obtain the dispatch.
    const command = fields[0];
204
    const dispatch = this.dispatchTable_.get(command);
205
    if (dispatch === undefined) return;
206 207
    const parsers = dispatch.parsers;
    const length = parsers.length;
208
    // Parse fields.
209
    const parsedFields = new Array(length);
210 211
    for (let i = 0; i < length; ++i) {
      const parser = parsers[i];
212 213
      if (parser === parseVarArgs) {
        parsedFields[i] = fields.slice(1 + i);
214 215
        break;
      } else {
216
        parsedFields[i] = parser(fields[1 + i]);
217
      }
218
    }
219
    // Run the processor.
220
    await dispatch.processor(...parsedFields);
221 222
  }

223 224 225
  /**
   * Processes log lines.
   *
226
   * @param {string[]} lines Log lines.
227 228
   * @private
   */
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 237 238 239 240
  /**
   * Processes a single log line.
   *
   * @param {String} a log line
   * @private
   */
241
  async processLogLine_(line) {
242 243 244
    if (line.length > 0) {
      try {
        const fields = this.csvParser_.parseLine(line);
245
        await this.dispatchLogRow_(fields);
246 247 248
      } catch (e) {
        this.printError(`line ${this.lineNum_ + 1}: ${e.message || e}\n${e.stack}`);
      }
249
    }
250
    this.lineNum_++;
251
  }
252
}