前言
当 Prometheus 通过 Exporter 采集到相应的监控指标样本数据后,我们就可以通过PromQL 对监控样本数据进行查询,从而对相应的数据样本进行分析以及制定报警规则。
1. PromQL的简介
PromQL(Prometheus Query Language)是 Prometheus 内置的数据查询语言。支持用户进行实时的数据查询及聚合操作。
Prometheus 基于指标名称(metrics name)以及附属的标签集(labelset)唯一定义一条时间序列
●指标名称代表着监控目标上某类可测量属性的基本特征标识
●标签则是这个基本特征上再次细分的多个可测量维度
基于 PromQL 表达式,用户可以针对指定的特征及其细分的纬度进行过滤、聚合、统计等运算从而产生期望的计算结果
●PromQL 使用表达式(expression)来表述查询需求
●根据其使用的指标和标签,以及时间范围,表达式的查询请求可灵活地覆盖在一个或多个时间序列的一定范围内的样本之上,甚至是只包含单个时间序列的单个样本
2. PromQL数据样本信息的含义理解
2.1 Prometheus 数据模型
Prometheus 中,每个时间序列都由指标名称(Metric Name)和标签(Label)来唯一标识
格式为:<metric_name>{<label_name>=<label_value>, ...}
●指标名称:通常用于描述系统上要测定的某个特征
例如,prometheus_http_requests_total 表示接收到的 HTTP 请求总数
●标签:键值型数据,附加在指标名称之上,从而让指标能够支持多纬度特征;可选项
例如,prometheus_http_requests_total{code="200"} 和 prometheus_http_requests_total{code="302"} 代表着两个不同的时间序列
●双下划线的标签(例如 __address__ )是 Prometheus 系统默认标签,是不会显示在 /metrics 页面里面的;
●系统默认标签在 target 页面中也是不显示的,需要鼠标放到 label 字段上才会显示。
常见的系统默认标签:
__address__ 当前 target 实例的套接字地址 <host>:<port>
__scheme__ 采集当前 target 上指标数据时使用的协议(http 或 https)
__metrics_path__ 采集当前 target 上的指标数据时使用 URI 路径,默认为 /metrics
__param_<name> 传递的 URL 参数中第一个名称为 <name> 的参数的值
__name__
此标签是标识指标名称的预留标签,能够使用标签选择器对指标名称进行过滤
指标名称及标签使用注意事项:
●指标名称和标签的特定组合代表着一个时间序列;指标名称相同,但标签不同的组合分别代表着不同的时间序列;不同的指标名称自然更是代表着不同的时间序列
●PromQL支持基于定义的指标维度进行过滤和聚合;更改任何标签值,包括添加或删除标签,都会创建一个新的时间序列;应该尽可能地保持标签的稳定性,否则,则很可能创建新的时间序列,更甚者会生成一个动态的数据环境,并使得监控的数据源难以跟踪,从而导致建立在该指标之上的图形、告警及记录规则变得无效
2.2 样本数据格式
Prometheus 的每个数据样本由两部分组成
●毫秒精度的时间戳
●float64 格式的数据
2.3 PromQL 的数据类型
PromQL 的表达式中支持 4 种数据类型:
●瞬时向量 (Instant vector): 特定或全部的时间序列集合上,具有相同时间戳的一组样本值
●区间向量 (Range vector): 特定或全部的时间序列集合上,在指定的同一时间范围内的所有样本值
●标量数据 (Scalar): 一个浮点型的数据值
●字符串 (String): 一个字符串,支持使用单引号、双引号进行引用
2.4 时间序列选择器(Time series Selectors)
PromQL 的查询操作可能需要针对若干个时间序列上的样本数据进行,挑选出目标时间序列是构建表达式时最为关键的一步;
用户可使用向量选择器表达式来挑选出给定指标名称下的所有时间序列或部分时间序列的即时样本值或至过去某个时间范围内的样本值,前者称为瞬时向量选择器,后者称为区间向量选择器。
(1)瞬时向量选择器(Instant Vector Selectors)
瞬时向量选择器可以返回 0 个、1 个或多个时间序列上在给定时间戳(instant)上的各自的一个样本。
瞬时向量选择器由两部分组成:
◆指标名称:用于限定特定指标下的时间序列,即负责过滤指标;可选
◆标签选择器:用于过滤时间序列上的标签;定义在 {} 之中;可选
定义瞬时向量选择器时,以上两个部分应该至少给出一个;因此存在以下三种组合:
◆仅给定指标名称,或在标签名称上使用了空值的标签选择器:返回给定的指标下的所有时间序列各自的即时样本
例如,prometheus_http_requests_total 和 prometheus_http_requests_total{} 的功能相同,都是用于返回这个指标下各时间序列的即时样本
◆仅给定标签选择器:返回所有符合给定的标签选择器的所有时间序列上的即时样本
例如,{code="200", job="prometheus"} ,这样的时间序列可能会有着不同的指标名称
◆指标名称和标签选择器的组合:返回给定的指标下的,且符合给定的标签过滤器的所有时间序列上的即时样本
例如,prometheus_http_requests_total{code="200", job="prometheus"},用于返回这个指标 code 为 200, 并且 job 为 prometheus 的时间序列的即时样本
标签选择器用于定义标签过滤条件,目前支持如下4种匹配操作符:
= :完全相等
!= : 不相等
=~ : 正则表达式匹配
!~ : 正则表达式不匹配
注意事项:
◆匹配到空标签值的标签选择器时,所有未定义该标签的时间序列同样符合条件
◆正则表达式将执行完全锚定机制,它需要匹配指定的标签的整个值
◆向量选择器至少要包含一个指标名称,或者至少有一个不会匹配到空字符串的标签选择器
例如,{ job=""} 为非法的向量选择器
◆使用 __name__ 做为标签名称,还能够对指标名称进行过滤
(2)区间向量选择器(Range Vector Selectors)
区间向量选择器可以返回 0 个、1 个或多个时间序列上在给定时间范值围内的各自的一组样本。
区间向量选择器的不同之处在于,需要通过在瞬时向量选择器表达式后面添加包含在 [] 里的时长来表达需在时间时序上返回的样本所处的时间范围。
时间范围:以当前时间为基准时间点,指向过去一个特定的时间长度;例如,[5m] 是指过去 5 分钟之内。
◆可用的时间单位有 ms(毫秒)、s(秒)、m(分钟)、h(小时)、d(天)、w(周)和 y(年)
◆必须使用整数时间,且能够将多个不同级别的单位进行串联组合,以时间单位由大到小为顺序,例如 1h30m,但不能使用 1.5h
2.5 偏移向量选择器
前面介绍的选择器默认都是以当前时间为基准时间,偏移修饰器用来调整基准时间,使其往前偏移一段时间。偏移修饰器紧跟在选择器后面,使用关键字 offset 来指定要偏移的量。
例如,prometheus_http_requests_total offset 5m ,表示获取以 prometheus_http_requests_total 为指标名称的所有时间序列在过去 5 分钟之时的即时样本;
prometheus_http_requests_total[5m] offset 1d ,表示获取距此刻 1 天时间之前的 5 分钟之内的所有样本
#向量表达式使用要点:
●表达式的返回值类型亦是即时向量、范围向量、标题或字符串4种数据类型其中之一,但是,有些使用场景要求表达式返回值必须满足特定的条件,例如:
(1)需要将返回值绘制成图形时,仅支持即时向量类型的数据;
(2)对于诸如 rate、irate 之类的速率函数来说,其要求使用的却又必须是区间向量型的数据
●由于区间向量选择器的返回的是区间向量型数据,它不能用于表达式浏览器中图形绘制功能
●区间向量选择器通常会结合速率类的函数 rate、irate 一同使用
2.6 PromQL 的指标类型
PromQL 有四个指标类型:
●Counter :计数器,用于保存单调递增型的数据;例如站点访问次数等。数据单调递增,不支持减少,不能为负值,重启进程后,会被重置回 0 ;
●Gauge :仪表盘,用于存储有着起伏特征的指标数据,例如内存空闲大小等。数据可变大,可变小;重启进程后,会被重置;
●Histogram :累积直方图,将时间范围内的数据划分成不同的时间段,并各自评估其样本个数及样本值之和,因而可计算出分位数;
◆可用于分析因异常值而引起的平均值过大的问题;
◆分位数计算要使用专用的 histogram_quantile 函数;
●Summary :类似于 Histogram,但会在客户端直接计算并上报分位数;
(1) Counter类型
通常,Counter 的总数并没有直接作用,而是需要借助于 rate、topk、increase 和 irate 等函数来生成样本数据的变化状况(增长率/变化率):
#获取该指标下 http 请求总数排名前 3 的时间序列
topk(3, prometheus_http_requests_total)
#获取 1 小内,该指标下各时间序列上的 http 总请求数的增长速率
rate(prometheus_http_requests_total[1h])
#irate 为高灵敏度函数,用于计算指标的瞬时速率,基于样本范围内的最后两个样本进行计算,相较于 rate 函数来说,irate 更适用于短期时间范围内的变化速率分析。
irate(prometheus_http_requests_total[1h])
(2)Gauge类型
Gauge 用于存储其值可增可减的指标的样本数据,常用于进行求和、取平均值、最小值、最大值等聚合计算;也会经常结合 PromQL 的 delta 和 predict_linear 函数使用:
●delta 函数计算范围向量中每个时间序列元素的第一个值与最后一个值之差,从而展示不同时间点上的样本值的差值
eg:返回该服务器上的CPU温度与2小时之前的差异
delta(cpu_temp_celsius{host="node01"}[2h])
●predict_linear 函数可以预测时间序列 v 在 t 秒后的值,它通过线性回归的方式,对样本数据的变化趋势做出预测
(3) Histogram类型
对于 Prometheus 来说,Histogram 会在一段时间范围内对数据进行采样(通常是请求持续时长或响应大小等),并将其计入可配置的 bucket(存储桶)中 ,后续可通过指定区间筛选样本,也可以统计样本总数,最后一般将数据展示为直方图。
Prometheus 取值间隔的划分采用的是累积区间间隔机制,即每个 bucket 中的样本均包含了其前面所有 bucket 中的样本,因而也称为累积直方图。
Histogram 类型的每个指标有一个基础指标名称 <basename>,它会提供多个时间序列:
●<basename>_sum :所有样本值的总和
●<basename>_count :总的采样次数,它自身本质上是一个 Counter 类型的指标
●<basename>_bucket{le="<上边界>"} :观测桶的上边界,即样本统计区间,表示样本值小于等于上边界的所有样本数量
<basename>_bucket{le="+Inf"} :最大区间(包含所有样本)的样本数量
#使用 histogram
在大多数情况下人们一般倾向于使用某些量化指标的平均值,例如 CPU 的平均使用率、页面的平均响应时间。这种方式的问题很明显,以系统 API 调用的平均响应时间为例:如果大多数 API 请求都维持在 100ms 的响应时间范围内,而个别请求的响应时间需要 5s,那么就会导致某些 Web 页面的响应时间落到中位数的情况,而这种现象被称为长尾问题。
为了区分是平均的慢还是长尾的慢,最简单的方式就是按照请求延迟的范围进行分组。例如,统计延迟在 0~10 ms 之间的请求数有多少,而 10~20 ms 之间的请求数又有多少。通过这种方式可以快速分析系统慢的原因。Histogram和Summary都是为了能够解决这样问题的存在,通过 Histogram 和 Summary 类型的监控指标,我们可以快速了解监控样本的分布情况。
http 请求响应时间 <= 0.005 秒 的请求次数为 10
prometheus_http_request_duration_seconds_bucket{handler="/metrics",le="0.005"} 10
所有样本值的大小总和,命名为 <basename>_sum
prometheus_http_request_duration_seconds_sum{handler="/metrics"} 10.107670803000001
样本总数,命名为 <basename>_count ,效果与 <basename>_bucket{le="+Inf"} 相同
prometheus_http_request_duration_seconds_count{handler="/metrics"} 20
注意:
bucket 可以理解为是对数据指标值域的一个划分,划分的依据应该基于数据值的分布。注意后面的样本是包含前面的样本,假设 prometheus_http_request_duration_seconds_bucket{...,le="0.01"} 的值为 10,而 prometheus_http_request_duration_seconds_bucket{...,le="0.05"} 的值为 30 ,那么意味着这 30 个样本中,有 10 个是小于 0.01s 的,其余 20 个采样点的响应时间是介于 0.01s 和 0.05s 之间的。
累积间隔机制生成的样本数据需要额外使用内置的 histogram_quantile 函数即可根据 Histogram 指标来计算相应的分位数(quantile),即某个 bucket 的样本数在所有样本数中占据的比例。
●histogram_quantile 函数在计算分位数时会假定每个区间内的样本满足线性分布状态,因而它的结果仅是一个预估值,并不完全准确
●预估的准确度取决于bucket区间划分的粒度;粒度越大,准确度越低
例如,假设 http 请求响应时间的样本的 9 分位数(quantile=0.9)的上边界为 0.01,即表示小于等于 0.01 的样本值的数量占总体样本值的 90%
histogram_quantile(prometheus_http_request_duration_seconds_bucket{handler="/metrics",le="0.01"}) 0.9
(4)Summary类型
Histogram 在客户端仅是简单的桶划分和分桶计数,分位数计算由 Prometheus Server 基于样本数据进行估算,因而其结果未必准确,甚至不合理的 bucket 划分会导致较大的误差。
Summary 是一种类似于 Histogram 的指标类型,但它在客户端于一段时间内(默认为 10 分钟)的每个采样点进行统计,计算并存储了分位数数值,Server 端直接抓取相应值即可。
对于每个指标,Summary 以指标名称 <basename> 为前缀,生成如下几个指标序列:
●<basename>_sum :统计所有样本值之和
●<basename>_count :统计所有样本总数
●<basename>{quantile="x"} :统计样本值的分位数分布情况,分位数范围:0 ≤ x ≤ 1
从上面的样本中可以得知当前Promtheus Server进行 wal_fsync 操作的总次数为 216 次,耗时 2.888716127000002s。 其中中位数(quantile=0.5)的耗时为 0.012352463s,9分位数(quantile=0.9)的耗时为0.014458005s。
(5)Histogram类型 与 Summary类型 的异同
它们都包含了 <basename>_sum 和 <basename>_count 指标,Histogram 需要通过 <basename>_bucket 来计算分位数,而 Summary 则直接存储了分位数的值。
3.PromQL的语法运用
3.1 基础查询
Prometheus的基础查询一般表达式格式为<metric name>{label=value},通过指标名称加标签的方式进行查询
(1)单指标空标签查询
eg:查询接收到的 HTTP 请求总数
prometheus_http_requests_total
或
prometheus_http_requests_total{}
注意:指标后面{}中的标签项只要有一个不同就代表着它是一个独立的时间序列。当指标和标签项都完全相同时才是一个时间序列
上面运用了指标或者指标加空标签的方式进行一个模糊查询,同时标签也可以进行一个模糊查询(标签必须以键值对的形式存在,如果仅仅只有键或者值是不符合PromQL的语法)
(2)单标签查询
eg:查看所有包含标签(instance="localhost:9090")的时间序列
(3)指标加标签组合缩小范围查询
eg:查看http请求总数(实例为本机,code返回码为302)
prometheus_http_requests_total{instance="localhost:9090",code="302"}
3.2 时间范围查询
(1)实时时间范围查询
在上述的基础查询案例中, 我们通过<metric name>{label=value}方式进行查询时,返回结果中只会包含该时间序列的最新一个值,这样的结果类型称为瞬时向量(instant vector )。除了瞬时向量,PromQL也支持返回时间序列在某个时间范围内的一组数据,这种称为范围向量(range vector )。
eg:查看http请求总数(实例为本机,code返回码为302),查看的时间范围为1分钟内
prometheus_http_requests_total{instance="localhost:9090",code="302"}[1m]
上图中我们就可以看到1分钟内的所有的采样结果
除了使用m表示分钟以外,PromQL的时间范围选择器支持其它时间单位:
s - 秒
m - 分钟
h - 小时
d - 天
w - 周
y - 年
注意:Prometheus不支持小数点数值范围的使用。比如查询一天半这样的时间段,并不会使用[1.5d]进行查询,而是使用[36h]
(2)时间位移查询
在时间序列的查询上,除了以当前时间为基准,也可以使用offset进行时间向前推移的操作。
eg:查看http请求总数(实例为本机,code返回码为302),要求是五分钟前(瞬时向量)
prometheus_http_requests_total{instance="localhost:9090",code="302"}offset 5m
eg: 查看http请求总数(实例为本机,code返回码为302),要求是五分钟前,并且时间范围为1分钟内
prometheus_http_requests_total{instance="localhost:9090",code="302"}[1m]offset 5m
3.3 聚合操作查询
PromQL语言提供了不少内置的聚合操作符,用于对瞬时向量的样本进行聚合操作 ,形成一个新的序列。目前支持的聚合操作符如下:
PromQL 中的聚合操作语法格式可采用如下面两种格式之一:
● <聚合函数>(向量表达式) by|without (标签)
● <聚合函数> by|without (标签) (向量表达式)
(1)计算所有http的请求数量总和
sum(prometheus_http_requests_total{})
(2)找到所有http的请求数量最大的时间序列
max(prometheus_http_requests_total{})
(3)求所有http的请求数量的平均值
avg(prometheus_http_requests_total{})
(4)使用topk ,显示value值排名前三的时间序列数据
topk(3,prometheus_http_requests_total{})
(5)使用bottomk,查看value值排名最后五位的时间序列
bottomk(5,prometheus_http_requests_total{})
除此之外:在聚合操作中,还可以在表达式中加上without或 by ,其中without用于在计算样本中移除列举的标签,而by正相反,结果向量中只保留列出的标签,其余标签则移除。
#计算除了标签中含有code,handler,job的时间序列,value值的和
sum(prometheus_http_requests_total{}) without (code,handler,job)
#只计算标签中含有code,handler,job的时间序列,value值的和
sum(prometheus_http_requests_total{}) by (instance)
3.4 通配符和正则表达式匹配标签查询
PromQL的查询语句中后面的标签往往会出现大量key相同但是value不同的标签,它们为两个不同的时间序列。通过value与key的关系可以快速定位时间序列并且key与value之间可以使用正则表达式 ,value值里的字段可以通过通配符进行模糊定义
PromQL常用的正则表达式:
(1)匹配标签中code不为200的时间序列
prometheus_http_requests_total{code!="200"}
2)匹配标签handler为以api为开头的时间序列
prometheus_http_requests_total{handler=~"/api.+"}
或
prometheus_http_requests_total{handler=~"/api.*"}
3.5 操作符的使用
(1)运算符
在PromQL的查询中,还可以通过表达式操作符,进行更加复杂的结果查询。其中数据运算符使用的加、减、乘、除等方式,对样本值进行计算,并返回计算后的结果。
PromQL支持的所有数学运算符如下所示:
+ (加法)
- (减法)
* (乘法)
/ (除法)
% (求余)
^ (幂运算)
eg:通过process_virtual_memory_bytes获取到的内存值单位为byte,我们希望转换为GB为单位时,只需要使用下列表达式处理。
process_virtual_memory_bytes/(1024*1024*1024)
2)比较运算符
比较运算符支持用户根据时间序列样本的值,对时间序列进行过滤。
Prometheus支持的比较运算符如下:
== (相等)
!= (不相等)
> (大于)
< (小于)
>= (大于等于)
<= (小于等于)
eg:我们只想查询Prometheus请求量大于1千的接口数据,则可以使用下列比较表达式进行过滤。
比较表达式还可以与bool修饰符进行搭配,添加bool后表达式将不再对数据进行过滤,而是根据比较结果返回1(true)或0(false)。
prometheus_http_requests_total>bool 1000
(3)逻辑运算符
逻辑运算符支持的操作有 and、or、unless(排除)三种,其中and为并集,用于匹配表达式中相同的结果。 unless与and正好相反,匹配结果将会排除两者中相同的样本,只显示其中对方互不包含部分的合集;而or的匹配范围最广,它除了会匹配表达式1所有的数据外,还会匹配表达式2中与其不相同的样本。
eg:http请求量在100到 1000之间的时间序列样本
prometheus_http_requests_total<1000 and prometheus_http_requests_total>100
http请求量在大于100或者小于1000之间的时间序列样本
prometheus_http_requests_total<1000 or prometheus_http_requests_total>100
注意:Prometheus 的运算符之间存在着优先级,其中由高到低依次为(^)> (*, /, %) > (+, -) > (==, !=, <=, <, >=, >) > (and, unless) > (or) ,在使用过程中需要注意优先级关系,避免出现错误结果。
3.6 内置函数
Prometheus内置不少函数,通过灵活的应用这些函数,可以更方便的查询及数据格式化.
ceil 函数
ceil函数会将返回结果的值向上取整数。
示例:
ceil(avg(prometheus_http_requests_total{code="200"}))
floor 函数
floor 函数与ceil相反,将会进行向下取整的操作。
示例:
floor(avg(prometheus_http_requests_total{code="200"}))
rate函数
rate函数是使用频率最高,也是最重要的函数之一。rate用于取某个时间区间内每秒的平均增量数,它会以该时间区间内的所有数据点进行统计。rate函数通常作用于Counter类型的指标,用于了解增量情况。
示例:获取http_request_total在2分钟内,平均每秒新增的请求数
rate(prometheus_http_requests_total{handler="/rules"}[1m])
irate函数
相比rate函数,irate提供了更高的灵敏度。irate函数是通过时间区间中最后两个样本数据来计算区间向量的增长速率,从而避免范围内的平均值拉低峰值的情况。
示例:该函数用法与rate相同
irate(prometheus_http_requests_total{handler="/rules"}[1m])
其它内置函数
除了上面提到的这些函数外,PromQL还提供了大量的其他函数供使用,功能范围涵盖了日常所需的功能,如用于标签替换的label_replace函数、统计Histogram指标分位数的histogram_quantile函数,更多信息可参阅官方文档
4.经典查询示例
(1)每台主机 CPU 在最近 5 分钟内的平均使用率
(1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)) * 100
(2)查询 1 分钟的 load average 的时间序列是否超过主机 CPU 数量 2 倍
node_load1 > on (instance) 2 * count (node_cpu_seconds_total{mode="idle"}) by (instance)
(3)计算主机内存使用率
可用内存空间:空闲内存、buffer、cache 指标之和
node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes
已用内存空间:总内存空间减去可用空间
node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)
使用率:已用空间除以总空间
(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) / node_memory_MemTotal_bytes * 100
4)计算所有 node 节点所有容器总计内存:
sum by (instance) (container_memory_usage_bytes{instance=~"node*"})/1024/1024/1024
(5)计算 node01 节点最近 1m 所有容器 cpu 使用率:
sum (rate(container_cpu_usage_seconds_total{instance="node01"}[1m])) / sum (machine_cpu_cores{instance="node01"}) * 100
#container_cpu_usage_seconds_total 代表容器占用CPU的时间总和
(6)计算最近 5m 每个容器 cpu 使用情况变化率
sum (rate(container_cpu_usage_seconds_total[5m])) by (container_name)
(7)查询 K8S 集群中最近 1m 每个 Pod 的 CPU 使用情况变化率
sum (rate(container_cpu_usage_seconds_total{image!="", pod_name!=""}[1m])) by (pod_name)
#由于查询到的数据都是容器相关的,所以最好按照 Pod 分组聚合