diff options
author | Michaël Zasso <targos@protonmail.com> | 2018-03-07 08:54:53 +0100 |
---|---|---|
committer | Michaël Zasso <targos@protonmail.com> | 2018-03-07 16:48:52 +0100 |
commit | 88786fecff336342a56e6f2e7ff3b286be716e47 (patch) | |
tree | 92e6ba5b8ac8dae1a058988d20c9d27bfa654390 /deps/v8/tools/map-processor.html | |
parent | 4e86f9b5ab83cbabf43839385bf383e6a7ef7d19 (diff) | |
download | android-node-v8-88786fecff336342a56e6f2e7ff3b286be716e47.tar.gz android-node-v8-88786fecff336342a56e6f2e7ff3b286be716e47.tar.bz2 android-node-v8-88786fecff336342a56e6f2e7ff3b286be716e47.zip |
deps: update V8 to 6.5.254.31
PR-URL: https://github.com/nodejs/node/pull/18453
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Yang Guo <yangguo@chromium.org>
Reviewed-By: Ali Ijaz Sheikh <ofrobots@google.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Diffstat (limited to 'deps/v8/tools/map-processor.html')
-rw-r--r-- | deps/v8/tools/map-processor.html | 1254 |
1 files changed, 1254 insertions, 0 deletions
diff --git a/deps/v8/tools/map-processor.html b/deps/v8/tools/map-processor.html new file mode 100644 index 0000000000..4029e96343 --- /dev/null +++ b/deps/v8/tools/map-processor.html @@ -0,0 +1,1254 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright 2017 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. + --> +<head> +<meta charset="UTF-8"> +<style> +html, body { + font-family: sans-serif; + padding: 0px; + margin: 0px; +} +h1, h2, h3, section { + padding-left: 15px; +} +#stats table { + display: inline-block; + padding-right: 50px; +} +#stats .transitionTable { + max-height: 200px; + overflow-y: scroll; +} +#timeline { + position: relative; + height: 300px; + overflow-y: hidden; + overflow-x: scroll; + user-select: none; +} +#timelineChunks { + height: 250px; + position: absolute; + margin-right: 100px; +} +#timelineCanvas { + height: 250px; + position: relative; + overflow: visible; + pointer-events: none; +} +.chunk { + width: 6px; + border: 0px white solid; + border-width: 0 2px 0 2px; + position: absolute; + background-size: 100% 100%; + image-rendering: pixelated; + bottom: 0px; +} +.timestamp { + height: 250px; + width: 100px; + border-left: 1px black dashed; + padding-left: 4px; + position: absolute; + pointer-events: none; + font-size: 10px; + opacity: 0.5; +} +#timelineOverview { + width: 100%; + height: 50px; + position: relative; + margin-top: -50px; + margin-bottom: 10px; + background-size: 100% 100%; + border: 1px black solid; + border-width: 1px 0 1px 0; + overflow: hidden; +} +#timelineOverviewIndicator { + height: 100%; + position: absolute; + box-shadow: 0px 2px 20px -5px black inset; + top: 0px; + cursor: ew-resize; +} +#timelineOverviewIndicator .leftMask, +#timelineOverviewIndicator .rightMask { + background-color: rgba(200, 200, 200, 0.5); + width: 10000px; + height: 100%; + position: absolute; + top: 0px; +} +#timelineOverviewIndicator .leftMask { + right: 100%; +} +#timelineOverviewIndicator .rightMask { + left: 100%; +} +#mapDetails { + font-family: monospace; + white-space: pre; +} +#transitionView { + overflow-x: scroll; + white-space: nowrap; + min-height: 50px; + max-height: 200px; + padding: 50px 0 0 0; + margin-top: -25px; + width: 100%; +} +.map { + width: 20px; + height: 20px; + display: inline-block; + border-radius: 50%; + background-color: black; + border: 4px solid white; + font-size: 10px; + text-align: center; + line-height: 18px; + color: white; + vertical-align: top; + margin-top: -13px; + /* raise z-index */ + position: relative; + z-index: 2; + cursor: pointer; +} +.map.selected { + border-color: black; +} +.transitions { + display: inline-block; + margin-left: -15px; +} +.transition { + min-height: 55px; + margin: 0 0 -2px 2px; +} +/* gray out deprecated transitions */ +.deprecated > .transitionEdge, +.deprecated > .map { + opacity: 0.5; +} +.deprecated > .transition { + border-color: rgba(0, 0, 0, 0.5); +} +/* Show a border for all but the first transition */ +.transition:nth-of-type(2), +.transition:nth-last-of-type(n+2) { + border-left: 2px solid; + margin-left: 0px; +} +/* special case for 2 transitions */ +.transition:nth-last-of-type(1) { + border-left: none; +} +/* topmost transitions are not related */ +#transitionView > .transition { + border-left: none; +} +/* topmost transition edge needs initial offset to be aligned */ +#transitionView > .transition > .transitionEdge { + margin-left: 13px; +} +.transitionEdge { + height: 2px; + width: 80px; + display: inline-block; + margin: 0 0 2px 0; + background-color: black; + vertical-align: top; + padding-left: 15px; +} +.transitionLabel { + color: black; + transform: rotate(-15deg); + transform-origin: top left; + margin-top: -10px; + font-size: 10px; + white-space: normal; + word-break: break-all; + background-color: rgba(255,255,255,0.5); +} +.red { + background-color: red; +} +.green { + background-color: green; +} +.yellow { + background-color: yellow; + color: black; +} +.blue { + background-color: blue; +} +.orange { + background-color: orange; +} +.violet { + background-color: violet; + color: black; +} +.showSubtransitions { + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 10px solid black; + cursor: zoom-in; + margin: 4px 0 0 4px; +} +.showSubtransitions.opened { + border-top: none; + border-bottom: 10px solid black; + cursor: zoom-out; +} +#tooltip { + position: absolute; + width: 10px; + height: 10px; + background-color: red; + pointer-events: none; + z-index: 100; + display: none; +} +</style> +<script src="./splaytree.js"></script> +<script src="./codemap.js"></script> +<script src="./csvparser.js"></script> +<script src="./consarray.js"></script> +<script src="./profile.js"></script> +<script src="./profile_view.js"></script> +<script src="./logreader.js"></script> +<script src="./SourceMap.js"></script> +<script src="./arguments.js"></script> +<script src="./map-processor.js"></script> +<script> +"use strict" +// ========================================================================= +const kChunkHeight = 250; +const kChunkWidth = 10; + +class State { + constructor() { + this._nofChunks = 400; + this._map = undefined; + this._timeline = undefined; + this._chunks = undefined; + this._view = new View(this); + this._navigation = new Navigation(this, this.view); + } + get timeline() { return this._timeline } + set timeline(value) { + this._timeline = value; + this.updateChunks(); + this.view.updateTimeline(); + this.view.updateStats(); + } + get chunks() { return this._chunks } + get nofChunks() { return this._nofChunks } + set nofChunks(count) { + this._nofChunks = count; + this.updateChunks(); + this.view.updateTimeline(); + } + get view() { return this._view } + get navigation() { return this._navigation } + get map() { return this._map } + set map(value) { + this._map = value; + this._navigation.updateUrl(); + this.view.updateMapDetails(); + this.view.redraw(); + } + updateChunks() { + this._chunks = this._timeline.chunks(this._nofChunks); + } + get entries() { + if (!this.map) return {}; + return { + map: this.map.id, + time: this.map.time + } + } +} + +// ========================================================================= +// DOM Helper +function $(id) { + return document.getElementById(id) +} + +function removeAllChildren(node) { + while (node.lastChild) { + node.removeChild(node.lastChild); + } +} + +function selectOption(select, match) { + let options = select.options; + for (let i = 0; i < options.length; i++) { + if (match(i, options[i])) { + select.selectedIndex = i; + return; + } + } +} + +function div(classes) { + let node = document.createElement('div'); + if (classes !== void 0) { + if (typeof classes == "string") { + node.classList.add(classes); + } else { + classes.forEach(cls => node.classList.add(cls)); + } + } + return node; +} + +function table(className) { + let node = document.createElement("table") + if (className) node.classList.add(className) + return node; +} +function td(text) { + let node = document.createElement("td"); + node.innerText = text; + return node; +} +function tr() { + let node = document.createElement("tr"); + return node; +} + +function define(prototype, name, fn) { + Object.defineProperty(prototype, name, {value:fn, enumerable:false}); +} + +define(Array.prototype, "max", function(fn) { + if (this.length == 0) return undefined; + if (fn == undefined) fn = (each) => each; + let max = fn(this[0]); + for (let i = 1; i < this.length; i++) { + max = Math.max(max, fn(this[i])); + } + return max; +}) +define(Array.prototype, "histogram", function(mapFn) { + let histogram = []; + for (let i = 0; i < this.length; i++) { + let value = this[i]; + let index = Math.round(mapFn(value)) + let bucket = histogram[index]; + if (bucket !== undefined) { + bucket.push(value); + } else { + histogram[index] = [value]; + } + } + for (let i = 0; i < histogram.length; i++) { + histogram[i] = histogram[i] || []; + } + return histogram; +}); + +define(Array.prototype, "first", function() { return this[0] }); +define(Array.prototype, "last", function() { return this[this.length - 1] }); + +// ========================================================================= +// EventHandlers +function handleBodyLoad() { + let upload = $('uploadInput'); + upload.onclick = (e) => { e.target.value = null }; + upload.onchange = (e) => { handleLoadFile(e.target) }; + upload.focus(); + + document.state = new State(); + $("transitionView").addEventListener("mousemove", e => { + let tooltip = $("tooltip"); + tooltip.style.left = e.pageX + "px"; + tooltip.style.top = e.pageY + "px"; + let map = e.target.map; + if (map) { + $("tooltipContents").innerText = map.description.join("\n"); + } + }); +} + +function handleLoadFile(upload) { + let files = upload.files; + let file = files[0]; + let reader = new FileReader(); + reader.onload = function(evt) { + handleLoadText(this.result); + } + reader.readAsText(file); +} + +function handleLoadText(text) { + let mapProcessor = new MapProcessor(); + document.state.timeline = mapProcessor.processString(text); +} + +function handleKeyDown(event) { + let nav = document.state.navigation; + switch(event.key) { + case "ArrowUp": + event.preventDefault(); + if (event.shiftKey) { + nav.selectPrevEdge(); + } else { + nav.moveInChunk(-1); + } + return false; + case "ArrowDown": + event.preventDefault(); + if (event.shiftKey) { + nav.selectNextEdge(); + } else { + nav.moveInChunk(1); + } + return false; + case "ArrowLeft": + nav.moveInChunks(false); + break; + case "ArrowRight": + nav.moveInChunks(true); + break; + case "+": + nav.increaseTimelineResolution(); + break; + case "-": + nav.decreaseTimelineResolution(); + break; + } +}; +document.onkeydown = handleKeyDown; + +function handleTimelineIndicatorMove(event) { + if (event.buttons == 0) return; + let timelineTotalWidth = $("timelineCanvas").offsetWidth; + let factor = $("timelineOverview").offsetWidth / timelineTotalWidth; + $("timeline").scrollLeft += event.movementX / factor; +} + +// ========================================================================= + +Object.defineProperty(Edge.prototype, 'getColor', { value:function() { + return transitionTypeToColor(this.type); +}}); + +class Navigation { + constructor(state, view) { + this.state = state; + this.view = view; + } + get map() { return this.state.map } + set map(value) { this.state.map = value } + get chunks() { return this.state.chunks } + + increaseTimelineResolution() { + this.state.nofChunks *= 1.5; + } + + decreaseTimelineResolution() { + this.state.nofChunks /= 1.5; + } + + selectNextEdge() { + if (!this.map) return; + if (this.map.children.length != 1) return; + this.map = this.map.children[0].to; + } + + selectPrevEdge() { + if (!this.map) return; + if (!this.map.parent()) return; + this.map = this.map.parent(); + } + + selectDefaultMap() { + this.map = this.chunks[0].at(0); + } + moveInChunks(next) { + if (!this.map) return this.selectDefaultMap(); + let chunkIndex = this.map.chunkIndex(this.chunks); + let chunk = this.chunks[chunkIndex]; + let index = chunk.indexOf(this.map); + if (next) { + chunk = chunk.next(this.chunks); + } else { + chunk = chunk.prev(this.chunks); + } + if (!chunk) return; + index = Math.min(index, chunk.size()-1); + this.map = chunk.at(index); + } + + moveInChunk(delta) { + if (!this.map) return this.selectDefaultMap(); + let chunkIndex = this.map.chunkIndex(this.chunks) + let chunk = this.chunks[chunkIndex]; + let index = chunk.indexOf(this.map) + delta; + let map; + if (index < 0) { + map = chunk.prev(this.chunks).last(); + } else if (index >= chunk.size()) { + map = chunk.next(this.chunks).first() + } else { + map = chunk.at(index); + } + this.map = map; + } + + updateUrl() { + let entries = this.state.entries; + let params = new URLSearchParams(entries); + window.history.pushState(entries, "", "?" + params.toString()); + } +} + +class View { + constructor(state) { + this.state = state; + setInterval(this.updateOverviewWindow, 50); + this.backgroundCanvas = document.createElement("canvas"); + this.transitionView = new TransitionView(state, $("transitionView")); + this.statsView = new StatsView(state, $("stats")); + this.isLocked = false; + } + get chunks() { return this.state.chunks } + get timeline() { return this.state.timeline } + get map() { return this.state.map } + + updateStats() { + this.statsView.update(); + } + + updateMapDetails() { + let details = ""; + if (this.map) { + details += "ID: " + this.map.id; + details += "\n" + this.map.description; + } + $("mapDetails").innerText = details; + this.transitionView.showMap(this.map); + } + + updateTimeline() { + let chunksNode = $("timelineChunks"); + removeAllChildren(chunksNode); + let chunks = this.chunks; + let max = chunks.max(each => each.size()); + let start = this.timeline.startTime; + let end = this.timeline.endTime; + let duration = end - start; + const timeToPixel = chunks.length * kChunkWidth / duration; + let addTimestamp = (time, name) => { + let timeNode = div("timestamp"); + timeNode.innerText = name; + timeNode.style.left = ((time-start) * timeToPixel) + "px"; + chunksNode.appendChild(timeNode); + }; + for (let i = 0; i < chunks.length; i++) { + let chunk = chunks[i]; + let height = (chunk.size() / max * kChunkHeight); + chunk.height = height; + if (chunk.isEmpty()) continue; + let node = div(); + node.className = "chunk"; + node.style.left = (i * kChunkWidth) + "px"; + node.style.height = height + "px"; + node.chunk = chunk; + node.addEventListener("mousemove", e => this.handleChunkMouseMove(e)); + node.addEventListener("click", e => this.handleChunkClick(e)); + node.addEventListener("dblclick", e => this.handleChunkDoubleClick(e)); + this.setTimelineChunkBackground(chunk, node); + chunksNode.appendChild(node); + chunk.markers.forEach(marker => addTimestamp(marker.time, marker.name)); + } + // Put a time marker roughly every 20 chunks. + let expected = duration / chunks.length * 20; + let interval = (10 ** Math.floor(Math.log10(expected))); + let correction = Math.log10(expected / interval); + correction = (correction < 0.33) ? 1 : (correction < 0.75) ? 2.5 : 5; + interval *= correction; + + let time = start; + while (time < end) { + addTimestamp(time, ((time-start) / 1000) + " ms"); + time += interval; + } + this.drawOverview(); + this.drawHistograms(); + this.redraw(); + } + + handleChunkMouseMove(event) { + if (this.isLocked) return false; + let chunk = event.target.chunk; + if (!chunk) return; + // topmost map (at chunk.height) == map #0. + let relativeIndex = + Math.round(event.layerY / event.target.offsetHeight * chunk.size()); + let map = chunk.at(relativeIndex); + this.state.map = map; + } + + handleChunkClick(event) { + this.isLocked = !this.isLocked; + } + + handleChunkDoubleClick(event) { + this.isLocked = true; + let chunk = event.target.chunk; + if (!chunk) return; + this.transitionView.showMaps(chunk.getUniqueTransitions()); + } + + setTimelineChunkBackground(chunk, node) { + // Render the types of transitions as bar charts + const kHeight = chunk.height; + const kWidth = 1; + this.backgroundCanvas.width = kWidth; + this.backgroundCanvas.height = kHeight; + let ctx = this.backgroundCanvas.getContext("2d"); + ctx.clearRect(0, 0, kWidth, kHeight); + let y = 0; + let total = chunk.size(); + let type, count; + if (true) { + chunk.getTransitionBreakdown().forEach(([type, count]) => { + ctx.fillStyle = transitionTypeToColor(type); + let height = count / total * kHeight; + ctx.fillRect(0, y, kWidth, y + height); + y += height; + }); + } else { + chunk.items.forEach(map => { + ctx.fillStyle = transitionTypeToColor(map.getType()); + let y = chunk.yOffset(map); + ctx.fillRect(0, y, kWidth, y + 1); + }); + } + + let imageData = this.backgroundCanvas.toDataURL("image/png"); + node.style.backgroundImage = "url(" + imageData + ")"; + } + + updateOverviewWindow() { + let indicator = $("timelineOverviewIndicator"); + let totalIndicatorWidth = $("timelineOverview").offsetWidth; + let div = $("timeline"); + let timelineTotalWidth = $("timelineCanvas").offsetWidth; + let factor = $("timelineOverview").offsetWidth / timelineTotalWidth; + let width = div.offsetWidth * factor; + let left = div.scrollLeft * factor; + indicator.style.width = width + "px"; + indicator.style.left = left + "px"; + } + + drawOverview() { + const height = 50; + const kFactor = 2; + let canvas = this.backgroundCanvas; + canvas.height = height; + canvas.width = window.innerWidth; + let ctx = canvas.getContext("2d"); + + let chunks = this.state.timeline.chunkSizes(canvas.width * kFactor); + let max = chunks.max(); + + ctx.clearRect(0, 0, canvas.width, height); + ctx.strokeStyle = "black"; + ctx.fillStyle = "black"; + ctx.beginPath(); + ctx.moveTo(0,height); + for (let i = 0; i < chunks.length; i++) { + ctx.lineTo(i/kFactor, height - chunks[i]/max * height); + } + ctx.lineTo(chunks.length, height); + ctx.stroke(); + ctx.closePath(); + ctx.fill(); + let imageData = canvas.toDataURL("image/png"); + $("timelineOverview").style.backgroundImage = "url(" + imageData + ")"; + } + + drawHistograms() { + $("mapsDepthHistogram").histogram = this.timeline.depthHistogram(); + $("mapsFanOutHistogram").histogram = this.timeline.fanOutHistogram(); + } + + drawMapsDepthHistogram() { + let canvas = $("mapsDepthCanvas"); + let histogram = this.timeline.depthHistogram(); + this.drawHistogram(canvas, histogram, true); + } + + drawMapsFanOutHistogram() { + let canvas = $("mapsFanOutCanvas"); + let histogram = this.timeline.fanOutHistogram(); + this.drawHistogram(canvas, histogram, true, true); + } + + drawHistogram(canvas, histogram, logScaleX=false, logScaleY=false) { + let ctx = canvas.getContext("2d"); + let yMax = histogram.max(each => each.length); + if (logScaleY) yMax = Math.log(yMax); + let xMax = histogram.length; + if (logScaleX) xMax = Math.log(xMax); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.beginPath(); + ctx.moveTo(0,canvas.height); + for (let i = 0; i < histogram.length; i++) { + let x = i; + if (logScaleX) x = Math.log(x); + x = x / xMax * canvas.width; + let bucketLength = histogram[i].length; + if (logScaleY) bucketLength = Math.log(bucketLength); + let y = (1 - bucketLength / yMax) * canvas.height; + ctx.lineTo(x, y); + } + ctx.lineTo(canvas.width, canvas.height); + ctx.closePath; + ctx.stroke(); + ctx.fill(); + } + + redraw() { + let canvas= $("timelineCanvas"); + canvas.width = (this.chunks.length+1) * kChunkWidth; + canvas.height = kChunkHeight; + let ctx = canvas.getContext("2d"); + ctx.clearRect(0, 0, canvas.width, kChunkHeight); + if (!this.state.map) return; + this.drawEdges(ctx); + } + + setMapStyle(map, ctx) { + ctx.fillStyle = map.edge && map.edge.from ? "black" : "green"; + } + + setEdgeStyle(edge, ctx) { + let color = edge.getColor(); + ctx.strokeStyle = color; + ctx.fillStyle = color; + } + + markMap(ctx, map) { + let [x, y] = map.position(this.state.chunks); + ctx.beginPath(); + this.setMapStyle(map, ctx); + ctx.arc(x, y, 3, 0, 2 * Math.PI); + ctx.fill(); + ctx.beginPath(); + ctx.fillStyle = "white"; + ctx.arc(x, y, 2, 0, 2 * Math.PI); + ctx.fill(); + } + + markSelectedMap(ctx, map) { + let [x, y] = map.position(this.state.chunks); + ctx.beginPath(); + this.setMapStyle(map, ctx); + ctx.arc(x, y, 6, 0, 2 * Math.PI); + ctx.stroke(); + } + + drawEdges(ctx) { + // Draw the trace of maps in reverse order to make sure the outgoing + // transitions of previous maps aren't drawn over. + const kMaxOutgoingEdges = 100; + let nofEdges = 0; + let stack = []; + let current = this.state.map; + while (current && nofEdges < kMaxOutgoingEdges) { + nofEdges += current.children.length; + stack.push(current); + current = current.parent(); + } + ctx.save(); + this.drawOutgoingEdges(ctx, this.state.map, 3); + ctx.restore(); + + let labelOffset = 15; + let xPrev = 0; + while (current = stack.pop()) { + if (current.edge) { + this.setEdgeStyle(current.edge, ctx); + let [xTo, yTo] = this.drawEdge(ctx, current.edge, true, labelOffset); + if (xTo == xPrev) { + labelOffset += 8; + } else { + labelOffset = 15 + } + xPrev = xTo; + } + this.markMap(ctx, current); + current = current.parent(); + ctx.save(); + // this.drawOutgoingEdges(ctx, current, 1); + ctx.restore(); + } + // Mark selected map + this.markSelectedMap(ctx, this.state.map); + } + + drawEdge(ctx, edge, showLabel=true, labelOffset=20) { + if (!edge.from || !edge.to) return [-1, -1]; + let [xFrom, yFrom] = edge.from.position(this.chunks); + let [xTo, yTo] = edge.to.position(this.chunks); + let sameChunk = xTo == xFrom; + if (sameChunk) labelOffset += 8; + + ctx.beginPath(); + ctx.moveTo(xFrom, yFrom); + let offsetX = 20; + let offsetY = 20; + let midX = xFrom + (xTo- xFrom) / 2; + let midY = (yFrom + yTo) / 2 - 100; + if (!sameChunk) { + ctx.quadraticCurveTo(midX, midY, xTo, yTo); + } else { + ctx.lineTo(xTo, yTo); + } + if (!showLabel) { + ctx.stroke(); + } else { + let centerX, centerY; + if (!sameChunk) { + centerX = (xFrom/2 + midX + xTo/2)/2; + centerY = (yFrom/2 + midY + yTo/2)/2; + } else { + centerX = xTo; + centerY = yTo; + } + ctx.moveTo(centerX, centerY); + ctx.lineTo(centerX + offsetX, centerY - labelOffset); + ctx.stroke(); + ctx.textAlign = "left"; + ctx.fillText(edge.toString(), centerX + offsetX + 2, centerY - labelOffset) + } + return [xTo, yTo]; + } + + drawOutgoingEdges(ctx, map, max=10, depth=0) { + if (!map) return; + if (depth >= max) return; + ctx.globalAlpha = 0.5 - depth * (0.3/max); + ctx.strokeStyle = "#666"; + + const limit = Math.min(map.children.length, 100) + for (let i = 0; i < limit; i++) { + let edge = map.children[i]; + this.drawEdge(ctx, edge, true); + this.drawOutgoingEdges(ctx, edge.to, max, depth+1); + } + } +} + + +class TransitionView { + constructor(state, node) { + this.state = state; + this.container = node; + this.currentNode = node; + this.currentMap = undefined; + } + + selectMap(map) { + this.currentMap = map; + this.state.map = map; + } + + showMap(map) { + if (this.currentMap === map) return; + this.currentMap = map; + this._showMaps([map]); + } + + showMaps(list, name) { + this.state.view.isLocked = true; + this._showMaps(list); + } + + _showMaps(list, name) { + // Hide the container to avoid any layouts. + this.container.style.display = "none"; + removeAllChildren(this.container); + list.forEach(map => this.addMapAndParentTransitions(map)); + this.container.style.display = "" + } + + addMapAndParentTransitions(map) { + if (map === void 0) return; + this.currentNode = this.container; + 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 = div("map"); + if (map.edge) node.classList.add(map.edge.getColor()); + node.map = map; + node.addEventListener("click", () => this.selectMap(map)); + if (map.children.length > 1) { + node.innerText = map.children.length; + let showSubtree = 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", map.edge.getColor()]; + let edge = div(classes); + let labelNode = div("transitionLabel"); + labelNode.innerText = map.edge.toString(); + edge.appendChild(labelNode); + return edge; + } + + addTransitionTo(map) { + // transition[ transitions[ transition[...], transition[...], ...]]; + + let transition = 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 = 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]); + } + } + } +} + +class StatsView { + constructor(state, node) { + this.state = state; + this.node = node; + } + get timeline() { return this.state.timeline } + get transitionView() { return this.state.view.transitionView; } + update() { + removeAllChildren(this.node); + this.updateGeneralStats(); + this.updateNamedTransitionsStats(); + } + updateGeneralStats() { + let pairs = [ + ["Maps", e => true], + ["Transitions", e => e.edge && e.edge.isTransition()], + ["Fast to Slow", e => e.edge && e.edge.isFastToSlow()], + ["Slow to Fast", e => e.edge && e.edge.isSlowToFast()], + ["Initial Map", e => e.edge && e.edge.isInitial()], + ["Replace Descriptors", e => e.edge && e.edge.isReplaceDescriptors()], + ["Copy as Prototype", e => e.edge && e.edge.isCopyAsPrototype()], + ["Optimize as Prototype", e => e.edge && e.edge.isOptimizeAsPrototype()], + ["Deprecated", e => e.isDeprecated()], + ]; + + let text = ""; + let tableNode = table(); + let name, filter; + let total = this.timeline.size(); + pairs.forEach(([name, filter]) => { + let row = tr(); + row.maps = this.timeline.filterUniqueTransitions(filter); + row.addEventListener("click", + e => this.transitionView.showMaps(e.target.parentNode.maps)); + row.appendChild(td(name)); + let count = this.timeline.count(filter); + row.appendChild(td(count)); + let percent = Math.round(count / total * 1000) / 10; + row.appendChild(td(percent + "%")); + tableNode.appendChild(row); + }); + this.node.appendChild(tableNode); + }; + updateNamedTransitionsStats() { + let tableNode = table("transitionTable"); + let nameMapPairs = Array.from(this.timeline.transitions.entries()); + nameMapPairs + .sort((a,b) => b[1].length - a[1].length) + .forEach(([name, maps]) => { + let row = tr(); + row.maps = maps; + row.addEventListener("click", + e => this.transitionView.showMaps( + e.target.parentNode.maps.map(map => map.to))); + row.appendChild(td(name)); + row.appendChild(td(maps.length)); + tableNode.appendChild(row); + }); + this.node.appendChild(tableNode); + } +} + +// ========================================================================= + +function transitionTypeToColor(type) { + switch(type) { + case "new": return "green"; + case "Normalize": return "violet"; + case "map=SlowToFast": return "orange"; + case "InitialMap": return "yellow"; + case "Transition": return "black"; + case "ReplaceDescriptors": return "red"; + } + return "black"; +} + +// ShadowDom elements ========================================================= +customElements.define('x-histogram', class extends HTMLElement { + constructor() { + super(); + let shadowRoot = this.attachShadow({mode: 'open'}); + const t = document.querySelector('#x-histogram-template'); + const instance = t.content.cloneNode(true); + shadowRoot.appendChild(instance); + this._histogram = undefined; + this.mouseX = 0; + this.mouseY = 0; + this.canvas.addEventListener('mousemove', event => this.handleCanvasMove(event)); + } + setBoolAttribute(name, value) { + if (value) { + this.setAttribute(name, ""); + } else { + this.deleteAttribute(name); + } + } + static get observedAttributes() { + return ['title', 'xlog', 'ylog', 'xlabel', 'ylabel']; + } + $(query) { return this.shadowRoot.querySelector(query) } + get h1() { return this.$("h2") } + get canvas() { return this.$("canvas") } + get xLabelDiv() { return this.$("#xLabel") } + get yLabelDiv() { return this.$("#yLabel") } + + get histogram() { + return this._histogram; + } + set histogram(array) { + this._histogram = array; + if (this._histogram) { + this.yMax = this._histogram.max(each => each.length); + this.xMax = this._histogram.length; + } + this.draw(); + } + + get title() { return this.getAttribute("title") } + set title(string) { this.setAttribute("title", string) } + get xLabel() { return this.getAttribute("xlabel") } + set xLabel(string) { this.setAttribute("xlabel", string)} + get yLabel() { return this.getAttribute("ylabel") } + set yLabel(string) { this.setAttribute("ylabel", string)} + get xLog() { return this.hasAttribute("xlog") } + set xLog(value) { this.setBoolAttribute("xlog", value) } + get yLog() { return this.hasAttribute("ylog") } + set yLog(value) { this.setBoolAttribute("ylog", value) } + + attributeChangedCallback(name, oldValue, newValue) { + if (name == "title") { + this.h1.innerText = newValue; + return; + } + if (name == "ylabel") { + this.yLabelDiv.innerText = newValue; + return; + } + if (name == "xlabel") { + this.xLabelDiv.innerText = newValue; + return; + } + this.draw(); + } + + handleCanvasMove(event) { + this.mouseX = event.offsetX; + this.mouseY = event.offsetY; + this.draw(); + } + xPosition(i) { + let x = i; + if (this.xLog) x = Math.log(x); + return x / this.xMax * this.canvas.width; + } + yPosition(i) { + let bucketLength = this.histogram[i].length; + if (this.yLog) { + return (1 - Math.log(bucketLength) / Math.log(this.yMax)) * this.drawHeight + 10; + } else { + return (1 - bucketLength / this.yMax) * this.drawHeight + 10; + } + } + + get drawHeight() { return this.canvas.height - 10 } + + draw() { + if (!this.histogram) return; + let width = this.canvas.width; + let height = this.drawHeight; + let ctx = this.canvas.getContext("2d"); + if (this.xLog) yMax = Math.log(yMax); + let xMax = this.histogram.length; + if (this.yLog) xMax = Math.log(xMax); + ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + ctx.beginPath(); + ctx.moveTo(0, height); + for (let i = 0; i < this.histogram.length; i++) { + ctx.lineTo(this.xPosition(i), this.yPosition(i)); + } + ctx.lineTo(width, height); + ctx.closePath; + ctx.stroke(); + ctx.fill(); + if (!this.mouseX) return; + ctx.beginPath(); + let index = Math.round(this.mouseX); + let yBucket = this.histogram[index]; + let y = this.yPosition(index); + if (this.yLog) y = Math.log(y); + ctx.moveTo(0, y); + ctx.lineTo(width-40, y); + ctx.moveTo(this.mouseX, 0); + ctx.lineTo(this.mouseX, height); + ctx.stroke(); + ctx.textAlign = "left"; + ctx.fillText(yBucket.length, width-30, y); + } +}); + +</script> +</head> +<template id="x-histogram-template"> + <style> + #yLabel { + transform: rotate(90deg); + } + canvas, #yLabel, #info { float: left; } + #xLabel { clear: both } + </style> + <h2></h2> + <div id="yLabel"></div> + <canvas height=50></canvas> + <div id="info"> + </div> + <div id="xLabel"></div> +</template> + +<body onload="handleBodyLoad(event)" onkeypress="handleKeyDown(event)"> + <h2>Data</h2> + <section> + <form name="fileForm"> + <p> + <input id="uploadInput" type="file" name="files"> + </p> + </form> + </section> + + <h2>Stats</h2> + <section id="stats"></section> + + <h2>Timeline</h2> + <div id="timeline"> + <div id=timelineChunks></div> + <canvas id="timelineCanvas" ></canvas> + </div> + <div id="timelineOverview" + onmousemove="handleTimelineIndicatorMove(event)" > + <div id="timelineOverviewIndicator"> + <div class="leftMask"></div> + <div class="rightMask"></div> + </div> + </div> + + <h2>Transitions</h2> + <section id="transitionView"></section> + <br/> + + <h2>Selected Map</h2> + <section id="mapDetails"></section> + + <x-histogram id="mapsDepthHistogram" + title="Maps Depth" xlabel="depth" ylabel="nof"></x-histogram> + <x-histogram id="mapsFanOutHistogram" xlabel="fan-out" + title="Maps Fan-out" ylabel="nof"></x-histogram> + + <div id="tooltip"> + <div id="tooltipContents"></div> + </div> +</body> +</html> |