---
name: prometheus-metric-naming
source: https://app.decimal.ai/s/prometheus-metric-naming@1/SKILL.md
source_sha256: 0324a31cd41b
---

# Prometheus metric and label naming

## Contract

Enforces the Prometheus naming best-practices on every metric and label a service exposes:
snake_case names, a single-word application prefix, a base-unit suffix, `_total` on counters,
dimensions expressed as labels, and low-cardinality snake_case label keys. Apply when defining
or reviewing instrumentation a Prometheus-compatible scraper reads; not for queries, alerting
rules, or scrape configuration.

## Rules

1. **snake_case, lowercase, ASCII.** Every metric and label name is lowercase words joined by
   single underscores. Never camelCase (`httpRequests`), never hyphens, never dots inside a
   name. A metric name matches `[a-z_][a-z0-9_]*`.

2. **A single-word application prefix (namespace).** Every metric name starts with one domain
   word naming the subsystem it belongs to — `http_`, `process_`, `queue_`, `db_`. A bare
   `requests_total` with no prefix is unnamespaced; give it one.

3. **A unit suffix, and always the base unit.** The name ends in the plural unit it measures,
   and that unit is the SI base unit — `_seconds` (never `_ms`, `_milliseconds`, `_millis`),
   `_bytes` (never `_kb`, `_mb`, `_kilobytes`, `_megabytes`), `_ratio` for a 0.0–1.0 fraction
   (never `_percent` on a 0–100 scale). Convert the value; don't rename the unit.

4. **An accumulating count ends in `_total`.** A counter — a value that only ever increases —
   carries the `_total` suffix (`http_requests_total`, `errors_total`). A gauge — a value that
   goes up and down, like a queue depth or a temperature — never carries `_total`.

5. **Never the Prometheus type in the name.** Do not suffix a name with the instrument kind:
   no `_gauge`, `_counter`, `_histogram`, `_summary`. And `_sum`, `_count`, and `_bucket` are
   RESERVED — the histogram/summary machinery emits them automatically — so they are never a
   suffix you choose for your own metric.

6. **Dimensions are labels, not name fragments.** A characteristic you want to slice by —
   method, status, region, outcome — is a label on ONE metric, never spliced into the name.
   One name (`http_requests_total`) with a `method` label beats a name per method
   (`http_get_requests_total`, `http_post_requests_total`).

7. **Label keys are snake_case too.** Label names follow the same grammar as metric names:
   lowercase, underscores, no camelCase (`status_code`, not `statusCode`).

8. **Labels stay low-cardinality.** Never use a value that is effectively unbounded as a label:
   no user id, email address, full URL or request path, request id, session id, or raw
   timestamp. Every distinct label-value combination is a new stored time series, so an
   unbounded label explodes storage. Bound the set (a normalized route template, an outcome
   enum) or leave the value out of the metric entirely.

## Worked examples

A request counter — camelCase, milliseconds, and a missing suffix become conforming:

```
BEFORE  httpRequestsMs        # a counter, camelCase, and it names a unit it is not measuring

AFTER   http_requests_total                      # counter: snake_case, prefix, _total
        http_request_duration_seconds            # the latency histogram: base unit seconds
```

A size gauge — megabytes and a type suffix become base units and a clean name:

```
BEFORE  memoryUsageMb_gauge

AFTER   process_resident_memory_bytes            # base unit bytes, no _gauge, no _total
```

A fraction — a 0–100 percentage becomes a 0–1 ratio:

```
BEFORE  cacheHitPercent       # 0..100

AFTER   cache_hit_ratio       # 0.0..1.0, suffix _ratio
```

A dimension baked into the name becomes a label:

```
BEFORE  http_get_requests_total
        http_post_requests_total

AFTER   http_requests_total{method="get"}
        http_requests_total{method="post"}
```

A high-cardinality label is dropped; a bounded one stays:

```
BEFORE  http_requests_total{user_id="u-99381", path="/orders/48213"}

AFTER   http_requests_total{route="/orders/{id}", status_code="200"}
```

## Edge cases & exceptions

- **A counter that measures a unit** combines both suffixes, unit before `_total`:
  `network_transmit_bytes_total`, `process_cpu_seconds_total`.
- **Timestamps** are gauges of seconds since the epoch: `..._timestamp_seconds`, never a
  millisecond timestamp.
- **A metric that is genuinely dimensionless** (an in-progress count, a queue depth) takes no
  unit suffix — but it is a gauge, so it still takes no `_total` either.
- **Info-style metadata** (build version, commit) goes on a `..._info` gauge fixed at value 1
  with the metadata as labels — not smuggled into a name.
- **The label value that is unbounded but wanted** (a full path) belongs in a trace or log, or
  as a normalized template label (`/orders/{id}`), never as the raw high-cardinality value.

## Do / Don't

- Do write `http_requests_total`. Don't write `httpRequests`, `HTTPRequests`, or `requests`.
- Do suffix a counter with `_total`. Don't add `_total` to a gauge.
- Do record durations in `_seconds`. Don't record `_ms` / `_milliseconds`.
- Do record sizes in `_bytes`. Don't record `_mb` / `_kb`.
- Do express a fraction as `_ratio` on 0..1. Don't use `_percent` on 0..100.
- Do put method/status/region in a label. Don't splice them into the metric name.
- Do keep label keys snake_case. Don't use `statusCode` or `userId` as a label key.
- Do bound every label's value set. Don't label by user id, email, request id, or full URL.
- Do namespace with a leading domain word. Don't reuse `_sum` / `_count` / `_bucket`.

## Common mistakes

- camelCase names copied from the code's variable names (`httpRequestDurationMs`).
- Omitting `_total`, so a counter looks like a gauge to every tool that keys on the suffix.
- Milliseconds and megabytes, because that is what the code already had.
- A metric name per method or per status, exploding the name space instead of using a label.
- A `user_id`, `email`, `session_id`, or full-path label — each unique value is a new series.
- `_gauge` / `_counter` type suffixes, or reusing the reserved `_count` / `_sum` / `_bucket`.

## Quick checklist

- snake_case, lowercase, single-word application prefix, no camelCase and no hyphens.
- Base-unit suffix: `_seconds` not ms, `_bytes` not mb, `_ratio` (0..1) not percent.
- `_total` on counters only; gauges take neither `_total` nor a type suffix.
- No `_gauge`/`_counter` type suffix; `_sum`/`_count`/`_bucket` reserved, never yours.
- Dimensions (method, status, region) are labels; label keys are snake_case.
- No user id, email, full path, request id, or timestamp as a label value.
