为什么 Grafana 的图表和实际监控数据不一样 —— 步长是如何计算的

起因

在做云原生相关监控场景时,Prometheus 和 Grafana 是对应的事实标准,因此不可避免的是测试环节和用户使用时都会将相关图表与 Grafana 对比,或是与自己用 PromQL 查询后绘制的图表或报警信息对比。

在这样的情况下往往会出现一个问题,即产品绘制的图表与 Grafana 结果不一致,或是 Grafana 与 PromQL 查询绘制后的图表又有出入(一个图表有抖动甚至间隔,另一个图表则很平稳),又或是图表状态与告警情况不一致(已经触发报警但图表上并没有超出阈值到警戒时间)。

monitor-step-overview

如上,不同端点得到的图表有时会有差别(云服务监控、Grafana、Prometheus Browser),虽然看起来有不小的区别,但他们实际上都是正确的。

这样的不一致经常会让人感到困惑,尤其是在平台提供监控和 Grafana 不一致时可能会有对准确性的质疑、又或者告警状态与图表不一致让人无法判断真实性等问题。下面记录一下这些问题的原因,并尝试减少这种不一致(由于机制问题这是无法完全解决的,只能从设计上缓解)。

最后说明 Grafana 是如何计算 Step(步长)提供给 Prometheus 得到 Data Frames 的。另外提及笔者的 PR,通过补充对字段的说明以减少误解。

其中部分内容有一些主观理解,可能会有错误,如有纰漏欢迎指正。

为什么会出现不一致

首先需要明确一点,Grafana 或是 Prometheus 本身的机制就导致了他们呈现的结果和实际值不是完全一致的,这是设计上的取舍,并且这种不一致作为可观测监控系统来说,是可以接受的(并不像日志要求严格的一致性),监控指标的目的还是发现问题,具体的排障流程还是需要留给日志和链路追踪。

因此对于一个短暂出现的波谷没有看到,或是数据飙升过程中有一些浮动被显示平滑,这些并不影响总体的趋势,与实际情况适量的差距是可以被接受的。当然这也不代表监控就无法观察到毛刺等现象,当时间区间够小(比如 5min 时),步长较小因此情况就会越接近实际情况(但依然不等于实际状态)。反而如果监控真的追求尽可能接近真实情况,那将会导致大量的采样点和数据存储,并且在绘制图表时巨量的数据点也很难呈现出可观测的图表,这是综合取舍下的结果。

Prometheus 采样机制

不一致的源头来源于采样,采样就会导致失真。Prometheus 本身按照 scrape_interval 配置定期从 exporter 或其他来源抓取数据,这样就会出现:在 0s、5s、10s 分别抓取,但在 3s 时有一个短暂的抖动,那么数据并不会反应出来,如果是 15s 等更长的抓取间隔,那有很多中间过程的细节就会被平滑掉(和常见的聚合有区别:将 10 个数据点聚合为 5 个会经过中间数据差值等处理,但抓取间隔就是中间状态彻底丢失了)。

之后我们往往会通过 Grafana 观察 Prometheus 抓取的数据,这时候 Grafana 发出的 Query 会携带两个参数,一个是时间范围(Time Range),另一个就是步长(也就是返回的数据点期望相间隔多久)。在这个过程中又会进一步失真,因为 Grafana 需求的步长可能会超过 Prometheus 抓取间隔,因此就会失去一些数据点的细节,而如果步长小于数据点间隔,则会出现多个时间点数据一样(找到了同一个采样点)的情况。

对于上图中的三个端点,就是采取了三种步长,因此得到的可视化结果不一致,在实际产品中一般会根据时间区间和图表大小计算相对合适的步长。下图通过列表进一步解释(数据为 Mock,不代表真实情况):

monitor-step-table

此处默认了步长为抓取间隔的倍数,如果抓取间隔为 10s 而步长为 15s,则可能出现部分数据点向前索引为同一个采样点导致出现直线的情况。

还有一些出现数据断点的情况,可能由于步长太小,且存在一部分数据点缺失。而向前索引的范围有最大值,有一些数据点没有能够找到最近的采样点,此时需要调整步长变大。步长变大后需要处理的数据就变少,更容易遇到最近的采样点,缓解数据断点问题。

Grafana 查询步长

上文中提到 Grafana 在 Query 时会给 Prometheus 一个步长,那么这个步长是如何得到的呢?直接根据图表选择的时间范围计算,还是说有什么其他的规则?

实际上 Grafana 计算步长的流程还是比较复杂的,受多个因素影响,并且参数在 Grafana 编辑面板中也支持调节,不过相关参数功能的描述和他们具体如何参与到步长计算中并不清晰,在后文中也有提及。在本节中省略探索过程,直接解释 Grafana 的步长计算流程以及相关参数是如何映射到面板编辑表单中的。

首先让我们先明确两个个基本概念:

  • Time Range:时间区间,图表的起点和终点的时间范围
  • Max Data Point:数据量,我倾向于把他单纯理解为分母

因此步长有个朴素的想法,我们想要可视化这么多(Max Data Point)个数据点,并且在一个时间区间中,那么每个数据点需要的时间间隔(Step)则为 Time Range / Max Data Point。

monitor-query-options

如图所示为 Grafana 中编辑查询的部分,Max Data Point 概念是一致的,Interval 则为我们上文一直提到的 Step,即步长。最大数据点很难直观的得到,因此 Grafana 中的处理为将该值设为面板宽度像素(这里指浏览器中的 px 值)。

monitor-data-point

虽然 Interval 右边也表明了计算方法,但我们实际计算一下,当前 Time Range 为 1h,即 3600s,3600 / 1178 ≈ 3.05s,但实际显示为 2s,似乎与描述不符。这实际上是因为 Grafana 在具体计算时会与一系列经验值取近似,比如当 a <= interval < b 时实际步长为 a,在下面源码分析过程中会具体描述规则。因此实际上 Interval = round(Time Range / Max Data Point)。在当前 Grafana 版本中,如果计算值小于 3.5s,则会取 2s,因此得到上述结果。

同时需要注意的还有选项中的 Min Interval,最后步长会和这个值取最大值,我们进一步得到:

$$Step = Max(Min\space interval, round(Time\space Range / Max\space Data \space Point))$$

后两个选项 Relative time 和 Time shift 则是控制 Time Range 的,Relative time 用来覆盖时间选择器以修改 Time range。Time shift 则是用来增加偏移量,修改 range 范围。

注意,placeholder 中显示为 1h,但他们的实际默认值为:Relative time 不设置则为空,Time shift 不设置则为 0。


对于每个独立的查询,实际上还有一个 Resolution 配置(不要与 Prometheus 的步长混淆):

monitor-grafana-res

这个选项在较新的 Grafana 版本中已被废弃,但由于向前兼容,因此导入之前的图表,还是会显示这一选项,如果将 Resolution 改为 1/1(即默认值)则选项会直接消失。

简单解释一下这里 Resolution 的作用,把之前计算得到的 Step 乘以 1 / Resolution 得到最终结果。这个设计最初的目的是减少请求过多对后端的负担,但缺点是总会放大最终的步长,不受其他限制。现在 Grafana 也有一个新的提案,引入对 Max Data Point 面板宽度的修正比率 Rate,即分母会变为 Max Data Point * Rate。

举一个例子,Time range 过小,因此计算后 Step 为 Min interval 的值 15s,之前的处理会永远放大这个步长,而在提案的算法中则不会。详见 Pull Request #51687 · grafana/grafana

探索计算过程

该段落是具体分析源码的过程,如何计算可以直接参考上文内容,不感兴趣可以直接跳过。

Grafana 由于迭代时间较久且代码逻辑比较复杂,容易找不到对应功能点或找错,可以搭建本地开发环境,通过输出来判断是否经过该段代码。

首先通过一些尝试找到计算查询的入口函数 getIntervals[1]

ray-so-getintervals

其中三个参数,range 比较好理解,low limit 对应前端的 Min interval,resolution 对应 Max data point(内部实现命名较混乱,注意对应)。兜底判断可以忽略,关键是调用 rangeUtil 的 calculateInterval[2]

ray-so-rangeutils

源代码中注释替换为文字解释,与上文公式类似,比较好理解。最后剩下 roundInterval[3] 具体是如何近似的:

ray-so-round

可以看到,Grafana 定制了一系列步长,将计算的结果向下取值,得到最终结果。

Grafana 步长相关问题

该段落主要解释了当前关于步长相关内容可能导致的误解,如果不感兴趣可以直接跳过。

实际上当前 Grafana 编辑面板中调节 Query 参数相关的表单是比较令人困惑的,无论是参数实际的作用还是命名,具体到 Grafana 代码中这种不一致就更加严重,因此笔者在理解 Grafana 具体规则时花了不少时间。

命名混淆

在之前的源码分析过程中,可以看到不同函数对相关变量的命名就有区别,同时和前端显示的提示标签也有区别,因此容易混淆这之间的关系。

另外,Resolution 这一命名也可能会混淆,首先很难直观的理解他在步长计算中的作用,另外分辨率可能更容易想象成表格像素(Max Data Point)类似的意义。

monitor-prom-res

在 Prometheus 默认的 Browser 中,图中 Resolution 则是我们上文中讨论的 Step 的实际作用,要注意区别。

查询面板提示

在查询面板中,虽然可以调整 Step 的相关参数,但是缺少对选项的解释,同时部分描述也容易被误解。因此笔者在 Pull Request #63138 · grafana/grafana 中尝试增加更多的说明。

具体来说,主要做了两点改动:

  • 调整了部分 placeholder,增加了更多气泡弹出说明
  • 调整了关于 interval 计算的描述,强调除法得到的并不是最后取值

最终效果如下所示:

monitor-pr-1 monitor-pr-2

在 PR 的讨论中,原先还对 Relative time 和 Time shift 的 placeholder 修改为其默认值(分别为 No override0h),但最后还是决定修改为原先的示例值(如 1h),并在气泡弹出中增强描述提示。

如何规避不一致

如果自己有搭建监控可视化的需求,个人觉得比较好的实践是将查询和步长计算规则尽可能接近 Grafana,并提供手动修改 Max Data Point(或其他影响因子)的入口。毕竟解释原因是一回事,但如果可视化结果和事实标准 Grafana 出入较大,肯定还是会遇到不必要的麻烦,虽然 Grafana 有时候其实也不能反应真实情况,但自实现接近业界标准,也算是一种最佳实践。

对于其他情况导致的不一致,如监控报警和图表对应不上,则需要在答疑或说明文档中给予提示,避免误解。

其他情况

在开头还提到了自己通过 PromQL 绘制图表以及监控和告警结果不一致的情况,自己 PromQL 绘制有出入的情况比较好理解,因为时间范围和步长选择都不一样,得到的可视化结果肯定也是有差异的。

而关于监控和告警不一致的问题已经有博客描述的非常清晰了[4]。我在这里也做简要说明,实际上也是步长的问题,Grafana 得到的步长是自己的计算逻辑,而告警引擎判断时间范围也是用自己逻辑内的步长。因此就会出现 Grafana 观测到了一些小于阈值的数据点,从图表上看并没有超出时间范围。但告警引擎没有看到这样的数据点,得到满足时间条件的结果从而触发告警的情况。也有可能反过来,图表显示已经数据异常超出时间范围,但系统迟迟不告警。

总结

对于这样的问题还是需要去接受这种不一致,并适当调节步长或时间范围,如果监控系统指标设计符合预期,那抓取和查询步长的损耗并不影响保障可用性的目的。

在主流的监控系统中,一般都会对 Step 设定一套计算规则,因此不同端点实际得到的图表往往会有区别,这是正常的,也可以调节图表中的 Query 参数以定制自己的需求。

在一些成熟的产品中会细化 Step 的定制过程。以 Grafana 为例,将 Step 拆解为多个输入和系统内部值组合得到的结果。而 Prometheus Browser 中则直接提供了简单的输入框调整 Step。

因此具体如何处理 Step,是否允许用户调整,以何种心智模型调整取决于产品的定位和用户需求。在产品中有可视化需求时,Step 计算可以参考 Grafana 等产品,这样可以保持绝大部分情况趋势一致,减少与其他环节的沟通成本。