# Metrics

> Prometheus-shaped metrics, canonical http.* names, and the cardinality discipline that keeps it cheap.

Canonical: https://docs.getlumin.dev/concepts/metrics/

A metric point in Lumin has four parts:

```text
name       e.g. http.requests
type       counter | gauge | histogram
labels     a small map of strings
value      number, plus aggregations (avg/sum/min/max/p50/p95/p99 for histograms)
```

Names use dotted notation by convention (`http.requests`, `mem.used_bytes`), but underscores work too — the agent doesn't normalise.

## How metrics get there

Two ingest paths:

1. **The agent scrapes your service's `/metrics` endpoint.** Add `lumin.metrics=true`, `lumin.metrics.port=…`, and (recommended) `lumin.metrics.framework=…` labels to a container. The agent fetches Prometheus exposition every tick, maps known framework names to the canonical schema, and forwards.
2. **You POST to `/v1/metrics` directly.** Same wire format and project scoping as logs.

The agent also emits **host** metrics (CPU, memory, disk, net) and **container** metrics (per-container resource usage) without you doing anything — those don't need labels.

## The canonical `http.*` schema

The Metrics page in the UI has KPI tiles, a latency-distribution histogram, and a top-routes table. **All of these are wired to three specific metric names.** If your service emits these, the panels light up; if it doesn't, you'll have to build custom charts.

| Metric | Type | Labels | Notes |
|---|---|---|---|
| `http.requests` | counter | `{method, route, status_class}` | `status_class` ∈ `{2xx, 3xx, 4xx, 5xx}`. |
| `http.latency_ms` | histogram | `{method, route}` | Buckets `[5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000]` ms work for typical Go/Python/Node services. |
| `http.errors` | counter | `{method, route, status}` | Increment only on `status >= 500`. Raw 3-digit code; low cardinality in practice. |

When `lumin.metrics.framework` is set on a container, the agent rewrites the framework's underlying metric names to these canonical names. See the [stack guides](https://docs.getlumin.dev/stacks/) for the exact mapping per framework.

## Cardinality discipline

The single most important number in metrics-land is **cardinality**: the count of distinct label-value combinations. Cardinality drives storage cost and query latency. The rules of thumb:

- **Use templated routes**, not expanded paths. `/projects/:id` is one route. `/projects/1`, `/projects/2`, … are N paths sharing one route. If you let expanded paths leak into the `route` label, the cardinality of `http.requests` grows linearly with your data.
- **`status_class` over `status` for counter labels.** 4 values instead of ~70.
- **Don't put user IDs, request IDs, trace IDs, or session IDs in labels.** Those go on log rows, where the storage model handles high cardinality. Putting them on metric labels is the classic way to make a monitoring stack expensive.

If you find yourself needing to slice metrics by something user-shaped, the answer is usually "log this event" or "track this as a browser event", not "add a label".

## Histograms

Use a histogram when you care about *distribution* (p50, p95, p99), which is almost always what you want for latency, payload sizes, or any "how big does this thing get" question. Use a counter when you only care about *count* over time, and use a gauge when the underlying quantity is point-in-time (memory used, queue depth, …).

The default histogram buckets in Lumin's stack guides cover the typical "1 ms to 5 seconds" range for HTTP latency. If your service is consistently faster or slower, override the buckets — the goal is to have at least three or four buckets straddle your service's actual latency distribution so percentile estimates stay accurate.

## Aggregations

Lumin computes avg, sum, min, max, and (for histograms) p50/p95/p99 at query time, over the window you pick in the UI.

Lumin does not downsample. It keeps every point at full resolution until the point passes your retention horizon, and then deletes it. A 30-day chart has the same resolution as a 1-hour chart. See [Retention](https://docs.getlumin.dev/concepts/retention/).

## Pulling vs. pushing

The agent **pulls** from `/metrics` endpoints (Prometheus model). If you'd rather push, the `POST /v1/metrics` endpoint accepts a JSON batch of points. Pulling is recommended for services that already expose Prometheus exposition; pushing is for cron jobs and short-lived processes that can't host an HTTP endpoint long enough for a scrape to happen.
