文章目录

01 引言


Prometheus查询语法官方文档地址:​​https://prometheus.io/docs/prometheus/latest/querying/basics/​


Prometheus提供了一种名为​​PromQL (Prometheus query language)​​​的函数式查询语言,允许用户实时选择和聚合时间序列数据,表达式的结果可以以图形的形式显示,通过​​HTTP API​​由外部系统使用。

在Prometheus页面,输入查询语句,可以看到类似于如下接界面:

Table

Graph

完全解读Prometheus查询_时间序列

完全解读Prometheus查询_Promethues_02

接下来,详细讲解下​​Prometheus​​的查询语法。

02 概念

2.1 表达式类型(Expression language data types)

在​​Prometheus​​的表达语言中,一个表达式或子表达式可以总结为以下四种类型:

  • 瞬时向量(Instant vector :一组时间序列,包含每个时间序列的单一样本,所有共享相同的时间戳;
  • 范围向量(Range vector:一组时间序列,包含每个时间序列在一段时间内的数据点范围;
  • 标量(Scalar:一个简单的数字浮点值;
  • 字符串(String:一个简单的字符串值(目前未使用);

2.2 字面量(Literals)

2.2.1 String 字面量(String literals)

字符串可以指定为单引号、双引号或反引号中的文字。

​PromQL​​​遵循与​​Go相同的转义规则​​​。在单引号或双引号中,反斜杠开始转义序列,转义序列后面可以是​​a、b、f、n、r、t、v​​​或​​\​​​。可以使用八进制(​​\nnn​​​)或十六进制(​​\xnn, \unnnn和\ unnnnnn​​)提供特定字符。

反引号内不处理转义,与​​Go​​​不同,​​Prometheus​​不会在反引号内丢弃换行符。

例子:

"this is a string"
'these are unescaped: \n \\ \t'
`these are not unescaped: \n ' " \t`

2.2.2 Float 字面量(Float literals)

标量浮点值可以写成文字整型数或浮点数格式(仅包含空格以提高可读性):

[-+]?(
[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?
| 0[xX][0-9a-fA-F]+
| [nN][aA][nN]
| [iI][nN][fF]
)

举例:

23
-2.43
3.4e-9
0x8f
-Inf
NaN

2.3 时间序列选择器(Time series Selectors)

2.3.1 瞬时向量选择器(Instant vector selectors)

瞬时向量选择器:允许在给定的时间戳(即时)上选择一组时间序列和每个序列的单个样本值,在最简单的形式中,只指定一个度量名称。这将产生一个包含所有具有此度量名称的时间序列元素的即时向量。

这个例子选择了所有带有​​http_requests_total​​度量名称的时间序列:

http_requests_total

通过在花括号(​​{}​​)中添加逗号分隔的标签匹配器列表,可以进一步过滤这些时间序列。

举例:只选择那些带有​​http_requests_total​​​度量名称的时间序列,并且作业标签设置为​​prometheus​​​,它们的组标签设置为​​canary​​:

http_requests_total{job="prometheus",group="canary"}

也可以对标签值进行负匹配,或者将标签值与正则表达式进行匹配,存在以下标签匹配操作符:

  • =​: 选择与提供的字符串完全相等的标签;
  • !=​: 选择与提供的字符串不相等的标签;
  • =~​: 选择正则表达式与提供的字符串匹配的标签;
  • !~​: 选择与提供的字符串不匹配的标签;

正则表达式匹配是完全对应的,匹配​​env=~"foo"​​​被视为​​env=~"^foo$"​​。

举例:这将为​​staging、testing和development​​​环境以及除​​GET​​​之外的​​HTTP​​​方法选择所有​​http_requests_total​​时间序列:

http_requests_total{environment=~"staging|testing|development",method!="GET"}

注意:向量选择器必须指定一个名称或至少一个与空字符串不匹配的标签匹配器,下面的表达式是非法的:

{job=~".*"} # Bad!

相反,这些表达式是有效的,因为它们都有一个不匹配空标签值的选择器:

{job=~".+"}              # Good!
{job=~".*",method="get"} # Good!

通过与内部的​​__name__​​​标签进行匹配,标签匹配器也可以应用于度量名称。例如,表达式​​http_requests_total​​​等价于​​{__name__="http_requests_total"}​​​,除​​= (!=, =~, !~)​​​也可以使用,以下表达式选择名称以​​job​​开头的所有指标:

{__name__=~"job:.*"}

指标名不能是关键字​​bool, on, ignoring, group_left​​​和​​group_right​​中的一个。下面的表达式是非法的:

on{} # Bad!

解决这个限制的方法是使用​​__name__​​标签:

{__name__="on"} # Good!

Prometheus中的所有正则表达式都使用 ​​RE2语法​​。

2.3.2 范量向量选择器(Range Vector Selectors)

范量标量:工作方式类似于瞬时向量,只是它们选择了从当前瞬间返回的一个样本范围。

从语法上讲,时间持续时间被附加在向量选择器末尾的方括号(​​[]​​)中,以指定应该为每个结果范围向量元素提取到多远的时间值

举例:选择在过去5分钟内记录的所有时间序列的值,这些时间序列的度量名称为​​http_requests_total​​​,作业标签设置为​​prometheus​​:

http_requests_total{job="prometheus"}[5m]

2.3.3 持续时间(Time Durations)

时间持续时间指定为一个数字,后面紧跟着下列的其中一个单位:

  • ​ms​​ :毫秒
  • ​s​​ :秒
  • ​m​​ : 分钟
  • ​h​​ : 小时
  • ​d​​ : 天(一天24小时)
  • ​w​​​ : 周(一周7天)
    -​​​y​​:年(一年365天)

时间持续时间可以进行串联组合(注意:单位必须从最长到最短订购,一个给定的单位在一段时间内只能出现一次),举例:

5h
1h30m
5m
10s

2.3.4 Offset修饰符(Offset modifier)

offset修饰符:允许在查询中更改单个瞬间和范围向量的时间偏移量。

举例:以下表达式返回相对于当前查询评估时间过去5分钟的​​http_requests_total​​的值:

http_requests_total offset 5m

注意:​​offset修饰符​​必须跟在选择器后面,即:

sum(http_requests_total{method="GET"} offset 5m) // GOOD.

如果这样是错误的:

sum(http_requests_total{method="GET"}) offset 5m // INVALID.

对于范围向量也是一样的,下面例子将返回​​http_requests_total​​一周前的5分钟速率:

rate(http_requests_total[5m] offset 1w)

对于时间上向前移动的比较,可以指定负偏移量:

rate(http_requests_total[5m] offset -1w)

2.3.5 @ modifier

@修饰符:允许在查询中改变单个瞬间向量和范围向量的计算时间,提供给@修饰符的时间是一个​​unix​​时间戳,用浮点文字描述。

例如,以下表达式返回​​http_requests_total​​​在​​2021-01-04T07:40:00+00:00​​的值:

http_requests_total @ 1609746000

注意,@修饰符必须跟在选择器后面,举例:

sum(http_requests_total{method="GET"} @ 1609746000) // GOOD.

以下是错误写法:

sum(http_requests_total{method="GET"}) @ 1609746000 // INVALID.

对于范围向量也是一样的,下面表达式将返回​​http_requests_total​​​在​​2021-01-04T07:40:00+00:00​​处的5分钟速率:

rate(http_requests_total[5m] @ 1609746000)

@修饰符在​​int64​​范围内支持上述浮点数字面量的所有表示。它还可以与offset修饰符一起使用,​​offset​​是相对于**@修饰符**时间应用的,而不管哪个修饰符先写,这两个查询将产生相同的结果。

# offset after @
http_requests_total @ 1609746000 offset 5m
# offset before @
http_requests_total offset 5m @ 1609746000

另外,​​start()​​​和​​end()​​也可以作为**@修饰符**的特殊值使用,对于范围查询,它们分别解析到范围查询的开始和结束,并对所有步骤保持相同,对于即时查询,start()和end()都解析为计算时间。

http_requests_total @ start()
rate(http_requests_total[5m] @ end())

注意,@修饰符允许查询提前查看其计算时间。

2.4 子查询(Subquery)

子查询允许您对给定的范围即时查询,子查询的结果是一个范围向量。

语法:

<instant_query> '[' <range> ':' [<resolution>] ']' [ @ <float_literal> ] [ offset <duration> ]

​<resolution>​​:是可选的,默认是全局计算间隔。

2.5 操作符(Operators)

​Prometheus​​​支持许多二进制和聚合操作符,详情参考:​​https://prometheus.io/docs/prometheus/latest/querying/operators/​

2.5.1 二元作符(Binary operators)

​Prometheus​​​的查询语言支持基本的逻辑和算术运算符。对于两个瞬时向量之间的运算,可以修改​​匹配行为​​。

2.5.1.1 算术二元操作符(Arithmetic binary operators)

在​​Prometheus​​中存在以下二进制算术运算符:

  • ​+​​ :(加)
  • ​-​​ :(减)
  • ​*​​​ :(乘)
    -​​​ /​​ :(除)
  • ​%​​ :(取余)
  • ​^ ​​:(求幂)

二进制算术运算符定义在标量/标量、向量/标量和向量/向量值对之间:

  • 两个标量之间的行为很明显:它们计算另一个标量,这个标量是应用于两个标量操作数的运算符的结果。
  • 在瞬时向量和标量之间,将运算符应用于向量中每个数据样本的值。例如,如果一个时间序列瞬间向量乘以2,结果是另一个向量,其中原始向量的每个样本值乘以2,指标名称被删除。
  • 在两个瞬时向量之间:对左边向量中的每一个元素以及右边向量中与其匹配的元素应用一个二元算术运算符。结果被传播到结果向量,分组标签成为输出标签集。指标名称被删除。右边向量中找不到匹配项的项不属于结果的一部分。

2.5.1.2 三角二元操作符符(Trigonometric binary operators)

以下是在弧度上工作的三角二元运算符,在普罗米修斯中存在:

三角运算符允许使用向量匹配对两个向量执行三角函数,这在普通函数中是不可用的。它们的作用与算术运算符相同。

2.5.1.3 比较二元操作符(Comparison binary operators)

比较二元操作符有如下:

  • == (等于)
  • != (不等于)
  • (大于)

  • < (小于)
  • = (大于等于)

  • <= (小于等于)

2.5.1.4 逻辑二元操作符(Logical/set binary operators)

逻辑二元操作符如下:

  • and: (intersection)
  • or: (union)
  • unless:(complement)

2.5.2 向量匹配(Vector matching)

向量之间的运算试图为左边的每一个元素在右边的向量中找到一个匹配的元素。匹配行为有两种基本类型:一对一和多对一/一对多。

2.5.2.1 一对一向量匹配(One-to-one vector matches)

一对一从操作的每一边找到一个唯一的条目对。在默认情况下,这是一个遵循​​vector1 <操作符> vector2​​格式的操作。

如果两个条目具有完全相同的标签集和相应的值,则它们是匹配的。​​ignore​​​关键字允许在匹配时忽略某些标签,而​​on​​关键字允许将考虑的标签集缩减为所提供的列表:

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

input举例:

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

query举例:

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

举例:下面将返回一个结果向量,其中包含每个方法在过去5分钟内的状态码为500的HTTP请求的比例。如果不忽略(代码),就不会有匹配,因为度量标准不共享相同的标签集。带有put和del方法的条目没有匹配,不会在结果中显示:

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

2.5.2.2 多对一和一对多向量匹配(Many-to-one and one-to-many vector matches)

多对一和一对多匹配指的是“一”面上的每个向量元素都可以与“多”面上的多个元素匹配。这必须使用group_left或group_right修饰符显式地请求,其中left/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>

与组修饰符一起提供的标签列表包含了要包含在结果度量中的来自“一侧”的附加标签。在一个标签上只能出现在一个列表中。结果向量的每个时间序列必须是唯一可识别的。

分组修饰符只能用于比较和算术。操作as和,除非和或默认匹配右向量中的所有可能条目。

示例查询:

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

在这种情况下,左向量每个方法标签值包含多个条目。因此,我们使用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

多对一和一对多匹配是应该仔细考虑的高级用例。通常,正确使用​​ignoring(<labels>)​​会提供所需的结果。

2.5.3 聚合运算符(Aggregation operators)

​Prometheus​​支持以下内置的聚合操作符,可用于聚合单个瞬时向量的元素,从而生成一个具有聚合值的元素更少的新向量:

  • sum(calculate sum over dimensions)
  • min(select minimum over dimensions)
  • max(select maximum over dimensions)
  • avg(calculate the average over dimensions)
  • group(all values in the resulting vector are 1)
  • stddev(calculate population standard deviation over dimensions)
  • stdvar(calculate population standard variance over dimensions)
  • count(count number of elements in the vector)
  • count_values(count number of elements with the same value)
  • bottomk(smallest k elements by sample value)
  • topk(largest k elements by sample value)
  • quantile(calculate φ-quantile (0 ≤ φ ≤ 1) over dimensions)

这些操作符既可以用于对所有标签维度进行聚合,也可以通过包含​​without​​​或​​by​​子句来保留不同的维度。这些子句可以在表达式之前或之后使用。

<aggr-op> [without|by (<label list>)] ([parameter,] <vector expression>)

或者

<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]

2.5.4 二元操作符优先级(Binary operator precedence)

下面的列表显示了二元制操作符在普罗米修斯中从高到低的优先级:

  1. ​^​
  2. ​ *, /, %, atan2​
  3. ​+, -​
  4. ​==, !=, <=, <, >=, >​
  5. ​and, unless​
  6. ​or​

具有相同优先级的操作符是左结合的。例如,​​2 * 3% % 2​​​等于​​(2 * 3)% 2​​​。然而​​^​​​是右结合律,所以​​2 ^ 3 ^ 2​​​等于​​2 ^ (3 ^ 2)​

2.6 函数(Functions)

​Prometheus​​​支持对数据进行操作的几个函数,详情参考:​​https://prometheus.io/docs/prometheus/latest/querying/functions/​

函数名

解析

abs()

求瞬时向量绝对值

absent()

传入一个瞬时非空向量则返回空向量,否则返回不带名称值为1的指标,用来监控空数据的情况,即nodata监控

absent_over_time()

返回一个空向量;如果传递给它的范围向量没有元素,则返回一个1元素的值为1的向量。

ceil()

四舍五入取整

changes()

计算区间向量中每个样本值变化次数,返回的是瞬时向量

clamp()

所有元素的样本值,使其下限为min,上限为max。

clamp_max()

瞬时向量值如果大于max 则值修改为max,否则值不变(用于将向量规范到一个不大于max的区间内)

clamp_min()

同理,将向量规范到一个不小于min值的范围内

day_of_month()

返回 1~31 向量所在UTC时间中的所在月份的第几天

day_of_week()

返回被给定 UTC 时间所在周的第几天。返回值范围:0~6,0 表示星期天。

days_in_month()

返回当月一共有多少天。返回值范围:28~31。

delta()

参数是一个区间向量,返回一个瞬时向量。它计算一个区间向量 v 的第一个元素和最后一个元素之间的差值(和张羽看了prometheus的实现代码,可以简单理解为就是最后一个点的值减去第一个点的值。但是实际上真实场景中,第一个点和最后一个点不可能每次都正好每和时间区间的第一个时间点和最后一个时间点相吻合,所以prometheus在代码中做了大量的计算,由于涉及到很多除法运算,因此结果一般都是小数)

deriv()

参数是一个区间向量,返回一个瞬时向量。它使用简单的线性回归计算区间向量 v 中各个时间序列的导数。

exp()

输入一个瞬时向量,返回各个样本值的 e 的指数值,即 e 的 N 次方。当得到一个无穷大的值,显示 +Inf, 反之显示0, e的负数次方无限趋进0。e的空向量指数依然为空向量。

floor()

与 ceil() 函数相反,舍弃小数部分取整

histogram_quantile()

从直方图的桶b计算φ-分位数(0≤φ≤1)。

holt_winters()

生成一个基于v的范围的时间序列的平滑值,平滑因子sf越低,旧数据越重要。趋势因子tf越高,数据中考虑的趋势越多。sf和tf都必须在0和1之间。

hour()

函数返回被给定 UTC 时间的当前第几个小时,时间范围:0~23。

idelta()

参数是一个区间向量, 返回一个瞬时向量。它计算最新的 2 个样本值之间的差值。(当区间内仅有一个向量时无返回值)

increase()

函数获取区间向量中的第一个和最后一个样本并返回其增长量,它会在单调性发生变化时(如由于采样目标重启引起的计数器复位)自动中断。

irate()

使用rate计算快速变化的样本平均增长率时,容易陷入长尾问题,因为它用平均值将峰值削平了,无法反映时间窗口内样本数据的快速变化。与rate类似,irate同样可以计算counter的平均增长率,但其反映出的是瞬时增长率。irate计算增长率时,使用指定时间范围内的最后两个样本数据

label_join()

函数可以将时间序列 v 中多个标签 src_label 的值,通过 separator 作为连接符写入到一个新的标签 dst_label 中。可以有多个 src_label 标签

label_replace()

在原标签中匹配字符,放到目标标签中

ln()

计算瞬时向量 v 中所有样本数据的自然对数。

log2()

计算瞬时向量 v 中所有样本数据的二进制对数。特殊情况同ln

log10()

计算瞬时向量 v 中所有样本数据的十进制对数。特殊情况同ln。

minute()

函数返回给定 UTC 时间当前小时的第多少分钟。结果范围:0~59。

month()

函数返回给定 UTC 时间当前属于第几个月,结果范围:0~12。

predict_linear()

可以预测时间序列 v 在 t 秒后的值。它基于简单线性回归的方式,对时间窗口内的样本数据进行统计,从而可以对时间序列的变化趋势做出预测。

rate()

用于计算区间向量平均增长率,如计算QPS

resets()

对于每个时间序列,它都返回一个计数器重置的次数。两个连续样本之间单调性发生变化被认为是一次计数器重置。

round()

与 ceil 和 floor 函数类似,返回向量中所有样本值的最接近to_nearest值整数倍的值,当to_nearest=1时等价于ceil。

scalar()

返回一个单元素瞬时向量的样本值,当多元素或者没有元素返回Nan。

sgn()

返回一个向量,所有样本值都转换为它们的符号,定义为:1如果v是正的,-1如果v是负的,0如果v等于0。

sort()

对向量按元素的值进行升序排序。

sort_desc()

对向量按元素的值进行降序排序。

sqrt()

计算向量 v 中所有元素的平方根。

time()

返回自UTC 1970年1月1日以来的秒数。请注意,这实际上并不返回当前时间,而是表达式被求值的时间。

timestamp()

返回向量 v 中的每个样本的时间戳(从 1970-01-01 到现在的秒数)。

vector()

将标量 s 作为没有标签的向量返回(和scalar作用刚好相反)。

year()

函数返回被给定 UTC 时间的当前年份。

​<aggregation>_over_time()​

允许在一段时间内聚合给定范围向量的每个序列,并返回带有每个序列聚合结果的即时向量

2.7 注释(Comments)

PromQL 支持以​​#​​开头的注释, 例如:

# This is a comment

2.8 陷阱(Gotchas)

2.8.1 过时(Staleness)

这里主要声明一些关于过期的描述,直接贴图:

完全解读Prometheus查询_修饰符_03

2.8.2 避免慢查询和重载(Avoiding slow queries and overloads)

这里主要讲慢查询和重载的注意点,直接贴图:

完全解读Prometheus查询_Promethues_04

03 举例

3.1 简单的时间序列选择(Simple time series selection)

返回带有度量​​http_requests_total​​的所有时间序列:

http_requests_total

返回带有指标http_requests_total和给定的作业和处理器标签的所有时间序列:

http_requests_total{job="apiserver", handler="/api/comments"}

返回同一向量的整个时间范围(在本例中为5分钟),使其成为一个范围向量:

http_requests_total{job="apiserver", handler="/api/comments"}[5m]

请注意,产生范围向量的表达式不能直接绘制图形,而是在表达式浏览器的表格(“Console”)视图中查看。

使用正则表达式,您可以仅为名称匹配特定模式的作业选择时间序列,在本例中,所有以server结尾的作业:

http_requests_total{job=~".*server"}

要选择除4xx以外的所有HTTP状态码,可以运行:

http_requests_total{status!~"4.."}

3.2 子查询(Subquery)

返回过去30分钟http_requests_total度量的5分钟速率,分辨率为1分钟。

rate(http_requests_total[5m])[30m:1m]

这是一个嵌套子查询的示例。派生函数的子查询使用默认分辨率。注意,不必要地使用子查询是不明智的。

max_over_time(deriv(rate(distance_covered_total[5s])[30s:5s])[10m:])

3.4 方法、操作符(Using functions, operators, etc.)

返回带有http_requests_total指标名称的所有时间序列的每秒速率,在过去5分钟内测量:

rate(http_requests_total[5m])

假设http_requests_total时间序列都有标签job(根据作业名称fanout)和instance(根据作业实例fanout),我们可能想要对所有实例的速率求和,这样我们得到更少的输出时间序列,但仍然保留作业维度:

sum by (job) (
rate(http_requests_total[5m])
)

如果我们有两个具有相同维度标签的不同度量,我们可以对它们应用二元运算符,两边具有相同标签集的元素将被匹配并传播到输出中。例如,该表达式返回MiB中每个实例的未使用内存(在一个虚构的集群调度程序上,暴露其运行的实例的这些指标):

(instance_memory_limit_bytes - instance_memory_usage_bytes) / 1024 / 1024

同样的表达式,但是通过应用求和,可以写成这样:

sum by (app, proc) (
instance_memory_limit_bytes - instance_memory_usage_bytes
) / 1024 / 1024

如果同一个虚构的集群调度程序暴露了每个实例的CPU使用率指标,如下所示:

instance_cpu_time_ns{app="lion", proc="web", rev="34d0f99", env="prod", job="cluster-manager"}
instance_cpu_time_ns{app="elephant", proc="worker", rev="34d0f99", env="prod", job="cluster-manager"}
instance_cpu_time_ns{app="turtle", proc="api", rev="4d3a513", env="prod", job="cluster-manager"}
instance_cpu_time_ns{app="fox", proc="widget", rev="4d3a513", env="prod", job="cluster-manager"}
...

我们可以得到按应用程序(app)和进程类型(proc)分组的前3名CPU用户,如下所示:

topk(3, sum by (app, proc) (rate(instance_cpu_time_ns[5m])))

假设这个指标包含每个运行实例一个时间序列,你可以这样计算每个应用程序运行实例的数量:

count by (app) (instance_cpu_time_ns)

04 HTTP接口获取

当前稳定的HTTP API在Prometheus服务器的/ API /v1下是可访问的,详情参考:​​https://prometheus.io/docs/prometheus/latest/querying/api/​

05 小结

本文主要讲解了​​PromSQL​​​的构造以及每块组成部分的解析,具体还需要去详细查看​​官方的文档​​,本文就不再过细的描述了。谢谢大家的阅读,本文完!