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