npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

bun-profiler

v0.4.3

Published

Continuous CPU profiling for Bun (JavaScriptCore) via Pyroscope/Grafana ingest — zero native dependencies

Readme

bun-profiler

Continuous CPU profiling for Bun via Pyroscope / Grafana — zero native dependencies.

Why this exists

Every other Node.js profiler package (@pyroscope/nodejs, @datadog/pprof, etc.) segfaults or silently fails on Bun because they call V8-specific native APIs that don't exist in JavaScriptCore (JSC). This package uses Bun's built-in node:inspector Profiler API directly, converts CDP profiles to Pyroscope's folded-stack format, and pushes them to your Pyroscope server.

Requirements

  • Bun ≥ 1.3.7
  • A running Pyroscope instance (self-hosted or Grafana Cloud)

Install

bun add bun-profiler

Usage

import { startProfiling } from "bun-profiler";

// Fire-and-forget — call at app startup
startProfiling({
  pyroscopeUrl: "http://localhost:4040",
  appName: "my-service",
});

For manual start/stop control:

import { BunPyroscope } from "bun-profiler";

const profiler = new BunPyroscope({
  pyroscopeUrl: "http://localhost:4040",
  appName: "my-service",
});

await profiler.start();

// Later, e.g. in tests or graceful shutdown:
await profiler.stop(); // flushes final profile before disconnecting

Configuration

| Option | Type | Default | Description | |---|---|---|---| | pyroscopeUrl | string | required | Pyroscope server URL | | appName | string | SERVICE_NAME env / npm_package_name / "bun-app" | Application name | | sampleIntervalUs | number | 10000 (10ms) | Sampling interval in microseconds | | pushIntervalMs | number | 15000 (15s) | How often to flush profiles | | labels | Record<string, string> | {} | Extra labels (merged with auto-detected) | | authToken | string | — | Bearer token for auth | | basicAuth | { username, password } | — | Basic auth credentials | | maxRetries | number | 2 | Push retry attempts before dropping window | | debug | boolean | false | Log debug info to stderr | | wallTime | { enabled: boolean } | { enabled: false } | Wall-time profiling (opt-in) | | heap | { enabled, samplingIntervalBytes? } | { enabled: false } | Heap allocation profiling (opt-in) |

Wall-time profiling

CPU profiling only captures on-CPU time — what your code does when it's actively executing JavaScript. For I/O-heavy servers that spend most time waiting on external APIs, databases, or network calls, CPU profiles miss the full picture.

Wall-time profiling weights stacks by elapsed wall-clock time (microseconds) and keeps (idle) frames visible, so you can see where wall-clock time actually goes — including I/O waits.

startProfiling({
  pyroscopeUrl: "http://localhost:4040",
  appName: "my-api-server",
  wallTime: { enabled: true },
});

When enabled, an additional wall profile stream is pushed alongside cpu. In Pyroscope/Grafana, select the my-api-server.wall{} stream to see the wall-time flamegraph.

Wall-time profiling adds no extra sampling overhead — it reuses the same CDP profile data as CPU profiling, just weights it by timeDeltas (actual elapsed microseconds per sample) instead of counting samples.

Heap profiling

Opt-in allocation profiling tracks where memory is being allocated:

startProfiling({
  pyroscopeUrl: "http://localhost:4040",
  appName: "my-service",
  heap: { enabled: true, samplingIntervalBytes: 32_768 },
});

When enabled, an alloc_space profile stream is pushed alongside cpu.

Bun limitation: Bun's JavaScriptCore runtime does not currently implement HeapProfiler.enable. When heap profiling is enabled on Bun, the profiler logs a warning and continues with CPU-only profiling. Heap profiling works on Node.js/V8. This will be supported once Bun adds HeapProfiler to their inspector implementation.

Auto-detected labels

The following labels are added automatically when the corresponding environment variables are set:

| Label | Environment variable(s) | |---|---| | service_name | SERVICE_NAME, npm_package_name, or appName option | | service_version | SERVICE_VERSION, npm_package_version | | environment | NODE_ENV, BUN_ENV | | hostname | os.hostname() (always present) | | fly_region | FLY_REGION | | fly_app_name | FLY_APP_NAME | | aws_region | AWS_REGION, AWS_DEFAULT_REGION | | railway_region | RAILWAY_REGION | | railway_service | RAILWAY_SERVICE_NAME | | pod_name | POD_NAME | | k8s_namespace | K8S_NAMESPACE |

Extra labels passed via the labels option override auto-detected values.

Local development

# Start Pyroscope
docker run -p 4040:4040 grafana/pyroscope

# Run checks (typecheck + lint + tests)
bun l

# Build
bun run build

How it works

  1. Connects to Bun's embedded JavaScriptCore inspector via node:inspector/promises
  2. Every pushIntervalMs: stops the profiler, converts the CDP profile to folded stacks, gzip-compresses it, and POSTs to POST /ingest
  3. Immediately restarts profiling — no gap in coverage
  4. On SIGTERM/SIGINT: flushes the current window before exiting

Why not Bun.jsc.profile()?

Bun exposes bun:jsc with a profile() function, but it's not suitable for continuous profiling:

  • Wrong output formatBun.jsc.profile() returns a SamplingProfile with pre-formatted text strings (.functions, .bytecodes, .stackTraces), not structured CDP/V8 JSON with nodes/samples/timeDeltas. There's no way to convert this to folded stacks without writing a brittle text parser.
  • No start/stop controlBun.jsc.startSamplingProfiler() has no corresponding stop function. It's a fire-and-forget debug tool that writes to a directory, not a programmatic API.
  • node:inspector already works — Bun added full node:inspector Profiler support in v1.3.7 (November 2024). This is the same CDP API that Chrome DevTools uses, returning proper CdpProfile objects that convert directly to Pyroscope's folded-stack format. That's why the minimum Bun version is 1.3.7.

If you're on Bun < 1.3.7, you'll get a clear error from start() explaining the requirement. Upgrade Bun and it works out of the box.

Graceful shutdown

Signal handlers are installed automatically. On SIGTERM or SIGINT, the profiler flushes the current window and disconnects before re-emitting the signal so your process exits normally.

Release

bun run release:patch   # 0.1.0 → 0.1.1  (bug fixes)
bun run release:minor   # 0.1.0 → 0.2.0  (new features)
bun run release:major   # 0.1.0 → 1.0.0  (breaking changes)

Bumps package.json, commits, tags, and pushes. GitHub Actions publishes to npm automatically via OIDC trusted publishing — no token required.

License

MIT


Built by mewc · ChartCastr