Counters vs gauges in practice

Prometheus · Essentials · 10 min

Most wrong dashboards come from treating a counter like a gauge or a gauge like a counter.

The counter/gauge decision shapes everything you can do later, so it's worth making it deliberately.

A counter answers "how many events happened?". Its stored value is the total since the process started, so two instances with different uptimes show unrelated numbers, and after a restart it resets to zero. Never compare or subtract raw counter values by hand: rate() and increase() understand resets and extrapolate across the window. Expose a counter even when you think you want "errors per minute"; the per-minute view is a query, not a metric.

A gauge answers "what is the value right now?": memory in use, temperature, items in a queue, number of goroutines. It can go up and down, so rate() on it is nonsense. Use avg_over_time, max_over_time, delta() or deriv() instead.

A few practical rules:

  • If the natural operation is "add one", it's a counter. If it's "set to", it's a gauge.
  • A gauge of "events in the last minute" computed by the app is an anti-pattern: Prometheus can't re-window it. Expose the counter.
  • Timestamps belong in gauges: last_backup_timestamp_seconds as Unix time; time() - last_backup_timestamp_seconds then gives the age.
  • Counters sampled every 15 seconds miss nothing in total, but short spikes are smoothed by the rate window.

Client libraries forbid decrementing a counter. If you find yourself wanting to, it's a gauge.

Example

# Counter: raw values are not comparable, rate() is
jobs_processed_total{instance="a"}  182034   # up 3 days
jobs_processed_total{instance="b"}     417   # restarted 2 min ago (reset)
rate(jobs_processed_total[5m])             # ~3.2 and ~3.4 jobs/s: comparable

# Gauge: ask about the value itself
queue_length                               # now
max_over_time(queue_length[1h])            # worst in the last hour
delta(queue_length[30m])                   # change, may be negative

# Timestamp gauge -> age
time() - last_backup_timestamp_seconds > 86400   # backups older than a day

Remember

  • Counter = cumulative events; only its rate/increase is meaningful
  • Gauge = current state; use *_over_time, delta, deriv
  • Expose raw counters and timestamps, compute windows in PromQL

Watch out for

  • Pre-computing a 'per minute' gauge in the application, which can't be re-aggregated or re-windowed.

Check yourself

  1. Instance A shows jobs_processed_total = 5000 and instance B shows 300. What can you conclude?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: prometheus.io/docs/practices/instrumentation/#counter-vs-gauge-summary-vs-histogram