[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: Patrick Thier <pthier@chromium.org> Cr-Commit-Position: refs/heads/master@{#75130}
This commit is contained in:
parent
1c249d33b5
commit
1837c6f983
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
}
|
||||
}
|
@ -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}`);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<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>
|
||||
<slot></slot>
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* SVG styles */
|
||||
.txt {
|
||||
font: 8px monospace;
|
||||
}
|
||||
@ -179,8 +190,10 @@ found in the LICENSE file. -->
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
.flameSelected {
|
||||
fill: none;
|
||||
fill: var(--on-background-color);
|
||||
fill-opacity: 0.1;
|
||||
stroke: var(--on-background-color);
|
||||
stroke-opacity: 0.8;
|
||||
stroke-width: 1;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
@ -204,9 +217,11 @@ found in the LICENSE file. -->
|
||||
<svg id="timelineChunks" xmlns="http://www.w3.org/2000/svg" class="dataSized">
|
||||
<g id="scalableContent"></g>
|
||||
</svg>
|
||||
<svg id="timelineAnnotations" xmlns="http://www.w3.org/2000/svg" class="dataSized"></svg>
|
||||
<svg id="timelineMarkers" xmlns="http://www.w3.org/2000/svg" class="dataSized"></svg>
|
||||
<canvas id="timelineCanvas"></canvas>
|
||||
<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 noPointerEvents"></svg>
|
||||
<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 class="timelineLegend">
|
||||
|
@ -2,13 +2,16 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import {Profile} from '../../../profile.mjs'
|
||||
import {delay} from '../../helper.mjs';
|
||||
import {TickLogEntry} from '../../log/tick.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'
|
||||
|
||||
const kFlameHeight = 10;
|
||||
|
||||
class Flame {
|
||||
constructor(time, entry, depth, id) {
|
||||
this.time = time;
|
||||
@ -16,13 +19,39 @@ class Flame {
|
||||
this.depth = depth;
|
||||
this.id = id;
|
||||
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) {
|
||||
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',
|
||||
(templateText) =>
|
||||
@ -33,7 +62,6 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this._annotations = new Annotations(this);
|
||||
this.timelineNode.style.overflowY = 'scroll';
|
||||
}
|
||||
|
||||
_updateChunks() {
|
||||
@ -48,17 +76,48 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
|
||||
this._updateFlames();
|
||||
}
|
||||
|
||||
_getEntryForEvent(event) {
|
||||
let logEntry = false;
|
||||
const target = event.target;
|
||||
const id = event.target.getAttribute('data-id');
|
||||
if (id) {
|
||||
const codeEntry = this._flames.at(id).entry;
|
||||
if (codeEntry.logEntry) {
|
||||
logEntry = codeEntry.logEntry;
|
||||
}
|
||||
_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 {logEntry, target};
|
||||
return undefined;
|
||||
}
|
||||
|
||||
_getEntryForEvent(event) {
|
||||
const depth = this._getFlameDepthForEvent(event);
|
||||
const time = this.positionToTime(event.pageX);
|
||||
const index = this._timeline.find(time);
|
||||
const tick = this._timeline.at(index);
|
||||
let stack = tick.stack;
|
||||
if (index > 0 && tick.time > time) {
|
||||
stack = this._timeline.at(index - 1).stack;
|
||||
}
|
||||
// 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() {
|
||||
@ -83,10 +142,7 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
|
||||
}
|
||||
flameStack.length = flameStackIndex;
|
||||
}
|
||||
const newFlame =
|
||||
new Flame(tick.time, entry, flameStack.length, tmpFlames.length);
|
||||
tmpFlames.push(newFlame);
|
||||
flameStack.push(newFlame);
|
||||
Flame.add(tick.time, entry, flameStack, tmpFlames);
|
||||
}
|
||||
if (tick.stack.length < flameStack.length) {
|
||||
for (let k = tick.stack.length; k < flameStack.length; k++) {
|
||||
@ -140,26 +196,17 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
|
||||
const x = this.timeToPosition(flame.time);
|
||||
const y = (flame.depth + 1) * kFlameHeight;
|
||||
let width = flame.duration * this._timeToPixel;
|
||||
|
||||
if (outline) {
|
||||
return `<rect x=${x} y=${y} width=${width} height=${
|
||||
kFlameHeight} class=flameSelected />`;
|
||||
}
|
||||
|
||||
let type = 'native';
|
||||
if (flame.entry?.state) {
|
||||
type = Profile.getKindFromState(flame.entry.state);
|
||||
}
|
||||
const color = this._legend.colorForType(type);
|
||||
const color = this._legend.colorForType(flame.type);
|
||||
return `<rect x=${x} y=${y} width=${width} height=${kFlameHeight} fill=${
|
||||
color} data-id=${flame.id} class=flame />`;
|
||||
color} class=flame />`;
|
||||
}
|
||||
|
||||
drawFlameText(flame) {
|
||||
let type = 'native';
|
||||
if (flame.entry?.state) {
|
||||
type = Profile.getKindFromState(flame.entry.state);
|
||||
}
|
||||
let type = flame.type;
|
||||
const kHeight = 9;
|
||||
const x = this.timeToPosition(flame.time);
|
||||
const y = flame.depth * (kHeight + 1);
|
||||
@ -167,7 +214,7 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
|
||||
width -= width * 0.1;
|
||||
|
||||
let buffer = '';
|
||||
if (width < 15 || type == 'native') return buffer;
|
||||
if (width < 15 || type == 'Other') return buffer;
|
||||
const rawName = flame.entry.getRawName();
|
||||
if (rawName.length == 0) return buffer;
|
||||
const kChartWidth = 5;
|
||||
@ -179,7 +226,7 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
|
||||
|
||||
_drawAnnotations(logEntry, time) {
|
||||
if (time === undefined) {
|
||||
time = this.relativePositionToTime(this.timelineNode.scrollLeft);
|
||||
time = this.relativePositionToTime(this._timelineScrollLeft);
|
||||
}
|
||||
this._annotations.update(logEntry, time);
|
||||
}
|
||||
@ -226,9 +273,11 @@ class Annotations {
|
||||
if (start < 0) start = 0;
|
||||
if (end > rawFlames.length) end = rawFlames.length;
|
||||
const code = this._logEntry.entry;
|
||||
// Also compare against the function
|
||||
const func = code.func ?? 0;
|
||||
for (let i = start; i < end; 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._drawBuffer();
|
||||
|
@ -11,14 +11,17 @@ found in the LICENSE file. -->
|
||||
}
|
||||
|
||||
#content {
|
||||
background-color: var(--surface-color);
|
||||
background-color: rgba(var(--surface-color-rgb), 0.8);
|
||||
border: 3px var(--primary-color) solid;
|
||||
border-radius: 10px;
|
||||
min-width: 100px;
|
||||
min-height: 100px;
|
||||
padding: 10px;
|
||||
box-shadow: 0px 0px 10px rgba(0,0,0,0.5);
|
||||
width: auto;
|
||||
min-width: 100px;
|
||||
max-width: 400px;
|
||||
min-height: 100px;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
box-shadow: 0px 0px 10px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
#content > h3 {
|
||||
@ -39,7 +42,7 @@ found in the LICENSE file. -->
|
||||
z-index: 99999;
|
||||
--tip-offset: 10px;
|
||||
--tip-width: 10px;
|
||||
--tip-height: 15px;
|
||||
--tip-height: 40px;
|
||||
}
|
||||
|
||||
#body.top {
|
||||
@ -55,29 +58,51 @@ found in the LICENSE file. -->
|
||||
left: calc(var(--tip-offset) * -1 - var(--tip-width));
|
||||
}
|
||||
|
||||
.tip {
|
||||
.tip, .tipThin {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
position: absolute;
|
||||
border-width: var(--tip-height) var(--tip-width) 0 var(--tip-width);
|
||||
border-color: var(--primary-color) transparent transparent transparent;
|
||||
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 {
|
||||
bottom: calc(var(--tip-width) * -1);
|
||||
}
|
||||
.top > .tipThin {
|
||||
bottom: calc(var(--tip-height) * -1);
|
||||
}
|
||||
.bottom > .tip {
|
||||
top: calc(var(--tip-width) * -1);
|
||||
transform: scaleY(-1);
|
||||
}
|
||||
.bottom > .tipThin {
|
||||
top: calc(var(--tip-height) * -1);
|
||||
transform: scaleY(-1);
|
||||
}
|
||||
.left > .tip {
|
||||
right: var(--tip-offset);
|
||||
}
|
||||
.left > .tipThin {
|
||||
right: var(--tip-offset);
|
||||
}
|
||||
.right > .tip {
|
||||
left: var(--tip-offset);
|
||||
}
|
||||
.right > .tipThin {
|
||||
left: var(--tip-offset);
|
||||
}
|
||||
|
||||
|
||||
.properties td {
|
||||
vertical-align: top;
|
||||
}
|
||||
@ -93,5 +118,7 @@ found in the LICENSE file. -->
|
||||
<div id="body">
|
||||
<div id="content">
|
||||
</div>
|
||||
<div class="tip"></div>
|
||||
<div class="tip">
|
||||
<div class="tipThin"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -155,13 +155,11 @@ class TableBuilder {
|
||||
const cell = row.insertCell();
|
||||
if (value == undefined) return;
|
||||
if (App.isClickable(value)) {
|
||||
cell.innerText = value.toString();
|
||||
cell.className = 'clickable';
|
||||
cell.onclick = this._logEntryClickHandler;
|
||||
cell.data = value;
|
||||
} else {
|
||||
new ExpandableText(cell, value.toString());
|
||||
}
|
||||
new ExpandableText(cell, value.toString());
|
||||
}
|
||||
|
||||
_addTitle(value) {
|
||||
|
Loading…
Reference in New Issue
Block a user