

























@@ -70,6 +70,28 @@ function getLatestCiRunId() {
7070return String(runId);
7171}
727273+function listRecentSuccessfulCiRuns(limit) {
74+const raw = execFileSync(
75+"gh",
76+[
77+"run",
78+"list",
79+"--branch",
80+"main",
81+"--workflow",
82+"CI",
83+"--limit",
84+String(Math.max(limit * 4, limit)),
85+"--json",
86+"databaseId,headSha,status,conclusion",
87+],
88+{ encoding: "utf8" },
89+);
90+return JSON.parse(raw)
91+.filter((run) => run.status === "completed" && run.conclusion === "success")
92+.slice(0, limit);
93+}
94+7395function loadRun(runId) {
7496return JSON.parse(
7597execFileSync(
@@ -82,6 +104,62 @@ function loadRun(runId) {
82104);
83105}
84106107+function summarizeJobs(run) {
108+const created = parseTime(run.createdAt);
109+const updated = parseTime(run.updatedAt);
110+const jobs = (run.jobs ?? [])
111+.filter((job) => !job.name?.startsWith("matrix."))
112+.map((job) => {
113+const started = parseTime(job.startedAt);
114+const completed = parseTime(job.completedAt);
115+return {
116+conclusion: job.conclusion ?? "",
117+durationSeconds: secondsBetween(started, completed),
118+name: job.name,
119+queueSeconds: secondsBetween(created, started),
120+ started,
121+ completed,
122+status: job.status,
123+};
124+})
125+.filter((job) => job.started !== null && job.completed !== null);
126+const successfulDurations = jobs
127+.filter((job) => job.status === "completed" && job.conclusion === "success")
128+.map((job) => job.durationSeconds)
129+.filter((duration) => duration !== null);
130+const firstStart = Math.min(...jobs.map((job) => job.started));
131+const lastComplete = Math.max(...jobs.map((job) => job.completed));
132+133+return {
134+avgDurationSeconds:
135+successfulDurations.length === 0
136+ ? null
137+ : Math.round(
138+successfulDurations.reduce((sum, duration) => sum + duration, 0) /
139+successfulDurations.length,
140+),
141+executionWindowSeconds:
142+Number.isFinite(firstStart) && Number.isFinite(lastComplete)
143+ ? secondsBetween(firstStart, lastComplete)
144+ : null,
145+firstQueueSeconds: Number.isFinite(firstStart) ? secondsBetween(created, firstStart) : null,
146+jobCount: successfulDurations.length,
147+maxDurationSeconds: successfulDurations.length === 0 ? null : Math.max(...successfulDurations),
148+p90DurationSeconds: percentile(successfulDurations, 0.9),
149+p95DurationSeconds: percentile(successfulDurations, 0.95),
150+wallSeconds: secondsBetween(created, updated),
151+};
152+}
153+154+function percentile(values, percentileValue) {
155+if (values.length === 0) {
156+return null;
157+}
158+const sorted = [...values].toSorted((left, right) => left - right);
159+const index = Math.min(sorted.length - 1, Math.ceil(sorted.length * percentileValue) - 1);
160+return sorted[index];
161+}
162+85163function printSection(title, jobs, metric) {
86164console.log(title);
87165for (const job of jobs) {
@@ -93,12 +171,39 @@ function printSection(title, jobs, metric) {
9317194172async function main() {
95173const args = process.argv.slice(2);
174+const recentIndex = args.indexOf("--recent");
96175const limitIndex = args.indexOf("--limit");
97176const limit =
98177limitIndex === -1 ? 15 : Math.max(1, Number.parseInt(args[limitIndex + 1] ?? "", 10) || 15);
178+if (recentIndex !== -1) {
179+const recentLimit = Math.max(1, Number.parseInt(args[recentIndex + 1] ?? "", 10) || 10);
180+for (const run of listRecentSuccessfulCiRuns(recentLimit)) {
181+const summary = summarizeJobs(loadRun(run.databaseId));
182+console.log(
183+[
184+`CI run ${run.databaseId}`,
185+run.headSha.slice(0, 10),
186+`wall=${formatSeconds(summary.wallSeconds)}`,
187+`exec=${formatSeconds(summary.executionWindowSeconds)}`,
188+`firstQueue=${formatSeconds(summary.firstQueueSeconds)}`,
189+`jobs=${summary.jobCount}`,
190+`avg=${formatSeconds(summary.avgDurationSeconds)}`,
191+`p90=${formatSeconds(summary.p90DurationSeconds)}`,
192+`p95=${formatSeconds(summary.p95DurationSeconds)}`,
193+`max=${formatSeconds(summary.maxDurationSeconds)}`,
194+].join(" "),
195+);
196+}
197+return;
198+}
99199const runId =
100-args.find((arg, index) => index !== limitIndex && index !== limitIndex + 1) ??
101-getLatestCiRunId();
200+args.find(
201+(arg, index) =>
202+index !== limitIndex &&
203+index !== limitIndex + 1 &&
204+index !== recentIndex &&
205+index !== recentIndex + 1,
206+) ?? getLatestCiRunId();
102207const summary = summarizeRunTimings(loadRun(runId), limit);
103208104209console.log(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。