Prometheus的四大指标类型
Prometheus有4大指标类型(Metrics Type),分别是Counter(计数器)、Gauge(仪表盘)、Histogram(直方图)和Summary(摘要)。
这是在Prometheus客户端(目前主要有Go、Java、Python、Ruby等语言版本)中提供的4种核心指标类型,但是Prometheus的服务端并不区分指标类型,而是简单地把这些指标统一视为无类型的时间序列。
注意:
<font color=red>上面这句话应该这么理解,四个指标类型,实际上就是客户端采集数据的四个维度,采集这四个维度的指标数据,但是最终汇总到服务端那里,则是对这四个维度无感的,只是简单的作为时间序列存储起来。</font>
Counter
计数器表示一种单调递增的指标,除非发生重置的情况下下只增不减,其样本值应该是不断增大的。例如,可以使用Counter类型的指标来表示服务的请求数、已完成的任务数、错误发生的次数等。
但是,计数器计算的总数对用户来说大多没有什么用,大家千万不要将计数器类型应用于样本数据非单调递增的指标上,比如当前运行的进程数量、当前登录的用户数量等应该使用仪表盘类型。
为了能够更直观地表示样本数据的变化情况,往往需要计算样本的增长速率,这时候通常使用PromQL的rate、topk、increase和irate等函数,如下所示:
rate(http_requests_total[5m]) // 通过rate()函数获取HTTP请求量的增长速率
topk(10, http_requests_total) // 查询当前系统中访问量排在前10的HTTP地址
如上所示,速率的输出rate(v range-vector)也应该用仪表盘来承接结果。
在上面的案例中,如果有一个标签是Device,那么在统计每台机器每秒接受的HTTP请求数时,可以用如下的例子进行操作。
sum without(device)(rate(http_requests_total[5m]))
补充
PromQL要先执行rate()再执行sum(),不能执行完sum()再执行rate()。
这背后与rate()的实现方式有关,rate()在设计上假定对应的指标是一个计数器,也就是只有<font color=red>incr(增加)和reset(归零)</font>两种行为。而执行了sum()或其他聚合操作之后,得到的就不再是一个计数器了。举个例子,比如sum()的计算对象中有一个归零了,那整体的和会下降,而不是归零,这会影响rate()中判断reset(归零)的逻辑,从而导致错误的结果。
increase函数
increase(v range-vector)函数传递的参数是一个区间向量,increase函数获取区间向量中的第一个和最后一个样本并返回其增长量。下面的例子可以查询Counter类型指标的增长速率,可以获取http_requests_total在最近5分钟内的平均样本,其中300代表300秒。
increase(http_requests_total[5m]) / 300
rate和increase函数的局限性
rate和increase函数计算的增长速率容易陷入<font color=red>长尾效应中</font>。比如在某一个由于访问量或者其他问题导致CPU占用100%的情况中,通过计算在时间窗口内的平均增长速率是无法反映出该问题的。
为什么监控和性能测试中,我们更关注p95/p99位?就是因为长尾效应。由于个别请求的响应时间需要1秒或者更久,<font color=red>传统的响应时间的平均值就体现不出响应时间中的尖刺了</font>,去尖刺也是数据采集中一个很重要的工序,这就是所谓的长尾效应。p95/p99就是长尾效应的分割线,如表示99%的请求在XXX范围内,或者是1%的请求在XXX范围之外。99%是一个范围,意思是99%的请求在某一延迟内,剩下的1%就在延迟之外了。只是正推与逆推而已,是一种概念的两种不同描述。
irate(v range-vector)是PromQL针对长尾效应专门提供的灵敏度更高的函数。irate同样用于计算区间向量的增长速率,但是其反映出的是瞬时增长速率。irate函数是通过区间向量中最后两个样本数据来计算区间向量的增长速率的。这种方式可以避免在时间窗口范围内的“长尾问题”,并且体现出更好的灵敏度。通过irate函数绘制的图标能够更好地反映样本数据的瞬时变化状态。irate的调用命令如下所示。
irate(http_requests_total[5m])
irate函数相比于rate函数提供了更高的灵敏度,不过分析长期趋势时或者在告警规则中,irate的这种灵敏度反而容易造成干扰。因此,在长期趋势分析或者告警中更推荐使用rate函数。
Gauge
仪表盘类型代表一种<font color=red>样本数据可以任意变化的指标,即可增可减</font>。它可以理解为状态的快照,Gauge通常用于表示温度或者内存使用率这种指标数据,也可以表示能随时增加或减少的“总数”,例如当前并发请求的数量node_memory_MemFree(主机当前空闲的内容大小)、node_memory_MemAvailable(可用内存大小)等。在使用Gauge时,用户往往希望使用它们<font color=red>求和、取平均值、最小值、最大值</font>等。
以Prometheus经典的Node Exporter的指标node_filesystem_size_bytes为例,它可以报告从node_filesystem_size_bytes采集来的文件系统大小,包含device、fstype和mountpoint等标签。如果想要对每一台机器上的总文件系统大小求和(sum),可以使用如下PromQL语句。
sum without(device, fstype, mountpoint)(node_filesystem_size_bytes)
without可以让sum指令根据相同的标签进行求和,但是忽略without涵盖的标签。如果在实际工作中需要忽略更多标签,可以根据实际情况在without里传递更多指标。
补充:
node_filesystem_size_bytes指标查询
device, fstype, mountpoint都是他的标签。
sum without(device, fstype, mountpoint)(node_filesystem_size_bytes)查询
如果要根据Node Exporter的指标node_filesystem_size_bytes计算每台机器上最大的文件安装系统大小,只需要将上述案例中的sum函数改为max函数,如下所示。
max without(device, fstype, mountpoint)(node_filesystem_size_bytes)
除了求和、求最大值等,利用Gauge的函数求最小值和平均值等原理是类似的。除了基本的操作外,Gauge经常结合PromQL的predict_linear和delta函数使用。
predict_linear(v range-vector,t scalar)函数可以预测时间序列v在t秒后的值,就是使用线性回归的方式,预测样本数据的Gauge变化趋势。例如,基于2小时的样本数据,预测未来24小时内磁盘是否会满,如下所示:
predict_linear(node_filesystem_free[2h],24*3600)<0
PromQL还有一个内置函数delta(),它可以获取样本在一段时间内的变化情况,也通常作用于Gauge。例如,计算磁盘空间在2小时内的差异,如下所示。
delta(node_filesystem_free_bytes{}[2h])
Histogram
Histogram是一个对数据分布情况的图形表示,由一系列高度不等的长条图(bar)或线段表示,用于展示单个测度得知的分布。
它一般用横轴表示某个指标维度的数据取值区间,用纵轴表示样本统计的频率或频数,从而能够以二维图的形式展现数值的分布状况
为了构建Histogram,首先需要将值的范围进行分段,即将所有值的整个可用范围分成一系列连续、相邻(相邻处可以是等同值)但不重叠的间隔,而后统计每个间隔中有多少值。
从统计学的角度看,分位数不能被聚合,也不能进行算术运算;
[图片上传失败...(image-3e55f2-1622153155462)]
上边界、样本值总和、样本总数
例子
prometheus_http_request_duration_seconds_bucket{handler="/targets",instance="192.168.16.134:9090",job="prometheus"}
prometheus_http_request_duration_seconds_count{handler="/targets",instance="192.168.16.134:9090",job="prometheus"}
prometheus_http_request_duration_seconds_sum{handler="/targets",instance="192.168.16.134:9090",job="prometheus"}
这三个查询一起看
上边界
- 样本的值分布在Bucket中的数量,命名为<basename>_bucket{le="<上边界>"}。这个值表示指标值小于等于上边界的所有样本数量。上述案例中的prometheus_http_request_duration_seconds_bucket{handler="/targets",instance="192.168.16.134:9090",job="prometheus",le="0.1"}12 就代表在总共的11次请求中,HTTP请求响应时间≤0.1s的请求一共是11次。
样本值总和
所有样本值的总和,命名为<basename>_sum。
prometheus_http_request_duration_seconds_sum{handler="/targets",instance="192.168.16.134:9090",job="prometheus"}0.405075955 表示12 次http请求的总响应时间是0.405075955
样本总数
命名为<basename>_count,其值和<basename>_bucket{le="+Inf"}相同(所有)。
prometheus_http_request_duration_seconds_count{handler="/targets",instance="192.168.16.134:9090",job="prometheus"}12 表示总共发生了12次请求
sum函数和count函数相除,可以得到一些平均值,比如Prometheus一天内的平均压缩时间,可由查询结果除以instance标签数量得到,如下所示。
sum without(instance)(rate(prometheus_tsdb_compaction_duration_sum[1d]))
/
sum without(instance)(rate(prometheus_tsdb_compaction_duration_count[1d]))
除了Prometheus内置的压缩时间,prometheus_local_storage_series_chunks_persisted表示Prometheus中每个时序需要存储的chunk数量,也可以用于计算待持久化的数据的分位数。
Histogram可以用于观察样本数据的分布情况。Histogram的分位数计算需要通过histogram_quantile(φfloat,b instant-vector)函数进行计算,但是histogram_quantile计算所得并非精确值。其中,φ(0<φ<1)表示需要计算的分位数(这个值主要是通过prometheus_http_request_duration_seconds_bucket和prometheus_http_request_duration_seconds_sum两个指标得到的,是一个近似值)。
例子如下。
histogram_quantile(0.1, prometheus_http_request_duration_seconds_bucket)
Summary
与Histogram类型类似,摘要用于表示一段时间内的数据采样的结果(通常是请求持续时间或响应大小等),但它直接存储了分位数(通过客户端计算,然后展示出来),而非通过区间来计算(Histogram的分位数需要通过histogram_quantile(φfloat,b instant-vector)函数计算得到)。因此,对于分位数的计算,Summary在通过PromQL进行查询时有更好的性能表现,而Histogram则会消耗更多的资源。反之,对于客户端而言,Histogram消耗的资源更少。在选择这两种方式时,用户应该根据自己的实际场景选择。
Histogram是在服务端计算的,Summary是在客户端计算的。
安装并启动Prometheus后,在访问http://localhost:9090/metrics时可以看到Prometheus自带的一些Summary信息,这些信息和Histogram一样在注释中(#HELP和#TYPE)也会显示,如下所示。
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 1.1666e-05
go_gc_duration_seconds{quantile="0.25"} 2.6265e-05
go_gc_duration_seconds{quantile="0.5"} 4.8366e-05
go_gc_duration_seconds{quantile="0.75"} 7.8298e-05
go_gc_duration_seconds{quantile="1"} 0.000280123
go_gc_duration_seconds_sum 0.193642882
go_gc_duration_seconds_count 1907
在上述例子中,可以看到基于Go语言编写的Prometheus的gc总次数是1907,耗时0.193642882s,其中中位数(quantile=0.5)计算的耗时为4.8366e-05s,代表1907次中50%的次数是小于4.8366e-05s的。
Summary类型的样本也会提供3种指标,假设指标名称为<basename>。
样本值的分位数分布情况,命名为<basename>{quantile="<φ>"},属于计数器类型。
所有样本值的大小总和,命名为<basenamesum,属于计数器类型。
样本总数,命名为<basename>_count,属于计数器类型。
Summary和Histogram的异同
它们都包含了<basename>_sum和<basename>_count指标。
Histogram需要通过<basename_bucket来计算分位数,而Summary则直接存储了分位数的值。
如果需要汇总或者了解要观察的值的范围和分布,建议使用Histogram;如果并不在乎要观察的值的范围和分布,仅需要精确的quantile值,那么建议使用Summary。
Summary的强大之处就是可以利用除法去计算时间的平均值。如果要从Histogram和Summary中计算最近5分钟内的平均请求持续时间http_request_duration_seconds,可以用如下表达式进行。
rate(http_request_duration_seconds_sum[5m])/rate(http_request_duration_seconds_
count[5m])
count本质上是一个计数器,sum通常情况下也会像计数器那样工作。但是<font color=red>Summary和Histogram可能观察到负值,比如温度(-20℃),这种情况下会导致观察的总量下降,无法再使用rate函数</font>。
比如下面的例子就可以计算过去5分钟内每次响应中返回的平均字节数。
sum without(handler)(rate(http_response_size_bytes_sum[5m]))
/
sum without(handler)(rate(http_response_size_bytes_count[5m]))
关于这个例子,我们需要注意几点。
·因为http_response_size_bytes_count和http_response_size_bytes_sum是计数器类型,所以必须在计算前先使用rate等函数。
·因为Prometheus的API会有很多handler,所以可以使用without过滤掉handler的返回值。
·PromQL要先执行rate()再执行sum(),不能先执行sum()再执行rate()。
·在统计学上,尤其是计算平均值时,要先进行sum等求和运算再做除法。对一个平均值再求平均是不正确的,如下所示。
// 合法
sum without(instance)(
sum without(handler)(rate(http_response_size_bytes_sum[5m]))
)
/
sum without(instance)(
sum without(handler)(rate(http_response_size_bytes_count[5m]))
)
// 非法
avg without(instance)(
sum without(handler)(rate(http_response_size_bytes_sum[5m]))
/
sum without(handler)(rate(http_response_size_bytes_count[5m]))
)
// 非法
avg(http_request_duration_seconds{quantile="0.95"})
count的例子
案例一:计算所有的实例CPU核心数。
count by (instance) ( count by (instance,cpu) (node_cpu_seconds_total{mode=
"system"}) )
案例二:计算单个实例192.168.1.1的CPU核心数。
count by (instance) ( count by (instance,cpu) (node_cpu_seconds_total{mode="system",
instance="192.168.1.1"})