Skip to content

Commit 9308e62

Browse files
authored
[CRCR] Add metrics page with success-rate graphs (#8318)
## Summary Adds a new CRCR Metrics page at `hud.pytorch.org/crcr/metrics` with time-series charts showing: - **Pass Rate Over Time** — daily success rate per downstream repo (line chart) - **Failures Over Time** — daily failure count per repo (stacked bar chart) - **Total Runs Over Time** — daily run volume per repo (stacked bar chart) Uses the existing `TimeSeriesPanel` (echarts) component with a new `crcr_success_rate` ClickHouse query that aggregates daily success/failure/timed_out counts from `crcr_workflow_job`. ### Changes - **New ClickHouse query**: `crcr_success_rate` — daily pass_rate, failures, total by repo - **New page**: `pages/crcr/metrics.tsx` — metrics page with 3 charts and time range selector - **NavBar**: Added "CRCR Metrics" link - **CRCR Summary**: Added link to metrics page in description text Resolves #8308 ## Test plan - [ ] Verify ClickHouse query returns expected results - [ ] Check charts render correctly with time range selector (7d, 14d, 30d, 90d) - [ ] Verify "CRCR Metrics" link appears in navbar - [ ] Verify "success-rate trends" link on CRCR summary page navigates correctly - [ ] Verify dark mode works (echarts theme)
1 parent 10e2f53 commit 9308e62

5 files changed

Lines changed: 296 additions & 1 deletion

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"params": {
3+
"days": "UInt64"
4+
},
5+
"tests": [
6+
{
7+
"days": "30"
8+
}
9+
]
10+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
SELECT
2+
toDate(started_at) AS day,
3+
downstream_repo AS repo,
4+
countIf(conclusion = 'success') AS successes,
5+
countIf(conclusion = 'failure') AS failures,
6+
countIf(conclusion = 'timed_out') AS timed_out,
7+
count() AS total,
8+
if(total > 0, successes / total, 0) AS pass_rate
9+
FROM
10+
default.crcr_workflow_job FINAL
11+
WHERE
12+
started_at > now() - INTERVAL {days: UInt64} DAY
13+
AND status = 'completed'
14+
GROUP BY
15+
day, repo
16+
ORDER BY
17+
day ASC, repo ASC

torchci/components/layout/NavBar.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ function NavBar() {
125125
name: "Claude Billing",
126126
href: "/claude_billing",
127127
},
128+
{
129+
name: "CRCR Metrics",
130+
href: "/crcr/metrics",
131+
},
128132
].map((item) => ({
129133
label: item.name,
130134
route: item.href,

torchci/pages/crcr/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,11 @@ export default function CrcrSummaryPage() {
491491
rel="noreferrer"
492492
>
493493
HUD dashboard
494-
</Link>
494+
</Link>{" "}
495+
or view{" "}
496+
<NextLink href="/crcr/metrics" passHref legacyBehavior>
497+
<Link underline="hover">success-rate trends</Link>
498+
</NextLink>
495499
.
496500
</Typography>
497501

torchci/pages/crcr/metrics.tsx

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
import {
2+
Box,
3+
FormControl,
4+
InputLabel,
5+
Link,
6+
MenuItem,
7+
Select,
8+
SelectChangeEvent,
9+
Skeleton,
10+
Stack,
11+
Typography,
12+
} from "@mui/material";
13+
import {
14+
seriesWithInterpolatedTimes,
15+
TimeSeriesPanelWithData,
16+
} from "components/metrics/panels/TimeSeriesPanel";
17+
import dayjs from "dayjs";
18+
import utc from "dayjs/plugin/utc";
19+
import { fetcher } from "lib/GeneralUtils";
20+
import Head from "next/head";
21+
import NextLink from "next/link";
22+
import { useMemo, useState } from "react";
23+
import useSWR from "swr";
24+
dayjs.extend(utc);
25+
26+
interface SuccessRateRow {
27+
day: string;
28+
repo: string;
29+
successes: number;
30+
failures: number;
31+
timed_out: number;
32+
total: number;
33+
pass_rate: number;
34+
}
35+
36+
function PassRateChart({
37+
data,
38+
days,
39+
}: {
40+
data: SuccessRateRow[];
41+
days: number;
42+
}) {
43+
const startTime = dayjs.utc().subtract(days, "day").startOf("day");
44+
const stopTime = dayjs.utc().endOf("day");
45+
46+
const series = seriesWithInterpolatedTimes(
47+
data,
48+
startTime,
49+
stopTime,
50+
"day",
51+
"repo",
52+
"day",
53+
"pass_rate",
54+
true,
55+
true,
56+
"name",
57+
"line"
58+
);
59+
60+
return (
61+
<Box sx={{ height: 420 }}>
62+
<TimeSeriesPanelWithData
63+
data={data}
64+
series={series}
65+
title="Pass Rate Over Time"
66+
groupByFieldName="repo"
67+
yAxisRenderer={(v: number) => `${(v * 100).toFixed(0)}%`}
68+
yAxisLabel="Pass Rate"
69+
additionalOptions={{
70+
yAxis: { min: 0, max: 1 },
71+
}}
72+
useUTC
73+
/>
74+
</Box>
75+
);
76+
}
77+
78+
function FailuresChart({
79+
data,
80+
days,
81+
}: {
82+
data: SuccessRateRow[];
83+
days: number;
84+
}) {
85+
const startTime = dayjs.utc().subtract(days, "day").startOf("day");
86+
const stopTime = dayjs.utc().endOf("day");
87+
88+
const series = seriesWithInterpolatedTimes(
89+
data,
90+
startTime,
91+
stopTime,
92+
"day",
93+
"repo",
94+
"day",
95+
"failures",
96+
true,
97+
false,
98+
"name",
99+
"stacked_bar"
100+
);
101+
102+
return (
103+
<Box sx={{ height: 420 }}>
104+
<TimeSeriesPanelWithData
105+
data={data}
106+
series={series}
107+
title="Failures Over Time"
108+
groupByFieldName="repo"
109+
yAxisRenderer={(v: number) => String(Math.round(v))}
110+
yAxisLabel="Failures"
111+
useUTC
112+
/>
113+
</Box>
114+
);
115+
}
116+
117+
function TotalRunsChart({
118+
data,
119+
days,
120+
}: {
121+
data: SuccessRateRow[];
122+
days: number;
123+
}) {
124+
const startTime = dayjs.utc().subtract(days, "day").startOf("day");
125+
const stopTime = dayjs.utc().endOf("day");
126+
127+
const series = seriesWithInterpolatedTimes(
128+
data,
129+
startTime,
130+
stopTime,
131+
"day",
132+
"repo",
133+
"day",
134+
"total",
135+
true,
136+
false,
137+
"name",
138+
"stacked_bar"
139+
);
140+
141+
return (
142+
<Box sx={{ height: 420 }}>
143+
<TimeSeriesPanelWithData
144+
data={data}
145+
series={series}
146+
title="Total Runs Over Time"
147+
groupByFieldName="repo"
148+
yAxisRenderer={(v: number) => String(Math.round(v))}
149+
yAxisLabel="Total Runs"
150+
useUTC
151+
/>
152+
</Box>
153+
);
154+
}
155+
156+
export default function CrcrMetricsPage() {
157+
const [days, setDays] = useState(30);
158+
159+
const url = `/api/clickhouse/crcr_success_rate?parameters=${encodeURIComponent(
160+
JSON.stringify({ days: String(days) })
161+
)}`;
162+
const { data, error } = useSWR<SuccessRateRow[]>(url, fetcher, {
163+
refreshInterval: 5 * 60_000,
164+
});
165+
166+
const repos = useMemo(() => {
167+
if (!data) return [];
168+
return [...new Set(data.map((r) => r.repo))].sort();
169+
}, [data]);
170+
171+
const isLoading = !data && !error;
172+
173+
return (
174+
<>
175+
<Head>
176+
<title>CRCR Metrics | PyTorch HUD</title>
177+
</Head>
178+
<Stack spacing={3} sx={{ p: 3, maxWidth: 1400, mx: "auto" }}>
179+
<Box display="flex" justifyContent="space-between" alignItems="center">
180+
<Typography variant="h4">CRCR Metrics</Typography>
181+
<FormControl size="small" sx={{ minWidth: 140 }}>
182+
<InputLabel>Time Range</InputLabel>
183+
<Select
184+
value={days}
185+
label="Time Range"
186+
onChange={(e: SelectChangeEvent<number>) =>
187+
setDays(Number(e.target.value))
188+
}
189+
>
190+
<MenuItem value={7}>Last 7 days</MenuItem>
191+
<MenuItem value={14}>Last 14 days</MenuItem>
192+
<MenuItem value={30}>Last 30 days</MenuItem>
193+
<MenuItem value={90}>Last 90 days</MenuItem>
194+
</Select>
195+
</FormControl>
196+
</Box>
197+
198+
<Typography variant="body2" color="text.secondary">
199+
Success rate and failure trends for all CRCR-registered downstream
200+
repos.{" "}
201+
<NextLink href="/crcr" passHref legacyBehavior>
202+
<Link underline="hover">Back to CRCR Summary</Link>
203+
</NextLink>
204+
</Typography>
205+
206+
{error && (
207+
<Typography color="error">
208+
{error.message || "Failed to load metrics data"}
209+
</Typography>
210+
)}
211+
212+
{isLoading && (
213+
<Stack spacing={2}>
214+
<Skeleton variant="rectangular" height={420} />
215+
<Skeleton variant="rectangular" height={420} />
216+
</Stack>
217+
)}
218+
219+
{data && (
220+
<Stack spacing={3}>
221+
<PassRateChart data={data} days={days} />
222+
<FailuresChart data={data} days={days} />
223+
<TotalRunsChart data={data} days={days} />
224+
</Stack>
225+
)}
226+
227+
{data && repos.length > 0 && (
228+
<Box>
229+
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
230+
Repos in view ({repos.length}):
231+
</Typography>
232+
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
233+
{repos.map((repo) => (
234+
<NextLink
235+
key={repo}
236+
href={`/crcr/${repo}`}
237+
passHref
238+
legacyBehavior
239+
>
240+
<Link
241+
underline="hover"
242+
sx={{
243+
fontSize: "0.85rem",
244+
px: 1,
245+
py: 0.25,
246+
bgcolor: "action.hover",
247+
borderRadius: 1,
248+
}}
249+
>
250+
{repo}
251+
</Link>
252+
</NextLink>
253+
))}
254+
</Box>
255+
</Box>
256+
)}
257+
</Stack>
258+
</>
259+
);
260+
}

0 commit comments

Comments
 (0)