From 1837c6f983e994dd64f6e3834f3e207603463932 Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Mon, 14 Jun 2021 14:44:00 +0200 Subject: [PATCH] [tools][system-analyzer] improve logEntry hit testing performance fixing flame graph rendering adding some comments adding flamechart highlighting Bug: v8:10644, v8:11835 Change-Id: I2ab2f63b9e8339c6c25bb7023772fc97dfc56c2e Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2959615 Commit-Queue: Camillo Bruni Reviewed-by: Patrick Thier Cr-Commit-Position: refs/heads/master@{#75130} --- tools/profile.mjs | 22 ++++ tools/system-analyzer/index.css | 3 +- tools/system-analyzer/log/tick.mjs | 28 +++-- tools/system-analyzer/view/helper.mjs | 2 +- .../view/log-file-reader-template.html | 6 +- .../view/timeline-panel-template.html | 18 ++- tools/system-analyzer/view/timeline-panel.mjs | 2 + .../view/timeline/timeline-track-base.mjs | 118 ++++++++++++------ .../timeline/timeline-track-template.html | 29 +++-- .../view/timeline/timeline-track-tick.mjs | 117 ++++++++++++----- .../view/tool-tip-template.html | 45 +++++-- tools/system-analyzer/view/tool-tip.mjs | 4 +- 12 files changed, 293 insertions(+), 101 deletions(-) diff --git a/tools/profile.mjs b/tools/profile.mjs index 5ca4c91cad..571517391b 100644 --- a/tools/profile.mjs +++ b/tools/profile.mjs @@ -267,6 +267,28 @@ export class Profile { throw new Error(`unknown code state: ${state}`); } + static vmStateString(state) { + switch (state) { + case this.VMState.JS: + return 'JS'; + case this.VMState.GC: + return 'GC'; + case this.VMState.PARSER: + return 'Parse'; + case this.VMState.BYTECODE_COMPILER: + return 'Compile Bytecode'; + case this.VMState.COMPILER: + return 'Compile'; + case this.VMState.OTHER: + return 'Other'; + case this.VMState.EXTERNAL: + return 'External'; + case this.VMState.IDLE: + return 'Idle'; + } + return 'unknown'; + } + /** * Called whenever the specified operation has failed finding a function * containing the specified address. Should be overriden by subclasses. diff --git a/tools/system-analyzer/index.css b/tools/system-analyzer/index.css index c387884ecb..08c09d8102 100644 --- a/tools/system-analyzer/index.css +++ b/tools/system-analyzer/index.css @@ -1,6 +1,7 @@ :root { --background-color: #000000; - --surface-color: #121212; + --surface-color-rgb: 18, 18, 18; + --surface-color: rgb(var(--surface-color-rgb)); --primary-color: #bb86fc; --secondary-color: #03dac6; --on-surface-color: #ffffff; diff --git a/tools/system-analyzer/log/tick.mjs b/tools/system-analyzer/log/tick.mjs index 9336d95564..64dbeb3780 100644 --- a/tools/system-analyzer/log/tick.mjs +++ b/tools/system-analyzer/log/tick.mjs @@ -7,17 +7,31 @@ import {LogEntry} from './log.mjs'; export class TickLogEntry extends LogEntry { constructor(time, vmState, processedStack) { - super(TickLogEntry.extractType(processedStack), time); + super(TickLogEntry.extractType(vmState, processedStack), time); this.state = vmState; this.stack = processedStack; } - static extractType(processedStack) { - if (processedStack.length == 0) return 'idle'; - const topOfStack = processedStack[processedStack.length - 1]; - if (topOfStack?.state) { - return Profile.getKindFromState(topOfStack.state); + static extractType(vmState, processedStack) { + if (processedStack.length == 0 || vmState == Profile.VMState.IDLE) { + return 'Idle'; } - return 'native'; + const topOfStack = processedStack[0]; + if (typeof topOfStack === 'number') { + // TODO(cbruni): Handle VmStack and native ticks better. + return 'Other'; + } + if (vmState != Profile.VMState.JS) { + topOfStack.vmState = vmState; + } + return this.extractCodeEntryType(topOfStack); + } + + static extractCodeEntryType(entry) { + if (entry?.state !== undefined) { + return 'JS ' + Profile.getKindFromState(entry.state); + } + if (entry?.vmState) return Profile.vmStateString(entry.vmState); + return 'Other'; } } \ No newline at end of file diff --git a/tools/system-analyzer/view/helper.mjs b/tools/system-analyzer/view/helper.mjs index ae8864c12c..1dec0c4ed0 100644 --- a/tools/system-analyzer/view/helper.mjs +++ b/tools/system-analyzer/view/helper.mjs @@ -83,7 +83,7 @@ export class CSSColor { return this.list[index % this.list.length]; } - static darken(hexColorString, amount = -40) { + static darken(hexColorString, amount = -50) { if (hexColorString[0] !== '#') { throw new Error(`Unsupported color: ${hexColorString}`); } diff --git a/tools/system-analyzer/view/log-file-reader-template.html b/tools/system-analyzer/view/log-file-reader-template.html index 478e08129c..b8e1781d15 100644 --- a/tools/system-analyzer/view/log-file-reader-template.html +++ b/tools/system-analyzer/view/log-file-reader-template.html @@ -14,15 +14,14 @@ found in the LICENSE file. --> transition: all 0.5s ease-in-out; background-color: var(--surface-color); } - + #fileReader:hover { background-color: var(--primary-color); color: var(--on-primary-color); } .done #fileReader{ - height: 20px; - line-height: 20px; + display: none; } .fail #fileReader { @@ -51,6 +50,7 @@ found in the LICENSE file. --> height: 100%; background-color: var(--file-reader-background-color); } + #spinner { position: absolute; width: 100px; diff --git a/tools/system-analyzer/view/timeline-panel-template.html b/tools/system-analyzer/view/timeline-panel-template.html index da0ce26380..6367e7e1ab 100644 --- a/tools/system-analyzer/view/timeline-panel-template.html +++ b/tools/system-analyzer/view/timeline-panel-template.html @@ -18,10 +18,26 @@ found in the LICENSE file. --> width: 30px; background-color: var(--border-color); } + .titleButtons { + float: right; + } + .titleButtons button { + border-radius: 20px; + width: 20px; + height: 20px; + font-weight: bold; + line-height: 11px; + }
-

Timeline Panel

+

+ Timeline Panel +
+ + +
+

diff --git a/tools/system-analyzer/view/timeline-panel.mjs b/tools/system-analyzer/view/timeline-panel.mjs index 1121665798..38badb4742 100644 --- a/tools/system-analyzer/view/timeline-panel.mjs +++ b/tools/system-analyzer/view/timeline-panel.mjs @@ -18,6 +18,8 @@ DOM.defineCustomElement( this.addEventListener( SynchronizeSelectionEvent.name, e => this.handleSelectionSyncronization(e)); + this.$('#zoomIn').onclick = () => this.nofChunks *= 1.5; + this.$('#zoomOut').onclick = () => this.nofChunks /= 1.5; } set nofChunks(count) { diff --git a/tools/system-analyzer/view/timeline/timeline-track-base.mjs b/tools/system-analyzer/view/timeline/timeline-track-base.mjs index d3509c7bc5..90b7774ed9 100644 --- a/tools/system-analyzer/view/timeline/timeline-track-base.mjs +++ b/tools/system-analyzer/view/timeline/timeline-track-base.mjs @@ -7,6 +7,8 @@ import {kChunkHeight, kChunkWidth} from '../../log/map.mjs'; import {SelectionEvent, SelectTimeEvent, SynchronizeSelectionEvent, ToolTipEvent,} from '../events.mjs'; import {CSSColor, DOM, SVG, V8CustomElement} from '../helper.mjs'; +export const kTimelineHeight = 200; + export class TimelineTrackBase extends V8CustomElement { _timeline; _nofChunks = 500; @@ -17,6 +19,9 @@ export class TimelineTrackBase extends V8CustomElement { _legend; _lastContentWidth = 0; + _cachedTimelineBoundingClientRect; + _cachedTimelineScrollLeft; + constructor(templateText) { super(templateText); this._selectionHandler = new SelectionHandler(this); @@ -24,9 +29,10 @@ export class TimelineTrackBase extends V8CustomElement { this._legend.onFilter = (type) => this._handleFilterTimeline(); this.timelineNode.addEventListener( 'scroll', e => this._handleTimelineScroll(e)); - this.timelineNode.onclick = (e) => this._handleClick(e); - this.timelineNode.ondblclick = (e) => this._handleDoubleClick(e); - this.timelineChunks.onmousemove = (e) => this._handleMouseMove(e); + this.hitPanelNode.onclick = this._handleClick.bind(this); + this.hitPanelNode.ondblclick = this._handleDoubleClick.bind(this); + this.hitPanelNode.onmousemove = this._handleMouseMove.bind(this); + window.addEventListener('resize', () => this._resetCachedDimensions()); this.isLocked = false; } @@ -61,10 +67,30 @@ export class TimelineTrackBase extends V8CustomElement { this._legend.update(); } - // Maps the clicked x position to the x position on timeline canvas + get _timelineBoundingClientRect() { + if (this._cachedTimelineBoundingClientRect === undefined) { + this._cachedTimelineBoundingClientRect = + this.timelineNode.getBoundingClientRect(); + } + return this._cachedTimelineBoundingClientRect; + } + + get _timelineScrollLeft() { + if (this._cachedTimelineScrollLeft === undefined) { + this._cachedTimelineScrollLeft = this.timelineNode.scrollLeft; + } + return this._cachedTimelineScrollLeft; + } + + _resetCachedDimensions() { + this._cachedTimelineBoundingClientRect = undefined; + this._cachedTimelineScrollLeft = undefined; + } + + // Maps the clicked x position to the x position on timeline positionOnTimeline(pagePosX) { - let rect = this.timelineNode.getBoundingClientRect(); - let posClickedX = pagePosX - rect.left + this.timelineNode.scrollLeft; + let rect = this._timelineBoundingClientRect; + let posClickedX = pagePosX - rect.left + this._timelineScrollLeft; return posClickedX; } @@ -74,7 +100,7 @@ export class TimelineTrackBase extends V8CustomElement { relativePositionToTime(timelineRelativeX) { const timelineAbsoluteX = timelineRelativeX + this._timeStartPixelOffset; - return timelineAbsoluteX / this._timeToPixel; + return (timelineAbsoluteX / this._timeToPixel) | 0; } timeToPosition(time) { @@ -83,8 +109,8 @@ export class TimelineTrackBase extends V8CustomElement { return relativePosX; } - get timelineCanvas() { - return this.$('#timelineCanvas'); + get toolTipTargetNode() { + return this.$('#toolTipTarget'); } get timelineChunks() { @@ -108,6 +134,10 @@ export class TimelineTrackBase extends V8CustomElement { return this._timelineNode; } + get hitPanelNode() { + return this.$('#hitPanel'); + } + get timelineAnnotationsNode() { return this.$('#timelineAnnotations'); } @@ -150,6 +180,7 @@ export class TimelineTrackBase extends V8CustomElement { set scrollLeft(offset) { this.timelineNode.scrollLeft = offset; + this._cachedTimelineScrollLeft = offset; } handleEntryTypeDoubleClick(e) { @@ -158,19 +189,20 @@ export class TimelineTrackBase extends V8CustomElement { timelineIndicatorMove(offset) { this.timelineNode.scrollLeft += offset; + this._cachedTimelineScrollLeft = undefined; } _handleTimelineScroll(e) { - let horizontal = e.currentTarget.scrollLeft; + let scrollLeft = e.currentTarget.scrollLeft; + this._cachedTimelineScrollLeft = scrollLeft; this.dispatchEvent(new CustomEvent( - 'scrolltrack', {bubbles: true, composed: true, detail: horizontal})); + 'scrolltrack', {bubbles: true, composed: true, detail: scrollLeft})); } _updateDimensions() { - const centerOffset = this.timelineNode.getBoundingClientRect().width / 2; - const time = this.relativePositionToTime( - this.timelineNode.scrollLeft + centerOffset); - + const centerOffset = this._timelineBoundingClientRect.width / 2; + const time = + this.relativePositionToTime(this._timelineScrollLeft + centerOffset); const start = this._timeline.startTime; const width = this._nofChunks * kChunkWidth; this._lastContentWidth = parseInt(this.timelineMarkersNode.style.width); @@ -179,10 +211,12 @@ export class TimelineTrackBase extends V8CustomElement { this.timelineChunks.style.width = `${width}px`; this.timelineMarkersNode.style.width = `${width}px`; this.timelineAnnotationsNode.style.width = `${width}px`; + this.hitPanelNode.style.width = `${width}px`; this._drawMarkers(); this._selectionHandler.update(); this._scaleContent(width); - this.timelineNode.scrollLeft = this.timeToPosition(time) - centerOffset; + this._cachedTimelineScrollLeft = this.timelineNode.scrollLeft = + this.timeToPosition(time) - centerOffset; } _scaleContent(currentWidth) { @@ -194,12 +228,15 @@ export class TimelineTrackBase extends V8CustomElement { _adjustHeight(height) { this.querySelectorAll('.dataSized') .forEach(node => {node.style.height = height + 'px'}); + this.timelineNode.style.overflowY = + (height > kTimelineHeight) ? 'scroll' : 'hidden'; } _update() { this._legend.update(); this._drawContent(); this._drawAnnotations(this.selectedEntry); + this._resetCachedDimensions(); } async _drawContent() { @@ -230,7 +267,7 @@ export class TimelineTrackBase extends V8CustomElement { const groups = chunk.getBreakdown(event => event.type); let buffer = ''; const kHeight = chunk.height; - let lastHeight = 200; + let lastHeight = kTimelineHeight; for (let i = 0; i < groups.length; i++) { const group = groups[i]; if (group.count == 0) break; @@ -281,7 +318,8 @@ export class TimelineTrackBase extends V8CustomElement { _handleDoubleClick(event) { this._selectionHandler.clearSelection(); - const chunk = event.target.chunk; + const time = this.positionToTime(event.pageX); + const chunk = this._getChunkForEvent(event) if (!chunk) return; event.stopImmediatePropagation(); this.dispatchEvent(new SelectTimeEvent(chunk.start, chunk.end)); @@ -291,28 +329,33 @@ export class TimelineTrackBase extends V8CustomElement { _handleMouseMove(event) { if (this.isLocked) return false; if (this._selectionHandler.isSelecting) return false; - const {logEntry, target} = this._getEntryForEvent(event); + const logEntry = this._getEntryForEvent(event); if (!logEntry) return false; - this.dispatchEvent(new ToolTipEvent(logEntry, target)); + this.dispatchEvent(new ToolTipEvent(logEntry, this.toolTipTargetNode)); const time = this.positionToTime(event.pageX); this._drawAnnotations(logEntry, time); } - _getEntryForEvent(event) { - let target = event.target; - let logEntry = false; - if (target === this.timelineChunks) return {logEntry, target}; - target = target.parentNode; + _getChunkForEvent(event) { const time = this.positionToTime(event.pageX); - const chunkIndex = (time - this._timeline.startTime) / - this._timeline.duration() * this._nofChunks; - const chunk = this.chunks[chunkIndex | 0]; - if (!chunk?.isEmpty()) { - const relativeIndex = - Math.round((200 - event.layerY) / chunk.height * (chunk.size() - 1)); - if (relativeIndex < chunk.size()) logEntry = chunk.at(relativeIndex); - } - return {logEntry, target}; + const chunkIndex = ((time - this._timeline.startTime) / + this._timeline.duration() * this._nofChunks) | + 0; + return this.chunks[chunkIndex]; + } + + _getEntryForEvent(event) { + const chunk = this._getChunkForEvent(event); + if (chunk?.isEmpty() ?? true) return false; + const relativeIndex = Math.round( + (kTimelineHeight - event.layerY) / chunk.height * (chunk.size() - 1)); + if (relativeIndex > chunk.size()) return false; + const logEntry = chunk.at(relativeIndex); + const style = this.toolTipTargetNode.style; + style.left = `${chunk.index * kChunkWidth}px`; + style.top = `${kTimelineHeight - chunk.height}px`; + style.height = `${chunk.height}px`; + return logEntry; } }; @@ -475,7 +518,12 @@ class Legend { } colorForType(type) { - return this._colors.get(type); + let color = this._colors.get(type); + if (color === undefined) { + color = CSSColor.at(this._colors.size); + this._colors.set(type, color); + } + return color; } filter(logEntry) { diff --git a/tools/system-analyzer/view/timeline/timeline-track-template.html b/tools/system-analyzer/view/timeline/timeline-track-template.html index 110e7050ca..84c38fef3d 100644 --- a/tools/system-analyzer/view/timeline/timeline-track-template.html +++ b/tools/system-analyzer/view/timeline/timeline-track-template.html @@ -29,15 +29,20 @@ found in the LICENSE file. --> } #timelineSamples, #timelineChunks, - #timelineMarkers, #timelineAnnotations { + #timelineMarkers, #timelineAnnotations, #hitPanel { top: 0px; position: absolute; margin-right: 100px; } - #timelineMarkers, #timelineAnnotations { + #timelineMarkers, #timelineAnnotations, + .noPointerEvents, .noPointerEvents * { pointer-events: none; } + #toolTipTarget { + position: absolute; + } + .title { position: relative; float: left; @@ -105,6 +110,9 @@ found in the LICENSE file. --> #selection { display: none; + top: 0px; + left: 0px; + position: absolute; } #rightHandle, @@ -121,6 +129,7 @@ found in the LICENSE file. --> } #rightHandle { border-right: 1px solid var(--on-surface-color); + margin-left: -5px; } #selectionBackground { @@ -147,7 +156,9 @@ found in the LICENSE file. --> .legend { flex: initial; } - + +