Skip to content

Commit d936ad8

Browse files
committed
initial uPlot & JSON-based graph page prototype. (#742)
1 parent 53d803d commit d936ad8

File tree

3 files changed

+298
-0
lines changed

3 files changed

+298
-0
lines changed

site/static/index2.html

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<link rel="stylesheet" href="uPlot.min.css">
5+
<style>
6+
.uplot {
7+
display: inline-block;
8+
vertical-align: top;
9+
width: min-content;
10+
}
11+
12+
.u-over {
13+
box-shadow: 0px 0px 0px 0.5px #ccc;
14+
}
15+
16+
.u-legend {
17+
text-align: left;
18+
padding-left: 50px;
19+
}
20+
21+
.u-inline tr {
22+
margin-right: 8px;
23+
}
24+
25+
.u-label {
26+
font-size: 12px;
27+
}
28+
29+
.u-tooltip {
30+
font-size: 10pt;
31+
position: absolute;
32+
background: #fff;
33+
display: none;
34+
border: 2px solid black;
35+
padding: 4px;
36+
pointer-events: none;
37+
z-index: 100;
38+
white-space: pre;
39+
font-family: monospace;
40+
}
41+
</style>
42+
<script src="uPlot.iife.min.js"></script>
43+
</head>
44+
<body>
45+
<script>
46+
const seriesColors = ["#7cb5ec", "#434348", "#90ed7d", "#f7a35c", "#8085e9", "#f15c80", "#e4d354", "#2b908f", "#f45b5b", "#91e8e1"];
47+
const interpolatedColor = "#fcb0f1";
48+
49+
function tooltipPlugin({onclick, commits, isInterpolated, shiftX = 10, shiftY = 10}) {
50+
let tooltipLeftOffset = 0;
51+
let tooltipTopOffset = 0;
52+
53+
const tooltip = document.createElement("div");
54+
tooltip.className = "u-tooltip";
55+
56+
let seriesIdx = null;
57+
let dataIdx = null;
58+
59+
const fmtDate = uPlot.fmtDate("{M}/{D}/{YY} {h}:{mm}:{ss} {AA}");
60+
61+
function setTooltip(u) {
62+
let top = u.valToPos(u.data[seriesIdx][dataIdx], 'y');
63+
let lft = u.valToPos(u.data[ 0][dataIdx], 'x');
64+
65+
tooltip.style.top = (tooltipTopOffset + top + shiftX) + "px";
66+
tooltip.style.left = (tooltipLeftOffset + lft + shiftY) + "px";
67+
68+
tooltip.style.borderColor = isInterpolated(dataIdx) ? interpolatedColor : seriesColors[seriesIdx - 1];
69+
let pctSinceStart = (((u.data[seriesIdx][dataIdx] - u.data[seriesIdx][0]) / u.data[seriesIdx][0]) * 100).toFixed(2);
70+
tooltip.textContent = (
71+
fmtDate(new Date(u.data[0][dataIdx] * 1e3)) + " - " + commits[dataIdx][1].slice(0,10) + "\n" +
72+
uPlot.fmtNum(u.data[seriesIdx][dataIdx]) + "(" + pctSinceStart + "% since start)"
73+
);
74+
}
75+
76+
return {
77+
hooks: {
78+
ready: [
79+
u => {
80+
let over = u.root.querySelector(".u-over");
81+
tooltipLeftOffset = parseFloat(over.style.left);
82+
tooltipTopOffset = parseFloat(over.style.top);
83+
u.root.querySelector(".u-wrap").appendChild(tooltip);
84+
85+
let clientX;
86+
let clientY;
87+
88+
over.addEventListener("mousedown", e => {
89+
clientX = e.clientX;
90+
clientY = e.clientY;
91+
});
92+
93+
over.addEventListener("mouseup", e => {
94+
// clicked in-place
95+
if (e.clientX == clientX && e.clientY == clientY) {
96+
if (seriesIdx != null && dataIdx != null) {
97+
onclick(u, seriesIdx, dataIdx);
98+
}
99+
}
100+
});
101+
}
102+
],
103+
setCursor: [
104+
u => {
105+
let c = u.cursor;
106+
107+
if (c.idx != dataIdx) {
108+
dataIdx = c.idx;
109+
110+
if (seriesIdx != null)
111+
setTooltip(u, setTooltip);
112+
}
113+
}
114+
],
115+
setSeries: [
116+
(u, sidx) => {
117+
if (seriesIdx != sidx) {
118+
seriesIdx = sidx;
119+
120+
if (sidx == null)
121+
tooltip.style.display = "none";
122+
else if (dataIdx != null) {
123+
tooltip.style.display = "block";
124+
setTooltip(u);
125+
}
126+
}
127+
}
128+
],
129+
}
130+
};
131+
}
132+
133+
function genPlotOpts({title, width, height, yAxisLabel, series, commits, stat, isInterpolated, alpha = 0.3, prox = 5}) {
134+
return {
135+
title,
136+
width,
137+
height,
138+
series,
139+
legend: {
140+
live: false,
141+
},
142+
focus: {
143+
alpha,
144+
},
145+
cursor: {
146+
focus: {
147+
prox,
148+
},
149+
drag: {
150+
x: true,
151+
y: true,
152+
},
153+
},
154+
scales: {
155+
y: {
156+
range: (self, dataMin, dataMax) => uPlot.rangeNum(0, dataMax, 0.2, true)
157+
}
158+
},
159+
axes: [
160+
{
161+
grid: {
162+
show: false,
163+
}
164+
},
165+
{
166+
label: yAxisLabel,
167+
space: 24,
168+
values: (self, splits) => {
169+
return splits.map(v => {
170+
return (
171+
v >= 1e12 ? v/1e12 + "T" :
172+
v >= 1e9 ? v/1e9 + "G" :
173+
v >= 1e6 ? v/1e6 + "M" :
174+
v >= 1e3 ? v/1e3 + "k" :
175+
v
176+
);
177+
});
178+
},
179+
},
180+
],
181+
plugins: [
182+
tooltipPlugin({
183+
onclick(u, seriesIdx, dataIdx) {
184+
let thisCommit = commits[dataIdx][1];
185+
let prevCommit = (commits[dataIdx-1] || [null,null])[1];
186+
window.open(`https://perf.rust-lang.org/compare.html?start=${prevCommit}&end=${thisCommit}&stat=${stat}`);
187+
},
188+
commits,
189+
isInterpolated,
190+
}),
191+
],
192+
};
193+
}
194+
195+
function renderPlots(data, state) {
196+
for (let benchName in data.benchmarks) {
197+
let benchKinds = data.benchmarks[benchName];
198+
199+
let i = 0;
200+
201+
for (let benchKind in benchKinds) {
202+
let cacheStates = benchKinds[benchKind];
203+
204+
let yAxisLabel = i == 0 ? "Value" : null;
205+
206+
let seriesOpts = [{}];
207+
208+
let xVals = data.commits.map(c => c[0]);
209+
210+
let plotData = [xVals];
211+
212+
let j = 0;
213+
214+
for (let cacheState in cacheStates) {
215+
let yVals = cacheStates[cacheState].points;
216+
217+
plotData.push(yVals);
218+
219+
seriesOpts.push({
220+
label: cacheState,
221+
width: devicePixelRatio,
222+
stroke: seriesColors[j],
223+
});
224+
225+
j++;
226+
}
227+
228+
let plotOpts = genPlotOpts({
229+
title: benchName + "-" + benchKind,
230+
width: Math.floor(window.innerWidth / 3) - 16,
231+
height: 300,
232+
yAxisLabel,
233+
series: seriesOpts,
234+
commits: data.commits,
235+
stat: state.stat,
236+
isInterpolated(dataIdx) {
237+
return cacheStates.full.is_interpolated.has(dataIdx);
238+
},
239+
});
240+
241+
let u = new uPlot(plotOpts, plotData, document.body);
242+
243+
i++;
244+
}
245+
}
246+
}
247+
248+
const BASE_URL = "https://perf.rust-lang.org/perf";
249+
250+
function post(path, body) {
251+
return fetch(BASE_URL + path, {
252+
method: "POST",
253+
// headers: {"Content-Type": "application/json"},
254+
body: JSON.stringify(body),
255+
}).then(r => r.json());
256+
}
257+
258+
function prepData(data) {
259+
let sortedBenchNames = Object.keys(data.benchmarks).sort();
260+
261+
let benchmarks = {};
262+
263+
function optInterpolated(buildKind) {
264+
for (let cacheState in buildKind)
265+
buildKind[cacheState].is_interpolated = new Set(buildKind[cacheState].is_interpolated);
266+
267+
return buildKind;
268+
}
269+
270+
sortedBenchNames.forEach(name => {
271+
const { Check, Debug, Opt } = data.benchmarks[name];
272+
273+
benchmarks[name] = {
274+
check: optInterpolated(Check),
275+
debug: optInterpolated(Debug),
276+
opt: optInterpolated(Opt),
277+
}
278+
});
279+
280+
data.benchmarks = benchmarks;
281+
282+
return data;
283+
}
284+
285+
const state = {
286+
start: "",
287+
end: "",
288+
stat: "instructions:u",
289+
absolute: true,
290+
};
291+
292+
post("/graph-new", state).then(prepData).then(data => renderPlots(data, state));
293+
</script>
294+
</body>
295+
</html>

0 commit comments

Comments
 (0)