Skip to content

Commit d43dd00

Browse files
authored
[HUD] TTS page: use blocklist instead of allowlist for workflow names, use CheckboxList component, permalink of multiple jobs (#6326)
A number of changes to the TTS page ROCm wanted to get TTS info about jobs on pytorch/ao, but because we use an explicit list of supported workflows, they didn't show up. Instead, use an explicit list of blocked workflows so that we don't need to add every workflow. Some cons of this include: showing way too many jobs, sending too much info over network Use CheckboxList component as the filterer for the graphs so that you can filter the jobs Add a permalink, which can handle multiple selected jobs. Previously the filtering list had links and you could choose one, and this changes it to handle multiple at the cost of making the old links stop working Use SWRImmutable since I don't think its that important that this data but refreshed all the time Other small changes include: * Getting rid of the Graph component in favor of putting it all in the parent * Moving some stuff around * Spacing change * Still rendering date pickers etc when fetching data fails Old vs new <img width="300" alt="image" src="https://github.com/user-attachments/assets/621cf44e-4ee3-423f-89be-13dcfbeacba5" /> <img width="300" alt="image" src="https://github.com/user-attachments/assets/12be8012-41df-4dc9-abc6-7b7a3b63d0e3" />
1 parent 3a7064f commit d43dd00

File tree

8 files changed

+126
-163
lines changed

8 files changed

+126
-163
lines changed

torchci/clickhouse_queries/tts_duration_historical/params.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"repo": "String",
66
"startTime": "DateTime64(3)",
77
"stopTime": "DateTime64(3)",
8-
"workflowNames": "Array(String)"
8+
"ignoredWorkflows": "Array(String)"
99
},
1010
"tests": []
11-
}
11+
}

torchci/clickhouse_queries/tts_duration_historical/query.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ FROM
1414
WHERE
1515
job.created_at >= {startTime: DateTime64(3) }
1616
AND job.created_at < {stopTime: DateTime64(3) }
17-
AND has({workflowNames: Array(String) }, workflow.name)
17+
AND not has({ignoredWorkflows: Array(String) }, workflow.name)
1818
AND workflow.head_branch LIKE {branch: String }
1919
AND workflow.run_attempt = 1
2020
AND workflow.repository. 'full_name' = {repo: String }

torchci/clickhouse_queries/tts_duration_historical_percentile/params.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"repo": "String",
77
"startTime": "DateTime64(3)",
88
"stopTime": "DateTime64(3)",
9-
"workflowNames": "Array(String)"
9+
"ignoredWorkflows": "Array(String)"
1010
},
1111
"tests": []
12-
}
12+
}

torchci/clickhouse_queries/tts_duration_historical_percentile/query.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ WITH tts_duration AS (
1111
WHERE
1212
job.created_at >= {startTime: DateTime64(3) }
1313
AND job.created_at < {stopTime: DateTime64(3) }
14-
AND has({workflowNames: Array(String) }, workflow.name)
14+
AND not has({ignoredWorkflows: Array(String) }, workflow.name)
1515
AND workflow.head_branch LIKE {branch: String }
1616
AND workflow.run_attempt = 1
1717
AND workflow.repository. 'full_name' = {repo: String }

torchci/components/common/CheckBoxList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export default function CheckBoxList({
2525
// and a list of checkboxes. Good for manual legends for charts
2626
const [filter, setFilter] = useState("");
2727
const filteredItems = Object.keys(items).filter((item) =>
28-
item.includes(filter)
28+
item.toLocaleLowerCase().includes(filter.toLocaleLowerCase())
2929
);
3030

3131
function toggleAllfilteredItems(checked: boolean) {

torchci/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"eslint-plugin-unused-imports": "^3.2.0",
4747
"jaro-winkler-typescript": "^1.0.1",
4848
"lodash": "^4.17.21",
49+
"lz-string": "^1.5.0",
4950
"minimatch": "^9.0.3",
5051
"minimist": "^1.2.6",
5152
"next": "14.2.21",
Lines changed: 113 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { Grid2, Paper, Skeleton, Stack, Typography } from "@mui/material";
1+
import { Grid2, Paper, Stack, Typography } from "@mui/material";
2+
import CheckBoxList from "components/common/CheckBoxList";
3+
import CopyLink from "components/CopyLink";
24
import GranularityPicker from "components/GranularityPicker";
3-
import styles from "components/hud.module.css";
5+
import LoadingPage from "components/LoadingPage";
46
import {
57
getTooltipMarker,
68
Granularity,
@@ -11,20 +13,21 @@ import dayjs from "dayjs";
1113
import { EChartsOption } from "echarts";
1214
import ReactECharts from "echarts-for-react";
1315
import { fetcher } from "lib/GeneralUtils";
16+
import {
17+
compressToEncodedURIComponent,
18+
decompressFromEncodedURIComponent,
19+
} from "lz-string";
1420
import { useRouter } from "next/router";
15-
import { useCallback, useState } from "react";
16-
import useSWR from "swr";
21+
import { encodeParams } from "pages/tests/search";
22+
import { useEffect, useState } from "react";
23+
import useSWRImmutable from "swr/immutable";
1724
import { TimeRangePicker, TtsPercentilePicker } from "../../../../metrics";
1825

19-
const SUPPORTED_WORKFLOWS = [
20-
"pull",
21-
"trunk",
22-
"nightly",
23-
"periodic",
24-
"inductor",
25-
"inductor-periodic",
26-
"rocm",
27-
"inductor-rocm",
26+
const INGORED_WORKFLOWS = [
27+
"Upload test stats",
28+
"Upload torch dynamo performance stats",
29+
"Validate and merge PR",
30+
"Revert merged PR",
2831
];
2932

3033
function Panel({
@@ -74,24 +77,33 @@ function Panel({
7477
);
7578
}
7679

77-
function Graphs({
78-
queryParams,
79-
granularity,
80-
ttsPercentile,
81-
checkboxRef,
82-
branchName,
83-
filter,
84-
toggleFilter,
85-
}: {
86-
queryParams: { [key: string]: any };
87-
granularity: Granularity;
88-
ttsPercentile: number;
89-
checkboxRef: any;
90-
branchName: string;
91-
filter: any;
92-
toggleFilter: any;
93-
}) {
94-
const ROW_HEIGHT = 800;
80+
export default function Page() {
81+
const router = useRouter();
82+
const repoOwner: string = (router.query.repoOwner as string) ?? "pytorch";
83+
const repoName: string = (router.query.repoName as string) ?? "pytorch";
84+
const branch: string = (router.query.branch as string) ?? "main";
85+
const jobNamesCompressed: string =
86+
(router.query.jobNamesCompressed as string) ?? "";
87+
const [startTime, setStartTime] = useState(dayjs().subtract(1, "week"));
88+
const [stopTime, setStopTime] = useState(dayjs());
89+
const [granularity, setGranularity] = useState<Granularity>("day");
90+
const [timeRange, setTimeRange] = useState<number>(7);
91+
const [ttsPercentile, setTtsPercentile] = useState<number>(0.5);
92+
const [selectedJobs, setSelectedJobs] = useState<{
93+
[key: string]: boolean;
94+
}>({});
95+
96+
const GRAPHS_HEIGHT = 800;
97+
98+
const queryParams: { [key: string]: any } = {
99+
branch: branch,
100+
granularity: granularity,
101+
percentile: ttsPercentile,
102+
repo: `${repoOwner}/${repoName}`,
103+
startTime: dayjs(startTime).utc().format("YYYY-MM-DDTHH:mm:ss.SSS"),
104+
stopTime: dayjs(stopTime).utc().format("YYYY-MM-DDTHH:mm:ss.SSS"),
105+
ignoredWorkflows: INGORED_WORKFLOWS,
106+
};
95107

96108
let queryName = "tts_duration_historical_percentile";
97109
let ttsFieldName = "tts_percentile_sec";
@@ -104,38 +116,19 @@ function Graphs({
104116
durationFieldName = "duration_avg_sec";
105117
}
106118

107-
const timeFieldName = "granularity_bucket";
108-
const groupByFieldName = "full_name";
109119
const url = `/api/clickhouse/${queryName}?parameters=${encodeURIComponent(
110120
JSON.stringify(queryParams)
111121
)}`;
112122

113-
const { data, error } = useSWR(url, fetcher, {
114-
refreshInterval: 60 * 60 * 1000, // refresh every hour
115-
});
116-
117-
if (error !== undefined) {
118-
// TODO: figure out how to deterine what error it actually is, can't just log the error
119-
// because its in html format instead of json?
120-
return (
121-
<div>
122-
error occured while fetching data, perhaps there are too many results
123-
with your choice of time range and granularity?
124-
</div>
125-
);
126-
}
127-
128-
if (data === undefined) {
129-
return <Skeleton variant={"rectangular"} height={"100%"} />;
130-
}
131-
132-
// Clamp to the nearest granularity (e.g. nearest hour) so that the times will
133-
// align with the data we get from the database
134-
const startTime = dayjs(queryParams["startTime"]).startOf(granularity);
135-
const stopTime = dayjs(queryParams["stopTime"]).startOf(granularity);
123+
const { data, error } = useSWRImmutable<{ [key: string]: any }[]>(
124+
url,
125+
fetcher
126+
);
136127

128+
const timeFieldName = "granularity_bucket";
129+
const groupByFieldName = "full_name";
137130
const tts_true_series = seriesWithInterpolatedTimes(
138-
data,
131+
data ?? [],
139132
startTime,
140133
stopTime,
141134
granularity,
@@ -144,109 +137,53 @@ function Graphs({
144137
ttsFieldName
145138
);
146139
const duration_true_series = seriesWithInterpolatedTimes(
147-
data,
140+
data ?? [],
148141
startTime,
149142
stopTime,
150143
granularity,
151144
groupByFieldName,
152145
timeFieldName,
153146
durationFieldName
154147
);
155-
var tts_series = tts_true_series.filter((item: any) =>
156-
filter.has(item["name"])
157-
);
158-
var duration_series = duration_true_series.filter((item: any) =>
159-
filter.has(item["name"])
160-
);
161148

162-
const repo = queryParams["repo"];
163-
const encodedBranchName = encodeURIComponent(branchName);
164-
const jobUrlPrefix = `/tts/${repo}/${encodedBranchName}?jobName=`;
165-
166-
return (
167-
<Grid2 container spacing={2}>
168-
<Grid2 size={{ xs: 9 }} height={ROW_HEIGHT}>
169-
<Paper sx={{ p: 2, height: "50%" }} elevation={3}>
170-
<Panel title={"tts"} series={tts_series} />
171-
</Paper>
172-
<Paper sx={{ p: 2, height: "50%" }} elevation={3}>
173-
<Panel title={"duration"} series={duration_series} />
174-
</Paper>
175-
</Grid2>
176-
<Grid2 size={{ xs: 3 }} height={ROW_HEIGHT}>
177-
<div
178-
style={{ overflow: "auto", height: ROW_HEIGHT, fontSize: "15px" }}
179-
ref={checkboxRef}
180-
>
181-
{tts_true_series.map((job) => (
182-
<div
183-
key={job["name"]}
184-
className={filter.has(job["name"]) ? styles.selectedRow : ""}
185-
>
186-
<input
187-
type="checkbox"
188-
id={job["name"]}
189-
onChange={toggleFilter}
190-
checked={filter.has(job["name"])}
191-
/>
192-
<label htmlFor={job["name"]}>
193-
<a href={jobUrlPrefix + encodeURIComponent(job["name"])}>
194-
{job["name"]}
195-
</a>
196-
</label>
197-
</div>
198-
))}
199-
</div>
200-
</Grid2>
201-
</Grid2>
149+
var tts_series = tts_true_series.filter(
150+
(item: any) => selectedJobs[item["name"]]
151+
);
152+
var duration_series = duration_true_series.filter(
153+
(item: any) => selectedJobs[item["name"]]
202154
);
203-
}
204-
205-
export default function Page() {
206-
const router = useRouter();
207-
const repoOwner: string = (router.query.repoOwner as string) ?? "pytorch";
208-
const repoName: string = (router.query.repoName as string) ?? "pytorch";
209-
const branch: string = (router.query.branch as string) ?? "main";
210-
const jobName: string = (router.query.jobName as string) ?? "none";
211-
const percentile: number =
212-
router.query.percentile === undefined
213-
? 0.5
214-
: parseFloat(router.query.percentile as string);
215-
216-
const [startTime, setStartTime] = useState(dayjs().subtract(1, "week"));
217-
const [stopTime, setStopTime] = useState(dayjs());
218-
const [timeRange, setTimeRange] = useState<number>(7);
219-
const [granularity, setGranularity] = useState<Granularity>("day");
220-
const [ttsPercentile, setTtsPercentile] = useState<number>(percentile);
221155

222-
const [filter, setFilter] = useState(new Set());
223-
function toggleFilter(e: any) {
224-
var jobName = e.target.id;
225-
const next = new Set(filter);
226-
if (filter.has(jobName)) {
227-
next.delete(jobName);
228-
} else {
229-
next.add(jobName);
156+
useEffect(() => {
157+
if (tts_true_series === undefined) {
158+
return;
230159
}
231-
setFilter(next);
232-
}
233160

234-
const queryParams: { [key: string]: any } = {
235-
branch: branch,
236-
granularity: granularity,
237-
percentile: ttsPercentile,
238-
repo: `${repoOwner}/${repoName}`,
239-
startTime: dayjs(startTime).utc().format("YYYY-MM-DDTHH:mm:ss.SSS"),
240-
stopTime: dayjs(stopTime).utc().format("YYYY-MM-DDTHH:mm:ss.SSS"),
241-
workflowNames: SUPPORTED_WORKFLOWS,
242-
};
161+
const jobNamesFromLink = JSON.parse(
162+
jobNamesCompressed != ""
163+
? decompressFromEncodedURIComponent(jobNamesCompressed)
164+
: "[]"
165+
);
243166

244-
const checkboxRef = useCallback(() => {
245-
const selectedJob = document.getElementById(jobName);
246-
if (selectedJob != undefined) {
247-
selectedJob.click();
248-
}
249-
}, [jobName]);
167+
setSelectedJobs(
168+
tts_true_series.reduce((acc: any, item: any) => {
169+
acc[item.name] = jobNamesFromLink.includes(item.name);
170+
return acc;
171+
}, {} as any)
172+
);
173+
}, [data, jobNamesCompressed]);
174+
175+
const permalink =
176+
typeof window !== "undefined" &&
177+
`${window.location.protocol}/${window.location.host}${router.asPath.replace(
178+
/\?.+/,
179+
""
180+
)}?${encodeParams({
181+
jobNamesCompressed: compressToEncodedURIComponent(
182+
JSON.stringify(
183+
Object.keys(selectedJobs).filter((key) => selectedJobs[key])
184+
)
185+
),
186+
})}`;
250187

251188
return (
252189
<div>
@@ -270,16 +207,36 @@ export default function Page() {
270207
ttsPercentile={ttsPercentile}
271208
setTtsPercentile={setTtsPercentile}
272209
/>
210+
<CopyLink textToCopy={permalink || ""} />
273211
</Stack>
274-
<Graphs
275-
queryParams={queryParams}
276-
granularity={granularity}
277-
ttsPercentile={ttsPercentile}
278-
checkboxRef={checkboxRef}
279-
branchName={branch}
280-
filter={filter}
281-
toggleFilter={toggleFilter}
282-
/>
212+
<Grid2 container spacing={2}>
213+
<Grid2 size={{ xs: 9 }} height={GRAPHS_HEIGHT}>
214+
{error !== undefined ? (
215+
<Typography>
216+
error occured while fetching data, perhaps there are too many
217+
results with your choice of time range and granularity?
218+
</Typography>
219+
) : data === undefined ? (
220+
<LoadingPage height={GRAPHS_HEIGHT} />
221+
) : (
222+
<Stack spacing={2} height={GRAPHS_HEIGHT}>
223+
<Paper sx={{ p: 2, height: "50%" }} elevation={3}>
224+
<Panel title={"tts"} series={tts_series} />
225+
</Paper>
226+
<Paper sx={{ p: 2, height: "50%" }} elevation={3}>
227+
<Panel title={"duration"} series={duration_series} />
228+
</Paper>
229+
</Stack>
230+
)}
231+
</Grid2>
232+
<Grid2 size={{ xs: 3 }} height={GRAPHS_HEIGHT} overflow={"auto"}>
233+
<CheckBoxList
234+
items={selectedJobs}
235+
onChange={setSelectedJobs}
236+
onClick={(_val) => {}}
237+
/>
238+
</Grid2>
239+
</Grid2>
283240
</div>
284241
);
285242
}

0 commit comments

Comments
 (0)