mirror of
https://github.com/prometheus/prometheus.git
synced 2026-04-15 22:09:39 -04:00
Merge pull request #17067 from prometheus/faster-promql-printer
[PERF] PromQL: Speed up PromQL to string conversion
This commit is contained in:
commit
1cd746ebfb
3 changed files with 126 additions and 56 deletions
|
|
@ -54,7 +54,7 @@ func (e *AggregateExpr) Pretty(level int) string {
|
|||
return s
|
||||
}
|
||||
|
||||
s += e.getAggOpStr()
|
||||
s += e.ShortString()
|
||||
s += "(\n"
|
||||
|
||||
if e.Op.IsAggregatorWithParam() {
|
||||
|
|
|
|||
|
|
@ -53,49 +53,57 @@ func (node *EvalStmt) String() string {
|
|||
}
|
||||
|
||||
func (es Expressions) String() (s string) {
|
||||
if len(es) == 0 {
|
||||
switch len(es) {
|
||||
case 0:
|
||||
return ""
|
||||
case 1:
|
||||
return es[0].String()
|
||||
}
|
||||
for _, e := range es {
|
||||
s += e.String()
|
||||
s += ", "
|
||||
b := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
b.WriteString(es[0].String())
|
||||
for _, e := range es[1:] {
|
||||
b.WriteString(", ")
|
||||
b.WriteString(e.String())
|
||||
}
|
||||
return s[:len(s)-2]
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (node *AggregateExpr) String() string {
|
||||
aggrString := node.getAggOpStr()
|
||||
aggrString += "("
|
||||
b := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
node.writeAggOpStr(b)
|
||||
b.WriteString("(")
|
||||
if node.Op.IsAggregatorWithParam() {
|
||||
aggrString += fmt.Sprintf("%s, ", node.Param)
|
||||
b.WriteString(node.Param.String())
|
||||
b.WriteString(", ")
|
||||
}
|
||||
aggrString += fmt.Sprintf("%s)", node.Expr)
|
||||
b.WriteString(node.Expr.String())
|
||||
b.WriteString(")")
|
||||
|
||||
return aggrString
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (node *AggregateExpr) ShortString() string {
|
||||
aggrString := node.getAggOpStr()
|
||||
return aggrString
|
||||
b := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
node.writeAggOpStr(b)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (node *AggregateExpr) getAggOpStr() string {
|
||||
aggrString := node.Op.String()
|
||||
func (node *AggregateExpr) writeAggOpStr(b *bytes.Buffer) {
|
||||
b.WriteString(node.Op.String())
|
||||
|
||||
switch {
|
||||
case node.Without:
|
||||
aggrString += fmt.Sprintf(" without (%s) ", joinLabels(node.Grouping))
|
||||
b.WriteString(" without (")
|
||||
writeLabels(b, node.Grouping)
|
||||
b.WriteString(") ")
|
||||
case len(node.Grouping) > 0:
|
||||
aggrString += fmt.Sprintf(" by (%s) ", joinLabels(node.Grouping))
|
||||
b.WriteString(" by (")
|
||||
writeLabels(b, node.Grouping)
|
||||
b.WriteString(") ")
|
||||
}
|
||||
|
||||
return aggrString
|
||||
}
|
||||
|
||||
func joinLabels(ss []string) string {
|
||||
var bytea [1024]byte // On stack to avoid memory allocation while building the output.
|
||||
b := bytes.NewBuffer(bytea[:0])
|
||||
|
||||
func writeLabels(b *bytes.Buffer, ss []string) {
|
||||
for i, s := range ss {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
|
|
@ -106,7 +114,18 @@ func joinLabels(ss []string) string {
|
|||
b.WriteString(s)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// writeStringsJoin is like strings.Join but appending to a bytes.Buffer.
|
||||
func writeStringsJoin(b *bytes.Buffer, elems []string, sep string) {
|
||||
if len(elems) == 0 {
|
||||
return
|
||||
}
|
||||
b.WriteString(elems[0])
|
||||
for _, s := range elems[1:] {
|
||||
b.WriteString(sep)
|
||||
b.WriteString(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (node *BinaryExpr) returnBool() string {
|
||||
|
|
@ -118,11 +137,11 @@ func (node *BinaryExpr) returnBool() string {
|
|||
|
||||
func (node *BinaryExpr) String() string {
|
||||
matching := node.getMatchingStr()
|
||||
return fmt.Sprintf("%s %s%s%s %s", node.LHS, node.Op, node.returnBool(), matching, node.RHS)
|
||||
return node.LHS.String() + " " + node.Op.String() + node.returnBool() + matching + " " + node.RHS.String()
|
||||
}
|
||||
|
||||
func (node *BinaryExpr) ShortString() string {
|
||||
return fmt.Sprintf("%s%s%s", node.Op, node.returnBool(), node.getMatchingStr())
|
||||
return node.Op.String() + node.returnBool() + node.getMatchingStr()
|
||||
}
|
||||
|
||||
func (node *BinaryExpr) getMatchingStr() string {
|
||||
|
|
@ -147,32 +166,54 @@ func (node *BinaryExpr) getMatchingStr() string {
|
|||
}
|
||||
|
||||
func (node *DurationExpr) String() string {
|
||||
var expr string
|
||||
b := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
node.writeTo(b)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (node *DurationExpr) writeTo(b *bytes.Buffer) {
|
||||
if node.Wrapped {
|
||||
b.WriteByte('(')
|
||||
}
|
||||
|
||||
switch {
|
||||
case node.Op == STEP:
|
||||
expr = "step()"
|
||||
b.WriteString("step()")
|
||||
case node.Op == MIN:
|
||||
expr = fmt.Sprintf("min(%s, %s)", node.LHS, node.RHS)
|
||||
b.WriteString("min(")
|
||||
b.WriteString(node.LHS.String())
|
||||
b.WriteString(", ")
|
||||
b.WriteString(node.RHS.String())
|
||||
b.WriteByte(')')
|
||||
case node.Op == MAX:
|
||||
expr = fmt.Sprintf("max(%s, %s)", node.LHS, node.RHS)
|
||||
b.WriteString("max(")
|
||||
b.WriteString(node.LHS.String())
|
||||
b.WriteString(", ")
|
||||
b.WriteString(node.RHS.String())
|
||||
b.WriteByte(')')
|
||||
case node.LHS == nil:
|
||||
// This is a unary duration expression.
|
||||
switch node.Op {
|
||||
case SUB:
|
||||
expr = fmt.Sprintf("%s%s", node.Op, node.RHS)
|
||||
b.WriteString(node.Op.String())
|
||||
b.WriteString(node.RHS.String())
|
||||
case ADD:
|
||||
expr = node.RHS.String()
|
||||
b.WriteString(node.RHS.String())
|
||||
default:
|
||||
// This should never happen.
|
||||
panic(fmt.Sprintf("unexpected unary duration expression: %s", node.Op))
|
||||
}
|
||||
default:
|
||||
expr = fmt.Sprintf("%s %s %s", node.LHS, node.Op, node.RHS)
|
||||
b.WriteString(node.LHS.String())
|
||||
b.WriteByte(' ')
|
||||
b.WriteString(node.Op.String())
|
||||
b.WriteByte(' ')
|
||||
b.WriteString(node.RHS.String())
|
||||
}
|
||||
|
||||
if node.Wrapped {
|
||||
return fmt.Sprintf("(%s)", expr)
|
||||
b.WriteByte(')')
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
func (node *DurationExpr) ShortString() string {
|
||||
|
|
@ -180,7 +221,7 @@ func (node *DurationExpr) ShortString() string {
|
|||
}
|
||||
|
||||
func (node *Call) String() string {
|
||||
return fmt.Sprintf("%s(%s)", node.Func.Name, node.Args)
|
||||
return node.Func.Name + "(" + node.Args.String() + ")"
|
||||
}
|
||||
|
||||
func (node *Call) ShortString() string {
|
||||
|
|
@ -294,15 +335,15 @@ func (node *NumberLiteral) String() string {
|
|||
}
|
||||
|
||||
func (node *ParenExpr) String() string {
|
||||
return fmt.Sprintf("(%s)", node.Expr)
|
||||
return "(" + node.Expr.String() + ")"
|
||||
}
|
||||
|
||||
func (node *StringLiteral) String() string {
|
||||
return fmt.Sprintf("%q", node.Val)
|
||||
return strconv.Quote(node.Val)
|
||||
}
|
||||
|
||||
func (node *UnaryExpr) String() string {
|
||||
return fmt.Sprintf("%s%s", node.Op, node.Expr)
|
||||
return node.Op.String() + node.Expr.String()
|
||||
}
|
||||
|
||||
func (node *UnaryExpr) ShortString() string {
|
||||
|
|
@ -321,28 +362,33 @@ func (node *VectorSelector) String() string {
|
|||
}
|
||||
labelStrings = append(labelStrings, matcher.String())
|
||||
}
|
||||
offset := ""
|
||||
switch {
|
||||
case node.OriginalOffsetExpr != nil:
|
||||
offset = fmt.Sprintf(" offset %s", node.OriginalOffsetExpr)
|
||||
case node.OriginalOffset > time.Duration(0):
|
||||
offset = fmt.Sprintf(" offset %s", model.Duration(node.OriginalOffset))
|
||||
case node.OriginalOffset < time.Duration(0):
|
||||
offset = fmt.Sprintf(" offset -%s", model.Duration(-node.OriginalOffset))
|
||||
b := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
b.WriteString(node.Name)
|
||||
if len(labelStrings) != 0 {
|
||||
b.WriteByte('{')
|
||||
sort.Strings(labelStrings)
|
||||
writeStringsJoin(b, labelStrings, ",")
|
||||
b.WriteByte('}')
|
||||
}
|
||||
at := ""
|
||||
switch {
|
||||
case node.Timestamp != nil:
|
||||
at = fmt.Sprintf(" @ %.3f", float64(*node.Timestamp)/1000.0)
|
||||
b.WriteString(" @ ")
|
||||
b.Write(strconv.AppendFloat(b.AvailableBuffer(), float64(*node.Timestamp)/1000.0, 'f', 3, 64))
|
||||
case node.StartOrEnd == START:
|
||||
at = " @ start()"
|
||||
b.WriteString(" @ start()")
|
||||
case node.StartOrEnd == END:
|
||||
at = " @ end()"
|
||||
b.WriteString(" @ end()")
|
||||
}
|
||||
|
||||
if len(labelStrings) == 0 {
|
||||
return fmt.Sprintf("%s%s%s", node.Name, at, offset)
|
||||
switch {
|
||||
case node.OriginalOffsetExpr != nil:
|
||||
b.WriteString(" offset ")
|
||||
node.OriginalOffsetExpr.writeTo(b)
|
||||
case node.OriginalOffset > time.Duration(0):
|
||||
b.WriteString(" offset ")
|
||||
b.WriteString(model.Duration(node.OriginalOffset).String())
|
||||
case node.OriginalOffset < time.Duration(0):
|
||||
b.WriteString(" offset -")
|
||||
b.WriteString(model.Duration(-node.OriginalOffset).String())
|
||||
}
|
||||
sort.Strings(labelStrings)
|
||||
return fmt.Sprintf("%s{%s}%s%s", node.Name, strings.Join(labelStrings, ","), at, offset)
|
||||
return b.String()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -217,6 +217,9 @@ func TestExprString(t *testing.T) {
|
|||
{
|
||||
in: "foo[200 - min(step() + 10s, -max(step() ^ 2, 3))]",
|
||||
},
|
||||
{
|
||||
in: `predict_linear(foo[1h], 3000)`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range inputs {
|
||||
|
|
@ -234,6 +237,27 @@ func TestExprString(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkExprString(b *testing.B) {
|
||||
inputs := []string{
|
||||
`sum by(code) (task:errors:rate10s{job="s"})`,
|
||||
`max( 100 * (1 - avg by(instance) (irate(node_cpu_seconds_total{instance=~".*cust01.prd.*",mode="idle"}[86400s]))))`,
|
||||
`http_requests_total{job="api-server", group="canary"} + rate(http_requests_total{job="api-server"}[10m]) * 5 * 60`,
|
||||
`sum by (pod) ((kube_pod_container_status_restarts_total{namespace="mynamespace",cluster="mycluster"} - kube_pod_container_status_restarts_total{namespace="mynamespace}",cluster="mycluster}"} offset 10m) >= 1 and ignoring (reason) min_over_time(kube_pod_container_status_last_terminated_reason{namespace="mynamespace",cluster="mycluster",reason="OOMKilled"}[10m]) == 1)`,
|
||||
`sum by (pod) ((kube_pod_container_status_restarts_total{cluster="mycluster",namespace="mynamespace"} - kube_pod_container_status_restarts_total{cluster="mycluster",namespace="mynamespace}"} offset 10m) >= 1 and ignoring (reason) min_over_time(kube_pod_container_status_last_terminated_reason{cluster="mycluster",namespace="mynamespace",reason="OOMKilled"}[10m]) == 1)`, // Sort matchers.
|
||||
`label_replace(testmetric, "dst", "destination-value-$1", "src", "source-value-(.*)")`,
|
||||
}
|
||||
|
||||
for _, test := range inputs {
|
||||
b.Run(readable(test), func(b *testing.B) {
|
||||
expr, err := ParseExpr(test)
|
||||
require.NoError(b, err)
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = expr.String()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVectorSelector_String(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
|
|
|
|||
Loading…
Reference in a new issue