mirror of
https://github.com/prometheus/prometheus.git
synced 2026-05-16 10:06:36 +02:00
Related to #12318 . Allow using a PromQL expression for setting the offset, provided that: the expression evaluates to a scalar the expression does not contain vector selector (no TSDB access) the expression does not call the info() function (no TSDB access) the time() function is only called on the top level of the expression, not inside a subquery We'll call such expression a time expression. During parsing we no longer set OriginalOffset field in sub-queries and vector selectors, instead we set a new OriginalOffsetExpr field to the time expression, even if the expression is a literal number. Before evaluating the overall PromQL expression, the engine shall evaluate the time expressions and set the OriginalOffset field in vector selectors and subqueries to the calculated value. This makes the change fairly non intrusive. Later we can change the code to start using the expression more directly. Note: this prohibits using time() function inside a sub-query as the evaluation time is not constant inside a sub-query. Signed-off-by: György Krajcsovits <gyorgy.krajcsovits@grafana.com>
292 lines
7.5 KiB
Go
292 lines
7.5 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 Children(node) {
|
|
t += tree(e, level)
|
|
}
|
|
|
|
return t
|
|
}
|
|
|
|
func (node *EvalStmt) String() string {
|
|
return "EVAL " + node.Expr.String()
|
|
}
|
|
|
|
func (es Expressions) String() (s string) {
|
|
if len(es) == 0 {
|
|
return ""
|
|
}
|
|
for _, e := range es {
|
|
s += e.String()
|
|
s += ", "
|
|
}
|
|
return s[:len(s)-2]
|
|
}
|
|
|
|
func (node *AggregateExpr) String() string {
|
|
aggrString := node.getAggOpStr()
|
|
aggrString += "("
|
|
if node.Op.IsAggregatorWithParam() {
|
|
aggrString += fmt.Sprintf("%s, ", node.Param)
|
|
}
|
|
aggrString += fmt.Sprintf("%s)", node.Expr)
|
|
|
|
return aggrString
|
|
}
|
|
|
|
func (node *AggregateExpr) ShortString() string {
|
|
aggrString := node.getAggOpStr()
|
|
return aggrString
|
|
}
|
|
|
|
func (node *AggregateExpr) getAggOpStr() string {
|
|
aggrString := node.Op.String()
|
|
|
|
switch {
|
|
case node.Without:
|
|
aggrString += fmt.Sprintf(" without (%s) ", joinLabels(node.Grouping))
|
|
case len(node.Grouping) > 0:
|
|
aggrString += fmt.Sprintf(" by (%s) ", joinLabels(node.Grouping))
|
|
}
|
|
|
|
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])
|
|
|
|
for i, s := range ss {
|
|
if i > 0 {
|
|
b.WriteString(", ")
|
|
}
|
|
if !model.IsValidLegacyMetricName(string(model.LabelValue(s))) {
|
|
b.Write(strconv.AppendQuote(b.AvailableBuffer(), s))
|
|
} else {
|
|
b.WriteString(s)
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func (node *BinaryExpr) returnBool() string {
|
|
if node.ReturnBool {
|
|
return " bool"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func (node *BinaryExpr) ShortString() string {
|
|
return fmt.Sprintf("%s%s%s", node.Op, 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 *Call) String() string {
|
|
return fmt.Sprintf("%s(%s)", node.Func.Name, node.Args)
|
|
}
|
|
|
|
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 := ""
|
|
if vecSelector.OriginalOffsetExpr != nil {
|
|
offset = fmt.Sprintf(" offset %s", timeExprString(vecSelector.OriginalOffsetExpr))
|
|
}
|
|
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.
|
|
offsetExpr, atVal, preproc := vecSelector.OriginalOffsetExpr, vecSelector.Timestamp, vecSelector.StartOrEnd
|
|
vecSelector.OriginalOffsetExpr = nil
|
|
vecSelector.Timestamp = nil
|
|
vecSelector.StartOrEnd = 0
|
|
|
|
str := fmt.Sprintf("%s[%s]%s%s", vecSelector.String(), model.Duration(node.Range), at, offset)
|
|
|
|
vecSelector.OriginalOffsetExpr, vecSelector.Timestamp, vecSelector.StartOrEnd = offsetExpr, atVal, preproc
|
|
|
|
return str
|
|
}
|
|
|
|
func (node *MatrixSelector) ShortString() string {
|
|
at, offset := node.atOffset()
|
|
return fmt.Sprintf("[%s]%s%s", model.Duration(node.Range), 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()
|
|
}
|
|
offset := ""
|
|
if node.OriginalOffsetExpr != nil {
|
|
offset = fmt.Sprintf(" offset %s", timeExprString(node.OriginalOffsetExpr))
|
|
}
|
|
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()"
|
|
}
|
|
return fmt.Sprintf("[%s:%s]%s%s", model.Duration(node.Range), step, at, offset)
|
|
}
|
|
|
|
func (node *NumberLiteral) String() string {
|
|
return fmt.Sprint(node.Val)
|
|
}
|
|
|
|
func (node *ParenExpr) String() string {
|
|
return fmt.Sprintf("(%s)", node.Expr)
|
|
}
|
|
|
|
func (node *StringLiteral) String() string {
|
|
return fmt.Sprintf("%q", node.Val)
|
|
}
|
|
|
|
func (node *UnaryExpr) String() string {
|
|
return fmt.Sprintf("%s%s", node.Op, node.Expr)
|
|
}
|
|
|
|
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())
|
|
}
|
|
offset := ""
|
|
if node.OriginalOffsetExpr != nil {
|
|
offset = fmt.Sprintf(" offset %s", timeExprString(node.OriginalOffsetExpr))
|
|
}
|
|
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()"
|
|
}
|
|
|
|
if len(labelStrings) == 0 {
|
|
return fmt.Sprintf("%s%s%s", node.Name, at, offset)
|
|
}
|
|
sort.Strings(labelStrings)
|
|
return fmt.Sprintf("%s{%s}%s%s", node.Name, strings.Join(labelStrings, ","), at, offset)
|
|
}
|
|
|
|
func timeExprString(e Expr) string {
|
|
if nl, ok := e.(*NumberLiteral); ok {
|
|
v, sign := nl.Val, ""
|
|
if nl.Val < 0 {
|
|
v = -1 * v
|
|
sign = "-"
|
|
}
|
|
return fmt.Sprintf("%s%s", sign, model.Duration(time.Duration(v)*time.Second))
|
|
}
|
|
return e.String()
|
|
}
|