Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2017 the V8 project authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 "use strict" | 5 "use strict" |
| 6 | 6 |
| 7 function $(id) { | 7 function $(id) { |
| 8 return document.getElementById(id); | 8 return document.getElementById(id); |
| 9 } | 9 } |
| 10 | 10 |
| 11 let components = []; | 11 let components = []; |
| 12 | 12 |
| 13 function createViews() { | 13 function createViews() { |
| 14 components.push(new CallTreeView()); | 14 components.push(new CallTreeView()); |
| 15 components.push(new TimelineView()); | 15 components.push(new TimelineView()); |
| 16 components.push(new HelpView()); | 16 components.push(new HelpView()); |
| 17 components.push(new SummaryView()); | |
| 18 components.push(new ModeBarView()); | |
| 17 | 19 |
| 18 let modeBar = $("mode-bar"); | 20 main.setMode("summary"); |
| 19 | |
| 20 function addMode(id, text, active) { | |
| 21 let div = document.createElement("div"); | |
| 22 div.classList = "mode-button" + (active ? " active-mode-button" : ""); | |
| 23 div.id = "mode-" + id; | |
| 24 div.textContent = text; | |
| 25 div.onclick = () => { | |
| 26 if (main.currentState.callTree.mode === id) return; | |
| 27 let old = $("mode-" + main.currentState.callTree.mode); | |
| 28 old.classList = "mode-button"; | |
| 29 div.classList = "mode-button active-mode-button"; | |
| 30 main.setMode(id); | |
| 31 }; | |
| 32 modeBar.appendChild(div); | |
| 33 } | |
| 34 | |
| 35 addMode("bottom-up", "Bottom up", true); | |
| 36 addMode("top-down", "Top down"); | |
| 37 addMode("function-list", "Functions"); | |
| 38 | |
| 39 main.setMode("bottom-up"); | |
| 40 } | 21 } |
| 41 | 22 |
| 42 function emptyState() { | 23 function emptyState() { |
| 43 return { | 24 return { |
| 44 file : null, | 25 file : null, |
| 26 mode : "none", | |
| 27 currentCodeId : null, | |
| 45 start : 0, | 28 start : 0, |
| 46 end : Infinity, | 29 end : Infinity, |
| 47 timeLine : { | 30 timeLine : { |
| 48 width : 100, | 31 width : 100, |
| 49 height : 100 | 32 height : 100 |
| 50 }, | 33 }, |
| 51 callTree : { | 34 callTree : { |
| 52 mode : "none", | |
| 53 attribution : "js-exclude-bc", | 35 attribution : "js-exclude-bc", |
| 54 categories : "code-type", | 36 categories : "code-type", |
| 55 sort : "time" | 37 sort : "time" |
| 56 } | 38 } |
| 57 }; | 39 }; |
| 58 } | 40 } |
| 59 | 41 |
| 60 function setCallTreeState(state, callTreeState) { | 42 function setCallTreeState(state, callTreeState) { |
| 61 state = Object.assign({}, state); | 43 state = Object.assign({}, state); |
| 62 state.callTree = callTreeState; | 44 state.callTree = callTreeState; |
| 63 return state; | 45 return state; |
| 64 } | 46 } |
| 65 | 47 |
| 66 let main = { | 48 let main = { |
| 67 currentState : emptyState(), | 49 currentState : emptyState(), |
| 68 | 50 |
| 69 setMode(mode) { | 51 setMode(mode) { |
| 70 if (mode != main.currentState.mode) { | 52 if (mode != main.currentState.mode) { |
| 71 let callTreeState = Object.assign({}, main.currentState.callTree); | 53 |
| 72 callTreeState.mode = mode; | 54 function setCallTreeModifiers(attribution, categories, sort) { |
| 55 let callTreeState = Object.assign({}, main.currentState.callTree); | |
| 56 callTreeState.attribution = attribution; | |
| 57 callTreeState.categories = categories; | |
| 58 callTreeState.sort = sort; | |
| 59 return callTreeState; | |
| 60 } | |
| 61 | |
| 62 let state = Object.assign({}, main.currentState); | |
| 63 | |
| 73 switch (mode) { | 64 switch (mode) { |
| 74 case "bottom-up": | 65 case "bottom-up": |
| 75 callTreeState.attribution = "js-exclude-bc"; | 66 state.callTree = |
| 76 callTreeState.categories = "code-type"; | 67 setCallTreeModifiers("js-exclude-bc", "code-type", "time"); |
| 77 callTreeState.sort = "time"; | |
| 78 break; | 68 break; |
| 79 case "top-down": | 69 case "top-down": |
| 80 callTreeState.attribution = "js-exclude-bc"; | 70 state.callTree = |
| 81 callTreeState.categories = "none"; | 71 setCallTreeModifiers("js-exclude-bc", "none", "time"); |
| 82 callTreeState.sort = "time"; | |
| 83 break; | 72 break; |
| 84 case "function-list": | 73 case "function-list": |
| 85 callTreeState.attribution = "js-exclude-bc"; | 74 state.callTree = |
| 86 callTreeState.categories = "code-type"; | 75 setCallTreeModifiers("js-exclude-bc", "code-type", "own-time"); |
| 87 callTreeState.sort = "own-time"; | |
| 88 break; | 76 break; |
| 89 default: | |
| 90 console.error("Invalid mode"); | |
| 91 } | 77 } |
| 92 main.currentState = setCallTreeState(main.currentState, callTreeState); | 78 |
| 79 state.mode = mode; | |
| 80 | |
| 81 main.currentState = state; | |
| 93 main.delayRender(); | 82 main.delayRender(); |
| 94 } | 83 } |
| 95 }, | 84 }, |
| 96 | 85 |
| 97 setCallTreeAttribution(attribution) { | 86 setCallTreeAttribution(attribution) { |
| 98 if (attribution != main.currentState.attribution) { | 87 if (attribution != main.currentState.attribution) { |
| 99 let callTreeState = Object.assign({}, main.currentState.callTree); | 88 let callTreeState = Object.assign({}, main.currentState.callTree); |
| 100 callTreeState.attribution = attribution; | 89 callTreeState.attribution = attribution; |
| 101 main.currentState = setCallTreeState(main.currentState, callTreeState); | 90 main.currentState = setCallTreeState(main.currentState, callTreeState); |
| 102 main.delayRender(); | 91 main.delayRender(); |
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 194 Promise.resolve().then(() => { | 183 Promise.resolve().then(() => { |
| 195 for (let c of components) { | 184 for (let c of components) { |
| 196 c.render(main.currentState); | 185 c.render(main.currentState); |
| 197 } | 186 } |
| 198 }); | 187 }); |
| 199 } | 188 } |
| 200 }; | 189 }; |
| 201 | 190 |
| 202 let bucketDescriptors = | 191 let bucketDescriptors = |
| 203 [ { kinds : [ "JSOPT" ], | 192 [ { kinds : [ "JSOPT" ], |
| 193 color : "#00ff00", | |
| 194 backgroundColor : "#c0ffc0", | |
| 195 text : "JS Optimized" }, | |
| 196 { kinds : [ "JSUNOPT", "BC" ], | |
| 204 color : "#ffb000", | 197 color : "#ffb000", |
| 205 backgroundColor : "#ffe0c0", | 198 backgroundColor : "#ffe0c0", |
| 206 text : "JS Optimized" }, | |
| 207 { kinds : [ "JSUNOPT", "BC" ], | |
| 208 color : "#00ff00", | |
| 209 backgroundColor : "#c0ffc0", | |
| 210 text : "JS Unoptimized" }, | 199 text : "JS Unoptimized" }, |
| 211 { kinds : [ "IC" ], | 200 { kinds : [ "IC" ], |
| 212 color : "#ffff00", | 201 color : "#ffff00", |
| 213 backgroundColor : "#ffffc0", | 202 backgroundColor : "#ffffc0", |
| 214 text : "IC" }, | 203 text : "IC" }, |
| 215 { kinds : [ "STUB", "BUILTIN", "REGEXP" ], | 204 { kinds : [ "STUB", "BUILTIN", "REGEXP" ], |
| 216 color : "#ffb0b0", | 205 color : "#ffb0b0", |
| 217 backgroundColor : "#fff0f0", | 206 backgroundColor : "#fff0f0", |
| 218 text : "Other generated" }, | 207 text : "Other generated" }, |
| 219 { kinds : [ "CPP", "LIB" ], | 208 { kinds : [ "CPP", "LIB" ], |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 318 case "full-tree": | 307 case "full-tree": |
| 319 return (type, kind) => true; | 308 return (type, kind) => true; |
| 320 case "js-funs": | 309 case "js-funs": |
| 321 return (type, kind) => type !== 'CODE'; | 310 return (type, kind) => type !== 'CODE'; |
| 322 case "js-exclude-bc": | 311 case "js-exclude-bc": |
| 323 return (type, kind) => | 312 return (type, kind) => |
| 324 type !== 'CODE' || !isBytecodeHandler(kind); | 313 type !== 'CODE' || !isBytecodeHandler(kind); |
| 325 } | 314 } |
| 326 } | 315 } |
| 327 | 316 |
| 317 function createTableExpander(indent) { | |
| 318 let div = document.createElement("div"); | |
| 319 div.style.width = (indent + 0.5) + "em"; | |
| 320 div.style.display = "inline-block"; | |
| 321 div.style.textAlign = "right"; | |
| 322 return div; | |
| 323 } | |
| 324 | |
| 325 function createFunctionNode(name, codeId) { | |
| 326 if (codeId == -1) { | |
| 327 return document.createTextNode(name); | |
| 328 } | |
| 329 let nameElement = document.createElement("span"); | |
| 330 nameElement.classList.add("codeid-link") | |
| 331 nameElement.onclick = function() { | |
| 332 main.setCurrentCode(codeId); | |
| 333 }; | |
| 334 nameElement.appendChild(document.createTextNode(name)); | |
| 335 return nameElement; | |
| 336 } | |
| 337 | |
| 328 class CallTreeView { | 338 class CallTreeView { |
| 329 constructor() { | 339 constructor() { |
| 330 this.element = $("calltree"); | 340 this.element = $("calltree"); |
| 331 this.treeElement = $("calltree-table"); | 341 this.treeElement = $("calltree-table"); |
| 332 this.selectAttribution = $("calltree-attribution"); | 342 this.selectAttribution = $("calltree-attribution"); |
| 333 this.selectCategories = $("calltree-categories"); | 343 this.selectCategories = $("calltree-categories"); |
| 334 this.selectSort = $("calltree-sort"); | 344 this.selectSort = $("calltree-sort"); |
| 335 | 345 |
| 336 this.selectAttribution.onchange = () => { | 346 this.selectAttribution.onchange = () => { |
| 337 main.setCallTreeAttribution(this.selectAttribution.value); | 347 main.setCallTreeAttribution(this.selectAttribution.value); |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 370 }; | 380 }; |
| 371 case "category-own-time": | 381 case "category-own-time": |
| 372 return (c1, c2) => { | 382 return (c1, c2) => { |
| 373 if (c1.type === c2.type) return c2.ownTicks - c1.ownTicks; | 383 if (c1.type === c2.type) return c2.ownTicks - c1.ownTicks; |
| 374 if (c1.type < c2.type) return 1; | 384 if (c1.type < c2.type) return 1; |
| 375 return -1; | 385 return -1; |
| 376 }; | 386 }; |
| 377 } | 387 } |
| 378 } | 388 } |
| 379 | 389 |
| 380 createExpander(indent) { | |
| 381 let div = document.createElement("div"); | |
| 382 div.style.width = (1 + indent) + "em"; | |
| 383 div.style.display = "inline-block"; | |
| 384 div.style.textAlign = "right"; | |
| 385 return div; | |
| 386 } | |
| 387 | |
| 388 createFunctionNode(name, codeId) { | |
| 389 if (codeId == -1) { | |
| 390 return document.createTextNode(name); | |
| 391 } | |
| 392 let nameElement = document.createElement("span"); | |
| 393 nameElement.classList.add("codeid-link") | |
| 394 nameElement.onclick = function() { | |
| 395 main.setCurrentCode(codeId); | |
| 396 }; | |
| 397 nameElement.appendChild(document.createTextNode(name)); | |
| 398 return nameElement; | |
| 399 } | |
| 400 | |
| 401 expandTree(tree, indent) { | 390 expandTree(tree, indent) { |
| 402 let that = this; | 391 let that = this; |
| 403 let index = 0; | 392 let index = 0; |
| 404 let id = "R/"; | 393 let id = "R/"; |
| 405 let row = tree.row; | 394 let row = tree.row; |
| 406 let expander = tree.expander; | 395 let expander = tree.expander; |
| 407 | 396 |
| 408 if (row) { | 397 if (row) { |
| 409 console.assert("expander"); | |
| 410 index = row.rowIndex; | 398 index = row.rowIndex; |
| 411 id = row.id; | 399 id = row.id; |
| 412 | 400 |
| 413 // Make sure we collapse the children when the row is clicked | 401 // Make sure we collapse the children when the row is clicked |
| 414 // again. | 402 // again. |
| 415 expander.textContent = "\u25BE"; | 403 expander.textContent = "\u25BE"; |
| 416 let expandHandler = expander.onclick; | 404 let expandHandler = expander.onclick; |
| 417 expander.onclick = () => { | 405 expander.onclick = () => { |
| 418 that.collapseRow(tree, expander, expandHandler); | 406 that.collapseRow(tree, expander, expandHandler); |
| 419 } | 407 } |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 445 | 433 |
| 446 // Inclusive time % cell. | 434 // Inclusive time % cell. |
| 447 let c = row.insertCell(); | 435 let c = row.insertCell(); |
| 448 c.textContent = (node.ticks * 100 / this.tickCount).toFixed(2) + "%"; | 436 c.textContent = (node.ticks * 100 / this.tickCount).toFixed(2) + "%"; |
| 449 c.style.textAlign = "right"; | 437 c.style.textAlign = "right"; |
| 450 // Percent-of-parent cell. | 438 // Percent-of-parent cell. |
| 451 c = row.insertCell(); | 439 c = row.insertCell(); |
| 452 c.textContent = (node.ticks * 100 / tree.ticks).toFixed(2) + "%"; | 440 c.textContent = (node.ticks * 100 / tree.ticks).toFixed(2) + "%"; |
| 453 c.style.textAlign = "right"; | 441 c.style.textAlign = "right"; |
| 454 // Exclusive time % cell. | 442 // Exclusive time % cell. |
| 455 if (this.currentState.callTree.mode !== "bottom-up") { | 443 if (this.currentState.mode !== "bottom-up") { |
| 456 c = row.insertCell(-1); | 444 c = row.insertCell(-1); |
| 457 c.textContent = (node.ownTicks * 100 / this.tickCount).toFixed(2) + "%"; | 445 c.textContent = (node.ownTicks * 100 / this.tickCount).toFixed(2) + "%"; |
| 458 c.style.textAlign = "right"; | 446 c.style.textAlign = "right"; |
| 459 } | 447 } |
| 460 | 448 |
| 461 // Create the name cell. | 449 // Create the name cell. |
| 462 let nameCell = row.insertCell(); | 450 let nameCell = row.insertCell(); |
| 463 let expander = this.createExpander(indent); | 451 let expander = createTableExpander(indent + 1); |
| 464 nameCell.appendChild(expander); | 452 nameCell.appendChild(expander); |
| 465 nameCell.appendChild(createTypeDiv(node.type)); | 453 nameCell.appendChild(createTypeDiv(node.type)); |
| 466 nameCell.appendChild(this.createFunctionNode(node.name, node.codeId)); | 454 nameCell.appendChild(createFunctionNode(node.name, node.codeId)); |
| 467 | 455 |
| 468 // Inclusive ticks cell. | 456 // Inclusive ticks cell. |
| 469 c = row.insertCell(); | 457 c = row.insertCell(); |
| 470 c.textContent = node.ticks; | 458 c.textContent = node.ticks; |
| 471 c.style.textAlign = "right"; | 459 c.style.textAlign = "right"; |
| 472 if (this.currentState.callTree.mode !== "bottom-up") { | 460 if (this.currentState.mode !== "bottom-up") { |
| 473 // Exclusive ticks cell. | 461 // Exclusive ticks cell. |
| 474 c = row.insertCell(-1); | 462 c = row.insertCell(-1); |
| 475 c.textContent = node.ownTicks; | 463 c.textContent = node.ownTicks; |
| 476 c.style.textAlign = "right"; | 464 c.style.textAlign = "right"; |
| 477 } | 465 } |
| 478 if (node.children.length > 0) { | 466 if (node.children.length > 0) { |
| 479 expander.textContent = "\u25B8"; | 467 expander.textContent = "\u25B8"; |
| 480 expander.onclick = () => { that.expandTree(node, indent + 1); }; | 468 expander.onclick = () => { that.expandTree(node, indent + 1); }; |
| 481 } | 469 } |
| 482 | 470 |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 493 let index = row.rowIndex; | 481 let index = row.rowIndex; |
| 494 while (row.rowIndex < this.rows.rows.length && | 482 while (row.rowIndex < this.rows.rows.length && |
| 495 this.rows.rows[index].id.startsWith(id)) { | 483 this.rows.rows[index].id.startsWith(id)) { |
| 496 this.rows.deleteRow(index); | 484 this.rows.deleteRow(index); |
| 497 } | 485 } |
| 498 | 486 |
| 499 expander.textContent = "\u25B8"; | 487 expander.textContent = "\u25B8"; |
| 500 expander.onclick = expandHandler; | 488 expander.onclick = expandHandler; |
| 501 } | 489 } |
| 502 | 490 |
| 503 fillSelects(calltree) { | 491 fillSelects(mode, calltree) { |
| 504 function addOptions(e, values, current) { | 492 function addOptions(e, values, current) { |
| 505 while (e.options.length > 0) { | 493 while (e.options.length > 0) { |
| 506 e.remove(0); | 494 e.remove(0); |
| 507 } | 495 } |
| 508 for (let i = 0; i < values.length; i++) { | 496 for (let i = 0; i < values.length; i++) { |
| 509 let option = document.createElement("option"); | 497 let option = document.createElement("option"); |
| 510 option.value = values[i].value; | 498 option.value = values[i].value; |
| 511 option.textContent = values[i].text; | 499 option.textContent = values[i].text; |
| 512 e.appendChild(option); | 500 e.appendChild(option); |
| 513 } | 501 } |
| 514 e.value = current; | 502 e.value = current; |
| 515 } | 503 } |
| 516 | 504 |
| 517 let attributions = [ | 505 let attributions = [ |
| 518 { value : "js-exclude-bc", | 506 { value : "js-exclude-bc", |
| 519 text : "Attribute bytecode handlers to caller" }, | 507 text : "Attribute bytecode handlers to caller" }, |
| 520 { value : "full-tree", | 508 { value : "full-tree", |
| 521 text : "Count each code object separately" }, | 509 text : "Count each code object separately" }, |
| 522 { value : "js-funs", | 510 { value : "js-funs", |
| 523 text : "Attribute non-functions to JS functions" } | 511 text : "Attribute non-functions to JS functions" } |
| 524 ]; | 512 ]; |
| 525 | 513 |
| 526 switch (calltree.mode) { | 514 switch (mode) { |
| 527 case "bottom-up": | 515 case "bottom-up": |
| 528 addOptions(this.selectAttribution, attributions, calltree.attribution); | 516 addOptions(this.selectAttribution, attributions, calltree.attribution); |
| 529 addOptions(this.selectCategories, [ | 517 addOptions(this.selectCategories, [ |
| 530 { value : "code-type", text : "Code type" }, | 518 { value : "code-type", text : "Code type" }, |
| 531 { value : "none", text : "None" } | 519 { value : "none", text : "None" } |
| 532 ], calltree.categories); | 520 ], calltree.categories); |
| 533 addOptions(this.selectSort, [ | 521 addOptions(this.selectSort, [ |
| 534 { value : "time", text : "Time (including children)" }, | 522 { value : "time", text : "Time (including children)" }, |
| 535 { value : "category-time", text : "Code category, time" }, | 523 { value : "category-time", text : "Code category, time" }, |
| 536 ], calltree.sort); | 524 ], calltree.sort); |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 557 { value : "own-time", text : "Own time" }, | 545 { value : "own-time", text : "Own time" }, |
| 558 { value : "time", text : "Time (including children)" }, | 546 { value : "time", text : "Time (including children)" }, |
| 559 { value : "category-own-time", text : "Code category, own time"}, | 547 { value : "category-own-time", text : "Code category, own time"}, |
| 560 { value : "category-time", text : "Code category, time" }, | 548 { value : "category-time", text : "Code category, time" }, |
| 561 ], calltree.sort); | 549 ], calltree.sort); |
| 562 return; | 550 return; |
| 563 } | 551 } |
| 564 console.error("Unexpected mode"); | 552 console.error("Unexpected mode"); |
| 565 } | 553 } |
| 566 | 554 |
| 555 static isCallTreeMode(mode) { | |
| 556 switch (mode) { | |
| 557 case "bottom-up": | |
| 558 case "top-down": | |
| 559 case "function-list": | |
| 560 return true; | |
| 561 default: | |
| 562 return false; | |
| 563 } | |
| 564 } | |
| 565 | |
| 567 render(newState) { | 566 render(newState) { |
| 568 let oldState = this.currentState; | 567 let oldState = this.currentState; |
| 569 if (!newState.file) { | 568 if (!newState.file || !CallTreeView.isCallTreeMode(newState.mode)) { |
| 570 this.element.style.display = "none"; | 569 this.element.style.display = "none"; |
| 570 this.currentState = null; | |
| 571 return; | 571 return; |
| 572 } | 572 } |
| 573 | 573 |
| 574 this.currentState = newState; | 574 this.currentState = newState; |
| 575 if (oldState) { | 575 if (oldState) { |
| 576 if (newState.file === oldState.file && | 576 if (newState.file === oldState.file && |
| 577 newState.start === oldState.start && | 577 newState.start === oldState.start && |
| 578 newState.end === oldState.end && | 578 newState.end === oldState.end && |
| 579 newState.callTree.mode === oldState.callTree.mode && | 579 newState.mode === oldState.mode && |
| 580 newState.callTree.attribution === oldState.callTree.attribution && | 580 newState.callTree.attribution === oldState.callTree.attribution && |
| 581 newState.callTree.categories === oldState.callTree.categories && | 581 newState.callTree.categories === oldState.callTree.categories && |
| 582 newState.callTree.sort === oldState.callTree.sort) { | 582 newState.callTree.sort === oldState.callTree.sort) { |
| 583 // No change => just return. | 583 // No change => just return. |
| 584 return; | 584 return; |
| 585 } | 585 } |
| 586 } | 586 } |
| 587 | 587 |
| 588 this.element.style.display = "inherit"; | 588 this.element.style.display = "inherit"; |
| 589 | 589 |
| 590 let mode = this.currentState.callTree.mode; | 590 let mode = this.currentState.mode; |
| 591 if (!oldState || mode !== oldState.callTree.mode) { | 591 if (!oldState || mode !== oldState.mode) { |
| 592 // Technically, we should also call this if attribution, categories or | 592 // Technically, we should also call this if attribution, categories or |
| 593 // sort change, but the selection is already highlighted by the combobox | 593 // sort change, but the selection is already highlighted by the combobox |
| 594 // itself, so we do need to do anything here. | 594 // itself, so we do need to do anything here. |
| 595 this.fillSelects(newState.callTree); | 595 this.fillSelects(newState.mode, newState.callTree); |
| 596 } | 596 } |
| 597 | 597 |
| 598 let ownTimeClass = (mode === "bottom-up") ? "numeric-hidden" : "numeric"; | 598 let ownTimeClass = (mode === "bottom-up") ? "numeric-hidden" : "numeric"; |
| 599 let ownTimeTh = $(this.treeElement.id + "-own-time-header"); | 599 let ownTimeTh = $(this.treeElement.id + "-own-time-header"); |
| 600 ownTimeTh.classList = ownTimeClass; | 600 ownTimeTh.classList = ownTimeClass; |
| 601 let ownTicksTh = $(this.treeElement.id + "-own-ticks-header"); | 601 let ownTicksTh = $(this.treeElement.id + "-own-ticks-header"); |
| 602 ownTicksTh.classList = ownTimeClass; | 602 ownTicksTh.classList = ownTimeClass; |
| 603 | 603 |
| 604 // Build the tree. | 604 // Build the tree. |
| 605 let stackProcessor; | 605 let stackProcessor; |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 654 this.canvas.onmousedown = this.onMouseDown.bind(this); | 654 this.canvas.onmousedown = this.onMouseDown.bind(this); |
| 655 this.canvas.onmouseup = this.onMouseUp.bind(this); | 655 this.canvas.onmouseup = this.onMouseUp.bind(this); |
| 656 this.canvas.onmousemove = this.onMouseMove.bind(this); | 656 this.canvas.onmousemove = this.onMouseMove.bind(this); |
| 657 | 657 |
| 658 this.selectionStart = null; | 658 this.selectionStart = null; |
| 659 this.selectionEnd = null; | 659 this.selectionEnd = null; |
| 660 this.selecting = false; | 660 this.selecting = false; |
| 661 | 661 |
| 662 this.fontSize = 12; | 662 this.fontSize = 12; |
| 663 this.imageOffset = Math.round(this.fontSize * 1.2); | 663 this.imageOffset = Math.round(this.fontSize * 1.2); |
| 664 this.functionTimelineHeight = 16; | 664 this.functionTimelineHeight = 24; |
| 665 this.functionTimelineTickHeight = 16; | |
| 665 | 666 |
| 666 this.currentState = null; | 667 this.currentState = null; |
| 667 } | 668 } |
| 668 | 669 |
| 669 onMouseDown(e) { | 670 onMouseDown(e) { |
| 670 this.selectionStart = | 671 this.selectionStart = |
| 671 e.clientX - this.canvas.getBoundingClientRect().left; | 672 e.clientX - this.canvas.getBoundingClientRect().left; |
| 672 this.selectionEnd = this.selectionStart + 1; | 673 this.selectionEnd = this.selectionStart + 1; |
| 673 this.selecting = true; | 674 this.selecting = true; |
| 674 } | 675 } |
| (...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 857 for (let j = 0; j < bucketDescriptors.length; j++) { | 858 for (let j = 0; j < bucketDescriptors.length; j++) { |
| 858 let desc = bucketDescriptors[j]; | 859 let desc = bucketDescriptors[j]; |
| 859 for (let k = 0; k < desc.kinds.length; k++) { | 860 for (let k = 0; k < desc.kinds.length; k++) { |
| 860 sum += buckets[i][desc.kinds[k]]; | 861 sum += buckets[i][desc.kinds[k]]; |
| 861 } | 862 } |
| 862 bucketData.push(Math.round(graphHeight * sum / total)); | 863 bucketData.push(Math.round(graphHeight * sum / total)); |
| 863 } | 864 } |
| 864 bucketsGraph.push(bucketData); | 865 bucketsGraph.push(bucketData); |
| 865 } | 866 } |
| 866 | 867 |
| 867 // Draw the graph into the buffer. | 868 // Draw the category graph into the buffer. |
| 868 let bucketWidth = width / (bucketsGraph.length - 1); | 869 let bucketWidth = width / bucketsGraph.length; |
| 869 let ctx = buffer.getContext('2d'); | 870 let ctx = buffer.getContext('2d'); |
| 870 for (let i = 0; i < bucketsGraph.length - 1; i++) { | 871 for (let i = 0; i < bucketsGraph.length - 1; i++) { |
| 871 let bucketData = bucketsGraph[i]; | 872 let bucketData = bucketsGraph[i]; |
| 872 let nextBucketData = bucketsGraph[i + 1]; | 873 let nextBucketData = bucketsGraph[i + 1]; |
| 873 for (let j = 0; j < bucketData.length; j++) { | 874 for (let j = 0; j < bucketData.length; j++) { |
| 874 let x1 = Math.round(i * bucketWidth); | 875 let x1 = Math.round(i * bucketWidth); |
| 875 let x2 = Math.round((i + 1) * bucketWidth); | 876 let x2 = Math.round((i + 1) * bucketWidth); |
| 876 ctx.beginPath(); | 877 ctx.beginPath(); |
| 877 ctx.moveTo(x1, j && bucketData[j - 1]); | 878 ctx.moveTo(x1, j && bucketData[j - 1]); |
| 878 ctx.lineTo(x2, j && nextBucketData[j - 1]); | 879 ctx.lineTo(x2, j && nextBucketData[j - 1]); |
| 879 ctx.lineTo(x2, nextBucketData[j]); | 880 ctx.lineTo(x2, nextBucketData[j]); |
| 880 ctx.lineTo(x1, bucketData[j]); | 881 ctx.lineTo(x1, bucketData[j]); |
| 881 ctx.closePath(); | 882 ctx.closePath(); |
| 882 ctx.fillStyle = bucketDescriptors[j].color; | 883 ctx.fillStyle = bucketDescriptors[j].color; |
| 883 ctx.fill(); | 884 ctx.fill(); |
| 884 } | 885 } |
| 885 } | 886 } |
| 887 | |
| 888 // Draw the function ticks. | |
| 886 let functionTimelineYOffset = graphHeight; | 889 let functionTimelineYOffset = graphHeight; |
| 887 let functionTimelineHeight = this.functionTimelineHeight; | 890 let functionTimelineTickHeight = this.functionTimelineTickHeight; |
| 888 let functionTimelineHalfHeight = Math.round(functionTimelineHeight / 2); | 891 let functionTimelineHalfHeight = |
| 892 Math.round(functionTimelineTickHeight / 2); | |
| 889 let timestampScaler = width / (lastTime - firstTime); | 893 let timestampScaler = width / (lastTime - firstTime); |
| 894 let timestampToX = (t) => Math.round((t - firstTime) * timestampScaler); | |
| 890 ctx.fillStyle = "white"; | 895 ctx.fillStyle = "white"; |
| 891 ctx.fillRect( | 896 ctx.fillRect( |
| 892 0, | 897 0, |
| 893 functionTimelineYOffset, | 898 functionTimelineYOffset, |
| 894 buffer.width, | 899 buffer.width, |
| 895 functionTimelineHeight); | 900 this.functionTimelineHeight); |
| 896 for (let i = 0; i < codeIdProcessor.blocks.length; i++) { | 901 for (let i = 0; i < codeIdProcessor.blocks.length; i++) { |
| 897 let block = codeIdProcessor.blocks[i]; | 902 let block = codeIdProcessor.blocks[i]; |
| 898 let bucket = kindToBucketDescriptor[block.kind]; | 903 let bucket = kindToBucketDescriptor[block.kind]; |
| 899 ctx.fillStyle = bucket.color; | 904 ctx.fillStyle = bucket.color; |
| 900 ctx.fillRect( | 905 ctx.fillRect( |
| 901 Math.round((block.start - firstTime) * timestampScaler), | 906 timestampToX(block.start), |
| 902 functionTimelineYOffset, | 907 functionTimelineYOffset, |
| 903 Math.max(1, Math.round((block.end - block.start) * timestampScaler)), | 908 Math.max(1, Math.round((block.end - block.start) * timestampScaler)), |
| 904 block.topOfStack ? functionTimelineHeight : functionTimelineHalfHeight); | 909 block.topOfStack ? |
| 910 functionTimelineTickHeight : functionTimelineHalfHeight); | |
| 905 } | 911 } |
| 906 ctx.strokeStyle = "black"; | 912 ctx.strokeStyle = "black"; |
| 907 ctx.lineWidth = "1"; | 913 ctx.lineWidth = "1"; |
| 908 ctx.beginPath(); | 914 ctx.beginPath(); |
| 909 ctx.moveTo(0, functionTimelineYOffset + 0.5); | 915 ctx.moveTo(0, functionTimelineYOffset + 0.5); |
| 910 ctx.lineTo(buffer.width, functionTimelineYOffset + 0.5); | 916 ctx.lineTo(buffer.width, functionTimelineYOffset + 0.5); |
| 911 ctx.stroke(); | 917 ctx.stroke(); |
| 912 ctx.strokeStyle = "rgba(0,0,0,0.2)"; | 918 ctx.strokeStyle = "rgba(0,0,0,0.2)"; |
| 913 ctx.lineWidth = "1"; | 919 ctx.lineWidth = "1"; |
| 914 ctx.beginPath(); | 920 ctx.beginPath(); |
| 915 ctx.moveTo(0, functionTimelineYOffset + functionTimelineHalfHeight - 0.5); | 921 ctx.moveTo(0, functionTimelineYOffset + functionTimelineHalfHeight - 0.5); |
| 916 ctx.lineTo(buffer.width, | 922 ctx.lineTo(buffer.width, |
| 917 functionTimelineYOffset + functionTimelineHalfHeight - 0.5); | 923 functionTimelineYOffset + functionTimelineHalfHeight - 0.5); |
| 918 ctx.stroke(); | 924 ctx.stroke(); |
| 919 | 925 |
| 926 // Draw marks for optimizations and deoptimizations in the function | |
| 927 // timeline. | |
| 928 if (currentCodeId && currentCodeId >= 0 && | |
| 929 file.code[currentCodeId].func) { | |
| 930 let y = Math.round(functionTimelineYOffset + functionTimelineTickHeight + | |
| 931 (this.functionTimelineHeight - functionTimelineTickHeight) / 2); | |
| 932 let func = file.functions[file.code[currentCodeId].func]; | |
| 933 for (let i = 0; i < func.codes.length; i++) { | |
| 934 let code = file.code[func.codes[i]]; | |
| 935 if (code.kind === "Opt") { | |
| 936 // Draw optimization mark. | |
| 937 let x = timestampToX(code.tm); | |
| 938 ctx.lineWidth = 0.7; | |
| 939 ctx.strokeStyle = "blue"; | |
| 940 ctx.beginPath(); | |
| 941 ctx.moveTo(x - 3, y - 3); | |
|
Leszek Swirski
2017/03/21 14:28:39
maybe we should extract out a "draw line" method,
| |
| 942 ctx.lineTo(x, y); | |
| 943 ctx.stroke(); | |
| 944 ctx.beginPath(); | |
| 945 ctx.moveTo(x - 3, y + 3); | |
| 946 ctx.lineTo(x, y); | |
| 947 ctx.stroke(); | |
| 948 if (code.deopt) { | |
| 949 // Draw deoptimization mark. | |
| 950 let x = timestampToX(code.deopt.tm); | |
| 951 ctx.lineWidth = 0.7; | |
| 952 ctx.strokeStyle = "red"; | |
| 953 ctx.beginPath(); | |
| 954 ctx.moveTo(x - 3, y - 3); | |
| 955 ctx.lineTo(x + 3, y + 3); | |
| 956 ctx.stroke(); | |
| 957 ctx.beginPath(); | |
| 958 ctx.moveTo(x - 3, y + 3); | |
| 959 ctx.lineTo(x + 3, y - 3); | |
| 960 ctx.stroke(); | |
| 961 } | |
| 962 } else { | |
| 963 // Draw code creation mark. | |
| 964 let x = Math.round(timestampToX(code.tm)); | |
| 965 ctx.beginPath(); | |
| 966 ctx.fillStyle = "black"; | |
| 967 ctx.arc(x, y, 3, 0, 2 * Math.PI); | |
| 968 ctx.fill(); | |
| 969 } | |
| 970 } | |
| 971 } | |
| 972 | |
| 920 // Remember stuff for later. | 973 // Remember stuff for later. |
| 921 this.buffer = buffer; | 974 this.buffer = buffer; |
| 922 | 975 |
| 923 // Draw the buffer. | 976 // Draw the buffer. |
| 924 this.drawSelection(); | 977 this.drawSelection(); |
| 925 | 978 |
| 926 // (Re-)Populate the graph legend. | 979 // (Re-)Populate the graph legend. |
| 927 while (this.legend.cells.length > 0) { | 980 while (this.legend.cells.length > 0) { |
| 928 this.legend.deleteCell(0); | 981 this.legend.deleteCell(0); |
| 929 } | 982 } |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 951 } | 1004 } |
| 952 if (currentCodeId) { | 1005 if (currentCodeId) { |
| 953 let currentCode = file.code[currentCodeId]; | 1006 let currentCode = file.code[currentCodeId]; |
| 954 this.currentCode.appendChild(document.createTextNode(currentCode.name)); | 1007 this.currentCode.appendChild(document.createTextNode(currentCode.name)); |
| 955 } else { | 1008 } else { |
| 956 this.currentCode.appendChild(document.createTextNode("<none>")); | 1009 this.currentCode.appendChild(document.createTextNode("<none>")); |
| 957 } | 1010 } |
| 958 } | 1011 } |
| 959 } | 1012 } |
| 960 | 1013 |
| 1014 class ModeBarView { | |
| 1015 constructor() { | |
| 1016 let modeBar = this.element = $("mode-bar"); | |
| 1017 | |
| 1018 function addMode(id, text, active) { | |
| 1019 let div = document.createElement("div"); | |
| 1020 div.classList = "mode-button" + (active ? " active-mode-button" : ""); | |
| 1021 div.id = "mode-" + id; | |
| 1022 div.textContent = text; | |
| 1023 div.onclick = () => { | |
| 1024 if (main.currentState.mode === id) return; | |
| 1025 let old = $("mode-" + main.currentState.mode); | |
| 1026 old.classList = "mode-button"; | |
| 1027 div.classList = "mode-button active-mode-button"; | |
| 1028 main.setMode(id); | |
| 1029 }; | |
| 1030 modeBar.appendChild(div); | |
| 1031 } | |
| 1032 | |
| 1033 addMode("summary", "Summary", true); | |
| 1034 addMode("bottom-up", "Bottom up"); | |
| 1035 addMode("top-down", "Top down"); | |
| 1036 addMode("function-list", "Functions"); | |
| 1037 } | |
| 1038 | |
| 1039 render(newState) { | |
| 1040 if (!newState.file) { | |
| 1041 this.element.style.display = "none"; | |
| 1042 return; | |
| 1043 } | |
| 1044 | |
| 1045 this.element.style.display = "inherit"; | |
| 1046 } | |
| 1047 } | |
| 1048 | |
| 1049 class SummaryView { | |
| 1050 constructor() { | |
| 1051 this.element = $("summary"); | |
| 1052 this.currentState = null; | |
| 1053 } | |
| 1054 | |
| 1055 render(newState) { | |
| 1056 let oldState = this.currentState; | |
| 1057 | |
| 1058 if (!newState.file || newState.mode !== "summary") { | |
| 1059 this.element.style.display = "none"; | |
| 1060 this.currentState = null; | |
| 1061 return; | |
| 1062 } | |
| 1063 | |
| 1064 this.currentState = newState; | |
| 1065 if (oldState) { | |
| 1066 if (newState.file === oldState.file && | |
| 1067 newState.start === oldState.start && | |
| 1068 newState.end === oldState.end) { | |
| 1069 // No change, nothing to do. | |
| 1070 return; | |
| 1071 } | |
| 1072 } | |
| 1073 | |
| 1074 this.element.style.display = "inherit"; | |
| 1075 | |
| 1076 while (this.element.firstChild) { | |
| 1077 this.element.removeChild(this.element.firstChild); | |
| 1078 } | |
| 1079 | |
| 1080 let stats = computeOptimizationStats( | |
| 1081 this.currentState.file, newState.start, newState.end); | |
| 1082 | |
| 1083 let table = document.createElement("table"); | |
| 1084 let rows = document.createElement("tbody"); | |
| 1085 | |
| 1086 function addRow(text, number, indent) { | |
| 1087 let row = rows.insertRow(-1); | |
| 1088 let textCell = row.insertCell(-1); | |
| 1089 textCell.textContent = text; | |
| 1090 let numberCell = row.insertCell(-1); | |
| 1091 numberCell.textContent = number; | |
| 1092 if (indent) { | |
| 1093 textCell.style.textIndent = indent + "em"; | |
| 1094 numberCell.style.textIndent = indent + "em"; | |
| 1095 } | |
| 1096 return row; | |
| 1097 } | |
| 1098 | |
| 1099 function makeCollapsible(row, expander) { | |
| 1100 expander.textContent = "\u25BE"; | |
| 1101 let expandHandler = expander.onclick; | |
| 1102 expander.onclick = () => { | |
| 1103 let id = row.id; | |
| 1104 let index = row.rowIndex + 1; | |
| 1105 while (index < rows.rows.length && | |
| 1106 rows.rows[index].id.startsWith(id)) { | |
| 1107 rows.deleteRow(index); | |
| 1108 } | |
| 1109 expander.textContent = "\u25B8"; | |
| 1110 expander.onclick = expandHandler; | |
| 1111 } | |
| 1112 } | |
| 1113 | |
| 1114 function expandDeoptInstances(row, expander, instances, indent, kind) { | |
| 1115 let index = row.rowIndex; | |
| 1116 for (let i = 0; i < instances.length; i++) { | |
| 1117 let childRow = rows.insertRow(index + 1); | |
| 1118 childRow.id = row.id + i + "/"; | |
| 1119 | |
| 1120 let deopt = instances[i].deopt; | |
| 1121 | |
| 1122 let textCell = childRow.insertCell(-1); | |
| 1123 textCell.appendChild(document.createTextNode(deopt.posText)); | |
| 1124 textCell.style.textIndent = indent + "em"; | |
| 1125 let reasonCell = childRow.insertCell(-1); | |
| 1126 reasonCell.appendChild( | |
| 1127 document.createTextNode("Reason: " + deopt.reason)); | |
| 1128 reasonCell.style.textIndent = indent + "em"; | |
| 1129 } | |
| 1130 makeCollapsible(row, expander); | |
| 1131 } | |
| 1132 | |
| 1133 function expandDeoptFunctionList(row, expander, list, indent, kind) { | |
| 1134 let index = row.rowIndex; | |
| 1135 for (let i = 0; i < list.length; i++) { | |
| 1136 let childRow = rows.insertRow(index + 1); | |
| 1137 childRow.id = row.id + i + "/"; | |
| 1138 | |
| 1139 let textCell = childRow.insertCell(-1); | |
| 1140 let expander = createTableExpander(indent); | |
| 1141 textCell.appendChild(expander); | |
| 1142 textCell.appendChild( | |
| 1143 createFunctionNode(list[i].f.name, list[i].f.codes[0])); | |
| 1144 | |
| 1145 let numberCell = childRow.insertCell(-1); | |
| 1146 numberCell.textContent = list[i].instances.length; | |
| 1147 numberCell.style.textIndent = indent + "em"; | |
| 1148 | |
| 1149 expander.textContent = "\u25B8"; | |
| 1150 expander.onclick = () => { | |
| 1151 expandDeoptInstances( | |
| 1152 childRow, expander, list[i].instances, indent + 1); | |
| 1153 }; | |
| 1154 } | |
| 1155 makeCollapsible(row, expander); | |
| 1156 } | |
| 1157 | |
| 1158 function expandOptimizedFunctionList(row, expander, list, indent, kind) { | |
| 1159 let index = row.rowIndex; | |
| 1160 for (let i = 0; i < list.length; i++) { | |
| 1161 let childRow = rows.insertRow(index + 1); | |
| 1162 childRow.id = row.id + i + "/"; | |
| 1163 | |
| 1164 let textCell = childRow.insertCell(-1); | |
| 1165 textCell.appendChild( | |
| 1166 createFunctionNode(list[i].f.name, list[i].f.codes[0])); | |
| 1167 textCell.style.textIndent = indent + "em"; | |
| 1168 | |
| 1169 let numberCell = childRow.insertCell(-1); | |
| 1170 numberCell.textContent = list[i].instances.length; | |
| 1171 numberCell.style.textIndent = indent + "em"; | |
| 1172 } | |
| 1173 makeCollapsible(row, expander); | |
| 1174 } | |
| 1175 | |
| 1176 function addExpandableRow(text, list, indent, kind) { | |
| 1177 let row = rows.insertRow(-1); | |
| 1178 | |
| 1179 row.id = "opt-table/" + kind + "/"; | |
| 1180 | |
| 1181 let textCell = row.insertCell(-1); | |
| 1182 let expander = createTableExpander(indent); | |
| 1183 textCell.appendChild(expander); | |
| 1184 textCell.appendChild(document.createTextNode(text)); | |
| 1185 | |
| 1186 let numberCell = row.insertCell(-1); | |
| 1187 numberCell.textContent = list.count; | |
| 1188 if (indent) { | |
| 1189 numberCell.style.textIndent = indent + "em"; | |
| 1190 } | |
| 1191 | |
| 1192 if (list.count > 0) { | |
| 1193 expander.textContent = "\u25B8"; | |
| 1194 if (kind === "opt") { | |
| 1195 expander.onclick = () => { | |
| 1196 expandOptimizedFunctionList( | |
| 1197 row, expander, list.functions, indent + 1, kind); | |
| 1198 }; | |
| 1199 } else { | |
| 1200 expander.onclick = () => { | |
| 1201 expandDeoptFunctionList( | |
| 1202 row, expander, list.functions, indent + 1, kind); | |
| 1203 }; | |
| 1204 } | |
| 1205 } | |
| 1206 return row; | |
| 1207 } | |
| 1208 | |
| 1209 addRow("Total function count:", stats.functionCount); | |
| 1210 addRow("Optimized function count:", stats.optimizedFunctionCount, 1); | |
| 1211 addRow("Deoptimized function count:", stats.deoptimizedFunctionCount, 2); | |
| 1212 | |
| 1213 addExpandableRow("Optimization count:", stats.optimizations, 0, "opt"); | |
| 1214 let deoptCount = stats.eagerDeoptimizations.count + | |
| 1215 stats.softDeoptimizations.count + stats.lazyDeoptimizations.count; | |
| 1216 addRow("Deoptimization count:", deoptCount); | |
| 1217 addExpandableRow("Eager:", stats.eagerDeoptimizations, 1, "eager"); | |
| 1218 addExpandableRow("Lazy:", stats.lazyDeoptimizations, 1, "lazy"); | |
| 1219 addExpandableRow("Soft:", stats.softDeoptimizations, 1, "soft"); | |
| 1220 | |
| 1221 table.appendChild(rows); | |
| 1222 this.element.appendChild(table); | |
| 1223 } | |
| 1224 } | |
| 1225 | |
| 961 class HelpView { | 1226 class HelpView { |
| 962 constructor() { | 1227 constructor() { |
| 963 this.element = $("help"); | 1228 this.element = $("help"); |
| 964 } | 1229 } |
| 965 | 1230 |
| 966 render(newState) { | 1231 render(newState) { |
| 967 this.element.style.display = newState.file ? "none" : "inherit"; | 1232 this.element.style.display = newState.file ? "none" : "inherit"; |
| 968 } | 1233 } |
| 969 } | 1234 } |
| OLD | NEW |