Files
orca/config/scripts/ci-shard-assignment.mjs
T
Neil 403b62a8d8 ci: balance existing unit and E2E shards using recorded timings (#20367)
* ci: balance existing unit and E2E shards using recorded timings

* ci: fix timing refresh units and deferred-menu test traversal

* ci: preserve isolated E2E window launch policy

* ci: keep diagnostic artifact outages from failing tests
2026-09-12 03:10:05 -07:00

67 lines
2.3 KiB
JavaScript

import { createHash } from 'node:crypto'
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { dirname } from 'node:path'
export const compareIds = (a, b) => (a < b ? -1 : a > b ? 1 : 0)
export function balanceFiles(files, count, timings, overheadMs = 0) {
if (!Number.isInteger(count) || count < 1) {
throw new Error('Invalid shard count')
}
if (new Set(files).size !== files.length) {
throw new Error('Duplicate discovered file')
}
const known = Object.values(timings).filter((value) => Number.isFinite(value) && value > 0)
known.sort((a, b) => a - b)
const fallbackMs = known[Math.floor(known.length / 2)] ?? 1000
const weighted = files.map((file) => ({
file,
durationMs:
(Number.isFinite(timings[file]) && timings[file] > 0 ? timings[file] : fallbackMs) +
overheadMs
}))
weighted.sort((a, b) => b.durationMs - a.durationMs || compareIds(a.file, b.file))
const shards = Array.from({ length: count }, () => ({ files: [], durationMs: 0 }))
for (const entry of weighted) {
const target = shards.reduce((best, shard) =>
shard.durationMs < best.durationMs ||
(shard.durationMs === best.durationMs && shard.files.length < best.files.length)
? shard
: best
)
target.files.push(entry.file)
target.durationMs += entry.durationMs
}
for (const shard of shards) {
shard.files.sort(compareIds)
}
const assigned = shards.flatMap((shard) => shard.files).sort(compareIds)
if (JSON.stringify(assigned) !== JSON.stringify([...files].sort(compareIds))) {
throw new Error('Shard coverage differs from discovery')
}
return { algorithm: 'file-lpt-v1', fallbackMs, overheadMs, shards }
}
export function readTimingBaseline(suite) {
const bytes = readFileSync(new URL('./ci-shard-timings.json', import.meta.url), 'utf8')
const baseline = JSON.parse(bytes)
return { ...baseline[suite], baselineSha256: createHash('sha256').update(bytes).digest('hex') }
}
export function writeAssignment(path, assignment) {
mkdirSync(dirname(path), { recursive: true })
writeFileSync(
path,
`${JSON.stringify(
{
sourceSha: process.env.ORCA_SHARD_SOURCE_SHA ?? process.env.GITHUB_SHA ?? null,
runId: process.env.GITHUB_RUN_ID ?? null,
runAttempt: process.env.GITHUB_RUN_ATTEMPT ?? null,
...assignment
},
null,
2
)}\n`
)
}