探索PromQL
PromQL基础
Prometheus会将所有采集到的样本数据以时间序列(time-series)的方式保存在内存数据库中,并且定时保存到硬盘上,每个数据称为一个样本。时间序列(time-series)是按照时间戳和值的序列顺序存放的,我们称之为向量(vector). 每条时间序列(time-series)通过指标名称(metrics name)和一组标签集(labelset)命名。如下所示,可以将时间序列(time-series)理解为将多个时间序列(time-series)放在同一个坐标系内(以时间为横轴,以序列为纵轴),将形成一个由数据点组成的矩阵;
可以把以下图想象成Promethus存储
- 每一行代表一个时间序列(time-series)我们也称为一个向量(Vector)
- 每一列代表时间的流逝时间点
在时间序列(time-series)中的每一个点称为一个样本(sample),样本由以下三部分组成:
- 指标名(metric name):指标名(metric name)和描述当前样本特征的标签(labels);
- 时间戳(timestamp):一个精确到毫秒的时间戳;
- 样本值(value): 一个float64的浮点型数据表示当前样本的值。
Prometheus数据模型
Prometheus中,每个时间序列都由指标名称(Metric Name)和标签(Label)来唯一标识(注意:指标名称相同,但标签不同的组合分别代表着不同的时间序列。不同的指标名称自然更是代表着不同的时间序列。),格式为:
1 |
|
指标名称(metric name)可以反映被监控样本的含义(比如,http_request_total
- 表示当前系统接收到的HTTP请求总量)支持使用字母、数字、下划线和冒号,且必须能匹配RE2规范的正则表达式;
标签(label)键值型数据,附加在指标名称之后,反映了当前样本的特征维度。通过这些维度Prometheus可以对样本数据进行过滤,聚合等。例如,http_requests_total{method=GET}和http_requests_total{method=POST}代表着两个不同的时间序列;标签名称可使用字母、数字和下划线,且必须能匹配RE2规范的正则表达式; 以__
为前缀的名称为Prometheus系统预留使用;
在Prometheus的底层实现中指标名称实际上是以__name__=<metric name>
的形式保存在数据库中的,因此以下两种方式均表示的同一条时间序列(time-series):
1 |
|
1 |
|
初识PromQL
Prometheus通过指标名称(metrics name)以及对应的一组标签(labels)唯一定义一条时间序列。
- 指标名称反映了监控样本的基本标识
- 而label则在这个基本特征上为采集到的数据提供了多种特征维度。
基于PromQL表达式,用户可以针对指定的特征(指标名)及其细分的纬度(标签)进行过滤、聚合、 统计等运算从而产生期望的计算结果。
PromQL的数据类型
PromQL的表达式中支持4种数据类型
- 即时向量(Instant Vector):特定或全部的时间序列集合上,具有相同时间戳的一组样本值称为即时向量;
- 范围向量(Range Vector):特定或全部的时间序列集合上,在指定的同一时间范围内的 所有样本值;
- 标量(Scalar):一个浮点型的数据值;
- 字符串(String):支持使用单引号、双引号或反引号进行引用,但反引号中不会对转义 字符进行转义;
时间序列选择器
用户可使用向量表达式来查询某个指标名称下的所有时间序列或部分时间序列(根据标签判断不同的标签标示不同的时间序列)的即时(当前)样本值或过去某个时间范围内的样本值,前者称为即时向量选择器,后者称为范围向量选择器
- 即时向量选择器(Instant Vector Selectors):返回值中只会包含该时间序列中的最新的一个样本值,这样的返回结果我们称之为即时向量(瞬时向量)
- 范围向量选择器(Range Vector Selectors) :返回值中只会包含该时间序列中的最新的一组样本值;范围选择器
[]
进行定义。
即时向量
当我们直接使用监控指标名称查询时,可以查询该指标下的所有时间序列。如:
1 |
|
等同于:
1 |
|
该表达式会返回指标名称为http_requests_total的所有时间序列:(以下是两个不同的时间序列,因为他们的标签不同)
1 |
|
直接通过类似于PromQL表达式http_requests_total
查询时间序列时,返回值中只会包含该时间序列中的最新的一个样本值,这样的返回结果我们称之为瞬时向量。而相应的这样的表达式称之为瞬时向量表达式。
范围向量
如果我们想过去一段时间范围内的样本数据时,我们则需要使用范围向量表达式。范围向量表达式和瞬时向量表达式之间的差异在于在范围向量表达式中我们需要定义时间选择的范围,时间范围通过时间范围选择器 []
进行定义。例如,通过以下表达式可以选择最近5分钟内的所有样本数据:
1 |
|
该表达式将会返回查询到的时间序列中最近5分钟的所有样本数据:
1 |
|
通过范围向量表达式查询到的结果我们称为范围向量。
除了使用m表示分钟以外,PromQL的时间范围选择器支持其它时间单位:
1 |
|
标量(Scalar):一个浮点型的数字值
标量只有一个数字,没有时序。
例如:
1 |
|
需要注意的是,当使用表达式count(http_requests_total)
,返回的数据类型,依然是瞬时向量。用户可以通过内置函数scalar()
将单个瞬时向量转换为标量。
字符串(String):一个简单的字符串值
直接使用字符串,作为PromQL表达式,则会直接返回字符串。
1 |
|
注意
需要将返回值绘制成图形时,仅支持即时向量类型和标量类型的数据;由于范围向量选择器的返回的是范围向量型数据,它不能用于表达式浏览器中图形绘制功能,否则,表达式浏览器会返回Error executing query: invalid expression type "range vector" for range query, must be Scalar or instant Vector
一类的错误,但事实上,范围向量选择几乎总是结合速率类的函数 rate
或 irate
一同使用。
Prometheus 中有四种指标类型其中一种 Counters(计数型),其值只增不减,除非监控系统发生了重置。你可以使用 counter 指标类型来表示服务的请求数、已完成的任务数、错误发生的次数等。需要使用counter绘图或做任何事之前,通常要借助rate(), irate()或 increase() 函数来查看它的rate(速率)。以下先介绍两个函数:
rate()
此函数计算整个采样周期内每秒的增长率。
例如:rate(http_requests_total[5m]) 得出的是HTTP在5分钟窗口内,平均每秒的请求率。作为最常见的函数,它以可预测的每秒输出单位产生平滑的rate。
手动计算rate
1 |
|
计算公式
1 |
|
先用10.0.23.29
这个 instance 算
1 |
|
谷歌搜的在线计算器算的(比windows的calc精度高一些),由于是float64,所以精度丢失了一些。结果一样。再算下另一个 instance
1 |
|
irate()
即 “瞬时rate”,此函数和rate()一样,计算每秒的增长率,但只对规定采样周期内的最后两个样本进行计算,而忽略前面所有样本。
例如:irate(http_requests_total[5m]) 选取规定5分钟窗口内的最后两个样本,并计算两者之间每秒的增长率。如果想让一个放大的图形显示出对rate变化的快速响应,那这个函数就很有用,它呈现的结果会比rate()的有更多的峰值。
手动计算irate
流程图
1 |
|
计算公式:选取时间范围内最后两个点:end1,end2
1 |
|
计算结果如下
1 |
|
increase(v range-vector)
此函数和 rate() 完全一样,只是它没有将最终单位转换为 “每秒”(1/s)。每个规定的采样周期就是它的最终单位。
例如:increase(http_requests_total[5m]) 得出的是5分钟的采样周期内处理完成的HTTP请求的总增长量(单位1/5m)。因此increase(foo[5m])/ (5 * 60) 等同于rate(foo[5m])。
1 |
|
<metric>
:要计算增长量的指标名称。<labels>
:指标的标签。可选项,如果指标有标签,可以通过该参数指定标签过滤条件。<duration>
:时间段,用于指定增长量的计算范围。
使用increase函数时,需要注意以下几点:
- 在使用
increase
函数之前,需要先确保指标具有单调递增的属性。如果指标值出现回退,使用该函数计算的结果将不准确。 <duration>
参数的单位可以是秒(s)、分钟(m)、小时(h)或天(d)。例如,increase(my_metric[5m])
表示计算my_metric
指标在过去5分钟内的增长量。
手动计算increase
1 |
|
计算公式
1 |
|
计算结果
1 |
|
上面提到increase(foo[5m])/ (5 * 60) 等同于rate(foo[5m])
1 |
|
即时向量选择器
即时向量选择器由两部分组成;
- 指标名称:用于限定特定指标下的时间序列,即负责过滤指标;可选;
- 匹配器(Matcher):或称为标签选择器,用于过滤时间序列上的标签;定义在{}之中; 可选;
显然,定义即时向量选择器时,以上两个部分应该至少给出一个;于是,这将存在以下三种组合;
- 仅给定指标名称,或在标签名称上使用了空值的匹配器:返回给定的指标下的所有时间 序列各自的即时样本;
- 例如,
http_requests_total
和http_requests_total{}
的功能相同,都是用于返回http_requests_total
指标下 各时间序列的即时样本;
- 例如,
- 仅给定匹配器:返回所有符合给定的匹配器的所有时间序列上的即时样本;
- 注意:这些时间序列可能会有着不同的指标名称;
- 例如,
{team=~".*",project="docker-service"}
- 指标名称和匹配器的组合:返回给定的指标下的,且符合给定的标签过滤器的所有时间序列上的即时样本;
- 例如,
http_requests_total{method="get"}
- 例如,
合法的PromQL表达式
1 |
|
1 |
|
标签匹配器也可以通过与内部__name__
标签匹配来应用于指标名称 。例如,表达式http_requests_total
等效于 {__name__="http_requests_total"}
。除了 =、!=、=~、!~
也可以使用以下表达式选择名称以job开头的所有指标:
1 |
|
指标名称不能是关键字bool, on, ignored, group_left和group_right中的一个。以下表达式是非法的:
1 |
|
匹配器(Matcher)
匹配器用于定义标签过滤条件,目前支持如下4种匹配操作符;
1 |
|
注意事项
- 匹配到空标签值的匹配器时,所有未定义该标签的时间序列同样符合条件;
- 例如,我们并没有给某个时间序列设置
env
标签但是我们使用http_requests_total{env=""}
去匹配,则表示未使用该标签(env)的时间序列也符合条件,比如时间序列http_requests_total{method="get"}
也会被匹配到;
- 例如,我们并没有给某个时间序列设置
- 正则表达式将执行完全锚定机制,它需要匹配指定的标签的整个值;
- 例如,时间序列
prom_http{job=node_exporter}
使用表达式prom_http{job=~"node"}
无法匹配,必须使用{job=~node.*}
才能匹配到。
- 例如,时间序列
- 向量选择器至少要包含一个指标名称,或者至少有一个不会匹配到空字符串的匹配器;
- 例如,
{job=~""}
为非法的选择器;
- 例如,
- 使用
__name__
做为标签名称,还能够对指标名称进行过滤;- 例如,
{__name__=~"http_requests_.*"}
能够匹配所有以http_requests_
为前缀的所有指标
- 例如,
范围向量选择器
同即时向量选择器的唯一不同之处在于,范围向量选择器需要在表达式后紧跟一个方括号[ ]
来表达需在时间时序上返回的样本所处的时间范围;
- 时间范围:以当前时间为基准时间点,指向过去一个特定的时间长度;例如[5m]便是指过去5分钟之内;
时间格式:一个整数后紧跟一个时间单位,例如“5m”中的“m”即是时间单位 ;
- 可用的时间单位有ms(毫秒)、s(秒)、m(分钟)、h(小时)、d(天)、w(周) 和y(年);
- 必须使用整数时间,且能够将多个不同级别的单位进行串联组合,以时间单位由大到小为顺序,例如1h30m,但不能使用1.5h;
需要注意的是,范围向量选择器返回的是一定时间范围内的数据样本,虽然不同时间序列的数据抓取时间点相同,但它们的时间戳并不会严格对齐;
- 多个Target上的数据抓取需要分散在抓取时间点前后一定的时间范围内(不会同时对所有target进行数据抓取,会分散开抓取数据),以均衡 Prometheus Server的负载;
- 因而,Prometheus在趋势上准确,但并非绝对精准;
偏移量修改器
- 默认情况下,即时向量选择器和范围向量选择器都以当前时间为基准时间点,而偏移量修改器能够修改该基准;
- 偏移量修改器的使用方法是紧跟在选择器表达式之后使用
offset
关键字指定http_requests_total offset 5m
,表示获取以http_requests_total为指标名称的所有时间序列在过去5分钟之时的即时样本;http_requests_total[5m] offset 30m
,表示获取距此刻30分钟之前的5分钟之内的所有样本;
PromQL的指标类型
PromQL有四个指标类型,它们主要由Prometheus的客户端库使用
- Counter:计数器,单调递增,除非重置(例如服务器或进程重启);
- Gauge:仪表盘,可增可减的数据;
- Histogram:直方图,将时间范围内的数据划分成不同的时间段,并各自评估其样本个数及样本值之和,因而可计算出分位数;
- 可用于分析因异常值而引起的平均值过大的问题;
- 分位数计算要使用专用的histogram_quantile函数;
- Summary:类似于Histogram,但客户端会直接计算并上报分位数;
Prometheus Server并不使用类型信息,而是将所有数据展平为时间序列
Counter和Gauge
通常,Counter的总数并没有直接作用,而是需要借助于rate、topk、increase和irate 等函数来生成样本数据的变化状况(增长率);
- rate(http_requests_total[2h]),获取2小内,该指标下各时间序列上的http总请求数的增长速 率;
- topk(3, http_requests_total),获取该指标下http请求总数排名前3的时间序列;
- irate(http_requests_total[2h]),高灵敏度函数,用于计算指标的瞬时速率;
- 基于样本范围内的最后两个样本进行计算,相较于rate函数来说,irate更适用于短期时间范围内的 变化速率分析;
Gauge用于存储其值可增可减的指标的样本数据,常用于进行求和、取平均值、最小值、最大值等聚合计算;也会经常结合PromQL的predict_linear和delta函数使用;
- predict_linear(v range-vector, t, scalar)函数可以预测时间序列v在t秒后的值,它通过线性回归 的方式来预测样本数据的Gauge变化趋势;
- delta(v range-vector)函数计算范围向量中每个时间序列元素的第一个值与最后一个值之差,从而展示不同时间点上的样本值的差值;
- delta(cpu_temp_celsius{host=”web01.magedu.com”}[2h]),返回该服务器上的CPU温度与2小时之前的差异;
Histogram和Summary
对于Prometheus来说,Histogram会在一段时间范围内对数据进行采样(通常是请 求持续时长或响应大小等),并将其计入可配置的bucket(存储桶)中
Histogram事先将特定测度可能的取值范围分隔为多个bucket,并通过对落入bucket内的观测值进行计数以及求和操作
与常规方式略有不同的是,Prometheus取值间隔的划分采用的是累积(Cumulative)区间间隔机制,即每个bucket中的样本均包含了其前面所有bucket中的样本,因而也称为累积直方图
- 可降低Histogram的维护成本
- 支持粗略计算样本值的分位数
- 单独提供了_sum和_count指标,从而支持计算平均值
根据上图假设我们想监控某个应用在一段时间内的响应时间,最后监控到的样本的响应时间范围为 0s~10s。现在我们将样本的值域划分为不同的区间,即不同的 bucket,每个 bucket 的宽度是 0.2s。那么第一个 bucket 表示响应时间小于等于 0.2s 的请求数量,第二个 bucket 表示响应时间小于等于 0.4s 的请求数量,以此类推。也就是说,每一个 bucket 的样本包含了之前所有 bucket 的样本,所以叫累积直方图。
histogram例子
如上表,设置bucket=[1,5,10],当实际采样数据如是采样点所示, Observe表示采样点落在该bucket中的数量,即落在[-,1]的样本数为2,即落在[1,5]的样本数为3,即落在[5,10]的杨样本数为1,write是得到的最终结果(histogram的最终结果bucket计数是向下包含的):
1 |
|
histogram并不会保存数据采样点值,每个bucket只有个记录样本数的counter(float64),即histogram存储的是区间的样本数统计值,因此客户端性能开销相比 Counter 和 Gauge 而言没有明显改变,适合高并发的数据收集。
Histogram 类型的样本会提供三种指标(假设指标名称为
- 样本的值分布在 bucket 中的数量,命名为
<basename>_bucket{le="<上边界>"}
。解释得更通俗易懂一点,这个值表示指标值小于等于上边界的所有样本数量。最大的上边界名称为<basename>_bucket{le="+Inf"}
表示无穷大;
1 |
|
- 所有样本值的大小总和,命名为
<basename>_sum
。
1 |
|
- 总的观测次数,命名为
<basename>_count
。值和<basename>_bucket{le="+Inf"}
相同。
1 |
|
累积间隔机制生成的样本数据需要额外使用内置的histogram_quantile()
函数即可根据Histogram指标来计算相应的分位数(quantile),即某个bucket的样本数在所有样本数中占据的比例。分位数可能不太好理解,我举个例子,假设你要计算样本的 9 分位数(quantile=0.9),即表示 90% 的样本的值。
- histogram_quantile()函数在计算分位数时会假定每个区间内的样本满足线性分布状态,因而它的结果仅是一个预估值,并不完全准确;
- 预估的准确度取决于bucket区间划分的粒度;粒度越大,准确度越低;
histogram计算方式
- 其中
bucket
代表事先定义好的bucket
upperBound
代表这个bucket
的上限值count
代表这个小于等于这个upperBound
的个数/次数workqueue_work_duration_seconds_bucket{name="crd_openapi_controller",le="10"}
65246- 所以上述表达式含义为
workqueue_work_duration_seconds
小于10秒的有65246个
我们现在有这些数据,然后求75分位值
1 |
|
- 其计算逻辑为:根据记录总数和分位值求目标落在第几个bucket段b
- 根据b得到起始bucket大小bucketStart,终止bucket大小bucketStart ,本bucket宽度 ,本bucket记录数。
- 根据本段记录数和分位值算出目标分位数在本bucket排行rank
- 最终的计算方式为分位值=起始bucket大小+(本bucket宽度)*(目标分位数在本bucket排行/本bucket记录数)
- 换成本例中: q75=0.4+(0.8-0.4)*(70259.75/100141) = 0.6806432929569308
1 |
|
1、根据记录总数和分位值求目标落在第几个bucket段
519593 * 0.75 = 389,694.75 (根据这个值我们可以判断他是在0.4和0.8之间的一个值,也就是落到了第四个bucket中)
2、根据本段记录数和分位值算出目标分位数在本bucket排行rank
389,694.75 - 319435 = 70,259.75 (根据这个值可以算出75%他在第四个bucket中属于哪个位置)
3、最终的计算公式
0.4+(0.8-0.4)*(70259.75/100141) = 0.6806432929569308 (计算出来的最终结果就是75%的请求小于0.6806432929569308s)
1 |
|
Summary
与 Histogram 类型类似,用于表示一段时间内的数据采样结果(通常是请求持续时间或响应大小等),但它直接存储了分位数(通过客户端计算,然后展示出来),而不是通过区间来计算。其中φ
是分位点,其取值范围是(0 ≤φ≤ 1)
;计数器类型指标;如下是几种典型的常用分位点;
- 0、0.25、0.5、0.75和1几个分位点;
- 0.5、0.9和0.99几个分位点;
- 0.01、0.05、0.5、0.9和0.99几个分位点;
Summary 类型的样本也会提供三种指标(假设指标名称为 ):
- 样本值的分位数分布情况,命名为
<basename>{quantile="<φ>"}
。
1 |
|
- 所有样本值的大小总和,命名为
<basename>_sum
。
1 |
|
- 样本总数,命名为
<basename>_count
。
1 |
|
根据前面分别对Summary和Histogram的描述,很显然Summary和Histogram计算quantile有很大的差别。另外,它们之间一个重要的区别在于,Summary对quantile的计算是在client端完成的,而Histogram对quantile的计算是在server端完成的
如果需要了解某个时间段内的请求的响应时间,则通常使用平均响应时间,但这样做无法体现数据的长尾效应。如,一个HTTP服务器的正常响应时间是30ms,但是有很少几次请求耗时3s,通过平均响应时间很难甄别长尾效应。这时候可以通过Histogram或者Summary展现。Histogram和Summary这两种指标类型在本质上是可以相互转化的。
在Summary中使用到了分位数,φ
代表分位数,0≤φ≤1
。例如,0.9分为数代表第90%位置上的数,0.95分位代表95%位置上的数。如Promethues_tsdb_wal_fsync_duration_seconds 指标为Promthues Server中WAL写入磁盘的同步时间:
1 |
|
从而得出,当前Prometheus Server进行wal_fsync操作的总次数为216次,总共耗时2.888716127000002s。有50%的同步时间低于0.012352463s,有90%的同步时间低于0.014458005s。
现在可以总结一下 Histogram 与 Summary 的异同:
- 它们都包含了
<basename>_sum
和<basename>_count
指标 - Histogram 需要通过
<basename>_bucket
来计算分位数,而 Summary 则直接存储了分位数的值。 - Summary不支持sum或avg一类的聚合运算,而且其分位数由客户端计算并生成, Server端无法获取客户端未定义的分位数,而Histogram可通过PromQL任意定义,有着较好的灵活性;