Headroom

Metrics & Monitoring

Monitor compression performance, cost savings, and system health with Headroom's built-in metrics, Prometheus endpoint, and SDK APIs.

Headroom provides comprehensive metrics for monitoring compression performance, cost savings, and system health through both the proxy server and the SDK.

Proxy Endpoints

Stats Endpoint

curl http://localhost:8787/stats
{
  "persistent_savings": {
    "lifetime": {
      "tokens_saved": 12500,
      "compression_savings_usd": 0.04
    }
  },
  "requests": {
    "total": 42,
    "cached": 5,
    "rate_limited": 0,
    "failed": 0
  },
  "tokens": {
    "input": 50000,
    "output": 8000,
    "saved": 12500,
    "savings_percent": 25.0
  },
  "cost": {
    "total_cost_usd": 0.15,
    "total_savings_usd": 0.04
  },
  "cache": {
    "entries": 10,
    "total_hits": 5
  }
}

Persistent savings are stored at ~/.headroom/proxy_savings.json and survive proxy restarts. Override the path with HEADROOM_SAVINGS_PATH.

Historical Savings

curl http://localhost:8787/stats-history

Returns durable compression history with hourly, daily, weekly, and monthly rollups. Supports CSV export:

curl "http://localhost:8787/stats-history?format=csv&series=daily"
curl "http://localhost:8787/stats-history?format=csv&series=monthly"

Prometheus Metrics

curl http://localhost:8787/metrics
# HELP headroom_requests_total Total requests processed
headroom_requests_total 1234

# HELP headroom_tokens_saved_total Total tokens saved
headroom_tokens_saved_total 5678900

# HELP headroom_persistent_savings_tokens_saved_total Durable lifetime input tokens saved by proxy compression
headroom_persistent_savings_tokens_saved_total 5678900

# HELP headroom_latency_ms_sum Sum of request latency in milliseconds
headroom_latency_ms_sum 152300
# HELP headroom_latency_ms_count Count of latency samples
headroom_latency_ms_count 1234

# HELP headroom_provider_cache_hit_requests_total Requests that read from the provider's prompt cache
headroom_provider_cache_hit_requests_total{provider="anthropic"} 456

No percentile buckets on /metrics

There are no Prometheus histogram buckets for compression ratio or latency — build percentiles or averages from _sum / _count pairs (headroom_latency_ms_sum, headroom_latency_ms_count, and so on), or use the headroom perf CLI for real p95/p99.

OpenTelemetry (OTLP) Export

The proxy can also push its counters to any OTLP/HTTP endpoint. Install the extra and set four variables:

pip install "headroom-ai[proxy,otel]"
HEADROOM_OTEL_METRICS_ENABLED=1
HEADROOM_OTEL_METRICS_ENDPOINT=http://127.0.0.1:4318/v1/metrics
HEADROOM_OTEL_SERVICE_NAME=headroom-proxy
HEADROOM_OTEL_RESOURCE_ATTRIBUTES=deployment.environment=prod
VariableDefaultPurpose
HEADROOM_OTEL_METRICS_ENABLED0Enable Headroom-managed OTLP metric export
HEADROOM_OTEL_METRICS_EXPORTERotlp_httpotlp_http or console (local debugging)
HEADROOM_OTEL_METRICS_ENDPOINTunsetFull OTLP metrics URL — Headroom does not append /v1/metrics for you
HEADROOM_OTEL_METRICS_HEADERSunsetComma-separated key=value auth headers
HEADROOM_OTEL_METRICS_EXPORT_INTERVAL_MS10000Export interval
HEADROOM_OTEL_SERVICE_NAMEheadroom-proxyOTEL service.name
HEADROOM_OTEL_RESOURCE_ATTRIBUTESunsetComma-separated resource attributes

Exported counters include headroom.proxy.requests, headroom.proxy.tokens.input, and headroom.proxy.tokens.output. headroom.proxy.tokens.saved is the all-layer total: message/compression savings plus tool-schema deferral savings. The component counter headroom.proxy.tokens.tool_schema_saved exposes the deferral portion separately; headroom.compression.tokens.saved remains the compression-pipeline component.

Confirm the exporter is live with curl -s http://localhost:8787/stats | jq .otel.

If your application already configures a global OTEL meter provider, leave HEADROOM_OTEL_* unset — Headroom records into the ambient provider automatically.

Dynatrace

Point the exporter at your environment's OTLP API and add the API token as a header. The token needs the metrics.ingest scope.

HEADROOM_OTEL_METRICS_ENABLED=1
HEADROOM_OTEL_METRICS_ENDPOINT="https://<env-id>.live.dynatrace.com/api/v2/otlp/v1/metrics"
HEADROOM_OTEL_METRICS_HEADERS="Authorization=Api-Token dt0c01.XXXX"
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=DELTA
HEADROOM_OTEL_SERVICE_NAME=headroom-proxy

Delta temporality is not optional

OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=DELTA is required. Dynatrace only ingests delta counters and rejects cumulative ones with UNSUPPORTED_METRIC_TYPE_MONOTONIC_CUMULATIVE_SUM, while the OTEL SDK default is cumulative. Without this line, every Headroom metric is dropped at ingest and the proxy logs no error.

Restart the proxy, then search the Dynatrace metric explorer for headroom.proxy.tokens.saved — data appears within ~30s.

For an ActiveGate deployment, swap the base URL for https://<activegate>:9999/e/<env-id>/api/v2/otlp/v1/metrics. If you already run an OpenTelemetry Collector, send Headroom to it instead and add the cumulativetodelta processor — then the temporality variable is unnecessary and the collector holds the token.

Trace export is separate: Headroom's self-configured tracing targets Langfuse only. To land its spans in Dynatrace, leave HEADROOM_LANGFUSE_* unset and run the proxy under opentelemetry-instrument with the standard OTEL_EXPORTER_OTLP_ENDPOINT / OTEL_EXPORTER_OTLP_HEADERS / OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf variables; Headroom records into the ambient tracer provider.

Health Check

curl http://localhost:8787/health
{
  "status": "healthy",
  "version": "0.37.0",
  "uptime_seconds": 3600
}

SDK Metrics

Proxy Stats

The TypeScript SDK queries the proxy for stats:

import {  } from 'headroom-ai';

const  = new ();

// Get proxy stats
const  = await .proxyStats();
.(`Tokens saved: ${.tokens.saved}`);
.(`Savings: ${.tokens.savingsPercent}%`);

Compression Result Metrics

Every compress() call returns metrics:

import {  } from 'headroom-ai';

const  = await (messages, { : 'gpt-4o' });
.(`Tokens: ${.tokensBefore} -> ${.tokensAfter}`);
.(`Saved: ${.tokensSaved} (${(.compressionRatio * 100).(1)}%)`);
.(`Transforms: ${.transformsApplied.join(', ')}`);

Session Stats

Quick stats for the current session (no database query):

stats = client.get_stats()
print(f"Mode: {stats['config']['mode']}")
print(f"Tokens saved: {stats['session']['tokens_saved_total']}")
print(f"Requests optimized: {stats['session']['requests_optimized']}")

Returns:

{
    "session": {
        "requests_total": 10,
        "requests_optimized": 8,
        "requests_audit": 2,
        "tokens_saved_total": 15000,
        "cache_hits": 3,
    },
    "config": {
        "mode": "optimize",
        "provider": "openai",
        "cache_optimizer": "openai-prefix-stabilizer",
        "semantic_cache": False,
    },
    "transforms": {
        "smart_crusher_enabled": True,
        "cache_aligner_enabled": True,
    },
}

Historical Metrics

Query stored metrics from the database:

from datetime import datetime, timedelta

metrics = client.get_metrics(
    start_time=datetime.utcnow() - timedelta(hours=1),
    limit=100,
)

for m in metrics:
    print(f"{m.timestamp}: {m.tokens_input_before} -> {m.tokens_input_after}")

Summary Statistics

Aggregate statistics across all stored metrics:

summary = client.get_summary()
print(f"Total requests: {summary['total_requests']}")
print(f"Total tokens saved: {summary['total_tokens_saved']}")
print(f"Avg tokens saved per request: {summary['avg_tokens_saved']:.0f}")

Logging

import logging

# INFO level shows compression summaries
logging.basicConfig(level=logging.INFO)

# DEBUG level shows detailed transform decisions
logging.basicConfig(level=logging.DEBUG)

Example output:

INFO:headroom.transforms.pipeline:Pipeline complete: 45000 -> 4500 tokens (saved 40500, 90.0% reduction)
INFO:headroom.transforms.smart_crusher:SmartCrusher applied top_n strategy: kept 15 of 1000 items
DEBUG:headroom.transforms.smart_crusher:Kept items: [0,1,2,42,77,97,98,99] (errors at 42, warnings at 77)
# Log to file
headroom proxy --log-file headroom.jsonl

# Increase verbosity (no --log-level flag; use the env var)
HEADROOM_LOG_LEVEL=debug headroom proxy

Cost Tracking

Budget Alerts

Set a budget limit in the proxy:

headroom proxy --budget 10.00

When the budget is exceeded, requests return a budget exceeded error, the /stats endpoint shows budget status, and logs indicate the budget state.

Measured vs Estimated Spend

Every cost record carries a basis — where its input-token count came from. When a provider response includes a usage breakdown, the basis is measured. When it doesn't, Headroom substitutes its own tokens_sent count so input cost isn't dropped from the budget, and the record's basis is estimated. Headroom logs one warning per model the first time this happens.

/stats keeps the two separable under cost.budget_basis:

{
  "cost": {
    "budget_limit_usd": 10.0,
    "budget_period": "daily",
    "budget_estimated_basis": "count",
    "budget_basis": {
      "total_usd": 3.1400,
      "measured_usd": 2.9000,
      "estimated_usd": 0.2400,
      "estimated_pct": 7.6,
      "records": 412,
      "estimated_records": 31
    }
  }
}

An estimate can drift in either direction, so you choose what it does to the hard limit:

headroom proxy --budget 10.00 --budget-estimated-basis count   # default
ValueEffect
countEstimated spend consumes the budget like measured spend. The default; matches historical behavior.
ignoreEstimated spend is still booked and reported, but only provider-reported spend consumes the budget.
blockRefuse requests once the period holds any estimated spend, rather than enforcing a hard limit against a guess.

Env: HEADROOM_BUDGET_ESTIMATED_BASIS. headroom doctor reports the estimated share alongside the budget check.

Key Metrics to Monitor

MetricWhat It Tells YouTarget
headroom_tokens_saved_totalRuntime tokens saved since this proxy process startedHigher is better
headroom_persistent_savings_tokens_saved_totalDurable lifetime tokens saved from /stats.persistent_savingsHigher is better
headroom_overhead_ms_sum / headroom_overhead_ms_countMean latency Headroom itself adds (there are no percentile buckets — see the callout above)Low is better
headroom_provider_cache_hit_requests_total / headroom_provider_cache_requests_totalCache effectiveness, by provider>20% is good
headroom_requests_failed_totalReliability (upstream 5xx errors)0

Grafana Dashboard

A dashboard template ships in examples/grafana/headroom-dashboard.json. Its metric names match /metrics, but every panel query filters on a pool label (e.g. headroom_tokens_saved_total{pool=~"..."}) that no metric in headroom/proxy/prometheus_metrics.py actually emits, so the panel dropdowns come up empty on import. Edit the queries to drop the pool=~"..." filter, or use the ad-hoc queries below instead.

Example ad-hoc queries against the same metric family:

PanelPromQL
Runtime Tokens Savedheadroom_tokens_saved_total
Lifetime Tokens Savedheadroom_persistent_savings_tokens_saved_total
Mean Headroom Overheadrate(headroom_overhead_ms_sum[5m]) / rate(headroom_overhead_ms_count[5m])
Cache Hit Rate (by provider)sum by (provider) (rate(headroom_provider_cache_hit_requests_total[5m])) / sum by (provider) (rate(headroom_provider_cache_requests_total[5m]))

On this page