4 个版本
0.4.2 | 2021 年 12 月 9 日 |
---|---|
0.4.0 | 2020 年 3 月 26 日 |
#415 in 解析器实现
334 个月下载量
72KB
1.5K SLoC
prometheus-parser-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