1. PromQL函数
Prometheus 提供了其它大量的内置函数,可以对时序数据进行丰富的处理。某些函数有默认的参数,例如:year(v=vector(time()) instant-vector)
。其中参数 v
是一个瞬时向量,如果不提供该参数,将使用默认值 vector(time())
。instant-vector 表示参数类型。
1.1 内置函数
Prometheus 内置了一些函数来辅助计算,下面介绍一些典型的
函数 | 描述 |
---|---|
abs() | 绝对值 |
sqrt() | 平方根 |
exp() | 指数计算 |
ln() | 自然对数 |
ceil() | 向上取整 |
floor() | 向下取整 |
round() | 四舍五入取整 |
delta() | 计算区间向量里每一个时序第一个和最后一个的差值 |
sort() | 排序 |
time() | 时间 |
1.abs
abs(v instant-vector)
返回输入向量的所有样本的绝对值
abs(process_open_fds{instance="alertmanager"} -20)
abs(process_open_fds{instance="alertmanager"} -20)
2.sqrt
sqrt(process_open_fds{instance="alertmanager"} +37)
sqrt(process_open_fds{instance="alertmanager"} +37)
3.round
round(vector(7.8))
round(vector(7.8))
4.time
time()-process_start_time_seconds
time()-process_start_time_seconds
5.absent
absent(v instant-vector)
,如果传递给它的向量参数具有样本数据,则返回空向量;如果传递的向量参数没有样本数据,则返回不带度量指标名称且带有标签的时间序列,且样本值为1。
监控度量指标时,如果获取到的样本数据是空的, 使用 absent 方法对告警是非常有用的。例如:
# 这里提供的向量有样本数据
absent(http_requests_total{method="get"}) => no
dataabsent(sum(http_requests_total{method="get"})) => no data
# 由于不存在度量指标 nonexistent,所以 返回不带度量指标名称且带有标签的时间序列,且样本值为1
absent(nonexistent{job="myjob"}) => {job="myjob"} 1
# 正则匹配的 instance 不作为返回 labels 中的一部分
absent(nonexistent{job="myjob",instance=~".*"}) => {job="myjob"} 1
# sum 函数返回的时间序列不带有标签,且没有样本数据
absent(sum(nonexistent{job="myjob"})) => {} 1
# 这里提供的向量有样本数据
absent(http_requests_total{method="get"}) => no
dataabsent(sum(http_requests_total{method="get"})) => no data
# 由于不存在度量指标 nonexistent,所以 返回不带度量指标名称且带有标签的时间序列,且样本值为1
absent(nonexistent{job="myjob"}) => {job="myjob"} 1
# 正则匹配的 instance 不作为返回 labels 中的一部分
absent(nonexistent{job="myjob",instance=~".*"}) => {job="myjob"} 1
# sum 函数返回的时间序列不带有标签,且没有样本数据
absent(sum(nonexistent{job="myjob"})) => {} 1
6.ceil()
ceil(v instant-vector)
将 v 中所有元素的样本值向上四舍五入到最接近的整数。例如:
ceil(node_load5{instance="192.168.1.75:9100"}) # 结果为 2.79ceil(node_load5{instance="192.168.1.75:9100"}) # 结果为 3
ceil(node_load5{instance="192.168.1.75:9100"}) # 结果为 2.79ceil(node_load5{instance="192.168.1.75:9100"}) # 结果为 3
7.changes()
changes(v range-vector)
输入一个区间向量, 返回这个区间向量内每个样本数据值变化的次数(瞬时向量)。例如:
# 如果样本数据值没有发生变化,则返回结果为 1
changes(node_load5{instance="192.168.1.75:9100"}[1m]) # 结果为 1
# 如果样本数据值没有发生变化,则返回结果为 1
changes(node_load5{instance="192.168.1.75:9100"}[1m]) # 结果为 1
8.clamp_max()
clamp_max(v instant-vector, max scalar)
函数,输入一个瞬时向量和最大值,样本数据值若大于 max,则改为 max,否则不变。例如:
node_load5{instance="192.168.1.75:9100"} # 结果为
2.79clamp_max(node_load5{instance="192.168.1.75:9100"}, 2) # 结果为 2
node_load5{instance="192.168.1.75:9100"} # 结果为
2.79clamp_max(node_load5{instance="192.168.1.75:9100"}, 2) # 结果为 2
9.clamp_min()
clamp_min(v instant-vector, min scalar)
函数,输入一个瞬时向量和最小值,样本数据值若小于 min,则改为 min,否则不变。例如:
node_load5{instance="192.168.1.75:9100"} # 结果为
2.79clamp_min(node_load5{instance="192.168.1.75:9100"}, 3) # 结果为 3
node_load5{instance="192.168.1.75:9100"} # 结果为
2.79clamp_min(node_load5{instance="192.168.1.75:9100"}, 3) # 结果为 3
10.increase()
increase(v range-vector)
函数获取区间向量中的第一个和最后一个样本并返回其增长量, 它会在单调性发生变化时(如由于采样目标重启引起的计数器复位)自动中断。由于这个值被外推到指定的整个时间范围,所以即使样本值都是整数,你仍然可能会得到一个非整数值。
increase(http_requests_total{job="apiserver"}[5m])
increase(http_requests_total{job="apiserver"}[5m])
increase
的返回值类型只能是计数器类型,主要作用是增加图表和数据的可读性。使用 rate
函数记录规则的使用率,以便持续跟踪数据样本值的变化。
11.irate()
irate(v range-vector)
函数用于计算区间向量的增长率,但是其反应出的是瞬时增长率。irate 函数是通过区间向量中最后两个两本数据来计算区间向量的增长速率,它会在单调性发生变化时(如由于采样目标重启引起的计数器复位)自动中断。这种方式可以避免在时间窗口范围内的“长尾问题”,并且体现出更好的灵敏度,通过irate函数绘制的图标能够更好的反应样本数据的瞬时变化状态。
例如,以下表达式返回区间向量中每个时间序列过去 5 分钟内最后两个样本数据的 HTTP 请求数的增长率:
irate(http_requests_total{job="api-server"}[5m])
irate(http_requests_total{job="api-server"}[5m])
irate 只能用于绘制快速变化的计数器,在长期趋势分析或者告警中更推荐使用 rate 函数。因为使用 irate 函数时,速率的简短变化会重置 FOR
语句,形成的图形有很多波峰,难以阅读。
当将
irate()
函数与聚合运算符(例如sum()
)或随时间聚合的函数(任何以_over_time
结尾的函数)一起使用时,必须先执行 irate 函数,然后再进行聚合操作,否则当采样目标重新启动时 irate() 无法检测到计数器是否被重置。
12.rate()
rate(v range-vector)
函数可以直接计算区间向量 v 在时间窗口内平均增长速率,它会在单调性发生变化时(如由于采样目标重启引起的计数器复位)自动中断。该函数的返回结果不带有度量指标,只有标签列表。
例如,以下表达式返回区间向量中每个时间序列过去 5 分钟内 HTTP 请求数的每秒增长率:
rate(http_requests_total[5m])结果:
{code="200",handler="label_values",instance="120.77.65.193:9090",job="prometheus",method="get"} 0
{code="200",handler="query_range",instance="120.77.65.193:9090",job="prometheus",method="get"} 0
{code="200",handler="prometheus",instance="120.77.65.193:9090",job="prometheus",method="get"} 0.2
...
rate(http_requests_total[5m])结果:
{code="200",handler="label_values",instance="120.77.65.193:9090",job="prometheus",method="get"} 0
{code="200",handler="query_range",instance="120.77.65.193:9090",job="prometheus",method="get"} 0
{code="200",handler="prometheus",instance="120.77.65.193:9090",job="prometheus",method="get"} 0.2
...
rate() 函数返回值类型只能用计数器,在长期趋势分析或者告警中推荐使用这个函数。
当将
rate()
函数与聚合运算符(例如sum()
)或随时间聚合的函数(任何以_over_time
结尾的函数)一起使用时,必须先执行 rate 函数,然后再进行聚合操作,否则当采样目标重新启动时 rate() 无法检测到计数器是否被重置。
13.label_join()
label_join(v instant-vector, dst_label string, separator string, src_label_1 string, src_label_2 string, ...)
函数可以将时间序列 v 中多个标签 src_label
的值,通过 separator
作为连接符写入到一个新的标签 dst_label
中。可以有多个 src_label 标签。
例如,以下表达式返回的时间序列多了一个 foo
标签,标签值为 etcd,etcd-k8s
:
up{endpoint="api",instance="192.168.123.248:2379",job="etcd",namespace="monitoring",service="etcd-k8s"}=> up{endpoint="api",instance="192.168.123.248:2379",job="etcd",namespace="monitoring",service="etcd-k8s"} 1
label_join(up{endpoint="api",instance="192.168.123.248:2379",job="etcd",namespace="monitoring",service="etcd-k8s"}, "foo", ",", "job", "service")=> up{endpoint="api",foo="etcd,etcd-k8s",instance="192.168.123.248:2379",job="etcd",namespace="monitoring",service="etcd-k8s"} 1
up{endpoint="api",instance="192.168.123.248:2379",job="etcd",namespace="monitoring",service="etcd-k8s"}=> up{endpoint="api",instance="192.168.123.248:2379",job="etcd",namespace="monitoring",service="etcd-k8s"} 1
label_join(up{endpoint="api",instance="192.168.123.248:2379",job="etcd",namespace="monitoring",service="etcd-k8s"}, "foo", ",", "job", "service")=> up{endpoint="api",foo="etcd,etcd-k8s",instance="192.168.123.248:2379",job="etcd",namespace="monitoring",service="etcd-k8s"} 1
14.label_replace()
为了能够让客户端的图标更具有可读性,可以通过 label_replace
函数为时间序列添加额外的标签。label_replace 的具体参数如下:
label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)
label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)
该函数会依次对 v 中的每一条时间序列进行处理,通过 regex
匹配 src_label 的值,并将匹配部分 relacement
写入到 dst_label 标签中。如下所示:
label_replace(up, "host", "$1", "instance", "(.*):.*")
label_replace(up, "host", "$1", "instance", "(.*):.*")
函数处理后,时间序列将包含一个 host
标签,host 标签的值为 Exporter 实例的 IP 地址:
up{host="localhost",instance="localhost:8080",job="cadvisor"} 1
up{host="localhost",instance="localhost:9090",job="prometheus"} 1
up{host="localhost",instance="localhost:9100",job="node"} 1
up{host="localhost",instance="localhost:8080",job="cadvisor"} 1
up{host="localhost",instance="localhost:9090",job="prometheus"} 1
up{host="localhost",instance="localhost:9100",job="node"} 1
15.sort()
sort(v instant-vector)
函数对向量按元素的值进行升序排序,返回结果:key: value = 度量指标:样本值[升序排列]。
16.sort_desc()
sort(v instant-vector)
函数对向量按元素的值进行降序排序,返回结果:key: value = 度量指标:样本值[降序排列]。
1.2 基于时间聚合
avg_over_time(range-vector) | 区间向量内每个指标的平均值 |
---|---|
min_over_time(range-vector) | 区间向量内每个指标的最小值 |
max_over_time(range-vector) | 区间向量内每个指标的最大值 |
sum_over_time(range-vector) | 区间向量内每个指标的求和 |
count_over_time(range-vector) | 区间向量内每个指标的样本数据个数 |
quantile_over_time(scalar, range-vector) | 区间向量内每个指标的样本数据值分位数 |
stddev_over_time(range-vector) | 区间向量内每个指标的总体标准差 |
stdvar_over_time(range-vector) | 区间向量内每个指标的总体标准方差 |
1.3 基于标签聚合
使用PromQL查询到时间序列后,可视化工具会根据时间序列的标签来渲染图表。例如通过up指标可以获取到当前所有运行的Exporter实例以及其状态:
可视化工具渲染图标时可能根据,instance和job的值进行渲染,为了能够让客户端的图标更具有可读性,可以通过label_replace标签为时间序列添加额外的标签。label_replace的具体语法如下:
label_replace(__input_vector__, "dst_label", "__replacement__", "src_label", "__regex__")
label_replace(__input_vector__, "dst_label", "__replacement__", "src_label", "__regex__")
该函数会依次对v中的每一条时间序列进行处理,通过regex匹配src_label的值,并将匹配部分relacement写入到dst_label标签中。如下所示:
label_replace(up,"host", "$1", "instance","(.*)")
label_replace(up,"host", "$1", "instance","(.*)")
执行之后
1.4 基于counter指标
通过增长率表示样本的变化情况
increase(vrange-vector)函数是PromQL中提供的众多内置函数之一。其中参数v是一个区间向量,increase函数获取区间向量中的第一个后最后一个样本并返回其增长量。因此,可以通过以下表达式Counter类型指标的增长率:
increase(node_load1[2m]) / 120
increase(node_load1[2m]) / 120
这里通过node_cpu[2m]获取时间序列最近两分钟的所有样本,increase计算出最近两分钟的增长量,最后除以时间120秒得到node_cpu样本在最近两分钟的平均增长率。并目这个值也近似于主机节点最近两分钟内的平均CPU使用率。
除了使用increase函数以外,PromQL中还直接内置了rate(Vrange-vector)函数,rate函数可以直接计算区间向量v在时间窗口内平均增长速率。因此,通过以下表达式可以得到与increase函数相同的结果:
rate(node_load1[2m])
rate(node_load1[2m])
需要注意的是使用rate或者increase函数去计算样本的平均增长速率,容易陷入"长尾问题"当中,其无法反应在时间窗口内样本数据的突发变化。例如,对于主机而言在2分钟的时间窗口内,可能在某一个由于访问量或者其它问题导致CPU占用100%的情况,但是通过计算在时间窗口内的平均增长率却无法反应出该问题。
为了解决该问题,PromQL提供了另外一个灵敏度更高的函数irate(vrange-vector)。irate同样用于计算区间向量的计算率,但是其反应出的是瞬时增长率。irate函数是通过区间向量中最后两个样本数据来计算区间向量的增长速率。这种方式可以避免在时间窗口范围内的"长尾问题”,并且体现出更好的灵敏度,通过irate函数绘制的图标能够更好的反应样本数据的瞬时变化状态。
irate(node_load1[2m])
irate(node_load1[2m])
长期还是建议使用rate函数
1.5 基于Gauge指标
比如有些某些业务增长,存储空间的增长速率提升了高几倍。这时,如果基于原有阈值去触发告警,当系统管理员接收到告警以后可能还没来得及去处理问题,系统就已经不可用了。因此阈值通常来说不是固定的,需要定期进行调整才能保证该告警阈值能够发挥去作用。那么还有没有更好的方法?
PromQL中内置的predict_linear(vrange-vector,tscalar)函数可以帮助系统管理员更好的处理此类情况,predict_linear函数可以预测时间序列v在t秒后的值。它基于简单线性回归的方式,对时间窗口内的样本数据进行统计,从而可以对时间序列的变化趋势做出预测。例如,基于1小时的样本数据,来预测主机可用磁盘空间的是否在24个小时候被占满,可以使用如下表达式:
predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs"}[1h],24 * 3600)<0
predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs"}[1h],24 * 3600)<0
node_export表达式
(node_filesystem_avail_bytes *100)/ node_filesystem_size_bytes<10 and ON (instance,device,mountpoint)
predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs"}[1h],24 * 3600)<0 and on (instance,device,
mountpoint) node_filesystem_readonly ==0
(node_filesystem_avail_bytes *100)/ node_filesystem_size_bytes<10 and ON (instance,device,mountpoint)
predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs"}[1h],24 * 3600)<0 and on (instance,device,
mountpoint) node_filesystem_readonly ==0
2.案例
下面的示例中我们看到另外一种写法[15m:1m]
,这表示取15分钟的一个范围数据,并按每分钟分为一段,最后分为15段。在15段中取一个最小值。
- alert: KubernetesPodNotHealthy
expr: min_over_time(sum by (namespace, pod) (kube_pod_status_phase{phase=~"Pending|Unknown|Failed"})[15m:1m]) > 0
for: 0m
labels:
severity: critical
annotations:
summary: Kubernetes Pod not healthy (instance {{ $labels.instance }})
description: "Pod has been in a non-ready state for longer than 15 minutes.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: KubernetesPodNotHealthy
expr: min_over_time(sum by (namespace, pod) (kube_pod_status_phase{phase=~"Pending|Unknown|Failed"})[15m:1m]) > 0
for: 0m
labels:
severity: critical
annotations:
summary: Kubernetes Pod not healthy (instance {{ $labels.instance }})
description: "Pod has been in a non-ready state for longer than 15 minutes.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"