Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5795,6 +5795,7 @@ jobs:
EXPLICIT_ONLY_JOBS: ${{ needs.generate-matrix.outputs.explicit_only_jobs }}
JOBS: ${{ inputs.jobs }}
RUNTIME_ARTIFACTS: ${{ runner.temp }}/e2e-runtime-audit
RUNTIME_SUMMARY_FILE: ${{ runner.temp }}/e2e-runtime-summary.json
TARGETS: ${{ inputs.targets }}
with:
script: |
Expand All @@ -5811,15 +5812,19 @@ jobs:
const runtimeAudit = require(
path.join(process.env.GITHUB_WORKSPACE, 'scripts/audit-test-runtime.mts'),
);
const runtimeHistory = require(
path.join(process.env.GITHUB_WORKSPACE, 'scripts/scorecard/analyze-runtime-history.mts'),
);
const needs = ${{ toJSON(needs) }};

// GitHub's jobs API is the canonical source because `needs.live`
// collapses every matrix target into one result and has no job URL.
// The typed helper owns and tests the degraded `needs` fallback.
const apiJobs = await scorecardJobs.loadWorkflowRunJobs({ github, context, core });
let runtimeSummaryMarkdown;
let runtimeRows = [];
try {
const runtimeRows = runtimeAudit.auditTestRuntime([process.env.RUNTIME_ARTIFACTS]);
runtimeRows = runtimeAudit.auditTestRuntime([process.env.RUNTIME_ARTIFACTS]);
runtimeSummaryMarkdown = runtimeAudit.formatRuntimeAuditSummary(runtimeRows);
} catch {
core.warning('E2E test phase runtime summary unavailable: invalid progress artifact');
Expand All @@ -5830,6 +5835,11 @@ jobs:
'',
].join('\n');
}
const runtimeHistoryMarkdown = await runtimeHistory.buildRuntimeHistory(
{ github, context, core },
runtimeRows,
process.env.RUNTIME_SUMMARY_FILE,
);
const trace = await traceTiming.buildTraceTimingResult({ github, context, core });
if (trace.budgetWarningMessage) core.warning(trace.budgetWarningMessage);

Expand All @@ -5848,7 +5858,9 @@ jobs:
today: new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
});

await core.summary.addRaw(`${summaryMarkdown}\n\n${runtimeSummaryMarkdown}`).write();
await core.summary
.addRaw(`${summaryMarkdown}\n\n${runtimeSummaryMarkdown}\n${runtimeHistoryMarkdown}`)
.write();
core.setOutput('scorecardData', JSON.stringify(scorecardData));
core.setOutput('slackData', JSON.stringify(slackData));

Expand Down Expand Up @@ -5909,3 +5921,12 @@ jobs:
if (!response.ok) {
core.setFailed(`Slack webhook returned ${response.status}`);
}

- name: Upload E2E runtime summary
if: ${{ always() && steps.scorecard.outcome == 'success' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: e2e-runtime-summary
path: ${{ runner.temp }}/e2e-runtime-summary.json
if-no-files-found: error
retention-days: 14
13 changes: 13 additions & 0 deletions scripts/audit-test-runtime.mts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export interface RuntimeAuditRow {
p95Ms: number;
maxMs: number;
variabilityMs: number;
passedRuns: number;
failedRuns: number;
skippedRuns: number;
slowestPhase: string;
slowestPhaseMs: number;
slowestPhaseOutcome: "passed" | "failed" | "skipped";
Expand Down Expand Up @@ -78,6 +81,12 @@ function median(sorted: readonly number[]): number {
return sorted[middle] ?? 0;
}

function summaryOutcome(summary: ProgressSummary): "passed" | "failed" | "skipped" {
if (summary.phases.some((phase) => phase.outcome === "failed")) return "failed";
if (summary.phases.some((phase) => phase.outcome === "skipped")) return "skipped";
return "passed";
}

export function auditTestRuntime(roots: readonly string[]): RuntimeAuditRow[] {
const summaries = roots.flatMap(progressFiles).map((file) => {
const parsed: unknown = JSON.parse(fs.readFileSync(file, "utf8"));
Expand Down Expand Up @@ -108,6 +117,7 @@ export function auditTestRuntime(roots: readonly string[]): RuntimeAuditRow[] {
);
const medianMs = median(durations);
const p95Ms = percentile(durations, 0.95);
const outcomes = runs.map(summaryOutcome);
return {
target: [first.targetId ?? "unlabeled", first.shardId].filter(Boolean).join("/"),
scenario: first.scenario,
Expand All @@ -116,6 +126,9 @@ export function auditTestRuntime(roots: readonly string[]): RuntimeAuditRow[] {
p95Ms,
maxMs: durations.at(-1) ?? 0,
variabilityMs: Math.max(0, p95Ms - medianMs),
passedRuns: outcomes.filter((outcome) => outcome === "passed").length,
failedRuns: outcomes.filter((outcome) => outcome === "failed").length,
skippedRuns: outcomes.filter((outcome) => outcome === "skipped").length,
slowestPhase: slowestPhase.label,
slowestPhaseMs: slowestPhase.durationMs,
slowestPhaseOutcome: slowestPhase.outcome,
Expand Down
Loading
Loading