mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
Vibed. This should help with running React PRs on sandboxes to measure impact on `bench/`. ## Usage ``` use bench skill to to measure impact of https://github.com/react/react/pull/37066 ``` - Should be able to point at React or Next PRs - Compares against mergebase - Grabs React builds from CI when possible - Before benching, asserts both React builds are green, or that tests pass - Runs on many sandboxes with interleaved runs - Caches reusable stuff (builds) between sandboxes - Uses `bench/` examples with realistic payloads ## Test Plan Running a bench: <img width="1445" height="584" alt="Screenshot 2026-07-20 at 03 08 02" src="https://github.com/user-attachments/assets/ac8b6aac-1e62-4695-9a59-fd0577a8a441" /> Bench that shows good results: <img width="1078" height="725" alt="Screenshot 2026-07-20 at 03 46 20" src="https://github.com/user-attachments/assets/ce49a806-f1b1-497c-a3dd-e5f8d08ea25b" /> Neutral bench: <img width="1032" height="455" alt="Screenshot 2026-07-20 at 03 47 27" src="https://github.com/user-attachments/assets/de3b700c-4875-4c70-b1c1-55cdddd73a1a" /> --------- Co-authored-by: Claude Fable 5 <[email protected]>
87 lines
2.9 KiB
JavaScript
87 lines
2.9 KiB
JavaScript
// List/remove leaked bench sandboxes. Every VM the skill's scripts
|
|
// create is named sbench-* and tagged purpose=sandbox-bench; the
|
|
// harnesses remove their VMs in finally blocks, but a killed local
|
|
// process leaks them until their timeout.
|
|
//
|
|
// The project may be shared, so removal is deliberately conservative:
|
|
// a VM is only listed when its name matches, its tag matches, AND it is
|
|
// older than --min-age-hours (default 3 — longer than any healthy run),
|
|
// and removal is per exact listed name.
|
|
//
|
|
// node sandbox-sweep.mjs # list what would be removed
|
|
// node sandbox-sweep.mjs --yes # remove
|
|
// node sandbox-sweep.mjs --min-age-hours 0 # include fresh VMs (danger:
|
|
// # only when nothing is active)
|
|
import { execFile } from 'child_process'
|
|
import { promisify } from 'util'
|
|
import { loadConfig, sandboxScope } from './config.mjs'
|
|
|
|
const execFileP = promisify(execFile)
|
|
const CONFIG = loadConfig()
|
|
const SCOPE = sandboxScope(CONFIG)
|
|
const argv = process.argv.slice(2)
|
|
const minAgeHours = argv.includes('--min-age-hours')
|
|
? Number(argv[argv.indexOf('--min-age-hours') + 1])
|
|
: 3
|
|
|
|
// Age from the CLI's "CREATED" column ("32 minutes ago", "2 hours ago").
|
|
function ageHours(words) {
|
|
const i = words.findIndex((w) => w === 'ago')
|
|
if (i < 2) return Infinity
|
|
const n = Number(words[i - 2])
|
|
const unit = words[i - 1]
|
|
if (!Number.isFinite(n)) return Infinity
|
|
if (unit.startsWith('second')) return n / 3600
|
|
if (unit.startsWith('minute')) return n / 60
|
|
if (unit.startsWith('hour')) return n
|
|
return n * 24
|
|
}
|
|
|
|
// Paginate: the CLI prints a cursor hint line when more pages exist.
|
|
const found = []
|
|
let cursor
|
|
for (let page = 0; page < 50; page++) {
|
|
const args = ['sandbox', 'list', '--limit', '100', ...SCOPE]
|
|
if (cursor) args.push('--cursor', cursor)
|
|
const { stdout, stderr } = await execFileP(CONFIG.vercelBin, args, {
|
|
maxBuffer: 1 << 24,
|
|
})
|
|
for (const line of stdout.split('\n')) {
|
|
const words = line.trim().split(/\s+/)
|
|
const name = words[0]
|
|
if (!/^sbench-/.test(name)) continue
|
|
if (!line.includes('purpose:sandbox-bench')) continue
|
|
const age = ageHours(words)
|
|
if (age < minAgeHours) {
|
|
console.log(
|
|
`skipping ${name} (${age.toFixed(1)}h old < --min-age-hours ${minAgeHours})`
|
|
)
|
|
continue
|
|
}
|
|
found.push(name)
|
|
}
|
|
cursor = `${stdout}\n${stderr}`.match(/--cursor\s+(\S+)/)?.[1]
|
|
if (!cursor) break
|
|
}
|
|
|
|
if (!found.length) {
|
|
console.log('no leaked bench sandboxes found')
|
|
process.exit(0)
|
|
}
|
|
const doIt = argv.includes('--yes')
|
|
let failures = 0
|
|
for (const name of found) {
|
|
if (doIt) {
|
|
try {
|
|
await execFileP(CONFIG.vercelBin, ['sandbox', 'rm', name, ...SCOPE])
|
|
console.log(`removed ${name}`)
|
|
} catch (e) {
|
|
failures++
|
|
console.error(`could not remove ${name}: ${e.message.split('\n')[0]}`)
|
|
}
|
|
} else {
|
|
console.log(`would remove ${name} (pass --yes)`)
|
|
}
|
|
}
|
|
process.exit(failures ? 1 : 0)
|