正确检测普罗米修斯计数指标的变化

时间:2019-09-09 18:06:07

标签: prometheus promql

我试图编写一个PromQL查询来检测计数指标的变化。

我的抓取间隔是15秒。

我这样查询指标:

http_server_requests_seconds_count{outcome!="REDIRECTION",outcome!="SUCCESS"}

它显示了所有http_server_requests未重定向且未成功的数量。

我尝试使用此指标编写警报表达式的过程如下:

sum by(service, method, outcome, status, uri) (
  rate(
    http_server_requests_seconds_count{
      outcome!="REDIRECTION",
      outcome!="SUCCESS"
    }[1m]
  )
) * 60

我的想法是,[1m]的速率乘以60秒将在发生更改时为1,但据我所知我得到了2

这些图形清楚地表明了这一点:

Prometheus graph

最上面的图是求和表达式,最下面的图是服务器请求计数的变化。当最下面的图计数为+1时,最上面的图也应暂时上升到1(但实际上它上升到2)。

我在做什么错?我误会了吗?发生更改时,如何编写一个值为1的查询?我应该期望能够编写这样的查询吗?

谢谢!

2 个答案:

答案 0 :(得分:1)

这是因为Prometheus优先考虑范围超出精度的一致定义。即它始终将范围定义为落入(包括)区间[now()-range,now()]内的所有样本。此定义对于量规来说是很有意义的:如果要计算时间范围等于步长的avg_over_time(),则希望每个输入样本都包括在一个输出样本的计算中。

但是对于计数器却不是这样。在等于步长的时间范围内,一个输入值(即两个连续采样之间的增加)基本上被丢弃。 (有关更多详细信息,请参见Prometheus问题#37463806。)为弥补所丢弃的数据,Prometheus使用外推法调整了计算结果。

意思是,如果(按照您的情况)您使用的时间范围是抓取间隔的2倍(1m抓取间隔的30s范围),则Prometheus将(平均)在每个样本中找到2个样本范围,但是这两个样本所覆盖的实际时间范围大约为30s。因此,Prometheus会将值加倍,从而有助于将速率推断到请求的1m。因此,结果2是预期的1,而不是预期的1。您还将注意到,由于连续样本之间的某些增加被丢弃了(即使没有样本),所以rate()图中并未显示计数器中的所有增加。 (即rate()中没有对应于第三个计数器增加的跳动。如果在不同的时间刷新,则会出现不同的增加并消失。Grafana通过始终将请求范围与步长对齐来“解决”了后者)始终缺少相同的增长。)

Prometheus开发人员建议的解决方案是计算更长持续时间的费率。但是,所有要做的就是减少错误(在3倍范围内得到1.5,在4倍范围内得到1.33,在5倍范围内得到1.25,等等),永远不要摆脱它。平滑增加计数器可以很好地隐藏Prometheus的外推法,但像您自己的计数器一样,拇指酸痛而突出,很少增加。)

此问题的唯一解决方法(未解决Prometheus,我已经为其提交了PR,并维护a fork)是对Prometheus对rate()的实现进行反向工程。即假设抓取间隔为30s,则类似rate(foo[1m])的表达式应替换为:

rate(foo[90s]) * 60 / 90

或更笼统(请注意,方括号内的表达式必须是时间文字,不能是计算值)

rate(foo[intended_range + scrape_interval]) * intended_range / (intended_range + scrape_interval)

之所以可行,是因为intended_range + scrape_interval范围将为您提供足够的样本,以覆盖intended_range所需要的数量。但是随后您必须撤消普罗米修斯外推法所带来的变化,从而随之而来的乘法和除法。这是一个丑陋的骇客,它取决于您知道抓取间隔并将其硬编码为录制规则和/或Grafana查询。

请注意,无论使用哪种方法,您都可能无法获得正好为1的值。由于服务,网络和内部Prometheus延迟,样本通常不会在毫秒内对齐,因此每秒的增长率将略低于或略高于预期值。

答案 1 :(得分:0)

这里有一个计算计数器指标正确变化的替代方法:

max_over_time(http_server_requests_seconds_count{outcome!="REDIRECTION",outcome!="SUCCESS"}[1m])-min_over_time(http_server_requests_seconds_count{outcome!="REDIRECTION",outcome!="SUCCESS"}[1m])

我花了一段时间才弄清楚的另一件事是,在绘制上述内容时,需要确保绘图的分辨率不大于刮取间隔。否则,您希望看到的部分或全部峰值将不会显示。