prometheus/promql/parser/printer.go
Bryan Boreham 153cdb2b0b [PERF] PromQL: Replace Fprintf %f with AppendFloat
The combination of `AvailableBuffer`` followed by `Write` is optimised
inside `bytes.Buffer`.

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
2025-08-22 10:58:01 +01:00

395 lines
10 KiB
Go

// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package parser
import (
"bytes"
"fmt"
"sort"
"strconv"
"strings"
"time"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/labels"
)
// Tree returns a string of the tree structure of the given node.
func Tree(node Node) string {
return tree(node, "")
}
func tree(node Node, level string) string {
if node == nil {
return fmt.Sprintf("%s |---- %T\n", level, node)
}
typs := strings.Split(fmt.Sprintf("%T", node), ".")[1]
t := fmt.Sprintf("%s |---- %s :: %s\n", level, typs, node)
level += " · · ·"
for e := range ChildrenIter(node) {
t += tree(e, level)
}
return t
}
func (node *EvalStmt) String() string {
return "EVAL " + node.Expr.String()
}
func (es Expressions) String() (s string) {
switch len(es) {
case 0:
return ""
case 1:
return es[0].String()
}
b := bytes.NewBuffer(make([]byte, 0, 1024))
b.WriteString(es[0].String())
for _, e := range es[1:] {
b.WriteString(", ")
b.WriteString(e.String())
}
return b.String()
}
func (node *AggregateExpr) String() string {
b := bytes.NewBuffer(make([]byte, 0, 1024))
node.writeAggOpStr(b)
b.WriteString("(")
if node.Op.IsAggregatorWithParam() {
b.WriteString(node.Param.String())
b.WriteString(", ")
}
b.WriteString(node.Expr.String())
b.WriteString(")")
return b.String()
}
func (node *AggregateExpr) ShortString() string {
b := bytes.NewBuffer(make([]byte, 0, 1024))
node.writeAggOpStr(b)
return b.String()
}
func (node *AggregateExpr) writeAggOpStr(b *bytes.Buffer) {
b.WriteString(node.Op.String())
switch {
case node.Without:
b.WriteString(" without (")
writeLabels(b, node.Grouping)
b.WriteString(") ")
case len(node.Grouping) > 0:
b.WriteString(" by (")
writeLabels(b, node.Grouping)
b.WriteString(") ")
}
}
func writeLabels(b *bytes.Buffer, ss []string) {
for i, s := range ss {
if i > 0 {
b.WriteString(", ")
}
if !model.LegacyValidation.IsValidMetricName(s) {
b.Write(strconv.AppendQuote(b.AvailableBuffer(), s))
} else {
b.WriteString(s)
}
}
}
// 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 {
if node.ReturnBool {
return " bool"
}
return ""
}
func (node *BinaryExpr) String() string {
matching := node.getMatchingStr()
return node.LHS.String() + " " + node.Op.String() + node.returnBool() + matching + " " + node.RHS.String()
}
func (node *BinaryExpr) ShortString() string {
return node.Op.String() + node.returnBool() + node.getMatchingStr()
}
func (node *BinaryExpr) getMatchingStr() string {
matching := ""
vm := node.VectorMatching
if vm != nil && (len(vm.MatchingLabels) > 0 || vm.On) {
vmTag := "ignoring"
if vm.On {
vmTag = "on"
}
matching = fmt.Sprintf(" %s (%s)", vmTag, strings.Join(vm.MatchingLabels, ", "))
if vm.Card == CardManyToOne || vm.Card == CardOneToMany {
vmCard := "right"
if vm.Card == CardManyToOne {
vmCard = "left"
}
matching += fmt.Sprintf(" group_%s (%s)", vmCard, strings.Join(vm.Include, ", "))
}
}
return matching
}
func (node *DurationExpr) String() 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:
b.WriteString("step()")
case node.Op == MIN:
b.WriteString("min(")
b.WriteString(node.LHS.String())
b.WriteString(", ")
b.WriteString(node.RHS.String())
b.WriteByte(')')
case node.Op == MAX:
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:
b.WriteString(node.Op.String())
b.WriteString(node.RHS.String())
case ADD:
b.WriteString(node.RHS.String())
default:
// This should never happen.
panic(fmt.Sprintf("unexpected unary duration expression: %s", node.Op))
}
default:
b.WriteString(node.LHS.String())
b.WriteByte(' ')
b.WriteString(node.Op.String())
b.WriteByte(' ')
b.WriteString(node.RHS.String())
}
if node.Wrapped {
b.WriteByte(')')
}
}
func (node *DurationExpr) ShortString() string {
return node.Op.String()
}
func (node *Call) String() string {
return node.Func.Name + "(" + node.Args.String() + ")"
}
func (node *Call) ShortString() string {
return node.Func.Name
}
func (node *MatrixSelector) atOffset() (string, string) {
// Copy the Vector selector before changing the offset
vecSelector := node.VectorSelector.(*VectorSelector)
offset := ""
switch {
case vecSelector.OriginalOffsetExpr != nil:
offset = fmt.Sprintf(" offset %s", vecSelector.OriginalOffsetExpr)
case vecSelector.OriginalOffset > time.Duration(0):
offset = fmt.Sprintf(" offset %s", model.Duration(vecSelector.OriginalOffset))
case vecSelector.OriginalOffset < time.Duration(0):
offset = fmt.Sprintf(" offset -%s", model.Duration(-vecSelector.OriginalOffset))
}
at := ""
switch {
case vecSelector.Timestamp != nil:
at = fmt.Sprintf(" @ %.3f", float64(*vecSelector.Timestamp)/1000.0)
case vecSelector.StartOrEnd == START:
at = " @ start()"
case vecSelector.StartOrEnd == END:
at = " @ end()"
}
return at, offset
}
func (node *MatrixSelector) String() string {
at, offset := node.atOffset()
// Copy the Vector selector before changing the offset
vecSelector := *node.VectorSelector.(*VectorSelector)
// Do not print the @ and offset twice.
offsetVal, offsetExprVal, atVal, preproc := vecSelector.OriginalOffset, vecSelector.OriginalOffsetExpr, vecSelector.Timestamp, vecSelector.StartOrEnd
vecSelector.OriginalOffset = 0
vecSelector.OriginalOffsetExpr = nil
vecSelector.Timestamp = nil
vecSelector.StartOrEnd = 0
rangeStr := model.Duration(node.Range).String()
if node.RangeExpr != nil {
rangeStr = node.RangeExpr.String()
}
str := fmt.Sprintf("%s[%s]%s%s", vecSelector.String(), rangeStr, at, offset)
vecSelector.OriginalOffset, vecSelector.OriginalOffsetExpr, vecSelector.Timestamp, vecSelector.StartOrEnd = offsetVal, offsetExprVal, atVal, preproc
return str
}
func (node *MatrixSelector) ShortString() string {
at, offset := node.atOffset()
rangeStr := model.Duration(node.Range).String()
if node.RangeExpr != nil {
rangeStr = node.RangeExpr.String()
}
return fmt.Sprintf("[%s]%s%s", rangeStr, at, offset)
}
func (node *SubqueryExpr) String() string {
return fmt.Sprintf("%s%s", node.Expr.String(), node.getSubqueryTimeSuffix())
}
func (node *SubqueryExpr) ShortString() string {
return node.getSubqueryTimeSuffix()
}
// getSubqueryTimeSuffix returns the '[<range>:<step>] @ <timestamp> offset <offset>' suffix of the subquery.
func (node *SubqueryExpr) getSubqueryTimeSuffix() string {
step := ""
if node.Step != 0 {
step = model.Duration(node.Step).String()
} else if node.StepExpr != nil {
step = node.StepExpr.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))
}
at := ""
switch {
case node.Timestamp != nil:
at = fmt.Sprintf(" @ %.3f", float64(*node.Timestamp)/1000.0)
case node.StartOrEnd == START:
at = " @ start()"
case node.StartOrEnd == END:
at = " @ end()"
}
rangeStr := model.Duration(node.Range).String()
if node.RangeExpr != nil {
rangeStr = node.RangeExpr.String()
}
return fmt.Sprintf("[%s:%s]%s%s", rangeStr, step, at, offset)
}
func (node *NumberLiteral) String() string {
if node.Duration {
if node.Val < 0 {
return fmt.Sprintf("-%s", model.Duration(-node.Val*1e9).String())
}
return model.Duration(node.Val * 1e9).String()
}
return strconv.FormatFloat(node.Val, 'f', -1, 64)
}
func (node *ParenExpr) String() string {
return "(" + node.Expr.String() + ")"
}
func (node *StringLiteral) String() string {
return strconv.Quote(node.Val)
}
func (node *UnaryExpr) String() string {
return node.Op.String() + node.Expr.String()
}
func (node *UnaryExpr) ShortString() string {
return node.Op.String()
}
func (node *VectorSelector) String() string {
var labelStrings []string
if len(node.LabelMatchers) > 1 {
labelStrings = make([]string, 0, len(node.LabelMatchers)-1)
}
for _, matcher := range node.LabelMatchers {
// Only include the __name__ label if its equality matching and matches the name, but don't skip if it's an explicit empty name matcher.
if matcher.Name == labels.MetricName && matcher.Type == labels.MatchEqual && matcher.Value == node.Name && matcher.Value != "" {
continue
}
labelStrings = append(labelStrings, matcher.String())
}
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('}')
}
switch {
case node.Timestamp != nil:
b.WriteString(" @ ")
b.Write(strconv.AppendFloat(b.AvailableBuffer(), float64(*node.Timestamp)/1000.0, 'f', 3, 64))
case node.StartOrEnd == START:
b.WriteString(" @ start()")
case node.StartOrEnd == END:
b.WriteString(" @ end()")
}
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())
}
return b.String()
}