1import assert from "node:assert/strict"
2import { writeFileSync } from "node:fs"
3import { Session } from "node:inspector/promises"
4import { fixture, validate } from "./fixtures.mts"
5
6const [mode, name, countArgument = "1000", intervalArgument = "0", profilePath] = process.argv.slice(2)
7if (mode === "validate") {
8 validate(name)
9 process.stdout.write(JSON.stringify({ validated: name }))
10} else {
11 assert.equal(mode, "measure")
12 const count = Number(countArgument)
13 const interval = Number(intervalArgument)
14 assert.ok(Number.isSafeInteger(count) && count > 0)
15 assert.ok(Number.isSafeInteger(interval) && interval >= 0)
16 const { build } = fixture(name)
17 const roots = new Array(count)
18 // This frame delimits the construction samples. Inspector and hrtime clocks
19 // have different origins on some platforms, so they must not be compared.
20 function constructBatch() {
21 const start = process.hrtime.bigint()
22 for (let index = 0; index < count; index++) roots[index] = build(index)
23 return Number(process.hrtime.bigint() - start) / 1e6
24 }
25 const session = new Session()
26 try {
27 if (interval > 0) {
28 assert.ok(profilePath)
29 session.connect()
30 await session.post("Profiler.enable")
31 await session.post("Profiler.setSamplingInterval", { interval })
32 await session.post("Profiler.start")
33 }
34 const elapsedMs = constructBatch()
35 if (interval > 0) {
36 const { profile } = await session.post("Profiler.stop")
37 writeFileSync(profilePath, JSON.stringify(profile))
38 }
39 // Do not prepare a decoder, traverse ASTs, or warm the construction path
40 // before timing. Full semantic validation runs in separate processes.
41 assert.equal(roots.length, count)
42 for (const root of roots) assert.equal(typeof root, "function")
43 assert.notEqual(roots[0], count > 1 ? roots[count - 1] : undefined)
44 process.stdout.write(JSON.stringify({ name, count, interval, elapsedMs, profilePath }))
45 } finally {
46 session.disconnect()
47 }
48}