This is an abstract of Prometheus Up and Running book and a few other sources, most listings are taken from them.
Prometheus is an open-source metrics-based monitoring and alerting system originally built at Soundcloud. It uses pulling model to periodically scrap target metrics from an HTTP end-point.
Prometheus designed for operational monitoring, where race conditions and inaccuracies are fact of life, therefore it can't have 100% correct data by design and doesn't aim to it.
Further Reading:
Prometheus scraps metrics in a simple format:
<metric name>{<label name>=<label value>, ...} <value>
For example:
api_http_requests_total{method="POST", handler="/messages"} 20
api_http_requests_total
is a metric namemethod
and handler
are labels used for querying a metric20
is a metric valueIt's a common convention to name metrics snake_case
and use reverse domain name notation.
Prometheus has common suffixes convention helps to understand metric types:
_total
is a counter_count
is a counter_sum
stands for summary_bucket
is for a histogramFurther Reading:
It's a good practice to annontate metrics by adding TYPE
and HELP
:
# HELP latency_seconds Latency in seconds.
# TYPE latency_seconds summary
latency_seconds_sum{path="/foo"} 1.0
TYPE
here stands for a metric type.HELP
is just a documentation string.Metric types are usually in the metric # TYPE
annotation
counter
represents any value that goes only up (http_request_total
, etc). counters are useful to monitor rate of an event, like rps
.
gauge
is similar to summary
and provides a bucket of metric values. Gauges are useful to observe some metrics can go up and down, like memory allocations (go_memstats_alloc_bytes
).
summary
is similar to histogram
, but don't require pre-defined buckets, that might be useful if you want to use quantiles, but not sure in ranges you need to use.
histogram
measures a frequency of an event that falls into specific predefined buckets, like request counts by response status codes (request_duration_seconds_bucket{code="200",entrypoint="traefik"} 38821
)
Further Reading:
Labels are key-value pairs stored in a metric. There is a syntax to add labels:
http_requests_total{path="/login"}
http_requests_total{path="/logout"}
http_requests_total{path="/adduser"}
http_requests_total{path="/comment"}
http_requests_total{path="/view"}
Values specific per label might be queried via PromQL
specifically as well as the total value of http_requests_total
.
Label names starting with __
(such as __name__
) are reserved for internal usage.
Labels in prometheus are divided into 2 purposes:
Instrumentation labels come from an instrumented application (type of HTTP request, etc).
Target labels identify a specific monitoring target, and related more to an architecture and infrastructure. For example, different teams may have different vision of what a "team", "region", or "service" is, so instrumented app shouldn't expose this labels itself rather than leave it to relabeling feature. Labels most likely used as target labels: env
, cluster
, service
, team
, zone
, and region
.
In case you have an enum label values (for a service state, etc), you can create child-metrics for each state and use boolean values on then (0
and 1
to be exact) like below:
# HELP gauge The current state of resources.
# TYPE gauge resource_state
resource_state{resource_state="STARTING",resource="app"} 0
resource_state{resource_state="RUNNING",resource="app"} 1
resource_state{resource_state="STOPPING",resource="app"} 0
resource_state{resource_state="TERMINATED",resource="app"} 0
Info metrics are specific metric category used to expose target details, such as build numbers, kernel version, etc. There is no sense to use target labels for this purpose, the convention is to use a gauge with a value of 1
for such ones.
Example:
# HELP python_info Python platform information
# TYPE python_info gauge
python_info{implementation="CPython",major="3",minor="5",patchlevel="2",
version="3.5.2"} 1.0
Generation:
from prometheus_client import Gauge
version_info = {
"implementation": "CPython",
"major": "3",
"minor": "5",
"patchlevel": "2",
"version": "3.5.2",
}
INFO = Gauge("my_python_info", "Python platform information",
labelnames=version_info.keys())
INFO.labels(**version_info).set(1)
By design Prometheus is using the pull model, but in case you need to push metrics to from a short-living targets such as builds and migrations, you can deploy push-gateway to handle them.
Push gateway has a few downsides:
There is a simple snippet how to send Tokei data during the main branch build:
#!/bin/bash
POSITIONAL=()
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
--jobName)
JOB_NAME="$2"
shift
shift
;;
--pushgateway)
PUSHGATEWAY="$2"
shift
shift
;;
*)
POSITIONAL+=("$1")
shift
;;
esac
done
set -- "${POSITIONAL[@]}"
METRICS=$(tokei . -e node_modules -o json \
| jq -r 'keys[] as $k | "\($k), \(.[$k] | .code)"' \
| sed 's/,//' | awk '{print "code_"$0}' \
| grep -vi "plain text" \
)
echo "${METRICS}" | curl --data-binary @- "${PUSHGATEWAY}/metrics/job/${JOB_NAME}"```
Further Reading:
Exporter gathers metrics from an application and sends them on Prometheus request. Prometheus has planty of exporters:
Also many apps such as Traefik and Nginx Ingress export their metrics from the box.
Further Reading:
Service discovery is a feature to get information about your machines and services from a database they stored in. Prometheus supports many common service discovery mechanisms, such as Kubernetes, EC2, and Consul.
Also prometheus supports metadata mapping in its monitor targets. See: Relabeling.
scrape_configs:
- job_name: example
consul_sd_configs:
- server: 'localhost:8500'
scrape_timeout: 5s
metrics_path: /admin/metrics
params:
foo: [bar]
scheme: https
tls_config:
insecure_skip_verify: true
basic_auth:
username: brian
password: hunter2
Static configuration can be templated with Ansible
# prometheus.yml
scrape_configs:
- job_name: node
static_config:
- targets:
- host1:9100
- targets:
- host2:9100
File SD uses a list of monitoring targets from the files you provide on the local system. It's useful to integrate SD systems prometheus doesn't support from the box. Files should be json
, yaml
or yml
.
scrape_configs:
- job_name: file
file_sd_configs:
- files:
- '*.json'
Consul service discovery integration.
scrape_configs:
- job_name: consul
consul_sd_configs:
- server: 'localhost:8500'
EC2 service discovery:
scrape_configs:
- job_name: ec2
ec2_sd_configs:
- region: <region>
access_key: <access key>
secret_key: <secret key>
Prometheus have read/write API you can write hooks againts, to get its data and work with.
Further Reading:
Prometheus has it's own query-language — PromQL.
metric{label=value}[1m]
is a selector combined from 2 parts:
metric
is a metric name{label=value}
is a matcher.[1m]
converts a metric into a rangeSelectors can have many matchers and logical operators like metric == 0
which returns only case when a metric is equal to zero.
=
- equality matcher!=
- negative equality matcher=~
- regexp matcher!~
- negative regexp matcherFew notes:
code~="4.."
, consider to combine this labels into 4xx
to use with equality matcher `code="4xx"{}
, {foo=""}
, {foo!=""}
, and {foo=~".*"}
returns an error.sum (metric)
returns a sum aggregated by same labels. max (metric)
returns max value from a gauge.avg (metric)
returns average value for a metric/
- the division operator matches the timeseries with the same labels.There is a without
function in case you need to omit specific labels from an aggregation (sum without(label, label)
).
Rate operators are used against counters to show how fast it's growing up within a sliding window.
For example,rate(metric[5m])
calculates 1 sec averages with a 5m
sliding window. rate
returns a gauge, so you can apply same aggregations as you do for gauges there.
Time offsets allows to get metric value with a given offset, which can be useful to visualize difference with the previous day, week, month, and so on.
For example, metric offset 1h
returns a value an hour before the query evaluation time.
Range Vector Instant Vector Scalars Strings
Two Instant Vectors are matched by labels one-to-one when you're for example doing division on them.
There are 2 ways to specify set of labels are participating in the matching:
Usage of ignoring
operator to specify not related labels:
sum without(cpu)(rate(node_cpu_seconds_total{mode="idle"}[5m]))
/ ignoring(mode)
sum without(mode, cpu)(rate(node_cpu_seconds_total[5m]))
Usage of on
operator to consider only provided labels:
sum by(instance, job)(rate(node_cpu_seconds_total{mode="idle"}[5m]))
/ on(instance, job)
sum by(instance, job)(rate(node_cpu_seconds_total[5m]))
When you do arithmetic operations over instant vectors, the left-side value is being returned.
An expression below returns all process_open_fds
for all instances whose open file descriptors are more then halfway to the maximum:
process_open_fds
>
(process_max_fds * .5)
Alerts can be configured by same promql you're using for graphing. If you can graph something, you can alert it.
Alertmanager recieves alerts from Prometheus servers and routes them to a proper reciever, such as email, slack or pagerduty. Or you can configure it to silence this alerts.
# prometheus.yml
alerting:
alertmanagers:
- static_configs:
- targets:
- localhost:9093
# rules.yml
groups:
- name: example
rules:
- alert: InstanceDown
expr: up == 0
for: 1m
Further Reading:
It's reasonable to have some critical metrics covered by tests. Not all of them (that's probably a fiction), but you might consider to cover transaction logs and some important application logs.
Further Reading:
There is a list of links and books used in this conspect:
Further Reading: