Пишем метрики в Prometheus на языке Go

28 января 2019

В рамках поста Устанавливаем связку из Prometheus и Grafana мы познакомились с Prometheus и разобрались с его настройкой. Теперь давайте выясним, как отправить в него каких-нибудь метрик из нашего собственного приложения. Писать будем на языке Go, но я почти уверен, что для других языков существуют аналогичные библиотеки.

Объем демонстрационного кода составил всего лишь 80 строк, так что просто рассмотрим его целиком:

package main

import (
  "log"
  "flag"
  "math/rand"
  "net/http"
  "time"

  "github.com/prometheus/client_golang/prometheus"
  "github.com/prometheus/client_golang/prometheus/promhttp"
)

var addr = flag.String("listen-address", ":8080",
  "The address to listen on for HTTP requests.")

func main() {
  flag.Parse()

  usersRegistered := prometheus.NewCounter(
    prometheus.CounterOpts{
      Name: "users_registered",
    })
  prometheus.MustRegister(usersRegistered)

  usersOnline := prometheus.NewGauge(
    prometheus.GaugeOpts{
      Name: "users_online",
    })
  prometheus.MustRegister(usersOnline)

  requestProcessingTimeSummaryMs := prometheus.NewSummary(
    prometheus.SummaryOpts{
      Name:       "request_processing_time_summary_ms",
      Objectives: map[float64]float64{0.5:0.05, 0.9:0.01, 0.99:0.001},
    })
  prometheus.MustRegister(requestProcessingTimeSummaryMs)

  requestProcessingTimeHistogramMs := prometheus.NewHistogram(
    prometheus.HistogramOpts{
      Name: "request_processing_time_histogram_ms",
      Buckets: prometheus.LinearBuckets(0, 10, 20),
    })
  prometheus.MustRegister(requestProcessingTimeHistogramMs)

  go func() {
    for {
      usersRegistered.Inc() // or: Add(5)
      time.Sleep(1000 * time.Millisecond)
    }
  }()

  go func() {
    for {
      for i := 0; i < 10000; i++ {
        usersOnline.Set(float64(i)) // or: Inc(), Dec(), Add(5), Dec(5)
        time.Sleep(10 * time.Millisecond)
      }
    }
  }()

  go func(){
    src := rand.NewSource(time.Now().UnixNano())
    rnd := rand.New(src)
    for {
      obs := float64(100 + rnd.Intn(30))
      requestProcessingTimeSummaryMs.Observe(obs)
      requestProcessingTimeHistogramMs.Observe(obs)
      time.Sleep(10 * time.Millisecond)
    }
  }()

  http.Handle("/metrics", promhttp.Handler())

  log.Printf("Starting web server at %s\n", *addr)
  err := http.ListenAndServe(*addr, nil)
  if err != nil {
    log.Printf("http.ListenAndServer: %v\n", err)
  }
}

Библиотека автоматически экспортирует метрики рантайма языка Go, такие, как количество работающих горутин, время сборки мусора, и так далее. То есть, для получения этих метрик особо ничего делать не нужно.

Кроме того, мы можем создавать собственные метрики следующих типов:

  • Counter, как несложно угадать по названию, представляет собой простой счетчик. Не самый полезный тип метрики, поскольку счетчик этот является неубывающим. То есть, подходит он для отображения только чего-то вроде суммарного числа учетных записей в системе, да и то лишь при условии, что учетные записи являются неудаляемыми.
  • Gauge, здесь используется в значении «мера». Gauge похож на Counter, но в отличие от него может не только возрастать, но и убывать. Этот тип отлично подходит для отображения текущего значения чего-то — температуры, давления, числа пользователей онлайн, и так далее.
  • Histogram представляет собой гистограмму. Этот тип метрики хранит число раз, которое измеряемая величина попала в заданный интервал значений (бакет). Гистограммы может быть трудновато использовать, если интервал допустимых значений величины заранее неизвестен. В Grafana по гистограмме можно примерно посчитать процентили, используя функцию histogram_quantile.
  • Summary честно считает заданные процентили. Идеально подходит для измерения времени ответа или чего-то такого. Минус Summary заключается в том, что его дорого считать. Поэтому часто обходятся гистограммами и примерными значениями процентилей.
  • Наконец, существуют типы HistogramVec, SummaryVec и так далее. Они представляют собой словарь (map) из описанных выше типов. То есть, это как бы метрики со строковыми метками, или создаваемые на лету метрики. Отлично подходят в случаях, когда вам нужно измерить время ответа сервера в зависимости от запроса, или вроде того. Примеры использования этих типов метрик вы найдете на godoc.org.

Ну вот и все. Остается только дописать адрес нашего приложения в prometheus.yml, как ранее мы это делали для Node Exporter, сказать:

sudo systemctl restart prometheus

…, и метрики начнут собираться.

Метрики, к сожалению, не являются панацеей от всего. Допустим, у одного конкретного пользователя почему-то тормозит один конкретный запрос. А запрос этот на самом деле распадается на множество подзапросов к разным микросервисам. В такой ситуации метрики не помогут отследить, где и почему тормозит этот один запрос. Тем не менее, метрики довольно полезны, когда требуется оценить состояние системы в целом.

Дополнение: Вас также могут заинтересовать посты про агрегацию логов с Loki и распределенную трассировку при помощи Jaeger.

Метки: .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.