CLI tool
discoveryjs/cpupro avatar
discoveryjs/cpupro

cpupro: a CPU profile viewer, CLI and Node.js profiler for V8 profiles

Rethinking of CPU profile analysis

789 stars16 forksTypeScriptMIT

At a glance

What is it?
CPUpro is a TypeScript rewrite of CPU profile analysis for V8 runtimes, shipping four ways to use it: a browser viewer on GitHub pages, a CLI that turns a profile into a self-contained HTML report, and a Node.js library with a preload module. It suits engineers who profile Node.js, Deno or Chromium and want one tool for V8 logs, cpuprofile files and Chromium performance traces, but the library path depends on the native v8-profiler-next package.
Who is it for?
Adopt cpupro when your profiles come from V8 runtimes and you want one viewer for V8 logs, .cpuprofile files and Chromium traces, especially if you need a self-contained HTML report you can send to someone else. Do not adopt it if you need profiles from runtimes outside V8, and check first that the native v8-profiler-next dependency builds on your platform, since the library and preload entry points depend on it while the viewer and the CLI do not.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One tool for V8 logs, cpuprofile files and Chromium traces

CPUpro is described as a rethinking of CPU profile analysis and processing, and its scope is stated in plain terms: profiles and logs of any size collected in V8 runtimes, which the README names as Node.js, Deno and Chromium browsers.

What sets it apart from a single-format viewer is the format list. The documentation lists five supported inputs: V8 log files with the .log extension, V8 logs preprocessed with --preprocess as .json, V8 CPU profiles as .cpuprofile, Chromium Performance Profile exports as .json, and Edge Enhanced Performance Traces with the .devtools extension.

Two details make that list practical. The file extension can be arbitrary, because the format is determined from the file content, and the content may be compressed with gzip or deflate. Both matter in practice: a profiler that insists on a particular extension or rejects a gzipped log will be abandoned the first time a CI job compresses its artefacts.

The homepage is a viewer hosted on GitHub pages at discoveryjs.github.io/cpupro, and the repository has no separate front end project. The top level layout shows a single TypeScript codebase with an `app/` directory for the viewer, `lib/` for the library, `bin/` for the CLI and `scripts/` for build helpers, plus several `.discoveryrc` configuration files that drive the build.

The MIT licence and the npm package name `cpupro` are both confirmed in the package manifest, where the version is 0.7.0.

The CLI writes a report with the data embedded

The CLI exists to produce a report, which the README describes as a viewer with embedded data, from a profile file. That distinction is the point of the tool: the result is a single HTML file that carries the profile inside it, so it can be opened by someone with no tooling installed and no access to your machine.

Installation is either a global npm install or a one-off invocation:

bash
npm install -g cpupro

or `npx cpupro` if you would rather not install anything. The documented invocations cover the cases you actually hit:

bash
cpupro
cpupro test.cpuprofile
cpupro - <test.cpuprofile
cat test.cpuprofile | cpupro -

The first opens the viewer with no embedded data, which is the quick way to browse a file you have not chosen yet. The second embeds a specific profile. The third and fourth read from standard input, using a bare dash as the path, and the fourth pipes a file in.

The options are printed by `cpupro -h`:

bash
cpupro [filepath] [options]

Options:

    -f, --filename <filename>    Specify a filename for a report; should ends with .htm or .html,
                                 otherwise .html will be added
    -h, --help                   Output usage information
    -n, --no-open                Prevent open a report in browser, the report will be written to file
    -o, --output-dir <path>      Specify an output path for a report (current working dir by default)
    -v, --version                Output version

Two of those options matter for automation. `--no-open` suppresses launching a browser and leaves the report on disk, which is what you want in a CI job, and `--output-dir` chooses where the file lands, since the current working directory is the default. The `--filename` option accepts .htm or .html and appends .html otherwise.

The package manifest names the executable as `./bin/cpupro` under the `bin` field, and the published `files` list includes `bin`, `lib`, `build/app.html` and `build/report.html`, so the viewer template and the report template ship with the package and work offline.

A profiling API shaped like console.profile

For Node.js programs, CPUpro exposes a library API that the documentation describes as similar to `console.profile()` and `console.profileEnd()`, with one deliberate difference: `profileEnd()` does nothing itself, it returns an object you use to save data or generate a report.

js
const profiler = require('cpupro');

profiler.profile('profileName');

// ... do something

const profile = profiler.profileEnd('profileName');

// write data to .cpuprofile file
profile.writeToFile('./path/to/demo.cpuprofile');
// or write a report (the viewer with embedded data) to file
profile.report.writeToFile('report.html');
// or open the report in a browser
profile.report.open();

The two output paths are separate and both are documented. `writeToFile` on the profile object produces a .cpuprofile file, while `profile.report.writeToFile` produces the HTML report with embedded data, and `profile.report.open()` opens that report in a browser.

Several profiles can be recorded at once, and the profile name is optional. If you would rather hold a reference than repeat a string, the documentation shows that form, where the returned object carries its own end method:

js
const profiler = require('cpupro');

const profile = profiler.profile();

// ... do something

// end profiling and open a report in a browser
profile.profileEnd().openReport();

There is a third style that changes the order of operations. You can invoke the actions at the start of profiling rather than at the end, and they will run when `profileEnd()` is called or, if it is never called, when the process exits:

js
const profiler = require('cpupro');

profiler.profile()
  .writeToFile('./path/to/demo.cpuprofile');

// calling profileEnd() is not necessary if a CPU profile should be dumped to a file upon process exit

That last pattern is the one to reach for in a long-running service or a script whose exit path you do not control.

The underlying sampling engine is the native module `v8-profiler-next`, pinned at about `~1.5.1` in the dependencies. It is a compiled dependency, which is the main portability constraint in this design.

Preload entry points decide what happens on script exit

The fourth way to use CPUpro is as a Node.js preload module, which means you do not change the script you want to profile at all. You change how Node is launched.

Recording a profile, generating a report and opening it in a browser takes the plain module path:

bash
node --require cpupro path/to/script.js

The module path accepts subpaths, and each one selects a different behaviour. `cpupro/file` records the profile, generates a report and writes it to a file, while `cpupro/file/report` does the same with an explicit report target:

bash
node --require cpupro/file path/to/script.js
# or
node --require cpupro/file/report path/to/script.js

A third subpath, `cpupro/file/data`, records the profile and writes it into a .cpuprofile file:

bash
node --require cpupro/file/data path/to/script.js

Those subpaths are real export paths, not conventions. The package manifest maps `.` to `./lib/index.js`, `./file` and `./file/report` to `./lib/require-file-report.js`, and `./file/data` to `./lib/require-file-data.js`, with `./build/*.html` and `./package.json` also exported.

This is the least invasive entry point for a codebase you cannot edit, and the most invasive in one respect: the process launches with different flags, so any wrapper script, systemd unit or container entry point has to change to match. In a container image with a fixed `CMD`, that is the detail to plan around.

A build pipeline that compiles WebAssembly and bundles twice

The build scripts in the package manifest show what this project is made of. The `build` script runs three steps in order: `npm run build-wasm`, then `npm run build-app`, then `npm run build-report-template`.

The wasm step is `node scripts/wat-compile.js`, which compiles WebAssembly from hand-written or generated WAT source. The other two steps use `discovery-build`, a tool from the same Discovery.js organisation as this project, driven by the `.discoveryrc.app.js` and `.discoveryrc.ghpages.js` configuration files. The app build uses `--single-file --no-data --entry-names "app" --script-names "cpupro-[name]"`, and the report template build uses `--single-file --no-data --no-model-data-upload --entry-names "report"`.

The `--single-file` flag on both is the reason a report can be handed to someone else. Bundling the viewer into a single HTML file, with no data, is what lets the CLI embed a profile into it and produce a portable artefact.

A separate `build-gh-pages` script produces the hosted viewer site, writing to a `.gh-pages` output directory and then running `node scripts/gh-pages-files`. The GitHub pages site is therefore generated from the same sources as the npm package rather than being a separate codebase.

The other scripts are ordinary housekeeping. `start` runs `discovery` for local development, `lint` runs `eslint lib app`, `typecheck` runs `tsc --noEmit`, and `prepublishOnly` runs the full build, so a published package always contains fresh artefacts. The `files` array in the manifest confirms what ends up in the tarball: `bin`, `build/app.html`, `build/report.html` and `lib`.

Recent releases added source code view and code states

The repository is not archived and the last push was on 2026-10-15, so work on it is current. The release history shows where the effort has gone.

Version 0.7.0, published on 2025-05-08, is titled with a list of what it added: a source code view, code states, raw V8 log support and gzipped profiles support. That is a version about inputs and about seeing what the profile refers to, rather than about new visualisation.

Version 0.6.0 from 2025-01-14 covers call stack restoration of inlined code, enhanced calculations and presentation, and a new data model. The inlined code point is the technically interesting one. When a function was inlined by the optimiser, a naive stack reconstruction loses the frame or attributes samples to the wrong function, so restoring those frames changes what the profile tells you.

Before that, 0.5.1 landed on 2024-05-11 with no elaboration in its title.

Two observations. First, the project is pre-1.0, so the data model itself changed in 0.6.0 and any tool you build on top of the library should expect that churn. Second, the version numbers and the release cadence suggest a tool that is still finding its own shape, which is normal for a rewrite of an established category.

The remaining dependencies in the manifest are small and mostly incidental: `@discoveryjs/json-ext` for JSON handling, `clap` for command line parsing and `open` for opening a file in the default browser.

Against the Chrome DevTools performance panel and against Node's own profiler

The honest comparison is with what you would otherwise use, which is usually two different tools depending on where the profile came from.

Chrome DevTools gives you an excellent performance panel, but it needs the browser open and the DevTools frontend loaded. For a `.cpuprofile` file captured in CI, or a V8 log from a production Node process, you cannot open it in DevTools at all, so you end up reading the JSON by hand or writing a converter. CPUpro's advantage is that it takes the file you already have and produces something readable.

Node's built-in `--cpu-prof` flag writes a .cpuprofile file, and the documentation names it directly among the supported formats. The difference in approach is that Node's profiler is the capture side and CPUpro is the analysis side. Using them together is the intended flow: capture with the runtime, analyse with CPUpro.

The `--cpu-prof` route also shows where CPUpro stops. It works with V8. Profiles from other engines, from a browser's non-V8 instrumentation, or from a sampling profiler that produces a different format are outside its scope, and the format list is explicit about that. If your runtime is not V8 based, this is the wrong tool and no amount of configuration will change it.

There is a third option worth naming for large data: writing your own parser. It gives full control and no dependency on a native module, but the reason CPUpro exists is that a CPU profile is a tree with sample counts, timestamps and node references, and reconstructing a readable flame view from it is a project in itself.

Editorial conclusion

Adopt cpupro when your profiles come from V8 runtimes and you want one viewer for V8 logs, .cpuprofile files and Chromium traces, especially if you need a self-contained HTML report you can send to someone else. Do not adopt it if you need profiles from runtimes outside V8, and check first that the native v8-profiler-next dependency builds on your platform, since the library and preload entry points depend on it while the viewer and the CLI do not.

Frequently asked questions

Which profile formats can cpupro open?

The documentation lists V8 log files, V8 logs preprocessed with --preprocess, V8 CPU profiles as .cpuprofile, Chromium Performance Profile exports as .json, and Edge Enhanced Performance Traces as .devtools. The file extension can be arbitrary because the format is detected from the content, and gzip or deflate compressed content is accepted.

How do I generate a report from a cpuprofile with the CLI?

Install it with `npm install -g cpupro` or use `npx cpupro`, then run `cpupro test.cpuprofile`. The report is a viewer with the profile data embedded, written to the current working directory unless you pass `--output-dir`.

Can I profile a Node.js script without changing the script itself?

Yes. Use the preload module, for example `node --require cpupro path/to/script.js` to record, report and open a report, or `node --require cpupro/file/data path/to/script.js` to write a .cpuprofile file instead.

What does the cpupro library API look like in Node.js?

It resembles `console.profile()` and `console.profileEnd()`, except that `profileEnd()` returns an object with methods such as `writeToFile` for the profile data and `profile.report.writeToFile` or `profile.report.open()` for the HTML report. Several profiles can be recorded at once, and actions can be queued at the start of profiling to run on process exit.

Does cpupro require a native module?

The dependencies include the native `v8-profiler-next` package at about `~1.5.1`, so the library and preload entry points need that compiled dependency on your platform. The hosted viewer and the report template are static HTML and do not.

Is cpupro still being worked on?

The repository is not archived and the last push was on 2026-10-15. The latest listed release is v0.7.0 from 2025-05-08, which added a source code view, code states, raw V8 log support and gzipped profile support.

Official sources

  1. discoveryjs/cpupro on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/discoveryjs-cpupro.svg)](https://hysenlabs.com/projects/discoveryjs-cpupro)

Was this useful?