logreader.mjs 7.7 KB

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