#查询解析器 #prometheus #pest-parser #解析器 #pest #foo-bar #静态分析

prometheus-parser

一个用于解析和验证 Prometheus 查询表达式的 Rust 库

4 个版本

0.4.2 2021 年 12 月 9 日
0.4.0 2020 年 3 月 26 日

#415 in 解析器实现

Download history 187/week @ 2024-03-11 167/week @ 2024-03-18 176/week @ 2024-03-25 188/week @ 2024-04-01 195/week @ 2024-04-08 309/week @ 2024-04-15 184/week @ 2024-04-22 166/week @ 2024-04-29 146/week @ 2024-05-06 368/week @ 2024-05-13 277/week @ 2024-05-20 152/week @ 2024-05-27 146/week @ 2024-06-03 100/week @ 2024-06-10 24/week @ 2024-06-17 53/week @ 2024-06-24

334 个月下载量

MIT 许可证

72KB
1.5K SLoC

prometheus-parser-rs

CircleCI docs.rs

prometheus-parser 将任意 Prometheus 表达式解析为适合语法检查、代码风格检查和通用静态分析的语法树。它不依赖于 Prometheus 服务器,完全离线工作。

这个库是用纯 Rust(使用 pest)实现的,不了解底层 Prometheus 服务器实现。它已通过公共 Prometheus 语法文档和各种公开可用的警报集合(例如 kubernetes-mixin)进行验证。

请注意,这个软件包并不试图 评估 Prometheus 表达式;考虑使用 promtool 的单元测试 来实现此目的。

使用场景

这个库可以用于实现自定义代码风格检查和静态分析工具,例如

  • 离线语法检查,提供有用的错误信息(多亏了 pest
  • 自动代码格式化
  • 各种抽象语法树(AST)检查
    • 确保表达式不会丢弃必要的标签,例如警报路由(命名空间、服务等)
    • 确保表达式不会犯导致虚假警报的常见错误,例如计数器的除零错误

局限性

  • 它根本不尝试评估表达式
  • 它不验证函数调用或参数类型,所以 foo(bar, baz, qux) 至少在 语法上 是有效的。不过,每个函数参数仍然需要解析为实际的表达式。
    • return_value() 工具会在某些类型错误上引发软错误(通过 ReturnKind::Unknown
    • 请注意,Prometheus 会根据运行时数据进行一些额外的查询验证,并在例如标签不匹配等我们无法捕获的错误时返回错误。
  • 它正确解析了 Prometheus 语法的大多数晦涩元素,然而并非完全无损;等价表达式(例如 sum by (a) (foo{})sum (foo) by (a))将被相同地评估,所以如果您使用它来实现格式化器,请期待微小的输出差异。
  • 尽管已尽可能使其匹配,但它可能并不完全与实际的 Prometheus 解析器相同。也就是说,此解析器可能认为某些表达式有效,而 Prometheus 则不认为有效,反之亦然。请考虑为任何差异提交错误报告。

返回值预测

该库有一个用于仅基于语法树的预测表达式返回类型的实用工具。这可能对粗略的静态分析很有用。

  • Expression::return_value() 递归地计算一个预期的返回类型(标量、瞬时向量、范围向量等)以及一组标签操作。如果由于某种原因失败,则返回 ReturnKind::Unknown 并附带解释原因的消息。

  • ReturnValue::passthrough(input_labels: &[&str]) 可以用来预测给定查询应存在的输出标签。它接受一个假设在指标上存在的初始标签名称列表(例如预期的指标如 namespace)并返回一组预期的输出标签。

    请注意,可能会返回额外的标签:如果表达式明确选择标签,它们也会被返回;可以使用空列表的初始标签来查看表达式将查询哪些额外标签。

  • ReturnValue::drops(label: &str) 确定哪个 AST 节点(如果有),丢弃了给定的输入标签。

示例

examples/parse.rs

这可以用来输出给定表达式的(稍微减少的)语法树。

尝试 cargo run --example parse 'foo'

$ cargo run --example parse 'count_values
  by(service) ("config_hash", alertmanager_config_hash{job="alertmanager-main",namespace="monitoring"})
  / on(service) group_left() label_replace(max by(name, job, namespace, controller)
  (prometheus_operator_spec_replicas{controller="alertmanager",job="prometheus-operator",namespace="monitoring"}),
  "service", "alertmanager-$1", "name", "(.*)") !=
  1'

Operator {
    kind: NotEqual,
    lhs: Operator {
        kind: Divide,
        lhs: Function {
            name: "count_values",
            args: [
                # ... snip ...
            ],
            aggregation: Some(
                Aggregation {
                    op: By,
                    labels: [
                        "service",
                    ],
                },
            ),
        },
        rhs: Function {
            name: "label_replace",
            args: [
                # ... snip ...
            ],
            aggregation: None,
        },
        matching: Some(
            Matching {
                op: On,
                labels: [
                    "service",
                ],
                group: Some(
                    MatchingGroup {
                        op: Left,
                        labels: [],
                    },
                ),
            },
        ),
    },
    rhs: 1.0,
    matching: None,
}

请注意,Expression 有一个 Debug 实现,它在一定程度上减少了样板输出。它使得调试输出更易读,但显示的对象结构将不会与内存中的版本完全匹配。具体来说,如果您正在构建自己的 AST 对象,请使用大多数 Expression 子类型上提供的 .wrap() 实用函数将它们转换为 Expression

examples/reformat.rs

解析输入表达式并使用默认的 Display 实现重新格式化。

$ cargo run --example reformat 'sum by(bar)(foo)'
sum(foo) by (bar)

examples/return_value.rs

显示为给定表达式计算出的原始 ReturnValue

$ cargo run --example return_value 'foo(sum(foo))'
ReturnValue {
    kind: InstantVector,
    label_ops: [
        LabelSetOpTuple {
            op: Clear,
            expression: Function {
                name: "sum",
                # ... snip ...
            },
            span: Some(
                Span {
                    start: 4,
                    end: 12,
                },
            ),
        },
    ],
}

examples/label_drop.rs

显示标签在表达式中的丢弃位置(如果有的话)。

$ cargo run --example label_drop 'foo(sum(foo))' namespace
label 'namespace' is dropped:

foo(sum(foo))
    --------

parent expression: sum(foo)

如果表达式无效并且具有未知返回类型,将打印一条消息

$ cargo run --example label_drop 'foo(sum(foo)) / bar[5m:]' namespace
note: expression return type is unknown, result may be inaccurate
  reason:     rhs return type (RangeVector) is not valid in an operator
  expression: foo(sum(foo)) / bar[5m:]

label 'namespace' is not dropped

examples/label_passthrough.rs

显示给定表达式的预期输出标签。表达式之后的参数是所有输入指标上存在的标签。

可能还会返回一些不在输入列表中的附加标签;这些标签是根据表达式引用的标签推断出来的。

$ cargo run --example label_passthrough 'sum by(bar)(foo)' baz
{
    "bar",
}

贡献

欢迎提交错误报告、功能请求和拉取请求!请务必阅读以下指南以开始:行为准则

请注意,正如行为准则中提到的,代码贡献必须表明您接受开发者来源证书,这本质上意味着您拥有在项目的MIT许可证下提交您贡献的代码的必要权利。如果您同意,只需在-s传递给git commit

git commit -s [...]

...之后,Git会自动将所需的Signed-off-by: ...附加到您的提交信息末尾。

依赖项

~3.5MB
~76K SLoC