From e607d54a0e165217c878e141174c8ade14832615 Mon Sep 17 00:00:00 2001 From: Zeynep Cankara Date: Wed, 22 Jul 2020 16:00:22 +0100 Subject: [PATCH] [tools][system-analyzer] Add map-details and map-transitions This CL encapsulates map panel behaviour with custom web components map-details for displaying information and map-transitions to show map transition tree. The web components emit/receive events to interact with the app. Bug: v8:10644 Change-Id: Ic61c2794ed7ab854ee61fa1e7c8fe9b1c3c31b7e Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2305889 Commit-Queue: Zeynep Cankara Reviewed-by: Camillo Bruni Reviewed-by: Sathya Gunasekaran Cr-Commit-Position: refs/heads/master@{#69004} --- tools/system-analyzer/helper.mjs | 7 +- tools/system-analyzer/index.css | 2 +- tools/system-analyzer/index.mjs | 3 +- tools/system-analyzer/map-model.mjs | 32 ++- tools/system-analyzer/map-panel-template.html | 140 +---------- tools/system-analyzer/map-panel.mjs | 221 +++--------------- .../map-panel/map-details-template.html | 19 ++ .../system-analyzer/map-panel/map-details.mjs | 37 +++ .../map-panel/map-transitions-template.html | 142 +++++++++++ .../map-panel/map-transitions.mjs | 185 +++++++++++++++ 10 files changed, 452 insertions(+), 336 deletions(-) create mode 100644 tools/system-analyzer/map-panel/map-details-template.html create mode 100644 tools/system-analyzer/map-panel/map-details.mjs create mode 100644 tools/system-analyzer/map-panel/map-transitions-template.html create mode 100644 tools/system-analyzer/map-panel/map-transitions.mjs diff --git a/tools/system-analyzer/helper.mjs b/tools/system-analyzer/helper.mjs index a517236d99..8bcf064de4 100644 --- a/tools/system-analyzer/helper.mjs +++ b/tools/system-analyzer/helper.mjs @@ -22,9 +22,10 @@ function formatSeconds(millis) { return (millis * kMillis2Seconds).toFixed(2) + 's'; } -function defineCustomElement(name, generator) { - let htmlTemplatePath = name + '-template.html'; - fetch(htmlTemplatePath) +function defineCustomElement(path, generator) { + let name = path.substring(path.lastIndexOf("/") + 1, path.length); + path = path + '-template.html'; + fetch(path) .then(stream => stream.text()) .then( templateText => customElements.define(name, generator(templateText))); diff --git a/tools/system-analyzer/index.css b/tools/system-analyzer/index.css index 29feeaeb72..b6c1ab8ba6 100644 --- a/tools/system-analyzer/index.css +++ b/tools/system-analyzer/index.css @@ -29,7 +29,7 @@ body { background-color: var(--background-color); letter-spacing: 0.5px; } -h2 { +h2, h4 { background-color: var(--primary-color); box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); transition: 0.3s; diff --git a/tools/system-analyzer/index.mjs b/tools/system-analyzer/index.mjs index b78a02fbb4..e07003510f 100644 --- a/tools/system-analyzer/index.mjs +++ b/tools/system-analyzer/index.mjs @@ -96,8 +96,7 @@ class App { } handleShowMaps(e) { - //document.state.view.transitionView.showMaps(e.detail); - document.state.mapPanel.showMaps(e.detail); + document.state.mapPanel.mapEntries = e.detail; } handleSelectIc(e){ diff --git a/tools/system-analyzer/map-model.mjs b/tools/system-analyzer/map-model.mjs index a1b90c3752..5f6adb3353 100644 --- a/tools/system-analyzer/map-model.mjs +++ b/tools/system-analyzer/map-model.mjs @@ -62,7 +62,7 @@ class State { set map(value) { this._map = value; this._navigation.updateUrl(); - this.mapPanel_.showMap(this.map); + this.mapPanel_.map = this._map; this.view.redraw(); } updateChunks() { @@ -155,12 +155,17 @@ class Navigation { class View { constructor(state, mapPanelId, timelinePanelId) { this.mapPanel_ = $(mapPanelId); + this.mapPanel_.addEventListener( + 'statemapchange', e => this.handleStateMapChange(e)); + this.mapPanel_.addEventListener( + 'selectmapdblclick', e => this.handleDblClickSelectMap(e)); + this.mapPanel_.addEventListener( + 'sourcepositionsclick', e => this.handleClickSourcePositions(e)); + this.timelinePanel_ = $(timelinePanelId); this.state = state; setInterval(this.timelinePanel_.updateOverviewWindow(), 50); this.timelinePanel_.createBackgroundCanvas(); - this.mapPanel_.transitionView = state; - this.isLocked = false; this._filteredEntries = []; } @@ -174,6 +179,24 @@ class View { return this.state.map } + handleClickSourcePositions(e){ + //TODO(zcankara) Handle source position + console.log("source position map detail: ", e.detail); + } + + handleDblClickSelectMap(e){ + //TODO(zcankara) Handle double clicked map + console.log("double clicked map: ", e.detail); + } + + handleStateMapChange(e){ + this.state.map = e.detail; + } + + handleIsLocked(e){ + this.state.view.isLocked = e.detail; + } + updateTimeline() { let chunksNode = this.timelinePanel_.timelineChunks; removeAllChildren(chunksNode); @@ -247,7 +270,8 @@ class View { this.isLocked = true; let chunk = event.target.chunk; if (!chunk) return; - this.mapPanel_.showMaps(chunk.getUniqueTransitions()); + this.state.view.isLocked = true; + this.mapPanel_.mapEntries = chunk.getUniqueTransitions(); } drawOverview() { diff --git a/tools/system-analyzer/map-panel-template.html b/tools/system-analyzer/map-panel-template.html index 7a572f7ce9..8ebaf51e29 100644 --- a/tools/system-analyzer/map-panel-template.html +++ b/tools/system-analyzer/map-panel-template.html @@ -4,132 +4,6 @@ found in the LICENSE file. --> +
+
+

Map Details

+
+
+
diff --git a/tools/system-analyzer/map-panel/map-details.mjs b/tools/system-analyzer/map-panel/map-details.mjs new file mode 100644 index 0000000000..65534c8feb --- /dev/null +++ b/tools/system-analyzer/map-panel/map-details.mjs @@ -0,0 +1,37 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +import {V8CustomElement, defineCustomElement} from '../helper.mjs'; + +defineCustomElement('./map-panel/map-details', (templateText) => + class MapDetails extends V8CustomElement { + constructor() { + super(templateText); + this.mapDetails.addEventListener('click', () => this.handleClickSourcePositions()); + this.selectedMap = undefined; + } + get mapDetails() { + return this.$('#mapDetails'); + } + + setSelectedMap(value) { + this.selectedMap = value; + } + + set mapDetails(map){ + let details = ''; + if (map) { + details += 'ID: ' + map.id; + details += '\nSource location: ' + map.filePosition; + details += '\n' + map.description; + this.setSelectedMap(map); + } + this.mapDetails.innerText = details; + } + + handleClickSourcePositions(){ + this.dispatchEvent(new CustomEvent( + 'sourcepositionsclick', {bubbles: true, composed: true, detail: this.selectedMap.filePosition})); + } + +}); diff --git a/tools/system-analyzer/map-panel/map-transitions-template.html b/tools/system-analyzer/map-panel/map-transitions-template.html new file mode 100644 index 0000000000..836ba4d8e8 --- /dev/null +++ b/tools/system-analyzer/map-panel/map-transitions-template.html @@ -0,0 +1,142 @@ + + + + + +
+
+

Transitions

+
+
+
+
+
+
diff --git a/tools/system-analyzer/map-panel/map-transitions.mjs b/tools/system-analyzer/map-panel/map-transitions.mjs new file mode 100644 index 0000000000..579cf85ea1 --- /dev/null +++ b/tools/system-analyzer/map-panel/map-transitions.mjs @@ -0,0 +1,185 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +import {V8CustomElement, defineCustomElement} from '../helper.mjs'; + +defineCustomElement('./map-panel/map-transitions', (templateText) => + class MapTransitions extends V8CustomElement { + #map; + constructor() { + super(templateText); + this.transitionView.addEventListener( + 'mousemove', e => this.handleTransitionViewChange(e)); + this.currentNode = this.transitionView; + this.currentMap = undefined; + } + + get transitionView() { + return this.$('#transitionView'); + } + + get tooltip() { + return this.$('#tooltip'); + } + + get tooltipContents() { + return this.$('#tooltipContents'); + } + + set map(value) { + this.#map = value; + this.showMap(); + } + + handleTransitionViewChange(e){ + this.tooltip.style.left = e.pageX + "px"; + this.tooltip.style.top = e.pageY + "px"; + let map = e.target.map; + if (map) { + this.tooltipContents.innerText = map.description; + } + } + + selectMap(map) { + this.currentMap = map; + this.dispatchEvent(new CustomEvent( + 'mapdetailsupdate', {bubbles: true, composed: true, detail: map})); + this.dispatchEvent(new CustomEvent( + 'statemapchange', {bubbles: true, composed: true, detail: map})); + } + + dblClickSelectMap(map) { + this.dispatchEvent(new CustomEvent( + 'selectmapdblclick', {bubbles: true, composed: true, detail: map})); + } + + showMap() { + // Called when a map selected + let selected = this.#map; + if (this.currentMap === this.#map) return; + this.currentMap = this.#map; + this.mapEntries = [this.#map]; + this.dispatchEvent(new CustomEvent( + 'mapdetailsupdate', {bubbles: true, composed: true, detail: selected})); + } + + showMaps() { + // Timeline dbl click to show map transitions of selected maps + this.transitionView.style.display = 'none'; + this.removeAllChildren(this.transitionView); + this.mapEntries.forEach(map => this.addMapAndParentTransitions(map)); + this.transitionView.style.display = ''; + } + + set mapEntries(list){ + this._mapEntries = list; + this.showMaps(); + } + + get mapEntries(){ + return this._mapEntries; + } + + addMapAndParentTransitions(map) { + if (map === void 0) return; + this.currentNode = this.transitionView; + let parents = map.getParents(); + if (parents.length > 0) { + this.addTransitionTo(parents.pop()); + parents.reverse().forEach(each => this.addTransitionTo(each)); + } + let mapNode = this.addSubtransitions(map); + // Mark and show the selected map. + mapNode.classList.add('selected'); + if (this.selectedMap == map) { + setTimeout( + () => mapNode.scrollIntoView( + {behavior: 'smooth', block: 'nearest', inline: 'nearest'}), + 1); + } + } + + addMapNode(map) { + let node = this.div('map'); + if (map.edge) node.style.backgroundColor = map.edge.getColor(); + node.map = map; + node.addEventListener('click', () => this.selectMap(map)); + node.addEventListener('dblclick', () => this.dblClickSelectMap(map)); + if (map.children.length > 1) { + node.innerText = map.children.length; + let showSubtree = this.div('showSubtransitions'); + showSubtree.addEventListener('click', (e) => this.toggleSubtree(e, node)); + node.appendChild(showSubtree); + } else if (map.children.length == 0) { + node.innerHTML = '●' + } + this.currentNode.appendChild(node); + return node; + } + + addSubtransitions(map) { + let mapNode = this.addTransitionTo(map); + // Draw outgoing linear transition line. + let current = map; + while (current.children.length == 1) { + current = current.children[0].to; + this.addTransitionTo(current); + } + return mapNode; + } + + addTransitionEdge(map) { + let classes = ['transitionEdge']; + let edge = this.div(classes); + edge.style.backgroundColor = map.edge.getColor(); + let labelNode = this.div('transitionLabel'); + labelNode.innerText = map.edge.toString(); + edge.appendChild(labelNode); + return edge; + } + + addTransitionTo(map) { + // transition[ transitions[ transition[...], transition[...], ...]]; + + let transition = this.div('transition'); + if (map.isDeprecated()) transition.classList.add('deprecated'); + if (map.edge) { + transition.appendChild(this.addTransitionEdge(map)); + } + let mapNode = this.addMapNode(map); + transition.appendChild(mapNode); + + let subtree = this.div('transitions'); + transition.appendChild(subtree); + + this.currentNode.appendChild(transition); + this.currentNode = subtree; + + return mapNode; + } + + toggleSubtree(event, node) { + let map = node.map; + event.target.classList.toggle('opened'); + let transitionsNode = node.parentElement.querySelector('.transitions'); + let subtransitionNodes = transitionsNode.children; + if (subtransitionNodes.length <= 1) { + // Add subtransitions excepth the one that's already shown. + let visibleTransitionMap = subtransitionNodes.length == 1 ? + transitionsNode.querySelector('.map').map : + void 0; + map.children.forEach(edge => { + if (edge.to != visibleTransitionMap) { + this.currentNode = transitionsNode; + this.addSubtransitions(edge.to); + } + }); + } else { + // remove all but the first (currently selected) subtransition + for (let i = subtransitionNodes.length - 1; i > 0; i--) { + transitionsNode.removeChild(subtransitionNodes[i]); + } + } + } + +});