一、PromQL介绍
1、PromQL 是 Prometheus 监控系统内置的一种查询语言,PromQL 允许以灵活的方式选择、聚合等其他方式转换和计算时间序列数据,该语言仅用于读取数据
2、当 Prometheus 从系统和服务收集指标数据时,会把数据存储在内置的时序数据库(TSDB)中,要对收集到的数据进行任何处理,可以使用 PromQL 从 TSDB 中读取数据,同时可以对所选的数据执行过滤、聚合以及其他转换操作
PromQL 的执行可以通过两种方式来触发:
- 在 Prometheus 服务器中,记录规则和警报规则会定期运行,并执行查询操作来计算规则结果(例如触发报警)。该执行在 Prometheus 服务内部进行,并在配置规则时自动发生。
- 外部用户和 UI 界面可以使用 Prometheus 服务提供的 HTTP API 来执行 PromQL 查询。这就是仪表盘软件(例如 Grafana、PromLens 以及 Prometheus 内置 Web UI)访问 PromQL 的方式。
二、指标类型
从存储上来讲所有的监控指标都是相同的,但是在不同的场景下这些指标又有一些细微的差异。为了能够帮助用户理解和区分这些不同监控指标之间的差异,Prometheus 定义了 4 种不同的指标类型:
- Counter(计数器)
- Gauge(仪表盘)
- Summary(摘要)
- Histogram(直方图)
1、Counter(计数器): Counter (只增不减的计数器) 类型的指标其工作方式和计数器一样,只增不减,所以它对于存储诸如服务的 HTTP 请求数量或使用的 CPU 时间之类的信息非常有用。常见的监控指标,如 http_requests_total、node_cpu_seconds_total 都是 Counter 类型的监控指标。
通常情况对于 Counter 指标我们都是去查看变化率而不是本身的数字。PromQL 内置的聚合操作和函数可以让用户对这些数据进行进一步的分析,例如,通过 rate() 函数获取 HTTP 请求的增长率:
rate(http_requests_total[5m])
2、Gauge(仪表盘): Gauge(可增可减的仪表盘)类型的指标侧重于反应系统的当前状态,因此这类指标的样本数据可增可减,Gauge 类型的监控指标,通过 PromQL 内置函数 delta() 可以获取样本在一段时间范围内的变化情况。例如,计算 CPU 温度在两个小时内的差异:
delta(cpu_temp_celsius{host="common"}[2h])
还可以直接使用 predict_linear() 对数据的变化趋势进行预测。例如,预测系统磁盘空间在 4 个小时之后的剩余情况
predict_linear(node_filesystem_free_bytes[1h], 4 * 3600)
3、Summary(摘要): 主用用于统计和分析样本的分布情况,摘要用于记录某些东西的平均大小,可能是计算所需的时间或处理的文件大小,摘要显示两个相关的信息:count(事件发生的次数)和 sum(所有事件的总大小)。 例如,指标 prometheus_tsdb_wal_fsync_duration_seconds 的指标类型为 Summary,它记录了 Prometheus Server 中 wal_fsync 的处理时间,通过访问 Prometheus Server 的 /metrics 地址,可以获取到监控样本数据
4、 Histogram(直方图): 主用用于统计和分析样本的分布情况,摘要非常有用,但是平均值会隐藏一些细节,与 Summary 类型的指标相似之处在于 Histogram 类型的样本同样会反应当前指标的记录的总数(以 _count 作为后缀)以及其值的总量(以 _sum 作为后缀)。不同在于 Histogram 指标直接反应了在不同区间内样本的个数,区间通过标签 le 进行定义。在 Prometheus Server 自身返回的样本数据中,我们也能找到类型为 Histogram 的监控指标:prometheus_tsdb_compaction_chunk_range_seconds_bucket
三、部署演示服务
1、部署go环境
wget https://go.dev/dl/go1.20.7.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xf go1.20.7.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
export GOPROXY=https://goproxy.cn
2、下载代码(此应用程序为 Go语言开发用于为PromQL 训练提供每种类型的可预测综合指标,而不是“现实”指标。)
git clone https://github.com/duanshuaixing/prometheus_demo_service.git
env GOOS=linux GOARCH=amd64 go build -o prometheus_demo_service
3、启动服务
nohup ./prometheus_demo_service --listen-address=:10000 &
nohup ./prometheus_demo_service --listen-address=:10001 &
nohup ./prometheus_demo_service --listen-address=:10002 &
请求
curl http://localhost:10000/metrics
curl http://localhost:10001/metrics
curl http://localhost:10002/metrics
4、注册到Prometheus,添加demo这个job
cat prometheus.yml
global:
scrape_interval: 5s # 抓取频率
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
- job_name: "demo"
static_configs:
- targets: ["localhost:10000","localhost:10001","localhost:10002"]
5、演示服务介绍
该演示服务模拟了一些用于我们测试的监控指标,包括:
暴露请求计数和响应时间(以 path、method 和响应状态码为标签 key)的 HTTP API 服务
一个定期的批处理任务,它暴露了最后一次成功运行的时间戳和处理的字节数
有关 CPU 数量及其使用情况的综合指标
有关内存使用情况的综合指标
有关磁盘总大小及其使用情况的综合指标
其他指标......
四、PromQL基础
1、嵌套结构:与 SQL 查询语言(SELECT * FROM …)不同,PromQL 是一种嵌套的函数式语言,需要查找的数据描述成一组嵌套的表达式,每个表达式都会评估为一个中间值,每个中间值都会被用作它上层表达式中的参数,而查询的最外层表达式表示你可以在表格、图形中看到的最终返回值。比如下面的查询语句:
histogram_quantile( # 查询的根,最终结果表示一个近似分位数。
0.9, # histogram_quantile() 的第一个参数,分位数的目标值
# histogram_quantile() 的第二个参数,聚合的直方图
sum by(le, method, path) (
# sum() 的参数,直方图过去5分钟每秒增量。
rate(
# rate() 的参数,过去5分钟的原始直方图序列
demo_api_request_duration_seconds_bucket{job="demo"}[5m]
)
)
)
PromQL 表达式不仅仅是整个查询,而是查询的任何嵌套部分(比如上面的rate(…)部分),你可以把它作为一个查询本身来运行。在上面的例子中,每行注释代表一个表达式。
2、结果类型: 在查询 Prometheus 时,有两个 类型 的概念经常出现,区分它们很重要
- 抓取目标报告的指标类型:counter、gauge、histogram、summary。
- PromQL 表达式的结果数据类型:字符串、标量、瞬时向量或区间向量。
PromQL 实际上没有直接的指标类型的概念,只关注表达式的结果类型。每个 PromQL 表达式都有一个类型,每个函数、运算符或其他类型的操作都要求其参数是某种表达式类型。例如,rate() 函数要求它的参数是一个区间向量,但是 rate() 本身评估为一个瞬时向量输出,所以 rate() 的结果只能用在期望是瞬时向量的地方。
PromQL 中可能的表达式类型包括:
-
string(字符串):字符串只会作为某些函数(如 label_join() 和 label_replace())的参数出现。
-
scalar(标量):一个单一的数字值,如 1.234,这些数字可以作为某些函数的参数,如 histogram_quantile(0.9, …) 或 topk(3, …),也会出现在算术运算中。
-
instant vector(瞬时向量):一组标记的时间序列,每个序列有一个样本,都在同一个时间戳,瞬时向量可以由 TSDB 时间序列选择器直接产生,如node_cpu_seconds_total,也可以由任何函数或其他转换来获取。
-
range vector(区间向量):一组标记的时间序列,每个序列都有一个随时间变化的样本范围。在 PromQL 中只有两种方法可以生成区间向量:在查询中使用字面区间向量选择器(如 node_cpu_seconds_total[5m]),或使用子查询表达式(如 [5m:10s]),当想要在指定的时间窗口内聚合一个序列的行为时,区间向量非常有用,就像 rate(node_cpu_seconds_total[5m]) 计算每秒增加率一样,在 node_cpu_seconds_total 指标的最后 5 分钟内求平均值
3、查询类型和评估时间: PromQL 查询中对时间的引用只有相对引用,比如 [5m],表示过去 5 分钟,那么如何指定一个绝对的时间范围,或在一个表格中显示查询结果的时间戳?在 PromQL 中,这样的时间参数是与表达式分开发送到 Prometheus 查询 API 的,确切的时间参数取决于你发送的查询类型,Prometheus 有两种类型的 PromQL 查询:瞬时查询和区间查询
1>瞬时查询: 瞬时查询用于类似表格的视图,你想在一个时间点上显示 PromQL 查询的结果。一个瞬时查询有以下参数:
- PromQL 表达式
- 一个评估的时间戳
2> 区间查询: 区间查询主要用于图形,想在一个指定的时间范围内显示一个 PromQL 表达式,范围查询的工作方式与即时查询完全相同,这些查询在指定时间范围的评估步长中进行评估。当然,这在后台是高度优化的,在这种情况下,Prometheus 实际上并没有运行许多独立的即时查询
区间查询包括以下一些参数:
- PromQL 表达式
- 开始时间
- 结束时间
- 评估步长
在开始时间和结束时间之间的每个评估步长上评估表达式后,单独评估的时间片被拼接到一个单一的区间向量中。区间查询允许传入瞬时向量类型或标量类型的表达式,但始终返回一个范围向量(标量或瞬时向量在一个时间范围内被评估的结果)
五、选择时间序列
1、过滤指标名称
最简单的 PromQL 查询就是直接选择具有指定指标名称的序列,例如,以下查询将返回所有具有指标名称 demo_api_request_duration_seconds_count 的序列:
demo_api_request_duration_seconds_count
该查询将返回许多具有相同指标名称的序列,但有不同的标签组合 instance、job、method、path 和 status 等。输出结果如下所示
2、根据标签过滤
如果我们只查询 demo_api_request_duration_seconds_count 中具有 method=“GET” 标签的那些指标序列,则可以在指标名称后用大括号加上这个过滤条件:
demo_api_request_duration_seconds_count{method="GET"}
此外我们还可以使用逗号来组合多个标签匹配器:
demo_api_request_duration_seconds_count{method="GET",job="demo"}
3、需要注意的是组合使用多个匹配条件的时候,是过滤所有条件都满足的时间序列。
除了相等匹配之外,Prometheus 还支持其他几种匹配器类型
!=:不等于
=~:正则表达式匹配
!~:正则表达式不匹配
甚至我们还可以完全省略指标名称,比如直接查询所有 path 标签以 /api 开头的所有序列:
demo_api_request_duration_seconds_count{path=~"/api.*"}
注意: Prometheus 中的正则表达式总是针对完整的字符串而不是部分字符串匹配。因此,在匹配任何以 /api 开通的路径时,不需要以 ^ 开头,但需要在结尾处添加 .*,这样可以匹配 path=“/api” 这样的序列
4、查询最近 5 分钟的可用内存,可以执行下面的查询语句:
demo_memory_usage_bytes{type="free"}[5m]
可以使用的有效的时间单位为:
- ms -毫秒
- s -秒
- m - 分钟
- h - 小时
- d - 天
- y - 年
5、有时我们还需要以时移方式访问过去的数据,通常用来与当前数据进行比较。要将过去的数据时移到当前位置,可以使用 offset 修饰符添加到任何范围或即时序列选择器进行查询(例如 my_metric offset 5m 或 my_metric[1m] offset 7d)。
例如,要选择一个小时前的可用内存,可以使用下面的查询语句:
demo_memory_usage_bytes{type="free"} offset 1h
六、变化率
1、rate: 用于计算变化率的最常见函数是 rate(),rate() 函数用于计算在指定时间范围内计数器平均每秒的增加量。因为是计算一个时间范围内的平均值,所以需要在序列选择器之后添加一个范围选择器,绘制的图形看起来显然更加有意义了,进行 rate 计算的时候是选择指定时间范围下的第一和最后一个样本进行计算
rate(demo_api_request_duration_seconds_count[5m])
对于 rate() 和相关函数有几个需要说明的:
- 当被抓取指标进的程重启时,Counter 指标可能会重置为 0,但 rate() 函数会自动处理这个问题,它会假设 Counter 指标的值只要是减少了就认为是被重置了,然后它可以调整后续的样本,例如,如果时间序列的值为[5,10,4,6],则将其视为[5,10,14,16]。
- 变化率是从指定的时间范围下包含的样本进行计算的,需要注意的是这个时间窗口的边界并不一定就是一个样本数据,可能会不完全对齐,所以,即使对于每次都是增加整数的 Counter,也可能计算结果是非整数。
另外我们需要注意当把 rate() 与一个聚合运算符(例如 sum())或一个随时间聚合的函数(任何以 _over_time 结尾的函数)结合起来使用时,总是先取用 rate() 函数,然后再进行聚合,否则,当你的目标重新启动时,rate() 函数无法检测到 Counter 的重置。
- 注意:rate() 函数需要在指定窗口下至少有两个样本才能计算输出。一般来说,比较好的做法是选择范围窗口大小至少是抓取间隔的4倍,这样即使在遇到窗口对齐或抓取故障时也有可以使用的样本进行计算,例如,对于 1 分钟的抓取间隔,你可以使用 4 分钟的 Rate 计算,但是通常将其四舍五入为 5 分钟。所以如果使用 query_range 区间查询,例如在绘图中,那么范围应该至少是步长的大小,否则会丢失一些数据。
2、irate: irate 用于计算区间向量的计算率,但是其反应出的是瞬时增长率;由于使用 rate 或者 increase 函数去计算样本的平均增长速率,容易陷入长尾问题当中,其无法反应在时间窗口内样本数据的突发变化。为了解决该问题,PromQL 提供了另外一个灵敏度更高的函数irate(v range-vector)。irate 同样用于计算区间向量的计算率,但是其反应出的是瞬时增长率。
3、increase: 函数查询指定时间范围内的总增量,它基本上相当于速率乘以时间范围选择器中的秒数
increase(demo_api_request_duration_seconds_count{job="demo"}[1h])
4、deriv: 函数可以计算一个区间向量中各个时间序列二阶导数,使用简单线性回归,deriv(v range-vector) 的参数是一个区间向量,返回一个瞬时向量,这个函数一般只用在 Gauge 类型的时间序列上。例如,要计算在 15 分钟的窗口下,每秒钟磁盘使用量上升或下降了多少
predict_linear(demo_disk_usage_bytes{job="demo"}[15m], 3600)
七、聚合
1、基于标签聚合: 例如查看demo 服务每秒处理的请求数的instance 和 path 的变化率,可以在 sum() 聚合器中添加一个 without() 的修饰符
sum without(instance,path) (rate(demo_api_request_duration_seconds_count{job="demo"}[5m]))
这里的分组概念和 SQL 语句中的分组去聚合就非常类似了。
除了 sum() 之外,Prometheus 还支持下面的这些聚合器:
- sum():对聚合分组中的所有值进行求和
- min():获取一个聚合分组中最小值
- max():获取一个聚合分组中最大值
- avg():计算聚合分组中所有值的平均值
- stddev():计算聚合分组中所有数值的标准差
- stdvar():计算聚合分组中所有数值的标准方差
- count():计算聚合分组中所有序列的总数
- count_values():计算具有相同样本值的元素数量
- bottomk(k, …):计算按样本值计算的最小的 k 个元素
- topk(k,…):计算最大的 k 个元素的样本值
- quantile(φ,…):计算维度上的 φ-分位数(0≤φ≤1)
- group(…):只是按标签分组,并将样本值设为 1。
示例:
1>按job分组,计算监控的所有进程的总内存使用量(process_resident_memory_bytes 指标)
sum by(job)(process_resident_memory_bytes)
2>计算 demo_cpu_usage_seconds_total 指标有多少不同的 CPU 模式
count(group by(mode) (demo_cpu_usage_seconds_total))
3>计算每个 job 任务和指标名称的时间序列数量
count by (job,__name__) ({__name__ != ""})
2、基于时间聚合: 基于时间来计算每个序列聚合,PromQL 提供了一些与标签聚合运算符类似的函数,但是在这些函数名前面附加了 _over_time()
- 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>查询 demo 实例中使用的 goroutine 的原始数量,可以使用查询语句 go_goroutines{job=“demo”},这会产生一些尖锐的峰值图,可以通过对图中的每一个点来计算 10 分钟内的 goroutines 数量进行平均来使图形更加平滑:
sum_over_time(go_goroutines{job="demo"}[10m])
2>查询 1 小时内内存的使用率则可以用下面的查询语句:
100 * (1 - ((avg_over_time(node_memory_MemFree_bytes[1h]) + avg_over_time(node_memory_Cached_bytes[1h]) + avg_over_time(node_memory_Buffers_bytes[1h])) / avg_over_time(node_memory_MemTotal_bytes[1h])))
3、子查询: 上面所有的 _over_time() 函数都需要一个范围向量作为输入,通常情况下只能由一个区间向量选择器来产生,比如 my_metric[5m]。但想使用例如 max_over_time() 函数来找出过去一天中 demo 服务的最大请求率, Prometheus 是支持子查询的,以指定的步长在一段时间内执行内部查询,根据子查询的结果计算外部查询。子查询的表示方式类似于区间向量的持续时间,需要冒号后添加了一个额外的步长参数:[:],写法如下:
max_over_time(
rate(
demo_api_request_duration_seconds_count{job="demo"}[5m]
)[1d:15s] # 在1天内明确地评估内部查询,步长为15秒
)
也可以省略冒号后的步长,在这种情况下,Prometheus 会使用配置的全局 evaluation_interval 参数进行评估内部表达式,这样就可以得到过去一天中 demo 服务最大的 5 分钟请求率,不过冒号仍然是需要的,以明确表示运行子查询。子查询还允许添加一个偏移修饰符 offset 来对内部查询进行时间偏移,类似于瞬时和区间向量选择器。
但是也需要注意长时间计算子查询代价也是非常昂贵的,我们可以使用记录规则(后续会讲解)预先记录中间的表达式,而不是每次运行外部查询时都实时计算它。
max_over_time(
rate(
demo_api_request_duration_seconds_count{job="demo"}[5m]
)[1d]
)
示例:
1>输出过去一小时内 demo 服务的最大 95 分位数延迟值(1 分钟内平均),按 path 划分
max_over_time(
histogram_quantile(0.95, sum by(le, path) (
rate(demo_api_request_duration_seconds_bucket[1m])
)
)[1h:]
)
八、运算(Prometheus 的查询语言支持基本的逻辑运算和算术运算)
1、算术运算符:
1>在 Prometheus 系统中支持下面的二元算术运算符:
+ 加法
- 减法
* 乘法
/ 除法
% 模
^ 幂等
最简单的我们可以将一个数字计算当做一个 PromQL 语句,用于标量与标量之间计算,比如:
(2 + 3 / 6) * 2^2
2>二元运算同样适用于向量和标量之间,例如我们可以将一个字节数除以两次 1024 来转换为 MiB,如下查询语句:
demo_batch_last_run_processed_bytes{job="demo"} / 1024 / 1024
3>PromQL 的一个强大功能就是可以让我们在向量与向量之间进行二元运算。
例如 demo_api_request_duration_seconds_sum 的数据包含了在 path、method、status 等不同维度上花费的总时间,指标 demo_api_request_duration_seconds_count 包含了上面同维度下的请求总次数。则我们可以用下面的语句来查询过去 5 分钟的平均请求持续时间:
rate(demo_api_request_duration_seconds_sum{job="demo"}[5m])
/
rate(demo_api_request_duration_seconds_count{job="demo"}[5m])
2、向量匹配
1> 一对一
计算的时候可以使用 on 或者 ignoring 修饰符来指定用于匹配的标签进行计算,由于示例中两边的标签都具有 color 标签,所以在进行计算的时候我们可以基于该标签(on (color))或者忽略其他的标签(ignoring (size))进行计算,这样得到的结果就是所以匹配的标签序列相加的结果,要注意结果中的标签也是匹配的标签
2>一对多与多对一
多对一和一对多两种匹配模式指的是一侧的每一个向量元素可以与多侧的多个元素匹配的情况,在这种情况下,必须使用 group 修饰符:group_left 或者 group_right 来确定哪一个向量具有更高的基数(充当多的角色)。多对一和一对多两种模式一定是出现在操作符两侧表达式返回的向量标签不一致的情况,因此同样需要使用 ignoring 和 on 修饰符来排除或者限定匹配的标签列表
九、阈值
1、介绍:PromQL 通过提供一组过滤的二元运算符(>、<、== 等),允许根据其样本值过滤一组序列,这种过滤最常见的场景就是在报警规则中使用的阈值,PromQL 还允许用一组时间序列过滤另一组序列。与二元运算一样,比较运算符会自动应用于比较左侧和右侧具有相同标签集的序列之间。 on() / ignoring() 和 group_left() / group_right() 修饰符的作用也与二元算术运算符一样
2、Prometheus 支持以下过滤操作:
==
!=
<
<=
>
>=
3、示例:服务实例在一小时内的预测磁盘使用量,但要过滤只有那些预测磁盘已满的实例
predict_linear(demo_disk_usage_bytes{job="demo"}[1h], 3600) >= demo_disk_total_bytes{job="demo"}
十、集合操作
要过滤或将一组时间序列与另一组时间序列进行合并,Prometheus 提供了 3 个在瞬时向量之间操作的集合运算符。
- and(集合交集):比如对较高错误率触发报警,但是只有当对应的总错误率超过某个阈值的时候才会触发报警
- or(集合并集):对序列进行并集计算
- unless(除非):比如要对磁盘空间不足进行告警,除非它是只读文件系统
与算术和过滤二元运算符类似,这些集合运算符会尝试根据相同的标签集在左侧和右侧之间查找来匹配序列,除非提供 on() 或 ignoring() 修饰符来指定应该如何找到匹配。
注意:与算术和过滤二进制运算符相比,集合运算符没有 group_left() 或 group_right() 修饰符,因为集合运算符总是进行多对多的匹配,也就是说,它们总是允许任何一边的匹配序列与另一边的多个序列相匹配。
对于 and 运算符,如果找到一个匹配的,左边的序列就会成为输出结果的一部分,如果右边没有匹配的序列,则不会输出任何结果。
- 1、例如我们想筛选出第 90 个百分位延迟高于 50ms 的所有 HTTP 端点,但只针对每秒收到多个请求的维度组合,查询方式如下所示:
histogram_quantile(0.9, rate(demo_api_request_duration_seconds_bucket{job="demo"}[5m])) > 0.05
and
rate(demo_api_request_duration_seconds_count{job="demo"}[5m]) > 1
- 2、有的时候我们也需要对两组时间序列进行合并操作,而不是交集,这个时候我们可以使用 or 集合运算符,产生的结果是运算符左侧的序列,加上来自右侧但左侧没有匹配标签集的时间序列。比如我们要列出所有低于 10 或者高于 30 的请求率,则可以用下面的表达式来查询
rate(demo_api_request_duration_seconds_count{job="demo"}[5m]) < 10
or
rate(demo_api_request_duration_seconds_count{job="demo"}[5m]) > 30
十一、排序
1、可以使用 sort()(升序) 或者 sort_desc()(降序)函数来实现对输出结果进行排序,例如,要显示按值排序的每个路径请求率,从最高到最低,我们可以用下面的语句进行查询:
sort_desc(sum by(path) (rate(demo_api_request_duration_seconds_count{job="demo"}[5m])))
2、最大最小排序
最大或最小的几个序列排序使用 topk() 和 bottomk() 这两个运算符来操作,可以返回 K 个最大或最小的序列,比如只显示每个 path 和 method 的前三的请求率,可以使用下面的语句来查询。
topk(3, sum by(path, method) (rate(demo_api_request_duration_seconds_count{job="demo"}[5m])))
十二、直方图
1、Prometheus 中的直方图指标允许一个服务记录一系列数值的分布。直方图通常用于跟踪请求的延迟或响应大小等指标值,在 Prometheus 内部,直方图被实现为一组时间序列,每个序列代表指定桶的计数(例如10ms以下的请求数、25ms以下的请求数、50ms以下的请求数等)。 在 Prometheus 中每个 bucket 桶的计数器是累加的,这意味着较大值的桶也包括所有低数值的桶的计数。在作为直方图一部分的每个时间序列上,相应的桶由特殊的 le 标签表示。le 代表的是小于或等于
2、计算过去 5 分钟内第 90 个百分位数的 API 延迟
histogram_quantile(0.9, rate(demo_api_request_duration_seconds_bucket{job="demo"}[5m]))
3、聚合步骤2的一些查询指标,可以在查询的时候使用 Prometheus 的 sum 运算符与 histogram_quantile() 函数结合起来,计算出聚合的百分位,比如:聚合的维度之间,直方图桶的配置方式相同(桶的数量相同,上限相同)。可以将不同维度之间具有相同 le 标签值的桶加在一起,得到一个聚合直方图。然后使用该聚合直方图作为 histogram_quantile() 函数的输入
注意:这是假设直方图的桶在要聚合的所有维度之间的配置是相同的,桶的配置也应该是相对静态的配置,不会一直变化,因为这会破坏使用 histogram_quantile() 查看的时间范围内的结果
histogram_quantile(0.9,sum without (status,method) (rate(demo_api_request_duration_seconds_bucket{job="demo"}[5m])))
4、构建一个查询,计算在 0.0001 秒内完成的 demo 服务 API 请求的总百分比,与过去 5 分钟内所有请求总数的平均值
sum(rate(demo_api_request_duration_seconds_bucket{le="0.0001"}[5m]))
/
sum(rate(demo_api_request_duration_seconds_bucket{le="+Inf"}[5m])) * 100
或者
sum(rate(demo_api_request_duration_seconds_bucket{le="0.0001"}[5m]))
/
sum(rate(demo_api_request_duration_seconds_count[5m])) *
5、构建一个查询,计算 demo 服务 API 请求的第 50 个百分位延迟,按 status code 和 method 进行划分,在过去一分钟的平均值
histogram_quantile(0.5, sum by(status, method, le) (rate(demo_api_request_duration_seconds_bucket[1m])))
十三、数据对比
1、有的时候可能需要去访问过去的数据,并和当前数据进行对比。例如,比较今天的请求率和一周前的请求率之间的差异。可以在任何区间向量或瞬时向量选择器上附加一个偏移量 offset 的修饰符(比如 my_metric offset 5m 或者 my_metric[1m] offset 7d)
在 demo 服务中暴露了一个 Counter 指标 demo_items_shipped_total,该指标追踪物品的运输情况,用 5 分钟来模拟"每日"流量周期
rate(demo_items_shipped_total{instance="localhost:10000"}[1m])
该服务还暴露了一个 0 或 1 的布尔指标,告诉我们现在是否是假期
demo_is_holiday{instance="localhost:10000"}
将假期与发货商品率进行比较,注意到节假日时它会减少!可以尝试将当前的发货速度与 7"天"(7 * 5 分钟)前的速度进行比较,看看是否有什么不正常的情况。该比率约为 1,但当当天或前一天是假期时,得到的比率比正常情况下要略低或高
rate(demo_items_shipped_total{instance="localhost:10000"}[1m])/rate(demo_items_shipped_total{instance="localhost:10000"}[1m] offset 35m)
如果原因只是假期,想忽略这个较低或较高的比率。可以在过去或现在是假期的时候过滤掉这个比率,方法是附加一个 unless 集合操作符
(rate(demo_items_shipped_total{instance="localhost:10000"}[1m])/rate(demo_items_shipped_total{instance="localhost:10000"}[1m] offset 35m))
/
(demo_is_holiday == 1 # Is it currently a holiday?
or
demo_is_holiday offset 35m == 1 # Was it a holiday 7 "days" ago?
)
或者另外一种方法,只需要比较今天和一周前是否有相同的节日, 这样就可以过滤掉当前时间有假期或过去有假期的结果
(
rate(demo_items_shipped_total{instance="localhost:10000"}[1m])
/
rate(demo_items_shipped_total{instance="localhost:10000"}[1m] offset 35m)
)
unless
(
demo_is_holiday
!=
demo_is_holiday offset 35m
)
2、构建一个查询,计算每个 path 路径的总请求率和 35 分钟前的差异
sum by(path) (rate(demo_api_request_duration_seconds_count[5m])) -
sum by(path) (rate(demo_api_request_duration_seconds_count[5m] offset 35m))
十四、检测
1、检查抓取实例
每当 Prometheus 抓取一个目标时,它都会存储一个合成的样本,其中包含指标名称 up 和被抓取实例的 job 和 instance 标签,如果抓取成功,则样本的值被设置为 1,如果抓取失败,则设置为 0,可以通过如下所示的查询来获取当前哪些实例处于正常或挂掉的状态:
up{job="demo"}
正常三个演示服务实例都处于正常状态,所以应该都为1。如果我们将第一个实例停掉,重新查询则第一个实例结果为0,如果只希望显示 down 掉的实例,可以通过过滤0值来获取
up{job="demo"} == 0
或者获取挂掉实例的总数
count by(job)(up{job="demo"} == 0)
一般情况下这种类型的查询会用于指标抓取健康状态报警。
注意:因为 count() 是一个聚合运算符,它期望有一组维度的时间序列作为其输入,并且可以根据 by 或 without 子句将输出序列分组。任何输出组只能基于现有的输入序列,如果根本没有输入序列,就不会产生输出
2、检查序列数据
某些情况下,只查看序列的样本值是不够的,有时还需要检测是否存在某些序列,上面我们用 up{job=“demo”} == 0 语句来查询所有无法抓取的演示服务实例,但是只有已经被抓取的目标才会被加上 up 指标,如果 Prometheus 都没有抓取到任何的演示服务目标应该怎么办呢?比如它的抓取配置出问题了,服务发现可能返回也为空,或者由于 Prometheus 自身出了某些问题。
在这种情况下,absent() 函数就非常有用了,absent() 将一个瞬时向量作为其输入,当输入包含序列时,将返回一个空结果,不包含时将返回单个输出序列,而且样本值为1。
例如,查询语句 absent(up{job=“demo”}) 将得到一个空的输出结果,如果测试一个没有被抓取的 job 是否存在的时候,将得到样本值1
这可以帮助我们检测序列是否存在的情况。此外还有一个 absent() 的变种,叫做 absent_over_time(),它接受一个区间向量,告诉你在该输入向量的整个时间范围内是否有样本。
3、构建一个查询,检测指标 demo_api_request_duration_seconds_count 是否具有 PUT 的 method 标签的序列
absent(demo_api_request_duration_seconds_count{method="PUT"})
4、构建一个查询,当过去一小时内任务 non-existent 没有记录 up 指标时,该查询输出一个系列
absent_over_time(up{job="non-existent"}[1h])