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 |