add promQL

This commit is contained in:
baim0
2023-11-08 22:01:16 +08:00
parent 4c0eb8a35a
commit d20f09f953
22 changed files with 427 additions and 1 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

+427 -1
View File
@@ -712,4 +712,430 @@ sum without(mode) (rate(demo_cpu_usage_seconds_total{mode=~"user|system"}[5m]))
```shell ```shell
rate(demo_cpu_usage_seconds_total{mode="user"}[5m]) + ignoring(mode) rate(demo_cpu_usage_seconds_total{mode="user"}[5m]) + ignoring(mode)
rate(demo_cpu_usage_seconds_total{mode="system"}[5m]) rate(demo_cpu_usage_seconds_total{mode="system"}[5m])
``` ```
# 阈值
PromQL 通过提供一组过滤的二元运算符(`>`、`<`、`==` 等),允许根据其样本值过滤一组序列,这种过滤最常见的场景就是在报警规则中使用的阈值。比如我们想查找在过去 15 分钟内的 `status="500"` 错误率大于 20% 的所有 HTTP 路径,我们在 `rate` 表达式后面添加一个 `>0.2` 的过滤运算符:
```promql
rate(demo_api_request_duration_seconds_count{status="500",job="demo"}[15m]) > 0.2
```
这个查询只会将错误率大于 20% 的数据过滤出来。
![>20%](../img/20210604115346.png)
> 注意:由于在图形中的每个步长都是完全独立评估表达式的,因此根据每个步骤的过滤条件,某些比率会出现或消失(因此存在间隙)。 一般来说,二元过滤运算符在图形中并不常见,大多数在报警条件中出现,用来表示阈值。
这种过滤方式不仅适用于单个数字,PromQL 还允许你用一组时间序列过滤另一组序列。与上面的二元运算一样,比较运算符会自动应用于比较左侧和右侧具有相同标签集的序列之间。 `on() / ignoring()` 和 `group_left() / group_right()` 修饰符的作用也与我们前面学习的二元算术运算符一样。
以下示例是选择所有具有 500 错误率且至少比同一路径的总请求率大 50 倍的路径:
```promql
rate(demo_api_request_duration_seconds_count{status="500",job="demo"}[5m]) * 50
> ignoring(status)
sum without(status) (rate(demo_api_request_duration_seconds_count{job="demo"}[5m]))
```
不过需要注意的是我们必须忽略匹配中的 status 标签,因为在左边一直有这个标签,而右边没有这个标签。
![错误率](../img/20210604115543.png)
比如我们还可以计算 demo 演示服务实例在一小时内的预测磁盘使用量,但要过滤只有那些预测磁盘已满的实例。
```promql
predict_linear(demo_disk_usage_bytes{job="demo"}[1h], 3600) >= demo_disk_total_bytes{job="demo"}
```
Prometheus 支持以下过滤操作:
- `==`
- `!=`
- `<`
- `<=`
- `>`
- `>=`
有时你可能想知道比较运算符的结果而不实际删除任何输出系列。要实现这一点,我们可以向运算符添加一个 bool 修饰符来保留所有的序列,但是把输出样本值设置为 1(比较为真)或 0(比较为假)。
例如,要简单地显示一组数据中哪些请求率高于或低于 `0.2/s`,我们可以这样查询:
```promql
rate(demo_api_request_duration_seconds_count{job="demo"}[5m]) > bool 0.2
```
我们可以看到输入序列的结果为 0 或 1,把数字条件转换为了布尔输出值。
![bool修饰符](../img/20210604115043.png)
## 练习
1.构建一个查询,显示使用少于 20MB 内存的目标(`process_resident_memory_bytes` 指标)。
```promql
process_resident_memory_bytes / 1024^2 < 20
```
2.构建一个查询,显示 Prometheus 服务内部所有在过去 5 分钟内没有收到任何查询的 HTTP 处理器。
```promql
rate(prometheus_http_requests_total[5m]) == 0
```
# 集合操作
有的时候我们需要过滤或将一组时间序列与另一组时间序列进行合并,Prometheus 提供了 3 个在瞬时向量之间操作的集合运算符。
- `and(集合交集)`:比如对较高错误率触发报警,但是只有当对应的总错误率超过某个阈值的时候才会触发报警
- `or(集合并集)`:对序列进行并集计算
- `unless(除非)`:比如要对磁盘空间不足进行告警,除非它是只读文件系统。
![union](../img/20211007144914.png)
与算术和过滤二元运算符类似,这些集合运算符会尝试根据相同的标签集在左侧和右侧之间查找来匹配序列,除非你提供 `on()` 或 `ignoring()` 修饰符来指定应该如何找到匹配。
> 注意:与算术和过滤二进制运算符相比,集合运算符没有 `group_left()` 或 `group_right()` 修饰符,因为集合运算符总是进行多对多的匹配,也就是说,它们总是允许任何一边的匹配序列与另一边的多个序列相匹配。
对于 `and` 运算符,如果找到一个匹配的,左边的序列就会成为输出结果的一部分,如果右边没有匹配的序列,则不会输出任何结果。
例如我们想筛选出第 90 个百分位延迟高于 50ms 的所有 HTTP 端点,但只针对每秒收到多个请求的维度组合,查询方式如下所示:
```promql
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
```
![and 操作](../img/20210604151520.png)
有的时候我们也需要对两组时间序列进行合并操作,而不是交集,这个时候我们可以使用 `or` 集合运算符,产生的结果是运算符左侧的序列,加上来自右侧但左侧没有匹配标签集的时间序列。比如我们要列出所有低于 10 或者高于 30 的请求率,则可以用下面的表达式来查询:
```promql
rate(demo_api_request_duration_seconds_count{job="demo"}[5m]) < 10
or
rate(demo_api_request_duration_seconds_count{job="demo"}[5m]) > 30
```
![or 操作](../img/20210604152205.png)
我们可以看到在图中使用值过滤器和集合操作会导致时间序列在图中有断点现象,这取决于他们在图中的时间间隔下是否能够与过滤器进行匹配,所以一般情况下,我们建议只在告警规则中使用这种过滤操作。
还有一个 `unless` 操作符,它只会保留左边的时间序列,如果右边不存在相等的标签集合的话。
## 练习
1.构建一个查询,显示按 path、method、status(5 分钟内平均)划分的 demo API 请求的第 95 个百分位延迟,除非这个维度组合每秒收到的请求少于 1 个请求(5 分钟内平均)。
```promql
histogram_quantile(0.95, sum by(path, method, status, le) (rate(demo_api_request_duration_seconds_bucket[5m])))
unless
sum by(path, method, status) (rate(demo_api_request_duration_seconds_count[5m])) < 1
```
# 排序
本节我们将学习如何对查询结果进行排序,或者只选择一组序列中最大或最小的值。
我们可以使用 `sort()`(升序) 或者 `sort_desc()`(降序)函数来实现对输出结果进行排序,例如,要显示按值排序的每个路径请求率,从最高到最低,我们可以用下面的语句进行查询:
```promql
sort_desc(sum by(path) (rate(demo_api_request_duration_seconds_count{job="demo"}[5m])))
```
![排序结果](../img/20210604155545.png)
有的时候我们并不是对所有的时间序列感兴趣,只对最大或最小的几个序列感兴趣,我们可以使用 `topk()` 和 `bottomk()` 这两个运算符来操作,可以返回 `K` 个最大或最小的序列,比如只显示每个 path 和 method 的前三的请求率,我们可以使用下面的语句来查询。
```promql
topk(3, sum by(path, method) (rate(demo_api_request_duration_seconds_count{job="demo"}[5m])))
```
![topk查询](../img/20210604160206.png)
## 练习
1.构建一个查询以升序的方式显示所有 3 个 demo 服务的磁盘使用情况。
```promql
sort(demo_disk_usage_bytes)
```
2.构建一个查询,按 method、path 和 status 维度显示 3 个最低流量的 demo API 请求比率。
```promql
bottomk(3, sum by(method, path, status) (rate(demo_api_request_duration_seconds_count[5m])))
```
# 直方图
在这一节中,我们将学习直方图指标,了解如何根据这些指标来计算分位数。Prometheus 中的直方图指标允许一个服务记录一系列数值的分布。直方图通常用于跟踪**请求的延迟或响应大小**等指标值,当然理论上它是可以跟踪任何根据某种分布而产生波动数值的大小。Prometheus 直方图是在客户端对数据进行的采样,它们使用的一些可配置的(例如延迟)bucket 桶对观察到的值进行计数,然后将这些 bucket 作为单独的时间序列暴露出来。
下图是一个非累积直方图的例子:
![非累积直方图](../img/20210417140114.png)
在 Prometheus 内部,直方图被实现为一组时间序列,每个序列代表指定桶的计数(例如`10ms以下的请求数`、`25ms以下的请求数`、`50ms以下的请求数`等)。 在 Prometheus 中每个 bucket 桶的计数器是累加的,这意味着较大值的桶也包括所有低数值的桶的计数。在作为直方图一部分的每个时间序列上,相应的桶由特殊的 `le` 标签表示。`le` 代表的是**小于或等于**。
与上面相同的直方图在 Prometheus 中的累积直方图如下所示:
![Prometheus 直方图](../img/20210603145656.png)
可以看到在 Prometheus 中直方图的计数是累计的,这是很奇怪的,因为通常情况下非累积的直方图更容易理解。Prometheus 为什么要这么做呢?想象一下,如果直方图指标中加入了额外的标签,或者划分了更多的 bucket,那么样本数据的分析就会变得越来越复杂,如果直方图是累积的,在抓取指标时就可以根据需要丢弃某些 bucket,这样可以在降低 Prometheus 维护成本的同时,还可以粗略计算样本值的分位数。通过这种方法,用户不需要修改应用代码,便可以动态减少抓取到的样本数量。另外直方图还提供了 `_sum` 指标和 `_count` 指标,所以即使你丢弃了所有的 bucket,仍然可以通过这两个指标值来计算请求的平均响应时间。通过累积直方图的方式,还可以很轻松地计算某个 bucket 的样本数占所有样本数的比例。
我们在演示的 demo 服务中暴露了一个直方图指标 `demo_api_request_duration_seconds_bucket`,用于跟踪 API 请求时长的分布,由于这个直方图为每个跟踪的维度导出了 26 个 bucket,因此这个指标有很多时间序列。我们可以先来看下来自一个服务实例的一个请求维度组合的直方图,查询语句如下所示:
```promql
demo_api_request_duration_seconds_bucket{instance="demo-service-0:10000", method="POST", path="/api/bar", status="200", job="demo"}
```
正常我们可以看到 26 个序列,每个序列代表一个 bucket,由 `le` 标签标识:
![26个序列](../img/20210603164356.png)
直方图可以帮助我们了解这样的问题,比如`"我有多少个请求超过了100ms的时间?"` (当然需要直方图中配置了一个以 100ms 为边界的桶),又比如`"我99%的请求是在多少延迟下完成的?"`,这类数值被称为**百分位数**或**分位数**。在 Prometheus 中这两个术语几乎是可以通用,只是百分位数指定在 0-100 范围内,而分位数表示在 0 和 1 之间,所以第 99 个百分位数相当于目标分位数 0.99。
如果你的直方图桶粒度足够小,那么我们可以使用 `histogram_quantile(φ scalar, b instant-vector)` 函数用于计算历史数据指标一段时间内的分位数。该函数将目标分位数 (`0 ≤ φ ≤ 1`) 和直方图指标作为输入,就是大家平时讲的 `pxx`,`p50` 就是中位数,参数 `b` 一定是包含 `le` 这个标签的瞬时向量,不包含就无从计算分位数了,但是计算的分位数是一个预估值,并不完全准确,因为这个函数是假定每个区间内的样本分布是线性分布来计算结果值的,预估的准确度取决于 bucket 区间划分的粒度,粒度越大,准确度越低。
回到我们的演示服务,我们可以尝试计算所有维度在所有时间内的第 90 个百分位数,也就是 90% 的请求的持续时间。
```promql
# BAD!
histogram_quantile(0.9, demo_api_request_duration_seconds_bucket{job="demo"})
```
但是这个查询方式是有一点问题的,当单个服务实例重新启动时,bucket 的 Counter 计数器会被重置,而且我们常常想看看`现在`的延迟是多少(比如在过去 5 分钟内),而不是整个时间内的指标。我们可以使用 `rate()` 函数应用于底层直方图计数器来实现这一点,该函数会自动处理 Counter 重置,又可以只计算每个桶在指定时间窗口内的平均增长。
我们可以这样去计算过去 5 分钟内第 90 个百分位数的 API 延迟:
```promql
# GOOD!
histogram_quantile(0.9, rate(demo_api_request_duration_seconds_bucket{job="demo"}[5m]))
```
这个查询就好很多了。
![API 延迟](../img/20210603170845.png)
这个查询会显示每个维度(job、instance、path、method 和 status)的第 90 个百分点,但是我们可能对单独的这些维度并不感兴趣,想把他们中的一些指标聚合起来,这个时候我们可以在查询的时候使用 Prometheus 的 `sum` 运算符与 `histogram_quantile()` 函数结合起来,计算出聚合的百分位,假设在我们想要聚合的维度之间,直方图桶的配置方式相同(桶的数量相同,上限相同),我们可以将不同维度之间具有相同 `le` 标签值的桶加在一起,得到一个聚合直方图。然后,我们可以使用该聚合直方图作为 `histogram_quantile()` 函数的输入。
> 注意:这是假设直方图的桶在你要聚合的所有维度之间的配置是相同的,桶的配置也应该是相对静态的配置,不会一直变化,因为这会破坏你使用 `histogram_quantile()` 查看的时间范围内的结果。
下面的查询计算了第 90 个百分位数的延迟,但只按 job、instance 和 path 维度进行聚合结果:
![90百分位数](../img/20210603171255.png)
## 练习
1.构建一个查询,计算在 0.0001 秒内完成的 demo 服务 API 请求的总百分比,与过去 5 分钟内所有请求总数的平均值。
```promql
sum(rate(demo_api_request_duration_seconds_bucket{le="0.0001"}[5m]))
/
sum(rate(demo_api_request_duration_seconds_bucket{le="+Inf"}[5m])) * 100
```
或者可以使用下面的语句查询
```promql
sum(rate(demo_api_request_duration_seconds_bucket{le="0.0001"}[5m]))
/
sum(rate(demo_api_request_duration_seconds_count[5m])) * 100
```
2.构建一个查询,计算 demo 服务 API 请求的第 50 个百分位延迟,按 status code 和 method 进行划分,在过去一分钟的平均值。
```promql
histogram_quantile(0.5, sum by(status, method, le) (rate(demo_api_request_duration_seconds_bucket[1m])))
```
# 数据对比
有的时候我们可能需要去访问过去的数据,并和当前数据进行对比。例如,我们可能想比较今天的请求率和一周前的请求率之间的差异。我们可以在任何区间向量或瞬时向量选择器上附加一个偏移量 `offset<duration>` 的修饰符(比如 `my_metric offset 5m` 或者 `my_metric[1m] offset 7d`)。
让我们来看一个示例,在我们的 demo 服务中暴露了一个 Counter 指标 `demo_items_shipped_total`,该指标追踪物品的运输情况,用 5 分钟来模拟`"每日"`流量周期,所以我们不必等待一整天才能查看该时段的数据。
我们只使用第一个演示服务实例来测试即可,首先我们来看看它的速率:
```promql
rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m])
```
![对比](../img/20210604165153.png)
该服务还暴露了一个 `0` 或 `1` 的布尔指标,告诉我们现在是否是假期:
![holiday 指标](../img/20210604165455.png)
将假期与发货商品率进行比较,注意到节假日时它会减少!我们可以尝试将当前的发货速度与 7"天"(7 * 5 分钟)前的速度进行比较,看看是否有什么不正常的情况。
```promql
rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m])
/
rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m] offset 35m)
```
通常情况下,该比率约为 1,但当当天或前一天是假期时,我们得到的比率比正常情况下要略低或高。
![对比](../img/20210604170248.png)
但是,如果原因只是假期,我们想忽略这个较低或较高的比率。我们可以在过去或现在是假期的时候过滤掉这个比率,方法是附加一个 unless 集合操作符。
```promql
(
rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m])
/
rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m] offset 35m)
)
unless
(
demo_is_holiday == 1 # Is it currently a holiday?
or
demo_is_holiday offset 35m == 1 # Was it a holiday 7 "days" ago?
)
```
或者另外一种方法,我们只需要比较今天和一周前是否有相同的节日:
```promql
(
rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m])
/
rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m] offset 35m)
)
unless
(
demo_is_holiday
!=
demo_is_holiday offset 35m
)
```
这样我们就可以过滤掉当前时间有假期或过去有假期的结果。
![过滤假期](../img/20210604172821.png)
## 练习
1.构建一个查询,计算每个 path 路径的总请求率和 35 分钟前的差异。
```promql
sum by(path) (rate(demo_api_request_duration_seconds_count[5m])) -
sum by(path) (rate(demo_api_request_duration_seconds_count[5m] offset 35m))
```
# 检测
本节我们将学习如何来检查我们的实例数据抓取健康状况。
## 检查抓取实例
每当 Prometheus 抓取一个目标时,它都会存储一个合成的样本,其中包含指标名称 `up` 和被抓取实例的 `job` 和 `instance` 标签,如果抓取成功,则样本的值被设置为 1,如果抓取失败,则设置为 0,所以我们可以通过如下所示的查询来获取当前哪些实例处于正常或挂掉的状态:
```promql
up{job="demo"}
```
正常三个演示服务实例都处于正常状态,所以应该都为**1**。如果我们将第一个实例停掉,重新查询则第一个实例结果为**0**:
![up](../img/20210922110731.png)
如果只希望显示 `down` 掉的实例,可以通过过滤**0**值来获取:
```promql
up{job="demo"} == 0
```
![down](../img/20210922111123.png)
或者获取挂掉实例的总数:
```promql
count by(job) (up{job="demo"} == 0)
```
![count](../img/20210922111212.png)
一般情况下这种类型的查询会用于指标抓取健康状态报警。
> 注意:因为 `count()` 是一个聚合运算符,它期望有一组维度的时间序列作为其输入,并且可以根据 `by` 或 `without` 子句将输出序列分组。任何输出组只能基于现有的输入序列,如果根本没有输入序列,就不会产生输出。
## 检查序列数据
在某些情况下,只查看序列的样本值是不够的,有时还需要检测是否存在某些序列,上面我们用 `up{job="demo"} == 0` 语句来查询所有无法抓取的演示服务实例,但是只有已经被抓取的目标才会被加上 `up` 指标,如果 Prometheus 都没有抓取到任何的演示服务目标应该怎么办呢?比如它的抓取配置出问题了,服务发现可能返回也为空,或者由于 Prometheus 自身出了某些问题。
在这种情况下,`absent()` 函数就非常有用了,`absent()` 将一个瞬时向量作为其输入,当输入包含序列时,将返回一个空结果,不包含时将返回单个输出序列,而且样本值为**1**。
例如,查询语句 `absent(up{job="demo"})` 将得到一个空的输出结果,如果测试一个没有被抓取的 job 是否存在的时候,将得到样本值**1**。
![non-existent](../img/20210922114313.png)
这可以帮助我们检测序列是否存在的情况。此外还有一个 `absent()` 的变种,叫做 `absent_over_time()`,它接受一个区间向量,告诉你在该输入向量的整个时间范围内是否有样本。
> **练习:**
>
> 1.构建一个查询,检测指标 `demo_api_request_duration_seconds_count` 是否具有 `PUT` 的 method 标签的序列。
> ```promql
> absent(demo_api_request_duration_seconds_count{method="PUT"})
> ```
> 2.构建一个查询,当过去一小时内任务 `non-existent` 没有记录 up 指标时,该查询输出一个系列。
> ```promql
> absent_over_time(up{job="non-existent"}[1h])
> ```