scripts/bench_plugin_validation_cpu.ts
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env bun
/**
* Comparative validation CPU bench for plugin request bodies.
*
* Contenders (same logical update payload shape):
* 1) handrolled Standard Schema (current plugin production path)
* 2) zod runtime
* 3) zod-compiler AOT (`--emit bag`)
*
* Measures process.cpuUsage() user+system ms (exact CPU, not wall guess).
*
* Usage:
* bun scripts/bench_plugin_validation_cpu.ts
* bun scripts/bench_plugin_validation_cpu.ts --save scripts/bench/plugin_validation_cpu_results.json
*/
import { spawnSync } from 'node:child_process'
import { mkdirSync, writeFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
import { performance } from 'node:perf_hooks'
const ROOT = resolve(import.meta.dirname, '..')
interface CpuBenchRow {
name: string
iterations: number
wallMs: number
cpuMs: number
nsCpuPerOp: number
successCount: number
failCount: number
}
function parseArgs(argv: string[]) {
let savePath: string | undefined
for (let i = 0; i < argv.length; i++) {
if (argv[i] === '--save')
savePath = argv[++i]
}
return { savePath }
}
function ensureCompiledZod() {
const out = resolve(ROOT, 'scripts/bench/validation/plugin_schemas.zod.compiled.ts')
const src = resolve(ROOT, 'scripts/bench/validation/plugin_schemas.zod.ts')
const result = spawnSync('bunx', ['zod-compiler', 'generate', src, '-o', out, '--emit', 'bag'], {
cwd: ROOT,
encoding: 'utf8',
})
if (result.status !== 0)
throw new Error(`zod-compiler generate failed:\n${result.stdout}\n${result.stderr}`)
}
function validPayload() {
return {
app_id: 'com.demo.app',
device_id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
version_name: '1.2.3',
version_build: '1.2.3',
is_emulator: false,
is_prod: true,
platform: 'ios' as const,
plugin_version: '6.8.1',
defaultChannel: 'production',
key_id: 'key_1',
}
}
function invalidPayload() {
return {
...validPayload(),
app_id: 'not a domain',
device_id: 'bad',
plugin_version: 'nope',
}
}
function benchCpu(name: string, iterations: number, fn: (input: unknown) => boolean, inputs: unknown[]): CpuBenchRow {
for (let i = 0; i < Math.min(2000, iterations); i++)
fn(inputs[i % inputs.length])
let successCount = 0
let failCount = 0
const cpu0 = process.cpuUsage()
const t0 = performance.now()
for (let i = 0; i < iterations; i++) {
if (fn(inputs[i % inputs.length]))
successCount++
else
failCount++
}
const wallMs = performance.now() - t0
const cpu = process.cpuUsage(cpu0)
const cpuMs = (cpu.user + cpu.system) / 1000
return {
name,
iterations,
wallMs,
cpuMs,
nsCpuPerOp: (cpuMs * 1e6) / iterations,
successCount,
failCount,
}
}
async function main() {
const args = parseArgs(process.argv.slice(2))
ensureCompiledZod()
const handrolled = await import(pathToFileURL(resolve(ROOT, 'supabase/functions/_backend/plugin_runtime/utils/plugin_validation.ts')).href)
const { safeParseSchema } = await import(pathToFileURL(resolve(ROOT, 'supabase/functions/_backend/utils/schema_validation.ts')).href)
const zodRuntime = await import(pathToFileURL(resolve(ROOT, 'scripts/bench/validation/plugin_schemas.zod.ts')).href)
const zodCompiled = await import(pathToFileURL(resolve(ROOT, 'scripts/bench/validation/plugin_schemas.zod.compiled.ts')).href)
const prodIs = await import(pathToFileURL(resolve(ROOT, 'supabase/functions/_backend/plugin_runtime/utils/plugin_schemas/update_request.is.ts')).href)
const validators = {
handrolled: (input: unknown) => safeParseSchema(handrolled.updateRequestSchema, input).success,
production_is_predicate: (input: unknown) => prodIs.isUpdateRequestBody(input),
zod_runtime: (input: unknown) => zodRuntime.updateRequestSchemaZod.safeParse(input).success,
zod_compiler_safeParse: (input: unknown) => zodCompiled.updateRequestSchemaZod.safeParse(input).success,
zod_compiler_is: (input: unknown) => zodCompiled.updateRequestSchemaZod.is(input),
}
const valid = validPayload()
const invalid = invalidPayload()
const mixed = Array.from({ length: 16 }, (_, i) => (i % 4 === 0 ? invalid : valid))
const iterations = 80_000
const agreement = {
valid: Object.fromEntries(Object.entries(validators).map(([k, fn]) => [k, fn(valid)])),
invalid: Object.fromEntries(Object.entries(validators).map(([k, fn]) => [k, fn(invalid)])),
}
const rows: CpuBenchRow[] = []
for (const [name, fn] of Object.entries(validators)) {
rows.push(benchCpu(`${name}_valid`, iterations, fn, [valid]))
rows.push(benchCpu(`${name}_mixed`, iterations, fn, mixed))
}
console.log('\n=== Plugin validation CPU bench ===')
console.log('agreement', JSON.stringify(agreement, null, 2))
console.log('\nlower cpuMs / nsCpuPerOp is better')
for (const row of rows) {
console.log(
`${row.name.padEnd(34)} cpu ${row.cpuMs.toFixed(2).padStart(8)} ms | ${row.nsCpuPerOp.toFixed(1).padStart(8)} ns/op | wall ${row.wallMs.toFixed(2).padStart(8)} ms | ok/fail ${row.successCount}/${row.failCount}`,
)
}
const byName = Object.fromEntries(rows.map(row => [row.name, row]))
const comparisons = [
['zod_runtime_valid', 'handrolled_valid'],
['zod_compiler_is_valid', 'handrolled_valid'],
['zod_compiler_is_valid', 'zod_runtime_valid'],
]
console.log('\n=== Comparative deltas (A → B, negative means B faster) ===')
for (const [a, b] of comparisons) {
const left = byName[a]
const right = byName[b]
if (!left || !right)
continue
const pct = ((right.cpuMs - left.cpuMs) / left.cpuMs) * 100
console.log(`${a} → ${b}: ${left.nsCpuPerOp.toFixed(1)} → ${right.nsCpuPerOp.toFixed(1)} ns/op (${pct >= 0 ? '+' : ''}${pct.toFixed(1)}% cpu)`)
}
const report = {
generatedAt: new Date().toISOString(),
iterations,
agreement,
rows,
}
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}`)
}
}
main().catch((error) => {
console.error(error)
process.exit(1)
})