Ctrl K
ComunidadGuías…

scripts/bench_zod_stable_cpu.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bun
/**
 * Stable CPU + memory bench for Zod version bumps (no z.compile).
 *
 * Measures existing validation paths only:
 * - production `.is` predicate
 * - zod-compiler AOT `.is`
 * - Zod runtime `safeParse`
 * - representative backend org schema `safeParse`
 *
 * Usage:
 *   bun scripts/bench_zod_stable_cpu.ts
 *   BENCH_RUNS=5 bun scripts/bench_zod_stable_cpu.ts /workspace
 */

import { spawnSync } from 'node:child_process'
import { resolve } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { performance } from 'node:perf_hooks'

const SCRIPT_PATH = fileURLToPath(import.meta.url)
const cliArgs = process.argv.slice(2)
const memWorkerCase = cliArgs[0] === '--mem-worker' ? cliArgs[1] : undefined
const ROOT = resolve(memWorkerCase ? cliArgs[2] : cliArgs[0] ?? resolve(import.meta.dirname, '..'))
const RUNS = Math.max(1, Number.parseInt(process.env.BENCH_RUNS ?? '1', 10) || 1)
const ITERATIONS = 80_000

interface CpuRow {
  name: string
  runs: number
  iterations: number
  nsCpuPerOp: number
  nsCpuPerOpStd: number
}

interface MemRow {
  name: string
  runs: number
  heapUsedDeltaMB: number
  rssDeltaMB: number
}

function forceGc() {
  if (typeof Bun !== 'undefined' && 'gc' in Bun && typeof Bun.gc === 'function')
    Bun.gc(true)
  else if (globalThis.gc)
    globalThis.gc()
}

async function readZodVersion(root: string) {
  const mod = await import(pathToFileURL(resolve(root, 'node_modules/zod/package.json')).href) as {
    default?: { version?: string }
    version?: string
  }
  return mod.default?.version ?? mod.version ?? 'unknown'
}

async function importZodFromRoot(root: string) {
  return import(pathToFileURL(resolve(root, 'node_modules/zod/index.js')).href)
}

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(process.execPath, ['x', 'zod-compiler', 'generate', src, '-o', out, '--emit', 'bag'], {
    cwd: ROOT,
    encoding: 'utf8',
  })
  if (result.status === null)
    throw new Error(`zod-compiler generate failed to start: ${result.error?.message ?? 'unknown error'}`)
  if (result.status !== 0)
    throw new Error(`zod-compiler generate failed:\n${result.stdout}\n${result.stderr}`)
}

function validUpdatePayload() {
  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 invalidUpdatePayload() {
  return {
    ...validUpdatePayload(),
    app_id: 'not a domain',
    device_id: 'bad',
    plugin_version: 'nope',
  }
}

function validOrgPayload() {
  return {
    id: '11111111-1111-4111-8111-111111111111',
    created_by: '22222222-2222-4222-8222-222222222222',
    created_at: '2026-01-01T00:00:00.000Z',
    updated_at: '2026-01-01T00:00:00.000Z',
    logo: null,
    name: 'Example Org',
    management_email: 'org-admin@example.com',
    customer_id: null,
    website: null,
  }
}

function mixedUpdateInputs(validRatio = 0.75) {
  const valid = validUpdatePayload()
  const invalid = invalidUpdatePayload()
  const invalidEvery = Math.max(1, Math.round(1 / (1 - validRatio)))
  return Array.from({ length: 16 }, (_, i) => (i % invalidEvery === 0 ? invalid : valid))
}

function benchCpuOnce(name: string, iterations: number, fn: (input: unknown) => boolean, inputs: unknown[]) {
  for (let i = 0; i < Math.min(2000, iterations); i++)
    fn(inputs[i % inputs.length])

  const cpu0 = process.cpuUsage()
  const t0 = performance.now()
  for (let i = 0; i < iterations; i++)
    fn(inputs[i % inputs.length])
  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,
  }
}

function summarizeCpu(name: string, iterations: number, samples: number[]) {
  const mean = samples.reduce((sum, value) => sum + value, 0) / samples.length
  const variance = samples.reduce((sum, value) => sum + (value - mean) ** 2, 0) / samples.length
  return {
    name,
    runs: samples.length,
    iterations,
    nsCpuPerOp: mean,
    nsCpuPerOpStd: Math.sqrt(variance),
  }
}

function summarizeMem(name: string, samples: Array<{ heapUsedDeltaMB: number, rssDeltaMB: number }>) {
  const heapUsedDeltaMB = samples.reduce((sum, row) => sum + row.heapUsedDeltaMB, 0) / samples.length
  const rssDeltaMB = samples.reduce((sum, row) => sum + row.rssDeltaMB, 0) / samples.length
  return {
    name,
    runs: samples.length,
    heapUsedDeltaMB,
    rssDeltaMB,
  }
}

function measureMemInChild(caseName: string) {
  const result = spawnSync(process.execPath, [SCRIPT_PATH, '--mem-worker', caseName, ROOT], {
    cwd: ROOT,
    encoding: 'utf8',
    env: process.env,
  })
  if (result.status === null)
    throw new Error(`memory worker failed to start for ${caseName}: ${result.error?.message ?? 'unknown error'}`)
  if (result.status !== 0)
    throw new Error(`memory worker ${caseName} failed:\n${result.stdout}\n${result.stderr}`)
  return JSON.parse(result.stdout.trim()) as { heapUsedDeltaMB: number, rssDeltaMB: number }
}

async function runMemWorker(caseName: string, root: string) {
  const zodRuntime = await import(pathToFileURL(resolve(root, 'scripts/bench/validation/plugin_schemas.zod.ts')).href)
  const { z } = await importZodFromRoot(root)
  const valid = validUpdatePayload()
  const retained: unknown[] = []

  forceGc()
  const before = process.memoryUsage()

  if (caseName === '80k_runtime_parse_heap_rss') {
    for (let i = 0; i < ITERATIONS; i++)
      retained.push(zodRuntime.updateRequestSchemaZod.safeParse(valid))
  }
  else if (caseName === '100x_z_string_heap') {
    for (let i = 0; i < 100; i++)
      retained.push(z.string())
  }
  else {
    throw new Error(`unknown memory worker case: ${caseName}`)
  }

  forceGc()
  const after = process.memoryUsage()
  void retained.length

  console.log(JSON.stringify({
    heapUsedDeltaMB: (after.heapUsed - before.heapUsed) / 1024 / 1024,
    rssDeltaMB: (after.rss - before.rss) / 1024 / 1024,
  }))
}

async function main() {
  ensureCompiledZod()

  const prodIs = await import(pathToFileURL(resolve(ROOT, 'supabase/functions/_backend/plugin_runtime/utils/plugin_schemas/update_request.is.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 { z } = await importZodFromRoot(ROOT)

  const orgSchema = z.object({
    id: z.uuid(),
    created_by: z.uuid(),
    created_at: z.union([z.string(), z.date()]),
    updated_at: z.union([z.string(), z.date()]),
    logo: z.string().nullable(),
    name: z.string(),
    management_email: z.email(),
    customer_id: z.string().nullable(),
    website: z.string().nullable(),
  })

  const valid = validUpdatePayload()
  const mixed = mixedUpdateInputs(0.75)
  const orgValid = validOrgPayload()

  const cpuCases: Array<{ name: string, fn: (input: unknown) => boolean, inputs: unknown[] }> = [
    {
      name: 'plugin_is_predicate_valid',
      fn: input => prodIs.isUpdateRequestBody(input),
      inputs: [valid],
    },
    {
      name: 'zod_compiler_is_valid',
      fn: input => zodCompiled.updateRequestSchemaZod.is(input),
      inputs: [valid],
    },
    {
      name: 'zod_runtime_safeParse_valid',
      fn: input => zodRuntime.updateRequestSchemaZod.safeParse(input).success,
      inputs: [valid],
    },
    {
      name: 'zod_runtime_safeParse_mixed_75pct_valid',
      fn: input => zodRuntime.updateRequestSchemaZod.safeParse(input).success,
      inputs: mixed,
    },
    {
      name: 'backend_org_schema_safeParse_valid',
      fn: input => orgSchema.safeParse(input).success,
      inputs: [orgValid],
    },
  ]

  const cpuRows: CpuRow[] = []
  for (const testCase of cpuCases) {
    const samples: number[] = []
    for (let run = 0; run < RUNS; run++)
      samples.push(benchCpuOnce(testCase.name, ITERATIONS, testCase.fn, testCase.inputs).nsCpuPerOp)
    cpuRows.push(summarizeCpu(testCase.name, ITERATIONS, samples))
  }

  const memCaseNames = ['80k_runtime_parse_heap_rss', '100x_z_string_heap'] as const
  const memRows: MemRow[] = []
  for (const caseName of memCaseNames) {
    const samples = []
    for (let run = 0; run < RUNS; run++)
      samples.push(measureMemInChild(caseName))
    memRows.push(summarizeMem(caseName, samples))
  }

  const zodVersion = await readZodVersion(ROOT)

  console.log(`\n=== Zod stable CPU + memory bench (zod@${zodVersion}, runs=${RUNS}) ===`)
  console.log('\nCPU (lower ns/op is better):')
  for (const row of cpuRows) {
    console.log(
      `${row.name.padEnd(40)} ${row.nsCpuPerOp.toFixed(1).padStart(10)} ns/op ± ${row.nsCpuPerOpStd.toFixed(1)}`,
    )
  }

  console.log('\nMemory (lower delta is better; each case runs in a fresh child process):')
  for (const row of memRows) {
    console.log(
      `${row.name.padEnd(40)} heap Δ ${row.heapUsedDeltaMB.toFixed(3).padStart(8)} MB | rss Δ ${row.rssDeltaMB.toFixed(3).padStart(8)} MB`,
    )
  }
}

if (memWorkerCase) {
  runMemWorker(memWorkerCase, ROOT).catch((error) => {
    console.error(error)
    process.exit(1)
  })
}
else {
  main().catch((error) => {
    console.error(error)
    process.exit(1)
  })
}