• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2012 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
28import { LogReader, parseString, parseVarArgs } from "./logreader.mjs";
29import { BaseArgumentsProcessor, parseBool } from "./arguments.mjs";
30import { Profile, JsonProfile } from "./profile.mjs";
31import { ViewBuilder } from "./profile_view.mjs";
32import { WebInspector} from "./sourcemap.mjs";
33
34
35class V8Profile extends Profile {
36  static IC_RE =
37      /^(LoadGlobalIC: )|(Handler: )|(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Load|Store)IC_)/;
38  static BYTECODES_RE = /^(BytecodeHandler: )/;
39  static BASELINE_HANDLERS_RE = /^(Builtin: .*Baseline.*)/;
40  static BUILTINS_RE = /^(Builtin: )/;
41  static STUBS_RE = /^(Stub: )/;
42
43  constructor(separateIc, separateBytecodes, separateBuiltins, separateStubs,
44        separateBaselineHandlers) {
45    super();
46    const regexps = [];
47    if (!separateIc) regexps.push(V8Profile.IC_RE);
48    if (!separateBytecodes) regexps.push(V8Profile.BYTECODES_RE);
49    if (!separateBuiltins) regexps.push(V8Profile.BUILTINS_RE);
50    if (!separateStubs) regexps.push(V8Profile.STUBS_RE);
51    if (regexps.length > 0) {
52      this.skipThisFunction = function(name) {
53        for (let i = 0; i < regexps.length; i++) {
54          if (regexps[i].test(name)) return true;
55        }
56        return false;
57      };
58    }
59  }
60}
61
62class CppEntriesProvider {
63  constructor() {
64    this._isEnabled = true;
65  }
66
67  inRange(funcInfo, start, end) {
68    return funcInfo.start >= start && funcInfo.end <= end;
69  }
70
71  async parseVmSymbols(libName, libStart, libEnd, libASLRSlide, processorFunc) {
72    if (!this._isEnabled) return;
73    await this.loadSymbols(libName);
74
75    let lastUnknownSize;
76    let lastAdded;
77
78    let addEntry = (funcInfo) => {
79      // Several functions can be mapped onto the same address. To avoid
80      // creating zero-sized entries, skip such duplicates.
81      // Also double-check that function belongs to the library address space.
82
83      if (lastUnknownSize &&
84        lastUnknownSize.start < funcInfo.start) {
85        // Try to update lastUnknownSize based on new entries start position.
86        lastUnknownSize.end = funcInfo.start;
87        if ((!lastAdded ||
88            !this.inRange(lastUnknownSize, lastAdded.start, lastAdded.end)) &&
89            this.inRange(lastUnknownSize, libStart, libEnd)) {
90          processorFunc(
91              lastUnknownSize.name, lastUnknownSize.start, lastUnknownSize.end);
92          lastAdded = lastUnknownSize;
93        }
94      }
95      lastUnknownSize = undefined;
96
97      if (funcInfo.end) {
98        // Skip duplicates that have the same start address as the last added.
99        if ((!lastAdded || lastAdded.start != funcInfo.start) &&
100          this.inRange(funcInfo, libStart, libEnd)) {
101          processorFunc(funcInfo.name, funcInfo.start, funcInfo.end);
102          lastAdded = funcInfo;
103        }
104      } else {
105        // If a funcInfo doesn't have an end, try to match it up with the next
106        // entry.
107        lastUnknownSize = funcInfo;
108      }
109    }
110
111    while (true) {
112      const funcInfo = this.parseNextLine();
113      if (funcInfo === null) continue;
114      if (funcInfo === false) break;
115      if (funcInfo.start < libStart - libASLRSlide &&
116        funcInfo.start < libEnd - libStart) {
117        funcInfo.start += libStart;
118      } else {
119        funcInfo.start += libASLRSlide;
120      }
121      if (funcInfo.size) {
122        funcInfo.end = funcInfo.start + funcInfo.size;
123      }
124      addEntry(funcInfo);
125    }
126    addEntry({ name: '', start: libEnd });
127  }
128
129  async loadSymbols(libName) {}
130
131  async loadSymbolsRemote(platform, libName) {
132    this.parsePos = 0;
133    const url = new URL("http://localhost:8000/v8/loadVMSymbols");
134    url.searchParams.set('libName', libName);
135    url.searchParams.set('platform', platform);
136    this._setRemoteQueryParams(url.searchParams);
137    let response;
138    let json;
139    try {
140      response = await fetch(url);
141      if (response.status == 404) {
142        throw new Error(
143          `Local symbol server returned 404: ${await response.text()}`);
144      }
145      json = await response.json();
146      if (json.error) console.warn(json.error);
147    } catch (e) {
148      if (!response || response.status == 404) {
149        // Assume that the local symbol server is not reachable.
150        console.warn("Disabling remote symbol loading:", e);
151        this._isEnabled = false;
152        return;
153      }
154    }
155    this._handleRemoteSymbolsResult(json);
156  }
157
158  _setRemoteQueryParams(searchParams) {
159    // Subclass responsibility.
160  }
161
162  _handleRemoteSymbolsResult(json) {
163    this.symbols = json.symbols;
164  }
165
166  parseNextLine() { return false }
167}
168
169export class LinuxCppEntriesProvider extends CppEntriesProvider {
170  constructor(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary) {
171    super();
172    this.symbols = [];
173    // File offset of a symbol minus the virtual address of a symbol found in
174    // the symbol table.
175    this.fileOffsetMinusVma = 0;
176    this.parsePos = 0;
177    this.nmExec = nmExec;
178    this.objdumpExec = objdumpExec;
179    this.targetRootFS = targetRootFS;
180    this.apkEmbeddedLibrary = apkEmbeddedLibrary;
181    this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/;
182  }
183
184  _setRemoteQueryParams(searchParams) {
185    super._setRemoteQueryParams(searchParams);
186    searchParams.set('targetRootFS', this.targetRootFS ?? "");
187    searchParams.set('apkEmbeddedLibrary', this.apkEmbeddedLibrary);
188  }
189
190  _handleRemoteSymbolsResult(json) {
191    super._handleRemoteSymbolsResult(json);
192    this.fileOffsetMinusVma = json.fileOffsetMinusVma;
193  }
194
195  async loadSymbols(libName) {
196    this.parsePos = 0;
197    if (this.apkEmbeddedLibrary && libName.endsWith('.apk')) {
198      libName = this.apkEmbeddedLibrary;
199    }
200    if (this.targetRootFS) {
201      libName = libName.substring(libName.lastIndexOf('/') + 1);
202      libName = this.targetRootFS + libName;
203    }
204    try {
205      this.symbols = [
206        os.system(this.nmExec, ['-C', '-n', '-S', libName], -1, -1),
207        os.system(this.nmExec, ['-C', '-n', '-S', '-D', libName], -1, -1)
208      ];
209
210      const objdumpOutput = os.system(this.objdumpExec, ['-h', libName], -1, -1);
211      for (const line of objdumpOutput.split('\n')) {
212        const [, sectionName, , vma, , fileOffset] = line.trim().split(/\s+/);
213        if (sectionName === ".text") {
214          this.fileOffsetMinusVma = parseInt(fileOffset, 16) - parseInt(vma, 16);
215        }
216      }
217    } catch (e) {
218      // If the library cannot be found on this system let's not panic.
219      this.symbols = ['', ''];
220    }
221  }
222
223  parseNextLine() {
224    if (this.symbols.length == 0) return false;
225    const lineEndPos = this.symbols[0].indexOf('\n', this.parsePos);
226    if (lineEndPos == -1) {
227      this.symbols.shift();
228      this.parsePos = 0;
229      return this.parseNextLine();
230    }
231
232    const line = this.symbols[0].substring(this.parsePos, lineEndPos);
233    this.parsePos = lineEndPos + 1;
234    const fields = line.match(this.FUNC_RE);
235    let funcInfo = null;
236    if (fields) {
237      funcInfo = { name: fields[3], start: parseInt(fields[1], 16) + this.fileOffsetMinusVma };
238      if (fields[2]) {
239        funcInfo.size = parseInt(fields[2], 16);
240      }
241    }
242    return funcInfo;
243  }
244}
245
246export class RemoteLinuxCppEntriesProvider extends LinuxCppEntriesProvider {
247  async loadSymbols(libName) {
248    return this.loadSymbolsRemote('linux', libName);
249  }
250}
251
252export class MacOSCppEntriesProvider extends LinuxCppEntriesProvider {
253  constructor(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary) {
254    super(nmExec, objdumpExec, targetRootFS, apkEmbeddedLibrary);
255    // Note an empty group. It is required, as LinuxCppEntriesProvider expects 3 groups.
256    this.FUNC_RE = /^([0-9a-fA-F]{8,16})() (.*)$/;
257  }
258
259  async loadSymbols(libName) {
260    this.parsePos = 0;
261    libName = this.targetRootFS + libName;
262
263    // It seems that in OS X `nm` thinks that `-f` is a format option, not a
264    // "flat" display option flag.
265    try {
266      this.symbols = [
267        os.system(this.nmExec, ['--demangle', '-n', libName], -1, -1),
268        ''];
269    } catch (e) {
270      // If the library cannot be found on this system let's not panic.
271      this.symbols = '';
272    }
273  }
274}
275
276export class RemoteMacOSCppEntriesProvider extends LinuxCppEntriesProvider {
277  async loadSymbols(libName) {
278    return this.loadSymbolsRemote('macos', libName);
279  }
280}
281
282
283export class WindowsCppEntriesProvider extends CppEntriesProvider {
284  constructor(_ignored_nmExec, _ignored_objdumpExec, targetRootFS,
285    _ignored_apkEmbeddedLibrary) {
286    super();
287    this.targetRootFS = targetRootFS;
288    this.symbols = '';
289    this.parsePos = 0;
290  }
291
292  static FILENAME_RE = /^(.*)\.([^.]+)$/;
293  static FUNC_RE =
294    /^\s+0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/;
295  static IMAGE_BASE_RE =
296    /^\s+0000:00000000\s+___ImageBase\s+([0-9a-fA-F]{8}).*$/;
297  // This is almost a constant on Windows.
298  static EXE_IMAGE_BASE = 0x00400000;
299
300  loadSymbols(libName) {
301    libName = this.targetRootFS + libName;
302    const fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE);
303    if (!fileNameFields) return;
304    const mapFileName = `${fileNameFields[1]}.map`;
305    this.moduleType_ = fileNameFields[2].toLowerCase();
306    try {
307      this.symbols = read(mapFileName);
308    } catch (e) {
309      // If .map file cannot be found let's not panic.
310      this.symbols = '';
311    }
312  }
313
314  parseNextLine() {
315    const lineEndPos = this.symbols.indexOf('\r\n', this.parsePos);
316    if (lineEndPos == -1) {
317      return false;
318    }
319
320    const line = this.symbols.substring(this.parsePos, lineEndPos);
321    this.parsePos = lineEndPos + 2;
322
323    // Image base entry is above all other symbols, so we can just
324    // terminate parsing.
325    const imageBaseFields = line.match(WindowsCppEntriesProvider.IMAGE_BASE_RE);
326    if (imageBaseFields) {
327      const imageBase = parseInt(imageBaseFields[1], 16);
328      if ((this.moduleType_ == 'exe') !=
329        (imageBase == WindowsCppEntriesProvider.EXE_IMAGE_BASE)) {
330        return false;
331      }
332    }
333
334    const fields = line.match(WindowsCppEntriesProvider.FUNC_RE);
335    return fields ?
336      { name: this.unmangleName(fields[1]), start: parseInt(fields[2], 16) } :
337      null;
338  }
339
340  /**
341   * Performs very simple unmangling of C++ names.
342   *
343   * Does not handle arguments and template arguments. The mangled names have
344   * the form:
345   *
346   *   ?LookupInDescriptor@JSObject@internal@v8@@...arguments info...
347   */
348  unmangleName(name) {
349    // Empty or non-mangled name.
350    if (name.length < 1 || name.charAt(0) != '?') return name;
351    const nameEndPos = name.indexOf('@@');
352    const components = name.substring(1, nameEndPos).split('@');
353    components.reverse();
354    return components.join('::');
355  }
356}
357
358
359export class ArgumentsProcessor extends BaseArgumentsProcessor {
360  getArgsDispatch() {
361    let dispatch = {
362      __proto__:null,
363      '-j': ['stateFilter', TickProcessor.VmStates.JS,
364        'Show only ticks from JS VM state'],
365      '-g': ['stateFilter', TickProcessor.VmStates.GC,
366        'Show only ticks from GC VM state'],
367      '-p': ['stateFilter', TickProcessor.VmStates.PARSER,
368        'Show only ticks from PARSER VM state'],
369      '-b': ['stateFilter', TickProcessor.VmStates.BYTECODE_COMPILER,
370        'Show only ticks from BYTECODE_COMPILER VM state'],
371      '-c': ['stateFilter', TickProcessor.VmStates.COMPILER,
372        'Show only ticks from COMPILER VM state'],
373      '-o': ['stateFilter', TickProcessor.VmStates.OTHER,
374        'Show only ticks from OTHER VM state'],
375      '-e': ['stateFilter', TickProcessor.VmStates.EXTERNAL,
376        'Show only ticks from EXTERNAL VM state'],
377      '--filter-runtime-timer': ['runtimeTimerFilter', null,
378        'Show only ticks matching the given runtime timer scope'],
379      '--call-graph-size': ['callGraphSize', TickProcessor.CALL_GRAPH_SIZE,
380        'Set the call graph size'],
381      '--ignore-unknown': ['ignoreUnknown', true,
382        'Exclude ticks of unknown code entries from processing'],
383      '--separate-ic': ['separateIc', parseBool,
384        'Separate IC entries'],
385      '--separate-bytecodes': ['separateBytecodes', parseBool,
386        'Separate Bytecode entries'],
387      '--separate-builtins': ['separateBuiltins', parseBool,
388        'Separate Builtin entries'],
389      '--separate-stubs': ['separateStubs', parseBool,
390        'Separate Stub entries'],
391      '--separate-baseline-handlers': ['separateBaselineHandlers', parseBool,
392        'Separate Baseline Handler entries'],
393      '--linux': ['platform', 'linux',
394        'Specify that we are running on *nix platform'],
395      '--windows': ['platform', 'windows',
396        'Specify that we are running on Windows platform'],
397      '--mac': ['platform', 'macos',
398        'Specify that we are running on Mac OS X platform'],
399      '--nm': ['nm', 'nm',
400        'Specify the \'nm\' executable to use (e.g. --nm=/my_dir/nm)'],
401      '--objdump': ['objdump', 'objdump',
402        'Specify the \'objdump\' executable to use (e.g. --objdump=/my_dir/objdump)'],
403      '--target': ['targetRootFS', '',
404        'Specify the target root directory for cross environment'],
405      '--apk-embedded-library': ['apkEmbeddedLibrary', '',
406        'Specify the path of the embedded library for Android traces'],
407      '--range': ['range', 'auto,auto',
408        'Specify the range limit as [start],[end]'],
409      '--distortion': ['distortion', 0,
410        'Specify the logging overhead in picoseconds'],
411      '--source-map': ['sourceMap', null,
412        'Specify the source map that should be used for output'],
413      '--timed-range': ['timedRange', true,
414        'Ignore ticks before first and after last Date.now() call'],
415      '--pairwise-timed-range': ['pairwiseTimedRange', true,
416        'Ignore ticks outside pairs of Date.now() calls'],
417      '--only-summary': ['onlySummary', true,
418        'Print only tick summary, exclude other information'],
419      '--serialize-vm-symbols': ['serializeVMSymbols', true,
420        'Print all C++ symbols and library addresses as JSON data'],
421      '--preprocess': ['preprocessJson', true,
422        'Preprocess for consumption with web interface']
423    };
424    dispatch['--js'] = dispatch['-j'];
425    dispatch['--gc'] = dispatch['-g'];
426    dispatch['--compiler'] = dispatch['-c'];
427    dispatch['--other'] = dispatch['-o'];
428    dispatch['--external'] = dispatch['-e'];
429    dispatch['--ptr'] = dispatch['--pairwise-timed-range'];
430    return dispatch;
431  }
432
433  getDefaultResults() {
434    return {
435      logFileName: 'v8.log',
436      platform: 'linux',
437      stateFilter: null,
438      callGraphSize: 5,
439      ignoreUnknown: false,
440      separateIc: true,
441      separateBytecodes: false,
442      separateBuiltins: true,
443      separateStubs: true,
444      separateBaselineHandlers: false,
445      preprocessJson: null,
446      sourceMap: null,
447      targetRootFS: '',
448      nm: 'nm',
449      objdump: 'objdump',
450      range: 'auto,auto',
451      distortion: 0,
452      timedRange: false,
453      pairwiseTimedRange: false,
454      onlySummary: false,
455      runtimeTimerFilter: null,
456      serializeVMSymbols: false,
457    };
458  }
459}
460
461
462export class TickProcessor extends LogReader {
463  static EntriesProvider = {
464    'linux': LinuxCppEntriesProvider,
465    'windows': WindowsCppEntriesProvider,
466    'macos': MacOSCppEntriesProvider
467  };
468
469  static fromParams(params, entriesProvider) {
470    if (entriesProvider == undefined) {
471      entriesProvider = new this.EntriesProvider[params.platform](
472          params.nm, params.objdump, params.targetRootFS,
473          params.apkEmbeddedLibrary);
474    }
475    return new TickProcessor(
476      entriesProvider,
477      params.separateIc,
478      params.separateBytecodes,
479      params.separateBuiltins,
480      params.separateStubs,
481      params.separateBaselineHandlers,
482      params.callGraphSize,
483      params.ignoreUnknown,
484      params.stateFilter,
485      params.distortion,
486      params.range,
487      params.sourceMap,
488      params.timedRange,
489      params.pairwiseTimedRange,
490      params.onlySummary,
491      params.runtimeTimerFilter,
492      params.preprocessJson);
493  }
494
495  constructor(
496    cppEntriesProvider,
497    separateIc,
498    separateBytecodes,
499    separateBuiltins,
500    separateStubs,
501    separateBaselineHandlers,
502    callGraphSize,
503    ignoreUnknown,
504    stateFilter,
505    distortion,
506    range,
507    sourceMap,
508    timedRange,
509    pairwiseTimedRange,
510    onlySummary,
511    runtimeTimerFilter,
512    preprocessJson) {
513    super(timedRange, pairwiseTimedRange);
514    this.setDispatchTable({
515      __proto__: null,
516      'shared-library': {
517        parsers: [parseString, parseInt, parseInt, parseInt],
518        processor: this.processSharedLibrary
519      },
520      'code-creation': {
521        parsers: [parseString, parseInt, parseInt, parseInt, parseInt,
522          parseString, parseVarArgs],
523        processor: this.processCodeCreation
524      },
525      'code-deopt': {
526        parsers: [parseInt, parseInt, parseInt, parseInt, parseInt,
527          parseString, parseString, parseString],
528        processor: this.processCodeDeopt
529      },
530      'code-move': {
531        parsers: [parseInt, parseInt,],
532        processor: this.processCodeMove
533      },
534      'code-delete': {
535        parsers: [parseInt],
536        processor: this.processCodeDelete
537      },
538      'code-source-info': {
539        parsers: [parseInt, parseInt, parseInt, parseInt, parseString,
540          parseString, parseString],
541        processor: this.processCodeSourceInfo
542      },
543      'script-source': {
544        parsers: [parseInt, parseString, parseString],
545        processor: this.processScriptSource
546      },
547      'sfi-move': {
548        parsers: [parseInt, parseInt],
549        processor: this.processFunctionMove
550      },
551      'active-runtime-timer': {
552        parsers: [parseString],
553        processor: this.processRuntimeTimerEvent
554      },
555      'tick': {
556        parsers: [parseInt, parseInt, parseInt,
557          parseInt, parseInt, parseVarArgs],
558        processor: this.processTick
559      },
560      'heap-sample-begin': {
561        parsers: [parseString, parseString, parseInt],
562        processor: this.processHeapSampleBegin
563      },
564      'heap-sample-end': {
565        parsers: [parseString, parseString],
566        processor: this.processHeapSampleEnd
567      },
568      'timer-event-start': {
569        parsers: [parseString, parseString, parseString],
570        processor: this.advanceDistortion
571      },
572      'timer-event-end': {
573        parsers: [parseString, parseString, parseString],
574        processor: this.advanceDistortion
575      },
576      // Ignored events.
577      'profiler': undefined,
578      'function-creation': undefined,
579      'function-move': undefined,
580      'function-delete': undefined,
581      'heap-sample-item': undefined,
582      'current-time': undefined,  // Handled specially, not parsed.
583      // Obsolete row types.
584      'code-allocate': undefined,
585      'begin-code-region': undefined,
586      'end-code-region': undefined
587    });
588
589    this.preprocessJson = preprocessJson;
590    this.cppEntriesProvider_ = cppEntriesProvider;
591    this.callGraphSize_ = callGraphSize;
592    this.ignoreUnknown_ = ignoreUnknown;
593    this.stateFilter_ = stateFilter;
594    this.runtimeTimerFilter_ = runtimeTimerFilter;
595    this.sourceMap = this.loadSourceMap(sourceMap);
596    const ticks = this.ticks_ =
597      { total: 0, unaccounted: 0, excluded: 0, gc: 0 };
598
599    distortion = parseInt(distortion);
600    // Convert picoseconds to nanoseconds.
601    this.distortion_per_entry = isNaN(distortion) ? 0 : (distortion / 1000);
602    this.distortion = 0;
603    const rangelimits = range ? range.split(",") : [];
604    const range_start = parseInt(rangelimits[0]);
605    const range_end = parseInt(rangelimits[1]);
606    // Convert milliseconds to nanoseconds.
607    this.range_start = isNaN(range_start) ? -Infinity : (range_start * 1000);
608    this.range_end = isNaN(range_end) ? Infinity : (range_end * 1000)
609
610    V8Profile.prototype.handleUnknownCode = function (
611      operation, addr, opt_stackPos) {
612      const op = Profile.Operation;
613      switch (operation) {
614        case op.MOVE:
615          printErr(`Code move event for unknown code: 0x${addr.toString(16)}`);
616          break;
617        case op.DELETE:
618          printErr(`Code delete event for unknown code: 0x${addr.toString(16)}`);
619          break;
620        case op.TICK:
621          // Only unknown PCs (the first frame) are reported as unaccounted,
622          // otherwise tick balance will be corrupted (this behavior is compatible
623          // with the original tickprocessor.py script.)
624          if (opt_stackPos == 0) {
625            ticks.unaccounted++;
626          }
627          break;
628      }
629    };
630
631    if (preprocessJson) {
632      this.profile_ = new JsonProfile();
633    } else {
634      this.profile_ = new V8Profile(separateIc, separateBytecodes,
635        separateBuiltins, separateStubs, separateBaselineHandlers);
636    }
637    this.codeTypes_ = {};
638    // Count each tick as a time unit.
639    this.viewBuilder_ = new ViewBuilder(1);
640    this.lastLogFileName_ = null;
641
642    this.generation_ = 1;
643    this.currentProducerProfile_ = null;
644    this.onlySummary_ = onlySummary;
645  }
646
647  loadSourceMap(sourceMap) {
648    if (!sourceMap) return null;
649    // Overwrite the load function to load scripts synchronously.
650    WebInspector.SourceMap.load = (sourceMapURL) => {
651      const content = d8.file.read(sourceMapURL);
652      const sourceMapObject = JSON.parse(content);
653      return new SourceMap(sourceMapURL, sourceMapObject);
654    };
655    return WebInspector.SourceMap.load(sourceMap);
656  }
657
658  static VmStates = {
659    JS: 0,
660    GC: 1,
661    PARSER: 2,
662    BYTECODE_COMPILER: 3,
663    // TODO(cbruni): add BASELINE_COMPILER
664    COMPILER: 4,
665    OTHER: 5,
666    EXTERNAL: 6,
667    IDLE: 7,
668  };
669
670  static CodeTypes = {
671    CPP: 0,
672    SHARED_LIB: 1
673  };
674  // Otherwise, this is JS-related code. We are not adding it to
675  // codeTypes_ map because there can be zillions of them.
676
677  static CALL_PROFILE_CUTOFF_PCT = 1.0;
678  static CALL_GRAPH_SIZE = 5;
679
680  /**
681   * @override
682   */
683  printError(str) {
684    printErr(str);
685  }
686
687  setCodeType(name, type) {
688    this.codeTypes_[name] = TickProcessor.CodeTypes[type];
689  }
690
691  isSharedLibrary(name) {
692    return this.codeTypes_[name] == TickProcessor.CodeTypes.SHARED_LIB;
693  }
694
695  isCppCode(name) {
696    return this.codeTypes_[name] == TickProcessor.CodeTypes.CPP;
697  }
698
699  isJsCode(name) {
700    return name !== "UNKNOWN" && !(name in this.codeTypes_);
701  }
702
703  async processLogFile(fileName) {
704    this.lastLogFileName_ = fileName;
705    let line;
706    while (line = readline()) {
707      await this.processLogLine(line);
708    }
709  }
710
711  async processLogFileInTest(fileName) {
712    // Hack file name to avoid dealing with platform specifics.
713    this.lastLogFileName_ = 'v8.log';
714    const contents = d8.file.read(fileName);
715    await this.processLogChunk(contents);
716  }
717
718  processSharedLibrary(name, startAddr, endAddr, aslrSlide) {
719    const entry = this.profile_.addLibrary(name, startAddr, endAddr, aslrSlide);
720    this.setCodeType(entry.getName(), 'SHARED_LIB');
721    this.cppEntriesProvider_.parseVmSymbols(
722      name, startAddr, endAddr, aslrSlide, (fName, fStart, fEnd) => {
723        this.profile_.addStaticCode(fName, fStart, fEnd);
724        this.setCodeType(fName, 'CPP');
725      });
726  }
727
728  processCodeCreation(type, kind, timestamp, start, size, name, maybe_func) {
729    if (type != 'RegExp' && maybe_func.length) {
730      const funcAddr = parseInt(maybe_func[0]);
731      const state = Profile.parseState(maybe_func[1]);
732      this.profile_.addFuncCode(type, name, timestamp, start, size, funcAddr, state);
733    } else {
734      this.profile_.addCode(type, name, timestamp, start, size);
735    }
736  }
737
738  processCodeDeopt(
739      timestamp, size, code, inliningId, scriptOffset, bailoutType,
740      sourcePositionText, deoptReasonText) {
741    this.profile_.deoptCode(timestamp, code, inliningId, scriptOffset,
742      bailoutType, sourcePositionText, deoptReasonText);
743  }
744
745  processCodeMove(from, to) {
746    this.profile_.moveCode(from, to);
747  }
748
749  processCodeDelete(start) {
750    this.profile_.deleteCode(start);
751  }
752
753  processCodeSourceInfo(
754      start, script, startPos, endPos, sourcePositions, inliningPositions,
755      inlinedFunctions) {
756    this.profile_.addSourcePositions(start, script, startPos,
757      endPos, sourcePositions, inliningPositions, inlinedFunctions);
758  }
759
760  processScriptSource(script, url, source) {
761    this.profile_.addScriptSource(script, url, source);
762  }
763
764  processFunctionMove(from, to) {
765    this.profile_.moveFunc(from, to);
766  }
767
768  includeTick(vmState) {
769    if (this.stateFilter_ !== null) {
770      return this.stateFilter_ == vmState;
771    } else if (this.runtimeTimerFilter_ !== null) {
772      return this.currentRuntimeTimer == this.runtimeTimerFilter_;
773    }
774    return true;
775  }
776
777  processRuntimeTimerEvent(name) {
778    this.currentRuntimeTimer = name;
779  }
780
781  processTick(pc,
782    ns_since_start,
783    is_external_callback,
784    tos_or_external_callback,
785    vmState,
786    stack) {
787    this.distortion += this.distortion_per_entry;
788    ns_since_start -= this.distortion;
789    if (ns_since_start < this.range_start || ns_since_start > this.range_end) {
790      return;
791    }
792    this.ticks_.total++;
793    if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++;
794    if (!this.includeTick(vmState)) {
795      this.ticks_.excluded++;
796      return;
797    }
798    if (is_external_callback) {
799      // Don't use PC when in external callback code, as it can point
800      // inside callback's code, and we will erroneously report
801      // that a callback calls itself. Instead we use tos_or_external_callback,
802      // as simply resetting PC will produce unaccounted ticks.
803      pc = tos_or_external_callback;
804      tos_or_external_callback = 0;
805    } else if (tos_or_external_callback) {
806      // Find out, if top of stack was pointing inside a JS function
807      // meaning that we have encountered a frameless invocation.
808      const funcEntry = this.profile_.findEntry(tos_or_external_callback);
809      if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction()) {
810        tos_or_external_callback = 0;
811      }
812    }
813
814    this.profile_.recordTick(
815      ns_since_start, vmState,
816      this.processStack(pc, tos_or_external_callback, stack));
817  }
818
819  advanceDistortion() {
820    this.distortion += this.distortion_per_entry;
821  }
822
823  processHeapSampleBegin(space, state, ticks) {
824    if (space != 'Heap') return;
825    this.currentProducerProfile_ = new CallTree();
826  }
827
828  processHeapSampleEnd(space, state) {
829    if (space != 'Heap' || !this.currentProducerProfile_) return;
830
831    print(`Generation ${this.generation_}:`);
832    const tree = this.currentProducerProfile_;
833    tree.computeTotalWeights();
834    const producersView = this.viewBuilder_.buildView(tree);
835    // Sort by total time, desc, then by name, desc.
836    producersView.sort((rec1, rec2) =>
837      rec2.totalTime - rec1.totalTime ||
838      (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1));
839    this.printHeavyProfile(producersView.head.children);
840
841    this.currentProducerProfile_ = null;
842    this.generation_++;
843  }
844
845  printVMSymbols() {
846    console.log(
847      JSON.stringify(this.profile_.serializeVMSymbols()));
848  }
849
850  printStatistics() {
851    if (this.preprocessJson) {
852      this.profile_.writeJson();
853      return;
854    }
855
856    print(`Statistical profiling result from ${this.lastLogFileName_}` +
857      `, (${this.ticks_.total} ticks, ${this.ticks_.unaccounted} unaccounted, ` +
858      `${this.ticks_.excluded} excluded).`);
859
860
861    if (this.ticks_.total == 0) return;
862
863    const flatProfile = this.profile_.getFlatProfile();
864    const flatView = this.viewBuilder_.buildView(flatProfile);
865    // Sort by self time, desc, then by name, desc.
866    flatView.sort((rec1, rec2) =>
867      rec2.selfTime - rec1.selfTime ||
868      (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1));
869    let totalTicks = this.ticks_.total;
870    if (this.ignoreUnknown_) {
871      totalTicks -= this.ticks_.unaccounted;
872    }
873    const printAllTicks = !this.onlySummary_;
874
875    // Count library ticks
876    const flatViewNodes = flatView.head.children;
877
878    let libraryTicks = 0;
879    if (printAllTicks) this.printHeader('Shared libraries');
880    this.printEntries(flatViewNodes, totalTicks, null,
881      name => this.isSharedLibrary(name),
882      (rec) => { libraryTicks += rec.selfTime; }, printAllTicks);
883    const nonLibraryTicks = totalTicks - libraryTicks;
884
885    let jsTicks = 0;
886    if (printAllTicks) this.printHeader('JavaScript');
887    this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks,
888      name => this.isJsCode(name),
889      (rec) => { jsTicks += rec.selfTime; }, printAllTicks);
890
891    let cppTicks = 0;
892    if (printAllTicks) this.printHeader('C++');
893    this.printEntries(flatViewNodes, totalTicks, nonLibraryTicks,
894      name => this.isCppCode(name),
895      (rec) => { cppTicks += rec.selfTime; }, printAllTicks);
896
897    this.printHeader('Summary');
898    this.printLine('JavaScript', jsTicks, totalTicks, nonLibraryTicks);
899    this.printLine('C++', cppTicks, totalTicks, nonLibraryTicks);
900    this.printLine('GC', this.ticks_.gc, totalTicks, nonLibraryTicks);
901    this.printLine('Shared libraries', libraryTicks, totalTicks, null);
902    if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) {
903      this.printLine('Unaccounted', this.ticks_.unaccounted,
904        this.ticks_.total, null);
905    }
906
907    if (printAllTicks) {
908      print('\n [C++ entry points]:');
909      print('   ticks    cpp   total   name');
910      const c_entry_functions = this.profile_.getCEntryProfile();
911      const total_c_entry = c_entry_functions[0].ticks;
912      for (let i = 1; i < c_entry_functions.length; i++) {
913        const c = c_entry_functions[i];
914        this.printLine(c.name, c.ticks, total_c_entry, totalTicks);
915      }
916
917      this.printHeavyProfHeader();
918      const heavyProfile = this.profile_.getBottomUpProfile();
919      const heavyView = this.viewBuilder_.buildView(heavyProfile);
920      // To show the same percentages as in the flat profile.
921      heavyView.head.totalTime = totalTicks;
922      // Sort by total time, desc, then by name, desc.
923      heavyView.sort((rec1, rec2) =>
924        rec2.totalTime - rec1.totalTime ||
925        (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1));
926      this.printHeavyProfile(heavyView.head.children);
927    }
928  }
929
930  printHeader(headerTitle) {
931    print(`\n [${headerTitle}]:`);
932    print('   ticks  total  nonlib   name');
933  }
934
935  printLine(
936    entry, ticks, totalTicks, nonLibTicks) {
937    const pct = ticks * 100 / totalTicks;
938    const nonLibPct = nonLibTicks != null
939      ? `${(ticks * 100 / nonLibTicks).toFixed(1).toString().padStart(5)}%  `
940      : '        ';
941    print(`${`  ${ticks.toString().padStart(5)}  ` +
942      pct.toFixed(1).toString().padStart(5)}%  ${nonLibPct}${entry}`);
943  }
944
945  printHeavyProfHeader() {
946    print('\n [Bottom up (heavy) profile]:');
947    print('  Note: percentage shows a share of a particular caller in the ' +
948      'total\n' +
949      '  amount of its parent calls.');
950    print(`  Callers occupying less than ${TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1)}% are not shown.\n`);
951    print('   ticks parent  name');
952  }
953
954  processProfile(profile, filterP, func) {
955    for (let i = 0, n = profile.length; i < n; ++i) {
956      const rec = profile[i];
957      if (!filterP(rec.internalFuncName)) {
958        continue;
959      }
960      func(rec);
961    }
962  }
963
964  getLineAndColumn(name) {
965    const re = /:([0-9]+):([0-9]+)$/;
966    const array = re.exec(name);
967    if (!array) {
968      return null;
969    }
970    return { line: array[1], column: array[2] };
971  }
972
973  hasSourceMap() {
974    return this.sourceMap != null;
975  }
976
977  formatFunctionName(funcName) {
978    if (!this.hasSourceMap()) {
979      return funcName;
980    }
981    const lc = this.getLineAndColumn(funcName);
982    if (lc == null) {
983      return funcName;
984    }
985    // in source maps lines and columns are zero based
986    const lineNumber = lc.line - 1;
987    const column = lc.column - 1;
988    const entry = this.sourceMap.findEntry(lineNumber, column);
989    const sourceFile = entry[2];
990    const sourceLine = entry[3] + 1;
991    const sourceColumn = entry[4] + 1;
992
993    return `${sourceFile}:${sourceLine}:${sourceColumn} -> ${funcName}`;
994  }
995
996  printEntries(
997        profile, totalTicks, nonLibTicks, filterP, callback, printAllTicks) {
998    this.processProfile(profile, filterP, (rec) => {
999      if (rec.selfTime == 0) return;
1000      callback(rec);
1001      const funcName = this.formatFunctionName(rec.internalFuncName);
1002      if (printAllTicks) {
1003        this.printLine(funcName, rec.selfTime, totalTicks, nonLibTicks);
1004      }
1005    });
1006  }
1007
1008  printHeavyProfile(profile, opt_indent) {
1009    const indent = opt_indent || 0;
1010    const indentStr = ''.padStart(indent);
1011    this.processProfile(profile, () => true, (rec) => {
1012      // Cut off too infrequent callers.
1013      if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return;
1014      const funcName = this.formatFunctionName(rec.internalFuncName);
1015      print(`${`  ${rec.totalTime.toString().padStart(5)}  ` +
1016        rec.parentTotalPercent.toFixed(1).toString().padStart(5)}%  ${indentStr}${funcName}`);
1017      // Limit backtrace depth.
1018      if (indent < 2 * this.callGraphSize_) {
1019        this.printHeavyProfile(rec.children, indent + 2);
1020      }
1021      // Delimit top-level functions.
1022      if (indent == 0) print('');
1023    });
1024  }
1025}
1026