Files
orca/config/scripts/ci-unit-selection.mjs
T
Neil f8f656ca19 perf(ci): spend fewer concurrency slots per pull request (#23810)
A concurrency slot is charged per job, not per core, and the account's cap is
the scarce resource: standard runner minutes are free and unlimited on a public
repository. Two paths spent slots that bought nothing.

The unit matrix ran eight fixed shards averaging 6.5 minutes each, 3384
job-slots a day and 68% of all slot demand, while the arm pool queued 10.5
minutes at p95 — the queue was the oversharding. Five shards run the same work
in ~10.5 minutes each for three fewer slots per run.

Bun profile persistence escalated to all six platforms on `config/`,
`resources/` and `.github/` wholesale, which took 36.5% of the last 1100
commits through the full matrix where a platform-flavoured predicate takes 19%.
A pull request now qualifies one platform unless the change is platform-
flavoured, and the push to main re-qualifies all six, so an unescalated miss
surfaces minutes after merge rather than at the next cron. Missing changed-file
evidence and an unavailable dependency graph still fail closed to all six.
2026-09-29 00:13:33 -07:00

69 lines
2.7 KiB
JavaScript

import { unitConsumers } from './ci-unit-dependency-graph.mjs'
import { balanceFiles } from './ci-shard-assignment.mjs'
export function selectUnitFiles(files, changed, graph) {
const full = (reason) => ({ files, reason, full: true })
if (!changed.length) {
return full('Missing changed-path evidence')
}
if (changed.some((file) => !file.startsWith('src/') || !graph.files.has(file))) {
return full('Global, deleted, renamed or unknown input')
}
for (const file of changed) {
const consumers = unitConsumers([file], graph.reverse)
if (!files.some((test) => consumers.has(test))) {
return full('No proven test coverage for changed inputs')
}
}
const affected = unitConsumers([...changed, ...graph.opaque], graph.reverse)
const selected = files.filter((file) => affected.has(file))
if (!selected.length) {
return full('No proven test coverage for changed inputs')
}
return {
files: selected,
reason: 'Transitive imports plus indirect-input consumers',
full: false
}
}
// A concurrency slot is charged per job, not per core, so eight 6.5-minute shards cost eight of
// the account's slots and made the unit matrix 68% of daily slot demand. Against the checked-in
// baseline, five shards each carry 24.7 test-minutes over four workers plus ~2.6 minutes of fixed
// setup, so ~8.8 minutes -- less than the 10.5-minute p95 queue the oversharding was causing.
export const FULL_SHARD_COUNT = 5
export function planUnitSelection({ files, changed, graph, timings, event, mode = 'shadow' }) {
const candidate = selectUnitFiles(files, changed, graph)
const selected = mode === 'selected' && event?.pull_request?.draft === true && !candidate.full
const executionFiles = selected ? candidate.files : files
const totalMs = balanceFiles(executionFiles, 1, timings).shards[0].durationMs
const count = selected
? Math.max(1, Math.min(FULL_SHARD_COUNT, Math.ceil(totalMs / 900_000), executionFiles.length))
: FULL_SHARD_COUNT
return {
version: 1,
mode: selected ? 'selected' : 'shadow',
selectionAvailable: !candidate.full,
reason: candidate.reason,
files,
candidateFiles: candidate.files,
executionFiles,
shards: Array.from({ length: count }, (_, index) => ({ index: index + 1, count }))
}
}
export function auditUnitSelection(plan, results) {
const candidates = new Set(plan.candidateFiles)
const omittedFailures = Object.entries(results)
.filter(([file, state]) => state === 'failed' && !candidates.has(file))
.map(([file]) => file)
return {
mode: plan.mode,
discovered: plan.files.length,
candidate: candidates.size,
executed: Object.keys(results).length,
omittedFailures
}
}