164 lines
5.0 KiB
JavaScript
164 lines
5.0 KiB
JavaScript
import { resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
import * as fc from 'fast-check'
|
|
|
|
import { parse, clearParseCache } from '../../dist/formula/parser.js'
|
|
import { evaluate } from '../../dist/formula/evaluator.js'
|
|
import { matchRoutePattern, findMatchingRoute } from '../../dist/infrastructure/route-matcher.js'
|
|
import { convertSchema } from '../../dist/domain/schema-to-arbitrary.js'
|
|
import { qualifyCommand } from '../../dist/cli/commands/qualify/index.js'
|
|
import { createContext } from '../../dist/cli/core/context.js'
|
|
import { getBenchOptions, measure, printResults } from './_shared.mjs'
|
|
|
|
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
const repoRoot = resolve(__dirname, '..', '..')
|
|
|
|
process.chdir(repoRoot)
|
|
|
|
const options = getBenchOptions()
|
|
const MICRO_ITERS = Number.parseInt(process.env.BENCH_INNER_ITERS ?? '2000', 10)
|
|
const generationProfiles = (process.env.BENCH_GENERATION_PROFILES ?? 'quick,standard,thorough')
|
|
.split(',')
|
|
.map((value) => value.trim())
|
|
.filter(Boolean)
|
|
|
|
const formula = 'response_code(this) == 200 && response_body(this).id != null && response_time(this) < 500'
|
|
const formulaPool = [
|
|
formula,
|
|
'response_payload(this).user.id != null && response_code(this) == 200',
|
|
'request_headers(this).authorization != null => response_code(this) != 401',
|
|
"response_body(this).name matches '^[A-Za-z ]+$'",
|
|
]
|
|
|
|
const parsedFormula = parse(formula).ast
|
|
const evalCtx = {
|
|
request: {
|
|
body: { name: 'Alice' },
|
|
headers: { authorization: 'Bearer token' },
|
|
query: {},
|
|
params: {},
|
|
cookies: {},
|
|
},
|
|
response: {
|
|
body: { id: 'usr-1', name: 'Alice' },
|
|
headers: {},
|
|
statusCode: 200,
|
|
responseTime: 42,
|
|
},
|
|
}
|
|
|
|
const routePatterns = Array.from({ length: 50 }, (_, i) => `/v1/resources/${i}/:id`)
|
|
routePatterns.push('/v1/resources/target/:id')
|
|
|
|
const complexSchema = {
|
|
type: 'object',
|
|
required: ['id', 'email', 'profile'],
|
|
properties: {
|
|
id: { type: 'string', minLength: 1, maxLength: 64 },
|
|
email: { type: 'string', format: 'email' },
|
|
tags: { type: 'array', items: { type: 'string', minLength: 1 }, minItems: 0, maxItems: 10 },
|
|
profile: {
|
|
type: 'object',
|
|
required: ['age', 'active'],
|
|
properties: {
|
|
age: { type: 'integer', minimum: 18, maximum: 90 },
|
|
active: { type: 'boolean' },
|
|
},
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
}
|
|
|
|
const qualifyCtx = createContext({
|
|
cwd: repoRoot,
|
|
quiet: true,
|
|
format: 'human',
|
|
color: 'never',
|
|
})
|
|
|
|
async function run() {
|
|
const results = []
|
|
|
|
results.push(await measure('formula.parse.cache-hit', async () => {
|
|
for (let i = 0; i < MICRO_ITERS; i++) {
|
|
parse(formula)
|
|
}
|
|
}, options))
|
|
|
|
results.push(await measure('formula.parse.cache-miss', async () => {
|
|
for (let i = 0; i < Math.max(1, Math.floor(MICRO_ITERS / 20)); i++) {
|
|
clearParseCache()
|
|
for (const candidate of formulaPool) {
|
|
parse(candidate)
|
|
}
|
|
}
|
|
}, options))
|
|
|
|
results.push(await measure('formula.evaluate', async () => {
|
|
for (let i = 0; i < MICRO_ITERS; i++) {
|
|
const result = evaluate(parsedFormula, evalCtx)
|
|
if (!result.success) {
|
|
throw new Error(result.error)
|
|
}
|
|
}
|
|
}, options))
|
|
|
|
results.push(await measure('route.match.single', async () => {
|
|
let matched = true
|
|
for (let i = 0; i < MICRO_ITERS; i++) {
|
|
matched = matchRoutePattern('/v1/resources/target/:id', '/v1/resources/target/abc-123').matched
|
|
}
|
|
if (!matched) {
|
|
throw new Error('Expected route pattern to match')
|
|
}
|
|
}, options))
|
|
|
|
results.push(await measure('route.match.collection', async () => {
|
|
let match = null
|
|
for (let i = 0; i < MICRO_ITERS; i++) {
|
|
match = findMatchingRoute(routePatterns, '/v1/resources/target/abc-123')
|
|
}
|
|
if (!match) {
|
|
throw new Error('Expected to find matching route')
|
|
}
|
|
}, options))
|
|
|
|
for (const generationProfile of generationProfiles) {
|
|
const schemaArbitrary = convertSchema(complexSchema, { context: 'request', generationProfile })
|
|
|
|
results.push(await measure(`schema.convert[${generationProfile}]`, async () => {
|
|
for (let i = 0; i < Math.max(1, Math.floor(MICRO_ITERS / 10)); i++) {
|
|
convertSchema(complexSchema, { context: 'request', generationProfile })
|
|
}
|
|
}, options))
|
|
|
|
results.push(await measure(`schema.sample[${generationProfile}]`, async () => {
|
|
for (let i = 0; i < Math.max(1, Math.floor(MICRO_ITERS / 10)); i++) {
|
|
fc.sample(schemaArbitrary, 1)
|
|
}
|
|
}, options))
|
|
|
|
const qualifyOptions = {
|
|
cwd: 'src/cli/__fixtures__/protocol-lab',
|
|
profile: 'oauth-nightly',
|
|
generationProfile,
|
|
seed: 42,
|
|
format: 'human',
|
|
}
|
|
results.push(await measure(`qualify.command.in-process[${generationProfile}]`, async () => {
|
|
const result = await qualifyCommand(qualifyOptions, qualifyCtx)
|
|
if (result.exitCode !== 0) {
|
|
throw new Error(`qualifyCommand failed with ${result.exitCode}: ${result.message ?? ''}`)
|
|
}
|
|
}, options))
|
|
}
|
|
|
|
printResults('Hot Path Benchmarks', results, options)
|
|
}
|
|
|
|
run().catch((error) => {
|
|
console.error(error)
|
|
process.exit(1)
|
|
})
|