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

@coralogix/opentelemetry-profiling

v0.2.0

Published

OpenTelemetry Node.js Profiling SDK

Downloads

220

Readme

@coralogix/opentelemetry-profiling

OpenTelemetry continuous profiling SDK for Node.js. Collects wall-clock and heap profiles using @datadog/pprof and exports them as OTLP (default) or native pprof.

Features

  • Wall-clock profiling — samples JS stacks at regular intervals (default 100Hz), capturing both on-CPU and idle time
  • Heap profiling — samples memory allocations to identify where memory is being consumed
  • Trace-profile correlation — attaches trace_id/span_id labels to samples for active OTel spans
  • Span attribute extraction — copies selected span attributes (e.g. http.route) onto profiling samples
  • Source map support — maps compiled JS filenames/lines back to original TypeScript sources
  • OTLP gRPC export (default) — sends OTLP profiles to OTel Collectors / OTLP-compatible backends
  • Pprof push export — opt-in alternative that sends raw pprof to the OTel Collector's pprofreceiver push endpoint, skipping OTLP conversion entirely
  • OTel environment variables — respects OTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES, OTEL_PROFILES_EXPORTER, etc.
  • Console exporter — debug exporter rendering raw pprof contents

Installation

npm install @coralogix/opentelemetry-profiling

For trace-profile correlation:

npm install @opentelemetry/api @opentelemetry/sdk-trace-node

Quick Start

import { ProfilingProvider } from '@coralogix/opentelemetry-profiling';

const provider = new ProfilingProvider({
  serviceName: 'my-service',
});

await provider.start();

// Your application code...

// On shutdown
await provider.stop();

Configuration

Programmatic

import {
  ProfilingProvider,
  ConsoleProfileExporter,
  OtlpGrpcProfileExporter,
} from '@coralogix/opentelemetry-profiling';

const provider = new ProfilingProvider({
  // Service identification
  serviceName: 'my-service',
  resource: {
    'deployment.environment': 'production',
    'service.version': '1.2.3',
  },

  // Profiler toggles
  wallProfilingEnabled: true,   // default: true
  heapProfilingEnabled: true,   // default: true

  // Collection interval — how often profiles are flushed to the exporter
  collectionIntervalMs: 10_000, // default: 10000 (10s)

  // Wall profiler tuning
  wallSamplingIntervalMicros: 10_000, // default: 10000 (100Hz)

  // Heap profiler tuning
  heapSamplingIntervalBytes: 524_288, // default: 524288 (512KB)
  heapStackDepth: 64,                 // default: 64

  // Trace-profile correlation (requires @opentelemetry/api)
  traceCorrelation: true,

  // Copy these span attributes onto profiling samples
  spanAttributeKeys: ['http.route', 'rpc.method'],

  // Source maps — map compiled JS back to original TypeScript sources
  sourceMapSearchPaths: ['./dist'],

  // Exporters — defaults to [OtlpGrpcProfileExporter]
  exporters: [
    new OtlpGrpcProfileExporter({
      endpoint: 'http://localhost:4317',
      headers: { 'x-api-key': 'secret' },
    }),
    new ConsoleProfileExporter({ verbosity: 'basic' }),
  ],
});

Environment Variables

| Variable | Description | Default | |----------|-------------|---------| | OTEL_SERVICE_NAME | Service name | — | | OTEL_RESOURCE_ATTRIBUTES | Comma-separated key=value pairs | — | | OTEL_PROFILES_EXPORTER | Comma-separated exporters: pprof, otlp, console, none | otlp | | OTEL_EXPORTER_PPROF_ENDPOINT | Pprof push endpoint | http://localhost:4040/v1/pprof | | OTEL_EXPORTER_PPROF_HEADERS | Headers for pprof push (comma-separated k=v) | — | | OTEL_EXPORTER_OTLP_PROFILES_ENDPOINT | Profiles-specific OTLP gRPC endpoint | — | | OTEL_EXPORTER_OTLP_ENDPOINT | General OTLP gRPC endpoint (fallback) | http://localhost:4317 | | OTEL_EXPORTER_OTLP_PROFILES_HEADERS | Profiles-specific OTLP headers | — | | OTEL_EXPORTER_OTLP_HEADERS | General OTLP headers (fallback) | — | | OTEL_PROFILING_WALL_ENABLED | Enable wall-clock profiler (true/false) | true | | OTEL_PROFILING_HEAP_ENABLED | Enable heap profiler (true/false) | true | | OTEL_PROFILING_COLLECTION_INTERVAL_MS | Profile collection interval | 10000 | | OTEL_PROFILING_WALL_SAMPLING_INTERVAL_MICROS | Wall-clock sample interval (microseconds) | 10000 (100Hz) | | OTEL_PROFILING_HEAP_SAMPLING_INTERVAL_BYTES | Heap allocation sample interval (bytes) | profiler default | | OTEL_PROFILING_HEAP_STACK_DEPTH | Max stack frames for heap samples | profiler default | | OTEL_PROFILING_HEAP_SAMPLE_TYPES | Heap sample types: bytes, objects, or both | both | | OTEL_PROFILING_TRACE_CORRELATION | Attach trace_id/span_id labels (true/false) | true | | OTEL_PROFILING_SPAN_ATTRIBUTE_KEYS | Comma-separated span attribute keys to copy onto samples | — | | OTEL_PROFILING_SOURCE_MAP_SEARCH_PATHS | Comma-separated directories scanned for source maps | — |

Environment variables are overridden by programmatic config.

Exporters

Pprof push (opt-in)

Sends raw pprof bytes (gzipped) over HTTP/1.1 to the OTel Collector's pprofreceiver push endpoint (POST /v1/pprof). No pprof→OTLP conversion happens in the SDK — the collector does it.

Select via OTEL_PROFILES_EXPORTER=pprof.

Resource attributes are sent as a single HTTP header Otel-Resource-Attributes: k=v,k=v (same syntax as OTEL_RESOURCE_ATTRIBUTES). The SDK adds profiler.type=wall|heap to the resource for each export so backends can distinguish profile kinds. The collector must be configured with include_metadata: true and a transform processor that promotes the header into resource attributes — see Using with the OpenTelemetry Collector.

import { PprofPushProfileExporter } from '@coralogix/opentelemetry-profiling';

const exporter = new PprofPushProfileExporter({
  endpoint: 'http://collector:4040/v1/pprof',
  headers: { authorization: 'Bearer …' },
});

OTLP gRPC (default)

Sends profiles to an OTLP-compatible collector via gRPC (HTTP/2 + protobuf). Uses Node's built-in http2 module — no @grpc/grpc-js dependency. Pprof→OTLP conversion happens in the SDK.

Selected by default; also OTEL_PROFILES_EXPORTER=otlp (or otlp_grpc).

import { OtlpGrpcProfileExporter } from '@coralogix/opentelemetry-profiling';

const exporter = new OtlpGrpcProfileExporter({
  endpoint: 'http://localhost:4317',
});

Console

Prints raw pprof contents to stdout. Three verbosity levels:

import { ConsoleProfileExporter } from '@coralogix/opentelemetry-profiling';

// basic — one summary line
new ConsoleProfileExporter({ verbosity: 'basic' });

// normal (default) — summary + resource attrs + table sizes + period info
new ConsoleProfileExporter({ verbosity: 'normal' });

// detailed — also includes top-N functions by leaf sample value
new ConsoleProfileExporter({ verbosity: 'detailed', topN: 10 });

Custom

Implement the ProfileExporter interface. Each exporter receives raw pprof — encode or convert as needed.

import { ProfileExporter, ProfileData, buildRequest, encodeRequest } from '@coralogix/opentelemetry-profiling';

class MyExporter implements ProfileExporter {
  async export(data: ProfileData): Promise<void> {
    // data.profile     — pprof-format Profile object
    // data.profileType — 'wall' | 'heap'
    // data.startedAt / data.stoppedAt — collection window
    // data.resource    — resolved resource attributes
  }

  async shutdown(): Promise<void> {
    // cleanup
  }
}

If your exporter needs an OTLP ExportProfilesServiceRequest, build it on demand:

const request = buildRequest(
  [{ profile: data.profile, profileType: data.profileType, startedAt: data.startedAt, stoppedAt: data.stoppedAt }],
  data.resource,
);
const encoded = encodeRequest(request);

Trace-Profile Correlation

When @opentelemetry/api is installed and a TracerProvider is active, enabling traceCorrelation links profiling samples to the spans they were captured in.

import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { ProfilingProvider } from '@coralogix/opentelemetry-profiling';

// Set up tracing first
const tracerProvider = new NodeTracerProvider();
tracerProvider.register();

// Then profiling
const profiling = new ProfilingProvider({
  serviceName: 'my-service',
  traceCorrelation: true,
});
await profiling.start();

Each wall-clock sample captured while a span is active gets trace_id and span_id labels in the pprof output.

  • Pprof push path: labels travel through the collector's pprof receiver translator as ordinary sample attributes. Backends key off the trace_id/span_id attributes directly.
  • OTLP path: the converter promotes these labels into the OTLP Link table (sample.linkIndex).

Either shape preserves the same information; only the structural layout differs.

Span Attribute Extraction

Use spanAttributeKeys to copy specific span attributes onto profiling samples. This enables grouping and filtering profiles by attributes like HTTP route or RPC method.

const profiling = new ProfilingProvider({
  traceCorrelation: true,
  spanAttributeKeys: ['http.route', 'rpc.method', 'rpc.service'],
});

Attributes are read from the span at profile collection time, not when the span is activated. This means attributes set after span creation (e.g. http.route set by Express after route matching) are captured correctly.

Note: Span attribute extraction only works for wall-clock profiles. Heap profiles use V8's AllocationProfiler which has no per-allocation context capture, so there's no way to know which span was active when a given allocation occurred.

Using with the OpenTelemetry Collector

Default: pprof push

This SDK sends raw pprof to the collector's pprofreceiver push endpoint. Resource attributes are conveyed in the Otel-Resource-Attributes HTTP header, so the receiver needs include_metadata: true and a transform processor that lifts the header into resource.attributes:

receivers:
  pprof:
    server:
      endpoint: 0.0.0.0:4040
      include_metadata: true

processors:
  transform:
    profile_statements:
      - set(resource.attributes, ParseKeyValue(metadata["otel-resource-attributes"], "=", ","))

exporters:
  debug:
    verbosity: detailed

service:
  pipelines:
    profiles:
      receivers: [pprof]
      processors: [transform]
      exporters: [debug]

Without the transform rule, profiles will arrive with no service.name or other resource attributes — the header carries them, but nothing promotes them by default.

OTLP gRPC

If you opt out of pprof push (OTEL_PROFILES_EXPORTER=otlp), the standard OTLP receiver applies:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317

exporters:
  debug:
    verbosity: detailed

service:
  pipelines:
    profiles:
      receivers: [otlp]
      exporters: [debug]

Note: The collector must have the --feature-gates=+service.profilesSupport flag set (or run a version where profiles support is GA).

How It Works

  1. @datadog/pprof drives the actual sampling — it uses a native addon with setitimer(SIGALRM) for wall-clock sampling and V8's AllocationProfiler for heap sampling
  2. Collection loop runs every collectionIntervalMs (default 10s), calling stop(restart=true) on the wall profiler and profile() on the heap profiler
  3. Per-exporter encoding — the provider hands raw pprof to each configured exporter. The pprof push exporter gzip-encodes the bytes and POSTs them. The OTLP exporter converts pprof → OTLP locally (dictionary-based string/function/location/stack tables), encodes as protobuf, frames it with gRPC length-prefixed encoding, and sends via HTTP/2. Conversion only happens for exporters that need it.

Wall-clock vs CPU profiling

The wall profiler samples at regular wall-clock intervals regardless of whether JavaScript is executing. This means:

  • On-CPU work (computation) shows as function frames
  • Off-CPU time (I/O wait, event loop idle) shows as (idle)
  • Both are captured, giving a complete picture of where time is spent

Async stack limitation

When Node.js hits an await, the call stack is unwound — there's no stack to sample. This means a function like await db.query() appears as (idle) samples, not as samples attributed to db.query. Enabling traceCorrelation helps bridge this gap by attributing idle time to the span that was active.

Source Maps

When profiling TypeScript or bundled applications, V8 reports function names and filenames from the compiled JS output. Enable sourceMapSearchPaths to map these back to original source files:

const provider = new ProfilingProvider({
  sourceMapSearchPaths: ['./dist'],
});
await provider.start();

The provider scans the specified directories for .js.map files at startup. Profiles will then show original filenames and line numbers (e.g. src/handler.ts:42 instead of dist/handler.js:120).

Requires your build to emit source maps (e.g. "sourceMap": true in tsconfig.json).

Examples

See the examples/ directory:

API Reference

ProfilingProviderConfig

| Option | Type | Default | Description | |--------|------|---------|-------------| | serviceName | string | — | Service name (optional; omitted from resource if unset) | | resource | Record<string, string \| number \| boolean> | — | Additional resource attributes | | exporters | ProfileExporter[] | [OtlpGrpcProfileExporter] | Exporter instances | | traceCorrelation | boolean | true | Link samples to active OTel spans | | spanAttributeKeys | string[] | [] | Span attributes to copy onto samples | | wallProfilingEnabled | boolean | true | Enable wall-clock profiling | | heapProfilingEnabled | boolean | true | Enable heap profiling | | collectionIntervalMs | number | 10000 | How often profiles are flushed | | wallSamplingIntervalMicros | number | 10000 | Wall profiler sampling interval (100Hz) | | heapSamplingIntervalBytes | number | 524288 | Heap profiler sampling interval (512KB) | | heapStackDepth | number | 64 | Max stack depth for heap samples | | sourceMapSearchPaths | string[] | — | Directories to scan for .js.map files |

ProfilingProvider

| Method | Description | |--------|-------------| | new ProfilingProvider(config?) | Create a provider with optional config | | start(): Promise<void> | Start profilers and begin periodic collection | | stop(): Promise<void> | Stop profilers, flush final profiles, shutdown exporter |

ProfileData

| Field | Type | Description | |-------|------|-------------| | profile | Profile (from pprof-format) | Raw pprof profile | | profileType | 'wall' \| 'heap' | Type of profile | | startedAt | Date | Start of collection window | | stoppedAt | Date | End of collection window | | resource | ResourceAttributes | Resolved resource attributes for this profile |

ProfileExporter

| Method | Description | |--------|-------------| | export(data: ProfileData): Promise<void> | Export a single profile | | shutdown(): Promise<void> | Clean up resources |

License

Apache-2.0