Skip to content
Conduit

skill

perfproof

Find the slow line in Node.js, then prove the fix: hot lines with code across every process of a command, flame graphs, and before/after benchmarks with a significance test. Built for you and your cod

feresbr1/perfproofcommunitylistedunreviewed

Set up

Skills are installed by placing their folder where your agent loads skills.

The download comes from github.com/feresbr1/perfproof itself, not from our server, so what you get is exactly what its author published.

Install instructions come from the README

Skills do not use an MCP config. Follow the steps in the README below.

README

synced Oct 26, 2026

perfproof

Find the slow line. Prove the fix.

A CPU profiler for Node.js that answers with the exact lines burning your CPU, plus a benchmark that tells you whether your change really made it faster. Works on whole command trees like npm test, and is built to be used by you or by your coding agent.

Live demo · Open a .cpuprofile · Releases · Issues

$ npx github:feresbr1/perfproof run -q -- node report.js
perfproof: profiling node report.js
perfproof · node report.js
wall 2.18 s · CPU 2.00 s · 1 process

Where the CPU time goes
  your code                         90%  ██████████████████··  1.80 s
  garbage collection               6.3%  █···················  127 ms
  native / V8 builtins             1.7%  ····················  34.2 ms
  Node.js internals                1.7%  ····················  33.7 ms

Hot lines (self time)
   1. report.js:24  summarise  1.24 s  62%
        22 |     const r = parse(line);
        23 |     if (!r) continue;
      > 24 |     if (!visitors.includes(r.ip)) visitors.push(r.ip);
        25 |     statuses[r.status] = (statuses[r.status] ?? 0) + 1;
        26 |     const key = `${r.method} ${endpoint(r.path)}`;
      hint: .includes() scans the whole array. Inside a loop that is O(n²); a Set or Map lookup is O(1).
   2. report.js:8  parse  220 ms  11%
         6 |
         7 | function parse(line) {
      >  8 |   const m = new RegExp(LINE).exec(line);
         9 |   if (!m) return null;
        10 |   return { ip: m[1], method: m[3], path: m[4], status: Number(m[5]), ms: Number(m[7]) };
      hint: A RegExp is compiled on a hot line; build it once outside the loop.
...

Save a baseline, fix those two lines (a Set, and the regex built once: that is report.fast.js), and compare:

$ perfproof bench --save before -- node report.js
perfproof bench · node report.js
  10 runs: 2.02 s ± 101 ms (95% CI) · median 2.07 s · min 1.82 s · max 2.24 s · sd 141 ms
perfproof: saved baseline "before" (.perfproof\baselines\before.json)

$ perfproof bench --compare before -- node report.fast.js
perfproof bench · node report.fast.js
  10 runs: 703 ms ± 41.5 ms (95% CI) · median 691 ms · min 633 ms · max 807 ms · sd 58.1 ms
  baseline "before": 2.02 s ± 101 ms (10 runs)

  FASTER: 2.88× faster than the baseline (-65.2% time, p < 0.0001)

Real output from examples/log-report on a Windows laptop (a few progress lines on stderr trimmed). Timings will differ on your machine.

Why

  • Profiles are hard to act on. node --cpu-prof gives you a JSON call tree that you then explore by hand in DevTools. perfproof reads it for you and ranks the lines that cost the most, with the code around them.
  • Real workloads are many processes. npm test is npm + a test runner + workers. perfproof profiles every Node.js process a command starts and merges them. (This relies on --cpu-prof in NODE_OPTIONS, which Node.js only allows since 22.15 / 23.9: nodejs/node#56944.)
  • The obvious profile can point at the wrong line. When V8 optimises a hot loop with on-stack replacement, its samples are blamed on the line that called the function. perfproof turns that off while profiling so the hot line is the one you need to change.
  • "Feels faster" is not evidence. perfproof bench saves a baseline, re-runs after your change and applies Welch's t-test, so noise is reported as noise.
  • Agents guess at performance. The text report is short, ranked and plain, so a coding agent can read it, fix the top line and back its claim with a verdict. A ready-made skill teaches it the loop.

Features

  • perfproof run -- <command>: profiles the command and all its Node.js child processes and worker threads.
    • Hot lines with source and enclosing function, plus hints for common slow patterns: linear scans in loops, regexes rebuilt per call, sync I/O, quadratic spreads, shift() queues and more.
    • Hot functions with self and total time.
    • Time split between your code, dependencies, Node.js internals, native code and GC.
    • Notes that flag heavy GC, dependency-dominated profiles, parents waiting on children, and I/O-bound runs where a CPU profile can't help.
    • Every run is saved to .perfproof/runs/<time>/ as report.md, report.json, the raw .cpuprofile files and flamegraph.html: one self-contained file with the report and a zoomable, searchable flame graph.
  • perfproof bench -- <command>: warm-up plus N timed runs, mean ± 95% CI, median, min/max.
    • --save before / --compare before for baselines, Welch's t-test, and a verdict line.
    • --fail-if-slower to gate scripts.
  • perfproof analyze <file|dir>: the same report for existing .cpuprofile files, from Node.js or Chrome DevTools.
  • Output as text (default), --md (for PRs) or --json.
  • Web viewer at feresbr1.github.io/perfproof: drop .cpuprofile files or a perfproof report.json. Everything runs in your browser; nothing is uploaded.
  • One dependency-free file. No install, no account, no native addons, no telemetry.

Quick start

Requires Node.js 22.15 or newer.

# Profile anything that runs on Node.js
npx github:feresbr1/perfproof run -- node scripts/build.js
npx github:feresbr1/perfproof run -q -- npm test

# Benchmark before and after a change
npx github:feresbr1/perfproof bench --save before -- node scripts/build.js
# ...edit the code...
npx github:feresbr1/perfproof bench --compare before -- node scripts/build.js

Prefer a shorter command? Install it globally or as a dev dependency:

npm install -g github:feresbr1/perfproof      # then: perfproof run -- ...
npm install -D github:feresbr1/perfproof      # then: npx perfproof run -- ...

Or download perfproof.mjs from the latest release and run node perfproof.mjs run -- ....

Use it in pull requests (GitHub Action)

Benchmark every pull request against its base branch, in the same job, and fail it if it makes your command significantly slower:

name: Performance
on: [pull_request]
jobs:
  perfproof:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - uses: feresbr1/perfproof@v1
        with:
          command: node scripts/build.js
          setup: npm ci # prepares the base branch's checkout

The job summary shows both timings with the Welch's t-test verdict, followed by the pull request's hot lines. On a test repository where the pull request reverted the examples/log-report fix, it reported SLOWER: takes 1.76× as long as the baseline (+75.7% time, p = 0.0016) and failed the job.

InputDefault
command(required)Command to benchmark, run in working-directory
base-refPR base branch, or the previous commit on pushWhat to compare against
setupnoneCommand run in the base checkout first (e.g. npm ci)
runs / warmup10 / 1Timed and untimed runs per side
fail-if-slowertrueFail on a significant slowdown (p < 0.05 and at least 2%)
profiletrueAdd the hot lines of the current checkout to the summary
working-directory.Where to run the command

Shared CI machines are noisy: perfproof only reports a difference when it is statistically significant, but raise runs for short commands.

Use it with a coding agent

Install the skill (Claude Code, Codex, Cursor, Gemini CLI and other agents that read SKILL.md):

npx skills add feresbr1/perfproof

Then ask "make the build faster" or "why is this test slow?". The skill tells the agent to save a baseline, profile, fix the hottest code it owns, re-benchmark, and report the verdict line instead of an impression. Without the skill, run npx github:feresbr1/perfproof guide and paste the workflow into your agent's instructions.

Usage

perfproof run [options] -- <command...>      Profile a command and every Node.js process it starts
perfproof bench [options] -- <command...>    Time a command, save a baseline, compare against it
perfproof analyze <file|dir>... [options]    Report on existing .cpuprofile files
perfproof guide                              The workflow, written for coding agents
OptionCommandsMeaning
--top <n>run, analyzeHot lines and functions to show (default 8)
--json, --mdallMachine-readable or Markdown output
-q, --quietrunHide the command's own output
--interval <µs>runSampling interval (default 500 µs)
--no-preciserunKeep V8's on-stack replacement (faster, less exact lines)
--no-saverunDon't keep the run in .perfproof/runs/
--openrun, analyzeOpen the flame graph in a browser
--html <file>analyzeWrite the flame graph HTML
-n, --runs <n>benchTimed runs (default 10)
-w, --warmup <n>benchUntimed warm-up runs (default 1)
--save [name]benchSave timings as a baseline (default name baseline)
--compare [name]benchCompare with a saved baseline
--fail-if-slowerbenchExit 1 when significantly slower than the baseline
--show-outputbenchShow the command's output

run exits with the command's own exit code, so it can wrap test suites in scripts. Baselines live in .perfproof/baselines/<name>.json; pass a path ending in .json to keep one elsewhere (for example, committed to the repo).

How the verdict works

bench runs the command warmup + n times and keeps the n timed runs. --compare applies Welch's t-test (which does not assume equal variances) to the two sets of timings. The verdict is FASTER or SLOWER only when p < 0.05 and the means differ by at least 2%; otherwise it is NO SIGNIFICANT DIFFERENCE. If runs vary by more than 10%, perfproof says so; add runs with -n.

How it works

  1. run sets NODE_OPTIONS=--cpu-prof --cpu-prof-dir=<run dir> --require=<preload>, so every Node.js process in the tree writes a V8 CPU profile when it exits.
  2. The preload switches off on-stack replacement (accurate lines) and turns an unhandled SIGTERM/SIGINT into a normal exit, so workers stopped by a test runner still write their profile.
  3. perfproof parses the profiles, weights each sample by its real duration, splits time across lines using V8's position ticks, and computes self and total time per function (recursion counted once). Function names for hot lines come from the source, so inlined code is named correctly.
  4. Hot lines in your files get a snippet and a pattern hint; everything is written as text, Markdown, JSON and a self-contained HTML flame graph.

Supported platforms and limitations

  • Node.js 22.15+ on Linux, macOS and Windows. Bun and Deno are not supported.
  • Line numbers refer to the JavaScript Node.js ran. TypeScript run with Node's built-in type stripping (node app.ts) maps to the right lines; bundled or transpiled output is reported as generated code (no source maps yet).
  • A process writes its profile only if it exits normally or by an unhandled SIGTERM/SIGINT. On Windows, workers that a test runner kills are terminated without a signal and are not captured: for Vitest use --pool=threads, or profile a single test file.
  • Precise mode disables on-stack replacement, which can slow long-running loops while profiling. Use bench (no profiler attached) for timing claims.
  • It is a CPU profiler: time spent waiting on network, disk or timers is not explained (perfproof notes when a run is mostly idle).
  • Hints are heuristics on the hot line's text, not diagnoses.

Development

npm install
npm run check        # format, lint, typecheck, unit + CLI tests, build
npm run test:e2e     # Playwright tests of the web viewer (desktop and mobile)
npm run dev          # the website
npm run build:cli && npm run demo-data   # refresh the demo from examples/log-report

Tech: TypeScript, Preact + Vite (viewer and site), esbuild (single-file CLI), Vitest, Playwright. The CLI has no runtime dependencies.

See CONTRIBUTING.md. Product reasoning and alternatives considered are in docs/PRODUCT_DECISIONS.md.

License

MIT

↑↓ navigate openesc close