pprof-nodejs
Native V8 CPU wall-time and heap profiler for Node.js producing pprof-format profiles.
Repository Health
Technical Analysis
@datadog/pprof is a Node.js profiling library that captures wall-clock CPU and heap allocation profiles directly from V8’s native profiler APIs and serializes them into the standard pprof protobuf format used by Google’s pprof toolchain and the Datadog Continuous Profiler. It ships a native addon (built with node-gyp, with prebuilt binaries for common platforms) alongside a TypeScript API exposing time and heap namespaces for starting, stopping, and collecting profiles either programmatically or via node --require.
Beyond basic sampling, it includes Datadog-specific extensions: async-context-aware sampling using V8’s Continuation-Preserving Embedder Data (CPED) on Node 22+, a workaround for a known V8 SIGPROF processing bug, automatic heap-limit extension to capture a profile just before an out-of-memory crash, source-map resolution for transpiled stacks, and an OpenTelemetry thread-local context writer that links profiling samples back to trace spans.
What You Get
- A
timenamespace for starting/stopping wall-clock CPU profiling sessions and collecting pprof-format profiles - A
heapnamespace for sampling heap allocation profiles, including out-of-memory monitoring with automatic heap-limit extension - Prebuilt native binaries for Linux, macOS, and Windows across supported Node.js versions, with a node-gyp fallback for unsupported platforms
- Source-map support to resolve profiled stack frames back to original TypeScript/pre-transpiled source
- An OpenTelemetry thread-local context writer for correlating profiling samples with distributed traces
Common Use Cases
- Continuous production profiling - running the wall-time profiler under Datadog’s or another APM’s continuous profiler to catch CPU hotspots without attaching a debugger
- Postmortem OOM diagnosis - enabling monitorOutOfMemory so a heap profile is captured automatically right before a process would otherwise crash
- Ad-hoc performance investigation - requiring the module with
node --require @datadog/pprof app.jsto capture a one-off wall-time profile for local analysis with the pprof CLI - Async-aware profiling - using CPED-based context propagation to attribute CPU samples to the async operation that triggered them, useful in high-concurrency Node services
Under The Hood
Architecture
The package layers a TypeScript public API (ts/src/index.ts) over a native Node addon: time-profiler.ts and heap-profiler.ts hold module-level singleton state (gProfiler, gStore, gSourceMapper) and delegate sampling to native bindings (bindings/profilers/wall.cc, bindings/profilers/heap.cc) that call V8’s CPU and heap profiler APIs directly, including a custom SIGPROF/CPED-based sampler for Node 22+. Native profile trees are translated to JS objects by translate-time-profile.cc/translate-heap-profile.cc, then profile-serializer.ts converts them into pprof-format Profile objects, gzip-encoded by profile-encoder.ts. Changing the core profile-tree shape would ripple through both the C++ translation layer and the JS serializer, and the reliance on global module state (rather than instantiable profiler objects) is the main architectural wart, offset by explicit lifecycle guards (gProfiler presence checks) and documented workarounds like handleStopRestart for a V8 stuck-event-loop bug.
Tech Stack
TypeScript compiled via tsc (targeting es2020, CommonJS) sits above a C++ addon built with node-gyp/nan against V8’s public profiler headers (v8-profiler.h), shipped as prebuilt binaries via node-gyp-build for Linux/macOS/Windows across supported Node versions, falling back to a local node-gyp build otherwise. Serialization uses pprof-format for protobuf encoding and source-map (v0.8) for stack resolution; context propagation uses AsyncLocalStorage and, on newer Node releases, --experimental-async-context-frame. CI runs a GitHub Actions build matrix (ubuntu) plus AppVeyor for Windows and a mirrored GitLab CI pipeline, with a dedicated package-size check workflow.
Code Quality
Twenty-four TypeScript test files under ts/test/ cover the time profiler, heap profiler, source-mapper, worker-thread and OpenTelemetry context propagation, and OOM heap-limit-extension paths, complemented by a separate C++ tap-based test suite (bindings/test/binding.cc) for the native layer - unusually thorough for a project with a native component. Error handling is explicit, throwing descriptive Errors (e.g. “Wall profiler is already started”) rather than swallowing failures; the codebase is enforced by gts (Google TypeScript Style, strict compiler options), ESLint, Prettier, and clang-format for the C++ side, with nyc coverage reported to Codecov in CI.
API Design
The public surface is deliberately small and namespaced (time.*, heap.*, otelThreadCtx.*), which keeps the common path (time.profile() / heap.profile()) to a couple of calls, but several lower-level knobs (CPED, runWithContext, HeapLimitExtensionSize, CallbackMode) require reading the source comments to use correctly - the tradeoff of an instrumentation library that prioritizes runtime performance over API ergonomics. Extensive inline documentation on the trickier functions (e.g. monitorOutOfMemory) partly compensates for this.
Used by 2 apps in this directory
Documenso
Digital Signiture
Self-hosted, open-source DocuSign alternative with legally binding PDF signatures, multi-party workflows, and a full REST and tRPC API.
GrowthBook
Developer Tools · Analytics · Monitoring
Open source feature flags, A/B testing, and warehouse-native experimentation that queries your existing data infrastructure—no data movement required.