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

[tools][system-analyzer] Improve flamechart

- Vertically adjust flamechart to show deep stacks
- Highlight currently hovered function in the complete flamechart

Bug: v8:10644, v8:11835
Change-Id: Ibb5839c332f28c552162943f3eb65435de11a36a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2950244Reviewed-by: 's avatarPatrick Thier <pthier@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75074}
parent 900d6b93
...@@ -191,6 +191,11 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -191,6 +191,11 @@ export class TimelineTrackBase extends V8CustomElement {
this._scalableContentNode.style.transform = `scale(${ratio}, 1)`; this._scalableContentNode.style.transform = `scale(${ratio}, 1)`;
} }
_adjustHeight(height) {
this.querySelectorAll('.dataSized')
.forEach(node => {node.style.height = height + 'px'});
}
_update() { _update() {
this._legend.update(); this._legend.update();
this._drawContent(); this._drawContent();
...@@ -261,7 +266,7 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -261,7 +266,7 @@ export class TimelineTrackBase extends V8CustomElement {
this.timelineMarkersNode.innerHTML = buffer; this.timelineMarkersNode.innerHTML = buffer;
} }
_drawAnnotations(logEntry) { _drawAnnotations(logEntry, time) {
// Subclass responsibility. // Subclass responsibility.
} }
...@@ -288,7 +293,8 @@ export class TimelineTrackBase extends V8CustomElement { ...@@ -288,7 +293,8 @@ export class TimelineTrackBase extends V8CustomElement {
const {logEntry, target} = this._getEntryForEvent(event); const {logEntry, target} = this._getEntryForEvent(event);
if (!logEntry) return false; if (!logEntry) return false;
this.dispatchEvent(new ToolTipEvent(logEntry, target)); this.dispatchEvent(new ToolTipEvent(logEntry, target));
this._drawAnnotations(logEntry); const time = this.positionToTime(event.pageX);
this._drawAnnotations(logEntry, time);
} }
_getEntryForEvent(event) { _getEntryForEvent(event) {
......
...@@ -24,11 +24,13 @@ found in the LICENSE file. --> ...@@ -24,11 +24,13 @@ found in the LICENSE file. -->
font-size: 10px; font-size: 10px;
opacity: 0.5; opacity: 0.5;
} }
.dataSized {
min-height: 200px;
}
#timelineSamples, #timelineChunks, #timelineSamples, #timelineChunks,
#timelineMarkers, #timelineAnnotations { #timelineMarkers, #timelineAnnotations {
top: 0px; top: 0px;
height: 200px;
position: absolute; position: absolute;
margin-right: 100px; margin-right: 100px;
} }
...@@ -36,39 +38,6 @@ found in the LICENSE file. --> ...@@ -36,39 +38,6 @@ found in the LICENSE file. -->
pointer-events: none; pointer-events: none;
} }
#timelineCanvas {
height: 200px;
position: relative;
overflow: visible;
pointer-events: none;
}
.chunk {
width: 6px;
position: absolute;
background-size: 100% 100%;
image-rendering: pixelated;
bottom: 0px;
background-color: var(--on-surface-color);
cursor: pointer;
content-visibility: auto;
}
.chunk:hover {
border-radius: 2px 2px 0 0;
margin: 0 0 -2px -2px;
border: 2px var(--primary-color) solid;
}
.timestamp {
height: 200px;
width: 100px;
border-left: 1px var(--on-surface-color) dashed;
padding-left: 4px;
position: absolute;
pointer-events: none;
font-size: 10px;
}
.title { .title {
position: relative; position: relative;
float: left; float: left;
...@@ -221,6 +190,10 @@ found in the LICENSE file. --> ...@@ -221,6 +190,10 @@ found in the LICENSE file. -->
dominant-baseline: hanging; dominant-baseline: hanging;
font-size: 9px; font-size: 9px;
} }
.flameSelected {
fill: none;
stroke: var(--on-background-color);
}
#scalableContent { #scalableContent {
} }
</style> </style>
...@@ -231,17 +204,17 @@ found in the LICENSE file. --> ...@@ -231,17 +204,17 @@ found in the LICENSE file. -->
<h3 class="title" id="title"></h3> <h3 class="title" id="title"></h3>
<div id="timeline"> <div id="timeline">
<div id="selection"> <div id="selection" class="dataSized">
<div id="leftHandle"></div> <div id="leftHandle"></div>
<div id="selectionBackground"></div> <div id="selectionBackground"></div>
<div id="rightHandle"></div> <div id="rightHandle"></div>
</div> </div>
<div id="timelineLabel">Frequency</div> <div id="timelineLabel">Frequency</div>
<svg id="timelineChunks" xmlns="http://www.w3.org/2000/svg"> <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"></svg> <svg id="timelineAnnotations" xmlns="http://www.w3.org/2000/svg" class="dataSized"></svg>
<svg id="timelineMarkers" xmlns="http://www.w3.org/2000/svg"></svg> <svg id="timelineMarkers" xmlns="http://www.w3.org/2000/svg" class="dataSized"></svg>
<canvas id="timelineCanvas"></canvas> <canvas id="timelineCanvas"></canvas>
</div> </div>
......
...@@ -11,18 +11,18 @@ import {TimelineTrackBase} from './timeline-track-base.mjs' ...@@ -11,18 +11,18 @@ import {TimelineTrackBase} from './timeline-track-base.mjs'
class Flame { class Flame {
constructor(time, entry, depth, id) { constructor(time, entry, depth, id) {
this.start = time; this.time = time;
this.end = this.start;
this.entry = entry; this.entry = entry;
this.depth = depth; this.depth = depth;
this.id = id; this.id = id;
} }
stop(time) { stop(time) {
this.end = time; this.duration = time - this.time
this.duration = time - this.start
} }
} }
const kFlameHeight = 10;
DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
(templateText) => (templateText) =>
class TimelineTrackTick extends TimelineTrackBase { class TimelineTrackTick extends TimelineTrackBase {
...@@ -31,6 +31,8 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', ...@@ -31,6 +31,8 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
constructor() { constructor() {
super(templateText); super(templateText);
this._annotations = new Annotations(this);
this.timelineNode.style.overflowY = 'scroll';
} }
_updateChunks() { _updateChunks() {
...@@ -62,9 +64,11 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', ...@@ -62,9 +64,11 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
const tmpFlames = []; const tmpFlames = [];
const stack = []; const stack = [];
const ticks = this._timeline.values; const ticks = this._timeline.values;
let maxDepth = 0;
for (let tickIndex = 0; tickIndex < ticks.length; tickIndex++) { for (let tickIndex = 0; tickIndex < ticks.length; tickIndex++) {
const tick = ticks[tickIndex]; const tick = ticks[tickIndex];
maxDepth = Math.max(maxDepth, tick.stack.length);
for (let stackIndex = 0; stackIndex < tick.stack.length; stackIndex++) { for (let stackIndex = 0; stackIndex < tick.stack.length; stackIndex++) {
const entry = tick.stack[stackIndex]; const entry = tick.stack[stackIndex];
if (stack.length <= stackIndex) { if (stack.length <= stackIndex) {
...@@ -91,6 +95,10 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', ...@@ -91,6 +95,10 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
stack[stackIndex].stop(lastTime); stack[stackIndex].stop(lastTime);
} }
this._flames = new Timeline(Flame, tmpFlames); this._flames = new Timeline(Flame, tmpFlames);
this._annotations.flames = this._flames;
// Account for empty top line
maxDepth++;
this._adjustHeight(maxDepth * kFlameHeight);
} }
_scaleContent(currentWidth) { _scaleContent(currentWidth) {
...@@ -123,19 +131,23 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', ...@@ -123,19 +131,23 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
add(); add();
} }
drawFlame(flame) { drawFlame(flame, outline = false) {
const x = this.timeToPosition(flame.time);
const y = (flame.depth + 1) * kFlameHeight;
let width = (flame.duration * this._timeToPixel) * 0.9;
let height = kFlameHeight - 1;
if (outline) {
return `<rect x=${x} y=${y} width=${width} height=${
height} class=flameSelected />`;
}
let type = 'native'; let type = 'native';
if (flame.entry?.state) { if (flame.entry?.state) {
type = Profile.getKindFromState(flame.entry.state); type = Profile.getKindFromState(flame.entry.state);
} }
const kHeight = 9;
const x = this.timeToPosition(flame.start);
const y = flame.depth * (kHeight + 1);
let width = flame.duration * this._timeToPixel;
width -= width * 0.1;
const color = this._legend.colorForType(type); const color = this._legend.colorForType(type);
return `<rect x=${x} y=${y} width=${width} height=${height} fill=${
return `<rect x=${x} y=${y} width=${width} height=${kHeight} fill=${
color} data-id=${flame.id} />`; color} data-id=${flame.id} />`;
} }
...@@ -145,7 +157,7 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', ...@@ -145,7 +157,7 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
type = Profile.getKindFromState(flame.entry.state); type = Profile.getKindFromState(flame.entry.state);
} }
const kHeight = 9; const kHeight = 9;
const x = this.timeToPosition(flame.start); const x = this.timeToPosition(flame.time);
const y = flame.depth * (kHeight + 1); const y = flame.depth * (kHeight + 1);
let width = flame.duration * this._timeToPixel; let width = flame.duration * this._timeToPixel;
width -= width * 0.1; width -= width * 0.1;
...@@ -160,4 +172,69 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick', ...@@ -160,4 +172,69 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-tick',
buffer += `<text x=${x + 1} y=${y - 3} class=txt>${text}</text>` buffer += `<text x=${x + 1} y=${y - 3} class=txt>${text}</text>`
return buffer; return buffer;
} }
})
\ No newline at end of file _drawAnnotations(logEntry, time) {
if (time === undefined) {
time = this.relativePositionToTime(this.timelineNode.scrollLeft);
}
this._annotations.update(logEntry, time);
}
})
class Annotations {
_flames;
_logEntry;
_buffer;
constructor(track) {
this._track = track;
}
set flames(flames) {
this._flames = flames;
}
get _node() {
return this._track.timelineAnnotationsNode;
}
async update(logEntry, time) {
if (this._logEntry == logEntry) return;
this._logEntry = logEntry;
this._node.innerHTML = '';
if (logEntry === undefined) return;
this._buffer = '';
const start = this._flames.find(time);
let offset = 0;
// Draw annotations gradually outwards starting form the given time.
for (let range = 0; range < this._flames.length; range += 10000) {
this._markFlames(start - range, start - offset);
this._markFlames(start + offset, start + range);
offset = range;
await delay(50);
// Abort if we started another update asynchronously.
if (this._logEntry != logEntry) return;
}
}
_markFlames(start, end) {
const rawFlames = this._flames.values;
if (start < 0) start = 0;
if (end > rawFlames.length) end = rawFlames.length;
const code = this._logEntry.entry;
for (let i = start; i < end; i++) {
const flame = rawFlames[i];
if (flame.entry != code) continue;
this._buffer += this._track.drawFlame(flame, true);
}
this._drawBuffer();
}
_drawBuffer() {
if (this._buffer.length == 0) return;
const svg = SVG.svg();
svg.innerHTML = this._buffer;
this._node.appendChild(svg);
this._buffer = '';
}
}
\ No newline at end of file
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