解析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!~"1111"}[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