Skip to content

Commit 66b21f4

Browse files
committed
Port detailed query page to Typescript
1 parent fdbb4c1 commit 66b21f4

File tree

6 files changed

+327
-300
lines changed

6 files changed

+327
-300
lines changed

site/frontend/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
"source": "src/pages/compare.ts",
4747
"distDir": "dist/scripts"
4848
},
49+
"detailed-query": {
50+
"source": "src/pages/detailed-query.ts",
51+
"distDir": "dist/scripts"
52+
},
4953
"uplot": {
5054
"source": "node_modules/uplot/dist/uPlot.min.css",
5155
"distDir": "dist/styles"
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
import {createUrlWithParams, getUrlParams} from "../utils/navigation";
2+
import {postMsgpack} from "../utils/requests";
3+
import {SELF_PROFILE_DATA_URL} from "../urls";
4+
5+
function to_seconds(time) {
6+
return time / 1000000000;
7+
}
8+
9+
function fmt_delta(to, delta, is_integral_delta) {
10+
let from = to - delta;
11+
let pct = (to - from) / from * 100;
12+
if (from == to) {
13+
pct = 0;
14+
}
15+
let classes;
16+
if (pct > 1) {
17+
classes = "positive";
18+
} else if (pct < -1) {
19+
classes = "negative";
20+
} else {
21+
classes = "neutral";
22+
}
23+
// some arbitrary "small" value
24+
// ignore this because it's not too interesting likely
25+
if (Math.abs(delta) <= 0.05) {
26+
classes = "neutral";
27+
}
28+
let text;
29+
if (is_integral_delta) {
30+
text = delta.toString();
31+
} else {
32+
text = delta.toFixed(3);
33+
}
34+
if (pct != Infinity && pct != -Infinity) {
35+
text += `(${pct.toFixed(1)}%)`.padStart(10, " ");
36+
} else {
37+
text += `-`.padStart(10, " ");
38+
}
39+
return `<span class="${classes}" title="${from.toFixed(3)} to ${to.toFixed(3)}${delta.toFixed(3)}">${text}</span>`;
40+
}
41+
42+
function populate_data(data, state: Selector) {
43+
let txt = `${state.commit.substring(0, 10)}: Self profile results for ${state.benchmark} run ${state.scenario}`;
44+
if (state.base_commit) {
45+
let self_href =
46+
`/detailed-query.html?sort_idx=${state.sort_idx}&commit=${state.commit}&scenario=${state.scenario}&benchmark=${state.benchmark}`;
47+
let base_href =
48+
`/detailed-query.html?sort_idx=${state.sort_idx}&commit=${state.base_commit}&scenario=${state.scenario}&benchmark=${state.benchmark}`;
49+
txt += `<br>diff vs base ${state.base_commit.substring(0, 10)}, <a href="${base_href}">query info for just base commit</a>`;
50+
txt += `<br><a href="${self_href}">query info for just this commit</a>`;
51+
}
52+
document.querySelector("#title").innerHTML = txt;
53+
let dl_url = (commit, bench, run) => {
54+
return `/perf/download-raw-self-profile?commit=${commit}&benchmark=${bench}&scenario=${run}`;
55+
};
56+
let dl_link = (commit, bench, run) => {
57+
let url = dl_url(commit, bench, run);
58+
return `<a href="${url}">raw</a>`;
59+
};
60+
let processed_url = (commit, bench, run, ty) => {
61+
return `/perf/processed-self-profile?commit=${commit}&benchmark=${bench}&scenario=${run}&type=${ty}`;
62+
};
63+
let processed_link = (commit, {benchmark, scenario}, ty) => {
64+
let url = processed_url(commit, benchmark, scenario, ty);
65+
return `<a href="${url}">${ty}</a>`;
66+
};
67+
let processed_crox_url = (commit, bench, run) => {
68+
let crox_url = window.location.origin + processed_url(commit, bench, run, "crox");
69+
return encodeURIComponent(crox_url);
70+
};
71+
let speedscope_link = (commit, bench, run) => {
72+
let benchmark_name = `${bench} run ${run}`;
73+
let crox_url = processed_crox_url(commit, bench, run);
74+
let speedscope_url = `https://www.speedscope.app/#profileURL=${crox_url}&title=${encodeURIComponent(benchmark_name)}`;
75+
return `<a href="${speedscope_url}">speedscope.app</a>`;
76+
};
77+
let firefox_profiler_link = (commit, bench, run) => {
78+
let crox_url = processed_crox_url(commit, bench, run);
79+
let ff_url = `https://profiler.firefox.com/from-url/${crox_url}/marker-chart/?v=5`;
80+
return `<a href="${ff_url}">Firefox profiler</a>`;
81+
};
82+
txt = "";
83+
if (state.base_commit) {
84+
txt += `Download/view
85+
${dl_link(state.base_commit, state.benchmark, state.scenario)},
86+
${processed_link(state.base_commit, state, "flamegraph")},
87+
${processed_link(state.base_commit, state, "crox")},
88+
${processed_link(state.base_commit, state, "codegen-schedule")}
89+
(${speedscope_link(state.base_commit, state.benchmark, state.scenario)},
90+
${firefox_profiler_link(state.base_commit, state.benchmark, state.scenario)})
91+
results for ${state.base_commit.substring(0, 10)} (base commit)`;
92+
txt += "<br>";
93+
}
94+
txt += `Download/view
95+
${dl_link(state.commit, state.benchmark, state.scenario)},
96+
${processed_link(state.commit, state, "flamegraph")},
97+
${processed_link(state.commit, state, "crox")},
98+
${processed_link(state.commit, state, "codegen-schedule")}
99+
(${speedscope_link(state.commit, state.benchmark, state.scenario)},
100+
${firefox_profiler_link(state.commit, state.benchmark, state.scenario)})
101+
results for ${state.commit.substring(0, 10)} (new commit)`;
102+
let profile = b => b.endsWith("-opt") ? "Opt" :
103+
b.endsWith("-doc") ? "Doc" :
104+
b.endsWith("-debug") ? "Debug" :
105+
b.endsWith("-check") ? "Check" : "???";
106+
let bench_name = b => b.replace(/-[^-]*$/, "");
107+
let scenario_filter = s => s == "full" ? "Full" :
108+
s == "incr-full" ? "IncrFull" :
109+
s == "incr-unchanged" ? "IncrUnchanged" :
110+
s.startsWith("incr-patched") ? "IncrPatched" :
111+
"???";
112+
if (state.base_commit) {
113+
txt += "<br>";
114+
txt += `Diff: <a
115+
href="/perf/processed-self-profile?commit=${state.commit}&base_commit=${state.base_commit}&benchmark=${state.benchmark}&scenario=${state.scenario}&type=codegen-schedule"
116+
>codegen-schedule</a>`;
117+
txt += "<br>Local profile (base): <code>" +
118+
`./target/release/collector profile_local cachegrind
119+
+${state.base_commit} --include ${bench_name(state.benchmark)} --profiles
120+
${profile(state.benchmark)} --scenarios ${scenario_filter(state.scenario)}</code>`;
121+
}
122+
txt += "<br>Local profile (new): <code>" +
123+
`./target/release/collector profile_local cachegrind
124+
+${state.commit} --include ${bench_name(state.benchmark)} --profiles
125+
${profile(state.benchmark)} --scenarios ${scenario_filter(state.scenario)}</code>`;
126+
if (state.base_commit) {
127+
txt += "<br>Local profile (diff): <code>" +
128+
`./target/release/collector profile_local cachegrind
129+
+${state.base_commit} --rustc2 +${state.commit} --include ${bench_name(state.benchmark)} --profiles
130+
${profile(state.benchmark)} --scenarios ${scenario_filter(state.scenario)}</code>`;
131+
}
132+
document.querySelector("#raw-urls").innerHTML = txt;
133+
let sort_idx = state.sort_idx;
134+
if (!data.base_profile_delta) {
135+
document.body.classList.add("hide-delta");
136+
}
137+
let header = document.getElementById("table-header");
138+
for (let th of (header.querySelectorAll("th") as any as HTMLElement[])) {
139+
// Skip non-sortable columns
140+
if (!th.attributes["data-sort-idx"]) {
141+
continue;
142+
}
143+
let idx = th.attributes["data-sort-idx"].value;
144+
if (idx == sort_idx) {
145+
th.setAttribute("data-sorted-by", "desc");
146+
} else if (idx == -sort_idx) {
147+
th.setAttribute("data-sorted-by", "asc");
148+
}
149+
let sortedBy = th.attributes["data-sorted-by"];
150+
let clickState = Object.assign({}, state);
151+
if (sortedBy && sortedBy.value == "desc") {
152+
clickState.sort_idx = -idx;
153+
} else if (sortedBy && sortedBy.value == "asc") {
154+
clickState.sort_idx = idx;
155+
} else {
156+
// start with descending
157+
if (th.attributes["data-default-sort-dir"].value == "1") {
158+
clickState.sort_idx = idx;
159+
} else {
160+
clickState.sort_idx = -idx;
161+
}
162+
}
163+
let inner = th.innerHTML;
164+
th.innerHTML = `<a href="${createUrlWithParams(clickState).toString()}">${inner}</a>`;
165+
}
166+
167+
if (!state.scenario.includes("incr-")) {
168+
// No need to show incremental columns if not showing
169+
// incremental data.
170+
document.body.classList.add("hide-incr");
171+
}
172+
173+
let table = document.getElementById("primary-table");
174+
let idx = 0;
175+
for (let element of [data.profile.totals, ...data.profile.query_data]) {
176+
let cur = to_object(element);
177+
let delta = null;
178+
if (data.base_profile_delta) {
179+
if (idx == 0) {
180+
delta = data.base_profile_delta.totals;
181+
} else {
182+
delta = data.base_profile_delta.query_data[idx - 1];
183+
}
184+
}
185+
let row = document.createElement("tr");
186+
187+
function td(row, content, is_delta = false) {
188+
let td = document.createElement("td");
189+
td.innerHTML = content;
190+
if (is_delta) {
191+
td.classList.add("delta");
192+
}
193+
row.appendChild(td);
194+
return td;
195+
}
196+
197+
td(row, cur.label);
198+
if (cur.percent_total_time < 0) {
199+
td(row, "-").setAttribute("title", "No wall-time stat collected for this run");
200+
} else {
201+
let t = td(row, cur.percent_total_time.toFixed(2) + "%");
202+
if (idx == 0) {
203+
t.innerText += "*";
204+
t.setAttribute("title", "% of cpu-time stat");
205+
}
206+
}
207+
td(row, to_seconds(cur.self_time).toFixed(3));
208+
if (delta) {
209+
td(row, fmt_delta(to_seconds(cur.self_time), to_seconds(delta.self_time), false), true);
210+
} else {
211+
td(row, "-", true);
212+
}
213+
td(row, cur.invocation_count);
214+
if (delta) {
215+
td(row, fmt_delta(cur.invocation_count, delta.invocation_count, true), true);
216+
} else {
217+
td(row, "-", true);
218+
}
219+
td(row,
220+
to_seconds(cur.incremental_load_time).toFixed(3)).classList.add("incr");
221+
if (delta) {
222+
td(row,
223+
fmt_delta(
224+
to_seconds(cur.incremental_load_time),
225+
to_seconds(delta.incremental_load_time),
226+
false
227+
),
228+
true).classList.add("incr");
229+
} else {
230+
td(row, "-", true).classList.add("incr");
231+
}
232+
table.appendChild(row);
233+
idx += 1;
234+
}
235+
236+
let artifactTable = document.getElementById("artifact-body");
237+
238+
function td(row, content) {
239+
let td = document.createElement("td");
240+
td.innerHTML = content;
241+
row.appendChild(td);
242+
return td;
243+
}
244+
245+
for (let [idx, element] of data.profile.artifact_sizes.entries()) {
246+
let row = document.createElement("tr");
247+
const label = td(row, element.label);
248+
label.style.textAlign = "center";
249+
td(row, element.bytes);
250+
if (data.base_profile_delta && data.base_profile_delta.artifact_sizes[idx]) {
251+
td(row, data.base_profile_delta.artifact_sizes[idx].bytes);
252+
}
253+
artifactTable.appendChild(row);
254+
idx += 1;
255+
}
256+
}
257+
258+
// https://stackoverflow.com/questions/6234773
259+
function escapeHtml(unsafe) {
260+
return unsafe
261+
.replace(/&/g, "&amp;")
262+
.replace(/</g, "&lt;")
263+
.replace(/>/g, "&gt;")
264+
.replace(/"/g, "&quot;")
265+
.replace(/'/g, "&#039;");
266+
}
267+
268+
function to_object(element) {
269+
if (!element.length) {
270+
element = [
271+
// escape html, to prevent rendering queries like <unknown> as tags
272+
escapeHtml(element.label),
273+
element.self_time,
274+
element.percent_total_time,
275+
element.number_of_cache_misses,
276+
element.number_of_cache_hits,
277+
element.invocation_count,
278+
element.blocked_time,
279+
element.incremental_load_time
280+
];
281+
}
282+
let [
283+
label, self_time, percent_total_time, _cache_misses,
284+
_cache_hits, invocation_count, _blocked_time,
285+
incremental_load_time
286+
] = element;
287+
return {
288+
label,
289+
self_time,
290+
percent_total_time,
291+
invocation_count,
292+
incremental_load_time
293+
};
294+
}
295+
296+
interface Selector {
297+
commit: string;
298+
base_commit: string | null;
299+
benchmark: string;
300+
// #[serde(alias = "run_name")]
301+
scenario: string;
302+
sort_idx: string | number;
303+
}
304+
305+
async function loadData() {
306+
const params = getUrlParams();
307+
const selector: Selector = {
308+
commit: params["commit"],
309+
base_commit: params["base_commit"] ?? null,
310+
benchmark: params["benchmark"],
311+
scenario: params["scenario"],
312+
sort_idx: params["sort_idx"] ?? "-2"
313+
};
314+
315+
const response = await postMsgpack(SELF_PROFILE_DATA_URL, selector);
316+
populate_data(response, selector);
317+
}
318+
319+
loadData();

site/frontend/src/urls.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const BASE_URL = window.location.origin + "/perf";
1+
const BASE_URL = window.location.origin + "/perf";
22

33
export const INFO_URL = `${BASE_URL}/info`;
44

@@ -7,3 +7,4 @@ export const STATUS_DATA_URL = `${BASE_URL}/status_page`;
77
export const BOOTSTRAP_DATA_URL = `${BASE_URL}/bootstrap`;
88
export const GRAPH_DATA_URL = `${BASE_URL}/graphs`;
99
export const COMPARE_DATA_URL = `${BASE_URL}/get`;
10+
export const SELF_PROFILE_DATA_URL = `${BASE_URL}/self-profile`;

site/frontend/src/utils/navigation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ export function createUrlParams(params: Dict<string>): URLSearchParams {
22
return createUrlWithParams(params).searchParams;
33
}
44

5-
export function createUrlWithParams(params: Dict<string>): URL {
5+
export function createUrlWithParams(params: Dict<any>): URL {
66
const originalUrl = window.location.toString();
77
const url = new URL(originalUrl);
88
for (const [key, value] of Object.entries(params)) {
99
if (value !== null && value !== undefined && value !== "") {
10-
url.searchParams.set(key, value);
10+
url.searchParams.set(key, value.toString());
1111
}
1212
}
1313
return url;

0 commit comments

Comments
 (0)