本文翻译自:PromLabs | Blog - How Exactly Does PromQL Calculate Rates?
PromQL到底如何计算rate()的?
Counter是prometheus的metric type,Counter的值只增不减,它代表的是“累加的总数量”,例如“共处理了多少个请求”,“处理请求耗时总数”。Counter的值依赖于 暴漏Counter指标的进程的 启动时间,Counter的绝对值几乎总是无用的,使用Counter画图或做任何事情之前,一般你总是需要使用rate(), irate(), increase()来包装Counter从而来看Counter递增的“有多快”。
这三个函数的确切行为经常是困惑的源泉,所以,本文我们来详细看下,每个函数为了给出最终函数值是如何处理Counter reset&数据外推(extrapolation)的。
本文只讨论rate(), irate(), increase()这三个Counter相关的函数。如果你想了解gauge类型的increase或decrease,你需要使用delta()或deriv()。
rate(), irate(), increase()这三个函数都是用来描述Counter增长速率的。其不同点的概要介绍如下
1. rate()
计算每一秒增长率(per-second),是所提供tw内的平均值。举例:
rate(http_requests_total[5m])
在5min的tw内,每一秒的平均增长率。它很常用,它产生的是平滑的per-second的 增长率。
2. irate()
它也是per-second增长率,是瞬时增长率。它只使用所提供tw内的最后两个采样,忽略掉前面的所有采样。举例:
irate(http_requests_total[5m])
在5min的tw内,只使用最后的两个采样,计算这两个采样点之间的per-second增长率。它反映增长率的瞬时变化,变化较快,它的图像比rate()有更多的尖刺。
3. increase()
除了不是per-second的,它和rate()完全一样(exactly equivalent to rate())。它的返回值的单位是 per-provided-time-window。举例:
increase(http_requests_total[5m])
在5min的tw内,所处理的http请求增长了多少个。所以,
increase(foo[5m]) / (5 * 60) 和 rate(foo[5m]) 完全一致。
以上三个函数都要求,在所提供的tw内,至少要有2个采样。如果某个time series,在所提供的tw内少于两个采样,则,该返回结果会直接剔除掉该time series。
给定 tw 和 落在该窗口内的采样,如何来计算增长率 这其实是 权衡和不完美近似的问题(a matter of tradeoffs and imperfect approximations)。
prometheus计算增长率的方式是:给定了tw,且,只能提供 有限个采样的情况下,提供 平均最正确的答案。我们来看prometheus是如何计算的。
人们最常感到困惑的是rate()和increase()的数据外推行为。即使该Counter增长时只按整数增长,increase()也可能返回非整数,例如2.5883。原因在于increase()尝试近似在tw内的增长值。但,现实中,在所提供的tw内,不能100%对齐到 tw的开始和结束点。因此,increase()和rate()在tw内的第一个和最后一个采样点的斜率到tw的边界点时,外推到tw的边界点,这样得到一个值,平均最贴近 整个tw所期待的增长,即在tw边界点处最精确的采样值。
下图展示了使用rate()的例子,tw 是 1min,step是15s,在整个tw内Counter递增只发生了一次
如你所见,结果是基于 tw内的 第一个和最后一个采样的斜率,然后,基于这个斜率推断tw边界点(window boundaries)的数据值。当然,当存在Counter reset时就不是这种情况了。
注意:对于这种数据外推的行为,是有列外的:当某个时序看起来 开始和结束于所提供的tw,我们并不想在时序终止的方向上外推的太远。那么,在tw内,数据外推时如何决定开始和结束的点?rate()和increase()是这么做的
第一个(或最后一个)采样 距离 左(或右)tw边界点 >1.1 * (tw内采样的平均间隔),则,就不再数据外推啦。在那种情况下,数据外推仅仅向tw边界点探出0.5个(tw内采样的平均间隔)。同样,它们还会避免数据外推到负数,最多只外推到0。
irate()只关心最后两个采样的值,因此,irate()不会做任何的数据外推。
Counter是单调递增的,但,当target重启时Counter就重置成0啦。不能针对Counter reset算出来负的增长率,Counter相关的函数有处理Counter reset的逻辑。当遍历tw内的采样时,函数会检查每一个采样值是否比前者变小,如果变小,则就是Counter reset。Counter reset时,会假设Counter总是从0开始的,函数所作的就是 将 变小的这个当前值 加到前一个值上从而得到新的当前值。
下图展示了rate()是如何处理tw内的Counter reset的。你可以想象为rate()根据“真实”采样常见了一些“虚拟”采样,最终增长率就是根据“虚拟”采样来计算的,如果Counter reset从未发生过一样。
注意:当发生Counter reset时,有可能prometheus最后拉取后,Counter又增加了,尚未发生再次拉取就发生了Counter reset,此时,这个递增值就永远丢失了。
举个例子:
prometheus最后拉取到的值是50,然后,Counter又递增后变成了 53,然后Counter reset发生了,于是,(53-50)这个递增值就永远的丢失了。
为了把影响降到最低,Counter reset应该很不频繁,远远小于prometheus拉取的频率。
PromQL中各种Counter相关函数的内部工作原理和其实际输出值是比较令人困惑的。本文阐述了rate(), irate(), increase()是如何处理步长/Counter reset/数据外推的,希望它能让你更清楚的了解这些微妙的行为。