使用PromQL 可以方便的对监控指标数据进行统计和分析,这里面会涉及到常用的运算符、内置函数,让我们方便对数据进行一些高级处理。

一、Prometheus时间序列

时间序列数据按照时间顺序记录系统、设备状态变化的数据,每个数据称为一个样本。数据采集以特定的时间周期进行,因而,随着时间流逝,将这些样本数据记录下来,将生成一个离散的样本数据序列。该序列也称为向量。而将多个序列放在同一个坐标系内(以时间为横轴,以序列为纵轴),将形成一个由数据点组成的矩阵。
       当我们直接使用监控指标名称查询时,可以查询该指标下的所有时间序列。如:prometheus_http_requests_total等同于prometheus_http_requests_total{},该表达式会返回指标名称为 prometheus_http_requests_total 的所有时间序列。

PromQL_PromQL

二、PromQL简介

Prometheus提供了内置的数据查询语言PromQL(Prometheus Query Language),支持用户进行实时的数据查询及聚合操作。
       PromQL支持在表达式中使用如下数据类型,并内置提供了一组用于数据处理的函数。

  • 即时向量:特定或全部的时间序列集合上,具有相同时间戳的一组样本称为即时向量;
  • 时间范围向量:特定或全部的时间序列集合上,指定的同一时间范围内的所有样本值。
  • 标量:一个浮点型的数据值。
  • 字符串:支持使用单引号、双引号或反引号进行引用,但反引号中不会对转义字符进行转义。

三、Prometheus的数据模型

Prometheus仅用于以”键值”形式存储时序式的聚合数据,它不支持存储文本信息。其中的”键”称为指标,通常指CPU速率、内存使用率或分区空闲比例等。而同一指标可能会适配到多个目标或设备,因此它使用”标签”作为元数据,从而为指标添加更多的信息描述维度。此外,这些标签还可以作为过滤器进行指标过滤及聚合运算。
       Prometheus基于指标名称(metrics name)和附属的标签集(labelset)唯一定义一条时间序列,每个时间序列都由指标名称和标签来唯一标识,格式为:<指标名>{<label name=value>,…}。

  • 指标名称通常用于描述系统上要测定的某个特征。
  • 例如:http_requests_total表示接收到的http请求总数。
  • 支持使用数字、字母、下划线和冒号,且必须能匹配RE2规范的正则表达式。
  • 标签也属于键值型数据,附加在指标名称之上,从而让指标能够支持多维度特征(可选项)。
  • 例如:http_requests_total{method=GET}和http_requests_total{method=POST}代表两个不同的时间序列。
  • 标签名称可使用数字、字母和下划线,且必须能匹配RE2规范的正则表达式。
  • 以”__”为前缀的名称为Prometheus系统预留使用。
Eg:cpu_usage{core=”1”,ip=”192.168.131.11”}=14.04
    cpu_usage{core!~”2”}=14.04

这里cpu_usage为键,14.04为值(指标类型);core=”1”、 ip=”192.168.131.11”为标签,用于指明对应的监控对象;core!~”2”为过滤器。

四、向量表达式使用要点

表达式的返回值是即时向量、范围向量、标题或字符串4种数据类型之一,但是,有些使用场景要求表达式返回值必须满足特定条件,例如:

  • 需要将返回值绘制成图形时,仅支持即时向量类型的数据;
  • 对于像rate一类的速率函数而言,其要求使用的却必须是范围向量型的数据。

由于范围向量选择器返回的是范围向量型数据,它不能用于表达式浏览器中图形绘制功能,否则,表达式浏览器会返回”Error executing query: invalid parameter "query": invalid expression type "range vector" for range query, must be Scalar or instant Vector”这样的错误。

事实上,范围向量几乎总是结合速率类的函数一同使用。

五、范围向量选择器

下图中的prometheus_http_requests_total[1m]就是范围向量选择器,即在表达式后紧跟一个方括号来指定在时间序列上所返回的样本所处的时间范围(这里为1分钟)。

PromQL_Promettheus_02

时间格式为整数+时间单位,可用的时间单位有毫秒(ms)、秒(s)、分钟(m)、小时(h)、天(d)、周(w)、年(y)。注意必须使用整数时间,且能够将多个不同级别的单位进行串联组合,以时间单位由大到小排序,比如1h30m可以使用,却不能使用1.5h。

注意:使用范围向量选择器时,虽然不同时间序列的数据抓取时间段相同,但它们的时间戳并不会严格对齐。

六、即时向量选择器

即时向量选择器由两部分组成:

  • 指标名称:用于限定特定指标下的时间序列,即负责过滤指标,可选;
  • 匹配器:又称为标签选择器,用于过滤时间序列上的标签;定义在{}之中,可选;

显然,定义即时向量选择器时,以上二项必选其一,因此存在如下三种组合:

  • 仅指定指标名称,或在标签名称上使用了空值的匹配器:返回给定的指标下的所有时间序列各自的即时样本。例如http_request_total和http_request_total{}功能相同,都是用于返回http_request_total指标下各时间序列的即时样本。
  • 仅给定匹配器:返回所有符合给定的匹配器的所有时间序列上的即时样本。这些时间序列可能会有不同的指标名称,返回的结果较多。例如{job=”*”,method=”get”}。
  • 指标名称和匹配器的结合:返回仅在指定的指标下,且符合指定的标签过滤器的所有时间序列上的即时样本。比如http_requests_total{method=”get”}。

七、匹配器

匹配器用于定义标签过滤条件,目前支持如下4种匹配操作符

  • =:左右相等即为真。
  • !=:左右不相等即为真。
  • =~:右边的部分(标签的整个值)被相关的正则表达式匹配到即为真。
  • !~:右边的部分不被相关的正则表达式匹配到即为真。

当匹配到空标签值的选择器时,所有未定义该标签的时间序列同样符合条件。比如http_requests_total{env=””},则此指标名称上所有未使用该标签(env)的时间序列也符合条件,比如时间序列http_requests_total{method=”get”}。

另外,向量选择器至少要包含一个指标名称,或至少有一个不会匹配到空字符串的匹配器。例如{job=””}为非法的选择器。

而使用”__name__”作为标签名称,还能对指标名称进行过滤。例如,{__name__=~”http_requests_.*”}能够匹配所有以”http_requests_”为前缀的所有指标。

八、偏移量修改器

默认情况下,即时向量选择器和范围向量选择器都以当前时间为基准时间点,而偏移量修改器能修改此基准。
       偏移量修改器的使用方法是在选择器表达式之后使用”offset”关键字指定。比如:

#表示获取以http_requests_total为指标名称的所有时间序列在过去5分钟之时的即时样本
http_requests_total offset 5m
#表示获取距此刻1天时间之前的5分钟之内的所有样本。
http_requests_total[5m] offset 1d

九、Prometheus的指标类型

Prometheus使用4种方法来描述监视的指标

9.1 Counter

计数器,用于保存单调递增型数据,比如网站访问次数等;不能为负值,也不支持减少,但可重置为0。通常,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更适用于短期时间范围内的变化速率分析。

9.2 Gauge

仪表盘,用来存储有起伏特征的指标数据(例如内存空闲大小),常用于进行求和、平均值、最大/最小值等聚合计算,也会结合PromQL的predict_linear和delta函数使用。

  • predict_linear(v range-vector,t,scalar)函数可以预测时间序列v在t秒后的值,它通过线性回归的方式预测样本数据Gauge的变化趋势;
  • delta(v range-vector)函数计算范围向量中每个时间序列元素中的最后一个值与第一个值的差,从而展示不同时间点上的样本值的差值。Eg:delta(cpu_temp_celsius{host=”web1.wxd.com”}[1h]),返回该服务器上当前的CPU温度与1小时之前的差异。

像常见指标,比如:

  • node_memory_MemFree_bytes(主机当前空闲的内容大小)
  • node_memory_MemAvailable(可用内存大小)

它们就是都是Gauge类型的监控指标。

9.3 Histogram

直方图(也可理解为柱状图),能够分组分区间显示指标的信息。对于Prometheus而言,Histogram会在一段时间内对数据进行采样(通常是请求持续时长或响应大小等),并将其计入可配置的bucket(存储桶,区间)中。
       Prometheus取值采用累积区间间隔机制,即每个区间(bucket)的样本均包含其前面所有区间的样本,因此又称为累积直方图。例如,比如对于请求响应时间,总共10w个请求,小于10ms的有5w个,小于50ms的有9w个,小于100ms的有9.9w个。注意:Histogram是在服务器端执行聚合操作,即在查询过程中执行聚合。
       Histogram类型的每个指标有一个基础指标名称<basename>,它会提供多个时间序列:

  • <basename>_bucket{le=”<upper inclusive bound>”}:观测桶的上边界(upper inclusive bound),即样本统计区间,最大区间(包含所有样本)的名称为<basename>_bucket{le=”+Inf”};
  • <basename>_sum:所有样本观测值的总和;
  • <basename>_count:总的观测次数,它自身本质上是一个Counter类型的指标。

这里由于提供了_sum和_count指标,因此可支持计算平均值。

累积区间间隔机制生成的样本数据需要额外使用内置的histogram_quantile()函数即可根据Histogram指标来计算相应的分位数(quantile),即某个bucket的样本数在所有样本数中所占的比例。但其结果只是一个预估值,并不完全准确。预估的准确度取决于bucket区间的划分粒度,粒度越大,准确度越低。

9.4 Summary

摘要,和Histogram直方图类似,只是表现形式不同,是按照百分比划分追踪的结果。比如还是对于请求响应时间,Summary描述则是,总共10w个请求,50%小于10ms,90%小于50ms,99%小于100ms。注意:Summary在被监控端采集数据时就已聚合好。

十、聚合函数

10.1 sum ()

对样本值求和。

#计算http请求的总和
sum(prometheus_http_requests_total)

PromQL_PromQL_03

#通过状态码分别统计
sum by(code)(prometheus_http_requests_total)

PromQL_Promettheus_04

10.2 min()

对样本值求最小值。

10.3 max ()

对样本值求最大值。

10.4 avg ()

对样本值求平均值。

10.5 stddev ()

对样本值计算标准差,以帮助用户了解数据的波动大小。

#通过标准差来反映网络波动。rate计算某段时间的速率
stddev(rate(node_network_transmit_bytes_total[1m]))

PromQL_PromQL_05

10.6 stdvar ()

对样本值计算方差。

stddev()和stdvar()反映一组数据离散程度,用以衡量数据值偏离算术平均值的程度。标准差为方差的开平方,标准差越小,这些值偏离平均值就越少,反之亦然。

10.7 count ()

对分组内的时间序列进行数量统计。

#统计时间序列的数量
count(prometheus_http_requests_total)

PromQL_PromQL_06

10.8 count_values ()

对分组内的时间序列的样本值进行数量统计。

#计算每个value的数量
count_values("value",prometheus_http_requests_total)

PromQL_PromQL_07

10.9 bottomk ()

返回样本中最小的后k个时间序列及其值。

10.10 topk ()

返回样本中最大的前k个时间序列及其值。

#获取HTTP请求数前5位的时序样本数据
topk(5, prometheus_http_requests_total)

PromQL_PromQL_08

10.11 quantile ()

用于评估数据的分布状态 quantile(φ, express) ,其中0 ≤ φ ≤1。此函数返回分组内指定分位数的值。

#查找当前样本数据中的9分位数的值,结果为4
quantile(0.9, prometheus_http_requests_total)

PromQL_PromQL_09

十一、聚合表达式

上述聚合函数使用聚合表达式的语法如下(也可将without/by部分放到前面):

<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]
  • parameter仅用于count_values,quantile,topk和bottomk
  • without:从结果向量中删除由without子句列出的标签,而所有其他标签都保留输出
  • by:与without相反。仅适用by子句中指定的标签进行聚合,结果向量中出现但未被by子句指定的标签会被忽略

十二、二元运算符

支持两个标量间运算、即时向量和标量间的运算、两个即时向量间的运算。

  • 算术运算:+(加)、-(减)、*(乘)、/(除)、%(取模)和^(幂运算)
  • 比较运算:==(等值比较)、!=(不等)、>(大于)、<(小于)、>=(大于等于)、<=(小于等于)
  • 逻辑/集合运算:and(且)、or(或)、unless(除了)。此运算仅允许在两个即时向量间进行。

二元运算符优先级由高到低依次为:

  • ^
  • *, /, %
  • +, -
  • ==, !=, <=, <, >=, >
  • and, unless
  • or

十三、向量匹配模式

向量与向量之间进行运算操作时会基于默认的匹配规则:依次找到与左边向量元素匹配(标签完全一致)的右边向量元素进行运算,如果没找到匹配元素,则直接丢弃。

PromQL中有两种典型的匹配模式:一对一(one-to-one),多对一(many-to-one)或一对多(one-to-many)。

13.1 一对一匹配

语法如下:

<vector expr> <bin-op> ignoring(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) <vector expr>
  • ignore:定义匹配检测时要忽略的标签
  • on:定义匹配检测时只使用的标签

例如,存在以下样本:

method_code:http_errors:rate5m{method="get", code="500"} 24
method_code:http_errors:rate5m{method="get", code="404"} 30
method_code:http_errors:rate5m{method="put", code="501"} 3
method_code:http_errors:rate5m{method="post", code="500"} 6
method_code:http_errors:rate5m{method="post", code="404"} 21

method:http_requests:rate5m{method="get"} 600
method:http_requests:rate5m{method="del"} 34
method:http_requests:rate5m{method="post"} 120

使用PromrQL表达式:

method_code:http_errors:rate5m{code="500"} / ignoring(code) method:http_requests:rate5m

该表达式会返回在过去5分钟内,HTTP请求状态码为500的在所有请求中的比例。如果没有使用ignoring(code),操作符两边表达式返回的瞬时向量中将找不到任何一个标签完全相同的匹配项。

结果如下:

{method="get"} 0.04  # 24 / 600
{method="post"} 0.05 # 6 / 120

同时由于method为put和del的样本找不到匹配项,因此不会出现在结果当中。

13.2 一对多/多对一匹配

多对一和一对多两种匹配模式指的是“一”侧的每一个向量元素可以与"多"侧的多个元素匹配的情况。在这种情况下,必须使用group修饰符:group_left或者group_right来确定哪一个向量具有更高的基数(充当“多”的角色)。

语法如下:

<vector expr> <bin-op> ignoring(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> ignoring(<label list>) group_right(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_right(<label list>) <vector expr>

多对一和一对多两种模式一定是出现在操作符两侧表达式返回的向量标签不一致的情况。因此需要使用ignoring和on修饰符来排除或者限定匹配的标签列表。

还是接上面的例子,使用表达式:

method_code:http_errors:rate5m / ignoring(code) group_left method:http_requests:rate5m

该表达式中,左向量method_code:http_errors:rate5m包含两个标签method和code。而右向量method:http_requests:rate5m中只包含一个标签method,因此匹配时需要使用ignoring限定匹配的标签为code。 在限定匹配标签后,右向量中的元素可能匹配到多个左向量中的元素 因此该表达式的匹配模式为多对一,需要使用group修饰符group_left指定左向量具有更好的基数。

最终结果如下:

{method="get", code="500"} 0.04 #24 / 600
{method="get", code="404"} 0.05 #30 / 600
{method="post", code="500"} 0.05 #6 / 120
{method="post", code="404"} 0.175 #21 / 120