npm.io
1.1.1 • Published 3d ago

@pawells/pyroscope

Licence
MIT
Version
1.1.1
Deps
7
Size
207 kB
Vulns
0
Weekly
0

Pyroscope Utility Library

GitHub Release CI npm version Node License: MIT GitHub Sponsors

Description

@pawells/pyroscope provides continuous profiling instrumentation for Node.js services via Pyroscope. It wraps @pyroscope/nodejs with:

  • PyroscopeExporter — implements MetricsExporter (Name = 'pyroscope') and self-registers with the @pawells/metrics registry during PyroscopeManager.Start(). Its consumer callbacks (OnDescriptorRegistered / OnMetricRecorded) are intentional no-ops — Pyroscope ingests profiles through its own agent, not foreign metric values. Flush() surfaces the latest aggregate counters to every registered backend; Shutdown() stops the profiler.
  • PyroscopeManager — static lifecycle management (Start, Shutdown, TrackFunction, CollectAndRecord) for profiling operations
  • Profiling decorators — class-level (@Profile) and method-level (@ProfileMethod, @ProfileAsync) decorators for automatic instrumentation
  • Health-state managementHealth getter and PyroscopeHealthStates enum (healthy/degraded/unhealthy)
  • Aggregate metrics — CPU samples, memory allocations, HTTP request counters, and response time tracking recorded via RecordCPUSample, RecordMemorySample, and RecordRequest. These counters are automatically published into the @pawells/metrics registry fan-out on a configurable periodic timer, surfacing in Prometheus, OpenTelemetry, and any other registered backend.

All profiling data is tagged with global metadata (application name, version, environment) set during initialization. Tags are tracked for metrics aggregation; note that per-profile dynamic tagging is not supported by the underlying @pyroscope/nodejs SDK.

Requirements

  • Node.js ≥ 22.0.0
  • TypeScript ~6.0.0
  • Dependency @pawells/metrics (foundation library for descriptor-first metric registration)
  • Dependency @pyroscope/nodejs ~0.4.13 (Pyroscope SDK)

See package.json for the full dependency list and versions.

Installation

Install the package and its dependencies:

yarn add @pawells/pyroscope

Quick Start

1. Initialize the Manager
import { PyroscopeManager } from '@pawells/pyroscope';

// Start profiling at application startup
await PyroscopeManager.Start();

// Shutdown gracefully at application exit
process.on('SIGTERM', async () => {
	await PyroscopeManager.Shutdown();
	process.exit(0);
});
2. Profile a Function
// Wrap any sync or async function with automatic profiling
const result = await PyroscopeManager.TrackFunction('expensiveQuery', async () => {
	return await database.query('SELECT * FROM users');
}, { userId: '123' });
3. Use Method Decorators
import { ProfileMethod, ProfileAsync } from '@pawells/pyroscope';

class UserService {
	// Decorator for both sync and async methods
	@ProfileMethod({ tags: { operation: 'fetch' } })
	async getUser(id: string) {
		return await this.database.findById(id);
	}

	// Explicit async decorator
	@ProfileAsync({ name: 'expensive.operation', tags: { service: 'api' } })
	async expensiveAsyncOperation() {
		// Profiling is automatically started and stopped
		return await someHeavyWork();
	}
}
4. Profile an Entire Class
import { Profile } from '@pawells/pyroscope';

@Profile({ tags: { service: 'auth' } })
export class AuthService {
	// All methods are automatically profiled
	async validateToken(token: string) {
		// Profiled as 'AuthService.validateToken'
		return await this.tokenValidator.check(token);
	}

	createToken(payload: object) {
		// Profiled as 'AuthService.createToken'
		return this.tokenGenerator.sign(payload);
	}
}
5. Record Metrics Manually
// Record CPU profiling sample (e.g. from a custom profiler)
PyroscopeManager.RecordCPUSample(45); // milliseconds

// Record memory allocation
PyroscopeManager.RecordMemorySample(1024 * 1024); // bytes (1 MB)

// Record HTTP request
PyroscopeManager.RecordRequest(200, 125); // status code, duration in ms
6. Check Health Status
const health = PyroscopeManager.Health;
console.log(`Status: ${health.status}`);
console.log(`Initialized: ${health.initialized}`);
console.log(`Active profiles: ${health.activeProfiles}`);
console.log(`Total recorded: ${health.totalProfiles}`);

Configuration

Configuration is loaded from environment variables with the PYROSCOPE_ prefix using @pawells/config v3. Access via PyroscopeConfig.Get(key).

Variable Type Default Description
PYROSCOPE_ENABLED boolean false Globally enable or disable Pyroscope profiling
PYROSCOPE_APPLICATION_NAME string 'nestjs-app' Application name label sent to Pyroscope
PYROSCOPE_APPLICATION_VERSION semver optional Application version label sent to Pyroscope (e.g. 1.0.0)
PYROSCOPE_SERVER_ADDRESS URL 'http://localhost:4040' URL of the Pyroscope server. HTTPS is required in all environments except development (see PYROSCOPE_ENVIRONMENT); a plaintext HTTP address causes Start() to throw PyroscopeError with code PYROSCOPE_INSECURE_URL.
PYROSCOPE_AUTH_USERNAME string optional Basic auth username for Pyroscope server
PYROSCOPE_AUTH_PASSWORD string optional Basic auth password for Pyroscope server
PYROSCOPE_ENVIRONMENT string 'production' Environment label (e.g. production, staging, development)
PYROSCOPE_PROFILING_MAX_METRICS_HISTORY number 1000 Maximum number of profiling metrics to keep in memory
PYROSCOPE_PROFILING_MAX_ACTIVE_PROFILES number 10000 Maximum number of active (in-progress) profiling sessions before new sessions are rejected
PYROSCOPE_PROFILING_STALE_PROFILE_TIMEOUT_MS number 1800000 Time in milliseconds after which a stale profile is considered expired (30 minutes)
PYROSCOPE_PROFILING_TAGS JSON string '{}' Global tags to include with all profiling data as JSON string (e.g. '{"env":"prod"}')
PYROSCOPE_PROFILING_DEGRADED_ACTIVE_PROFILES_THRESHOLD number 1000 Active-profile count above which the health indicator returns degraded status
PYROSCOPE_PROFILING_METRICS_INTERVAL_MS number 15000 Interval (ms) at which aggregate profiling counters are collected and recorded into the @pawells/metrics registry
Example: Basic .env
PYROSCOPE_ENABLED=true
PYROSCOPE_APPLICATION_NAME=my-service
PYROSCOPE_APPLICATION_VERSION=1.2.3
PYROSCOPE_SERVER_ADDRESS=http://pyroscope-server:4040
PYROSCOPE_ENVIRONMENT=production
PYROSCOPE_PROFILING_TAGS={"service":"api","region":"us-east-1"}
Example: With Basic Auth
PYROSCOPE_ENABLED=true
PYROSCOPE_SERVER_ADDRESS=http://pyroscope-server:4040
PYROSCOPE_AUTH_USERNAME=user
PYROSCOPE_AUTH_PASSWORD=pass

API Reference

PyroscopeManager

Static class controlling the profiling lifecycle and recording metrics.

Methods
Method Signature Description
Start async Start(): Promise<void> Initializes and starts the Pyroscope profiler. Configures the client with server address, app name, and auth credentials. Logs a warning if unsupported config keys are set.
Shutdown async Shutdown(): Promise<void> Stops the profiler and cleans up resources. Clears all active and completed profiling sessions.
StartProfiling StartProfiling(context: TPyroscopeProfileContext): string Begins profiling a named operation. Generates a unique profile ID, tracks the context, and evicts stale profiles if active limit exceeded. Returns the generated profile ID.
StopProfiling StopProfiling(context: TPyroscopeProfileContext): TPyroscopeProfileMetrics Ends profiling for an operation. Calculates duration, records metrics, and cleans up context. Returns the recorded metrics.
TrackFunction async TrackFunction<T>(name: string, fn: () => T | Promise<T>, tags?: Record<string, string>): Promise<T> Wraps a function call with automatic profiling start and stop. Handles both sync and async functions.
RecordCPUSample RecordCPUSample(duration: number): void Records a CPU profiling sample. Increments sample counter and adds duration to total CPU time. Negative durations are ignored. Manual-only API.
RecordMemorySample RecordMemorySample(bytes: number): void Records a memory profiling sample. Increments sample counter and adds allocation size to total. Negative bytes are ignored. Manual-only API.
RecordRequest RecordRequest(statusCode: number, duration: number): void Records an HTTP request with status code and duration. Updates success/failure counters (2xx-3xx = success). Negative durations are ignored.
ResetProfilingMetrics ResetProfilingMetrics(): void Resets all aggregate profiling metric counters to zero. Active profiling sessions are not affected. Also resets the delta baseline so the next CollectAndRecord call starts from zero.
CollectAndRecord CollectAndRecord(): void Collects the current aggregate profiling counters and records them as deltas into the @pawells/metrics registry. Monotonic counters are recorded as the increment since the previous call; the active-sessions gauge (pyroscope_active_profiles) is always absolute. Called automatically on the periodic timer and by PyroscopeExporter.Flush(); may also be called manually. Throws PyroscopeError if a downstream exporter fails.
Getters
Getter Return Type Description
Initialized boolean Whether the profiler has been successfully initialized via Start().
State PyroscopeHealthStates Current health state enum: Healthy, Degraded, or Unhealthy.
Health TPyroscopeManagerHealth Complete health status object with status, enabled, initialized, serverAddress, applicationName, activeProfiles, totalProfiles, lastProfileAt.
ActiveProfiles ReadonlyMap<string, TPyroscopeProfileContext> Live read-only view of all currently active (in-flight) profiling sessions keyed by profile ID. Changes to the map are reflected immediately.
Metrics readonly TPyroscopeProfileMetrics[] Live read-only view of all recorded profiling metrics from completed sessions. Changes to the array are reflected immediately.
ProfilingMetrics TPyroscopeProfilingMetrics Aggregate profiling metrics: CPU_SAMPLES, CPU_TOTAL_DURATION, MEMORY_SAMPLES, MEMORY_ALLOCATIONS, REQUESTS_TOTAL, REQUESTS_SUCCESS, REQUESTS_FAILURE, RESPONSE_TIME.
LastMetricUpdate number Unix timestamp (ms) of the most recent profiling metric, or 0 if no metrics exist.
PyroscopeConfig

Static configuration accessor that loads Pyroscope settings from environment variables with the PYROSCOPE_ prefix using @pawells/config. Access configuration values via the static Get(key) method.

import { PyroscopeConfig } from '@pawells/pyroscope';

const enabled = PyroscopeConfig.Get('ENABLED'); // boolean
const serverAddress = PyroscopeConfig.Get('SERVER_ADDRESS'); // string
const appName = PyroscopeConfig.Get('APPLICATION_NAME'); // string

See the Configuration section for a complete list of supported keys.

PyroscopeExporter

Registry-citizen exporter that wires @pawells/pyroscope into the @pawells/metrics fan-out as a first-class MetricsExporter. PyroscopeManager.Start() self-registers an instance; PyroscopeManager.Shutdown() self-deregisters it, so Instrumentation.FlushAll() and Instrumentation.ShutdownAll() work without any additional wiring.

Pyroscope is a producer of its own profiling statistics, not a consumer of arbitrary application metrics. Profiles are streamed to the Pyroscope server by its own agent. For this reason, the consumer callbacks are intentional no-ops:

  • OnDescriptorRegistered and OnMetricRecorded do nothing — this exporter ignores foreign metric descriptors and values.
  • Flush() calls PyroscopeManager.CollectAndRecord(), surfacing the latest aggregate counters to every registered backend.
  • Shutdown() delegates to PyroscopeManager.Shutdown().

Per the exporter error contract, lifecycle hooks propagate failures (throw / reject) rather than swallowing them — the registry isolates and aggregates the failure into a MetricsExporterError.

Property:

Property Type Description
Name 'pyroscope' Stable identifier used by the registry for diagnostics and deduplication.

Methods:

Method Signature Description
OnDescriptorRegistered OnDescriptorRegistered(descriptor: TMetricDescriptor): void No-op. Pyroscope does not ingest foreign metric descriptors.
OnMetricRecorded OnMetricRecorded(value: TMetricValue): void No-op. Pyroscope does not consume foreign metric values.
Flush async Flush(): Promise<void> Calls PyroscopeManager.CollectAndRecord(). Throws PyroscopeError on failure.
Shutdown async Shutdown(): Promise<void> Calls PyroscopeManager.Shutdown(). Throws PyroscopeError on failure.

Example:

import { Instrumentation } from '@pawells/metrics';

// PyroscopeExporter is constructed internally by PyroscopeManager.Start()
// and automatically registered with the Instrumentation registry.
// No manual registration is required.

// During graceful shutdown, call either:
await Instrumentation.ShutdownAll();
// OR
await PyroscopeManager.Shutdown();
Profiling Metrics Emitted

When the profiler is running, @pawells/pyroscope publishes the following metrics into the @pawells/metrics registry on each CollectAndRecord tick. These metrics surface in every registered backend — Prometheus, OpenTelemetry, or any custom MetricsExporter.

Monotonic counters are recorded as deltas since the previous tick so backend counter instruments accumulate correctly without double-counting. pyroscope_active_profiles is always an absolute gauge value.

Metric Type Label Description
pyroscope_cpu_samples_total Counter Total CPU profiling samples recorded
pyroscope_cpu_duration_milliseconds_total Counter Total CPU profiling sample duration (ms)
pyroscope_memory_samples_total Counter Total memory profiling samples recorded
pyroscope_memory_allocated_bytes_total Counter Total bytes of memory allocation observed
pyroscope_requests_total Counter outcome: success|failure HTTP requests recorded by the profiler
pyroscope_response_time_milliseconds_total Counter Total request/response duration (ms)
pyroscope_active_profiles Gauge In-flight profiling sessions at collection time
Registry Constants
PyroscopeMetricNames

Enum of stable string constants for the seven metrics that @pawells/pyroscope registers with the @pawells/metrics registry. Use these constants to build dashboards or alerts without hard-coding metric name strings.

export enum PyroscopeMetricNames {
	CpuSamplesTotal = 'pyroscope_cpu_samples_total',
	CpuDurationMillisecondsTotal = 'pyroscope_cpu_duration_milliseconds_total',
	MemorySamplesTotal = 'pyroscope_memory_samples_total',
	MemoryAllocatedBytesTotal = 'pyroscope_memory_allocated_bytes_total',
	RequestsTotal = 'pyroscope_requests_total',
	ResponseTimeMillisecondsTotal = 'pyroscope_response_time_milliseconds_total',
	ActiveProfiles = 'pyroscope_active_profiles'
}
PYROSCOPE_PROFILING_DESCRIPTORS

readonly TMetricDescriptor[] — the full set of metric descriptors that PyroscopeManager.Start() registers with the registry. Useful for pre-registering descriptors in test setups or when building custom instrumentation that needs to reference the same descriptor definitions.

import { Instrumentation } from '@pawells/metrics';
import { PYROSCOPE_PROFILING_DESCRIPTORS } from '@pawells/pyroscope';

for (const descriptor of PYROSCOPE_PROFILING_DESCRIPTORS) {
	if (Instrumentation.GetDescriptor(descriptor.name) === undefined) {
		Instrumentation.RegisterDescriptor(descriptor);
	}
}
PYROSCOPE_REQUEST_OUTCOME_LABEL

string — the label name ('outcome') used to partition pyroscope_requests_total into success and failure series. Use this constant instead of the literal string when writing PromQL queries or OTel attribute filters.

import { PYROSCOPE_REQUEST_OUTCOME_LABEL } from '@pawells/pyroscope';

// 'outcome'
console.log(PYROSCOPE_REQUEST_OUTCOME_LABEL);
Profiling Decorators
@Profile

Class-level decorator that profiles all methods in a class.

@Profile(options?: ProfileClassOptions): ClassDecorator

Options:

Option Type Description
tags Record<string, string> Global metadata tags to attach to all method profiles within the class. Merged with per-method tags. Tags are tracked for metrics only, not forwarded to the SDK.

Behavior:

  • Automatically wraps every method (except constructor) with profiling start/stop
  • Works with both sync and async methods
  • Profile name formatted as ClassName.methodName
  • Silently skips profiling if ENABLED=false or manager not initialized

Example:

@Profile({ tags: { service: 'user' } })
@Injectable()
export class UserService {
	async findById(id: string) {
		// Profiled as 'UserService.findById'
	}
}
@ProfileMethod

Method-level decorator for selective profiling of specific methods.

@ProfileMethod(options?: ProfileMethodOptions): MethodDecorator

Options:

Option Type Description
name string Custom name for the profiled operation. If not provided, uses ClassName.methodName.
tags Record<string, string> Metadata tags to attach to this profile. Tracked for metrics aggregation only.

Behavior:

  • Works with both sync and async methods
  • Automatically detects Promises and handles async completion
  • Silently skips profiling if ENABLED=false or manager not initialized

Example:

class DatabaseService {
	@ProfileMethod({ name: 'db.query.expensive' })
	async executeExpensiveQuery(sql: string) {
		return await this.db.query(sql);
	}
}
@ProfileAsync

Method-level decorator specifically designed for async methods.

@ProfileAsync(options?: ProfileMethodOptions): MethodDecorator

Options: Same as @ProfileMethod.

Behavior:

  • Designed for async methods (returning Promise)
  • Guarantees proper Promise handling and timing accuracy
  • Silently skips profiling if ENABLED=false or manager not initialized

Example:

class ApiService {
	@ProfileAsync({ name: 'api.fetch.user', tags: { endpoint: 'users' } })
	async fetchUser(userId: string) {
		return await this.http.get(`/users/${userId}`).toPromise();
	}
}
Enums
PyroscopeHealthStates

Health status enumeration for profiler state.

export enum PyroscopeHealthStates {
	Healthy = 'healthy',      // All services operational
	Degraded = 'degraded',    // Services running with issues (e.g. high active profiles)
	Unhealthy = 'unhealthy'   // Services not operational
}
PyroscopeProfileTypes

Profile type enumeration. Only Node.js-supported types are included.

export enum PyroscopeProfileTypes {
	CPU = 'cpu',
	Memory = 'memory'
}
Error Classes
PyroscopeError

Base error class for Pyroscope-related errors. Extends @pawells/typescript-common BaseError with optional context string for diagnostics.

constructor(message: string, options: IPyroscopeErrorOptions = {})

Options:

  • code: string (default: 'PYROSCOPE_ERROR') — Error code for programmatic identification
  • context: string (optional) — Context string providing additional diagnostic detail
  • cause: Error (optional) — Original error that caused this error (cause-chain propagation)

Getters:

  • Code: string — Error code for programmatic identification
  • Context: string | undefined — Context string, if set

Example:

try {
	await someProfilingOperation();
} catch (error) {
	throw new PyroscopeError('Profiling failed', {
		code: 'PYROSCOPE_OPERATION_FAILED',
		context: 'PyroscopeManager.TrackFunction',
		cause: error instanceof Error ? error : undefined
	});
}
Types

TPyroscopeProfileContext — Context for an in-flight profiling session.

{
	functionName: string;           // Operation name
	className?: string;             // Class name (for method profiles)
	methodName?: string;            // Method name (for method profiles)
	profileId?: string;             // Unique ID (set by StartProfiling)
	tags?: Record<string, string>;  // Metadata tags
	startTime?: number;             // Start timestamp (ms)
	endTime?: number;               // End timestamp (ms)
	duration?: number;              // Elapsed milliseconds
	error?: Error;                  // Error, if occurred
}

TPyroscopeProfileMetrics — Metrics from a completed profiling session.

{
	durationMs: number;                  // Elapsed milliseconds
	timestamp: number;                   // Completion timestamp (ms)
	tags?: Record<string, string>;       // Metadata tags
}

TPyroscopeProfilingMetrics — Aggregate profiling metrics.

{
	CPU_SAMPLES: number;           // Count of CPU samples recorded
	CPU_TOTAL_DURATION: number;    // Total CPU duration (ms)
	MEMORY_SAMPLES: number;        // Count of memory samples recorded
	MEMORY_ALLOCATIONS: number;    // Total memory allocated (bytes)
	REQUESTS_TOTAL: number;        // Total HTTP requests recorded
	REQUESTS_SUCCESS: number;      // Count of successful requests (2xx-3xx)
	REQUESTS_FAILURE: number;      // Count of failed requests
	RESPONSE_TIME: number;         // Total response time (ms)
}

TPyroscopeManagerHealth — Complete health status object returned by PyroscopeManager.Health.

{
	status: PyroscopeHealthStates;  // Current health state
	enabled: boolean;               // Whether profiling is enabled
	initialized: boolean;           // Whether manager is initialized
	serverAddress: string;          // Configured Pyroscope server URL
	applicationName: string;        // Configured application name
	activeProfiles: number;         // Count of in-flight profiling sessions
	totalProfiles: number;          // Total recorded profiling sessions
	lastProfileAt: number;          // Timestamp of most recent metric (ms)
}

Security note: The serverAddress field is intentionally omitted from TPyroscopeHealthResponse (the type intended for external-facing health responses a consuming application may choose to expose) to avoid leaking internal infrastructure details. serverAddress is only present on the in-process TPyroscopeManagerHealth object returned by PyroscopeManager.Health. This package does not itself register any HTTP endpoints — health status must be surfaced by the consuming application.

License

MIT — Phillip Aaron Wells. See LICENSE for details.

Keywords