Skip to content

Metrics

In-process metrics collection for the worker pool and individual workers, with export in JSON, Prometheus text format, and SSE. Includes a built-in UI at /metrics (Overview + Workers). Point-in-time snapshots — for historical retention use Prometheus + Grafana.

For the plugin model (lifecycle, provides, manifest), see Plugin System. For a plugin focused on logs and diagnostics, see Logs.

The plugin reads ctx.pool on the main thread and exposes four endpoints with different formats for the same underlying data. It does not modify the pool or workers — it is strictly read-only.

CapabilityDetail
JSONPool snapshot — for health checks and custom dashboards
PrometheusText exposition format with # HELP / # TYPE for scraping
SSEPush the snapshot every sseInterval ms — for live UIs
Full statsPool + array of workers (id, app, state, requests, uptime, memory)
Built-in UIReact SPA at /metrics with Overview and Workers table
ctx.pool (main thread)
Metrics service (setPool, setConfig)
├──> GET /api/metrics/ → JSON
├──> GET /api/metrics/prometheus → text/plain ──> Prometheus scraper
├──> GET /api/metrics/sse → event-stream ──> React SPA (/metrics)
└──> GET /api/metrics/stats → JSON + workers[]
ConfigurationDefaultNotes
enabledfalsePlugin is opt-in; manifest needs enabled: true
base/metricsRouting prefix
injectBasetrueUI receives base path for reverse proxy
prometheustrueEnables the /api/metrics/prometheus endpoint; when false, returns 404
sseInterval1000ms between SSE events
PersistencenoneIn-process snapshots — Prometheus/VictoriaMetrics handle the time series

All config lives in manifest.yaml. There is no runtime API to change options at execution time.

name: "@buntime/plugin-metrics"
base: "/metrics"
enabled: true # default false
injectBase: true
entrypoint: dist/client/index.html
pluginEntry: dist/plugin.js
menus:
- icon: lucide:activity
path: /metrics
title: Metrics
items:
- icon: lucide:layout-dashboard
path: /metrics
title: Overview
- icon: lucide:cpu
path: /metrics/workers
title: Workers
prometheus: true
sseInterval: 1000
OptionTypeDefaultDescription
prometheusbooleantrueWhen false, /api/metrics/prometheus returns 404. JSON/SSE/stats remain available
sseIntervalnumber (ms)1000SSE push frequency
ValueUpdate rateCPU/networkScenario
2504/sHighReal-time dashboard, load test
5002/sModerateActive debugging
10001/sLow (default)General monitoring
50000.2/sMinimalBackground monitoring
100000.1/sVery lowInfrequent health checks

All routes are under {base}/api/metrics/* (default /metrics/api/metrics/* — note the metrics/api/metrics/ duplication). No auth by default; protect via plugin-authn if needed.

MethodEndpointContent-TypeUse
GET/api/metrics/application/jsonPool snapshot (lightweight)
GET/api/metrics/prometheustext/plain; charset=utf-8Prometheus scraping
GET/api/metrics/ssetext/event-streamLive stream for UIs
GET/api/metrics/statsapplication/jsonPool + full workers array
{
"pool": {
"size": 500,
"active": 12,
"idle": 3,
"creating": 0,
"total": 15,
"utilization": 0.03
},
"uptime": 86400,
"timestamp": "2024-01-23T10:30:00.000Z"
}
{
"pool": { "size": 500, "active": 12, "idle": 3, "creating": 0, "total": 15, "utilization": 0.03 },
"workers": [
{
"id": "worker-abc-123",
"app": "my-app@latest",
"state": "active",
"requests": 1542,
"uptime": 3600,
"memory": { "rss": 52428800, "heapUsed": 31457280 }
}
]
}

Same payload as GET /api/metrics/, pushed every sseInterval ms. Automatic reconnection via EventSource.

const source = new EventSource("/metrics/api/metrics/sse");
source.onmessage = (e) => {
const { pool, uptime } = JSON.parse(e.data);
console.log(`util ${(pool.utilization * 100).toFixed(1)}%`);
};

GET /api/metrics/prometheus returns text exposition format compatible with prometheus v2.x+.

# HELP buntime_pool_size Maximum pool size
# TYPE buntime_pool_size gauge
buntime_pool_size 500
# HELP buntime_pool_active Active workers
# TYPE buntime_pool_active gauge
buntime_pool_active 12
# HELP buntime_pool_idle Idle workers
# TYPE buntime_pool_idle gauge
buntime_pool_idle 3
# HELP buntime_pool_creating Workers being created
# TYPE buntime_pool_creating gauge
buntime_pool_creating 0
# HELP buntime_pool_total Total workers
# TYPE buntime_pool_total gauge
buntime_pool_total 15
# HELP buntime_pool_utilization Pool utilization ratio
# TYPE buntime_pool_utilization gauge
buntime_pool_utilization 0.03
# HELP buntime_uptime_seconds Server uptime in seconds
# TYPE buntime_uptime_seconds counter
buntime_uptime_seconds 86400
MetricTypeDescriptionFormula / source
buntime_pool_sizegaugeMaximum pool capacityStatic runtime configuration
buntime_pool_activegaugeWorkers currently processing requestsstate == "active"
buntime_pool_idlegaugeLive workers with no active requeststate == "idle" or "ready"
buntime_pool_creatinggaugeWorkers being spawnedstate == "creating"
buntime_pool_totalgaugeTotal workers in the poolactive + idle + creating
buntime_pool_utilizationgaugeOccupied fraction (0.0–1.0)active / size
buntime_uptime_secondscounterRuntime process uptimeprocess.uptime()

The source of truth is the runtime’s ctx.pool; this plugin simply serializes the state.

[*] ──> creating ──> ready ──> active
│ ▲
▼ │
idle
active ──> terminated <── idle ──┘
terminated ──> [*]
StateDescription
creatingWorker process is starting up
readyInitialized, waiting for the first request
activeProcessing a request
idleAlive but idle
terminatedShut down (cleanup, scaling, crash)
FieldTypeDescription
idstringUnique identifier
appstringApp name and version (e.g. "my-app@latest")
stateenumCurrent state (see above)
requestsnumberTotal requests processed since spawn
uptimenumberSeconds since spawn
memory.rssnumber (bytes)Resident Set Size
memory.heapUsednumber (bytes)V8 heap in use
export interface MetricsConfig {
prometheus?: boolean;
sseInterval?: number;
}
interface PoolMetrics {
size: number;
active: number;
idle: number;
creating: number;
total: number;
utilization: number;
}
interface WorkerMetrics {
id: string;
app: string;
state: "creating" | "ready" | "active" | "idle" | "terminated";
requests: number;
uptime: number;
memory: { rss: number; heapUsed: number };
}
interface MetricsResponse {
pool: PoolMetrics;
uptime: number;
timestamp: string;
}
interface StatsResponse {
pool: PoolMetrics;
workers: WorkerMetrics[];
}
HookAction
onInitReads ctx.pool and ctx.config; calls setPool(ctx.pool) and setConfig({ prometheus, sseInterval }) on the service layer

There is no onShutdown — resources are GC’d with the process. SSE clients are disconnected by the natural close of the HTTP connection.

The UI is at /metrics and registered via menus in the manifest with a submenu (Overview + Workers). It is hosted by the CPanel shell when enabled.

ViewContent
Overview (/metrics)Utilization gauge, active/idle/total/creating counters, uptime, timestamp
Workers (/metrics/workers)Table with id, app, state, requests, uptime, memory.rss

The SPA consumes GET /api/metrics/sse for a live feed and GET /api/metrics/stats for the workers table.

name: "@buntime/plugin-metrics"
enabled: true
prometheus: true
sseInterval: 1000
prometheus.yml
scrape_configs:
- job_name: buntime
metrics_path: /metrics/api/metrics/prometheus
scrape_interval: 15s
scrape_timeout: 10s
static_configs:
- targets: ['buntime:8000']
labels:
environment: production
service: buntime

Multiple instances:

static_configs:
- targets: ['buntime-1:8000', 'buntime-2:8000', 'buntime-3:8000']

For Kubernetes service discovery, use kubernetes_sd_configs: [{ role: pod }] with relabel_configs filtering by label app=buntime and setting __address__ to pod_ip:8000 — identical to any standard pod scraping pattern.

QuestionQuery
Current utilization (%)buntime_pool_utilization * 100
5-min average utilization (%)avg_over_time(buntime_pool_utilization[5m]) * 100
Remaining capacitybuntime_pool_size - buntime_pool_total
% of slots used(buntime_pool_total / buntime_pool_size) * 100
Rate of change (workers/min)rate(buntime_pool_active[1m])
Uptime in hoursbuntime_uptime_seconds / 3600
Detect restartchanges(buntime_uptime_seconds[1h])
AlertExpressionforSeverity
BuntimeHighUtilizationbuntime_pool_utilization > 0.85mwarning
BuntimePoolExhaustedbuntime_pool_active >= buntime_pool_size1mcritical
BuntimeNoWorkersbuntime_pool_total == 030scritical
BuntimeRestartchanges(buntime_uptime_seconds[5m]) > 0info
PanelTypeQueryNotes
Pool UtilizationGaugebuntime_pool_utilization * 100Thresholds 60/80/100 (green/yellow/red)
WorkersTime seriesbuntime_pool_active, _idle, _creatingStacked
Pool CapacityBar gauge_active, _idle, _size - _totalmax = _size
Utilization 5m avgTime seriesavg_over_time(... [5m]) * 100Smoothed
UptimeStatbuntime_uptime_seconds / 3600Unit: hours

The full stack (Buntime + Prometheus + Grafana) is a standard three-service Docker Compose: mount prometheus.yml/alerts.yml into prom/prometheus, expose 9090/3000, persist a Grafana volume. No plugin-specific nuance beyond the scrape config above.

SymptomLikely causeAction
Prometheus shows target downPlugin disabled, prometheus: false, or network issuecurl http://buntime:8000/metrics/api/metrics/prometheus; check manifest and logs
404 on /api/metrics/prometheusprometheus: false in manifestSet prometheus: true and restart the runtime
Metrics stale in GrafanaShort scrape_timeout or clock driftIncrease scrape_timeout; check time range in Grafana
utilization always 0No requests or very large ctx.pool.sizeGenerate traffic; check pool configuration in the runtime
total > sizeBookkeeping bug (should not occur)Report; check runtime version; review logs
buntime_uptime_seconds resetsProcess restartExpected; use changes() to detect restarts
SSE consuming too much CPULow sseInterval + many clientsRaise to 1000+ ms
Plugin memory growingOnly if there is a leak in the service layer (uncommon)Report; compare /api/metrics/ vs /api/metrics/stats to isolate
Terminal window
curl /metrics/api/metrics/ # JSON snapshot
curl /metrics/api/metrics/prometheus # text exposition
curl /metrics/api/metrics/stats | jq . # pool + workers
curl -N /metrics/api/metrics/sse # stream
plugins/plugin-metrics/
├── manifest.yaml # config + menus (Overview + Workers)
├── plugin.ts # routes + lifecycle (main thread)
├── plugin.test.ts # tests
├── index.ts # worker entrypoint — serves the UI SPA
├── server/
│ ├── api.ts # Hono routes (JSON, prometheus, SSE, stats)
│ └── services.ts # setPool, setConfig, formatters
├── client/ # React + TanStack Router
└── dist/ # build output
  • Plugin System — plugin model, lifecycle, manifest.
  • Logs — companion plugin for SSE-based diagnostics.
  • Runtime Logging — for correlating worker IDs in logs.