Skip to content

Commit b2d7424

Browse files
committed
feat: ui improvements in realtime profile
- make toggling happen via quickpick - kept configurator, allow opening and closing by click on the graph or labels - improved graph scale https://memes.peet.io/img/20-09-2c2e3815-0120-4b26-9663-5439dfcc1fdb.png
1 parent b2d4bd2 commit b2d7424

12 files changed

+182
-93
lines changed

packages/vscode-js-profile-flame/package.json

+20-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"vscode": "^1.43.0"
2121
},
2222
"activationEvents": [
23-
"onView:vscode-js-profile-flame.realtime",
23+
"onView:vscode-js-profile-flame.realtime",
2424
"onCustomEditor:jsProfileVisualizer.cpuprofile.flame"
2525
],
2626
"contributes": {
@@ -42,11 +42,28 @@
4242
"type": "webview",
4343
"id": "vscode-js-profile-flame.realtime",
4444
"name": "Realtime Performance",
45-
"when": "debugConfigurationType =~ /^pwa-/"
45+
"when": "debugConfigurationType =~ /^pwa-/ || debugConfigurationType == node || debugConfigurationType == chrome"
4646
}
4747
]
4848
},
49-
"configuration":[
49+
"menus": {
50+
"view/title": [
51+
{
52+
"command": "vscode-js-profile-flame.setRealtimeCharts",
53+
"when": "view == vscode-js-profile-flame.realtime",
54+
"group": "navigation@1"
55+
}
56+
]
57+
},
58+
"commands": [
59+
{
60+
"command": "vscode-js-profile-flame.setRealtimeCharts",
61+
"category": "Debug",
62+
"title": "Toggle Realtime Performance Charts",
63+
"icon": "$(gear)"
64+
}
65+
],
66+
"configuration": [
5067
{
5168
"title": "Flamegraph Visaulizer",
5269
"properties": {

packages/vscode-js-profile-flame/src/extension.ts

+34-3
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ import { join } from 'path';
1414
import * as vscode from 'vscode';
1515
import { CpuProfileEditorProvider } from 'vscode-js-profile-core/out/cpu/editorProvider';
1616
import { ProfileCodeLensProvider } from 'vscode-js-profile-core/out/profileCodeLensProvider';
17-
import { RealtimeSessionTracker } from './realtimeSessionTracker';
17+
import { createMetrics } from './realtime/metrics';
18+
import { readRealtimeSettings, RealtimeSessionTracker } from './realtimeSessionTracker';
1819
import { RealtimeWebviewProvider } from './realtimeWebviewProvider';
1920

2021
export function activate(context: vscode.ExtensionContext) {
21-
const realtimeTracker = new RealtimeSessionTracker();
22-
const realtime = new RealtimeWebviewProvider(context.extensionUri, context, realtimeTracker);
22+
const realtimeTracker = new RealtimeSessionTracker(context);
23+
const realtime = new RealtimeWebviewProvider(context.extensionUri, realtimeTracker);
2324

2425
context.subscriptions.push(
2526
vscode.window.registerCustomEditorProvider(
@@ -43,6 +44,36 @@ export function activate(context: vscode.ExtensionContext) {
4344
),
4445

4546
vscode.debug.onDidTerminateDebugSession(session => realtimeTracker.onSessionDidEnd(session)),
47+
48+
vscode.commands.registerCommand('vscode-js-profile-flame.setRealtimeCharts', async () => {
49+
const metrics = createMetrics();
50+
const settings = readRealtimeSettings(context);
51+
const quickpick = vscode.window.createQuickPick<{ label: string; index: number }>();
52+
53+
quickpick.canSelectMany = true;
54+
quickpick.items = metrics.map((metric, i) => ({
55+
label: metric.name(),
56+
index: i,
57+
}));
58+
quickpick.selectedItems = settings.enabledMetrics.length
59+
? settings.enabledMetrics.map(index => quickpick.items[index])
60+
: quickpick.items.slice();
61+
62+
quickpick.show();
63+
64+
const chosen = await new Promise<number[] | undefined>(resolve => {
65+
quickpick.onDidAccept(() => resolve(quickpick.selectedItems.map(i => i.index)));
66+
quickpick.onDidHide(() => resolve(undefined));
67+
});
68+
69+
quickpick.dispose();
70+
71+
if (!chosen || !chosen.length) {
72+
return;
73+
}
74+
75+
realtimeTracker.setEnabledMetrics(chosen);
76+
}),
4677
);
4778
}
4879

packages/vscode-js-profile-flame/src/realtime/baseMetric.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,20 @@ export abstract class Metric {
1010
private maxMetric = 1;
1111

1212
/**
13-
* Gets the max value of all the metrics.
13+
* Gets the max y value that the chart should be scaled to. Defaults to
14+
* get the max-higher power of 2.
1415
*/
15-
public get max() {
16-
return this.maxMetric;
16+
public get maxY() {
17+
let maxY = 10 ** Math.ceil(Math.log10(this.maxMetric));
18+
if (maxY > this.maxMetric * 2) {
19+
maxY /= 2;
20+
}
21+
22+
if (maxY > this.maxMetric * 2) {
23+
maxY /= 2;
24+
}
25+
26+
return maxY;
1727
}
1828

1929
/**

packages/vscode-js-profile-flame/src/realtime/chart.css

+37-30
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,55 @@
99
flex-direction: column;
1010
}
1111

12+
.container.noData > * {
13+
display: none;
14+
}
15+
16+
.noDataText {
17+
display: none;
18+
position: absolute;
19+
top: 0;
20+
left: 0;
21+
right: 0;
22+
bottom: 0;
23+
text-align: center;
24+
justify-content: center;
25+
align-items: center;
26+
opacity: 0.8;
27+
}
28+
29+
.container.noData .noDataText {
30+
display: flex;
31+
}
32+
1233
@media (min-width: 600px) {
1334
.configOpen.container {
1435
flex-direction: row;
1536
align-items: stretch;
1637
}
1738
}
1839

40+
.configOpen canvas {
41+
cursor: pointer;
42+
}
43+
1944
.labelList {
2045
font-size: 11px;
46+
cursor: pointer;
2147
font-family: var(--font-family);
2248
display: flex;
2349
align-items: center;
2450
white-space: nowrap;
2551
}
2652

53+
.labelList > span > span {
54+
padding-left: 0.25em;
55+
}
56+
2757
.labelList > span {
2858
display: inline-flex;
2959
align-items: center;
30-
margin-left: 4px;
60+
margin: 0 4px;
3161
color: var(--sideBar-foreground, var(--vscode-foreground));
3262
}
3363

@@ -44,14 +74,17 @@
4474
.maxContainer {
4575
position: absolute;
4676
top: 0;
47-
right: 0;
77+
left: 0;
4878
background: linear-gradient(to bottom, var(--vscode-sideBar-background), transparent);
79+
font-size: 0.8em;
4980
}
5081

5182
.max {
52-
color: var(--metric-color);
5383
display: inline;
54-
padding: 0 3px;
84+
}
85+
86+
.max + .max::before {
87+
content: ' / ';
5588
}
5689

5790
.timeLeft,
@@ -68,29 +101,3 @@
68101
.timeRight {
69102
right: 0;
70103
}
71-
72-
.toggle {
73-
position: absolute;
74-
left: 4px;
75-
top: 4px;
76-
background: 0;
77-
border: 0;
78-
padding: 3px;
79-
cursor: pointer;
80-
color: inherit;
81-
line-height: 0;
82-
}
83-
84-
.toggle:focus {
85-
outline: 1px solid var(--vscode-focusBorder);
86-
background: none;
87-
}
88-
89-
.configOpen .toggle {
90-
background: var(--vscode-inputOption-activeBackground);
91-
line-height: 0;
92-
}
93-
94-
.toggle svg {
95-
width: 15px;
96-
}

packages/vscode-js-profile-flame/src/realtime/chart.ts

+44-23
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
* Copyright (C) Microsoft Corporation. All rights reserved.
33
*--------------------------------------------------------*/
44

5-
import * as Gear from 'vscode-codicons/src/icons/gear.svg';
65
import { Metric } from './baseMetric';
76
import styles from './chart.css';
87
import { Configurator } from './configurator';
@@ -17,9 +16,10 @@ const openToSideWidth = 250;
1716
const openToSideMinSpace = 600;
1817

1918
export class Chart {
20-
private valElements: [Metric, HTMLElement][] = [];
19+
private valElements: [Metric, { max: HTMLElement; val: HTMLElement }][] = [];
2120
private configOpen = false;
2221
private configHadManualToggle = false;
22+
private hasAnyData = false;
2323

2424
private readonly frameCanvas = new FrameCanvas(this.width, this.height, this.settings);
2525
private readonly elements = this.createElements();
@@ -62,7 +62,8 @@ export class Chart {
6262
graphHeight = height - Sizing.LabelHeight;
6363
graphWidth = width;
6464
} else if (width < openToSideMinSpace) {
65-
graphHeight = Math.min(height - Sizing.LabelHeight, Math.round(width / naturalAspectRatio));
65+
const cfgrect = this.configurator.elem.getBoundingClientRect();
66+
graphHeight = height - cfgrect.height;
6667
graphWidth = width;
6768
} else {
6869
graphHeight = height;
@@ -82,9 +83,20 @@ export class Chart {
8283
this.configurator.updateMetrics();
8384
}
8485

85-
for (const [metric, el] of this.valElements) {
86-
el.innerText = metric.format(metric.current);
86+
for (const [metric, { val, max }] of this.valElements) {
87+
max.innerText = metric.format(metric.maxY);
88+
val.innerText = metric.format(metric.current);
8789
}
90+
91+
this.setHasData(this.settings.allMetrics.some(m => m.hasData()));
92+
}
93+
94+
private setHasData(hasData: boolean) {
95+
if (hasData === this.hasAnyData) {
96+
return;
97+
}
98+
99+
this.elements.container.classList[hasData ? 'remove' : 'add'](styles.noData);
88100
}
89101

90102
private setConfiguratorOpen(isOpen: boolean) {
@@ -109,18 +121,25 @@ export class Chart {
109121

110122
private createElements() {
111123
const container = document.createElement('div');
112-
container.classList.add(styles.container);
124+
container.classList.add(styles.container, styles.noData);
113125
container.style.setProperty('--primary-series-color', this.settings.colors.primaryGraph);
114126
container.style.setProperty('--secondary-series-color', this.settings.colors.secondaryGraph);
115127

116128
const graph = document.createElement('div');
117129
graph.style.position = 'relative';
118130
graph.appendChild(this.frameCanvas.elem);
131+
graph.addEventListener('click', () => this.toggleConfiguration(false));
119132
container.appendChild(graph);
120133

134+
const noData = document.createElement('div');
135+
noData.classList.add(styles.noDataText);
136+
noData.innerText = 'No data yet -- start a debug session to collect some!';
137+
container.appendChild(noData);
138+
121139
const labelList = document.createElement('div');
122140
labelList.classList.add(styles.labelList);
123141
labelList.style.height = `${Sizing.LabelHeight}px`;
142+
labelList.addEventListener('click', () => this.toggleConfiguration(true));
124143
container.appendChild(labelList);
125144

126145
const leftTime = document.createElement('div');
@@ -136,20 +155,18 @@ export class Chart {
136155
valueContainer.classList.add(styles.maxContainer);
137156
graph.appendChild(valueContainer);
138157

139-
const toggleButton = document.createElement('button');
140-
toggleButton.classList.add(styles.toggle);
141-
toggleButton.innerHTML = Gear;
142-
toggleButton.addEventListener('click', () => this.toggleConfiguration());
143-
graph.appendChild(toggleButton);
144-
145158
this.setSeries(labelList, valueContainer);
146159

147-
return { toggleButton, container, labelList, leftTime, valueContainer };
160+
return { container, labelList, leftTime, valueContainer };
148161
}
149162

150-
private toggleConfiguration() {
163+
private toggleConfiguration(toState = !this.configOpen) {
164+
if (toState === this.configOpen) {
165+
return;
166+
}
167+
151168
this.configHadManualToggle = true;
152-
this.setConfiguratorOpen(!this.configOpen);
169+
this.setConfiguratorOpen(toState);
153170
this.updateSize(this.width, this.height);
154171
}
155172

@@ -160,8 +177,9 @@ export class Chart {
160177
}
161178

162179
private setSeries(labelList: HTMLElement, maxContainer: HTMLElement) {
163-
for (const [, el] of this.valElements) {
164-
maxContainer.removeChild(el);
180+
for (const [, { max, val }] of this.valElements) {
181+
max.parentElement?.removeChild(max);
182+
val.parentElement?.removeChild(val);
165183
}
166184

167185
labelList.innerHTML = '';
@@ -171,16 +189,19 @@ export class Chart {
171189
const label = document.createElement('span');
172190
label.style.setProperty('--metric-color', this.settings.metricColor(metric));
173191
label.classList.add(styles.primary);
174-
label.innerText = metric.name();
192+
label.innerText = `${metric.name()}: `;
175193
labelList.appendChild(label);
176194

177-
const val = document.createElement('div');
178-
val.classList.add(styles.max, styles.primary);
179-
val.style.setProperty('--metric-color', this.settings.metricColor(metric));
195+
const val = document.createElement('span');
180196
val.innerText = metric.format(metric.current);
197+
label.appendChild(val);
198+
199+
const max = document.createElement('div');
200+
max.classList.add(styles.max, styles.primary);
201+
max.innerText = metric.format(metric.maxY);
202+
maxContainer.appendChild(max);
181203

182-
maxContainer.appendChild(val);
183-
this.valElements.push([metric, val]);
204+
this.valElements.push([metric, { max, val }]);
184205
}
185206
}
186207
}

packages/vscode-js-profile-flame/src/realtime/configurator.css

+2-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
.configurator {
2-
display: flex;
3-
flex-grow: 1;
4-
flex-direction: column;
5-
margin-top: 4px;
2+
padding-top: 4px;
63
}
74

85
.metric {
96
display: none;
10-
min-height: 1.2em;
11-
max-height: 2.2em;
12-
flex-grow: 1;
7+
height: 2em;
138
align-items: center;
149
padding: 0 7px;
1510
position: relative;

0 commit comments

Comments
 (0)