• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 the V8 project 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
7let codeKinds = [
8    "UNKNOWN",
9    "CPP_PARSE",
10    "CPP_COMP_BC",
11    "CPP_COMP_BASELINE",
12    "CPP_COMP",
13    "CPP_GC",
14    "CPP_EXT",
15    "CPP",
16    "LIB",
17    "IC",
18    "BC",
19    "STUB",
20    "BUILTIN",
21    "REGEXP",
22    "JS_OPT",
23    "JS_UNOPT",
24    "JS_TURBOPROP",
25    "JS_BASELINE",
26];
27
28function resolveCodeKind(code) {
29  if (!code || !code.type) {
30    return "UNKNOWN";
31  } else if (code.type === "CPP") {
32    return "CPP";
33  } else if (code.type === "SHARED_LIB") {
34    return "LIB";
35  } else if (code.type === "CODE") {
36    if (code.kind === "LoadIC" ||
37        code.kind === "StoreIC" ||
38        code.kind === "KeyedStoreIC" ||
39        code.kind === "KeyedLoadIC" ||
40        code.kind === "LoadGlobalIC" ||
41        code.kind === "Handler") {
42      return "IC";
43    } else if (code.kind === "BytecodeHandler") {
44      return "BC";
45    } else if (code.kind === "Stub") {
46      return "STUB";
47    } else if (code.kind === "Builtin") {
48      return "BUILTIN";
49    } else if (code.kind === "RegExp") {
50      return "REGEXP";
51    }
52    console.log("Unknown CODE: '" + code.kind + "'.");
53    return "CODE";
54  } else if (code.type === "JS") {
55    if (code.kind === "Builtin") {
56      return "JS_UNOPT";
57    } else if (code.kind === "Opt") {
58      return "JS_OPT";
59    } else if (code.kind === "Unopt") {
60      return "JS_UNOPT";
61    } else if (code.kind === "Baseline") {
62      return "JS_BASELINE";
63    } else if (code.kind === "Turboprop") {
64      return "JS_TURBOPROP";
65    }
66  }
67  console.log("Unknown code type '" + type + "'.");
68}
69
70function resolveCodeKindAndVmState(code, vmState) {
71  let kind = resolveCodeKind(code);
72  if (kind === "CPP") {
73    if (vmState === 1) {
74      kind = "CPP_GC";
75    } else if (vmState === 2) {
76      kind = "CPP_PARSE";
77    } else if (vmState === 3) {
78      kind = "CPP_COMP_BC";
79    } else if (vmState === 4) {
80      kind = "CPP_COMP";
81    } else if (vmState === 6) {
82      kind = "CPP_EXT";
83    }
84    // TODO(cbruni): add CPP_COMP_BASELINE
85  }
86  return kind;
87}
88
89function codeEquals(code1, code2, allowDifferentKinds = false) {
90  if (!code1 || !code2) return false;
91  if (code1.name !== code2.name || code1.type !== code2.type) return false;
92
93  if (code1.type === 'CODE') {
94    if (!allowDifferentKinds && code1.kind !== code2.kind) return false;
95  } else if (code1.type === 'JS') {
96    if (!allowDifferentKinds && code1.kind !== code2.kind) return false;
97    if (code1.func !== code2.func) return false;
98  }
99  return true;
100}
101
102function createNodeFromStackEntry(code, codeId, vmState) {
103  let name = code ? code.name : "UNKNOWN";
104  let node = createEmptyNode(name);
105  node.codeId = codeId;
106  node.type = resolveCodeKindAndVmState(code, vmState);
107  return node;
108}
109
110function childIdFromCode(codeId, code) {
111  // For JavaScript function, pretend there is one instance of optimized
112  // function and one instance of unoptimized function per SFI.
113  // Otherwise, just compute the id from code id.
114  let type = resolveCodeKind(code);
115  if (type === "JSOPT") {
116    return code.func * 4 + 1;
117  } else if (type === "JSUNOPT") {
118    return code.func * 4 + 2;
119  } else {
120    return codeId * 4;
121  }
122}
123
124// We store list of ticks and positions within the ticks stack by
125// storing flattened triplets of { tickIndex, depth, count }.
126// Triplet { 123, 2, 3 } encodes positions in ticks 123, 124, 125,
127// all of them at depth 2. The flattened array is used to encode
128// position within the call-tree.
129
130// The following function helps to encode such triplets.
131function addFrameToFrameList(paths, pathIndex, depth) {
132  // Try to combine with the previous code run.
133  if (paths.length > 0 &&
134      paths[paths.length - 3] + 1 === pathIndex &&
135      paths[paths.length - 2] === depth) {
136    paths[paths.length - 1]++;
137  } else {
138    paths.push(pathIndex, depth, 1);
139  }
140}
141
142function findNextFrame(file, stack, stackPos, step, filter) {
143  let codeId = -1;
144  let code = null;
145  while (stackPos >= 0 && stackPos < stack.length) {
146    codeId = stack[stackPos];
147    code = codeId >= 0 ? file.code[codeId] : undefined;
148
149    if (filter) {
150      let type = code ? code.type : undefined;
151      let kind = code ? code.kind : undefined;
152      if (filter(type, kind)) return stackPos;
153    }
154    stackPos += step;
155  }
156  return -1;
157}
158
159function addOrUpdateChildNode(parent, file, stackIndex, stackPos, ascending) {
160  if (stackPos === -1) {
161    // We reached the end without finding the next step.
162    // If we are doing top-down call tree, update own ticks.
163    if (!ascending) {
164      parent.ownTicks++;
165    }
166    return;
167  }
168
169  let stack = file.ticks[stackIndex].s;
170  console.assert(stackPos >= 0 && stackPos < stack.length);
171  let codeId = stack[stackPos];
172  let code = codeId >= 0 ? file.code[codeId] : undefined;
173  // We found a child node.
174  let childId = childIdFromCode(codeId, code);
175  let child = parent.children[childId];
176  if (!child) {
177    let vmState = file.ticks[stackIndex].vm;
178    child = createNodeFromStackEntry(code, codeId, vmState);
179    child.delayedExpansion = { frameList : [], ascending };
180    parent.children[childId] = child;
181  }
182  child.ticks++;
183  addFrameToFrameList(child.delayedExpansion.frameList, stackIndex, stackPos);
184}
185
186// This expands a tree node (direct children only).
187function expandTreeNode(file, node, filter) {
188  let { frameList, ascending } = node.delayedExpansion;
189
190  let step = ascending ? 2 : -2;
191
192  for (let i = 0; i < frameList.length; i+= 3) {
193    let firstStackIndex = frameList[i];
194    let depth = frameList[i + 1];
195    let count = frameList[i + 2];
196    for (let j = 0; j < count; j++) {
197      let stackIndex = firstStackIndex + j;
198      let stack = file.ticks[stackIndex].s;
199
200      // Get to the next frame that has not been filtered out.
201      let stackPos = findNextFrame(file, stack, depth + step, step, filter);
202      addOrUpdateChildNode(node, file, stackIndex, stackPos, ascending);
203    }
204  }
205  node.delayedExpansion = null;
206}
207
208function createEmptyNode(name) {
209  return {
210      name : name,
211      codeId: -1,
212      type : "CAT",
213      children : [],
214      ownTicks : 0,
215      ticks : 0
216  };
217}
218
219class RuntimeCallTreeProcessor {
220  constructor() {
221    this.tree = createEmptyNode("root");
222    this.tree.delayedExpansion = { frameList : [], ascending : false };
223  }
224
225  addStack(file, tickIndex) {
226    this.tree.ticks++;
227
228    let stack = file.ticks[tickIndex].s;
229    let i;
230    for (i = 0; i < stack.length; i += 2) {
231      let codeId = stack[i];
232      if (codeId < 0) return;
233      let code = file.code[codeId];
234      if (code.type !== "CPP" && code.type !== "SHARED_LIB") {
235        i -= 2;
236        break;
237      }
238    }
239    if (i < 0 || i >= stack.length) return;
240    addOrUpdateChildNode(this.tree, file, tickIndex, i, false);
241  }
242}
243
244class PlainCallTreeProcessor {
245  constructor(filter, isBottomUp) {
246    this.filter = filter;
247    this.tree = createEmptyNode("root");
248    this.tree.delayedExpansion = { frameList : [], ascending : isBottomUp };
249    this.isBottomUp = isBottomUp;
250  }
251
252  addStack(file, tickIndex) {
253    let stack = file.ticks[tickIndex].s;
254    let step = this.isBottomUp ? 2 : -2;
255    let start = this.isBottomUp ? 0 : stack.length - 2;
256
257    let stackPos = findNextFrame(file, stack, start, step, this.filter);
258    addOrUpdateChildNode(this.tree, file, tickIndex, stackPos, this.isBottomUp);
259
260    this.tree.ticks++;
261  }
262}
263
264function buildCategoryTreeAndLookup() {
265  let root = createEmptyNode("root");
266  let categories = {};
267  function addCategory(name, types) {
268    let n = createEmptyNode(name);
269    for (let i = 0; i < types.length; i++) {
270      categories[types[i]] = n;
271    }
272    root.children.push(n);
273  }
274  addCategory("JS Optimized", [ "JS_OPT" ]);
275  addCategory("JS Turboprop", [ "JS_TURBOPROP" ]);
276  addCategory("JS Baseline", [ "JS_BASELINE" ]);
277  addCategory("JS Unoptimized", [ "JS_UNOPT", "BC" ]);
278  addCategory("IC", [ "IC" ]);
279  addCategory("RegExp", [ "REGEXP" ]);
280  addCategory("Other generated", [ "STUB", "BUILTIN" ]);
281  addCategory("C++", [ "CPP", "LIB" ]);
282  addCategory("C++/GC", [ "CPP_GC" ]);
283  addCategory("C++/Parser", [ "CPP_PARSE" ]);
284  addCategory("C++/Bytecode Compiler", [ "CPP_COMP_BC" ]);
285  addCategory("C++/Baseline Compiler", [ "CPP_COMP_BASELINE" ]);
286  addCategory("C++/Compiler", [ "CPP_COMP" ]);
287  addCategory("C++/External", [ "CPP_EXT" ]);
288  addCategory("Unknown", [ "UNKNOWN" ]);
289
290  return { categories, root };
291}
292
293class CategorizedCallTreeProcessor {
294  constructor(filter, isBottomUp) {
295    this.filter = filter;
296    let { categories, root } = buildCategoryTreeAndLookup();
297
298    this.tree = root;
299    this.categories = categories;
300    this.isBottomUp = isBottomUp;
301  }
302
303  addStack(file, tickIndex) {
304    let stack = file.ticks[tickIndex].s;
305    let vmState = file.ticks[tickIndex].vm;
306    if (stack.length === 0) return;
307    let codeId = stack[0];
308    let code = codeId >= 0 ? file.code[codeId] : undefined;
309    let kind = resolveCodeKindAndVmState(code, vmState);
310    let node = this.categories[kind];
311
312    this.tree.ticks++;
313    node.ticks++;
314
315    let step = this.isBottomUp ? 2 : -2;
316    let start = this.isBottomUp ? 0 : stack.length - 2;
317
318    let stackPos = findNextFrame(file, stack, start, step, this.filter);
319    addOrUpdateChildNode(node, file, tickIndex, stackPos, this.isBottomUp);
320  }
321}
322
323class FunctionListTree {
324  constructor(filter, withCategories) {
325    if (withCategories) {
326      let { categories, root } = buildCategoryTreeAndLookup();
327      this.tree = root;
328      this.categories = categories;
329    } else {
330      this.tree = createEmptyNode("root");
331      this.categories = null;
332    }
333
334    this.codeVisited = [];
335    this.filter = filter;
336  }
337
338  addStack(file, tickIndex) {
339    let stack = file.ticks[tickIndex].s;
340    let vmState = file.ticks[tickIndex].vm;
341
342    this.tree.ticks++;
343    let child = null;
344    let tree = null;
345    for (let i = stack.length - 2; i >= 0; i -= 2) {
346      let codeId = stack[i];
347      if (codeId < 0 || this.codeVisited[codeId]) continue;
348
349      let code = file.code[codeId];
350      if (this.filter) {
351        let type = code ? code.type : undefined;
352        let kind = code ? code.kind : undefined;
353        if (!this.filter(type, kind)) continue;
354      }
355      let childId = childIdFromCode(codeId, code);
356      if (this.categories) {
357        let kind = resolveCodeKindAndVmState(code, vmState);
358        tree = this.categories[kind];
359      } else {
360        tree = this.tree;
361      }
362      child = tree.children[childId];
363      if (!child) {
364        child = createNodeFromStackEntry(code, codeId, vmState);
365        child.children[0] = createEmptyNode("Top-down tree");
366        child.children[0].delayedExpansion =
367          { frameList : [], ascending : false };
368        child.children[1] = createEmptyNode("Bottom-up tree");
369        child.children[1].delayedExpansion =
370          { frameList : [], ascending : true };
371        tree.children[childId] = child;
372      }
373      child.ticks++;
374      child.children[0].ticks++;
375      addFrameToFrameList(
376          child.children[0].delayedExpansion.frameList, tickIndex, i);
377      child.children[1].ticks++;
378      addFrameToFrameList(
379          child.children[1].delayedExpansion.frameList, tickIndex, i);
380      this.codeVisited[codeId] = true;
381    }
382    if (child) {
383      child.ownTicks++;
384      console.assert(tree !== null);
385      tree.ticks++;
386      console.assert(tree.type === "CAT");
387    }
388
389    for (let i = 0; i < stack.length; i += 2) {
390      let codeId = stack[i];
391      if (codeId >= 0) this.codeVisited[codeId] = false;
392    }
393  }
394}
395
396
397class CategorySampler {
398  constructor(file, bucketCount) {
399    this.bucketCount = bucketCount;
400
401    this.firstTime = file.ticks[0].tm;
402    let lastTime = file.ticks[file.ticks.length - 1].tm;
403    this.step = (lastTime - this.firstTime) / bucketCount;
404
405    this.buckets = [];
406    let bucket = {};
407    for (let i = 0; i < codeKinds.length; i++) {
408      bucket[codeKinds[i]] = 0;
409    }
410    for (let i = 0; i < bucketCount; i++) {
411      this.buckets.push(Object.assign({ total : 0 }, bucket));
412    }
413  }
414
415  addStack(file, tickIndex) {
416    let { tm : timestamp, vm : vmState, s : stack } = file.ticks[tickIndex];
417
418    let i = Math.floor((timestamp - this.firstTime) / this.step);
419    if (i === this.buckets.length) i--;
420    console.assert(i >= 0 && i < this.buckets.length);
421
422    let bucket = this.buckets[i];
423    bucket.total++;
424
425    let codeId = (stack.length > 0) ? stack[0] : -1;
426    let code = codeId >= 0 ? file.code[codeId] : undefined;
427    let kind = resolveCodeKindAndVmState(code, vmState);
428    bucket[kind]++;
429  }
430}
431
432class FunctionTimelineProcessor {
433  constructor(functionCodeId, filter) {
434    this.functionCodeId = functionCodeId;
435    this.filter = filter;
436    this.blocks = [];
437    this.currentBlock = null;
438  }
439
440  addStack(file, tickIndex) {
441    if (!this.functionCodeId) return;
442
443    let { tm : timestamp, vm : vmState, s : stack } = file.ticks[tickIndex];
444    let functionCode = file.code[this.functionCodeId];
445
446    // Find if the function is on the stack, and its position on the stack,
447    // ignoring any filtered entries.
448    let stackCode = undefined;
449    let functionPosInStack = -1;
450    let filteredI = 0;
451    for (let i = 0; i < stack.length - 1; i += 2) {
452      let codeId = stack[i];
453      let code = codeId >= 0 ? file.code[codeId] : undefined;
454      let type = code ? code.type : undefined;
455      let kind = code ? code.kind : undefined;
456      if (!this.filter(type, kind)) continue;
457
458      // Match other instances of the same function (e.g. unoptimised, various
459      // different optimised versions).
460      if (codeEquals(code, functionCode, true)) {
461        functionPosInStack = filteredI;
462        stackCode = code;
463        break;
464      }
465      filteredI++;
466    }
467
468    if (functionPosInStack >= 0) {
469      let stackKind = resolveCodeKindAndVmState(stackCode, vmState);
470
471      let codeIsTopOfStack = (functionPosInStack === 0);
472
473      if (this.currentBlock !== null) {
474        this.currentBlock.end = timestamp;
475
476        if (codeIsTopOfStack === this.currentBlock.topOfStack
477          && stackKind === this.currentBlock.kind) {
478          // If we haven't changed the stack top or the function kind, then
479          // we're happy just extending the current block and not starting
480          // a new one.
481          return;
482        }
483      }
484
485      // Start a new block at the current timestamp.
486      this.currentBlock = {
487        start: timestamp,
488        end: timestamp,
489        code: stackCode,
490        kind: stackKind,
491        topOfStack: codeIsTopOfStack
492      };
493      this.blocks.push(this.currentBlock);
494    } else {
495      this.currentBlock = null;
496    }
497  }
498}
499
500// Generates a tree out of a ticks sequence.
501// {file} is the JSON files with the ticks and code objects.
502// {startTime}, {endTime} is the interval.
503// {tree} is the processor of stacks.
504function generateTree(
505    file, startTime, endTime, tree) {
506  let ticks = file.ticks;
507  let i = 0;
508  while (i < ticks.length && ticks[i].tm < startTime) {
509    i++;
510  }
511
512  let tickCount = 0;
513  while (i < ticks.length && ticks[i].tm < endTime) {
514    tree.addStack(file, i);
515    i++;
516    tickCount++;
517  }
518
519  return tickCount;
520}
521
522function computeOptimizationStats(file,
523    timeStart = -Infinity, timeEnd = Infinity) {
524  function newCollection() {
525    return { count : 0, functions : [], functionTable : [] };
526  }
527  function addToCollection(collection, code) {
528    collection.count++;
529    let funcData = collection.functionTable[code.func];
530    if (!funcData) {
531      funcData = { f : file.functions[code.func], instances : [] };
532      collection.functionTable[code.func] = funcData;
533      collection.functions.push(funcData);
534    }
535    funcData.instances.push(code);
536  }
537
538  let functionCount = 0;
539  let optimizedFunctionCount = 0;
540  let turbopropOptimizedFunctionCount = 0;
541  let deoptimizedFunctionCount = 0;
542  let optimizations = newCollection();
543  let turbopropOptimizations = newCollection();
544  let eagerDeoptimizations = newCollection();
545  let softDeoptimizations = newCollection();
546  let lazyDeoptimizations = newCollection();
547  let softBailouts = newCollection();
548  let eagerBailouts = newCollection();
549
550  for (let i = 0; i < file.functions.length; i++) {
551    let f = file.functions[i];
552
553    // Skip special SFIs that do not correspond to JS functions.
554    if (f.codes.length === 0) continue;
555    if (file.code[f.codes[0]].type !== "JS") continue;
556
557    functionCount++;
558    let optimized = false;
559    let turboprop_optimized = false;
560    let deoptimized = false;
561
562    for (let j = 0; j < f.codes.length; j++) {
563      let code = file.code[f.codes[j]];
564      console.assert(code.type === "JS");
565      if (code.kind === "Opt") {
566        optimized = true;
567        if (code.tm >= timeStart && code.tm <= timeEnd) {
568          addToCollection(optimizations, code);
569        }
570      }
571      if (code.kind === "Turboprop") {
572        turboprop_optimized = true;
573        if (code.tm >= timeStart && code.tm <= timeEnd) {
574          addToCollection(turbopropOptimizations, code);
575        }
576      }
577      if (code.deopt) {
578        if (code.deopt.bailoutType === "deopt-lazy" || code.deopt.bailoutType === "deopt-eager" || code.deopt.bailoutType === "deopt-lazy") {
579          deoptimized = true;
580        }
581        if (code.deopt.tm >= timeStart && code.deopt.tm <= timeEnd) {
582          switch (code.deopt.bailoutType) {
583            case "deopt-lazy":
584              addToCollection(lazyDeoptimizations, code);
585              break;
586            case "deopt-eager":
587              addToCollection(eagerDeoptimizations, code);
588              break;
589            case "deopt-soft":
590              addToCollection(softDeoptimizations, code);
591              break;
592            case "bailout-soft":
593              addToCollection(softBailouts, code);
594              break;
595            case "bailout":
596              addToCollection(eagerBailouts, code);
597              break;
598          }
599        }
600      }
601    }
602    if (optimized) {
603      optimizedFunctionCount++;
604    }
605    if (turboprop_optimized) {
606      turbopropOptimizedFunctionCount++;
607    }
608    if (deoptimized) {
609      deoptimizedFunctionCount++;
610    }
611  }
612
613  function sortCollection(collection) {
614    collection.functions.sort(
615        (a, b) => a.instances.length - b.instances.length);
616  }
617
618  sortCollection(eagerDeoptimizations);
619  sortCollection(lazyDeoptimizations);
620  sortCollection(softDeoptimizations);
621  sortCollection(optimizations);
622  sortCollection(turbopropOptimizations);
623
624  return {
625    functionCount,
626    optimizedFunctionCount,
627    turbopropOptimizedFunctionCount,
628    deoptimizedFunctionCount,
629    optimizations,
630    turbopropOptimizations,
631    eagerDeoptimizations,
632    lazyDeoptimizations,
633    softDeoptimizations,
634    softBailouts,
635    eagerBailouts,
636  };
637}
638
639function normalizeLeadingWhitespace(lines) {
640  let regex = /^\s*/;
641  let minimumLeadingWhitespaceChars = Infinity;
642  for (let line of lines) {
643    minimumLeadingWhitespaceChars =
644        Math.min(minimumLeadingWhitespaceChars, regex.exec(line)[0].length);
645  }
646  for (let i = 0; i < lines.length; i++) {
647    lines[i] = lines[i].substring(minimumLeadingWhitespaceChars);
648  }
649}
650