fix(ci): page CI timing job reads

This commit is contained in:
Peter Steinberger
2026-06-01 14:33:25 +01:00
parent b9f181635f
commit 930bc9691b
2 changed files with 127 additions and 9 deletions

View File

@@ -2,6 +2,53 @@
import { execFileSync } from "node:child_process";
const DEFAULT_GITHUB_REPOSITORY = "openclaw/openclaw";
const RUN_JOBS_PAGE_SIZE = 20;
const RUN_JOBS_MAX_PAGES = 25;
const GH_JSON_RETRY_DELAYS_MS = [1_000, 3_000, 6_000];
function sleepSync(ms) {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
}
function parseJsonCommand(command, args, options = {}) {
let lastError;
for (let attempt = 0; attempt <= GH_JSON_RETRY_DELAYS_MS.length; attempt += 1) {
try {
return JSON.parse(
execFileSync(command, args, {
encoding: "utf8",
...options,
}),
);
} catch (error) {
lastError = error;
const message = error instanceof Error ? error.message : String(error);
const retryable = /HTTP 5\d\d|Server Error|ETIMEDOUT|ECONNRESET|EAI_AGAIN/u.test(message);
if (!retryable || attempt === GH_JSON_RETRY_DELAYS_MS.length) {
throw error;
}
sleepSync(GH_JSON_RETRY_DELAYS_MS[attempt]);
}
}
throw lastError;
}
function normalizeRunJob(job) {
return {
completedAt: job.completedAt ?? job.completed_at ?? null,
conclusion: job.conclusion ?? "",
databaseId: job.databaseId ?? job.id,
name: job.name,
startedAt: job.startedAt ?? job.started_at ?? null,
status: job.status ?? "",
};
}
export function collectRunJobsFromPages(pages) {
return pages.flatMap((page) => (Array.isArray(page.jobs) ? page.jobs.map(normalizeRunJob) : []));
}
function parseTime(value) {
if (!value || value === "0001-01-01T00:00:00Z") {
return null;
@@ -216,15 +263,37 @@ function listRecentSuccessfulCiRuns(limit) {
}
function loadRun(runId) {
return JSON.parse(
execFileSync(
"gh",
["run", "view", runId, "--json", "status,conclusion,createdAt,updatedAt,jobs"],
{
encoding: "utf8",
},
),
);
const run = parseJsonCommand("gh", [
"run",
"view",
runId,
"--json",
"status,conclusion,createdAt,updatedAt",
]);
const repository = process.env.GITHUB_REPOSITORY || DEFAULT_GITHUB_REPOSITORY;
const pages = [];
let totalCount = null;
for (let page = 1; page <= RUN_JOBS_MAX_PAGES; page += 1) {
const payload = parseJsonCommand("gh", [
"api",
"-X",
"GET",
`repos/${repository}/actions/runs/${runId}/jobs?per_page=${RUN_JOBS_PAGE_SIZE}&page=${page}`,
]);
pages.push(payload);
const jobs = Array.isArray(payload.jobs) ? payload.jobs : [];
totalCount = typeof payload.total_count === "number" ? payload.total_count : totalCount;
if (
jobs.length === 0 ||
(totalCount !== null && collectRunJobsFromPages(pages).length >= totalCount)
) {
break;
}
}
return {
...run,
jobs: collectRunJobsFromPages(pages),
};
}
function summarizeJobs(run) {

View File

@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import {
collectRunJobsFromPages,
parseRunTimingArgs,
selectLatestMainPushCiRun,
summarizePnpmStoreWarmupBarrier,
@@ -81,6 +82,54 @@ describe("scripts/ci-run-timings.mjs", () => {
});
});
it("normalizes paginated GitHub Actions job payloads", () => {
expect(
collectRunJobsFromPages([
{
jobs: [
{
completed_at: "2026-06-01T13:26:16Z",
conclusion: "success",
id: 101,
name: "preflight",
started_at: "2026-06-01T13:25:16Z",
status: "completed",
},
],
},
{
jobs: [
{
completedAt: "2026-06-01T13:28:00Z",
conclusion: "failure",
databaseId: 102,
name: "ci-timings-summary",
startedAt: "2026-06-01T13:27:00Z",
status: "completed",
},
],
},
]),
).toEqual([
{
completedAt: "2026-06-01T13:26:16Z",
conclusion: "success",
databaseId: 101,
name: "preflight",
startedAt: "2026-06-01T13:25:16Z",
status: "completed",
},
{
completedAt: "2026-06-01T13:28:00Z",
conclusion: "failure",
databaseId: 102,
name: "ci-timings-summary",
startedAt: "2026-06-01T13:27:00Z",
status: "completed",
},
]);
});
it("summarizes the pnpm store warmup fanout barrier", () => {
expect(
summarizePnpmStoreWarmupBarrier({