Commit 1837c6f9 authored by Camillo Bruni's avatar Camillo Bruni Committed by V8 LUCI CQ

[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 <cbruni@chromium.org>
Reviewed-by: 's avatarPatrick Thier <pthier@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75130}
parent 1c249d33
...@@ -267,6 +267,28 @@ export class Profile { ...@@ -267,6 +267,28 @@ export class Profile {
throw new Error(`unknown code state: ${state}`); 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 * Called whenever the specified operation has failed finding a function
* containing the specified address. Should be overriden by subclasses. * containing the specified address. Should be overriden by subclasses.
......
:root { :root {
--background-color: #000000; --background-color: #000000;
--surface-color: #121212; --surface-color-rgb: 18, 18, 18;
--surface-color: rgb(var(--surface-color-rgb));
--primary-color: #bb86fc; --primary-color: #bb86fc;
--secondary-color: #03dac6; --secondary-color: #03dac6;
--on-surface-color: #ffffff; --on-surface-color: #ffffff;
......
...@@ -7,17 +7,31 @@ import {LogEntry} from './log.mjs'; ...@@ -7,17 +7,31 @@ import {LogEntry} from './log.mjs';
export class TickLogEntry extends LogEntry { export class TickLogEntry extends LogEntry {
constructor(time, vmState, processedStack) { constructor(time, vmState, processedStack) {
super(TickLogEntry.extractType(processedStack), time); super(TickLogEntry.extractType(vmState, processedStack), time);
this.state = vmState; this.state = vmState;
this.stack = processedStack; this.stack = processedStack;
} }
static extractType(processedStack) { static extractType(vmState, processedStack) {
if (processedStack.length == 0) return 'idle'; if (processedStack.length == 0 || vmState == Profile.VMState.IDLE) {
const topOfStack = processedStack[processedStack.length - 1]; return 'Idle';
if (topOfStack?.state) {
return Profile.getKindFromState(topOfStack.state);
} }
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
...@@ -83,7 +83,7 @@ export class CSSColor { ...@@ -83,7 +83,7 @@ export class CSSColor {
return this.list[index % this.list.length]; return this.list[index % this.list.length];
} }
static darken(hexColorString, amount = -40) { static darken(hexColorString, amount = -50) {
if (hexColorString[0] !== '#') { if (hexColorString[0] !== '#') {
throw new Error(`Unsupported color: ${hexColorString}`); throw new Error(`Unsupported color: ${hexColorString}`);
} }
......
...@@ -14,15 +14,14 @@ found in the LICENSE file. --> ...@@ -14,15 +14,14 @@ found in the LICENSE file. -->
transition: all 0.5s ease-in-out; transition: all 0.5s ease-in-out;
background-color: var(--surface-color); background-color: var(--surface-color);
} }
#fileReader:hover { #fileReader:hover {
background-color: var(--primary-color); background-color: var(--primary-color);
color: var(--on-primary-color); color: var(--on-primary-color);
} }
.done #fileReader{ .done #fileReader{
height: 20px; display: none;
line-height: 20px;
} }
.fail #fileReader { .fail #fileReader {
...@@ -51,6 +50,7 @@ found in the LICENSE file. --> ...@@ -51,6 +50,7 @@ found in the LICENSE file. -->
height: 100%; height: 100%;
background-color: var(--file-reader-background-color); background-color: var(--file-reader-background-color);
} }
#spinner { #spinner {
position: absolute; position: absolute;
width: 100px; width: 100px;
......
...@@ -18,10 +18,26 @@ found in the LICENSE file. --> ...@@ -18,10 +18,26 @@ found in the LICENSE file. -->
width: 30px; width: 30px;
background-color: var(--border-color); background-color: var(--border-color);
} }
.titleButtons {
float: right;
}
.titleButtons button {
border-radius: 20px;
width: 20px;
height: 20px;
font-weight: bold;
line-height: 11px;
}
</style> </style>
</head> </head>
<div class="panel"> <div class="panel">
<h2>Timeline Panel</h2> <h2>
Timeline Panel
<div class="titleButtons">
<button id="zoomIn" title="Increase resolution">+</button>
<button id="zoomOut" title="Decrease resolution"></button>
</div>
</h2>
<div class="titleBackground"></div> <div class="titleBackground"></div>
<div> <div>
<slot></slot> <slot></slot>
......
...@@ -18,6 +18,8 @@ DOM.defineCustomElement( ...@@ -18,6 +18,8 @@ DOM.defineCustomElement(
this.addEventListener( this.addEventListener(
SynchronizeSelectionEvent.name, SynchronizeSelectionEvent.name,
e => this.handleSelectionSyncronization(e)); e => this.handleSelectionSyncronization(e));
this.$('#zoomIn').onclick = () => this.nofChunks *= 1.5;
this.$('#zoomOut').onclick = () => this.nofChunks /= 1.5;
} }
set nofChunks(count) { set nofChunks(count) {
......
...@@ -7,6 +7,8 @@ import {kChunkHeight, kChunkWidth} from '../../log/map.mjs'; ...@@ -7,6 +7,8 @@ import {kChunkHeight, kChunkWidth} from '../../log/map.mjs';
import {SelectionEvent, SelectTimeEvent, SynchronizeSelectionEvent, ToolTipEvent,} from '../events.mjs'; import {SelectionEvent, SelectTimeEvent, SynchronizeSelectionEvent, ToolTipEvent,} from '../events.mjs';
import {CSSColor, DOM, SVG, V8CustomElement} from '../helper.mjs'; import {CSSColor, DOM, SVG, V8CustomElement} from '../helper.mjs';
export const kTimelineHeight = 200;
export class TimelineTrackBase extends V8CustomElement { export class TimelineTrackBase extends V8CustomElement {
_timeline; _timeline;
_nofChunks = 500; _nofChunks = 500;
...@@ -17,6 +19,9 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -17,6 +19,9 @@ export class TimelineTrackBase extends V8CustomElement {
_legend; _legend;
_lastContentWidth = 0; _lastContentWidth = 0;
_cachedTimelineBoundingClientRect;
_cachedTimelineScrollLeft;
constructor(templateText) { constructor(templateText) {
super(templateText); super(templateText);
this._selectionHandler = new SelectionHandler(this); this._selectionHandler = new SelectionHandler(this);
...@@ -24,9 +29,10 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -24,9 +29,10 @@ export class TimelineTrackBase extends V8CustomElement {
this._legend.onFilter = (type) => this._handleFilterTimeline(); this._legend.onFilter = (type) => this._handleFilterTimeline();
this.timelineNode.addEventListener( this.timelineNode.addEventListener(
'scroll', e => this._handleTimelineScroll(e)); 'scroll', e => this._handleTimelineScroll(e));
this.timelineNode.onclick = (e) => this._handleClick(e); this.hitPanelNode.onclick = this._handleClick.bind(this);
this.timelineNode.ondblclick = (e) => this._handleDoubleClick(e); this.hitPanelNode.ondblclick = this._handleDoubleClick.bind(this);
this.timelineChunks.onmousemove = (e) => this._handleMouseMove(e); this.hitPanelNode.onmousemove = this._handleMouseMove.bind(this);
window.addEventListener('resize', () => this._resetCachedDimensions());
this.isLocked = false; this.isLocked = false;
} }
...@@ -61,10 +67,30 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -61,10 +67,30 @@ export class TimelineTrackBase extends V8CustomElement {
this._legend.update(); 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) { positionOnTimeline(pagePosX) {
let rect = this.timelineNode.getBoundingClientRect(); let rect = this._timelineBoundingClientRect;
let posClickedX = pagePosX - rect.left + this.timelineNode.scrollLeft; let posClickedX = pagePosX - rect.left + this._timelineScrollLeft;
return posClickedX; return posClickedX;
} }
...@@ -74,7 +100,7 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -74,7 +100,7 @@ export class TimelineTrackBase extends V8CustomElement {
relativePositionToTime(timelineRelativeX) { relativePositionToTime(timelineRelativeX) {
const timelineAbsoluteX = timelineRelativeX + this._timeStartPixelOffset; const timelineAbsoluteX = timelineRelativeX + this._timeStartPixelOffset;
return timelineAbsoluteX / this._timeToPixel; return (timelineAbsoluteX / this._timeToPixel) | 0;
} }
timeToPosition(time) { timeToPosition(time) {
...@@ -83,8 +109,8 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -83,8 +109,8 @@ export class TimelineTrackBase extends V8CustomElement {
return relativePosX; return relativePosX;
} }
get timelineCanvas() { get toolTipTargetNode() {
return this.$('#timelineCanvas'); return this.$('#toolTipTarget');
} }
get timelineChunks() { get timelineChunks() {
...@@ -108,6 +134,10 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -108,6 +134,10 @@ export class TimelineTrackBase extends V8CustomElement {
return this._timelineNode; return this._timelineNode;
} }
get hitPanelNode() {
return this.$('#hitPanel');
}
get timelineAnnotationsNode() { get timelineAnnotationsNode() {
return this.$('#timelineAnnotations'); return this.$('#timelineAnnotations');
} }
...@@ -150,6 +180,7 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -150,6 +180,7 @@ export class TimelineTrackBase extends V8CustomElement {
set scrollLeft(offset) { set scrollLeft(offset) {
this.timelineNode.scrollLeft = offset; this.timelineNode.scrollLeft = offset;
this._cachedTimelineScrollLeft = offset;
} }
handleEntryTypeDoubleClick(e) { handleEntryTypeDoubleClick(e) {
...@@ -158,19 +189,20 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -158,19 +189,20 @@ export class TimelineTrackBase extends V8CustomElement {
timelineIndicatorMove(offset) { timelineIndicatorMove(offset) {
this.timelineNode.scrollLeft += offset; this.timelineNode.scrollLeft += offset;
this._cachedTimelineScrollLeft = undefined;
} }
_handleTimelineScroll(e) { _handleTimelineScroll(e) {
let horizontal = e.currentTarget.scrollLeft; let scrollLeft = e.currentTarget.scrollLeft;
this._cachedTimelineScrollLeft = scrollLeft;
this.dispatchEvent(new CustomEvent( this.dispatchEvent(new CustomEvent(
'scrolltrack', {bubbles: true, composed: true, detail: horizontal})); 'scrolltrack', {bubbles: true, composed: true, detail: scrollLeft}));
} }
_updateDimensions() { _updateDimensions() {
const centerOffset = this.timelineNode.getBoundingClientRect().width / 2; const centerOffset = this._timelineBoundingClientRect.width / 2;
const time = this.relativePositionToTime( const time =
this.timelineNode.scrollLeft + centerOffset); this.relativePositionToTime(this._timelineScrollLeft + centerOffset);
const start = this._timeline.startTime; const start = this._timeline.startTime;
const width = this._nofChunks * kChunkWidth; const width = this._nofChunks * kChunkWidth;
this._lastContentWidth = parseInt(this.timelineMarkersNode.style.width); this._lastContentWidth = parseInt(this.timelineMarkersNode.style.width);
...@@ -179,10 +211,12 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -179,10 +211,12 @@ export class TimelineTrackBase extends V8CustomElement {
this.timelineChunks.style.width = `${width}px`; this.timelineChunks.style.width = `${width}px`;
this.timelineMarkersNode.style.width = `${width}px`; this.timelineMarkersNode.style.width = `${width}px`;
this.timelineAnnotationsNode.style.width = `${width}px`; this.timelineAnnotationsNode.style.width = `${width}px`;
this.hitPanelNode.style.width = `${width}px`;
this._drawMarkers(); this._drawMarkers();
this._selectionHandler.update(); this._selectionHandler.update();
this._scaleContent(width); this._scaleContent(width);
this.timelineNode.scrollLeft = this.timeToPosition(time) - centerOffset; this._cachedTimelineScrollLeft = this.timelineNode.scrollLeft =
this.timeToPosition(time) - centerOffset;
} }
_scaleContent(currentWidth) { _scaleContent(currentWidth) {
...@@ -194,12 +228,15 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -194,12 +228,15 @@ export class TimelineTrackBase extends V8CustomElement {
_adjustHeight(height) { _adjustHeight(height) {
this.querySelectorAll('.dataSized') this.querySelectorAll('.dataSized')
.forEach(node => {node.style.height = height + 'px'}); .forEach(node => {node.style.height = height + 'px'});
this.timelineNode.style.overflowY =
(height > kTimelineHeight) ? 'scroll' : 'hidden';
} }
_update() { _update() {
this._legend.update(); this._legend.update();
this._drawContent(); this._drawContent();
this._drawAnnotations(this.selectedEntry); this._drawAnnotations(this.selectedEntry);
this._resetCachedDimensions();
} }
async _drawContent() { async _drawContent() {
...@@ -230,7 +267,7 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -230,7 +267,7 @@ export class TimelineTrackBase extends V8CustomElement {
const groups = chunk.getBreakdown(event => event.type); const groups = chunk.getBreakdown(event => event.type);
let buffer = ''; let buffer = '';
const kHeight = chunk.height; const kHeight = chunk.height;
let lastHeight = 200; let lastHeight = kTimelineHeight;
for (let i = 0; i < groups.length; i++) { for (let i = 0; i < groups.length; i++) {
const group = groups[i]; const group = groups[i];
if (group.count == 0) break; if (group.count == 0) break;
...@@ -281,7 +318,8 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -281,7 +318,8 @@ export class TimelineTrackBase extends V8CustomElement {
_handleDoubleClick(event) { _handleDoubleClick(event) {
this._selectionHandler.clearSelection(); this._selectionHandler.clearSelection();
const chunk = event.target.chunk; const time = this.positionToTime(event.pageX);
const chunk = this._getChunkForEvent(event)
if (!chunk) return; if (!chunk) return;
event.stopImmediatePropagation(); event.stopImmediatePropagation();
this.dispatchEvent(new SelectTimeEvent(chunk.start, chunk.end)); this.dispatchEvent(new SelectTimeEvent(chunk.start, chunk.end));
...@@ -291,28 +329,33 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -291,28 +329,33 @@ export class TimelineTrackBase extends V8CustomElement {
_handleMouseMove(event) { _handleMouseMove(event) {
if (this.isLocked) return false; if (this.isLocked) return false;
if (this._selectionHandler.isSelecting) return false; if (this._selectionHandler.isSelecting) return false;
const {logEntry, target} = this._getEntryForEvent(event); const logEntry = this._getEntryForEvent(event);
if (!logEntry) return false; if (!logEntry) return false;
this.dispatchEvent(new ToolTipEvent(logEntry, target)); this.dispatchEvent(new ToolTipEvent(logEntry, this.toolTipTargetNode));
const time = this.positionToTime(event.pageX); const time = this.positionToTime(event.pageX);
this._drawAnnotations(logEntry, time); this._drawAnnotations(logEntry, time);
} }
_getEntryForEvent(event) { _getChunkForEvent(event) {
let target = event.target;
let logEntry = false;
if (target === this.timelineChunks) return {logEntry, target};
target = target.parentNode;
const time = this.positionToTime(event.pageX); const time = this.positionToTime(event.pageX);
const chunkIndex = (time - this._timeline.startTime) / const chunkIndex = ((time - this._timeline.startTime) /
this._timeline.duration() * this._nofChunks; this._timeline.duration() * this._nofChunks) |
const chunk = this.chunks[chunkIndex | 0]; 0;
if (!chunk?.isEmpty()) { return this.chunks[chunkIndex];
const relativeIndex = }
Math.round((200 - event.layerY) / chunk.height * (chunk.size() - 1));
if (relativeIndex < chunk.size()) logEntry = chunk.at(relativeIndex); _getEntryForEvent(event) {
} const chunk = this._getChunkForEvent(event);
return {logEntry, target}; 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 { ...@@ -475,7 +518,12 @@ class Legend {
} }
colorForType(type) { 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) { filter(logEntry) {
......
...@@ -29,15 +29,20 @@ found in the LICENSE file. --> ...@@ -29,15 +29,20 @@ found in the LICENSE file. -->
} }
#timelineSamples, #timelineChunks, #timelineSamples, #timelineChunks,
#timelineMarkers, #timelineAnnotations { #timelineMarkers, #timelineAnnotations, #hitPanel {
top: 0px; top: 0px;
position: absolute; position: absolute;
margin-right: 100px; margin-right: 100px;
} }
#timelineMarkers, #timelineAnnotations { #timelineMarkers, #timelineAnnotations,
.noPointerEvents, .noPointerEvents * {
pointer-events: none; pointer-events: none;
} }
#toolTipTarget {
position: absolute;
}
.title { .title {
position: relative; position: relative;
float: left; float: left;
...@@ -105,6 +110,9 @@ found in the LICENSE file. --> ...@@ -105,6 +110,9 @@ found in the LICENSE file. -->
#selection { #selection {
display: none; display: none;
top: 0px;
left: 0px;
position: absolute;
} }
#rightHandle, #rightHandle,
...@@ -121,6 +129,7 @@ found in the LICENSE file. --> ...@@ -121,6 +129,7 @@ found in the LICENSE file. -->
} }
#rightHandle { #rightHandle {
border-right: 1px solid var(--on-surface-color); border-right: 1px solid var(--on-surface-color);
margin-left: -5px;
} }
#selectionBackground { #selectionBackground {
...@@ -147,7 +156,9 @@ found in the LICENSE file. --> ...@@ -147,7 +156,9 @@ found in the LICENSE file. -->
.legend { .legend {
flex: initial; flex: initial;
} }
</style>
<style>
/* SVG styles */
.txt { .txt {
font: 8px monospace; font: 8px monospace;
} }
...@@ -179,8 +190,10 @@ found in the LICENSE file. --> ...@@ -179,8 +190,10 @@ found in the LICENSE file. -->
vector-effect: non-scaling-stroke; vector-effect: non-scaling-stroke;
} }
.flameSelected { .flameSelected {
fill: none; fill: var(--on-background-color);
fill-opacity: 0.1;
stroke: var(--on-background-color); stroke: var(--on-background-color);
stroke-opacity: 0.8;
stroke-width: 1; stroke-width: 1;
vector-effect: non-scaling-stroke; vector-effect: non-scaling-stroke;
} }
...@@ -204,9 +217,11 @@ found in the LICENSE file. --> ...@@ -204,9 +217,11 @@ found in the LICENSE file. -->
<svg id="timelineChunks" xmlns="http://www.w3.org/2000/svg" class="dataSized"> <svg id="timelineChunks" xmlns="http://www.w3.org/2000/svg" class="dataSized">
<g id="scalableContent"></g> <g id="scalableContent"></g>
</svg> </svg>
<svg id="timelineAnnotations" xmlns="http://www.w3.org/2000/svg" class="dataSized"></svg> <svg id="timelineAnnotations" xmlns="http://www.w3.org/2000/svg" class="dataSized noPointerEvents"></svg>
<svg id="timelineMarkers" xmlns="http://www.w3.org/2000/svg" class="dataSized"></svg> <svg id="timelineMarkers" xmlns="http://www.w3.org/2000/svg" class="dataSized noPointerEvents"></svg>
<canvas id="timelineCanvas"></canvas> <div id="toolTipTarget"></div>
<!-- Use a div element covering all complex items to prevent slow hit test-->
<div id="hitPanel" class="dataSized"></div>
</div> </div>
<div class="timelineLegend"> <div class="timelineLegend">
......
...@@ -2,13 +2,16 @@ ...@@ -2,13 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import {Profile} from '../../../profile.mjs'
import {delay} from '../../helper.mjs'; import {delay} from '../../helper.mjs';
import {TickLogEntry} from '../../log/tick.mjs';
import {Timeline} from '../../timeline.mjs'; import {Timeline} from '../../timeline.mjs';
import {CSSColor, DOM, SVG, V8CustomElement} from '../helper.mjs'; import {SelectTimeEvent} from '../events.mjs';
import {DOM, SVG} from '../helper.mjs';
import {TimelineTrackBase} from './timeline-track-base.mjs' import {TimelineTrackBase} from './timeline-track-base.mjs'
const kFlameHeight = 10;
class Flame { class Flame {
constructor(time, entry, depth, id) { constructor(time, entry, depth, id) {
this.time = time; this.time = time;
...@@ -16,13 +19,39 @@ class Flame { ...@@ -16,13 +19,39 @@ class Flame {
this.depth = depth; this.depth = depth;
this.id = id; this.id = id;
this.duration = -1; this.duration = -1;
this.parent = undefined;
this.children = [];
} }
static add(time, entry, stack, flames) {
const depth = stack.length;
const id = flames.length;
const newFlame = new Flame(time, entry, depth, id)
if (depth > 0) {
const parent = stack[depth - 1];
newFlame.parent = parent;
parent.children.push(newFlame);
}
flames.push(newFlame);
stack.push(newFlame);
}
stop(time) { stop(time) {
this.duration = time - this.time this.duration = time - this.time
} }
}
const kFlameHeight = 10; get start() {
return this.time;
}
get end() {
return this.time + this.duration;
}
get type() {
return TickLogEntry.extractCodeEntryType(this.entry);
}
}
DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
(templateText) => (templateText) =>
...@@ -33,7 +62,6 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', ...@@ -33,7 +62,6 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
constructor() { constructor() {
super(templateText); super(templateText);
this._annotations = new Annotations(this); this._annotations = new Annotations(this);
this.timelineNode.style.overflowY = 'scroll';
} }
_updateChunks() { _updateChunks() {
...@@ -48,17 +76,48 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', ...@@ -48,17 +76,48 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
this._updateFlames(); this._updateFlames();
} }
_handleDoubleClick(event) {
this._selectionHandler.clearSelection();
const flame = this._getFlameForEvent(event);
if (flame === undefined) return;
event.stopImmediatePropagation();
this.dispatchEvent(new SelectTimeEvent(flame.start, flame.end));
return false;
}
_getFlameDepthForEvent(event) {
return Math.floor(event.layerY / kFlameHeight) - 1;
}
_getFlameForEvent(event) {
const depth = this._getFlameDepthForEvent(event);
const time = this.positionToTime(event.pageX);
const index = this._flames.find(time);
for (let i = index - 1; i > 0; i--) {
const flame = this._flames.at(i);
if (flame.depth != depth) continue;
if (flame.end < time) continue;
return flame;
}
return undefined;
}
_getEntryForEvent(event) { _getEntryForEvent(event) {
let logEntry = false; const depth = this._getFlameDepthForEvent(event);
const target = event.target; const time = this.positionToTime(event.pageX);
const id = event.target.getAttribute('data-id'); const index = this._timeline.find(time);
if (id) { const tick = this._timeline.at(index);
const codeEntry = this._flames.at(id).entry; let stack = tick.stack;
if (codeEntry.logEntry) { if (index > 0 && tick.time > time) {
logEntry = codeEntry.logEntry; stack = this._timeline.at(index - 1).stack;
}
} }
return {logEntry, target}; // tick.stack = [top, ...., bottom];
const logEntry = stack[stack.length - depth - 1]?.logEntry ?? false;
// Filter out raw pc entries.
if (typeof logEntry == 'number' || logEntry === false) return false;
this.toolTipTargetNode.style.left = `${event.layerX}px`;
this.toolTipTargetNode.style.top = `${(depth + 2) * kFlameHeight}px`;
return logEntry;
} }
_updateFlames() { _updateFlames() {
...@@ -83,10 +142,7 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', ...@@ -83,10 +142,7 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
} }
flameStack.length = flameStackIndex; flameStack.length = flameStackIndex;
} }
const newFlame = Flame.add(tick.time, entry, flameStack, tmpFlames);
new Flame(tick.time, entry, flameStack.length, tmpFlames.length);
tmpFlames.push(newFlame);
flameStack.push(newFlame);
} }
if (tick.stack.length < flameStack.length) { if (tick.stack.length < flameStack.length) {
for (let k = tick.stack.length; k < flameStack.length; k++) { for (let k = tick.stack.length; k < flameStack.length; k++) {
...@@ -140,26 +196,17 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', ...@@ -140,26 +196,17 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
const x = this.timeToPosition(flame.time); const x = this.timeToPosition(flame.time);
const y = (flame.depth + 1) * kFlameHeight; const y = (flame.depth + 1) * kFlameHeight;
let width = flame.duration * this._timeToPixel; let width = flame.duration * this._timeToPixel;
if (outline) { if (outline) {
return `<rect x=${x} y=${y} width=${width} height=${ return `<rect x=${x} y=${y} width=${width} height=${
kFlameHeight} class=flameSelected />`; kFlameHeight} class=flameSelected />`;
} }
const color = this._legend.colorForType(flame.type);
let type = 'native';
if (flame.entry?.state) {
type = Profile.getKindFromState(flame.entry.state);
}
const color = this._legend.colorForType(type);
return `<rect x=${x} y=${y} width=${width} height=${kFlameHeight} fill=${ return `<rect x=${x} y=${y} width=${width} height=${kFlameHeight} fill=${
color} data-id=${flame.id} class=flame />`; color} class=flame />`;
} }
drawFlameText(flame) { drawFlameText(flame) {
let type = 'native'; let type = flame.type;
if (flame.entry?.state) {
type = Profile.getKindFromState(flame.entry.state);
}
const kHeight = 9; const kHeight = 9;
const x = this.timeToPosition(flame.time); const x = this.timeToPosition(flame.time);
const y = flame.depth * (kHeight + 1); const y = flame.depth * (kHeight + 1);
...@@ -167,7 +214,7 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', ...@@ -167,7 +214,7 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
width -= width * 0.1; width -= width * 0.1;
let buffer = ''; let buffer = '';
if (width < 15 || type == 'native') return buffer; if (width < 15 || type == 'Other') return buffer;
const rawName = flame.entry.getRawName(); const rawName = flame.entry.getRawName();
if (rawName.length == 0) return buffer; if (rawName.length == 0) return buffer;
const kChartWidth = 5; const kChartWidth = 5;
...@@ -179,7 +226,7 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', ...@@ -179,7 +226,7 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
_drawAnnotations(logEntry, time) { _drawAnnotations(logEntry, time) {
if (time === undefined) { if (time === undefined) {
time = this.relativePositionToTime(this.timelineNode.scrollLeft); time = this.relativePositionToTime(this._timelineScrollLeft);
} }
this._annotations.update(logEntry, time); this._annotations.update(logEntry, time);
} }
...@@ -226,9 +273,11 @@ class Annotations { ...@@ -226,9 +273,11 @@ class Annotations {
if (start < 0) start = 0; if (start < 0) start = 0;
if (end > rawFlames.length) end = rawFlames.length; if (end > rawFlames.length) end = rawFlames.length;
const code = this._logEntry.entry; const code = this._logEntry.entry;
// Also compare against the function
const func = code.func ?? 0;
for (let i = start; i < end; i++) { for (let i = start; i < end; i++) {
const flame = rawFlames[i]; const flame = rawFlames[i];
if (flame.entry != code) continue; if (flame.entry !== code && flame.entry?.func !== func) continue;
this._buffer += this._track.drawFlame(flame, true); this._buffer += this._track.drawFlame(flame, true);
} }
this._drawBuffer(); this._drawBuffer();
......
...@@ -11,14 +11,17 @@ found in the LICENSE file. --> ...@@ -11,14 +11,17 @@ found in the LICENSE file. -->
} }
#content { #content {
background-color: var(--surface-color); background-color: rgba(var(--surface-color-rgb), 0.8);
border: 3px var(--primary-color) solid; border: 3px var(--primary-color) solid;
border-radius: 10px; border-radius: 10px;
padding: 10px;
width: auto;
min-width: 100px; min-width: 100px;
max-width: 400px;
min-height: 100px; min-height: 100px;
padding: 10px; max-height: 400px;
overflow: auto;
box-shadow: 0px 0px 10px rgba(0,0,0,0.5); box-shadow: 0px 0px 10px rgba(0,0,0,0.5);
width: auto;
} }
#content > h3 { #content > h3 {
...@@ -39,7 +42,7 @@ found in the LICENSE file. --> ...@@ -39,7 +42,7 @@ found in the LICENSE file. -->
z-index: 99999; z-index: 99999;
--tip-offset: 10px; --tip-offset: 10px;
--tip-width: 10px; --tip-width: 10px;
--tip-height: 15px; --tip-height: 40px;
} }
#body.top { #body.top {
...@@ -55,29 +58,51 @@ found in the LICENSE file. --> ...@@ -55,29 +58,51 @@ found in the LICENSE file. -->
left: calc(var(--tip-offset) * -1 - var(--tip-width)); left: calc(var(--tip-offset) * -1 - var(--tip-width));
} }
.tip { .tip, .tipThin {
width: 0; width: 0;
height: 0; height: 0;
border-style: solid; border-style: solid;
position: absolute; position: absolute;
border-width: var(--tip-height) var(--tip-width) 0 var(--tip-width);
border-color: var(--primary-color) transparent transparent transparent; border-color: var(--primary-color) transparent transparent transparent;
pointer-events: none; pointer-events: none;
} }
.tip {
border-width: var(--tip-width) var(--tip-width) 0 var(--tip-width);
}
.tipThin {
border-width: var(--tip-height) 4px 2px 4px;
bottom: -30px;
left: -4px;
}
/* Tip positioning modifiers */
.top > .tip { .top > .tip {
bottom: calc(var(--tip-width) * -1);
}
.top > .tipThin {
bottom: calc(var(--tip-height) * -1); bottom: calc(var(--tip-height) * -1);
} }
.bottom > .tip { .bottom > .tip {
top: calc(var(--tip-width) * -1);
transform: scaleY(-1);
}
.bottom > .tipThin {
top: calc(var(--tip-height) * -1); top: calc(var(--tip-height) * -1);
transform: scaleY(-1); transform: scaleY(-1);
} }
.left > .tip { .left > .tip {
right: var(--tip-offset); right: var(--tip-offset);
} }
.left > .tipThin {
right: var(--tip-offset);
}
.right > .tip { .right > .tip {
left: var(--tip-offset); left: var(--tip-offset);
} }
.right > .tipThin {
left: var(--tip-offset);
}
.properties td { .properties td {
vertical-align: top; vertical-align: top;
} }
...@@ -93,5 +118,7 @@ found in the LICENSE file. --> ...@@ -93,5 +118,7 @@ found in the LICENSE file. -->
<div id="body"> <div id="body">
<div id="content"> <div id="content">
</div> </div>
<div class="tip"></div> <div class="tip">
<div class="tipThin"></div>
</div>
</div> </div>
...@@ -155,13 +155,11 @@ class TableBuilder { ...@@ -155,13 +155,11 @@ class TableBuilder {
const cell = row.insertCell(); const cell = row.insertCell();
if (value == undefined) return; if (value == undefined) return;
if (App.isClickable(value)) { if (App.isClickable(value)) {
cell.innerText = value.toString();
cell.className = 'clickable'; cell.className = 'clickable';
cell.onclick = this._logEntryClickHandler; cell.onclick = this._logEntryClickHandler;
cell.data = value; cell.data = value;
} else {
new ExpandableText(cell, value.toString());
} }
new ExpandableText(cell, value.toString());
} }
_addTitle(value) { _addTitle(value) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment