ts-flight
v1.0.0
Published
Flight recorder for Node.js processes: runs a target script as a child process, attaches a probe, and records memory, CPU, event-loop delay, active handles, stdio and crashes into a single JSON/HTML/Markdown report.
Maintainers
Readme
Flight Recorder
A profiler for Node.js processes. Flight runs a target script as a child process, attaches a probe to it, and records what the process did while it ran: memory, CPU, event-loop delay, active handles, stdio, and crashes. When the child exits it writes a single JSON report, and optionally an HTML and Markdown view of the same data.

How it works
Flight never instruments your code. It spawns
node --import probe/register.ts <target> [target args...]The probe (probe/register.ts) runs inside the child before the target loads.
On a fixed interval (--sample-ms, default 250 ms) it samples
process.memoryUsage(), process.cpuUsage(), the event-loop delay histogram
(monitorEventLoopDelay), and active handles, and it listens for warning,
unhandledRejection, and uncaughtException. Each sample is appended as one
JSON line to an NDJSON file under out/.
The parent (src/run.ts) owns the child: it forwards SIGINT/SIGTERM,
enforces --timeout-ms, and force-kills after --kill-timeout-ms if the child
ignores the shutdown signal. When the child closes, the parent reads the NDJSON
stream, folds it into a canonical report (src/report.ts, schemaVersion: 1),
and writes it out.
Requirements
Node.js with native TypeScript support and --import of .ts files (developed
on Node 24). No build step, no runtime dependencies — @types/node and
typescript are dev-only.
Usage
Run a target and write the default JSON report (reports/flight.json):
npm run flight -- fixtures/hello.tsWrite the HTML and Markdown views as well:
npm run flight -- --html reports/flight.html --markdown reports/flight.md fixtures/buffer-leak.tsFlight exits with the child's exit code, so it slots into a shell pipeline or CI step unchanged.
Run options
| Flag | Description |
| --- | --- |
| --json <path> | JSON report path (default reports/flight.json) |
| --html <path> | Also write a self-contained HTML report |
| --markdown <path> | Also write a Markdown summary |
| --sample-ms <ms> | Probe sample interval (default 250) |
| --timeout-ms <ms> | Stop the target after this deadline |
| --kill-timeout-ms <ms> | Force-kill after a forwarded shutdown (default 5000) |
| --env KEY=value | Set a child environment variable (repeatable) |
| --env-file <path> | Load child environment values from a file |
| --node-flag <flag> | Pass a Node runtime flag to the child (repeatable) |
| --runs <n> | Run the target n times and aggregate (default 1) |
| --max-rss <bytes> | Exit 1 if peak RSS exceeds this |
| --max-loop-delay-ms <ms> | Exit 1 if max event-loop delay exceeds this |
| --fail-on-failures | Exit 1 if the child records any fatal failure |
| --intermediate-reports | Also write the per-aspect debug files under reports/ |
Secret-looking child environment values (keys matching TOKEN, SECRET,
PASSWORD, KEY, CREDENTIAL) are redacted in the report.
Budget gate
--max-rss, --max-loop-delay-ms, and --fail-on-failures turn a run into a
pass/fail check. Each breach prints a BUDGET VIOLATION: line to stderr and
forces a non-zero exit, so a single run can guard a threshold in CI:
npm run flight -- --max-rss 134217728 --fail-on-failures fixtures/buffer-leak.tsMulti-run aggregation
A single run is noisy. --runs N runs the target N times sequentially, writes
each run to reports/runs/run-NN.json, and writes an aggregate report whose
standard numeric fields hold the mean across runs:
npm run flight -- --runs 5 --json reports/agg.json --markdown reports/agg.md fixtures/hello.tsThe aggregate keeps the canonical report shape, plus an aggregate block with
mean / p95 / min / max / stddev and the raw per-run values for each
metric. Because the shape is unchanged, compare reads an aggregate report
directly (see below).
Comparing reports
compare diffs two or more reports against a baseline and writes a Markdown
table of deltas:
npm run flight -- compare reports/before.json reports/after.json --markdown reports/diff.mdThe first input is the baseline unless you pass --baseline <path>. It compares
duration, peak memory (RSS / heap / external / array buffers), total CPU, max
event-loop delay, exit status, and warning / failure counts.
--fail-on-regression <percent> makes compare exit 1 when any metric worsens
beyond the given percentage versus the baseline — pairing aggregate reports with
this flag gives a statistically meaningful regression check.
Reports
- JSON — the canonical report. Stable
schemaVersion, consumed bycompare. This is the source of truth. - HTML — a self-contained page (shown above): summary cards, a metrics table, a warnings/failures table, and the event timeline.
- Markdown — a short summary table plus a timeline excerpt, for pasting into a PR or issue.
Layout
probe/register.ts probe injected into the child via --import
src/run.ts spawns the child, owns signals/timeouts, parses the probe stream
src/report.ts buildReport (canonical JSON) + HTML and Markdown renderers
src/aggregate.ts --runs aggregation (mean/p95/min/max/stddev)
src/compare.ts compare mode: baseline diff, regression gate
src/budget.ts --max-rss / --max-loop-delay-ms / --fail-on-failures checks
src/cli.ts argument parsing and the run / compare entry points
src/utils.ts probe parsing, formatting, and stat helpers
fixtures/ sample targets (leaks, CPU loops, warnings, crashes, servers)Fixtures
fixtures/ holds targets that exercise specific behaviours — buffer-leak.ts
grows RSS, cpu-loop.ts burns CPU, warning.ts emits a process warning,
unhandled-rejection.ts and uncaught-exception.ts crash, hanging-server.ts
keeps the event loop alive. Use them to see what each section of a report looks
like.
Credits
Built as a lab following @ishtms's Node.js material.
