解析Prometheus PromQL

charlieroro發表於2021-09-14

解析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^2UnaryExpr只適用於獲取標量結果的表示式。

結構體如下,label位於UnaryExpr.Expr中:

type UnaryExpr struct {
	Op   ItemType
	Expr Expr

	StartPos Pos
}

BinaryExpr

多元表示式,使用二元運算子組合成的表示式,被運算子分割的表示式被儲存到LHS和RHS樹中

結構體如下,label位於BinaryExpr.LHSBinaryExpr.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

數字表示式,如10xc5e-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
}

小結:一般日常中使用的表示式可以分為多元表示式和非多元表示式兩種。多元表示式裡面是被二元運算子分隔的非多元表示式。一般使用的非多元表示式有:AggregateExprCallVectorSelectorMatrixSelectorSubqueryExpr用的比較少

PromQl的解析

從上面分析可以看出,Prometheus的查詢語句的基本型別為:NumberLiteralStringLiteralVectorSelectorMatrixSelector,前兩個本身就沒有任何標籤,後兩個有明確的結構體來儲存標籤。其他型別只是這四種基本型別的組合。

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

相關文章