# Типы метрик: counter, gauge, histogram, summary _Observability и мониторинг · LinuxLab Knowledge Base_ **TL;DR:** 4 типа метрик: counter (только вверх), gauge (любое значение), histogram (buckets для p99), summary (quantile в клиенте). Native histogram (Prom 2.40+) - sparse buckets, аккуратнее по памяти. Exemplars связывают метрику с trace_id. ## Зачем разные типы Метрика - это не просто число. Семантика определяет как её агрегировать. - `http_requests_total` за минуту вырос на 1200 → **rate = 20 req/s**. Counter монотонен, разница между измерениями = темп. - `memory_usage_bytes = 2.3 GB` сейчас. **Прямое значение**, gauge может скакать вверх-вниз. Среднее за час = average. - `request_duration_seconds` распределение → нужны **percentiles** (p50, p99). Считать среднее бесполезно: hides tail latency. Каждый тип, это контракт между приложением и системой запросов (PromQL/MetricsQL). Использование `rate()` на gauge, мусор. `avg()` на histogram, теряешь смысл. ## Counter, монотонно растущее Семантика: «сколько раз случилось событие». Только вверх, reset на рестарт процесса. ``` http_requests_total{method="GET",status="200"} 12345 ``` В PromQL **не используй raw counter**. Всегда через `rate()` или `increase()`: ``` rate(http_requests_total[5m]) # req/s в среднем за 5 мин increase(http_requests_total[1h]) # сколько прироста за час sum by (status)(rate(http_requests_total[5m])) # по статусам ``` `rate()` автоматически handles **counter reset** (при рестарте). Примеры counter в реальных системах: - `process_cpu_seconds_total`, суммарное CPU - `node_network_receive_bytes_total`, байты пришли - `kafka_consumer_messages_consumed_total` ## Gauge, текущее значение Семантика: «значение прямо сейчас». Может расти и падать. ``` node_memory_MemAvailable_bytes 4521234432 goroutines_active 142 queue_depth{queue="orders"} 87 ``` В PromQL, напрямую: ``` node_memory_MemAvailable_bytes / 1024 / 1024 / 1024 avg_over_time(queue_depth[10m]) max(queue_depth) by (queue) ``` **Не используй `rate()` на gauge!** `rate(temperature[5m])` бессмысленно. ## Histogram, distribution с buckets Cемантика: «сколько событий попало в каждый bucket по значению». ``` http_request_duration_seconds_bucket{le="0.1"} 4500 http_request_duration_seconds_bucket{le="0.25"} 4800 http_request_duration_seconds_bucket{le="0.5"} 4920 http_request_duration_seconds_bucket{le="1"} 4980 http_request_duration_seconds_bucket{le="+Inf"} 5000 http_request_duration_seconds_sum 350.5 http_request_duration_seconds_count 5000 ``` Каждый bucket, counter. `le="0.5"` = «сколько <=0.5 сек». Percentile вычисляется query-time: ``` histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) ``` **Aggregatable** между instances: можно `sum by (status)`. В отличие от summary. Подбирать buckets, внимательно. Default 10 buckets от 5ms до 10s подходит для HTTP, но не для DB queries (нужны microseconds) или batch jobs (минуты). Слишком много buckets → cardinality explosion ([cardinality-explosion](/kb/cardinality-explosion.md)). ## Summary, pre-computed quantiles Клиент сам считает quantile (p50, p95, p99) и экспортирует: ``` http_request_duration_seconds{quantile="0.5"} 0.012 http_request_duration_seconds{quantile="0.95"} 0.087 http_request_duration_seconds{quantile="0.99"} 0.241 http_request_duration_seconds_sum 350.5 http_request_duration_seconds_count 5000 ``` **Проблемы summary:** - **Не aggregatable**: нельзя `sum by (instance)`, это математически неверно. Имеет смысл только per-instance. - **Дорого CPU/memory**, клиент держит running quantile estimator. - **Quantile fixed at compile time**, нельзя посчитать p99.9 если клиент не экспортит. Использовать summary только когда p99 дёшево считать в клиенте и агрегация не нужна. В большинстве случаев, **histogram лучше**. ## Histogram vs Summary | Критерий | Histogram | Summary | |----------|-----------|---------| | Где считается | server (PromQL) | client | | Aggregatable across instances | да | **нет** | | Quantile precision | bucket-bounded | exact-ish | | Quantile changeable post-fact | да (новый запрос) | нет | | Memory in client | low | medium | | Cardinality | bucket × labels | quantile × labels | **Правило**: используй histogram. Summary, только для legacy. ## Native histogram (Prom 2.40+) Проблема classic histogram: фиксированные buckets, либо много (cardinality), либо мало (плохая precision). **Native histogram** (a.k.a sparse histogram), буткеты на лету, логарифмическая шкала, sparse encoding: ``` metric_native_histogram{} {schema:1, count:5000, sum:350.5, positive_buckets: ...sparse...} ``` - Один time series на метрику (вместо N buckets) - Точность ≈ 1-3% на любом quantile - В 100× меньше storage чем classic с такой же precision Требует: - Client SDK с поддержкой (Go ≥1.16, Python ≥0.18, Java ≥1.0) - Prometheus 2.40+ с `--enable-feature=native-histograms` - Grafana 10+ для визуализации Production-ready с Prom 2.50+ (2023). Должен стать дефолтом. ## Exemplars, мост к трассам Exemplar, конкретный sample, прикреплённый к bucket: ``` http_request_duration_seconds_bucket{le="0.5"} 4920 # {trace_id="abc123"} 0.42 1683456789.123 ``` «Один из 4920 запросов в этом bucket был с trace_id=abc123». В Grafana можно кликнуть на точку графика и прыгнуть в трассу ([tracing-basics](/kb/tracing-basics.md)). Поддержка: - Prometheus 2.26+ (требует OpenMetrics format) - SDK: Go, Java, Python, .NET - Grafana 8+ ## OpenMetrics, formal spec OpenMetrics, стандарт CNCF (RFC-style) для метрик, расширяет Prometheus exposition format: - UTF-8, не ASCII - `# UNIT` line - Exemplars формализованы - JSON serialization (опционально) В 2025, большинство SDK exportят в OpenMetrics автоматически. Prometheus и [[opentelemetry|OTel collector]] оба понимают. ## Когда что-то пошло не так - **`rate(my_metric[5m])` даёт 0**, counter называется как gauge, PromQL вычисляет `rate` от непрерывно одинакового значения. Переименуй метрику в `_total`. - **p99 latency скачет**, недостаточно samples в окне (rate over `[5m]` для 0.1 req/s = 30 событий, шумно). Расширь окно до `[30m]`. - **histogram_quantile() возвращает NaN**, buckets не покрывают наблюдаемые значения, или нет данных в окне. Проверь `_count > 0`. - **Cardinality explosion**, добавили `endpoint=/api/v1/user/123` каждый user_id создаёт новый series. Рефактор: `endpoint=/api/v1/user/:id`. - **Summary quantile неточный после рестарта**, client estimator сбрасывается. Это особенность summary, не баг. - **Buckets `le=` строкой, не числом**, Prom expects strings `"0.1"`, `"0.5"`. `le=0.1` (число) ломается. ## Команды ```bash curl -s localhost:9090/metrics | grep -E 'http_requests_total|^# (TYPE|HELP) http_requests' ``` Посмотреть raw expose - HELP, TYPE и значения counter в OpenMetrics формате ```bash promtool query instant 'rate(http_requests_total[5m])' ``` Вычислить request rate за 5-min окно через CLI без открытия Grafana ```bash promtool query instant 'histogram_quantile(0.99, sum by (le)(rate(http_request_duration_seconds_bucket[5m])))' ``` p99 latency aggregated across instances - правильный sum by (le) ДО quantile ```bash curl -s 'localhost:9090/api/v1/query?query=up' | jq '.data.result[].value' ``` Все 'up' gauge values - 1 = scrape работает, 0 = target down ```bash promtool check metrics /etc/prometheus/recording.yml ``` Валидация что в recording rules используются корректные функции для типов ```bash prometheus --enable-feature=native-histograms,exemplar-storage ``` Включить native histograms и storage для exemplars в Prometheus 2.40+ ## См. также - [Prometheus: scrape, TSDB, PromQL и production-pitfalls](/kb/prometheus-basics.md) - [OpenTelemetry: signals, OTLP, Collector pipeline](/kb/opentelemetry.md) - [Cardinality explosion: как убить Prometheus и как чинить](/kb/cardinality-explosion.md) - [SLI / SLO / error budget: SRE-метрики без шума](/kb/sli-slo-error-budget.md)