操作符
PromQL 支持一元、二元和聚合操作符。
一元操作符
PromQL 中唯一的一元操作符是 - (一元负号)。它可以应用于标量或瞬时向量。在前一种情况下,它返回一个符号相反的标量。在后一种情况下,它返回一个瞬时向量,其中每个元素的符号都相反。直方图样本的符号通过将所有桶的总体以及观测值的计数和总和的符号取反来取反。生成的直方图样本始终被视为 Gauge 类型的直方图。
注意任何具有负桶总体或负观测计数的直方图应仅用作中间结果。如果这样的负直方图是记录规则的最终结果,则规则评估将失败。负直方图不能由任何交换格式(暴露格式、远程写入、OTLP)表示,因此它们不能以任何方式被摄取到 Prometheus 中,只能由 PromQL 表达式创建。
二元操作符
二元操作符涵盖基本的逻辑和算术运算。对于两个瞬时向量之间的操作,可以修改匹配行为。
算术二元操作符
PromQL 中存在以下二元算术操作符
+(加)-(减)*(乘)/(除)%(模)^(幂/指数)
二元算术操作符定义在标量/标量、向量/标量和向量/向量值对之间。它们遵循常规的 IEEE 754 浮点算术 ,包括处理特殊值如 NaN、+Inf 和 -Inf。
在两个标量之间,行为很简单:它们计算为另一个标量,该标量是操作符应用于两个标量操作数的结果。
在瞬时向量和标量之间,操作符将应用于向量中每个数据样本的值。
如果数据样本是浮点数,则操作在该浮点数和标量之间执行。例如,如果一个浮点样本的瞬时向量乘以 2,结果是另一个浮点样本的向量,其中原始向量的每个样本值都乘以 2。
对于作为直方图样本的向量元素,行为如下:
-
对于
*,所有桶的总体以及观测值的计数和总和都乘以标量。如果标量为负,则生成的直方图被视为 Gauge 类型的直方图。否则,保留输入直方图样本的 Counter 或 Gauge 类型。 -
对于
/,直方图样本必须在左侧(LHS),标量在右侧(RHS)。然后,所有桶的总体以及观测值的计数和总和都除以标量。除以零会导致直方图没有常规桶,零桶总体以及观测值的计数和总和都设置为+Inf、-Inf或NaN,这取决于它们在输入直方图中的值(分别为正、负或零/NaN)。如果标量为负,则生成的直方图被视为 Gauge 类型的直方图。否则,保留输入直方图样本的 Counter 或 Gauge 类型。 -
对于左侧是标量、右侧是直方图样本的
/运算,以及标量和直方图样本的任何其他算术二元操作符的任何组合,都没有结果,相应的元素将从结果向量中移除。这种移除会通过信息级别的注释进行标记。
在两个瞬时向量之间,二元算术操作符应用于左侧向量中的每个条目及其在右侧向量中的匹配元素。结果传播到结果向量中,分组标签成为输出标签集。在右侧向量中找不到匹配条目的条目不属于结果的一部分。
如果两个浮点样本匹配,则算术操作符应用于两个输入值。
如果浮点样本与直方图样本匹配,其行为遵循与标量和直方图样本之间相同的逻辑(见上文),即 * 和 /(后者直方图样本在左侧)是有效操作,而所有其他操作都会导致相应元素从结果向量中移除。
如果两个直方图样本匹配,只有 + 和 - 是有效操作,分别对所有匹配的桶总体以及观测值的计数和总和进行加减。所有其他操作都会导致相应元素从输出向量中移除,并通过信息级别的注释进行标记。+ 和 - 操作通常只应应用于 Gauge 类型的直方图,但 PromQL 也允许它们用于 Counter 类型的直方图,以涵盖特定的使用场景,对于这些场景需要特别注意以避免计数器重置未对齐的问题。(PromQL 可以检测到某些计数器重置的不兼容性,并通过警告级别的注释进行标记。)两个 Counter 直方图相加的结果是一个 Counter 直方图。所有其他操作数组合以及所有减法操作的结果都是一个 Gauge 直方图。
在任何涉及向量的算术二元运算中,指标名称都会被丢弃。即使在 on 中明确提到了 __name__,也会发生这种情况(进一步讨论请参见 https://github.com/prometheus/prometheus/issues/16631 )。
对于任何可能导致负直方图的算术二元运算,请考虑上面的相关说明。
三角二元操作符
Prometheus 中存在以下以弧度为单位的三角二元操作符:
atan2(基于 https://pkg.go.dev/math#Atan2 )
三角操作符允许使用向量匹配在两个向量上执行三角函数,这在普通函数中是不可用的。它们的行为方式与算术操作符相同。它们只对浮点样本进行操作。涉及直方图样本的操作会导致相应向量元素从输出向量中移除,并通过信息级别的注释进行标记。
比较二元操作符
Prometheus 中存在以下二元比较操作符:
==(等于)!=(不等于)>(大于)<(小于)>=(大于或等于)<=(小于或等于)
比较操作符定义在标量/标量、向量/标量和向量/向量值对之间。默认情况下它们进行过滤。它们的行为可以通过在操作符后提供 bool 来修改,这将返回值 0 或 1,而不是进行过滤。
在两个标量之间,必须提供 bool 修饰符,这些操作符会产生另一个标量,值为 0 (false) 或 1 (true),具体取决于比较结果。
在瞬时向量和标量之间,这些操作符应用于向量中每个数据样本的值,比较结果为假的向量元素将从结果向量中删除。这些操作只适用于向量中的浮点样本。对于直方图样本,相应的元素将从结果向量中移除,并通过信息级别的注释进行标记。
在两个瞬时向量之间,这些操作符默认作为过滤器,应用于匹配的条目。表达式不为真或在表达式另一侧找不到匹配的向量元素将从结果中删除,而其他元素则传播到结果向量中,分组标签成为输出标签集。
两个浮点样本之间的匹配按常规方式进行。
浮点样本和直方图样本之间的匹配是无效的,相应的元素将从结果向量中移除,并通过信息级别的注释进行标记。
在两个直方图样本之间,== 和 != 的工作方式符合预期,但所有其他比较二元操作仍然无效。
在任何涉及向量的比较二元运算中,提供 bool 修饰符会以下列方式改变行为:
- 在表达式另一侧找到匹配但表达式为假的向量元素的值将变为
0,而找到匹配且表达式为真的向量元素的值将变为1。(请注意,没有匹配或涉及直方图样本的无效操作仍返回无结果,而不是值0。) - 指标名称将被丢弃。
如果未提供 bool 修饰符,则保留左侧的指标名称,但有以下例外:
- 如果使用
on,则指标名称将被丢弃。 - 如果使用
group_right,则保留右侧的指标名称,以避免冲突。
逻辑/集合二元操作符
这些逻辑/集合二元操作符仅在瞬时向量之间定义:
and(交集)or(并集)unless(补集)
vector1 and vector2 的结果是一个由 vector1 中元素组成的向量,这些元素在 vector2 中存在具有完全匹配标签集的元素。其他元素将被丢弃。指标名称和值从左侧向量继承。
vector1 or vector2 的结果是一个包含 vector1 所有原始元素(标签集 + 值)的向量,此外还包括 vector2 中所有在 vector1 中没有匹配标签集的元素。
vector1 unless vector2 的结果是一个由 vector1 中元素组成的向量,这些元素在 vector2 中不存在具有完全匹配标签集的元素。两个向量中所有匹配的元素都将被丢弃。
由于这些逻辑/集合二元操作符不与样本值交互,因此它们对浮点样本和直方图样本的作用方式相同。
向量匹配
向量之间的操作会尝试为左侧向量中的每个条目在右侧向量中找到一个匹配的元素。有两种基本的匹配行为:一对一和多对一/一对多。
向量匹配关键字
这些向量匹配关键字允许在具有不同标签集的时间序列之间进行匹配:
onignoring
提供给匹配关键字的标签列表将决定向量如何组合。示例可以在一对一向量匹配和多对一和一对多向量匹配中找到。
分组修饰符
这些分组修饰符启用了多对一/一对多向量匹配:
group_leftgroup_right
可以向分组修饰符提供标签列表,其中包含要包含在结果指标中的“一”侧的标签。
多对一和一对多匹配是高级用例,应仔细考虑。通常,正确使用 ignoring(<labels>) 会提供期望的结果。
分组修饰符只能用于比较和算术运算。像 and、unless 和 or 这样的运算默认与右侧向量中所有可能的条目进行匹配。
一对一向量匹配
一对一从操作的每一侧找到一个唯一的条目对。在默认情况下,这是一个遵循 vector1 <operator> vector2 格式的操作。如果两个条目具有完全相同的标签集和相应的值,则它们匹配。ignoring 关键字允许在匹配时忽略某些标签,而 on 关键字允许将考虑的标签集减少到提供的列表。
<vector expr> <bin-op> ignoring(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) <vector expr>
示例输入
method_code:http_errors:rate5m{method="get", code="500"} 24
method_code:http_errors:rate5m{method="get", code="404"} 30
method_code:http_errors:rate5m{method="put", code="501"} 3
method_code:http_errors:rate5m{method="post", code="500"} 6
method_code:http_errors:rate5m{method="post", code="404"} 21
method:http_requests:rate5m{method="get"} 600
method:http_requests:rate5m{method="del"} 34
method:http_requests:rate5m{method="post"} 120
示例查询
method_code:http_errors:rate5m{code="500"} / ignoring(code) method:http_requests:rate5m
这将返回一个结果向量,其中包含过去 5 分钟内每种方法的 HTTP 请求中状态码为 500 的请求的比例。如果没有 ignoring(code),就不会有匹配,因为指标不共享相同的标签集。put 和 del 方法的条目没有匹配项,因此不会出现在结果中。
{method="get"} 0.04 // 24 / 600
{method="post"} 0.05 // 6 / 120
多对一和一对多向量匹配
多对一和一对多匹配指的是“一”侧的每个向量元素可以与“多”侧的多个元素匹配的情况。这必须使用 group_left 或 group_right 修饰符明确请求,其中左/右决定哪个向量具有更高的基数。
<vector expr> <bin-op> ignoring(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> ignoring(<label list>) group_right(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_right(<label list>) <vector expr>
与分组修饰符一起提供的标签列表包含要包含在结果指标中的来自“一”侧的附加标签。对于 on,一个标签只能出现在一个列表中。结果向量的每个时间序列都必须是唯一可识别的。
示例查询
method_code:http_errors:rate5m / ignoring(code) group_left method:http_requests:rate5m
在这种情况下,左侧向量每个 method 标签值包含多个条目。因此,我们使用 group_left 来表示这一点。现在,右侧的元素与左侧具有相同 method 标签的多个元素匹配。
{method="get", code="500"} 0.04 // 24 / 600
{method="get", code="404"} 0.05 // 30 / 600
{method="post", code="500"} 0.05 // 6 / 120
{method="post", code="404"} 0.175 // 21 / 120
聚合操作符
Prometheus 支持以下内置聚合操作符,可用于聚合单个瞬时向量的元素,从而产生一个包含较少元素和聚合值的新向量:
-
sum(v)(计算维度上的总和) -
avg(v)(计算维度上的算术平均值) -
min(v)(选择维度上的最小值) -
max(v)(选择维度上的最大值) -
bottomk(k, v)(按样本值最小的k个元素) -
topk(k, v)(按样本值最大的k个元素) -
limitk(k, v)(采样k个元素,实验性功能,必须使用--enable-feature=promql-experimental-functions启用) -
limit_ratio(r, v)(采样一个伪随机比例r的元素,实验性功能,必须使用--enable-feature=promql-experimental-functions启用) -
group(v)(结果向量中的所有值均为 1) -
count(v)(计算向量中的元素数量) -
count_values(l, v)(计算具有相同值的元素数量) -
stddev(v)(计算维度上的总体标准差) -
stdvar(v)(计算维度上的总体标准方差) -
quantile(φ, v)(计算维度上的 φ-分位数 (0 ≤ φ ≤ 1))
这些操作符既可以用于聚合所有标签维度,也可以通过包含 without 或 by 子句来保留不同的维度。这些子句可以在表达式之前或之后使用。
<aggr-op> [without|by (<label list>)] ([parameter,] <vector expression>)
或
<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]
label list 是一个不带引号的标签列表,可以包含尾随逗号,即 (label1, label2) 和 (label1, label2,) 都是有效的语法。
without 从结果向量中移除列出的标签,而所有其他标签则保留在输出中。by 则相反,它会丢弃未在 by 子句中列出的标签,即使它们在向量的所有元素之间的标签值都相同。
详细解释
sum
sum(v) 对 v 中的样本值进行求和,其方式与 + 二元操作符在两个值之间的方式相同。
所有被聚合成单个结果向量元素的样本值必须要么是浮点样本,要么是直方图样本。混合两者的聚合是无效的,会导致相应的向量元素从输出向量中移除,并通过警告级别的注释进行标记。
示例
如果指标 memory_consumption_bytes 的时间序列按 application、instance 和 group 标签进行了细分,我们可以通过以下方式计算每个应用程序和组在所有实例上的总内存消耗:
sum without (instance) (memory_consumption_bytes)
这等同于:
sum by (application, group) (memory_consumption_bytes)
如果我们只对所有应用程序的总内存消耗感兴趣,我们可以简单地写:
sum(memory_consumption_bytes)
avg
avg(v) 将 v 的总和除以聚合样本的数量,其方式与 / 二元操作符相同。
所有被聚合成单个结果向量元素的样本值必须要么是浮点样本,要么是直方图样本。混合两者的聚合是无效的,会导致相应的向量元素从输出向量中移除,并通过警告级别的注释进行标记。
min 和 max
min(v) 和 max(v) 分别返回 v 中的最小值或最大值。
它们仅对浮点数样本进行操作,遵循 IEEE 754 浮点数算术规则,这特别意味着只有当所有聚合值都是 NaN 时,NaN 才会被视为最小值或最大值。输入向量中的直方图样本将被忽略,并通过信息级别的注释进行标记。
topk 和 bottomk
topk(k, v) 和 bottomk(k, v) 与其他聚合器的不同之处在于,它们会将输入样本中 k 个值的子集(包括原始标签)返回到结果向量中。
by 和 without 仅用于对输入向量进行分桶。
与 min 和 max 类似,它们仅对浮点数样本进行操作,将 NaN 值分别视为离顶部或底部最远的值。输入向量中的直方图样本将被忽略,并通过信息级别的注释进行标记。
如果在即时查询中使用,topk 和 bottomk 会分别按降序或升序返回值排序后的序列。如果与 by 或 without 一起使用,则每个分桶内的序列会按值排序,并且同一分桶中的序列会连续返回,但不保证序列的分桶会以任何特定顺序返回。
范围查询不适用任何排序。
示例
要获取所有实例中内存消耗最高的 5 个实例,我们可以这样写:
topk(5, memory_consumption_bytes)
limitk 和 limit_ratio
limitk(k, v) 返回 k 个输入样本的子集,结果向量中包含原始标签。
子集的选择方式是确定性的伪随机。这与样本类型无关。因此,它对浮点数样本和直方图样本都有效。
示例
要采样 10 个时间序列,我们可以这样写:
limitk(10, memory_consumption_bytes)
limit_ratio
limit_ratio(r, v) 返回输入样本的一个子集,结果向量中包含原始标签。
子集的选择方式是确定性的伪随机。这与样本类型无关。因此,它对浮点数样本和直方图样本都有效。
r 的取值范围在 +1 和 -1 之间。r 的绝对值用作选择比例,但对于负数 r,选择顺序会反转,这可用于选择补集。例如,limit_ratio(0.1, ...) 返回一个确定性的、约占输入样本 10% 的集合,而 limit_ratio(-0.9, ...) 则精确返回 limit_ratio(0.1, ...) 未返回的、剩余的约 90% 的输入样本。
group
对于在该时间戳包含任何值的每个分组,group(v) 返回 1。
该值可以是浮点数或直方图样本。
count
count(v) 返回该时间戳的值的数量,如果该时间戳没有任何值,则不返回任何值。
该值可以是浮点数或直方图样本。
count_values
count_values(l, v) 为 v 中的每个唯一样本值输出一个时间序列。每个序列都有一个由 l 给出的附加标签,该标签的值是唯一的样本值。每个时间序列的值是该样本值出现的次数。
count_values 对浮点数样本和直方图样本都有效。对于后者,直方图样本值的紧凑字符串表示将用作标签值。
示例
要计算运行每个构建版本的二进制文件数量,我们可以这样写:
count_values("version", build_version)
stddev
stddev(v) 返回 v 的标准差。
stddev 仅对浮点数样本有效,遵循 IEEE 754 浮点数算术规则。输入向量中的直方图样本将被忽略,并通过信息级别的注释进行标记。
stdvar
stdvar(v) 返回 v 的标准方差。
stdvar 仅对浮点数样本有效,遵循 IEEE 754 浮点数算术规则。输入向量中的直方图样本将被忽略,并通过信息级别的注释进行标记。
quantile
quantile(φ, v) 计算 φ-分位数,即在聚合维度上的 N 个指标值中,排名为 φ*N 的值。
quantile 仅对浮点数样本有效。输入向量中的直方图样本将被忽略,并通过信息级别的注释进行标记。
NaN 被视为可能的最小值。
例如,quantile(0.5, ...) 计算中位数,quantile(0.95, ...) 计算 95 百分位数。
特殊情况
- 当 φ =
NaN时,返回NaN。 - 当 φ < 0 时,返回
-Inf。 - 当 φ > 1 时,返回
+Inf。
二元运算符优先级
以下列表显示了 Prometheus 中二元运算符的优先级,从高到低排列。
^*、/、%、atan2+,-==,!=,<=,<,>=,>and、unless或
同一优先级的运算符是左结合的。例如,2 * 3 % 2 等价于 (2 * 3) % 2。但是 ^ 是右结合的,所以 2 ^ 3 ^ 2 等价于 2 ^ (3 ^ 2)。