Prometheus - это набор инструментов для системного мониторинга и оповещения с открытым исходным кодом (Apache-2.0 License), изначально созданный в SoundCloud . С момента его создания в 2012 году многие компании и организации используют Prometheus, у проекта очень активное сообщество разработчиков и пользователей. Написан на языке Go.

Он собирает метрики от настроенных целей с заданными интервалами (pull model), оценивает выражения правил, отображает результаты и может рассылать алерты при соблюдении определенных условий.

Для примера создадим простое HTTP приложение, которое отвечает всего по 3-м запросам:

  • /ping - основной URI. Получив запрос ждем от 100 до 350 миллисекунд и вызвращаем ответ.
  • /health - выводит текущее время.
  • /metrics - URI с метриками для опросов от Prometheus сервера.
package main

import (
	"fmt"
	"math/rand"
	"net/http"
	"time"

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

var (
	exampleCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "example_total",
			Help: "Statistics of calls endpoints",
		},
		[]string{
			"uri",
		},
	)

	exampleResponseTimeHistogram = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name:    "example_response_time_s",
			Help:    "Histogram of response times in seconds",
			Buckets: []float64{.100, .150, .200, .250, .300, .350},
		},
		[]string{
			"uri",
		},
	)
)

func init() {
	prometheus.MustRegister(exampleCounter)
	prometheus.MustRegister(exampleResponseTimeHistogram)
}

func countCall(uri string) {
	exampleCounter.WithLabelValues(
		uri,
	).Inc()
}

func main() {
	// histogram observer for response time
	histogram := exampleResponseTimeHistogram.WithLabelValues(
		"/ping",
	)
	// health handler
	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		countCall("/health")
		fmt.Fprintln(w, time.Now().String())

	})
	// ping handler
	http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
		countCall("/ping")
		start := time.Now()

		randSrc := rand.NewSource(time.Now().UnixNano())
		rnd := rand.New(randSrc)
		// t will be between 100 and 350
		t := 100 + rnd.Intn(250)

		time.Sleep(time.Duration(t) * time.Millisecond)

		responseTime := time.Since(start)
		responseTimeMs := responseTime.Nanoseconds() / int64(time.Millisecond)
		histogram.Observe(responseTime.Seconds())

		fmt.Fprintf(w, "pong %dms %fs\n", responseTimeMs, responseTime.Seconds())
	})

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

	// Listen and server
	if err := http.ListenAndServe(":8080", nil); err != nil {
		panic(err)
	}
}

запустим и проверим

$ for i in $(seq 1 10); do curl -s http://127.0.0.1:8080/ping ; done && curl -s http://127.0.0.1:8080/health
pong 148ms 0.148886s
pong 298ms 0.298304s
pong 289ms 0.289214s
pong 253ms 0.253870s
pong 344ms 0.344168s
pong 117ms 0.117342s
pong 276ms 0.276180s
pong 146ms 0.146125s
pong 258ms 0.258153s
pong 286ms 0.286994s
2021-01-12 16:27:49.210935 +0300 MSK m=+10.435253922
$ curl -s http://127.0.0.1:8080/metrics | fgrep example
# HELP example_response_time_s Histogram of response times in seconds
# TYPE example_response_time_s histogram
example_response_time_s_bucket{uri="/ping",le="0.1"} 0
example_response_time_s_bucket{uri="/ping",le="0.15"} 3
example_response_time_s_bucket{uri="/ping",le="0.2"} 3
example_response_time_s_bucket{uri="/ping",le="0.25"} 3
example_response_time_s_bucket{uri="/ping",le="0.3"} 9
example_response_time_s_bucket{uri="/ping",le="0.35"} 10
example_response_time_s_bucket{uri="/ping",le="+Inf"} 10
example_response_time_s_sum{uri="/ping"} 2.419235913
example_response_time_s_count{uri="/ping"} 10
# HELP example_total Statistics of calls endpoints
# TYPE example_total counter
example_total{uri="/health"} 1
example_total{uri="/ping"} 10

Теперь подробнее остановимся на типах метрик.

  • Counter - это кумулятивная метрика, представляющая один монотонно увеличивающийся счетчик, значение которого может увеличиваться или сбрасываться до нуля только при перезапуске. Например, можно использовать счетчик для представления количества обслуженных запросов, выполненных задач или ошибок. Счетчик не рекомендуется использовать для значений, которые могут уменьшиться. Например, для подсчета запущенных горутин; вместо этого используется gauge.
  • Gauge - это показатель (датчик), который представляет собой одно числовое значение, которое может произвольно увеличиваться и уменьшаться. Датчики обычно используются для измерения значений, таких как температура или текущее использование памяти, но также для “счетчиков”, которые могут увеличиваться и уменьшаться, например, количество одновременных запросов.
  • Histogram - гистограмма представляет собой выборку наблюдений (observations) (обычно такие вещи, как продолжительность запросов или времена ответов)
  • и подсчитывает их в настраиваемых сегментах (buckets). Также предоставляет сумму всех наблюдаемых значений.
  • Summary - подобно гистограмме, в summary выбираются наблюдения. Хотя также представляется общее количество наблюдений и сумму всех наблюдаемых значений, вычисляются настраиваемые квантили в скользящем временном окне.