1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5'use strict';
6
7/**
8 * @fileoverview Log Reader is used to process log file produced by V8.
9 */
10base.exportTo('tracing.importer.v8', function() {
11  /**
12   * Creates a CSV lines parser.
13   */
14  function CsvParser() {
15  };
16
17
18  /**
19   * A regex for matching a CSV field.
20   * @private
21   */
22  CsvParser.CSV_FIELD_RE_ = /^"((?:[^"]|"")*)"|([^,]*)/;
23
24
25  /**
26   * A regex for matching a double quote.
27   * @private
28   */
29  CsvParser.DOUBLE_QUOTE_RE_ = /""/g;
30
31
32  /**
33   * Parses a line of CSV-encoded values. Returns an array of fields.
34   *
35   * @param {string} line Input line.
36   */
37  CsvParser.prototype.parseLine = function(line) {
38    var fieldRe = CsvParser.CSV_FIELD_RE_;
39    var doubleQuoteRe = CsvParser.DOUBLE_QUOTE_RE_;
40    var pos = 0;
41    var endPos = line.length;
42    var fields = [];
43    if (endPos > 0) {
44      do {
45        var fieldMatch = fieldRe.exec(line.substr(pos));
46        if (typeof fieldMatch[1] === 'string') {
47          var field = fieldMatch[1];
48          pos += field.length + 3;  // Skip comma and quotes.
49          fields.push(field.replace(doubleQuoteRe, '"'));
50        } else {
51          // The second field pattern will match anything, thus
52          // in the worst case the match will be an empty string.
53          var field = fieldMatch[2];
54          pos += field.length + 1;  // Skip comma.
55          fields.push(field);
56        }
57      } while (pos <= endPos);
58    }
59    return fields;
60  };
61
62  /**
63   * Base class for processing log files.
64   *
65   * @param {Array.<Object>} dispatchTable A table used for parsing and
66   * processing log records.
67   *
68   * @constructor
69   */
70  function LogReader(dispatchTable) {
71    /**
72     * @type {Array.<Object>}
73     */
74    this.dispatchTable_ = dispatchTable;
75
76    /**
77     * Current line.
78     * @type {number}
79     */
80    this.lineNum_ = 0;
81
82    /**
83     * CSV lines parser.
84     * @type {CsvParser}
85     */
86    this.csvParser_ = new CsvParser();
87  };
88
89
90  /**
91   * Used for printing error messages.
92   *
93   * @param {string} str Error message.
94   */
95  LogReader.prototype.printError = function(str) {
96    // Do nothing.
97  };
98
99
100  /**
101   * Processes a portion of V8 profiler event log.
102   *
103   * @param {string} chunk A portion of log.
104   */
105  LogReader.prototype.processLogChunk = function(chunk) {
106    this.processLog_(chunk.split('\n'));
107  };
108
109
110  /**
111   * Processes a line of V8 profiler event log.
112   *
113   * @param {string} line A line of log.
114   */
115  LogReader.prototype.processLogLine = function(line) {
116    this.processLog_([line]);
117  };
118
119
120  /**
121   * Processes stack record.
122   *
123   * @param {number} pc Program counter.
124   * @param {number} func JS Function.
125   * @param {Array.<string>} stack String representation of a stack.
126   * @return {Array.<number>} Processed stack.
127   */
128  LogReader.prototype.processStack = function(pc, func, stack) {
129    var fullStack = func ? [pc, func] : [pc];
130    var prevFrame = pc;
131    for (var i = 0, n = stack.length; i < n; ++i) {
132      var frame = stack[i];
133      var firstChar = frame.charAt(0);
134      if (firstChar == '+' || firstChar == '-') {
135        // An offset from the previous frame.
136        prevFrame += parseInt(frame, 16);
137        fullStack.push(prevFrame);
138      // Filter out possible 'overflow' string.
139      } else if (firstChar != 'o') {
140        fullStack.push(parseInt(frame, 16));
141      }
142    }
143    return fullStack;
144  };
145
146
147  /**
148   * Returns whether a particular dispatch must be skipped.
149   *
150   * @param {!Object} dispatch Dispatch record.
151   * @return {boolean} True if dispatch must be skipped.
152   */
153  LogReader.prototype.skipDispatch = function(dispatch) {
154    return false;
155  };
156
157
158  /**
159   * Does a dispatch of a log record.
160   *
161   * @param {Array.<string>} fields Log record.
162   * @private
163   */
164  LogReader.prototype.dispatchLogRow_ = function(fields) {
165    // Obtain the dispatch.
166    var command = fields[0];
167    if (!(command in this.dispatchTable_)) return;
168
169    var dispatch = this.dispatchTable_[command];
170
171    if (dispatch === null || this.skipDispatch(dispatch)) {
172      return;
173    }
174
175    // Parse fields.
176    var parsedFields = [];
177    for (var i = 0; i < dispatch.parsers.length; ++i) {
178      var parser = dispatch.parsers[i];
179      if (parser === null) {
180        parsedFields.push(fields[1 + i]);
181      } else if (typeof parser == 'function') {
182        parsedFields.push(parser(fields[1 + i]));
183      } else {
184        // var-args
185        parsedFields.push(fields.slice(1 + i));
186        break;
187      }
188    }
189
190    // Run the processor.
191    dispatch.processor.apply(this, parsedFields);
192  };
193
194
195  /**
196   * Processes log lines.
197   *
198   * @param {Array.<string>} lines Log lines.
199   * @private
200   */
201  LogReader.prototype.processLog_ = function(lines) {
202    for (var i = 0, n = lines.length; i < n; ++i, ++this.lineNum_) {
203      var line = lines[i];
204      if (!line) {
205        continue;
206      }
207      try {
208        var fields = this.csvParser_.parseLine(line);
209        this.dispatchLogRow_(fields);
210      } catch (e) {
211        this.printError('line ' + (this.lineNum_ + 1) + ': ' +
212                        (e.message || e));
213      }
214    }
215  };
216  return {
217    LogReader: LogReader
218  };
219});
220