scripts/bench_plugin_worker_cpu.ts
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env bun
/**
* Plugin worker isolate CPU bench (cold start + hot request path).
*
* Builds the plugin worker with wrangler dry-run, boots ONE Miniflare isolate,
* and measures process.cpuUsage() around dispatchFetch:
* 1) Cold first /ok (includes module evaluation)
* 2) Warm /ok
* 3) Warm invalid /updates validation path (no DB)
*
* Usage:
* bun scripts/bench_plugin_worker_cpu.ts
* bun scripts/bench_plugin_worker_cpu.ts --save scripts/bench/plugin_worker_cpu_results.json
* bun scripts/bench_plugin_worker_cpu.ts --baseline-script scripts/bench/bundles/plugin_worker_baseline_main.js
*/
import { spawnSync } from 'node:child_process'
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { performance } from 'node:perf_hooks'
import { Miniflare } from 'miniflare'
const ROOT = resolve(import.meta.dirname, '..')
interface Sample {
name: string
wallMs: number
cpuMs: number
status: number
}
function parseArgs(argv: string[]) {
let savePath: string | undefined
let baselineScript: string | undefined
for (let i = 0; i < argv.length; i++) {
if (argv[i] === '--save')
savePath = argv[++i]
else if (argv[i] === '--baseline-script')
baselineScript = argv[++i]
}
return { savePath, baselineScript }
}
function buildPlugin(outDir: string) {
mkdirSync(outDir, { recursive: true })
const result = spawnSync('bunx', [
'wrangler',
'deploy',
'--config',
'cloudflare_workers/plugin/wrangler.jsonc',
'--env=local',
'--dry-run',
'--minify',
`--outdir=${outDir}`,
], {
cwd: ROOT,
encoding: 'utf8',
maxBuffer: 32 * 1024 * 1024,
})
if (result.status !== 0)
throw new Error(`wrangler dry-run failed:\n${result.stdout}\n${result.stderr}`)
const uploadMatch = result.stdout.match(/Total Upload:\s*([\d.]+)\s*KiB\s*\/\s*gzip:\s*([\d.]+)\s*KiB/)
return {
uploadKiB: uploadMatch ? Number.parseFloat(uploadMatch[1]) : null,
gzipKiB: uploadMatch ? Number.parseFloat(uploadMatch[2]) : null,
scriptPath: join(outDir, 'index.js'),
}
}
function createMiniflare(scriptPath: string) {
return new Miniflare({
compatibilityDate: '2026-04-21',
compatibilityFlags: ['nodejs_compat', 'nodejs_compat_populate_process_env'],
modules: [
{
type: 'ESModule',
path: 'index.js',
contents: readFileSync(scriptPath, 'utf8'),
},
],
bindings: {
ENV_NAME: 'capgo_plugin-local',
CAPGO_PREVENT_BACKGROUND_FUNCTIONS: 'true',
},
})
}
async function measureRequest(mf: Miniflare, name: string, request: Request): Promise<Sample> {
const cpu0 = process.cpuUsage()
const t0 = performance.now()
const response = await mf.dispatchFetch(request)
await response.arrayBuffer()
const wallMs = performance.now() - t0
const cpu = process.cpuUsage(cpu0)
return {
name,
wallMs,
cpuMs: (cpu.user + cpu.system) / 1000,
status: response.status,
}
}
function median(values: number[]) {
const sorted = [...values].sort((a, b) => a - b)
return sorted[Math.floor(sorted.length / 2)]
}
async function measureScript(scriptPath: string, label: string) {
const mf = createMiniflare(scriptPath)
try {
const cold = await measureRequest(mf, `${label}_cold_ok`, new Request('http://local/ok'))
const warmOk: Sample[] = []
for (let i = 0; i < 25; i++)
warmOk.push(await measureRequest(mf, `${label}_warm_ok_${i}`, new Request('http://local/ok')))
const invalidBody = JSON.stringify({
app_id: 'not a domain',
device_id: 'bad',
version_name: '1.0.0',
version_build: '1.0.0',
is_emulator: false,
is_prod: true,
platform: 'ios',
plugin_version: 'nope',
})
const warmInvalid: Sample[] = []
for (let i = 0; i < 25; i++) {
warmInvalid.push(await measureRequest(
mf,
`${label}_warm_updates_invalid_${i}`,
new Request('http://local/updates', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: invalidBody,
}),
))
}
return {
label,
bytes: readFileSync(scriptPath).byteLength,
coldOk: {
wallMs: cold.wallMs,
cpuMs: cold.cpuMs,
status: cold.status,
},
warmOk: {
medianWallMs: median(warmOk.map(s => s.wallMs)),
medianCpuMs: median(warmOk.map(s => s.cpuMs)),
statuses: [...new Set(warmOk.map(s => s.status))],
},
warmUpdatesInvalid: {
medianWallMs: median(warmInvalid.map(s => s.wallMs)),
medianCpuMs: median(warmInvalid.map(s => s.cpuMs)),
statuses: [...new Set(warmInvalid.map(s => s.status))],
},
}
}
finally {
// Hard-exit path: dispose can hang on this workerd version; rely on process exit.
void mf.dispose().catch(() => {})
}
}
async function main() {
const args = parseArgs(process.argv.slice(2))
const outDir = mkdtempSync(join(tmpdir(), 'capgo-plugin-cpu-'))
console.log(`Building plugin worker into ${outDir}`)
const built = buildPlugin(outDir)
console.log(`upload ${built.uploadKiB} KiB / gzip ${built.gzipKiB} KiB`)
let baseline = null as Awaited<ReturnType<typeof measureScript>> | null
if (args.baselineScript)
baseline = await measureScript(resolve(ROOT, args.baselineScript), 'baseline')
const current = await measureScript(built.scriptPath, 'current')
console.log('\n=== Plugin worker isolate CPU ===')
console.log(`[current] cold /ok: wall ${current.coldOk.wallMs.toFixed(2)} ms | cpu ${current.coldOk.cpuMs.toFixed(2)} ms | status ${current.coldOk.status}`)
console.log(`[current] warm /ok: wall ${current.warmOk.medianWallMs.toFixed(2)} ms | cpu ${current.warmOk.medianCpuMs.toFixed(2)} ms | status ${current.warmOk.statuses.join(',')}`)
console.log(`[current] warm invalid /updates: wall ${current.warmUpdatesInvalid.medianWallMs.toFixed(2)} ms | cpu ${current.warmUpdatesInvalid.medianCpuMs.toFixed(2)} ms | status ${current.warmUpdatesInvalid.statuses.join(',')}`)
if (baseline) {
const pct = (a: number, b: number) => {
const v = ((b - a) / a) * 100
return `${v >= 0 ? '+' : ''}${v.toFixed(1)}%`
}
console.log('\n=== Comparative (baseline → current) ===')
console.log(`bytes: ${baseline.bytes} → ${current.bytes}`)
console.log(`cold cpu: ${baseline.coldOk.cpuMs.toFixed(2)} → ${current.coldOk.cpuMs.toFixed(2)} ms (${pct(baseline.coldOk.cpuMs, current.coldOk.cpuMs)})`)
console.log(`warm /ok cpu: ${baseline.warmOk.medianCpuMs.toFixed(2)} → ${current.warmOk.medianCpuMs.toFixed(2)} ms (${pct(baseline.warmOk.medianCpuMs, current.warmOk.medianCpuMs)})`)
console.log(`warm invalid /updates cpu: ${baseline.warmUpdatesInvalid.medianCpuMs.toFixed(2)} → ${current.warmUpdatesInvalid.medianCpuMs.toFixed(2)} ms (${pct(baseline.warmUpdatesInvalid.medianCpuMs, current.warmUpdatesInvalid.medianCpuMs)})`)
}
const report = {
generatedAt: new Date().toISOString(),
bundle: {
uploadKiB: built.uploadKiB,
gzipKiB: built.gzipKiB,
},
current,
baseline,
}
if (args.savePath) {
const abs = resolve(ROOT, args.savePath)
mkdirSync(resolve(abs, '..'), { recursive: true })
writeFileSync(abs, `${JSON.stringify(report, null, 2)}\n`)
console.log(`\nSaved ${abs}`)
}
// Miniflare/workerd dispose can hang; force exit after reporting.
process.exit(0)
}
main().catch((error) => {
console.error(error)
process.exit(1)
})