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