Skip to content

Commit 675346d

Browse files
authored
Merge pull request #1590 from Kobzol/npm-detailed-query
Port detailed query page to TypeScript
2 parents fdbb4c1 + 917696e commit 675346d

File tree

7 files changed

+328
-564
lines changed

7 files changed

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

0 commit comments

Comments
 (0)