# atatus-profiling-agent

Continuous profiling agent for Node.js applications — part of the [Atatus](https://www.atatus.com) observability platform.

Collects **CPU (wall-time)**, **heap (space)**, and **timeline/event** profiles from running Node.js processes and uploads them to the Atatus profiling backend in [pprof](https://github.com/google/pprof) format.

---

## Table of Contents

- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Configuration Reference](#configuration-reference)
  - [Code Options](#code-options)
  - [Environment Variables](#environment-variables)
- [Profiler Types](#profiler-types)
- [Exporters](#exporters)
- [Logging](#logging)
- [OOM Monitoring](#oom-monitoring)
- [Source Map Support](#source-map-support)
- [Graceful Shutdown](#graceful-shutdown)
- [Architecture Overview](#architecture-overview)
- [Development](#development)
- [License](#license)

---

## Requirements

| Requirement | Version |
|-------------|---------|
| Node.js | ≥ 16.x |
| OS | Linux, macOS (Windows: limited support) |
| Atatus API Key | Required for upload |

---

## Installation

```bash
npm install atatus-profiling-agent
```

---

## Quick Start

Require the profiler **before any other module** in your application entry point, then call `start()`:

```javascript
const profiler = require('atatus-profiling-agent/profiler');

profiler.start({
  service: 'my-node-app',     // Required: name of your service
  version: '1.0.0',           // Optional: service version
  env: 'production',          // Optional: deployment environment
  apiKey: 'YOUR_API_KEY',     // Required: your Atatus API key
  profiling: {
    enabled: true
  }
});

// Rest of your app below...
const express = require('express');
```

Or pass the API key via environment variable:

```bash
ATATUS_API_KEY=YOUR_API_KEY node app.js
```

---

## Configuration Reference

### Code Options

Pass these as properties to `profiler.start(config)`:

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `service` | `string` | `"node"` | Service name shown in the Atatus UI |
| `version` | `string` | — | Service version |
| `env` | `string` | — | Deployment environment (`production`, `staging`, etc.) |
| `apiKey` | `string` | `ATATUS_API_KEY` env var | **Required.** Your 36-character Atatus API key |
| `url` | `string` | `https://profiling-rx.atatus.com` | Profiling ingestion endpoint |
| `hostname` | `string` | `localhost` | Agent host (when using agent exporter) |
| `port` | `number` | `8126` | Agent port (when using agent exporter) |
| `tags` | `object\|string` | — | Custom tags, e.g. `{ team: 'backend' }` or `"team:backend,region:us"` |
| `logger` | `object` | ConsoleLogger | Custom logger (see [Logging](#logging)) |
| `interval` | `number` | `65000` | Profile upload interval in milliseconds (min ~60s) |
| `uploadTimeout` | `number` | `60000` | Upload timeout in milliseconds |
| `sourceMap` | `boolean` | `true` | Enable source map resolution for minified stacks |
| `debugSourceMaps` | `boolean` | `false` | Log source map resolution details |
| `profilers` | `string[]` | `["wall","space"]` | Profilers to enable (see [Profiler Types](#profiler-types)) |
| `exporters` | `string[]\|Exporter[]` | `["agent"]` | Where to send profiles (see [Exporters](#exporters)) |
| `repositoryUrl` | `string` | — | Git repo URL for source code integration |
| `commitSHA` | `string` | — | Git commit SHA for source code integration |
| `timelineEnabled` | `boolean` | `true` (non-Windows) | Enable timeline view in the Atatus UI |
| `codeHotspotsEnabled` | `boolean` | `true` (non-Windows) | Link profiles to traces (code hotspots) |
| `endpointCollection` | `boolean` | `true` (non-Windows) | Enable endpoint-level profiling aggregation |
| `cpuProfilingEnabled` | `boolean` | `true` (non-Windows) | Enable CPU profiling |
| `samplingInterval` | `number` | `~10.1ms` (99 Hz) | Wall profiler sampling interval in milliseconds |
| `heapSamplingInterval` | `number` | — | Heap profiler sampling interval in bytes |
| `uploadCompression` | `string` | `"gzip"` | Compression: `"on"`, `"off"`, `"gzip"`, `"zstd"`, or with level e.g. `"gzip-6"` |
| `pprofPrefix` | `string` | `""` | File path prefix when using the file exporter |
| `oomMonitoring` | `boolean` | `true` (non-Windows) | Capture heap snapshot on OOM |
| `oomHeapLimitExtensionSize` | `number` | `0` | Bytes to extend heap before triggering OOM export |
| `oomMaxHeapExtensionCount` | `number` | `0` | Max heap extensions before forcing OOM export |
| `oomExportStrategies` | `string[]` | `["process"]` | OOM strategies: `"process"`, `"worker"`, `"interrupt"` |

### Environment Variables

| Environment Variable | Equivalent Option |
|----------------------|-------------------|
| `ATATUS_API_KEY` | `apiKey` |
| `ATATUS_SERVICE` | `service` |
| `ATATUS_VERSION` | `version` |
| `ATATUS_ENV` | `env` |
| `ATATUS_TAGS` | `tags` (comma-separated `key:value` pairs) |
| `ATATUS_PROFILING_URL` | `url` |
| `ATATUS_AGENT_HOST` | `hostname` |
| `ATATUS_TRACE_AGENT_PORT` | `port` |
| `ATATUS_PROFILING_PROFILERS` | `profilers` (e.g. `wall,space`) |
| `ATATUS_PROFILING_WALLTIME_ENABLED` | Enable/disable wall profiler |
| `ATATUS_PROFILING_HEAP_ENABLED` | Enable/disable heap profiler |
| `ATATUS_PROFILING_CPU_ENABLED` | `cpuProfilingEnabled` |
| `ATATUS_PROFILING_UPLOAD_PERIOD` | `interval` (in seconds) |
| `ATATUS_PROFILING_UPLOAD_TIMEOUT` | `uploadTimeout` (in ms) |
| `ATATUS_PROFILING_SOURCE_MAP` | `sourceMap` |
| `ATATUS_PROFILING_DEBUG_SOURCE_MAPS` | `debugSourceMaps` |
| `ATATUS_PROFILING_TIMELINE_ENABLED` | `timelineEnabled` |
| `ATATUS_PROFILING_CODEHOTSPOTS_ENABLED` | `codeHotspotsEnabled` |
| `ATATUS_PROFILING_ENDPOINT_COLLECTION_ENABLED` | `endpointCollection` |
| `ATATUS_PROFILING_DEBUG_UPLOAD_COMPRESSION` | `uploadCompression` |
| `ATATUS_PROFILING_HEAP_SAMPLING_INTERVAL` | `heapSamplingInterval` |
| `ATATUS_PROFILING_PPROF_PREFIX` | `pprofPrefix` |
| `ATATUS_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED` | `oomMonitoring` |
| `ATATUS_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE` | `oomHeapLimitExtensionSize` |
| `ATATUS_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT` | `oomMaxHeapExtensionCount` |
| `ATATUS_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES` | `oomExportStrategies` |

---

## Profiler Types

| Name | Alias | Description |
|------|-------|-------------|
| `wall` | `cpu` | **Wall-time profiler** — samples call stack at ~99 Hz to show CPU time |
| `space` | — | **Heap profiler** — samples allocations to show memory usage by call site |
| `events` | — | **Events profiler** — auto-added when timeline is enabled; captures async activity |

Default: `wall,space`

```bash
# Enable only wall profiler
ATATUS_PROFILING_PROFILERS=wall node app.js

# Disable heap profiler
ATATUS_PROFILING_HEAP_ENABLED=false node app.js
```

---

## Exporters

| Exporter | Description |
|----------|-------------|
| `"agent"` | *(default)* Uploads profiles to `https://profiling-rx.atatus.com` |
| `"file"` | Writes raw pprof files to disk (path set by `pprofPrefix`) |

```javascript
// Send to API and also save to disk
profiler.start({
  service: 'my-app',
  apiKey: 'YOUR_API_KEY',
  exporters: ['agent', 'file'],
  pprofPrefix: '/tmp/profiles/my-app'
});
```

---

## Logging

The profiler accepts any logger implementing these four methods:

```javascript
{ debug, info, warn, error }
```

### Integrate with Pino / Winston / Bunyan

```javascript
const pino = require('pino');
const logger = pino();

profiler.start({
  service: 'my-app',
  apiKey: 'YOUR_API_KEY',
  logger: {
    debug: (msg) => logger.debug(msg),
    info:  (msg) => logger.info(msg),
    warn:  (msg) => logger.warn(msg),
    error: (msg, ...args) => logger.error(msg, ...args),
  }
});
```

### Silence all logging

```javascript
profiler.start({
  service: 'my-app',
  apiKey: 'YOUR_API_KEY',
  logger: { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} }
});
```

---

## OOM Monitoring

Captures a heap snapshot automatically when the process is about to run out of memory.

> **Note:** Not supported on Windows.

```javascript
profiler.start({
  service: 'my-app',
  apiKey: 'YOUR_API_KEY',
  oomMonitoring: true,
  oomHeapLimitExtensionSize: 50 * 1024 * 1024, // extend heap by 50MB before snapshot
  oomMaxHeapExtensionCount: 1,
  oomExportStrategies: ['process'],
});
```

**Export strategies:**

| Strategy | Description |
|----------|-------------|
| `process` | Export in the current process (default) |
| `worker` | Export in a spawned worker thread |
| `interrupt` | Use async interrupt to trigger export |

---

## Source Map Support

When `sourceMap: true` (default), minified/transpiled stack frames are resolved back to original source locations — useful for TypeScript projects.

```javascript
profiler.start({
  service: 'my-ts-app',
  apiKey: 'YOUR_API_KEY',
  sourceMap: true,
  debugSourceMaps: true, // log resolution details
});
```

---

## Graceful Shutdown

The agent hooks into `process.beforeExit` to flush profiles automatically. For explicit control:

```javascript
process.on('SIGTERM', () => {
  profiler.stop();
  process.exit(0);
});
```

---

## Architecture Overview

```
atatus-profiling-agent/
├── profiler.js            # Public API: start() / stop()
├── profiling/
│   ├── index.js           # Profiler lifecycle manager
│   ├── config.js          # Config parsing (options + env vars)
│   ├── profiler.js        # Core scheduler & upload loop
│   ├── profilers/
│   │   ├── wall.js        # Wall-time (CPU) profiler
│   │   ├── space.js       # Heap profiler
│   │   └── events.js      # Timeline events profiler
│   ├── exporters/
│   │   ├── agent.js       # HTTP upload to Atatus API
│   │   └── file.js        # Write pprof files to disk
│   └── loggers/
│       └── console.js     # Default console logger
├── pprof/                 # Native pprof bindings (@atatus/pprof)
├── libdatadog/            # Native Rust bindings (@atatus/libatatus)
├── plugins/               # Instrumentation shims
└── log/                   # Internal logging utilities
```

---

## Development

```bash
# Install dependencies
npm install

# Run tests
node test.js
```

### Runtime dependencies

| Package | Purpose |
|---------|---------|
| `@atatus/pprof` | Native pprof sampling (wall + heap) |
| `@atatus/libatatus` | Native Rust library bindings |
| `atatus-nodejs` | Core Atatus Node.js agent |
| `delay` | Async delay utility (used by pprof) |
| `p-limit` | Concurrency limiter (used by pprof) |
| `pprof-format` | pprof protobuf serialization |
| `source-map` | Source map resolution for stack frames |
| `dc-polyfill` | `diagnostics_channel` polyfill |
| `koalas` | Null-coalescing helper |
| `retry` | Upload retry logic |
| `node-gyp-build` | Native addon loader |

---

## License

ISC © Atatus — [https://www.atatus.com](https://www.atatus.com)
