← На главную

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

В рамках поста Устанавливаем связку из 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.