解析PromQL
目前对Prometheus 的promQL 的解析文章比较少,且Prometheus官方也没有提供一个公共的库来对其进行解析。下面实现对promQL的解析,并实现注入label功能。
表达式类型
AggregateExpr
对应聚合操作,如sum without (instance) (http_requests_total)
,定义可以查看Aggregation operators。源码定义在prometheus/promql/parser/lex.go
// Aggregators. "sum": SUM, "avg": AVG, "count": COUNT, "min": MIN, "max": MAX, "group": GROUP, "stddev": STDDEV, "stdvar": STDVAR, "topk": TOPK, "bottomk": BOTTOMK, "count_values": COUNT_VALUES, "quantile": QUANTILE,
结构体如下,label
位于AggregateExpr.Expr
中:
type AggregateExpr struct { Op ItemType // The used aggregation operation. Expr Expr // The Vector expression over which is aggregated. Param Expr // Parameter used by some aggregators. Grouping []string // The labels by which to group the Vector. Without bool // Whether to drop the given labels rather than keep them. PosRange PositionRange }
Call
对应函数调用,如absent(nonexistent{job="myjob"})
,函数定义可以查看function。源码定义在Prometheus/promql/parser/function.go文件中,
// Functions is a list of all functions supported by PromQL, including their types. var Functions = map[string]*Function{ "abs": { Name: "abs", ArgTypes: []ValueType{ValueTypeVector}, ReturnType: ValueTypeVector, }, "absent": { Name: "absent", ArgTypes: []ValueType{ValueTypeVector}, ReturnType: ValueTypeVector, }, "absent_over_time": { Name: "absent_over_time", ArgTypes: []ValueType{ValueTypeMatrix}, ReturnType: ValueTypeVector, }, "avg_over_time": { Name: "avg_over_time", ArgTypes: []ValueType{ValueTypeMatrix}, ReturnType: ValueTypeVector, },
结构体如下,label
位于Call.Args
中:
type Call struct { Func *Function // The function that was called. Args Expressions // Arguments used in the call. PosRange PositionRange }
ParenExpr
圆括号表达式,即表达式外面加了圆括号,如(up)
、(3*1)
,一般集成在BinaryExpr中,如1 + 2/(3*1)
,但根据ParenExpr
的定义,(1 + 2/(3*1))
又变成了ParenExpr。
结构体如下,label
位于ParenExpr.Expr
中:
type ParenExpr struct { Expr Expr PosRange PositionRange }
UnaryExpr
一元表达式,如-some_metric
、+some_metric
、-1^2
。UnaryExpr只适用于获取标量结果的表达式。
结构体如下,label
位于UnaryExpr.Expr
中:
type UnaryExpr struct { Op ItemType Expr Expr StartPos Pos }
BinaryExpr
多元表达式,使用二元运算符组合成的表达式,被运算符分割的表达式被保存到LHS和RHS树中
结构体如下,label
位于BinaryExpr.LHS
和BinaryExpr.RHS
中:
type BinaryExpr struct { Op ItemType // The operation of the expression. LHS, RHS Expr // The operands on the respective sides of the operator. // The matching behavior for the operation if both operands are Vectors. // If they are not this field is nil. VectorMatching *VectorMatching // If a comparison operator, return 0/1 rather than filtering. ReturnBool bool }
NumberLiteral
数字表达式,如1
、0xc
、5e-3
,该类型的表达式与UnaryExpr类似,也是集成到其他类型的表达式中使用的,单独使用并没有意义。
结构体如下,无label
:
type NumberLiteral struct { Val float64 PosRange PositionRange }
StringLiteral
字符表达式,如"version"
,与NumberLiteral类似,一般会集成到其他表达式中,如count_values("version", build_version)
结构体如下,无label
:
type StringLiteral struct { Val string PosRange PositionRange }
VectorSelector
瞬时向量,无需指定时间范围,如http_requests_total
、{job=~".*",method="get"}
结构体如下,label
位于VectorSelector.LabelMatchers
中:
type VectorSelector struct { Name string Offset time.Duration LabelMatchers []*labels.Matcher // The unexpanded seriesSet populated at query preparation time. UnexpandedSeriesSet storage.SeriesSet Series []storage.Series PosRange PositionRange }
MatrixSelector
区间向量,需指定时间范围,如http_requests_total[1m]
、{job=~".*",method="get"}[1m]
结构体如下,label
位于MatrixSelector.LabelMatchers
中:
type MatrixSelector struct { Name string Range time.Duration Offset time.Duration LabelMatchers []*labels.Matcher // The series are populated at query preparation time. series []storage.Series }
SubqueryExpr
子查询表达式,支持指定查询范围和精度,其实就是MatrixSelector加了精度功能。格式为<instant_query> '[' <range> ':' [<resolution>] ']'
,如rate(http_requests_total[5m])[30m:1m]
,官方定义参见Subquery
结构体如下,label
位于SubqueryExpr.Expr
中:
type SubqueryExpr struct { Expr Expr Range time.Duration Offset time.Duration Step time.Duration EndPos Pos }
小结:一般日常中使用的表达式可以分为多元表达式和非多元表达式两种。多元表达式里面是被二元运算符分隔的非多元表达式。一般使用的非多元表达式有:AggregateExpr、Call、VectorSelector和MatrixSelector。SubqueryExpr用的比较少
PromQl的解析
从上面分析可以看出,Prometheus的查询语句的基本类型为:NumberLiteral、StringLiteral、VectorSelector、MatrixSelector,前两个本身就没有任何标签,后两个有明确的结构体来保存标签。其他类型只是这四种基本类型的组合。
Prometheus源码的eval
函数(位于Prometheus/promql/engine.go文件中)对分别不同类型的promQL进行了处理,可以参考此处代码。
解析代码如下:
import ( "fmt" "github.com/pkg/errors" "github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/pkg/labels" ) func injectLabels(expr parser.Expr, match labels.MatchType, name,value string){ switch e := expr.(type) { case *parser.AggregateExpr: injectLabels(e.Expr,match,name,value) case *parser.Call: for _,v := range e.Args{ injectLabels(v,match,name,value) } case *parser.ParenExpr: injectLabels(e.Expr,match,name,value) case *parser.UnaryExpr: injectLabels(e.Expr,match,name,value) case *parser.BinaryExpr: injectLabels(e.LHS,match,name,value) injectLabels(e.RHS,match,name,value) case *parser.VectorSelector: l := genMetricLabel(match,name,value) e.LabelMatchers = append(e.LabelMatchers, l) return case *parser.MatrixSelector: injectLabels(e.VectorSelector,match,name,value) case *parser.SubqueryExpr: injectLabels(e.Expr,match,name,value) case *parser.NumberLiteral,*parser.StringLiteral: return default: panic(errors.Errorf("unhandled expression of type: %T", expr)) } return } func genMetricLabel(match labels.MatchType, name,value string) *labels.Matcher{ m,err := labels.NewMatcher(match,name,value) if nil != err { return nil } return m }
使用方式如下,在rate(http_requests_total[5m])[30m:1m]
种注入"appName !~ testAppName"的表达式,输出结果为:rate(http_requests_total{appname!~"testAppName"}[5m])[30m:1m]
func main() { ql := `rate(http_requests_total[5m])[30m:1m]` expr,_ := parser.ParseExpr(ql) injectLabels(expr,labels.MatchNotRegexp,"appName","testAppName") fmt.Println(expr.String()) }
Prometheus支持如下四种匹配模式:
MatchEqual: "=", MatchNotEqual: "!=", MatchRegexp: "=~", MatchNotRegexp: "!~",
TIPs
- 只有较高版本的Prometheus库才能支持解析SubqueryExpr,但直接通过
go mod tidy
命令(目前)只能获取到v2.5.0
版本,该版本并不支持解析SubqueryExpr。可以在GitHub上找到对应版本的tag,然后执行go get github.com/prometheus/prometheus@${commitId}
来获得该版本。需要注意的是执行之后go.mod中的Prometheus版本为v1.8.2
,可以忽略此版本标记。具体参见该issue