From 3ca11bcaf58685d3994d6f643fff57075cbed595 Mon Sep 17 00:00:00 2001 From: Fabian Reinartz Date: Mon, 30 Mar 2015 19:43:19 +0200 Subject: [PATCH] Switch Prometheus to promql package. This commit removes all functionality from rules/ that is now handled in promql/. All parts of Prometheus are changed to use the promql/ package. --- main.go | 36 +- rules/Makefile | 27 - rules/alerting.go | 37 +- rules/ast/ast.go | 1175 ------------- rules/ast/functions.go | 752 -------- rules/ast/functions_test.go | 63 - rules/ast/printer.go | 441 ----- rules/ast/quantile.go | 106 -- rules/ast/query_analyzer.go | 229 --- rules/ast/walk.go | 28 - rules/fixtures/empty.rules | 0 rules/fixtures/mixed.rules | 16 - rules/fixtures/non_vector.rules | 1 - rules/fixtures/syntax_error.rules | 15 - rules/helpers.go | 221 --- rules/helpers_test.go | 487 ----- rules/lexer.l | 118 -- rules/lexer.l.go | 2738 ----------------------------- rules/load.go | 164 -- rules/{manager => }/manager.go | 90 +- rules/parser.y | 281 --- rules/parser.y.go | 784 --------- rules/recording.go | 34 +- rules/rules.go | 11 +- rules/rules_test.go | 1720 ++---------------- templates/templates.go | 25 +- templates/templates_test.go | 5 +- tools/rule_checker/main.go | 10 +- utility/strconv.go | 23 + web/alerts.go | 6 +- web/api/api.go | 18 +- web/api/api_test.go | 10 +- web/api/query.go | 64 +- web/consoles.go | 8 +- web/httputils/httputils.go | 40 + web/status.go | 4 +- 36 files changed, 351 insertions(+), 9436 deletions(-) delete mode 100644 rules/Makefile delete mode 100644 rules/ast/ast.go delete mode 100644 rules/ast/functions.go delete mode 100644 rules/ast/functions_test.go delete mode 100644 rules/ast/printer.go delete mode 100644 rules/ast/quantile.go delete mode 100644 rules/ast/query_analyzer.go delete mode 100644 rules/ast/walk.go delete mode 100644 rules/fixtures/empty.rules delete mode 100644 rules/fixtures/mixed.rules delete mode 100644 rules/fixtures/non_vector.rules delete mode 100644 rules/fixtures/syntax_error.rules delete mode 100644 rules/helpers.go delete mode 100644 rules/helpers_test.go delete mode 100644 rules/lexer.l delete mode 100644 rules/lexer.l.go delete mode 100644 rules/load.go rename rules/{manager => }/manager.go (78%) delete mode 100644 rules/parser.y delete mode 100644 rules/parser.y.go diff --git a/main.go b/main.go index 1deabd339b..3e6f775501 100644 --- a/main.go +++ b/main.go @@ -32,8 +32,9 @@ import ( "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/notification" + "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/retrieval" - "github.com/prometheus/prometheus/rules/manager" + "github.com/prometheus/prometheus/rules" "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage/local" "github.com/prometheus/prometheus/storage/remote" @@ -76,7 +77,8 @@ var ( ) type prometheus struct { - ruleManager manager.RuleManager + queryEngine *promql.Engine + ruleManager rules.RuleManager targetManager retrieval.TargetManager notificationHandler *notification.NotificationHandler storage local.Storage @@ -155,17 +157,26 @@ func NewPrometheus() *prometheus { targetManager := retrieval.NewTargetManager(sampleAppender, conf.GlobalLabels()) targetManager.AddTargetsFromConfig(conf) - ruleManager := manager.NewRuleManager(&manager.RuleManagerOptions{ + queryEngine := promql.NewEngine(memStorage) + + ruleManager := rules.NewRuleManager(&rules.RuleManagerOptions{ SampleAppender: sampleAppender, NotificationHandler: notificationHandler, EvaluationInterval: conf.EvaluationInterval(), - Storage: memStorage, + QueryEngine: queryEngine, PrometheusURL: web.MustBuildServerURL(*pathPrefix), PathPrefix: *pathPrefix, }) - if err := ruleManager.AddRulesFromConfig(conf); err != nil { - glog.Error("Error loading rule files: ", err) - os.Exit(1) + for _, rf := range conf.Global.GetRuleFile() { + query, err := queryEngine.NewQueryFromFile(rf) + if err != nil { + glog.Errorf("Error loading rule file %q: %s", rf, err) + os.Exit(1) + } + if res := query.Exec(); res.Err != nil { + glog.Errorf("Error initializing rules: %s", res.Err) + os.Exit(1) + } } flags := map[string]string{} @@ -188,8 +199,8 @@ func NewPrometheus() *prometheus { } consolesHandler := &web.ConsolesHandler{ - Storage: memStorage, - PathPrefix: *pathPrefix, + QueryEngine: queryEngine, + PathPrefix: *pathPrefix, } graphsHandler := &web.GraphsHandler{ @@ -197,8 +208,9 @@ func NewPrometheus() *prometheus { } metricsService := &api.MetricsService{ - Now: clientmodel.Now, - Storage: memStorage, + Now: clientmodel.Now, + Storage: memStorage, + QueryEngine: queryEngine, } webService := &web.WebService{ @@ -210,6 +222,7 @@ func NewPrometheus() *prometheus { } p := &prometheus{ + queryEngine: queryEngine, ruleManager: ruleManager, targetManager: targetManager, notificationHandler: notificationHandler, @@ -252,6 +265,7 @@ func (p *prometheus) Serve() { p.targetManager.Stop() p.ruleManager.Stop() + p.queryEngine.Stop() if err := p.storage.Stop(); err != nil { glog.Error("Error stopping local storage: ", err) diff --git a/rules/Makefile b/rules/Makefile deleted file mode 100644 index 12df33ff06..0000000000 --- a/rules/Makefile +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2013 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. - -all: parser.y.go lexer.l.go - -include ../Makefile.INCLUDE - -parser.y.go: parser.y - $(GOCC) tool yacc -o parser.y.go -v "" parser.y - -lexer.l.go: parser.y.go lexer.l - # This is golex from https://github.com/cznic/golex. - $(GO_GET) github.com/cznic/golex - golex -o="lexer.l.go" lexer.l - -clean: - rm lexer.l.go parser.y.go diff --git a/rules/alerting.go b/rules/alerting.go index 7703db1c24..8a558aeca4 100644 --- a/rules/alerting.go +++ b/rules/alerting.go @@ -22,9 +22,7 @@ import ( clientmodel "github.com/prometheus/client_golang/model" - "github.com/prometheus/prometheus/rules/ast" - "github.com/prometheus/prometheus/stats" - "github.com/prometheus/prometheus/storage/local" + "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/utility" ) @@ -80,7 +78,7 @@ type Alert struct { } // sample returns a Sample suitable for recording the alert. -func (a Alert) sample(timestamp clientmodel.Timestamp, value clientmodel.SampleValue) *ast.Sample { +func (a Alert) sample(timestamp clientmodel.Timestamp, value clientmodel.SampleValue) *promql.Sample { recordedMetric := clientmodel.Metric{} for label, value := range a.Labels { recordedMetric[label] = value @@ -90,7 +88,7 @@ func (a Alert) sample(timestamp clientmodel.Timestamp, value clientmodel.SampleV recordedMetric[AlertNameLabel] = clientmodel.LabelValue(a.Name) recordedMetric[AlertStateLabel] = clientmodel.LabelValue(a.State.String()) - return &ast.Sample{ + return &promql.Sample{ Metric: clientmodel.COWMetric{ Metric: recordedMetric, Copied: true, @@ -105,7 +103,7 @@ type AlertingRule struct { // The name of the alert. name string // The vector expression from which to generate alerts. - Vector ast.VectorNode + Vector promql.Expr // The duration for which a labelset needs to persist in the expression // output vector before an alert transitions from Pending to Firing state. holdDuration time.Duration @@ -129,14 +127,18 @@ func (rule *AlertingRule) Name() string { } // EvalRaw returns the raw value of the rule expression, without creating alerts. -func (rule *AlertingRule) EvalRaw(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) { - return ast.EvalVectorInstant(rule.Vector, timestamp, storage, stats.NewTimerGroup()) +func (rule *AlertingRule) EvalRaw(timestamp clientmodel.Timestamp, engine *promql.Engine) (promql.Vector, error) { + query, err := engine.NewInstantQuery(rule.Vector.String(), timestamp) + if err != nil { + return nil, err + } + return query.Exec().Vector() } // Eval evaluates the rule expression and then creates pending alerts and fires // or removes previously pending alerts accordingly. -func (rule *AlertingRule) Eval(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) { - exprResult, err := rule.EvalRaw(timestamp, storage) +func (rule *AlertingRule) Eval(timestamp clientmodel.Timestamp, engine *promql.Engine) (promql.Vector, error) { + exprResult, err := rule.EvalRaw(timestamp, engine) if err != nil { return nil, err } @@ -170,7 +172,7 @@ func (rule *AlertingRule) Eval(timestamp clientmodel.Timestamp, storage local.St } } - vector := ast.Vector{} + vector := promql.Vector{} // Check if any pending alerts should be removed or fire now. Write out alert timeseries. for fp, activeAlert := range rule.activeAlerts { @@ -191,8 +193,8 @@ func (rule *AlertingRule) Eval(timestamp clientmodel.Timestamp, storage local.St return vector, nil } -// ToDotGraph returns the text representation of a dot graph. -func (rule *AlertingRule) ToDotGraph() string { +// DotGraph returns the text representation of a dot graph. +func (rule *AlertingRule) DotGraph() string { graph := fmt.Sprintf( `digraph "Rules" { %#p[shape="box",label="ALERT %s IF FOR %s"]; @@ -201,7 +203,8 @@ func (rule *AlertingRule) ToDotGraph() string { }`, &rule, rule.name, utility.DurationToString(rule.holdDuration), &rule, reflect.ValueOf(rule.Vector).Pointer(), - rule.Vector.NodeTreeToDotGraph()) + rule.Vector.DotGraph(), + ) return graph } @@ -217,9 +220,9 @@ func (rule *AlertingRule) HTMLSnippet() template.HTML { } return template.HTML(fmt.Sprintf( `ALERT %s IF %s FOR %s WITH %s`, - GraphLinkForExpression(alertMetric.String()), + utility.GraphLinkForExpression(alertMetric.String()), rule.name, - GraphLinkForExpression(rule.Vector.String()), + utility.GraphLinkForExpression(rule.Vector.String()), rule.Vector, utility.DurationToString(rule.holdDuration), rule.Labels)) @@ -252,7 +255,7 @@ func (rule *AlertingRule) ActiveAlerts() []Alert { } // NewAlertingRule constructs a new AlertingRule. -func NewAlertingRule(name string, vector ast.VectorNode, holdDuration time.Duration, labels clientmodel.LabelSet, summary string, description string) *AlertingRule { +func NewAlertingRule(name string, vector promql.Expr, holdDuration time.Duration, labels clientmodel.LabelSet, summary string, description string) *AlertingRule { return &AlertingRule{ name: name, Vector: vector, diff --git a/rules/ast/ast.go b/rules/ast/ast.go deleted file mode 100644 index a279a4f9d8..0000000000 --- a/rules/ast/ast.go +++ /dev/null @@ -1,1175 +0,0 @@ -// Copyright 2013 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 ast - -import ( - "errors" - "flag" - "fmt" - "math" - "time" - - clientmodel "github.com/prometheus/client_golang/model" - - "github.com/prometheus/prometheus/stats" - "github.com/prometheus/prometheus/storage/local" - "github.com/prometheus/prometheus/storage/metric" -) - -var ( - stalenessDelta = flag.Duration("query.staleness-delta", 300*time.Second, "Staleness delta allowance during expression evaluations.") - queryTimeout = flag.Duration("query.timeout", 2*time.Minute, "Maximum time a query may take before being aborted.") -) - -type queryTimeoutError struct { - timeoutAfter time.Duration -} - -func (e queryTimeoutError) Error() string { - return fmt.Sprintf("query timeout after %v", e.timeoutAfter) -} - -// ---------------------------------------------------------------------------- -// Raw data value types. - -// SampleStream is a stream of Values belonging to an attached COWMetric. -type SampleStream struct { - Metric clientmodel.COWMetric `json:"metric"` - Values metric.Values `json:"values"` -} - -// Sample is a single sample belonging to a COWMetric. -type Sample struct { - Metric clientmodel.COWMetric `json:"metric"` - Value clientmodel.SampleValue `json:"value"` - Timestamp clientmodel.Timestamp `json:"timestamp"` -} - -// Vector is basically only an alias for clientmodel.Samples, but the -// contract is that in a Vector, all Samples have the same timestamp. -type Vector []*Sample - -// Matrix is a slice of SampleStreams that implements sort.Interface and -// has a String method. -// BUG(julius): Pointerize this. -type Matrix []SampleStream - -type groupedAggregation struct { - labels clientmodel.COWMetric - value clientmodel.SampleValue - groupCount int -} - -// ---------------------------------------------------------------------------- -// Enums. - -// ExprType is an enum for the rule language expression types. -type ExprType int - -// Possible language expression types. We define these as integer constants -// because sometimes we need to pass around just the type without an object of -// that type. -const ( - ScalarType ExprType = iota - VectorType - MatrixType - StringType -) - -// BinOpType is an enum for binary operator types. -type BinOpType int - -// Possible binary operator types. -const ( - Add BinOpType = iota - Sub - Mul - Div - Mod - NE - EQ - GT - LT - GE - LE - And - Or -) - -// shouldDropMetric indicates whether the metric name should be dropped after -// applying this operator to a vector. -func (opType BinOpType) shouldDropMetric() bool { - switch opType { - case Add, Sub, Mul, Div, Mod: - return true - default: - return false - } -} - -// AggrType is an enum for aggregation types. -type AggrType int - -// Possible aggregation types. -const ( - Sum AggrType = iota - Avg - Min - Max - Count -) - -// ---------------------------------------------------------------------------- -// Interfaces. - -// Nodes is a slice of any mix of node types as all node types -// implement the Node interface. -type Nodes []Node - -// Node is the top-level interface for any kind of nodes. Each node -// type implements one of the ...Node interfaces, each of which embeds -// this Node interface. -type Node interface { - Type() ExprType - Children() Nodes - NodeTreeToDotGraph() string - String() string -} - -// ScalarNode is a Node for scalar values. -type ScalarNode interface { - Node - // Eval evaluates and returns the value of the scalar represented by this node. - Eval(timestamp clientmodel.Timestamp) clientmodel.SampleValue -} - -// VectorNode is a Node for vector values. -type VectorNode interface { - Node - // Eval evaluates the node recursively and returns the result - // as a Vector (i.e. a slice of Samples all at the given - // Timestamp). - Eval(timestamp clientmodel.Timestamp) Vector -} - -// MatrixNode is a Node for matrix values. -type MatrixNode interface { - Node - // Eval evaluates the node recursively and returns the result as a Matrix. - Eval(timestamp clientmodel.Timestamp) Matrix - // Eval evaluates the node recursively and returns the result - // as a Matrix that only contains the boundary values. - EvalBoundaries(timestamp clientmodel.Timestamp) Matrix -} - -// StringNode is a Node for string values. -type StringNode interface { - Node - // Eval evaluates and returns the value of the string - // represented by this node. - Eval(timestamp clientmodel.Timestamp) string -} - -// ---------------------------------------------------------------------------- -// ScalarNode types. - -type ( - // ScalarLiteral represents a numeric selector. - ScalarLiteral struct { - value clientmodel.SampleValue - } - - // ScalarFunctionCall represents a function with a numeric - // return type. - ScalarFunctionCall struct { - function *Function - args Nodes - } - - // ScalarArithExpr represents an arithmetic expression of - // numeric type. - ScalarArithExpr struct { - opType BinOpType - lhs ScalarNode - rhs ScalarNode - } -) - -// ---------------------------------------------------------------------------- -// VectorNode types. - -type ( - // A VectorSelector represents a metric name plus labelset. - VectorSelector struct { - labelMatchers metric.LabelMatchers - offset time.Duration - // The series iterators are populated at query analysis time. - iterators map[clientmodel.Fingerprint]local.SeriesIterator - metrics map[clientmodel.Fingerprint]clientmodel.COWMetric - // Fingerprints are populated from label matchers at query analysis time. - fingerprints clientmodel.Fingerprints - } - - // VectorFunctionCall represents a function with vector return - // type. - VectorFunctionCall struct { - function *Function - args Nodes - } - - // A VectorAggregation with vector return type. - VectorAggregation struct { - aggrType AggrType - groupBy clientmodel.LabelNames - keepExtraLabels bool - vector VectorNode - } - - // VectorArithExpr represents an arithmetic expression of vector type. At - // least one of the two operand Nodes must be a VectorNode. The other may be - // a VectorNode or ScalarNode. Both criteria are checked at runtime. - VectorArithExpr struct { - opType BinOpType - lhs Node - rhs Node - matchCardinality VectorMatchCardinality - matchOn clientmodel.LabelNames - includeLabels clientmodel.LabelNames - } -) - -// VectorMatchCardinality is an enum describing vector matches (1:1, n:1, 1:n, n:m). -type VectorMatchCardinality int - -// Constants for VectorMatchCardinality enum. -const ( - MatchOneToOne VectorMatchCardinality = iota - MatchManyToOne - MatchOneToMany - MatchManyToMany -) - -// ---------------------------------------------------------------------------- -// MatrixNode types. - -type ( - // A MatrixSelector represents a metric name plus labelset and - // timerange. - MatrixSelector struct { - labelMatchers metric.LabelMatchers - // The series iterators are populated at query analysis time. - iterators map[clientmodel.Fingerprint]local.SeriesIterator - metrics map[clientmodel.Fingerprint]clientmodel.COWMetric - // Fingerprints are populated from label matchers at query analysis time. - fingerprints clientmodel.Fingerprints - interval time.Duration - offset time.Duration - } -) - -// ---------------------------------------------------------------------------- -// StringNode types. - -type ( - // A StringLiteral is what you think it is. - StringLiteral struct { - str string - } - - // StringFunctionCall represents a function with string return - // type. - StringFunctionCall struct { - function *Function - args Nodes - } -) - -// ---------------------------------------------------------------------------- -// Implementations. - -// Type implements the Node interface. -func (node ScalarLiteral) Type() ExprType { return ScalarType } - -// Type implements the Node interface. -func (node ScalarFunctionCall) Type() ExprType { return ScalarType } - -// Type implements the Node interface. -func (node ScalarArithExpr) Type() ExprType { return ScalarType } - -// Type implements the Node interface. -func (node VectorSelector) Type() ExprType { return VectorType } - -// Type implements the Node interface. -func (node VectorFunctionCall) Type() ExprType { return VectorType } - -// Type implements the Node interface. -func (node VectorAggregation) Type() ExprType { return VectorType } - -// Type implements the Node interface. -func (node VectorArithExpr) Type() ExprType { return VectorType } - -// Type implements the Node interface. -func (node MatrixSelector) Type() ExprType { return MatrixType } - -// Type implements the Node interface. -func (node StringLiteral) Type() ExprType { return StringType } - -// Type implements the Node interface. -func (node StringFunctionCall) Type() ExprType { return StringType } - -// Children implements the Node interface and returns an empty slice. -func (node ScalarLiteral) Children() Nodes { return Nodes{} } - -// Children implements the Node interface and returns the args of the -// function call. -func (node ScalarFunctionCall) Children() Nodes { return node.args } - -// Children implements the Node interface and returns the LHS and the RHS -// of the expression. -func (node ScalarArithExpr) Children() Nodes { return Nodes{node.lhs, node.rhs} } - -// Children implements the Node interface and returns an empty slice. -func (node VectorSelector) Children() Nodes { return Nodes{} } - -// Children implements the Node interface and returns the args of the -// function call. -func (node VectorFunctionCall) Children() Nodes { return node.args } - -// Children implements the Node interface and returns the vector to be -// aggregated. -func (node VectorAggregation) Children() Nodes { return Nodes{node.vector} } - -// Children implements the Node interface and returns the LHS and the RHS -// of the expression. -func (node VectorArithExpr) Children() Nodes { return Nodes{node.lhs, node.rhs} } - -// Children implements the Node interface and returns an empty slice. -func (node MatrixSelector) Children() Nodes { return Nodes{} } - -// Children implements the Node interface and returns an empty slice. -func (node StringLiteral) Children() Nodes { return Nodes{} } - -// Children implements the Node interface and returns the args of the -// function call. -func (node StringFunctionCall) Children() Nodes { return node.args } - -// Eval implements the ScalarNode interface and returns the selector -// value. -func (node *ScalarLiteral) Eval(timestamp clientmodel.Timestamp) clientmodel.SampleValue { - return node.value -} - -// Eval implements the ScalarNode interface and returns the result of -// the expression. -func (node *ScalarArithExpr) Eval(timestamp clientmodel.Timestamp) clientmodel.SampleValue { - lhs := node.lhs.Eval(timestamp) - rhs := node.rhs.Eval(timestamp) - return evalScalarBinop(node.opType, lhs, rhs) -} - -// Eval implements the ScalarNode interface and returns the result of -// the function call. -func (node *ScalarFunctionCall) Eval(timestamp clientmodel.Timestamp) clientmodel.SampleValue { - return node.function.callFn(timestamp, node.args).(clientmodel.SampleValue) -} - -// EvalVectorInstant evaluates a VectorNode with an instant query. -func EvalVectorInstant(node VectorNode, timestamp clientmodel.Timestamp, storage local.Storage, queryStats *stats.TimerGroup) (Vector, error) { - totalEvalTimer := queryStats.GetTimer(stats.TotalEvalTime).Start() - defer totalEvalTimer.Stop() - - closer, err := prepareInstantQuery(node, timestamp, storage, queryStats) - if err != nil { - return nil, err - } - defer closer.Close() - if et := totalEvalTimer.ElapsedTime(); et > *queryTimeout { - return nil, queryTimeoutError{et} - } - return node.Eval(timestamp), nil -} - -// EvalVectorRange evaluates a VectorNode with a range query. -func EvalVectorRange(node VectorNode, start clientmodel.Timestamp, end clientmodel.Timestamp, interval time.Duration, storage local.Storage, queryStats *stats.TimerGroup) (Matrix, error) { - totalEvalTimer := queryStats.GetTimer(stats.TotalEvalTime).Start() - defer totalEvalTimer.Stop() - // Explicitly initialize to an empty matrix since a nil Matrix encodes to - // null in JSON. - matrix := Matrix{} - - prepareTimer := queryStats.GetTimer(stats.TotalQueryPreparationTime).Start() - closer, err := prepareRangeQuery(node, start, end, interval, storage, queryStats) - prepareTimer.Stop() - if err != nil { - return nil, err - } - defer closer.Close() - - evalTimer := queryStats.GetTimer(stats.InnerEvalTime).Start() - sampleStreams := map[clientmodel.Fingerprint]*SampleStream{} - for t := start; !t.After(end); t = t.Add(interval) { - if et := totalEvalTimer.ElapsedTime(); et > *queryTimeout { - evalTimer.Stop() - return nil, queryTimeoutError{et} - } - vector := node.Eval(t) - for _, sample := range vector { - samplePair := metric.SamplePair{ - Value: sample.Value, - Timestamp: sample.Timestamp, - } - fp := sample.Metric.Metric.Fingerprint() - if sampleStreams[fp] == nil { - sampleStreams[fp] = &SampleStream{ - Metric: sample.Metric, - Values: metric.Values{samplePair}, - } - } else { - sampleStreams[fp].Values = append(sampleStreams[fp].Values, samplePair) - } - } - } - evalTimer.Stop() - - appendTimer := queryStats.GetTimer(stats.ResultAppendTime).Start() - for _, sampleStream := range sampleStreams { - matrix = append(matrix, *sampleStream) - } - appendTimer.Stop() - - return matrix, nil -} - -func labelIntersection(metric1, metric2 clientmodel.COWMetric) clientmodel.COWMetric { - for label, value := range metric1.Metric { - if metric2.Metric[label] != value { - metric1.Delete(label) - } - } - return metric1 -} - -func (node *VectorAggregation) groupedAggregationsToVector(aggregations map[uint64]*groupedAggregation, timestamp clientmodel.Timestamp) Vector { - vector := Vector{} - for _, aggregation := range aggregations { - switch node.aggrType { - case Avg: - aggregation.value = aggregation.value / clientmodel.SampleValue(aggregation.groupCount) - case Count: - aggregation.value = clientmodel.SampleValue(aggregation.groupCount) - default: - // For other aggregations, we already have the right value. - } - sample := &Sample{ - Metric: aggregation.labels, - Value: aggregation.value, - Timestamp: timestamp, - } - vector = append(vector, sample) - } - return vector -} - -// Eval implements the VectorNode interface and returns the aggregated -// Vector. -func (node *VectorAggregation) Eval(timestamp clientmodel.Timestamp) Vector { - vector := node.vector.Eval(timestamp) - result := map[uint64]*groupedAggregation{} - for _, sample := range vector { - groupingKey := clientmodel.SignatureForLabels(sample.Metric.Metric, node.groupBy) - if groupedResult, ok := result[groupingKey]; ok { - if node.keepExtraLabels { - groupedResult.labels = labelIntersection(groupedResult.labels, sample.Metric) - } - - switch node.aggrType { - case Sum: - groupedResult.value += sample.Value - case Avg: - groupedResult.value += sample.Value - groupedResult.groupCount++ - case Max: - if groupedResult.value < sample.Value { - groupedResult.value = sample.Value - } - case Min: - if groupedResult.value > sample.Value { - groupedResult.value = sample.Value - } - case Count: - groupedResult.groupCount++ - default: - panic("Unknown aggregation type") - } - } else { - var m clientmodel.COWMetric - if node.keepExtraLabels { - m = sample.Metric - m.Delete(clientmodel.MetricNameLabel) - } else { - m = clientmodel.COWMetric{ - Metric: clientmodel.Metric{}, - Copied: true, - } - for _, l := range node.groupBy { - if v, ok := sample.Metric.Metric[l]; ok { - m.Set(l, v) - } - } - } - result[groupingKey] = &groupedAggregation{ - labels: m, - value: sample.Value, - groupCount: 1, - } - } - } - - return node.groupedAggregationsToVector(result, timestamp) -} - -// Eval implements the VectorNode interface and returns the value of -// the selector. -func (node *VectorSelector) Eval(timestamp clientmodel.Timestamp) Vector { - //// timer := v.stats.GetTimer(stats.GetValueAtTimeTime).Start() - samples := Vector{} - for fp, it := range node.iterators { - sampleCandidates := it.GetValueAtTime(timestamp.Add(-node.offset)) - samplePair := chooseClosestSample(sampleCandidates, timestamp.Add(-node.offset)) - if samplePair != nil { - samples = append(samples, &Sample{ - Metric: node.metrics[fp], - Value: samplePair.Value, - Timestamp: timestamp, - }) - } - } - //// timer.Stop() - return samples -} - -// chooseClosestSample chooses the closest sample of a list of samples -// surrounding a given target time. If samples are found both before and after -// the target time, the sample value is interpolated between these. Otherwise, -// the single closest sample is returned verbatim. -func chooseClosestSample(samples metric.Values, timestamp clientmodel.Timestamp) *metric.SamplePair { - var closestBefore *metric.SamplePair - var closestAfter *metric.SamplePair - for _, candidate := range samples { - delta := candidate.Timestamp.Sub(timestamp) - // Samples before target time. - if delta < 0 { - // Ignore samples outside of staleness policy window. - if -delta > *stalenessDelta { - continue - } - // Ignore samples that are farther away than what we've seen before. - if closestBefore != nil && candidate.Timestamp.Before(closestBefore.Timestamp) { - continue - } - sample := candidate - closestBefore = &sample - } - - // Samples after target time. - if delta >= 0 { - // Ignore samples outside of staleness policy window. - if delta > *stalenessDelta { - continue - } - // Ignore samples that are farther away than samples we've seen before. - if closestAfter != nil && candidate.Timestamp.After(closestAfter.Timestamp) { - continue - } - sample := candidate - closestAfter = &sample - } - } - - switch { - case closestBefore != nil && closestAfter != nil: - return interpolateSamples(closestBefore, closestAfter, timestamp) - case closestBefore != nil: - return closestBefore - default: - return closestAfter - } -} - -// interpolateSamples interpolates a value at a target time between two -// provided sample pairs. -func interpolateSamples(first, second *metric.SamplePair, timestamp clientmodel.Timestamp) *metric.SamplePair { - dv := second.Value - first.Value - dt := second.Timestamp.Sub(first.Timestamp) - - dDt := dv / clientmodel.SampleValue(dt) - offset := clientmodel.SampleValue(timestamp.Sub(first.Timestamp)) - - return &metric.SamplePair{ - Value: first.Value + (offset * dDt), - Timestamp: timestamp, - } -} - -// Eval implements the VectorNode interface and returns the result of -// the function call. -func (node *VectorFunctionCall) Eval(timestamp clientmodel.Timestamp) Vector { - return node.function.callFn(timestamp, node.args).(Vector) -} - -func evalScalarBinop(opType BinOpType, - lhs clientmodel.SampleValue, - rhs clientmodel.SampleValue) clientmodel.SampleValue { - switch opType { - case Add: - return lhs + rhs - case Sub: - return lhs - rhs - case Mul: - return lhs * rhs - case Div: - return lhs / rhs - case Mod: - if rhs != 0 { - return clientmodel.SampleValue(int(lhs) % int(rhs)) - } - return clientmodel.SampleValue(math.NaN()) - case EQ: - if lhs == rhs { - return 1 - } - return 0 - case NE: - if lhs != rhs { - return 1 - } - return 0 - case GT: - if lhs > rhs { - return 1 - } - return 0 - case LT: - if lhs < rhs { - return 1 - } - return 0 - case GE: - if lhs >= rhs { - return 1 - } - return 0 - case LE: - if lhs <= rhs { - return 1 - } - return 0 - } - panic("Not all enum values enumerated in switch") -} - -func evalVectorBinop(opType BinOpType, - lhs clientmodel.SampleValue, - rhs clientmodel.SampleValue) (clientmodel.SampleValue, bool) { - switch opType { - case Add: - return lhs + rhs, true - case Sub: - return lhs - rhs, true - case Mul: - return lhs * rhs, true - case Div: - return lhs / rhs, true - case Mod: - if rhs != 0 { - return clientmodel.SampleValue(int(lhs) % int(rhs)), true - } - return clientmodel.SampleValue(math.NaN()), true - case EQ: - if lhs == rhs { - return lhs, true - } - return 0, false - case NE: - if lhs != rhs { - return lhs, true - } - return 0, false - case GT: - if lhs > rhs { - return lhs, true - } - return 0, false - case LT: - if lhs < rhs { - return lhs, true - } - return 0, false - case GE: - if lhs >= rhs { - return lhs, true - } - return 0, false - case LE: - if lhs <= rhs { - return lhs, true - } - return 0, false - } - panic("Not all enum values enumerated in switch") -} - -func labelsEqual(labels1, labels2 clientmodel.Metric) bool { - for label, value := range labels1 { - if labels2[label] != value && label != clientmodel.MetricNameLabel { - return false - } - } - return true -} - -// Eval implements the VectorNode interface and returns the result of -// the expression. -func (node *VectorArithExpr) Eval(timestamp clientmodel.Timestamp) Vector { - // Calculate vector-to-vector operation. - if node.lhs.Type() == VectorType && node.rhs.Type() == VectorType { - lhs := node.lhs.(VectorNode).Eval(timestamp) - rhs := node.rhs.(VectorNode).Eval(timestamp) - - return node.evalVectors(timestamp, lhs, rhs) - } - - // Calculate vector-to-scalar operation. - var lhs Vector - var rhs clientmodel.SampleValue - swap := false - - if node.lhs.Type() == ScalarType && node.rhs.Type() == VectorType { - lhs = node.rhs.(VectorNode).Eval(timestamp) - rhs = node.lhs.(ScalarNode).Eval(timestamp) - swap = true - } else { - lhs = node.lhs.(VectorNode).Eval(timestamp) - rhs = node.rhs.(ScalarNode).Eval(timestamp) - } - - result := make(Vector, 0, len(lhs)) - - for _, lhsSample := range lhs { - lv, rv := lhsSample.Value, rhs - // lhs always contains the vector. If the original position was different - // swap for calculating the value. - if swap { - lv, rv = rv, lv - } - value, keep := evalVectorBinop(node.opType, lv, rv) - if keep { - lhsSample.Value = value - if node.opType.shouldDropMetric() { - lhsSample.Metric.Delete(clientmodel.MetricNameLabel) - } - result = append(result, lhsSample) - } - } - return result -} - -// evalVectors evaluates the binary operation for the given vectors. -func (node *VectorArithExpr) evalVectors(timestamp clientmodel.Timestamp, lhs, rhs Vector) Vector { - result := make(Vector, 0, len(rhs)) - // The control flow below handles one-to-one or many-to-one matching. - // For one-to-many, swap sidedness and account for the swap when calculating - // values. - if node.matchCardinality == MatchOneToMany { - lhs, rhs = rhs, lhs - } - // All samples from the rhs hashed by the matching label/values. - rm := make(map[uint64]*Sample) - // Maps the hash of the label values used for matching to the hashes of the label - // values of the include labels (if any). It is used to keep track of already - // inserted samples. - added := make(map[uint64][]uint64) - - // Add all rhs samples to a map so we can easily find matches later. - for _, rs := range rhs { - hash := node.hashForMetric(rs.Metric.Metric) - // The rhs is guaranteed to be the 'one' side. Having multiple samples - // with the same hash means that the matching is many-to-many, - // which is not supported. - if _, found := rm[hash]; node.matchCardinality != MatchManyToMany && found { - // Many-to-many matching not allowed. - // TODO(fabxc): Return a query error here once AST nodes support that. - return Vector{} - } - // In many-to-many matching the entry is simply overwritten. It can thus only - // be used to check whether any matching rhs entry exists but not retrieve them all. - rm[hash] = rs - } - - // For all lhs samples find a respective rhs sample and perform - // the binary operation. - for _, ls := range lhs { - hash := node.hashForMetric(ls.Metric.Metric) - // Any lhs sample we encounter in an OR operation belongs to the result. - if node.opType == Or { - ls.Metric = node.resultMetric(ls, nil) - result = append(result, ls) - added[hash] = nil // Ensure matching rhs sample is not added later. - continue - } - - rs, found := rm[hash] // Look for a match in the rhs vector. - if !found { - continue - } - var value clientmodel.SampleValue - var keep bool - - if node.opType == And { - value = ls.Value - keep = true - } else { - if _, exists := added[hash]; node.matchCardinality == MatchOneToOne && exists { - // Many-to-one matching must be explicit. - // TODO(fabxc): Return a query error here once AST nodes support that. - return Vector{} - } - // Account for potentially swapped sidedness. - vl, vr := ls.Value, rs.Value - if node.matchCardinality == MatchOneToMany { - vl, vr = vr, vl - } - value, keep = evalVectorBinop(node.opType, vl, vr) - } - - if keep { - metric := node.resultMetric(ls, rs) - // Check if the same label set has been added for a many-to-one matching before. - if node.matchCardinality == MatchManyToOne || node.matchCardinality == MatchOneToMany { - insHash := clientmodel.SignatureForLabels(metric.Metric, node.includeLabels) - if ihs, exists := added[hash]; exists { - for _, ih := range ihs { - if ih == insHash { - // TODO(fabxc): Return a query error here once AST nodes support that. - return Vector{} - } - } - added[hash] = append(ihs, insHash) - } else { - added[hash] = []uint64{insHash} - } - } - ns := &Sample{ - Metric: metric, - Value: value, - Timestamp: timestamp, - } - result = append(result, ns) - if _, ok := added[hash]; !ok { - added[hash] = nil // Set existence to true. - } - } - } - - // Add all remaining samples in the rhs in an OR operation if they - // have not been matched up with a lhs sample. - if node.opType == Or { - for hash, rs := range rm { - if _, exists := added[hash]; !exists { - rs.Metric = node.resultMetric(rs, nil) - result = append(result, rs) - } - } - } - return result -} - -// resultMetric returns the metric for the given sample(s) based on the vector -// binary operation and the matching options. If a label that has to be included is set on -// both sides an error is returned. -func (node *VectorArithExpr) resultMetric(ls, rs *Sample) clientmodel.COWMetric { - if len(node.matchOn) == 0 || node.opType == Or || node.opType == And { - if node.opType.shouldDropMetric() { - ls.Metric.Delete(clientmodel.MetricNameLabel) - } - return ls.Metric - } - - m := clientmodel.Metric{} - for _, ln := range node.matchOn { - m[ln] = ls.Metric.Metric[ln] - } - - for _, ln := range node.includeLabels { - // Included labels from the `group_x` modifier are taken from the "many"-side. - v, ok := ls.Metric.Metric[ln] - if ok { - m[ln] = v - } - } - return clientmodel.COWMetric{false, m} -} - -// hashForMetric calculates a hash value for the given metric based on the matching -// options for the binary operation. -func (node *VectorArithExpr) hashForMetric(metric clientmodel.Metric) uint64 { - var labels clientmodel.LabelNames - - if len(node.matchOn) > 0 { - var match bool - for _, ln := range node.matchOn { - if _, match = metric[ln]; !match { - break - } - } - // If the metric does not contain the labels to match on, build the hash - // over the whole metric to give it a unique hash. - if !match { - labels = make(clientmodel.LabelNames, 0, len(metric)) - for ln := range metric { - labels = append(labels, ln) - } - } else { - labels = node.matchOn - } - } else { - labels = make(clientmodel.LabelNames, 0, len(metric)) - for ln := range metric { - if ln != clientmodel.MetricNameLabel { - labels = append(labels, ln) - } - } - } - return clientmodel.SignatureForLabels(metric, labels) -} - -// Eval implements the MatrixNode interface and returns the value of -// the selector. -func (node *MatrixSelector) Eval(timestamp clientmodel.Timestamp) Matrix { - interval := &metric.Interval{ - OldestInclusive: timestamp.Add(-node.interval - node.offset), - NewestInclusive: timestamp.Add(-node.offset), - } - - //// timer := v.stats.GetTimer(stats.GetRangeValuesTime).Start() - sampleStreams := []SampleStream{} - for fp, it := range node.iterators { - samplePairs := it.GetRangeValues(*interval) - if len(samplePairs) == 0 { - continue - } - - if node.offset != 0 { - for _, sp := range samplePairs { - sp.Timestamp = sp.Timestamp.Add(node.offset) - } - } - - sampleStream := SampleStream{ - Metric: node.metrics[fp], - Values: samplePairs, - } - sampleStreams = append(sampleStreams, sampleStream) - } - //// timer.Stop() - return sampleStreams -} - -// EvalBoundaries implements the MatrixNode interface and returns the -// boundary values of the selector. -func (node *MatrixSelector) EvalBoundaries(timestamp clientmodel.Timestamp) Matrix { - interval := &metric.Interval{ - OldestInclusive: timestamp.Add(-node.interval), - NewestInclusive: timestamp, - } - - //// timer := v.stats.GetTimer(stats.GetBoundaryValuesTime).Start() - sampleStreams := []SampleStream{} - for fp, it := range node.iterators { - samplePairs := it.GetBoundaryValues(*interval) - if len(samplePairs) == 0 { - continue - } - - sampleStream := SampleStream{ - Metric: node.metrics[fp], - Values: samplePairs, - } - sampleStreams = append(sampleStreams, sampleStream) - } - //// timer.Stop() - return sampleStreams -} - -// Len implements sort.Interface. -func (matrix Matrix) Len() int { - return len(matrix) -} - -// Less implements sort.Interface. -func (matrix Matrix) Less(i, j int) bool { - return matrix[i].Metric.String() < matrix[j].Metric.String() -} - -// Swap implements sort.Interface. -func (matrix Matrix) Swap(i, j int) { - matrix[i], matrix[j] = matrix[j], matrix[i] -} - -// Eval implements the StringNode interface and returns the value of -// the selector. -func (node *StringLiteral) Eval(timestamp clientmodel.Timestamp) string { - return node.str -} - -// Eval implements the StringNode interface and returns the result of -// the function call. -func (node *StringFunctionCall) Eval(timestamp clientmodel.Timestamp) string { - return node.function.callFn(timestamp, node.args).(string) -} - -// ---------------------------------------------------------------------------- -// Constructors. - -// NewScalarLiteral returns a ScalarLiteral with the given value. -func NewScalarLiteral(value clientmodel.SampleValue) *ScalarLiteral { - return &ScalarLiteral{ - value: value, - } -} - -// NewVectorSelector returns a (not yet evaluated) VectorSelector with -// the given LabelSet. -func NewVectorSelector(m metric.LabelMatchers, offset time.Duration) *VectorSelector { - return &VectorSelector{ - labelMatchers: m, - offset: offset, - iterators: map[clientmodel.Fingerprint]local.SeriesIterator{}, - metrics: map[clientmodel.Fingerprint]clientmodel.COWMetric{}, - } -} - -// NewVectorAggregation returns a (not yet evaluated) -// VectorAggregation, aggregating the given VectorNode using the given -// AggrType, grouping by the given LabelNames. -func NewVectorAggregation(aggrType AggrType, vector VectorNode, groupBy clientmodel.LabelNames, keepExtraLabels bool) *VectorAggregation { - return &VectorAggregation{ - aggrType: aggrType, - groupBy: groupBy, - keepExtraLabels: keepExtraLabels, - vector: vector, - } -} - -// NewFunctionCall returns a (not yet evaluated) function call node -// (of type ScalarFunctionCall, VectorFunctionCall, or -// StringFunctionCall). -func NewFunctionCall(function *Function, args Nodes) (Node, error) { - if err := function.CheckArgTypes(args); err != nil { - return nil, err - } - switch function.returnType { - case ScalarType: - return &ScalarFunctionCall{ - function: function, - args: args, - }, nil - case VectorType: - return &VectorFunctionCall{ - function: function, - args: args, - }, nil - case StringType: - return &StringFunctionCall{ - function: function, - args: args, - }, nil - } - panic("Function with invalid return type") -} - -func nodesHaveTypes(nodes Nodes, exprTypes []ExprType) bool { - for _, node := range nodes { - correctType := false - for _, exprType := range exprTypes { - if node.Type() == exprType { - correctType = true - } - } - if !correctType { - return false - } - } - return true -} - -// NewArithExpr returns a (not yet evaluated) expression node (of type -// VectorArithExpr or ScalarArithExpr). -func NewArithExpr(opType BinOpType, lhs Node, rhs Node, matchCard VectorMatchCardinality, matchOn, include clientmodel.LabelNames) (Node, error) { - if !nodesHaveTypes(Nodes{lhs, rhs}, []ExprType{ScalarType, VectorType}) { - return nil, errors.New("binary operands must be of vector or scalar type") - } - - if opType == And || opType == Or { - if lhs.Type() == ScalarType || rhs.Type() == ScalarType { - return nil, errors.New("AND and OR operators may only be used between vectors") - } - // Logical operations must never be used with group modifiers. - if len(include) > 0 { - return nil, errors.New("AND and OR operators must not have a group modifier") - } - } - if lhs.Type() != VectorType || rhs.Type() != VectorType { - if matchCard != MatchOneToOne || matchOn != nil || include != nil { - return nil, errors.New("binary scalar expressions cannot have vector matching options") - } - } - - if lhs.Type() == VectorType || rhs.Type() == VectorType { - return &VectorArithExpr{ - opType: opType, - lhs: lhs, - rhs: rhs, - matchCardinality: matchCard, - matchOn: matchOn, - includeLabels: include, - }, nil - } - - return &ScalarArithExpr{ - opType: opType, - lhs: lhs.(ScalarNode), - rhs: rhs.(ScalarNode), - }, nil -} - -// NewMatrixSelector returns a (not yet evaluated) MatrixSelector with -// the given VectorSelector and Duration. -func NewMatrixSelector(vector *VectorSelector, interval time.Duration, offset time.Duration) *MatrixSelector { - return &MatrixSelector{ - labelMatchers: vector.labelMatchers, - interval: interval, - offset: offset, - iterators: map[clientmodel.Fingerprint]local.SeriesIterator{}, - metrics: map[clientmodel.Fingerprint]clientmodel.COWMetric{}, - } -} - -// NewStringLiteral returns a StringLiteral with the given string as -// value. -func NewStringLiteral(str string) *StringLiteral { - return &StringLiteral{ - str: str, - } -} diff --git a/rules/ast/functions.go b/rules/ast/functions.go deleted file mode 100644 index ea95a1966a..0000000000 --- a/rules/ast/functions.go +++ /dev/null @@ -1,752 +0,0 @@ -// Copyright 2013 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 ast - -import ( - "container/heap" - "fmt" - "math" - "sort" - "strconv" - "time" - - clientmodel "github.com/prometheus/client_golang/model" - - "github.com/prometheus/prometheus/storage/metric" -) - -// Function represents a function of the expression language and is -// used by function nodes. -type Function struct { - name string - argTypes []ExprType - optionalArgs int - returnType ExprType - callFn func(timestamp clientmodel.Timestamp, args []Node) interface{} -} - -// CheckArgTypes returns a non-nil error if the number or types of -// passed in arg nodes do not match the function's expectations. -func (function *Function) CheckArgTypes(args []Node) error { - if len(function.argTypes) < len(args) { - return fmt.Errorf( - "too many arguments to function %v(): %v expected at most, %v given", - function.name, len(function.argTypes), len(args), - ) - } - if len(function.argTypes)-function.optionalArgs > len(args) { - return fmt.Errorf( - "too few arguments to function %v(): %v expected at least, %v given", - function.name, len(function.argTypes)-function.optionalArgs, len(args), - ) - } - for idx, arg := range args { - invalidType := false - var expectedType string - if _, ok := arg.(ScalarNode); function.argTypes[idx] == ScalarType && !ok { - invalidType = true - expectedType = "scalar" - } - if _, ok := arg.(VectorNode); function.argTypes[idx] == VectorType && !ok { - invalidType = true - expectedType = "vector" - } - if _, ok := arg.(MatrixNode); function.argTypes[idx] == MatrixType && !ok { - invalidType = true - expectedType = "matrix" - } - if _, ok := arg.(StringNode); function.argTypes[idx] == StringType && !ok { - invalidType = true - expectedType = "string" - } - - if invalidType { - return fmt.Errorf( - "wrong type for argument %v in function %v(), expected %v", - idx, function.name, expectedType, - ) - } - } - return nil -} - -// === time() clientmodel.SampleValue === -func timeImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - return clientmodel.SampleValue(timestamp.Unix()) -} - -// === delta(matrix MatrixNode, isCounter=0 ScalarNode) Vector === -func deltaImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - matrixNode := args[0].(MatrixNode) - isCounter := len(args) >= 2 && args[1].(ScalarNode).Eval(timestamp) > 0 - resultVector := Vector{} - - // If we treat these metrics as counters, we need to fetch all values - // in the interval to find breaks in the timeseries' monotonicity. - // I.e. if a counter resets, we want to ignore that reset. - var matrixValue Matrix - if isCounter { - matrixValue = matrixNode.Eval(timestamp) - } else { - matrixValue = matrixNode.EvalBoundaries(timestamp) - } - for _, samples := range matrixValue { - // No sense in trying to compute a delta without at least two points. Drop - // this vector element. - if len(samples.Values) < 2 { - continue - } - - counterCorrection := clientmodel.SampleValue(0) - lastValue := clientmodel.SampleValue(0) - for _, sample := range samples.Values { - currentValue := sample.Value - if isCounter && currentValue < lastValue { - counterCorrection += lastValue - currentValue - } - lastValue = currentValue - } - resultValue := lastValue - samples.Values[0].Value + counterCorrection - - targetInterval := args[0].(*MatrixSelector).interval - sampledInterval := samples.Values[len(samples.Values)-1].Timestamp.Sub(samples.Values[0].Timestamp) - if sampledInterval == 0 { - // Only found one sample. Cannot compute a rate from this. - continue - } - // Correct for differences in target vs. actual delta interval. - // - // Above, we didn't actually calculate the delta for the specified target - // interval, but for an interval between the first and last found samples - // under the target interval, which will usually have less time between - // them. Depending on how many samples are found under a target interval, - // the delta results are distorted and temporal aliasing occurs (ugly - // bumps). This effect is corrected for below. - intervalCorrection := clientmodel.SampleValue(targetInterval) / clientmodel.SampleValue(sampledInterval) - resultValue *= intervalCorrection - - resultSample := &Sample{ - Metric: samples.Metric, - Value: resultValue, - Timestamp: timestamp, - } - resultSample.Metric.Delete(clientmodel.MetricNameLabel) - resultVector = append(resultVector, resultSample) - } - return resultVector -} - -// === rate(node MatrixNode) Vector === -func rateImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - args = append(args, &ScalarLiteral{value: 1}) - vector := deltaImpl(timestamp, args).(Vector) - - // TODO: could be other type of MatrixNode in the future (right now, only - // MatrixSelector exists). Find a better way of getting the duration of a - // matrix, such as looking at the samples themselves. - interval := args[0].(*MatrixSelector).interval - for i := range vector { - vector[i].Value /= clientmodel.SampleValue(interval / time.Second) - } - return vector -} - -type vectorByValueHeap Vector - -func (s vectorByValueHeap) Len() int { - return len(s) -} - -func (s vectorByValueHeap) Less(i, j int) bool { - return s[i].Value < s[j].Value -} - -func (s vectorByValueHeap) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -func (s *vectorByValueHeap) Push(x interface{}) { - *s = append(*s, x.(*Sample)) -} - -func (s *vectorByValueHeap) Pop() interface{} { - old := *s - n := len(old) - el := old[n-1] - *s = old[0 : n-1] - return el -} - -type reverseHeap struct { - heap.Interface -} - -func (s reverseHeap) Less(i, j int) bool { - return s.Interface.Less(j, i) -} - -// === sort(node VectorNode) Vector === -func sortImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - byValueSorter := vectorByValueHeap(args[0].(VectorNode).Eval(timestamp)) - sort.Sort(byValueSorter) - return Vector(byValueSorter) -} - -// === sortDesc(node VectorNode) Vector === -func sortDescImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - byValueSorter := vectorByValueHeap(args[0].(VectorNode).Eval(timestamp)) - sort.Sort(sort.Reverse(byValueSorter)) - return Vector(byValueSorter) -} - -// === topk(k ScalarNode, node VectorNode) Vector === -func topkImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - k := int(args[0].(ScalarNode).Eval(timestamp)) - if k < 1 { - return Vector{} - } - - topk := make(vectorByValueHeap, 0, k) - vector := args[1].(VectorNode).Eval(timestamp) - - for _, el := range vector { - if len(topk) < k || topk[0].Value < el.Value { - if len(topk) == k { - heap.Pop(&topk) - } - heap.Push(&topk, el) - } - } - sort.Sort(sort.Reverse(topk)) - return Vector(topk) -} - -// === bottomk(k ScalarNode, node VectorNode) Vector === -func bottomkImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - k := int(args[0].(ScalarNode).Eval(timestamp)) - if k < 1 { - return Vector{} - } - - bottomk := make(vectorByValueHeap, 0, k) - bkHeap := reverseHeap{Interface: &bottomk} - vector := args[1].(VectorNode).Eval(timestamp) - - for _, el := range vector { - if len(bottomk) < k || bottomk[0].Value > el.Value { - if len(bottomk) == k { - heap.Pop(&bkHeap) - } - heap.Push(&bkHeap, el) - } - } - sort.Sort(bottomk) - return Vector(bottomk) -} - -// === drop_common_labels(node VectorNode) Vector === -func dropCommonLabelsImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - vector := args[0].(VectorNode).Eval(timestamp) - if len(vector) < 1 { - return Vector{} - } - common := clientmodel.LabelSet{} - for k, v := range vector[0].Metric.Metric { - // TODO(julius): Should we also drop common metric names? - if k == clientmodel.MetricNameLabel { - continue - } - common[k] = v - } - - for _, el := range vector[1:] { - for k, v := range common { - if el.Metric.Metric[k] != v { - // Deletion of map entries while iterating over them is safe. - // From http://golang.org/ref/spec#For_statements: - // "If map entries that have not yet been reached are deleted during - // iteration, the corresponding iteration values will not be produced." - delete(common, k) - } - } - } - - for _, el := range vector { - for k := range el.Metric.Metric { - if _, ok := common[k]; ok { - el.Metric.Delete(k) - } - } - } - return vector -} - -// === round(vector VectorNode, toNearest=1 Scalar) Vector === -func roundImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - // round returns a number rounded to toNearest. - // Ties are solved by rounding up. - toNearest := float64(1) - if len(args) >= 2 { - toNearest = float64(args[1].(ScalarNode).Eval(timestamp)) - } - // Invert as it seems to cause fewer floating point accuracy issues. - toNearestInverse := 1.0 / toNearest - - n := args[0].(VectorNode) - vector := n.Eval(timestamp) - for _, el := range vector { - el.Metric.Delete(clientmodel.MetricNameLabel) - el.Value = clientmodel.SampleValue(math.Floor(float64(el.Value)*toNearestInverse+0.5) / toNearestInverse) - } - return vector -} - -// === scalar(node VectorNode) Scalar === -func scalarImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - v := args[0].(VectorNode).Eval(timestamp) - if len(v) != 1 { - return clientmodel.SampleValue(math.NaN()) - } - return clientmodel.SampleValue(v[0].Value) -} - -// === count_scalar(vector VectorNode) model.SampleValue === -func countScalarImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - return clientmodel.SampleValue(len(args[0].(VectorNode).Eval(timestamp))) -} - -func aggrOverTime(timestamp clientmodel.Timestamp, args []Node, aggrFn func(metric.Values) clientmodel.SampleValue) interface{} { - n := args[0].(MatrixNode) - matrixVal := n.Eval(timestamp) - resultVector := Vector{} - - for _, el := range matrixVal { - if len(el.Values) == 0 { - continue - } - - el.Metric.Delete(clientmodel.MetricNameLabel) - resultVector = append(resultVector, &Sample{ - Metric: el.Metric, - Value: aggrFn(el.Values), - Timestamp: timestamp, - }) - } - return resultVector -} - -// === avg_over_time(matrix MatrixNode) Vector === -func avgOverTimeImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - return aggrOverTime(timestamp, args, func(values metric.Values) clientmodel.SampleValue { - var sum clientmodel.SampleValue - for _, v := range values { - sum += v.Value - } - return sum / clientmodel.SampleValue(len(values)) - }) -} - -// === count_over_time(matrix MatrixNode) Vector === -func countOverTimeImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - return aggrOverTime(timestamp, args, func(values metric.Values) clientmodel.SampleValue { - return clientmodel.SampleValue(len(values)) - }) -} - -// === floor(vector VectorNode) Vector === -func floorImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - n := args[0].(VectorNode) - vector := n.Eval(timestamp) - for _, el := range vector { - el.Metric.Delete(clientmodel.MetricNameLabel) - el.Value = clientmodel.SampleValue(math.Floor(float64(el.Value))) - } - return vector -} - -// === max_over_time(matrix MatrixNode) Vector === -func maxOverTimeImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - return aggrOverTime(timestamp, args, func(values metric.Values) clientmodel.SampleValue { - max := math.Inf(-1) - for _, v := range values { - max = math.Max(max, float64(v.Value)) - } - return clientmodel.SampleValue(max) - }) -} - -// === min_over_time(matrix MatrixNode) Vector === -func minOverTimeImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - return aggrOverTime(timestamp, args, func(values metric.Values) clientmodel.SampleValue { - min := math.Inf(1) - for _, v := range values { - min = math.Min(min, float64(v.Value)) - } - return clientmodel.SampleValue(min) - }) -} - -// === sum_over_time(matrix MatrixNode) Vector === -func sumOverTimeImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - return aggrOverTime(timestamp, args, func(values metric.Values) clientmodel.SampleValue { - var sum clientmodel.SampleValue - for _, v := range values { - sum += v.Value - } - return sum - }) -} - -// === abs(vector VectorNode) Vector === -func absImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - n := args[0].(VectorNode) - vector := n.Eval(timestamp) - for _, el := range vector { - el.Metric.Delete(clientmodel.MetricNameLabel) - el.Value = clientmodel.SampleValue(math.Abs(float64(el.Value))) - } - return vector -} - -// === absent(vector VectorNode) Vector === -func absentImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - n := args[0].(VectorNode) - if len(n.Eval(timestamp)) > 0 { - return Vector{} - } - m := clientmodel.Metric{} - if vs, ok := n.(*VectorSelector); ok { - for _, matcher := range vs.labelMatchers { - if matcher.Type == metric.Equal && matcher.Name != clientmodel.MetricNameLabel { - m[matcher.Name] = matcher.Value - } - } - } - return Vector{ - &Sample{ - Metric: clientmodel.COWMetric{ - Metric: m, - Copied: true, - }, - Value: 1, - Timestamp: timestamp, - }, - } -} - -// === ceil(vector VectorNode) Vector === -func ceilImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - n := args[0].(VectorNode) - vector := n.Eval(timestamp) - for _, el := range vector { - el.Metric.Delete(clientmodel.MetricNameLabel) - el.Value = clientmodel.SampleValue(math.Ceil(float64(el.Value))) - } - return vector -} - -// === exp(vector VectorNode) Vector === -func expImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - n := args[0].(VectorNode) - vector := n.Eval(timestamp) - for _, el := range vector { - el.Metric.Delete(clientmodel.MetricNameLabel) - el.Value = clientmodel.SampleValue(math.Exp(float64(el.Value))) - } - return vector -} - -// === ln(vector VectorNode) Vector === -func lnImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - n := args[0].(VectorNode) - vector := n.Eval(timestamp) - for _, el := range vector { - el.Metric.Delete(clientmodel.MetricNameLabel) - el.Value = clientmodel.SampleValue(math.Log(float64(el.Value))) - } - return vector -} - -// === log2(vector VectorNode) Vector === -func log2Impl(timestamp clientmodel.Timestamp, args []Node) interface{} { - n := args[0].(VectorNode) - vector := n.Eval(timestamp) - for _, el := range vector { - el.Metric.Delete(clientmodel.MetricNameLabel) - el.Value = clientmodel.SampleValue(math.Log2(float64(el.Value))) - } - return vector -} - -// === log10(vector VectorNode) Vector === -func log10Impl(timestamp clientmodel.Timestamp, args []Node) interface{} { - n := args[0].(VectorNode) - vector := n.Eval(timestamp) - for _, el := range vector { - el.Metric.Delete(clientmodel.MetricNameLabel) - el.Value = clientmodel.SampleValue(math.Log10(float64(el.Value))) - } - return vector -} - -// === deriv(node MatrixNode) Vector === -func derivImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - matrixNode := args[0].(MatrixNode) - resultVector := Vector{} - - matrixValue := matrixNode.Eval(timestamp) - for _, samples := range matrixValue { - // No sense in trying to compute a derivative without at least two points. - // Drop this vector element. - if len(samples.Values) < 2 { - continue - } - - // Least squares. - n := clientmodel.SampleValue(0) - sumY := clientmodel.SampleValue(0) - sumX := clientmodel.SampleValue(0) - sumXY := clientmodel.SampleValue(0) - sumX2 := clientmodel.SampleValue(0) - for _, sample := range samples.Values { - x := clientmodel.SampleValue(sample.Timestamp.UnixNano() / 1e9) - n += 1.0 - sumY += sample.Value - sumX += x - sumXY += x * sample.Value - sumX2 += x * x - } - numerator := sumXY - sumX*sumY/n - denominator := sumX2 - (sumX*sumX)/n - - resultValue := numerator / denominator - - resultSample := &Sample{ - Metric: samples.Metric, - Value: resultValue, - Timestamp: timestamp, - } - resultSample.Metric.Delete(clientmodel.MetricNameLabel) - resultVector = append(resultVector, resultSample) - } - return resultVector -} - -// === histogram_quantile(k ScalarNode, vector VectorNode) Vector === -func histogramQuantileImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - q := args[0].(ScalarNode).Eval(timestamp) - inVec := args[1].(VectorNode).Eval(timestamp) - outVec := Vector{} - signatureToMetricWithBuckets := map[uint64]*metricWithBuckets{} - for _, el := range inVec { - upperBound, err := strconv.ParseFloat( - string(el.Metric.Metric[clientmodel.BucketLabel]), 64, - ) - if err != nil { - // Oops, no bucket label or malformed label value. Skip. - // TODO(beorn7): Issue a warning somehow. - continue - } - signature := clientmodel.SignatureWithoutLabels(el.Metric.Metric, excludedLabels) - mb, ok := signatureToMetricWithBuckets[signature] - if !ok { - el.Metric.Delete(clientmodel.BucketLabel) - el.Metric.Delete(clientmodel.MetricNameLabel) - mb = &metricWithBuckets{el.Metric, nil} - signatureToMetricWithBuckets[signature] = mb - } - mb.buckets = append(mb.buckets, bucket{upperBound, el.Value}) - } - - for _, mb := range signatureToMetricWithBuckets { - outVec = append(outVec, &Sample{ - Metric: mb.metric, - Value: clientmodel.SampleValue(quantile(q, mb.buckets)), - Timestamp: timestamp, - }) - } - - return outVec -} - -var functions = map[string]*Function{ - "abs": { - name: "abs", - argTypes: []ExprType{VectorType}, - returnType: VectorType, - callFn: absImpl, - }, - "absent": { - name: "absent", - argTypes: []ExprType{VectorType}, - returnType: VectorType, - callFn: absentImpl, - }, - "avg_over_time": { - name: "avg_over_time", - argTypes: []ExprType{MatrixType}, - returnType: VectorType, - callFn: avgOverTimeImpl, - }, - "bottomk": { - name: "bottomk", - argTypes: []ExprType{ScalarType, VectorType}, - returnType: VectorType, - callFn: bottomkImpl, - }, - "ceil": { - name: "ceil", - argTypes: []ExprType{VectorType}, - returnType: VectorType, - callFn: ceilImpl, - }, - "count_over_time": { - name: "count_over_time", - argTypes: []ExprType{MatrixType}, - returnType: VectorType, - callFn: countOverTimeImpl, - }, - "count_scalar": { - name: "count_scalar", - argTypes: []ExprType{VectorType}, - returnType: ScalarType, - callFn: countScalarImpl, - }, - "delta": { - name: "delta", - argTypes: []ExprType{MatrixType, ScalarType}, - optionalArgs: 1, // The 2nd argument is deprecated. - returnType: VectorType, - callFn: deltaImpl, - }, - "deriv": { - name: "deriv", - argTypes: []ExprType{MatrixType}, - returnType: VectorType, - callFn: derivImpl, - }, - "drop_common_labels": { - name: "drop_common_labels", - argTypes: []ExprType{VectorType}, - returnType: VectorType, - callFn: dropCommonLabelsImpl, - }, - "exp": { - name: "exp", - argTypes: []ExprType{VectorType}, - returnType: VectorType, - callFn: expImpl, - }, - "floor": { - name: "floor", - argTypes: []ExprType{VectorType}, - returnType: VectorType, - callFn: floorImpl, - }, - "histogram_quantile": { - name: "histogram_quantile", - argTypes: []ExprType{ScalarType, VectorType}, - returnType: VectorType, - callFn: histogramQuantileImpl, - }, - "ln": { - name: "ln", - argTypes: []ExprType{VectorType}, - returnType: VectorType, - callFn: lnImpl, - }, - "log10": { - name: "log10", - argTypes: []ExprType{VectorType}, - returnType: VectorType, - callFn: log10Impl, - }, - "log2": { - name: "log2", - argTypes: []ExprType{VectorType}, - returnType: VectorType, - callFn: log2Impl, - }, - "max_over_time": { - name: "max_over_time", - argTypes: []ExprType{MatrixType}, - returnType: VectorType, - callFn: maxOverTimeImpl, - }, - "min_over_time": { - name: "min_over_time", - argTypes: []ExprType{MatrixType}, - returnType: VectorType, - callFn: minOverTimeImpl, - }, - "rate": { - name: "rate", - argTypes: []ExprType{MatrixType}, - returnType: VectorType, - callFn: rateImpl, - }, - "round": { - name: "round", - argTypes: []ExprType{VectorType, ScalarType}, - optionalArgs: 1, - returnType: VectorType, - callFn: roundImpl, - }, - "scalar": { - name: "scalar", - argTypes: []ExprType{VectorType}, - returnType: ScalarType, - callFn: scalarImpl, - }, - "sort": { - name: "sort", - argTypes: []ExprType{VectorType}, - returnType: VectorType, - callFn: sortImpl, - }, - "sort_desc": { - name: "sort_desc", - argTypes: []ExprType{VectorType}, - returnType: VectorType, - callFn: sortDescImpl, - }, - "sum_over_time": { - name: "sum_over_time", - argTypes: []ExprType{MatrixType}, - returnType: VectorType, - callFn: sumOverTimeImpl, - }, - "time": { - name: "time", - argTypes: []ExprType{}, - returnType: ScalarType, - callFn: timeImpl, - }, - "topk": { - name: "topk", - argTypes: []ExprType{ScalarType, VectorType}, - returnType: VectorType, - callFn: topkImpl, - }, -} - -// GetFunction returns a predefined Function object for the given -// name. -func GetFunction(name string) (*Function, error) { - function, ok := functions[name] - if !ok { - return nil, fmt.Errorf("couldn't find function %v()", name) - } - return function, nil -} diff --git a/rules/ast/functions_test.go b/rules/ast/functions_test.go deleted file mode 100644 index ca3f44a571..0000000000 --- a/rules/ast/functions_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2013 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 ast - -import ( - "testing" - - clientmodel "github.com/prometheus/client_golang/model" - - "github.com/prometheus/prometheus/storage/metric" -) - -type emptyRangeNode struct{} - -func (node emptyRangeNode) Type() ExprType { return MatrixType } -func (node emptyRangeNode) NodeTreeToDotGraph() string { return "" } -func (node emptyRangeNode) String() string { return "" } -func (node emptyRangeNode) Children() Nodes { return Nodes{} } - -func (node emptyRangeNode) Eval(timestamp clientmodel.Timestamp) Matrix { - return Matrix{ - SampleStream{ - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{clientmodel.MetricNameLabel: "empty_metric"}, - }, - Values: metric.Values{}, - }, - } -} - -func (node emptyRangeNode) EvalBoundaries(timestamp clientmodel.Timestamp) Matrix { - return Matrix{ - SampleStream{ - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{clientmodel.MetricNameLabel: "empty_metric"}, - }, - Values: metric.Values{}, - }, - } -} - -func TestDeltaWithEmptyElementDoesNotCrash(t *testing.T) { - now := clientmodel.Now() - vector := deltaImpl(now, []Node{emptyRangeNode{}, &ScalarLiteral{value: 0}}).(Vector) - if len(vector) != 0 { - t.Fatalf("Expected empty result vector, got: %v", vector) - } - vector = deltaImpl(now, []Node{emptyRangeNode{}, &ScalarLiteral{value: 1}}).(Vector) - if len(vector) != 0 { - t.Fatalf("Expected empty result vector, got: %v", vector) - } -} diff --git a/rules/ast/printer.go b/rules/ast/printer.go deleted file mode 100644 index 7c9ebcade3..0000000000 --- a/rules/ast/printer.go +++ /dev/null @@ -1,441 +0,0 @@ -// Copyright 2013 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 ast - -import ( - "encoding/json" - "errors" - "fmt" - "reflect" - "sort" - "strings" - - clientmodel "github.com/prometheus/client_golang/model" - - "github.com/prometheus/prometheus/stats" - "github.com/prometheus/prometheus/storage/local" - "github.com/prometheus/prometheus/utility" -) - -// OutputFormat is an enum for the possible output formats. -type OutputFormat int - -// Possible output formats. -const ( - Text OutputFormat = iota - JSON -) - -const jsonFormatVersion = 1 - -func (opType BinOpType) String() string { - opTypeMap := map[BinOpType]string{ - Add: "+", - Sub: "-", - Mul: "*", - Div: "/", - Mod: "%", - GT: ">", - LT: "<", - EQ: "==", - NE: "!=", - GE: ">=", - LE: "<=", - And: "AND", - Or: "OR", - } - return opTypeMap[opType] -} - -func (aggrType AggrType) String() string { - aggrTypeMap := map[AggrType]string{ - Sum: "SUM", - Avg: "AVG", - Min: "MIN", - Max: "MAX", - Count: "COUNT", - } - return aggrTypeMap[aggrType] -} - -func (exprType ExprType) String() string { - exprTypeMap := map[ExprType]string{ - ScalarType: "scalar", - VectorType: "vector", - MatrixType: "matrix", - StringType: "string", - } - return exprTypeMap[exprType] -} - -func (vector Vector) String() string { - metricStrings := make([]string, 0, len(vector)) - for _, sample := range vector { - metricStrings = append(metricStrings, - fmt.Sprintf("%s => %v @[%v]", - sample.Metric, - sample.Value, sample.Timestamp)) - } - return strings.Join(metricStrings, "\n") -} - -func (matrix Matrix) String() string { - metricStrings := make([]string, 0, len(matrix)) - for _, sampleStream := range matrix { - metricName, hasName := sampleStream.Metric.Metric[clientmodel.MetricNameLabel] - numLabels := len(sampleStream.Metric.Metric) - if hasName { - numLabels-- - } - labelStrings := make([]string, 0, numLabels) - for label, value := range sampleStream.Metric.Metric { - if label != clientmodel.MetricNameLabel { - labelStrings = append(labelStrings, fmt.Sprintf("%s=%q", label, value)) - } - } - sort.Strings(labelStrings) - valueStrings := make([]string, 0, len(sampleStream.Values)) - for _, value := range sampleStream.Values { - valueStrings = append(valueStrings, - fmt.Sprintf("\n%v @[%v]", value.Value, value.Timestamp)) - } - metricStrings = append(metricStrings, - fmt.Sprintf("%s{%s} => %s", - metricName, - strings.Join(labelStrings, ", "), - strings.Join(valueStrings, ", "))) - } - sort.Strings(metricStrings) - return strings.Join(metricStrings, "\n") -} - -// ErrorToJSON converts the given error into JSON. -func ErrorToJSON(err error) string { - errorStruct := struct { - Type string `json:"type"` - Value string `json:"value"` - Version int `json:"version"` - }{ - Type: "error", - Value: err.Error(), - Version: jsonFormatVersion, - } - - errorJSON, err := json.Marshal(errorStruct) - if err != nil { - return "" - } - return string(errorJSON) -} - -// TypedValueToJSON converts the given data of type 'scalar', -// 'vector', or 'matrix' into its JSON representation. -func TypedValueToJSON(data interface{}, typeStr string) string { - dataStruct := struct { - Type string `json:"type"` - Value interface{} `json:"value"` - Version int `json:"version"` - }{ - Type: typeStr, - Value: data, - Version: jsonFormatVersion, - } - dataJSON, err := json.Marshal(dataStruct) - if err != nil { - return ErrorToJSON(err) - } - return string(dataJSON) -} - -// EvalToString evaluates the given node into a string of the given format. -func EvalToString(node Node, timestamp clientmodel.Timestamp, format OutputFormat, storage local.Storage, queryStats *stats.TimerGroup) string { - totalEvalTimer := queryStats.GetTimer(stats.TotalEvalTime).Start() - defer totalEvalTimer.Stop() - - prepareTimer := queryStats.GetTimer(stats.TotalQueryPreparationTime).Start() - closer, err := prepareInstantQuery(node, timestamp, storage, queryStats) - prepareTimer.Stop() - if err != nil { - panic(err) - } - defer closer.Close() - - evalTimer := queryStats.GetTimer(stats.InnerEvalTime).Start() - switch node.Type() { - case ScalarType: - scalar := node.(ScalarNode).Eval(timestamp) - evalTimer.Stop() - switch format { - case Text: - return fmt.Sprintf("scalar: %v @[%v]", scalar, timestamp) - case JSON: - return TypedValueToJSON(scalar, "scalar") - } - case VectorType: - vector := node.(VectorNode).Eval(timestamp) - evalTimer.Stop() - switch format { - case Text: - return vector.String() - case JSON: - return TypedValueToJSON(vector, "vector") - } - case MatrixType: - matrix := node.(MatrixNode).Eval(timestamp) - evalTimer.Stop() - switch format { - case Text: - return matrix.String() - case JSON: - return TypedValueToJSON(matrix, "matrix") - } - case StringType: - str := node.(StringNode).Eval(timestamp) - evalTimer.Stop() - switch format { - case Text: - return str - case JSON: - return TypedValueToJSON(str, "string") - } - } - panic("Switch didn't cover all node types") -} - -// EvalToVector evaluates the given node into a Vector. Matrices aren't supported. -func EvalToVector(node Node, timestamp clientmodel.Timestamp, storage local.Storage, queryStats *stats.TimerGroup) (Vector, error) { - totalEvalTimer := queryStats.GetTimer(stats.TotalEvalTime).Start() - defer totalEvalTimer.Stop() - - prepareTimer := queryStats.GetTimer(stats.TotalQueryPreparationTime).Start() - closer, err := prepareInstantQuery(node, timestamp, storage, queryStats) - prepareTimer.Stop() - if err != nil { - panic(err) - } - defer closer.Close() - - evalTimer := queryStats.GetTimer(stats.InnerEvalTime).Start() - switch node.Type() { - case ScalarType: - scalar := node.(ScalarNode).Eval(timestamp) - evalTimer.Stop() - return Vector{&Sample{Value: scalar}}, nil - case VectorType: - vector := node.(VectorNode).Eval(timestamp) - evalTimer.Stop() - return vector, nil - case MatrixType: - return nil, errors.New("matrices not supported by EvalToVector") - case StringType: - str := node.(StringNode).Eval(timestamp) - evalTimer.Stop() - return Vector{ - &Sample{ - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - "__value__": clientmodel.LabelValue(str), - }, - Copied: true, - }, - }, - }, nil - } - panic("Switch didn't cover all node types") -} - -// NodeTreeToDotGraph returns a DOT representation of the scalar -// literal. -func (node *ScalarLiteral) NodeTreeToDotGraph() string { - return fmt.Sprintf("%#p[label=\"%v\"];\n", node, node.value) -} - -func functionArgsToDotGraph(node Node, args []Node) string { - graph := "" - for _, arg := range args { - graph += fmt.Sprintf("%x -> %x;\n", reflect.ValueOf(node).Pointer(), reflect.ValueOf(arg).Pointer()) - } - for _, arg := range args { - graph += arg.NodeTreeToDotGraph() - } - return graph -} - -// NodeTreeToDotGraph returns a DOT representation of the function -// call. -func (node *ScalarFunctionCall) NodeTreeToDotGraph() string { - graph := fmt.Sprintf("%#p[label=\"%s\"];\n", node, node.function.name) - graph += functionArgsToDotGraph(node, node.args) - return graph -} - -// NodeTreeToDotGraph returns a DOT representation of the expression. -func (node *ScalarArithExpr) NodeTreeToDotGraph() string { - nodeAddr := reflect.ValueOf(node).Pointer() - graph := fmt.Sprintf( - ` - %x[label="%s"]; - %x -> %x; - %x -> %x; - %s - %s - }`, - nodeAddr, node.opType, - nodeAddr, reflect.ValueOf(node.lhs).Pointer(), - nodeAddr, reflect.ValueOf(node.rhs).Pointer(), - node.lhs.NodeTreeToDotGraph(), - node.rhs.NodeTreeToDotGraph(), - ) - return graph -} - -// NodeTreeToDotGraph returns a DOT representation of the vector selector. -func (node *VectorSelector) NodeTreeToDotGraph() string { - return fmt.Sprintf("%#p[label=\"%s\"];\n", node, node) -} - -// NodeTreeToDotGraph returns a DOT representation of the function -// call. -func (node *VectorFunctionCall) NodeTreeToDotGraph() string { - graph := fmt.Sprintf("%#p[label=\"%s\"];\n", node, node.function.name) - graph += functionArgsToDotGraph(node, node.args) - return graph -} - -// NodeTreeToDotGraph returns a DOT representation of the vector -// aggregation. -func (node *VectorAggregation) NodeTreeToDotGraph() string { - groupByStrings := make([]string, 0, len(node.groupBy)) - for _, label := range node.groupBy { - groupByStrings = append(groupByStrings, string(label)) - } - - graph := fmt.Sprintf("%#p[label=\"%s BY (%s)\"]\n", - node, - node.aggrType, - strings.Join(groupByStrings, ", ")) - graph += fmt.Sprintf("%#p -> %x;\n", node, reflect.ValueOf(node.vector).Pointer()) - graph += node.vector.NodeTreeToDotGraph() - return graph -} - -// NodeTreeToDotGraph returns a DOT representation of the expression. -func (node *VectorArithExpr) NodeTreeToDotGraph() string { - nodeAddr := reflect.ValueOf(node).Pointer() - graph := fmt.Sprintf( - ` - %x[label="%s"]; - %x -> %x; - %x -> %x; - %s - %s - }`, - nodeAddr, node.opType, - nodeAddr, reflect.ValueOf(node.lhs).Pointer(), - nodeAddr, reflect.ValueOf(node.rhs).Pointer(), - node.lhs.NodeTreeToDotGraph(), - node.rhs.NodeTreeToDotGraph(), - ) - return graph -} - -// NodeTreeToDotGraph returns a DOT representation of the matrix -// selector. -func (node *MatrixSelector) NodeTreeToDotGraph() string { - return fmt.Sprintf("%#p[label=\"%s\"];\n", node, node) -} - -// NodeTreeToDotGraph returns a DOT representation of the string -// literal. -func (node *StringLiteral) NodeTreeToDotGraph() string { - return fmt.Sprintf("%#p[label=\"'%q'\"];\n", node, node.str) -} - -// NodeTreeToDotGraph returns a DOT representation of the function -// call. -func (node *StringFunctionCall) NodeTreeToDotGraph() string { - graph := fmt.Sprintf("%#p[label=\"%s\"];\n", node, node.function.name) - graph += functionArgsToDotGraph(node, node.args) - return graph -} - -func (nodes Nodes) String() string { - nodeStrings := make([]string, 0, len(nodes)) - for _, node := range nodes { - nodeStrings = append(nodeStrings, node.String()) - } - return strings.Join(nodeStrings, ", ") -} - -func (node *ScalarLiteral) String() string { - return fmt.Sprint(node.value) -} - -func (node *ScalarFunctionCall) String() string { - return fmt.Sprintf("%s(%s)", node.function.name, node.args) -} - -func (node *ScalarArithExpr) String() string { - return fmt.Sprintf("(%s %s %s)", node.lhs, node.opType, node.rhs) -} - -func (node *VectorSelector) String() string { - labelStrings := make([]string, 0, len(node.labelMatchers)-1) - var metricName clientmodel.LabelValue - for _, matcher := range node.labelMatchers { - if matcher.Name != clientmodel.MetricNameLabel { - labelStrings = append(labelStrings, fmt.Sprintf("%s%s%q", matcher.Name, matcher.Type, matcher.Value)) - } else { - metricName = matcher.Value - } - } - - switch len(labelStrings) { - case 0: - return string(metricName) - default: - sort.Strings(labelStrings) - return fmt.Sprintf("%s{%s}", metricName, strings.Join(labelStrings, ",")) - } -} - -func (node *VectorFunctionCall) String() string { - return fmt.Sprintf("%s(%s)", node.function.name, node.args) -} - -func (node *VectorAggregation) String() string { - aggrString := fmt.Sprintf("%s(%s)", node.aggrType, node.vector) - if len(node.groupBy) > 0 { - return fmt.Sprintf("%s BY (%s)", aggrString, node.groupBy) - } - return aggrString -} - -func (node *VectorArithExpr) String() string { - return fmt.Sprintf("(%s %s %s)", node.lhs, node.opType, node.rhs) -} - -func (node *MatrixSelector) String() string { - vectorString := (&VectorSelector{labelMatchers: node.labelMatchers}).String() - intervalString := fmt.Sprintf("[%s]", utility.DurationToString(node.interval)) - return vectorString + intervalString -} - -func (node *StringLiteral) String() string { - return fmt.Sprintf("%q", node.str) -} - -func (node *StringFunctionCall) String() string { - return fmt.Sprintf("%s(%s)", node.function.name, node.args) -} diff --git a/rules/ast/quantile.go b/rules/ast/quantile.go deleted file mode 100644 index 38ddc79767..0000000000 --- a/rules/ast/quantile.go +++ /dev/null @@ -1,106 +0,0 @@ -// 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 ast - -import ( - "math" - "sort" - - clientmodel "github.com/prometheus/client_golang/model" -) - -// Helpers to calculate quantiles. - -// excludedLabels are the labels to exclude from signature calculation for -// quantiles. -var excludedLabels = map[clientmodel.LabelName]struct{}{ - clientmodel.MetricNameLabel: {}, - clientmodel.BucketLabel: {}, -} - -type bucket struct { - upperBound float64 - count clientmodel.SampleValue -} - -// buckets implements sort.Interface. -type buckets []bucket - -func (b buckets) Len() int { return len(b) } -func (b buckets) Swap(i, j int) { b[i], b[j] = b[j], b[i] } -func (b buckets) Less(i, j int) bool { return b[i].upperBound < b[j].upperBound } - -type metricWithBuckets struct { - metric clientmodel.COWMetric - buckets buckets -} - -// quantile calculates the quantile 'q' based on the given buckets. The buckets -// will be sorted by upperBound by this function (i.e. no sorting needed before -// calling this function). The quantile value is interpolated assuming a linear -// distribution within a bucket. However, if the quantile falls into the highest -// bucket, the upper bound of the 2nd highest bucket is returned. A natural -// lower bound of 0 is assumed if the upper bound of the lowest bucket is -// greater 0. In that case, interpolation in the lowest bucket happens linearly -// between 0 and the upper bound of the lowest bucket. However, if the lowest -// bucket has an upper bound less or equal 0, this upper bound is returned if -// the quantile falls into the lowest bucket. -// -// There are a number of special cases (once we have a way to report errors -// happening during evaluations of AST functions, we should report those -// explicitly): -// -// If 'buckets' has fewer than 2 elements, NaN is returned. -// -// If the highest bucket is not +Inf, NaN is returned. -// -// If q<0, -Inf is returned. -// -// If q>1, +Inf is returned. -func quantile(q clientmodel.SampleValue, buckets buckets) float64 { - if q < 0 { - return math.Inf(-1) - } - if q > 1 { - return math.Inf(+1) - } - if len(buckets) < 2 { - return math.NaN() - } - sort.Sort(buckets) - if !math.IsInf(buckets[len(buckets)-1].upperBound, +1) { - return math.NaN() - } - - rank := q * buckets[len(buckets)-1].count - b := sort.Search(len(buckets)-1, func(i int) bool { return buckets[i].count >= rank }) - - if b == len(buckets)-1 { - return buckets[len(buckets)-2].upperBound - } - if b == 0 && buckets[0].upperBound <= 0 { - return buckets[0].upperBound - } - var ( - bucketStart float64 - bucketEnd = buckets[b].upperBound - count = buckets[b].count - ) - if b > 0 { - bucketStart = buckets[b-1].upperBound - count -= buckets[b-1].count - rank -= buckets[b-1].count - } - return bucketStart + (bucketEnd-bucketStart)*float64(rank/count) -} diff --git a/rules/ast/query_analyzer.go b/rules/ast/query_analyzer.go deleted file mode 100644 index 9807f7527d..0000000000 --- a/rules/ast/query_analyzer.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright 2013 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 ast - -import ( - "time" - - clientmodel "github.com/prometheus/client_golang/model" - - "github.com/prometheus/prometheus/stats" - "github.com/prometheus/prometheus/storage/local" -) - -// preloadTimes tracks which instants or ranges to preload for a set of -// fingerprints. One of these structs is collected for each offset by the query -// analyzer. -type preloadTimes struct { - // Instants require single samples to be loaded along the entire query - // range, with intervals between the samples corresponding to the query - // resolution. - instants map[clientmodel.Fingerprint]struct{} - // Ranges require loading a range of samples at each resolution step, - // stretching backwards from the current evaluation timestamp. The length of - // the range into the past is given by the duration, as in "foo[5m]". - ranges map[clientmodel.Fingerprint]time.Duration -} - -// A queryAnalyzer recursively traverses the AST to look for any nodes -// which will need data from the datastore. Instantiate with -// newQueryAnalyzer. -type queryAnalyzer struct { - // Tracks one set of times to preload per offset that occurs in the query - // expression. - offsetPreloadTimes map[time.Duration]preloadTimes - // The underlying storage to which the query will be applied. Needed for - // extracting timeseries fingerprint information during query analysis. - storage local.Storage -} - -// newQueryAnalyzer returns a pointer to a newly instantiated -// queryAnalyzer. The storage is needed to extract timeseries -// fingerprint information during query analysis. -func newQueryAnalyzer(storage local.Storage) *queryAnalyzer { - return &queryAnalyzer{ - offsetPreloadTimes: map[time.Duration]preloadTimes{}, - storage: storage, - } -} - -func (analyzer *queryAnalyzer) getPreloadTimes(offset time.Duration) preloadTimes { - if _, ok := analyzer.offsetPreloadTimes[offset]; !ok { - analyzer.offsetPreloadTimes[offset] = preloadTimes{ - instants: map[clientmodel.Fingerprint]struct{}{}, - ranges: map[clientmodel.Fingerprint]time.Duration{}, - } - } - return analyzer.offsetPreloadTimes[offset] -} - -// visit implements the visitor interface. -func (analyzer *queryAnalyzer) visit(node Node) { - switch n := node.(type) { - case *VectorSelector: - pt := analyzer.getPreloadTimes(n.offset) - fingerprints := analyzer.storage.GetFingerprintsForLabelMatchers(n.labelMatchers) - n.fingerprints = fingerprints - for _, fp := range fingerprints { - // Only add the fingerprint to the instants if not yet present in the - // ranges. Ranges always contain more points and span more time than - // instants for the same offset. - if _, alreadyInRanges := pt.ranges[fp]; !alreadyInRanges { - pt.instants[fp] = struct{}{} - } - - n.metrics[fp] = analyzer.storage.GetMetricForFingerprint(fp) - } - case *MatrixSelector: - pt := analyzer.getPreloadTimes(n.offset) - fingerprints := analyzer.storage.GetFingerprintsForLabelMatchers(n.labelMatchers) - n.fingerprints = fingerprints - for _, fp := range fingerprints { - if pt.ranges[fp] < n.interval { - pt.ranges[fp] = n.interval - // Delete the fingerprint from the instants. Ranges always contain more - // points and span more time than instants, so we don't need to track - // an instant for the same fingerprint, should we have one. - delete(pt.instants, fp) - } - - n.metrics[fp] = analyzer.storage.GetMetricForFingerprint(fp) - } - } -} - -type iteratorInitializer struct { - storage local.Storage -} - -func (i *iteratorInitializer) visit(node Node) { - switch n := node.(type) { - case *VectorSelector: - for _, fp := range n.fingerprints { - n.iterators[fp] = i.storage.NewIterator(fp) - } - case *MatrixSelector: - for _, fp := range n.fingerprints { - n.iterators[fp] = i.storage.NewIterator(fp) - } - } -} - -func prepareInstantQuery(node Node, timestamp clientmodel.Timestamp, storage local.Storage, queryStats *stats.TimerGroup) (local.Preloader, error) { - totalTimer := queryStats.GetTimer(stats.TotalEvalTime) - - analyzeTimer := queryStats.GetTimer(stats.QueryAnalysisTime).Start() - analyzer := newQueryAnalyzer(storage) - Walk(analyzer, node) - analyzeTimer.Stop() - - preloadTimer := queryStats.GetTimer(stats.PreloadTime).Start() - p := storage.NewPreloader() - for offset, pt := range analyzer.offsetPreloadTimes { - ts := timestamp.Add(-offset) - for fp, rangeDuration := range pt.ranges { - if et := totalTimer.ElapsedTime(); et > *queryTimeout { - preloadTimer.Stop() - p.Close() - return nil, queryTimeoutError{et} - } - if err := p.PreloadRange(fp, ts.Add(-rangeDuration), ts, *stalenessDelta); err != nil { - preloadTimer.Stop() - p.Close() - return nil, err - } - } - for fp := range pt.instants { - if et := totalTimer.ElapsedTime(); et > *queryTimeout { - preloadTimer.Stop() - p.Close() - return nil, queryTimeoutError{et} - } - if err := p.PreloadRange(fp, ts, ts, *stalenessDelta); err != nil { - preloadTimer.Stop() - p.Close() - return nil, err - } - } - } - preloadTimer.Stop() - - ii := &iteratorInitializer{ - storage: storage, - } - Walk(ii, node) - - return p, nil -} - -func prepareRangeQuery(node Node, start clientmodel.Timestamp, end clientmodel.Timestamp, interval time.Duration, storage local.Storage, queryStats *stats.TimerGroup) (local.Preloader, error) { - totalTimer := queryStats.GetTimer(stats.TotalEvalTime) - - analyzeTimer := queryStats.GetTimer(stats.QueryAnalysisTime).Start() - analyzer := newQueryAnalyzer(storage) - Walk(analyzer, node) - analyzeTimer.Stop() - - preloadTimer := queryStats.GetTimer(stats.PreloadTime).Start() - p := storage.NewPreloader() - for offset, pt := range analyzer.offsetPreloadTimes { - offsetStart := start.Add(-offset) - offsetEnd := end.Add(-offset) - for fp, rangeDuration := range pt.ranges { - if et := totalTimer.ElapsedTime(); et > *queryTimeout { - preloadTimer.Stop() - p.Close() - return nil, queryTimeoutError{et} - } - if err := p.PreloadRange(fp, offsetStart.Add(-rangeDuration), offsetEnd, *stalenessDelta); err != nil { - preloadTimer.Stop() - p.Close() - return nil, err - } - /* - if interval < rangeDuration { - if err := p.GetMetricRange(fp, offsetEnd, offsetEnd.Sub(offsetStart)+rangeDuration); err != nil { - p.Close() - return nil, err - } - } else { - if err := p.GetMetricRangeAtInterval(fp, offsetStart, offsetEnd, interval, rangeDuration); err != nil { - p.Close() - return nil, err - } - } - */ - } - for fp := range pt.instants { - if et := totalTimer.ElapsedTime(); et > *queryTimeout { - preloadTimer.Stop() - p.Close() - return nil, queryTimeoutError{et} - } - if err := p.PreloadRange(fp, offsetStart, offsetEnd, *stalenessDelta); err != nil { - preloadTimer.Stop() - p.Close() - return nil, err - } - } - } - preloadTimer.Stop() - - ii := &iteratorInitializer{ - storage: storage, - } - Walk(ii, node) - - return p, nil -} diff --git a/rules/ast/walk.go b/rules/ast/walk.go deleted file mode 100644 index 143120e8f6..0000000000 --- a/rules/ast/walk.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2013 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 ast - -// visitor is the interface for a Node visitor. -type visitor interface { - visit(node Node) -} - -// Walk does a depth-first traversal of the AST, starting at node, -// calling visitor.visit for each encountered Node in the tree. -func Walk(v visitor, node Node) { - v.visit(node) - for _, childNode := range node.Children() { - Walk(v, childNode) - } -} diff --git a/rules/fixtures/empty.rules b/rules/fixtures/empty.rules deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/rules/fixtures/mixed.rules b/rules/fixtures/mixed.rules deleted file mode 100644 index 7b9520607b..0000000000 --- a/rules/fixtures/mixed.rules +++ /dev/null @@ -1,16 +0,0 @@ -// A simple test recording rule. -dc_http_request_rate5m = sum(rate(http_request_count[5m])) by (dc) - -// A simple test alerting rule. -ALERT GlobalRequestRateLow IF(dc_http_request_rate5m < 10000) FOR 5m WITH { - service = "testservice" - /* ... more fields here ... */ - } - SUMMARY "Global request rate low" - DESCRIPTION "The global request rate is low" - -foo = bar{label1="value1"} - -ALERT BazAlert IF(foo > 10) WITH {} - SUMMARY "Baz" - DESCRIPTION "BazAlert" diff --git a/rules/fixtures/non_vector.rules b/rules/fixtures/non_vector.rules deleted file mode 100644 index f3b1046f2e..0000000000 --- a/rules/fixtures/non_vector.rules +++ /dev/null @@ -1 +0,0 @@ -now = time() diff --git a/rules/fixtures/syntax_error.rules b/rules/fixtures/syntax_error.rules deleted file mode 100644 index d44c8c9a8c..0000000000 --- a/rules/fixtures/syntax_error.rules +++ /dev/null @@ -1,15 +0,0 @@ -// A simple test recording rule. -dc_http_request_rate5m = sum(rate(http_request_count[5m])) by (dc) - -// A simple test alerting rule with a syntax error (invalid duration string "5"). -ALERT GlobalRequestRateLow IF(dc_http_request_rate5m < 10000) FOR 5 WITH { - description = "Global HTTP request rate low!", - summary = "Request rate low" - /* ... more fields here ... */ - } - SUMMARY "summary" - DESCRIPTION "description" - -foo = bar{label1="value1"} - -ALERT BazAlert IF(foo > 10) WITH {} SUMMARY "summary" DESCRIPTION "description" diff --git a/rules/helpers.go b/rules/helpers.go deleted file mode 100644 index 9f41622158..0000000000 --- a/rules/helpers.go +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright 2013 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 rules - -import ( - "fmt" - "net/url" - "strings" - - clientmodel "github.com/prometheus/client_golang/model" - - "github.com/prometheus/prometheus/rules/ast" - "github.com/prometheus/prometheus/storage/metric" - "github.com/prometheus/prometheus/utility" -) - -// CreateRecordingRule is a convenience function to create a recording rule. -func CreateRecordingRule(name string, labels clientmodel.LabelSet, expr ast.Node, permanent bool) (*RecordingRule, error) { - if _, ok := expr.(ast.VectorNode); !ok { - return nil, fmt.Errorf("recording rule expression %v does not evaluate to vector type", expr) - } - return &RecordingRule{ - name: name, - labels: labels, - vector: expr.(ast.VectorNode), - permanent: permanent, - }, nil -} - -// CreateAlertingRule is a convenience function to create a new alerting rule. -func CreateAlertingRule(name string, expr ast.Node, holdDurationStr string, labels clientmodel.LabelSet, summary string, description string) (*AlertingRule, error) { - if _, ok := expr.(ast.VectorNode); !ok { - return nil, fmt.Errorf("alert rule expression %v does not evaluate to vector type", expr) - } - holdDuration, err := utility.StringToDuration(holdDurationStr) - if err != nil { - return nil, err - } - return NewAlertingRule(name, expr.(ast.VectorNode), holdDuration, labels, summary, description), nil -} - -// NewScalarLiteral returns a ScalarLiteral with the given value. If sign is "-" -// the value is negated. -func NewScalarLiteral(value clientmodel.SampleValue, sign string) *ast.ScalarLiteral { - if sign == "-" { - value = -value - } - return ast.NewScalarLiteral(value) -} - -// NewFunctionCall is a convenience function to create a new AST function-call node. -func NewFunctionCall(name string, args []ast.Node) (ast.Node, error) { - function, err := ast.GetFunction(name) - if err != nil { - return nil, fmt.Errorf("unknown function %q", name) - } - functionCall, err := ast.NewFunctionCall(function, args) - if err != nil { - return nil, fmt.Errorf(err.Error()) - } - return functionCall, nil -} - -// NewVectorAggregation is a convenience function to create a new AST vector aggregation. -func NewVectorAggregation(aggrTypeStr string, vector ast.Node, groupBy clientmodel.LabelNames, keepExtraLabels bool) (*ast.VectorAggregation, error) { - if _, ok := vector.(ast.VectorNode); !ok { - return nil, fmt.Errorf("operand of %v aggregation must be of vector type", aggrTypeStr) - } - var aggrTypes = map[string]ast.AggrType{ - "SUM": ast.Sum, - "MAX": ast.Max, - "MIN": ast.Min, - "AVG": ast.Avg, - "COUNT": ast.Count, - } - aggrType, ok := aggrTypes[aggrTypeStr] - if !ok { - return nil, fmt.Errorf("unknown aggregation type %q", aggrTypeStr) - } - return ast.NewVectorAggregation(aggrType, vector.(ast.VectorNode), groupBy, keepExtraLabels), nil -} - -// vectorMatching combines data used to match samples between vectors. -type vectorMatching struct { - matchCardinality ast.VectorMatchCardinality - matchOn clientmodel.LabelNames - includeLabels clientmodel.LabelNames -} - -// newVectorMatching is a convenience function to create a new vectorMatching. -func newVectorMatching(card string, matchOn, include clientmodel.LabelNames) (*vectorMatching, error) { - var matchCardinalities = map[string]ast.VectorMatchCardinality{ - "": ast.MatchOneToOne, - "GROUP_LEFT": ast.MatchManyToOne, - "GROUP_RIGHT": ast.MatchOneToMany, - } - matchCard, ok := matchCardinalities[card] - if !ok { - return nil, fmt.Errorf("invalid vector match cardinality %q", card) - } - if matchCard != ast.MatchOneToOne && len(include) == 0 { - return nil, fmt.Errorf("grouped vector matching must provide labels") - } - // There must be no overlap between both labelname lists. - for _, matchLabel := range matchOn { - for _, incLabel := range include { - if matchLabel == incLabel { - return nil, fmt.Errorf("use of label %s in ON and %s clauses not allowed", incLabel, card) - } - } - } - return &vectorMatching{matchCard, matchOn, include}, nil -} - -// NewArithExpr is a convenience function to create a new AST arithmetic expression. -func NewArithExpr(opTypeStr string, lhs ast.Node, rhs ast.Node, vecMatching *vectorMatching) (ast.Node, error) { - var opTypes = map[string]ast.BinOpType{ - "+": ast.Add, - "-": ast.Sub, - "*": ast.Mul, - "/": ast.Div, - "%": ast.Mod, - ">": ast.GT, - "<": ast.LT, - "==": ast.EQ, - "!=": ast.NE, - ">=": ast.GE, - "<=": ast.LE, - "AND": ast.And, - "OR": ast.Or, - } - opType, ok := opTypes[opTypeStr] - if !ok { - return nil, fmt.Errorf("invalid binary operator %q", opTypeStr) - } - var vm vectorMatching - if vecMatching != nil { - vm = *vecMatching - // And/or always do many-to-many matching. - if opType == ast.And || opType == ast.Or { - vm.matchCardinality = ast.MatchManyToMany - } - } - expr, err := ast.NewArithExpr(opType, lhs, rhs, vm.matchCardinality, vm.matchOn, vm.includeLabels) - if err != nil { - return nil, fmt.Errorf(err.Error()) - } - return expr, nil -} - -// NewVectorSelector is a convenience function to create a new AST vector selector. -func NewVectorSelector(m metric.LabelMatchers, offsetStr string) (ast.VectorNode, error) { - offset, err := utility.StringToDuration(offsetStr) - if err != nil { - return nil, err - } - return ast.NewVectorSelector(m, offset), nil -} - -// NewMatrixSelector is a convenience function to create a new AST matrix selector. -func NewMatrixSelector(vector ast.Node, intervalStr string, offsetStr string) (ast.MatrixNode, error) { - interval, err := utility.StringToDuration(intervalStr) - if err != nil { - return nil, err - } - offset, err := utility.StringToDuration(offsetStr) - if err != nil { - return nil, err - } - vectorSelector, ok := vector.(*ast.VectorSelector) - if !ok { - return nil, fmt.Errorf("intervals are currently only supported for vector selectors") - } - return ast.NewMatrixSelector(vectorSelector, interval, offset), nil -} - -func newLabelMatcher(matchTypeStr string, name clientmodel.LabelName, value clientmodel.LabelValue) (*metric.LabelMatcher, error) { - matchTypes := map[string]metric.MatchType{ - "=": metric.Equal, - "!=": metric.NotEqual, - "=~": metric.RegexMatch, - "!~": metric.RegexNoMatch, - } - matchType, ok := matchTypes[matchTypeStr] - if !ok { - return nil, fmt.Errorf("invalid label matching operator %q", matchTypeStr) - } - return metric.NewLabelMatcher(matchType, name, value) -} - -// TableLinkForExpression creates an escaped relative link to the table view of -// the provided expression. -func TableLinkForExpression(expr string) string { - // url.QueryEscape percent-escapes everything except spaces, for which it - // uses "+". However, in the non-query part of a URI, only percent-escaped - // spaces are legal, so we need to manually replace "+" with "%20" after - // query-escaping the string. - // - // See also: - // http://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20. - urlData := url.QueryEscape(fmt.Sprintf(`[{"expr":%q,"tab":1}]`, expr)) - return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1)) -} - -// GraphLinkForExpression creates an escaped relative link to the graph view of -// the provided expression. -func GraphLinkForExpression(expr string) string { - urlData := url.QueryEscape(fmt.Sprintf(`[{"expr":%q,"tab":0}]`, expr)) - return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1)) -} diff --git a/rules/helpers_test.go b/rules/helpers_test.go deleted file mode 100644 index 037fec86e6..0000000000 --- a/rules/helpers_test.go +++ /dev/null @@ -1,487 +0,0 @@ -// Copyright 2013 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 rules - -import ( - "time" - - clientmodel "github.com/prometheus/client_golang/model" - - "github.com/prometheus/prometheus/rules/ast" - "github.com/prometheus/prometheus/storage/local" - "github.com/prometheus/prometheus/storage/metric" -) - -var testSampleInterval = time.Duration(5) * time.Minute -var testStartTime = clientmodel.Timestamp(0) - -func getTestValueStream(startVal clientmodel.SampleValue, endVal clientmodel.SampleValue, stepVal clientmodel.SampleValue, startTime clientmodel.Timestamp) (resultValues metric.Values) { - currentTime := startTime - for currentVal := startVal; currentVal <= endVal; currentVal += stepVal { - sample := metric.SamplePair{ - Value: currentVal, - Timestamp: currentTime, - } - resultValues = append(resultValues, sample) - currentTime = currentTime.Add(testSampleInterval) - } - return resultValues -} - -func getTestVectorFromTestMatrix(matrix ast.Matrix) ast.Vector { - vector := ast.Vector{} - for _, sampleStream := range matrix { - lastSample := sampleStream.Values[len(sampleStream.Values)-1] - vector = append(vector, &ast.Sample{ - Metric: sampleStream.Metric, - Value: lastSample.Value, - Timestamp: lastSample.Timestamp, - }) - } - return vector -} - -func storeMatrix(storage local.Storage, matrix ast.Matrix) { - pendingSamples := clientmodel.Samples{} - for _, sampleStream := range matrix { - for _, sample := range sampleStream.Values { - pendingSamples = append(pendingSamples, &clientmodel.Sample{ - Metric: sampleStream.Metric.Metric, - Value: sample.Value, - Timestamp: sample.Timestamp, - }) - } - } - for _, s := range pendingSamples { - storage.Append(s) - } - storage.WaitForIndexing() -} - -var testMatrix = ast.Matrix{ - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "http_requests", - clientmodel.JobLabel: "api-server", - "instance": "0", - "group": "production", - }, - }, - Values: getTestValueStream(0, 100, 10, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "http_requests", - clientmodel.JobLabel: "api-server", - "instance": "1", - "group": "production", - }, - }, - Values: getTestValueStream(0, 200, 20, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "http_requests", - clientmodel.JobLabel: "api-server", - "instance": "0", - "group": "canary", - }, - }, - Values: getTestValueStream(0, 300, 30, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "http_requests", - clientmodel.JobLabel: "api-server", - "instance": "1", - "group": "canary", - }, - }, - Values: getTestValueStream(0, 400, 40, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "http_requests", - clientmodel.JobLabel: "app-server", - "instance": "0", - "group": "production", - }, - }, - Values: getTestValueStream(0, 500, 50, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "http_requests", - clientmodel.JobLabel: "app-server", - "instance": "1", - "group": "production", - }, - }, - Values: getTestValueStream(0, 600, 60, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "http_requests", - clientmodel.JobLabel: "app-server", - "instance": "0", - "group": "canary", - }, - }, - Values: getTestValueStream(0, 700, 70, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "http_requests", - clientmodel.JobLabel: "app-server", - "instance": "1", - "group": "canary", - }, - }, - Values: getTestValueStream(0, 800, 80, testStartTime), - }, - // Single-letter metric and label names. - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "x", - "y": "testvalue", - }, - }, - Values: getTestValueStream(0, 100, 10, testStartTime), - }, - // Counter reset in the middle of range. - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testcounter_reset_middle", - }, - }, - Values: append(getTestValueStream(0, 40, 10, testStartTime), getTestValueStream(0, 50, 10, testStartTime.Add(testSampleInterval*5))...), - }, - // Counter reset at the end of range. - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testcounter_reset_end", - }, - }, - Values: append(getTestValueStream(0, 90, 10, testStartTime), getTestValueStream(0, 0, 10, testStartTime.Add(testSampleInterval*10))...), - }, - // For label-key grouping regression test. - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "label_grouping_test", - "a": "aa", - "b": "bb", - }, - }, - Values: getTestValueStream(0, 100, 10, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "label_grouping_test", - "a": "a", - "b": "abb", - }, - }, - Values: getTestValueStream(0, 200, 20, testStartTime), - }, - // Two histograms with 4 buckets each (*_sum and *_count not included, - // only buckets). Lowest bucket for one histogram < 0, for the other > - // 0. They have the same name, just separated by label. Not useful in - // practice, but can happen (if clients change bucketing), and the - // server has to cope with it. - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testhistogram_bucket", - "le": "0.1", - "start": "positive", - }, - }, - Values: getTestValueStream(0, 50, 5, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testhistogram_bucket", - "le": ".2", - "start": "positive", - }, - }, - Values: getTestValueStream(0, 70, 7, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testhistogram_bucket", - "le": "1e0", - "start": "positive", - }, - }, - Values: getTestValueStream(0, 110, 11, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testhistogram_bucket", - "le": "+Inf", - "start": "positive", - }, - }, - Values: getTestValueStream(0, 120, 12, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testhistogram_bucket", - "le": "-.2", - "start": "negative", - }, - }, - Values: getTestValueStream(0, 10, 1, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testhistogram_bucket", - "le": "-0.1", - "start": "negative", - }, - }, - Values: getTestValueStream(0, 20, 2, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testhistogram_bucket", - "le": "0.3", - "start": "negative", - }, - }, - Values: getTestValueStream(0, 20, 2, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testhistogram_bucket", - "le": "+Inf", - "start": "negative", - }, - }, - Values: getTestValueStream(0, 30, 3, testStartTime), - }, - // Now a more realistic histogram per job and instance to test aggregation. - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "request_duration_seconds_bucket", - clientmodel.JobLabel: "job1", - "instance": "ins1", - "le": "0.1", - }, - }, - Values: getTestValueStream(0, 10, 1, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "request_duration_seconds_bucket", - clientmodel.JobLabel: "job1", - "instance": "ins1", - "le": "0.2", - }, - }, - Values: getTestValueStream(0, 30, 3, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "request_duration_seconds_bucket", - clientmodel.JobLabel: "job1", - "instance": "ins1", - "le": "+Inf", - }, - }, - Values: getTestValueStream(0, 40, 4, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "request_duration_seconds_bucket", - clientmodel.JobLabel: "job1", - "instance": "ins2", - "le": "0.1", - }, - }, - Values: getTestValueStream(0, 20, 2, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "request_duration_seconds_bucket", - clientmodel.JobLabel: "job1", - "instance": "ins2", - "le": "0.2", - }, - }, - Values: getTestValueStream(0, 50, 5, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "request_duration_seconds_bucket", - clientmodel.JobLabel: "job1", - "instance": "ins2", - "le": "+Inf", - }, - }, - Values: getTestValueStream(0, 60, 6, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "request_duration_seconds_bucket", - clientmodel.JobLabel: "job2", - "instance": "ins1", - "le": "0.1", - }, - }, - Values: getTestValueStream(0, 30, 3, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "request_duration_seconds_bucket", - clientmodel.JobLabel: "job2", - "instance": "ins1", - "le": "0.2", - }, - }, - Values: getTestValueStream(0, 40, 4, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "request_duration_seconds_bucket", - clientmodel.JobLabel: "job2", - "instance": "ins1", - "le": "+Inf", - }, - }, - Values: getTestValueStream(0, 60, 6, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "request_duration_seconds_bucket", - clientmodel.JobLabel: "job2", - "instance": "ins2", - "le": "0.1", - }, - }, - Values: getTestValueStream(0, 40, 4, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "request_duration_seconds_bucket", - clientmodel.JobLabel: "job2", - "instance": "ins2", - "le": "0.2", - }, - }, - Values: getTestValueStream(0, 70, 7, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "request_duration_seconds_bucket", - clientmodel.JobLabel: "job2", - "instance": "ins2", - "le": "+Inf", - }, - }, - Values: getTestValueStream(0, 90, 9, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "vector_matching_a", - "l": "x", - }, - }, - Values: getTestValueStream(0, 100, 1, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "vector_matching_a", - "l": "y", - }, - }, - Values: getTestValueStream(0, 100, 2, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "vector_matching_b", - "l": "x", - }, - }, - Values: getTestValueStream(0, 100, 4, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "cpu_count", - "instance": "0", - "type": "numa", - }, - }, - Values: getTestValueStream(0, 500, 30, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "cpu_count", - "instance": "0", - "type": "smp", - }, - }, - Values: getTestValueStream(0, 200, 10, testStartTime), - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "cpu_count", - "instance": "1", - "type": "smp", - }, - }, - Values: getTestValueStream(0, 200, 20, testStartTime), - }, -} - -var testVector = getTestVectorFromTestMatrix(testMatrix) diff --git a/rules/lexer.l b/rules/lexer.l deleted file mode 100644 index d4c9d19af0..0000000000 --- a/rules/lexer.l +++ /dev/null @@ -1,118 +0,0 @@ -/* Copyright 2013 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 rules - -import ( - "fmt" - "strconv" - "strings" - - clientmodel "github.com/prometheus/client_golang/model" -) - -// Lex is called by the parser generated by "go tool yacc" to obtain each -// token. The method is opened before the matching rules block and closed at -// the end of the file. -func (lexer *RulesLexer) Lex(lval *yySymType) int { - // Internal lexer states. - const ( - S_INITIAL = iota - S_COMMENTS - ) - - // We simulate multiple start symbols for closely-related grammars via dummy tokens. See - // http://www.gnu.org/software/bison/manual/html_node/Multiple-start_002dsymbols.html - // Reason: we want to be able to parse lists of named rules as well as single expressions. - if lexer.startToken != 0 { - startToken := lexer.startToken - lexer.startToken = 0 - return startToken - } - - c := lexer.current - currentState := 0 - - if lexer.empty { - c, lexer.empty = lexer.getChar(), false - } - -%} - -D [0-9] -L [a-zA-Z_] -M [a-zA-Z_:] -U [smhdwy] - -FLOAT ({D}*\.?{D}+|{D}+\.?{D}*){EXPONENT}?|[+-]?[iI][nN][fF]|[nN][aA][nN] -EXPONENT [eE][-+]?[0-9]+ - -STR \"(\\.|[^\\"])*\"|\'(\\.|[^\\'])*\' - -%x S_COMMENTS - -%yyc c -%yyn c = lexer.getChar() -%yyt currentState - -%% - lexer.buf = lexer.buf[:0] // The code before the first rule executed before every scan cycle (rule #0 / state 0 action) - -"/*" currentState = S_COMMENTS -"*/" currentState = S_INITIAL -.|\n /* ignore chars within multi-line comments */ - -\/\/[^\r\n]*\n /* gobble up one-line comments */ - -ALERT|alert return ALERT -IF|if return IF -FOR|for return FOR -WITH|with return WITH -SUMMARY|summary return SUMMARY -DESCRIPTION|description return DESCRIPTION - -PERMANENT|permanent return PERMANENT -BY|by return GROUP_OP -ON|on return MATCH_OP -GROUP_LEFT|GROUP_RIGHT lval.str = lexer.token(); return MATCH_MOD -group_left|group_right lval.str = strings.ToUpper(lexer.token()); return MATCH_MOD -KEEPING_EXTRA|keeping_extra return KEEPING_EXTRA -OFFSET|offset return OFFSET -AVG|SUM|MAX|MIN|COUNT lval.str = lexer.token(); return AGGR_OP -avg|sum|max|min|count lval.str = strings.ToUpper(lexer.token()); return AGGR_OP -\<|>|AND|OR|and|or lval.str = strings.ToUpper(lexer.token()); return CMP_OP -==|!=|>=|<=|=~|!~ lval.str = lexer.token(); return CMP_OP -[+\-] lval.str = lexer.token(); return ADDITIVE_OP -[*/%] lval.str = lexer.token(); return MULT_OP - -{FLOAT} num, err := strconv.ParseFloat(lexer.token(), 64); - if (err != nil && err.(*strconv.NumError).Err == strconv.ErrSyntax) { - panic("Invalid float") - } - lval.num = clientmodel.SampleValue(num) - return NUMBER - -{D}+{U} lval.str = lexer.token(); return DURATION -{L}({L}|{D})* lval.str = lexer.token(); return IDENTIFIER -{M}({M}|{D})* lval.str = lexer.token(); return METRICNAME - -{STR} lval.str = lexer.token()[1:len(lexer.token()) - 1]; return STRING - -[{}\[\]()=,] return int(lexer.buf[0]) -[\t\n\r ] /* gobble up any whitespace */ -%% - - lexer.empty = true - return int(c) -} diff --git a/rules/lexer.l.go b/rules/lexer.l.go deleted file mode 100644 index 892d1befdb..0000000000 --- a/rules/lexer.l.go +++ /dev/null @@ -1,2738 +0,0 @@ -/* Copyright 2013 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 rules - -import ( - "fmt" - "strconv" - "strings" - - clientmodel "github.com/prometheus/client_golang/model" -) - -// Lex is called by the parser generated by "go tool yacc" to obtain each -// token. The method is opened before the matching rules block and closed at -// the end of the file. -func (lexer *RulesLexer) Lex(lval *yySymType) int { - // Internal lexer states. - const ( - S_INITIAL = iota - S_COMMENTS - ) - - // We simulate multiple start symbols for closely-related grammars via dummy tokens. See - // http://www.gnu.org/software/bison/manual/html_node/Multiple-start_002dsymbols.html - // Reason: we want to be able to parse lists of named rules as well as single expressions. - if lexer.startToken != 0 { - startToken := lexer.startToken - lexer.startToken = 0 - return startToken - } - - c := lexer.current - currentState := 0 - - if lexer.empty { - c, lexer.empty = lexer.getChar(), false - } - -yystate0: - - lexer.buf = lexer.buf[:0] // The code before the first rule executed before every scan cycle (rule #0 / state 0 action) - - switch yyt := currentState; yyt { - default: - panic(fmt.Errorf(`invalid start condition %d`, yyt)) - case 0: // start condition: INITIAL - goto yystart1 - case 1: // start condition: S_COMMENTS - goto yystart197 - } - - goto yystate0 // silence unused label error - goto yystate1 // silence unused label error -yystate1: - c = lexer.getChar() -yystart1: - switch { - default: - goto yyabort - case c == '!': - goto yystate3 - case c == '"': - goto yystate5 - case c == '%' || c == '*': - goto yystate8 - case c == '(' || c == ')' || c == ',' || c == '[' || c == ']' || c == '{' || c == '}': - goto yystate11 - case c == '+' || c == '-': - goto yystate12 - case c == '.': - goto yystate16 - case c == '/': - goto yystate21 - case c == ':': - goto yystate27 - case c == '<' || c == '>': - goto yystate28 - case c == '=': - goto yystate29 - case c == 'A': - goto yystate30 - case c == 'B': - goto yystate40 - case c == 'C': - goto yystate42 - case c == 'D': - goto yystate46 - case c == 'E' || c == 'H' || c == 'J' || c == 'L' || c == 'Q' || c == 'R' || c >= 'T' && c <= 'V' || c >= 'X' && c <= 'Z' || c == '_' || c == 'e' || c == 'h' || c == 'j' || c == 'l' || c == 'q' || c == 'r' || c >= 't' && c <= 'v' || c >= 'x' && c <= 'z': - goto yystate31 - case c == 'F': - goto yystate57 - case c == 'G': - goto yystate60 - case c == 'I': - goto yystate73 - case c == 'K': - goto yystate77 - case c == 'M': - goto yystate90 - case c == 'N' || c == 'n': - goto yystate93 - case c == 'O': - goto yystate95 - case c == 'P': - goto yystate102 - case c == 'S': - goto yystate111 - case c == 'W': - goto yystate118 - case c == '\'': - goto yystate9 - case c == '\t' || c == '\n' || c == '\r' || c == ' ': - goto yystate2 - case c == 'a': - goto yystate122 - case c == 'b': - goto yystate129 - case c == 'c': - goto yystate130 - case c == 'd': - goto yystate134 - case c == 'f': - goto yystate144 - case c == 'g': - goto yystate146 - case c == 'i': - goto yystate159 - case c == 'k': - goto yystate160 - case c == 'm': - goto yystate172 - case c == 'o': - goto yystate175 - case c == 'p': - goto yystate180 - case c == 's': - goto yystate188 - case c == 'w': - goto yystate194 - case c >= '0' && c <= '9': - goto yystate25 - } - -yystate2: - c = lexer.getChar() - goto yyrule30 - -yystate3: - c = lexer.getChar() - switch { - default: - goto yyabort - case c == '=' || c == '~': - goto yystate4 - } - -yystate4: - c = lexer.getChar() - goto yyrule21 - -yystate5: - c = lexer.getChar() - switch { - default: - goto yyabort - case c == '"': - goto yystate6 - case c == '\\': - goto yystate7 - case c >= '\x01' && c <= '!' || c >= '#' && c <= '[' || c >= ']' && c <= 'ÿ': - goto yystate5 - } - -yystate6: - c = lexer.getChar() - goto yyrule28 - -yystate7: - c = lexer.getChar() - switch { - default: - goto yyabort - case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': - goto yystate5 - } - -yystate8: - c = lexer.getChar() - goto yyrule23 - -yystate9: - c = lexer.getChar() - switch { - default: - goto yyabort - case c == '\'': - goto yystate6 - case c == '\\': - goto yystate10 - case c >= '\x01' && c <= '&' || c >= '(' && c <= '[' || c >= ']' && c <= 'ÿ': - goto yystate9 - } - -yystate10: - c = lexer.getChar() - switch { - default: - goto yyabort - case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': - goto yystate9 - } - -yystate11: - c = lexer.getChar() - goto yyrule29 - -yystate12: - c = lexer.getChar() - switch { - default: - goto yyrule22 - case c == 'I' || c == 'i': - goto yystate13 - } - -yystate13: - c = lexer.getChar() - switch { - default: - goto yyabort - case c == 'N' || c == 'n': - goto yystate14 - } - -yystate14: - c = lexer.getChar() - switch { - default: - goto yyabort - case c == 'F' || c == 'f': - goto yystate15 - } - -yystate15: - c = lexer.getChar() - goto yyrule24 - -yystate16: - c = lexer.getChar() - switch { - default: - goto yyabort - case c >= '0' && c <= '9': - goto yystate17 - } - -yystate17: - c = lexer.getChar() - switch { - default: - goto yyrule24 - case c == 'E' || c == 'e': - goto yystate18 - case c >= '0' && c <= '9': - goto yystate17 - } - -yystate18: - c = lexer.getChar() - switch { - default: - goto yyabort - case c == '+' || c == '-': - goto yystate19 - case c >= '0' && c <= '9': - goto yystate20 - } - -yystate19: - c = lexer.getChar() - switch { - default: - goto yyabort - case c >= '0' && c <= '9': - goto yystate20 - } - -yystate20: - c = lexer.getChar() - switch { - default: - goto yyrule24 - case c >= '0' && c <= '9': - goto yystate20 - } - -yystate21: - c = lexer.getChar() - switch { - default: - goto yyrule23 - case c == '*': - goto yystate22 - case c == '/': - goto yystate23 - } - -yystate22: - c = lexer.getChar() - goto yyrule1 - -yystate23: - c = lexer.getChar() - switch { - default: - goto yyabort - case c == '\n': - goto yystate24 - case c >= '\x01' && c <= '\t' || c == '\v' || c == '\f' || c >= '\x0e' && c <= 'ÿ': - goto yystate23 - } - -yystate24: - c = lexer.getChar() - goto yyrule4 - -yystate25: - c = lexer.getChar() - switch { - default: - goto yyrule24 - case c == '.': - goto yystate17 - case c == 'E' || c == 'e': - goto yystate18 - case c == 'd' || c == 'h' || c == 'm' || c == 's' || c == 'w' || c == 'y': - goto yystate26 - case c >= '0' && c <= '9': - goto yystate25 - } - -yystate26: - c = lexer.getChar() - goto yyrule25 - -yystate27: - c = lexer.getChar() - switch { - default: - goto yyrule27 - case c >= '0' && c <= ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate27 - } - -yystate28: - c = lexer.getChar() - switch { - default: - goto yyrule20 - case c == '=': - goto yystate4 - } - -yystate29: - c = lexer.getChar() - switch { - default: - goto yyrule29 - case c == '=' || c == '~': - goto yystate4 - } - -yystate30: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'L': - goto yystate32 - case c == 'N': - goto yystate36 - case c == 'V': - goto yystate38 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'K' || c == 'M' || c >= 'O' && c <= 'U' || c >= 'W' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate31: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate32: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'E': - goto yystate33 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'D' || c >= 'F' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate33: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'R': - goto yystate34 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate34: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'T': - goto yystate35 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'S' || c >= 'U' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate35: - c = lexer.getChar() - switch { - default: - goto yyrule5 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate36: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'D': - goto yystate37 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'C' || c >= 'E' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate37: - c = lexer.getChar() - switch { - default: - goto yyrule20 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate38: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'G': - goto yystate39 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'H' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate39: - c = lexer.getChar() - switch { - default: - goto yyrule18 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate40: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'Y': - goto yystate41 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'X' || c == 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate41: - c = lexer.getChar() - switch { - default: - goto yyrule12 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate42: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'O': - goto yystate43 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'N' || c >= 'P' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate43: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'U': - goto yystate44 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'T' || c >= 'V' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate44: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'N': - goto yystate45 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'M' || c >= 'O' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate45: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'T': - goto yystate39 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'S' || c >= 'U' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate46: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'E': - goto yystate47 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'D' || c >= 'F' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate47: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'S': - goto yystate48 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'R' || c >= 'T' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate48: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'C': - goto yystate49 - case c >= '0' && c <= '9' || c == 'A' || c == 'B' || c >= 'D' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate49: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'R': - goto yystate50 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate50: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'I': - goto yystate51 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'H' || c >= 'J' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate51: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'P': - goto yystate52 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'O' || c >= 'Q' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate52: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'T': - goto yystate53 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'S' || c >= 'U' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate53: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'I': - goto yystate54 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'H' || c >= 'J' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate54: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'O': - goto yystate55 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'N' || c >= 'P' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate55: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'N': - goto yystate56 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'M' || c >= 'O' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate56: - c = lexer.getChar() - switch { - default: - goto yyrule10 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate57: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'O': - goto yystate58 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'N' || c >= 'P' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate58: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'R': - goto yystate59 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate59: - c = lexer.getChar() - switch { - default: - goto yyrule7 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate60: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'R': - goto yystate61 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate61: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'O': - goto yystate62 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'N' || c >= 'P' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate62: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'U': - goto yystate63 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'T' || c >= 'V' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate63: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'P': - goto yystate64 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'O' || c >= 'Q' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate64: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == '_': - goto yystate65 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate65: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'L': - goto yystate66 - case c == 'R': - goto yystate70 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'K' || c >= 'M' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate66: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'E': - goto yystate67 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'D' || c >= 'F' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate67: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'F': - goto yystate68 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'E' || c >= 'G' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate68: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'T': - goto yystate69 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'S' || c >= 'U' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate69: - c = lexer.getChar() - switch { - default: - goto yyrule14 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate70: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'I': - goto yystate71 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'H' || c >= 'J' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate71: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'G': - goto yystate72 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'H' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate72: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'H': - goto yystate68 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'G' || c >= 'I' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate73: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'F': - goto yystate74 - case c == 'N' || c == 'n': - goto yystate75 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'E' || c >= 'G' && c <= 'M' || c >= 'O' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z': - goto yystate31 - } - -yystate74: - c = lexer.getChar() - switch { - default: - goto yyrule6 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate75: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'F' || c == 'f': - goto yystate76 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'E' || c >= 'G' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'e' || c >= 'g' && c <= 'z': - goto yystate31 - } - -yystate76: - c = lexer.getChar() - switch { - default: - goto yyrule24 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate77: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'E': - goto yystate78 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'D' || c >= 'F' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate78: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'E': - goto yystate79 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'D' || c >= 'F' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate79: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'P': - goto yystate80 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'O' || c >= 'Q' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate80: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'I': - goto yystate81 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'H' || c >= 'J' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate81: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'N': - goto yystate82 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'M' || c >= 'O' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate82: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'G': - goto yystate83 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'H' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate83: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == '_': - goto yystate84 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate84: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'E': - goto yystate85 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'D' || c >= 'F' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate85: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'X': - goto yystate86 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'W' || c == 'Y' || c == 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate86: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'T': - goto yystate87 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'S' || c >= 'U' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate87: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'R': - goto yystate88 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate88: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'A': - goto yystate89 - case c >= '0' && c <= '9' || c >= 'B' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate89: - c = lexer.getChar() - switch { - default: - goto yyrule16 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate90: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'A': - goto yystate91 - case c == 'I': - goto yystate92 - case c >= '0' && c <= '9' || c >= 'B' && c <= 'H' || c >= 'J' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate91: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'X': - goto yystate39 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'W' || c == 'Y' || c == 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate92: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'N': - goto yystate39 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'M' || c >= 'O' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate93: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'A' || c == 'a': - goto yystate94 - case c >= '0' && c <= '9' || c >= 'B' && c <= 'Z' || c == '_' || c >= 'b' && c <= 'z': - goto yystate31 - } - -yystate94: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'N' || c == 'n': - goto yystate76 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'M' || c >= 'O' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z': - goto yystate31 - } - -yystate95: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'F': - goto yystate96 - case c == 'N': - goto yystate101 - case c == 'R': - goto yystate37 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'E' || c >= 'G' && c <= 'M' || c >= 'O' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate96: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'F': - goto yystate97 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'E' || c >= 'G' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate97: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'S': - goto yystate98 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'R' || c >= 'T' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate98: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'E': - goto yystate99 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'D' || c >= 'F' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate99: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'T': - goto yystate100 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'S' || c >= 'U' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate100: - c = lexer.getChar() - switch { - default: - goto yyrule17 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate101: - c = lexer.getChar() - switch { - default: - goto yyrule13 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate102: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'E': - goto yystate103 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'D' || c >= 'F' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate103: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'R': - goto yystate104 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate104: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'M': - goto yystate105 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'L' || c >= 'N' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate105: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'A': - goto yystate106 - case c >= '0' && c <= '9' || c >= 'B' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate106: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'N': - goto yystate107 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'M' || c >= 'O' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate107: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'E': - goto yystate108 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'D' || c >= 'F' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate108: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'N': - goto yystate109 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'M' || c >= 'O' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate109: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'T': - goto yystate110 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'S' || c >= 'U' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate110: - c = lexer.getChar() - switch { - default: - goto yyrule11 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate111: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'U': - goto yystate112 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'T' || c >= 'V' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate112: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'M': - goto yystate113 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'L' || c >= 'N' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate113: - c = lexer.getChar() - switch { - default: - goto yyrule18 - case c == ':': - goto yystate27 - case c == 'M': - goto yystate114 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'L' || c >= 'N' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate114: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'A': - goto yystate115 - case c >= '0' && c <= '9' || c >= 'B' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate115: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'R': - goto yystate116 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate116: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'Y': - goto yystate117 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'X' || c == 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate117: - c = lexer.getChar() - switch { - default: - goto yyrule9 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate118: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'I': - goto yystate119 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'H' || c >= 'J' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate119: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'T': - goto yystate120 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'S' || c >= 'U' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate120: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'H': - goto yystate121 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'G' || c >= 'I' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate121: - c = lexer.getChar() - switch { - default: - goto yyrule8 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate122: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'l': - goto yystate123 - case c == 'n': - goto yystate126 - case c == 'v': - goto yystate127 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'k' || c == 'm' || c >= 'o' && c <= 'u' || c >= 'w' && c <= 'z': - goto yystate31 - } - -yystate123: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'e': - goto yystate124 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z': - goto yystate31 - } - -yystate124: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'r': - goto yystate125 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z': - goto yystate31 - } - -yystate125: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 't': - goto yystate35 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z': - goto yystate31 - } - -yystate126: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'd': - goto yystate37 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'c' || c >= 'e' && c <= 'z': - goto yystate31 - } - -yystate127: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'g': - goto yystate128 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'f' || c >= 'h' && c <= 'z': - goto yystate31 - } - -yystate128: - c = lexer.getChar() - switch { - default: - goto yyrule19 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate129: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'y': - goto yystate41 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'x' || c == 'z': - goto yystate31 - } - -yystate130: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'o': - goto yystate131 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'n' || c >= 'p' && c <= 'z': - goto yystate31 - } - -yystate131: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'u': - goto yystate132 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 't' || c >= 'v' && c <= 'z': - goto yystate31 - } - -yystate132: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'n': - goto yystate133 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z': - goto yystate31 - } - -yystate133: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 't': - goto yystate128 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z': - goto yystate31 - } - -yystate134: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'e': - goto yystate135 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z': - goto yystate31 - } - -yystate135: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 's': - goto yystate136 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'r' || c >= 't' && c <= 'z': - goto yystate31 - } - -yystate136: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'c': - goto yystate137 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c == 'a' || c == 'b' || c >= 'd' && c <= 'z': - goto yystate31 - } - -yystate137: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'r': - goto yystate138 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z': - goto yystate31 - } - -yystate138: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'i': - goto yystate139 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'h' || c >= 'j' && c <= 'z': - goto yystate31 - } - -yystate139: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'p': - goto yystate140 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'o' || c >= 'q' && c <= 'z': - goto yystate31 - } - -yystate140: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 't': - goto yystate141 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z': - goto yystate31 - } - -yystate141: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'i': - goto yystate142 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'h' || c >= 'j' && c <= 'z': - goto yystate31 - } - -yystate142: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'o': - goto yystate143 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'n' || c >= 'p' && c <= 'z': - goto yystate31 - } - -yystate143: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'n': - goto yystate56 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z': - goto yystate31 - } - -yystate144: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'o': - goto yystate145 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'n' || c >= 'p' && c <= 'z': - goto yystate31 - } - -yystate145: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'r': - goto yystate59 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z': - goto yystate31 - } - -yystate146: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'r': - goto yystate147 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z': - goto yystate31 - } - -yystate147: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'o': - goto yystate148 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'n' || c >= 'p' && c <= 'z': - goto yystate31 - } - -yystate148: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'u': - goto yystate149 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 't' || c >= 'v' && c <= 'z': - goto yystate31 - } - -yystate149: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'p': - goto yystate150 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'o' || c >= 'q' && c <= 'z': - goto yystate31 - } - -yystate150: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == '_': - goto yystate151 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate151: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'l': - goto yystate152 - case c == 'r': - goto yystate156 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'k' || c >= 'm' && c <= 'q' || c >= 's' && c <= 'z': - goto yystate31 - } - -yystate152: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'e': - goto yystate153 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z': - goto yystate31 - } - -yystate153: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'f': - goto yystate154 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'e' || c >= 'g' && c <= 'z': - goto yystate31 - } - -yystate154: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 't': - goto yystate155 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z': - goto yystate31 - } - -yystate155: - c = lexer.getChar() - switch { - default: - goto yyrule15 - case c == ':': - goto yystate27 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate156: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'i': - goto yystate157 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'h' || c >= 'j' && c <= 'z': - goto yystate31 - } - -yystate157: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'g': - goto yystate158 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'f' || c >= 'h' && c <= 'z': - goto yystate31 - } - -yystate158: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'h': - goto yystate154 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'g' || c >= 'i' && c <= 'z': - goto yystate31 - } - -yystate159: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'N' || c == 'n': - goto yystate75 - case c == 'f': - goto yystate74 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'M' || c >= 'O' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'e' || c >= 'g' && c <= 'm' || c >= 'o' && c <= 'z': - goto yystate31 - } - -yystate160: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'e': - goto yystate161 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z': - goto yystate31 - } - -yystate161: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'e': - goto yystate162 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z': - goto yystate31 - } - -yystate162: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'p': - goto yystate163 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'o' || c >= 'q' && c <= 'z': - goto yystate31 - } - -yystate163: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'i': - goto yystate164 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'h' || c >= 'j' && c <= 'z': - goto yystate31 - } - -yystate164: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'n': - goto yystate165 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z': - goto yystate31 - } - -yystate165: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'g': - goto yystate166 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'f' || c >= 'h' && c <= 'z': - goto yystate31 - } - -yystate166: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == '_': - goto yystate167 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z': - goto yystate31 - } - -yystate167: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'e': - goto yystate168 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z': - goto yystate31 - } - -yystate168: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'x': - goto yystate169 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'w' || c == 'y' || c == 'z': - goto yystate31 - } - -yystate169: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 't': - goto yystate170 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z': - goto yystate31 - } - -yystate170: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'r': - goto yystate171 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z': - goto yystate31 - } - -yystate171: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'a': - goto yystate89 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'b' && c <= 'z': - goto yystate31 - } - -yystate172: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'a': - goto yystate173 - case c == 'i': - goto yystate174 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'b' && c <= 'h' || c >= 'j' && c <= 'z': - goto yystate31 - } - -yystate173: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'x': - goto yystate128 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'w' || c == 'y' || c == 'z': - goto yystate31 - } - -yystate174: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'n': - goto yystate128 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z': - goto yystate31 - } - -yystate175: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'f': - goto yystate176 - case c == 'n': - goto yystate101 - case c == 'r': - goto yystate37 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'e' || c >= 'g' && c <= 'm' || c >= 'o' && c <= 'q' || c >= 's' && c <= 'z': - goto yystate31 - } - -yystate176: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'f': - goto yystate177 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'e' || c >= 'g' && c <= 'z': - goto yystate31 - } - -yystate177: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 's': - goto yystate178 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'r' || c >= 't' && c <= 'z': - goto yystate31 - } - -yystate178: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'e': - goto yystate179 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z': - goto yystate31 - } - -yystate179: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 't': - goto yystate100 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z': - goto yystate31 - } - -yystate180: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'e': - goto yystate181 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z': - goto yystate31 - } - -yystate181: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'r': - goto yystate182 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z': - goto yystate31 - } - -yystate182: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'm': - goto yystate183 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'l' || c >= 'n' && c <= 'z': - goto yystate31 - } - -yystate183: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'a': - goto yystate184 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'b' && c <= 'z': - goto yystate31 - } - -yystate184: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'n': - goto yystate185 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z': - goto yystate31 - } - -yystate185: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'e': - goto yystate186 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z': - goto yystate31 - } - -yystate186: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'n': - goto yystate187 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z': - goto yystate31 - } - -yystate187: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 't': - goto yystate110 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z': - goto yystate31 - } - -yystate188: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'u': - goto yystate189 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 't' || c >= 'v' && c <= 'z': - goto yystate31 - } - -yystate189: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'm': - goto yystate190 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'l' || c >= 'n' && c <= 'z': - goto yystate31 - } - -yystate190: - c = lexer.getChar() - switch { - default: - goto yyrule19 - case c == ':': - goto yystate27 - case c == 'm': - goto yystate191 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'l' || c >= 'n' && c <= 'z': - goto yystate31 - } - -yystate191: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'a': - goto yystate192 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'b' && c <= 'z': - goto yystate31 - } - -yystate192: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'r': - goto yystate193 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z': - goto yystate31 - } - -yystate193: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'y': - goto yystate117 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'x' || c == 'z': - goto yystate31 - } - -yystate194: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'i': - goto yystate195 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'h' || c >= 'j' && c <= 'z': - goto yystate31 - } - -yystate195: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 't': - goto yystate196 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z': - goto yystate31 - } - -yystate196: - c = lexer.getChar() - switch { - default: - goto yyrule26 - case c == ':': - goto yystate27 - case c == 'h': - goto yystate121 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'g' || c >= 'i' && c <= 'z': - goto yystate31 - } - - goto yystate197 // silence unused label error -yystate197: - c = lexer.getChar() -yystart197: - switch { - default: - goto yyabort - case c == '*': - goto yystate199 - case c >= '\x01' && c <= ')' || c >= '+' && c <= 'ÿ': - goto yystate198 - } - -yystate198: - c = lexer.getChar() - goto yyrule3 - -yystate199: - c = lexer.getChar() - switch { - default: - goto yyrule3 - case c == '/': - goto yystate200 - } - -yystate200: - c = lexer.getChar() - goto yyrule2 - -yyrule1: // "/*" - { - currentState = S_COMMENTS - goto yystate0 - } -yyrule2: // "*/" - { - currentState = S_INITIAL - goto yystate0 - } -yyrule3: // .|\n - { - /* ignore chars within multi-line comments */ - goto yystate0 - } -yyrule4: // \/\/[^\r\n]*\n - { - /* gobble up one-line comments */ - goto yystate0 - } -yyrule5: // ALERT|alert - { - return ALERT - } -yyrule6: // IF|if - { - return IF - } -yyrule7: // FOR|for - { - return FOR - } -yyrule8: // WITH|with - { - return WITH - } -yyrule9: // SUMMARY|summary - { - return SUMMARY - } -yyrule10: // DESCRIPTION|description - { - return DESCRIPTION - } -yyrule11: // PERMANENT|permanent - { - return PERMANENT - } -yyrule12: // BY|by - { - return GROUP_OP - } -yyrule13: // ON|on - { - return MATCH_OP - } -yyrule14: // GROUP_LEFT|GROUP_RIGHT - { - lval.str = lexer.token() - return MATCH_MOD - goto yystate0 - } -yyrule15: // group_left|group_right - { - lval.str = strings.ToUpper(lexer.token()) - return MATCH_MOD - goto yystate0 - } -yyrule16: // KEEPING_EXTRA|keeping_extra - { - return KEEPING_EXTRA - } -yyrule17: // OFFSET|offset - { - return OFFSET - } -yyrule18: // AVG|SUM|MAX|MIN|COUNT - { - lval.str = lexer.token() - return AGGR_OP - goto yystate0 - } -yyrule19: // avg|sum|max|min|count - { - lval.str = strings.ToUpper(lexer.token()) - return AGGR_OP - goto yystate0 - } -yyrule20: // \<|>|AND|OR|and|or - { - lval.str = strings.ToUpper(lexer.token()) - return CMP_OP - goto yystate0 - } -yyrule21: // ==|!=|>=|<=|=~|!~ - { - lval.str = lexer.token() - return CMP_OP - goto yystate0 - } -yyrule22: // [+\-] - { - lval.str = lexer.token() - return ADDITIVE_OP - goto yystate0 - } -yyrule23: // [*/%] - { - lval.str = lexer.token() - return MULT_OP - goto yystate0 - } -yyrule24: // {FLOAT} - { - num, err := strconv.ParseFloat(lexer.token(), 64) - if err != nil && err.(*strconv.NumError).Err == strconv.ErrSyntax { - panic("Invalid float") - } - lval.num = clientmodel.SampleValue(num) - return NUMBER - } -yyrule25: // {D}+{U} - { - lval.str = lexer.token() - return DURATION - goto yystate0 - } -yyrule26: // {L}({L}|{D})* - { - lval.str = lexer.token() - return IDENTIFIER - goto yystate0 - } -yyrule27: // {M}({M}|{D})* - { - lval.str = lexer.token() - return METRICNAME - goto yystate0 - } -yyrule28: // {STR} - { - lval.str = lexer.token()[1 : len(lexer.token())-1] - return STRING - goto yystate0 - } -yyrule29: // [{}\[\]()=,] - { - return int(lexer.buf[0]) - } -yyrule30: // [\t\n\r ] - { - /* gobble up any whitespace */ - goto yystate0 - } - panic("unreachable") - - goto yyabort // silence unused label error - -yyabort: // no lexem recognized - - lexer.empty = true - return int(c) -} diff --git a/rules/load.go b/rules/load.go deleted file mode 100644 index c165b7d2a4..0000000000 --- a/rules/load.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2013 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 rules - -import ( - "bufio" - "errors" - "fmt" - "io" - "os" - "strings" - - "github.com/golang/glog" - - "github.com/prometheus/prometheus/rules/ast" -) - -// RulesLexer is the lexer for rule expressions. -type RulesLexer struct { - // Errors encountered during parsing. - errors []string - // Dummy token to simulate multiple start symbols (see below). - startToken int - // Parsed full rules. - parsedRules []Rule - // Parsed single expression. - parsedExpr ast.Node - - // Current character. - current byte - // Current token buffer. - buf []byte - // Input text. - src *bufio.Reader - // Whether we have a current char. - empty bool - - // Current input line. - line int - // Current character position within the current input line. - pos int -} - -func (lexer *RulesLexer) Error(errorStr string) { - err := fmt.Sprintf("Error parsing rules at line %v, char %v: %v", lexer.line, lexer.pos, errorStr) - lexer.errors = append(lexer.errors, err) -} - -func (lexer *RulesLexer) getChar() byte { - if lexer.current != 0 { - lexer.buf = append(lexer.buf, lexer.current) - } - lexer.current = 0 - if b, err := lexer.src.ReadByte(); err == nil { - if b == '\n' { - lexer.line++ - lexer.pos = 0 - } else { - lexer.pos++ - } - lexer.current = b - } else if err != io.EOF { - glog.Fatal(err) - } - return lexer.current -} - -func (lexer *RulesLexer) token() string { - return string(lexer.buf) -} - -func newRulesLexer(src io.Reader, singleExpr bool) *RulesLexer { - lexer := &RulesLexer{ - startToken: START_RULES, - src: bufio.NewReader(src), - pos: 1, - line: 1, - } - - if singleExpr { - lexer.startToken = START_EXPRESSION - } - lexer.getChar() - return lexer -} - -func lexAndParse(rulesReader io.Reader, singleExpr bool) (*RulesLexer, error) { - lexer := newRulesLexer(rulesReader, singleExpr) - ret := yyParse(lexer) - if ret != 0 && len(lexer.errors) == 0 { - lexer.Error("unknown parser error") - } - - if len(lexer.errors) > 0 { - err := errors.New(strings.Join(lexer.errors, "\n")) - return nil, err - } - return lexer, nil -} - -// LoadRulesFromReader parses rules from the provided reader and returns them. -func LoadRulesFromReader(rulesReader io.Reader) ([]Rule, error) { - lexer, err := lexAndParse(rulesReader, false) - if err != nil { - return nil, err - } - return lexer.parsedRules, err -} - -// LoadRulesFromString parses rules from the provided string returns them. -func LoadRulesFromString(rulesString string) ([]Rule, error) { - rulesReader := strings.NewReader(rulesString) - return LoadRulesFromReader(rulesReader) -} - -// LoadRulesFromFile parses rules from the file of the provided name and returns -// them. -func LoadRulesFromFile(fileName string) ([]Rule, error) { - rulesReader, err := os.Open(fileName) - if err != nil { - return []Rule{}, err - } - defer rulesReader.Close() - return LoadRulesFromReader(rulesReader) -} - -// LoadExprFromReader parses a single expression from the provided reader and -// returns it as an AST node. -func LoadExprFromReader(exprReader io.Reader) (ast.Node, error) { - lexer, err := lexAndParse(exprReader, true) - if err != nil { - return nil, err - } - return lexer.parsedExpr, err -} - -// LoadExprFromString parses a single expression from the provided string and -// returns it as an AST node. -func LoadExprFromString(exprString string) (ast.Node, error) { - exprReader := strings.NewReader(exprString) - return LoadExprFromReader(exprReader) -} - -// LoadExprFromFile parses a single expression from the file of the provided -// name and returns it as an AST node. -func LoadExprFromFile(fileName string) (ast.Node, error) { - exprReader, err := os.Open(fileName) - if err != nil { - return nil, err - } - defer exprReader.Close() - return LoadExprFromReader(exprReader) -} diff --git a/rules/manager/manager.go b/rules/manager.go similarity index 78% rename from rules/manager/manager.go rename to rules/manager.go index 0b1a386a5c..26d12f95df 100644 --- a/rules/manager/manager.go +++ b/rules/manager.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package manager +package rules import ( "fmt" @@ -20,15 +20,15 @@ import ( "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" + "golang.org/x/net/context" clientmodel "github.com/prometheus/client_golang/model" - "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/notification" - "github.com/prometheus/prometheus/rules" + "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/storage" - "github.com/prometheus/prometheus/storage/local" "github.com/prometheus/prometheus/templates" + "github.com/prometheus/prometheus/utility" ) // Constants for instrumentation. @@ -73,60 +73,61 @@ func init() { // A RuleManager manages recording and alerting rules. Create instances with // NewRuleManager. type RuleManager interface { - // Load and add rules from rule files specified in the configuration. - AddRulesFromConfig(config config.Config) error // Start the rule manager's periodic rule evaluation. Run() // Stop the rule manager's rule evaluation cycles. Stop() // Return all rules. - Rules() []rules.Rule + Rules() []Rule // Return all alerting rules. - AlertingRules() []*rules.AlertingRule + AlertingRules() []*AlertingRule } type ruleManager struct { // Protects the rules list. sync.Mutex - rules []rules.Rule + rules []Rule done chan bool - interval time.Duration - storage local.Storage + interval time.Duration + queryEngine *promql.Engine sampleAppender storage.SampleAppender notificationHandler *notification.NotificationHandler prometheusURL string - pathPrefix string + pathPrefix string } // RuleManagerOptions bundles options for the RuleManager. type RuleManagerOptions struct { EvaluationInterval time.Duration - Storage local.Storage + QueryEngine *promql.Engine NotificationHandler *notification.NotificationHandler SampleAppender storage.SampleAppender PrometheusURL string - PathPrefix string + PathPrefix string } // NewRuleManager returns an implementation of RuleManager, ready to be started // by calling the Run method. func NewRuleManager(o *RuleManagerOptions) RuleManager { manager := &ruleManager{ - rules: []rules.Rule{}, + rules: []Rule{}, done: make(chan bool), interval: o.EvaluationInterval, - storage: o.Storage, sampleAppender: o.SampleAppender, + queryEngine: o.QueryEngine, notificationHandler: o.NotificationHandler, prometheusURL: o.PrometheusURL, } + manager.queryEngine.RegisterAlertHandler("rule_manager", manager.AddAlertingRule) + manager.queryEngine.RegisterRecordHandler("rule_manager", manager.AddRecordingRule) + return manager } @@ -162,7 +163,7 @@ func (m *ruleManager) Stop() { m.done <- true } -func (m *ruleManager) queueAlertNotifications(rule *rules.AlertingRule, timestamp clientmodel.Timestamp) { +func (m *ruleManager) queueAlertNotifications(rule *AlertingRule, timestamp clientmodel.Timestamp) { activeAlerts := rule.ActiveAlerts() if len(activeAlerts) == 0 { return @@ -170,7 +171,7 @@ func (m *ruleManager) queueAlertNotifications(rule *rules.AlertingRule, timestam notifications := make(notification.NotificationReqs, 0, len(activeAlerts)) for _, aa := range activeAlerts { - if aa.State != rules.Firing { + if aa.State != Firing { // BUG: In the future, make AlertManager support pending alerts? continue } @@ -192,7 +193,7 @@ func (m *ruleManager) queueAlertNotifications(rule *rules.AlertingRule, timestam defs := "{{$labels := .Labels}}{{$value := .Value}}" expand := func(text string) string { - template := templates.NewTemplateExpander(defs+text, "__alert_"+rule.Name(), tmplData, timestamp, m.storage, m.pathPrefix) + template := templates.NewTemplateExpander(defs+text, "__alert_"+rule.Name(), tmplData, timestamp, m.queryEngine, m.pathPrefix) result, err := template.Expand() if err != nil { result = err.Error() @@ -205,12 +206,12 @@ func (m *ruleManager) queueAlertNotifications(rule *rules.AlertingRule, timestam Summary: expand(rule.Summary), Description: expand(rule.Description), Labels: aa.Labels.Merge(clientmodel.LabelSet{ - rules.AlertNameLabel: clientmodel.LabelValue(rule.Name()), + AlertNameLabel: clientmodel.LabelValue(rule.Name()), }), Value: aa.Value, ActiveSince: aa.ActiveSince.Time(), RuleString: rule.String(), - GeneratorURL: m.prometheusURL + rules.GraphLinkForExpression(rule.Vector.String()), + GeneratorURL: m.prometheusURL + utility.GraphLinkForExpression(rule.Vector.String()), }) } m.notificationHandler.SubmitReqs(notifications) @@ -221,18 +222,18 @@ func (m *ruleManager) runIteration() { wg := sync.WaitGroup{} m.Lock() - rulesSnapshot := make([]rules.Rule, len(m.rules)) + rulesSnapshot := make([]Rule, len(m.rules)) copy(rulesSnapshot, m.rules) m.Unlock() for _, rule := range rulesSnapshot { wg.Add(1) // BUG(julius): Look at fixing thundering herd. - go func(rule rules.Rule) { + go func(rule Rule) { defer wg.Done() start := time.Now() - vector, err := rule.Eval(now, m.storage) + vector, err := rule.Eval(now, m.queryEngine) duration := time.Since(start) if err != nil { @@ -242,17 +243,17 @@ func (m *ruleManager) runIteration() { } switch r := rule.(type) { - case *rules.AlertingRule: + case *AlertingRule: m.queueAlertNotifications(r, now) evalDuration.WithLabelValues(alertingRuleType).Observe( float64(duration / time.Millisecond), ) - case *rules.RecordingRule: + case *RecordingRule: evalDuration.WithLabelValues(recordingRuleType).Observe( float64(duration / time.Millisecond), ) default: - panic(fmt.Sprintf("Unknown rule type: %T", rule)) + panic(fmt.Errorf("Unknown rule type: %T", rule)) } for _, s := range vector { @@ -267,35 +268,40 @@ func (m *ruleManager) runIteration() { wg.Wait() } -func (m *ruleManager) AddRulesFromConfig(config config.Config) error { - for _, ruleFile := range config.Global.RuleFile { - newRules, err := rules.LoadRulesFromFile(ruleFile) - if err != nil { - return fmt.Errorf("%s: %s", ruleFile, err) - } - m.Lock() - m.rules = append(m.rules, newRules...) - m.Unlock() - } +func (m *ruleManager) AddAlertingRule(ctx context.Context, r *promql.AlertStmt) error { + rule := NewAlertingRule(r.Name, r.Expr, r.Duration, r.Labels, r.Summary, r.Description) + + m.Lock() + m.rules = append(m.rules, rule) + m.Unlock() return nil } -func (m *ruleManager) Rules() []rules.Rule { +func (m *ruleManager) AddRecordingRule(ctx context.Context, r *promql.RecordStmt) error { + rule := &RecordingRule{r.Name, r.Expr, r.Labels} + + m.Lock() + m.rules = append(m.rules, rule) + m.Unlock() + return nil +} + +func (m *ruleManager) Rules() []Rule { m.Lock() defer m.Unlock() - rules := make([]rules.Rule, len(m.rules)) + rules := make([]Rule, len(m.rules)) copy(rules, m.rules) return rules } -func (m *ruleManager) AlertingRules() []*rules.AlertingRule { +func (m *ruleManager) AlertingRules() []*AlertingRule { m.Lock() defer m.Unlock() - alerts := []*rules.AlertingRule{} + alerts := []*AlertingRule{} for _, rule := range m.rules { - if alertingRule, ok := rule.(*rules.AlertingRule); ok { + if alertingRule, ok := rule.(*AlertingRule); ok { alerts = append(alerts, alertingRule) } } diff --git a/rules/parser.y b/rules/parser.y deleted file mode 100644 index feba0fc0bd..0000000000 --- a/rules/parser.y +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright 2013 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 rules - - import ( - clientmodel "github.com/prometheus/client_golang/model" - - "github.com/prometheus/prometheus/rules/ast" - "github.com/prometheus/prometheus/storage/metric" - ) -%} - -%union { - num clientmodel.SampleValue - str string - ruleNode ast.Node - ruleNodeSlice []ast.Node - boolean bool - labelNameSlice clientmodel.LabelNames - labelSet clientmodel.LabelSet - labelMatcher *metric.LabelMatcher - labelMatchers metric.LabelMatchers - vectorMatching *vectorMatching -} - -/* We simulate multiple start symbols for closely-related grammars via dummy tokens. See - http://www.gnu.org/software/bison/manual/html_node/Multiple-start_002dsymbols.html - Reason: we want to be able to parse lists of named rules as well as single expressions. - */ -%token START_RULES START_EXPRESSION - -%token IDENTIFIER STRING DURATION METRICNAME -%token NUMBER -%token PERMANENT GROUP_OP KEEPING_EXTRA OFFSET MATCH_OP -%token AGGR_OP CMP_OP ADDITIVE_OP MULT_OP MATCH_MOD -%token ALERT IF FOR WITH SUMMARY DESCRIPTION - -%type func_arg_list -%type label_list grouping_opts -%type label_assign label_assign_list rule_labels -%type label_match -%type label_match_list label_matches -%type vector_matching -%type rule_expr func_arg -%type qualifier extra_labels_opts -%type for_duration metric_name label_match_type offset_opts - -%right '=' -%left CMP_OP -%left ADDITIVE_OP -%left MULT_OP -%start start - -%% -start : START_RULES rules_stat_list - | START_EXPRESSION saved_rule_expr - ; - -rules_stat_list : /* empty */ - | rules_stat_list rules_stat - ; - -saved_rule_expr : rule_expr - { yylex.(*RulesLexer).parsedExpr = $1 } - ; - - -rules_stat : qualifier metric_name rule_labels '=' rule_expr - { - rule, err := CreateRecordingRule($2, $3, $5, $1) - if err != nil { yylex.Error(err.Error()); return 1 } - yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule) - } - | ALERT IDENTIFIER IF rule_expr for_duration WITH rule_labels SUMMARY STRING DESCRIPTION STRING - { - rule, err := CreateAlertingRule($2, $4, $5, $7, $9, $11) - if err != nil { yylex.Error(err.Error()); return 1 } - yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule) - } - ; - -for_duration : /* empty */ - { $$ = "0s" } - | FOR DURATION - { $$ = $2 } - ; - -qualifier : /* empty */ - { $$ = false } - | PERMANENT - { $$ = true } - ; - -metric_name : METRICNAME - { $$ = $1 } - | IDENTIFIER - { $$ = $1 } - ; - -rule_labels : /* empty */ - { $$ = clientmodel.LabelSet{} } - | '{' label_assign_list '}' - { $$ = $2 } - | '{' '}' - { $$ = clientmodel.LabelSet{} } - -label_assign_list : label_assign - { $$ = $1 } - | label_assign_list ',' label_assign - { for k, v := range $3 { $$[k] = v } } - ; - -label_assign : IDENTIFIER '=' STRING - { $$ = clientmodel.LabelSet{ clientmodel.LabelName($1): clientmodel.LabelValue($3) } } - ; - -label_matches : /* empty */ - { $$ = metric.LabelMatchers{} } - | '{' '}' - { $$ = metric.LabelMatchers{} } - | '{' label_match_list '}' - { $$ = $2 } - ; - -label_match_list : label_match - { $$ = metric.LabelMatchers{$1} } - | label_match_list ',' label_match - { $$ = append($$, $3) } - ; - -label_match : IDENTIFIER label_match_type STRING - { - var err error - $$, err = newLabelMatcher($2, clientmodel.LabelName($1), clientmodel.LabelValue($3)) - if err != nil { yylex.Error(err.Error()); return 1 } - } - ; - -label_match_type : '=' - { $$ = "=" } - | CMP_OP - { $$ = $1 } - ; - -offset_opts : /* empty */ - { $$ = "0s" } - | OFFSET DURATION - { $$ = $2 } - ; - -rule_expr : '(' rule_expr ')' - { $$ = $2 } - | '{' label_match_list '}' offset_opts - { - var err error - $$, err = NewVectorSelector($2, $4) - if err != nil { yylex.Error(err.Error()); return 1 } - } - | metric_name label_matches offset_opts - { - var err error - m, err := metric.NewLabelMatcher(metric.Equal, clientmodel.MetricNameLabel, clientmodel.LabelValue($1)) - if err != nil { yylex.Error(err.Error()); return 1 } - $2 = append($2, m) - $$, err = NewVectorSelector($2, $3) - if err != nil { yylex.Error(err.Error()); return 1 } - } - | IDENTIFIER '(' func_arg_list ')' - { - var err error - $$, err = NewFunctionCall($1, $3) - if err != nil { yylex.Error(err.Error()); return 1 } - } - | IDENTIFIER '(' ')' - { - var err error - $$, err = NewFunctionCall($1, []ast.Node{}) - if err != nil { yylex.Error(err.Error()); return 1 } - } - | rule_expr '[' DURATION ']' offset_opts - { - var err error - $$, err = NewMatrixSelector($1, $3, $5) - if err != nil { yylex.Error(err.Error()); return 1 } - } - | AGGR_OP '(' rule_expr ')' grouping_opts extra_labels_opts - { - var err error - $$, err = NewVectorAggregation($1, $3, $5, $6) - if err != nil { yylex.Error(err.Error()); return 1 } - } - | AGGR_OP grouping_opts extra_labels_opts '(' rule_expr ')' - { - var err error - $$, err = NewVectorAggregation($1, $5, $2, $3) - if err != nil { yylex.Error(err.Error()); return 1 } - } - /* Yacc can only attach associativity to terminals, so we - * have to list all operators here. */ - | rule_expr ADDITIVE_OP vector_matching rule_expr - { - var err error - $$, err = NewArithExpr($2, $1, $4, $3) - if err != nil { yylex.Error(err.Error()); return 1 } - } - | rule_expr MULT_OP vector_matching rule_expr - { - var err error - $$, err = NewArithExpr($2, $1, $4, $3) - if err != nil { yylex.Error(err.Error()); return 1 } - } - | rule_expr CMP_OP vector_matching rule_expr - { - var err error - $$, err = NewArithExpr($2, $1, $4, $3) - if err != nil { yylex.Error(err.Error()); return 1 } - } - | NUMBER - { $$ = NewScalarLiteral($1, "+")} - | ADDITIVE_OP NUMBER - { $$ = NewScalarLiteral($2, $1)} - ; - -extra_labels_opts : /* empty */ - { $$ = false } - | KEEPING_EXTRA - { $$ = true } - ; - -vector_matching : /* empty */ - { $$ = nil } - | MATCH_OP '(' label_list ')' - { - var err error - $$, err = newVectorMatching("", $3, nil) - if err != nil { yylex.Error(err.Error()); return 1 } - } - | MATCH_OP '(' label_list ')' MATCH_MOD '(' label_list ')' - { - var err error - $$, err = newVectorMatching($5, $3, $7) - if err != nil { yylex.Error(err.Error()); return 1 } - } - ; - -grouping_opts : - { $$ = clientmodel.LabelNames{} } - | GROUP_OP '(' label_list ')' - { $$ = $3 } - ; - -label_list : IDENTIFIER - { $$ = clientmodel.LabelNames{clientmodel.LabelName($1)} } - | label_list ',' IDENTIFIER - { $$ = append($$, clientmodel.LabelName($3)) } - ; - -func_arg_list : func_arg - { $$ = []ast.Node{$1} } - | func_arg_list ',' func_arg - { $$ = append($$, $3) } - ; - -func_arg : rule_expr - { $$ = $1 } - | STRING - { $$ = ast.NewStringLiteral($1) } - ; -%% diff --git a/rules/parser.y.go b/rules/parser.y.go deleted file mode 100644 index e066ff3a5a..0000000000 --- a/rules/parser.y.go +++ /dev/null @@ -1,784 +0,0 @@ -//line parser.y:15 -package rules - -import __yyfmt__ "fmt" - -//line parser.y:15 -import ( - clientmodel "github.com/prometheus/client_golang/model" - - "github.com/prometheus/prometheus/rules/ast" - "github.com/prometheus/prometheus/storage/metric" -) - -//line parser.y:25 -type yySymType struct { - yys int - num clientmodel.SampleValue - str string - ruleNode ast.Node - ruleNodeSlice []ast.Node - boolean bool - labelNameSlice clientmodel.LabelNames - labelSet clientmodel.LabelSet - labelMatcher *metric.LabelMatcher - labelMatchers metric.LabelMatchers - vectorMatching *vectorMatching -} - -const START_RULES = 57346 -const START_EXPRESSION = 57347 -const IDENTIFIER = 57348 -const STRING = 57349 -const DURATION = 57350 -const METRICNAME = 57351 -const NUMBER = 57352 -const PERMANENT = 57353 -const GROUP_OP = 57354 -const KEEPING_EXTRA = 57355 -const OFFSET = 57356 -const MATCH_OP = 57357 -const AGGR_OP = 57358 -const CMP_OP = 57359 -const ADDITIVE_OP = 57360 -const MULT_OP = 57361 -const MATCH_MOD = 57362 -const ALERT = 57363 -const IF = 57364 -const FOR = 57365 -const WITH = 57366 -const SUMMARY = 57367 -const DESCRIPTION = 57368 - -var yyToknames = []string{ - "START_RULES", - "START_EXPRESSION", - "IDENTIFIER", - "STRING", - "DURATION", - "METRICNAME", - "NUMBER", - "PERMANENT", - "GROUP_OP", - "KEEPING_EXTRA", - "OFFSET", - "MATCH_OP", - "AGGR_OP", - "CMP_OP", - "ADDITIVE_OP", - "MULT_OP", - "MATCH_MOD", - "ALERT", - "IF", - "FOR", - "WITH", - "SUMMARY", - "DESCRIPTION", - "'='", -} -var yyStatenames = []string{} - -const yyEofCode = 1 -const yyErrCode = 2 -const yyMaxDepth = 200 - -//line parser.y:281 - -//line yacctab:1 -var yyExca = []int{ - -1, 1, - 1, -1, - -2, 0, - -1, 4, - 1, 1, - -2, 10, -} - -const yyNprod = 56 -const yyPrivate = 57344 - -var yyTokenNames []string -var yyStates []string - -const yyLast = 159 - -var yyAct = []int{ - - 78, 61, 83, 58, 55, 54, 31, 48, 6, 25, - 20, 21, 23, 21, 10, 56, 64, 14, 12, 10, - 56, 19, 14, 12, 11, 19, 13, 19, 92, 11, - 113, 13, 22, 20, 21, 57, 8, 32, 109, 7, - 53, 8, 77, 65, 7, 67, 68, 101, 19, 22, - 20, 21, 70, 69, 10, 98, 30, 14, 12, 22, - 20, 21, 94, 95, 11, 19, 13, 87, 85, 92, - 96, 99, 86, 84, 76, 19, 8, 66, 60, 7, - 29, 88, 90, 89, 24, 93, 22, 20, 21, 22, - 20, 21, 92, 100, 91, 75, 82, 74, 103, 73, - 43, 42, 19, 44, 43, 19, 26, 108, 62, 47, - 111, 28, 80, 51, 114, 110, 38, 105, 63, 46, - 18, 107, 39, 9, 49, 59, 32, 33, 35, 50, - 17, 14, 106, 72, 37, 115, 112, 104, 40, 41, - 34, 71, 79, 84, 102, 26, 36, 2, 3, 15, - 5, 4, 1, 45, 97, 16, 27, 81, 52, -} -var yyPact = []int{ - - 143, -1000, -1000, 48, 109, -1000, 72, 48, 139, 83, - 49, 25, -1000, 117, -1000, -1000, 122, 140, -1000, 126, - 107, 107, 107, 69, 74, -1000, 92, 110, 100, 8, - 48, 112, 47, -1000, 80, -1000, 96, -18, 48, 46, - 48, 48, -1000, 139, 110, 134, -1000, -1000, -1000, 125, - -1000, 70, 65, -1000, -1000, 72, -1000, 42, 11, -1000, - 136, 85, 67, 48, 110, -6, 136, -12, -8, -1000, - -1000, -1000, -1000, -1000, -1000, 13, 114, 48, 62, -1000, - 48, 33, -1000, -1000, 43, 32, -1000, 39, -1000, 112, - 15, -1000, 138, 72, -1000, 137, 130, 93, 124, 101, - -1000, -1000, -1000, -1000, -1000, 80, -1000, 7, 90, 136, - 129, -2, 88, -1000, 128, -1000, -} -var yyPgo = []int{ - - 0, 158, 0, 6, 2, 157, 1, 9, 84, 156, - 116, 4, 5, 155, 3, 154, 123, 153, 7, 152, - 151, 150, 149, -} -var yyR1 = []int{ - - 0, 19, 19, 20, 20, 21, 22, 22, 15, 15, - 13, 13, 16, 16, 6, 6, 6, 5, 5, 4, - 9, 9, 9, 8, 8, 7, 17, 17, 18, 18, - 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, - 11, 11, 11, 14, 14, 10, 10, 10, 3, 3, - 2, 2, 1, 1, 12, 12, -} -var yyR2 = []int{ - - 0, 2, 2, 0, 2, 1, 5, 11, 0, 2, - 0, 1, 1, 1, 0, 3, 2, 1, 3, 3, - 0, 2, 3, 1, 3, 3, 1, 1, 0, 2, - 3, 4, 3, 4, 3, 5, 6, 6, 4, 4, - 4, 1, 2, 0, 1, 0, 4, 8, 0, 4, - 1, 3, 1, 3, 1, 1, -} -var yyChk = []int{ - - -1000, -19, 4, 5, -20, -21, -11, 31, 28, -16, - 6, 16, 10, 18, 9, -22, -13, 21, 11, 33, - 18, 19, 17, -11, -8, -7, 6, -9, 28, 31, - 31, -3, 12, 10, -16, 6, 6, 8, -10, 15, - -10, -10, 32, 30, 29, -17, 27, 17, -18, 14, - 29, -8, -1, 32, -12, -11, 7, -11, -14, 13, - 31, -6, 28, 22, 34, -11, 31, -11, -11, -7, - -18, 7, 8, 29, 32, 30, 32, 31, -2, 6, - 27, -5, 29, -4, 6, -11, -18, -2, -12, -3, - -11, 32, 30, -11, 29, 30, 27, -15, 23, 32, - -14, 32, 6, -4, 7, 24, 8, 20, -6, 31, - 25, -2, 7, 32, 26, 7, -} -var yyDef = []int{ - - 0, -2, 3, 0, -2, 2, 5, 0, 0, 20, - 13, 48, 41, 0, 12, 4, 0, 0, 11, 0, - 45, 45, 45, 0, 0, 23, 0, 28, 0, 0, - 0, 43, 0, 42, 14, 13, 0, 0, 0, 0, - 0, 0, 30, 0, 28, 0, 26, 27, 32, 0, - 21, 0, 0, 34, 52, 54, 55, 0, 0, 44, - 0, 0, 0, 0, 28, 38, 0, 39, 40, 24, - 31, 25, 29, 22, 33, 0, 48, 0, 0, 50, - 0, 0, 16, 17, 0, 8, 35, 0, 53, 43, - 0, 49, 0, 6, 15, 0, 0, 0, 0, 46, - 36, 37, 51, 18, 19, 14, 9, 0, 0, 0, - 0, 0, 0, 47, 0, 7, -} -var yyTok1 = []int{ - - 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 31, 32, 3, 3, 30, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 27, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 33, 3, 34, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 28, 3, 29, -} -var yyTok2 = []int{ - - 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, - 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - 22, 23, 24, 25, 26, -} -var yyTok3 = []int{ - 0, -} - -//line yaccpar:1 - -/* parser for yacc output */ - -var yyDebug = 0 - -type yyLexer interface { - Lex(lval *yySymType) int - Error(s string) -} - -const yyFlag = -1000 - -func yyTokname(c int) string { - // 4 is TOKSTART above - if c >= 4 && c-4 < len(yyToknames) { - if yyToknames[c-4] != "" { - return yyToknames[c-4] - } - } - return __yyfmt__.Sprintf("tok-%v", c) -} - -func yyStatname(s int) string { - if s >= 0 && s < len(yyStatenames) { - if yyStatenames[s] != "" { - return yyStatenames[s] - } - } - return __yyfmt__.Sprintf("state-%v", s) -} - -func yylex1(lex yyLexer, lval *yySymType) int { - c := 0 - char := lex.Lex(lval) - if char <= 0 { - c = yyTok1[0] - goto out - } - if char < len(yyTok1) { - c = yyTok1[char] - goto out - } - if char >= yyPrivate { - if char < yyPrivate+len(yyTok2) { - c = yyTok2[char-yyPrivate] - goto out - } - } - for i := 0; i < len(yyTok3); i += 2 { - c = yyTok3[i+0] - if c == char { - c = yyTok3[i+1] - goto out - } - } - -out: - if c == 0 { - c = yyTok2[1] /* unknown char */ - } - if yyDebug >= 3 { - __yyfmt__.Printf("lex %s(%d)\n", yyTokname(c), uint(char)) - } - return c -} - -func yyParse(yylex yyLexer) int { - var yyn int - var yylval yySymType - var yyVAL yySymType - yyS := make([]yySymType, yyMaxDepth) - - Nerrs := 0 /* number of errors */ - Errflag := 0 /* error recovery flag */ - yystate := 0 - yychar := -1 - yyp := -1 - goto yystack - -ret0: - return 0 - -ret1: - return 1 - -yystack: - /* put a state and value onto the stack */ - if yyDebug >= 4 { - __yyfmt__.Printf("char %v in %v\n", yyTokname(yychar), yyStatname(yystate)) - } - - yyp++ - if yyp >= len(yyS) { - nyys := make([]yySymType, len(yyS)*2) - copy(nyys, yyS) - yyS = nyys - } - yyS[yyp] = yyVAL - yyS[yyp].yys = yystate - -yynewstate: - yyn = yyPact[yystate] - if yyn <= yyFlag { - goto yydefault /* simple state */ - } - if yychar < 0 { - yychar = yylex1(yylex, &yylval) - } - yyn += yychar - if yyn < 0 || yyn >= yyLast { - goto yydefault - } - yyn = yyAct[yyn] - if yyChk[yyn] == yychar { /* valid shift */ - yychar = -1 - yyVAL = yylval - yystate = yyn - if Errflag > 0 { - Errflag-- - } - goto yystack - } - -yydefault: - /* default state action */ - yyn = yyDef[yystate] - if yyn == -2 { - if yychar < 0 { - yychar = yylex1(yylex, &yylval) - } - - /* look through exception table */ - xi := 0 - for { - if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate { - break - } - xi += 2 - } - for xi += 2; ; xi += 2 { - yyn = yyExca[xi+0] - if yyn < 0 || yyn == yychar { - break - } - } - yyn = yyExca[xi+1] - if yyn < 0 { - goto ret0 - } - } - if yyn == 0 { - /* error ... attempt to resume parsing */ - switch Errflag { - case 0: /* brand new error */ - yylex.Error("syntax error") - Nerrs++ - if yyDebug >= 1 { - __yyfmt__.Printf("%s", yyStatname(yystate)) - __yyfmt__.Printf(" saw %s\n", yyTokname(yychar)) - } - fallthrough - - case 1, 2: /* incompletely recovered error ... try again */ - Errflag = 3 - - /* find a state where "error" is a legal shift action */ - for yyp >= 0 { - yyn = yyPact[yyS[yyp].yys] + yyErrCode - if yyn >= 0 && yyn < yyLast { - yystate = yyAct[yyn] /* simulate a shift of "error" */ - if yyChk[yystate] == yyErrCode { - goto yystack - } - } - - /* the current p has no shift on "error", pop stack */ - if yyDebug >= 2 { - __yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys) - } - yyp-- - } - /* there is no state on the stack with an error shift ... abort */ - goto ret1 - - case 3: /* no shift yet; clobber input char */ - if yyDebug >= 2 { - __yyfmt__.Printf("error recovery discards %s\n", yyTokname(yychar)) - } - if yychar == yyEofCode { - goto ret1 - } - yychar = -1 - goto yynewstate /* try again in the same state */ - } - } - - /* reduction by production yyn */ - if yyDebug >= 2 { - __yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate)) - } - - yynt := yyn - yypt := yyp - _ = yypt // guard against "declared and not used" - - yyp -= yyR2[yyn] - yyVAL = yyS[yyp+1] - - /* consult goto table to find next state */ - yyn = yyR1[yyn] - yyg := yyPgo[yyn] - yyj := yyg + yyS[yyp].yys + 1 - - if yyj >= yyLast { - yystate = yyAct[yyg] - } else { - yystate = yyAct[yyj] - if yyChk[yystate] != -yyn { - yystate = yyAct[yyg] - } - } - // dummy call; replaced with literal code - switch yynt { - - case 5: - //line parser.y:76 - { - yylex.(*RulesLexer).parsedExpr = yyS[yypt-0].ruleNode - } - case 6: - //line parser.y:81 - { - rule, err := CreateRecordingRule(yyS[yypt-3].str, yyS[yypt-2].labelSet, yyS[yypt-0].ruleNode, yyS[yypt-4].boolean) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule) - } - case 7: - //line parser.y:87 - { - rule, err := CreateAlertingRule(yyS[yypt-9].str, yyS[yypt-7].ruleNode, yyS[yypt-6].str, yyS[yypt-4].labelSet, yyS[yypt-2].str, yyS[yypt-0].str) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule) - } - case 8: - //line parser.y:95 - { - yyVAL.str = "0s" - } - case 9: - //line parser.y:97 - { - yyVAL.str = yyS[yypt-0].str - } - case 10: - //line parser.y:101 - { - yyVAL.boolean = false - } - case 11: - //line parser.y:103 - { - yyVAL.boolean = true - } - case 12: - //line parser.y:107 - { - yyVAL.str = yyS[yypt-0].str - } - case 13: - //line parser.y:109 - { - yyVAL.str = yyS[yypt-0].str - } - case 14: - //line parser.y:113 - { - yyVAL.labelSet = clientmodel.LabelSet{} - } - case 15: - //line parser.y:115 - { - yyVAL.labelSet = yyS[yypt-1].labelSet - } - case 16: - //line parser.y:117 - { - yyVAL.labelSet = clientmodel.LabelSet{} - } - case 17: - //line parser.y:120 - { - yyVAL.labelSet = yyS[yypt-0].labelSet - } - case 18: - //line parser.y:122 - { - for k, v := range yyS[yypt-0].labelSet { - yyVAL.labelSet[k] = v - } - } - case 19: - //line parser.y:126 - { - yyVAL.labelSet = clientmodel.LabelSet{clientmodel.LabelName(yyS[yypt-2].str): clientmodel.LabelValue(yyS[yypt-0].str)} - } - case 20: - //line parser.y:130 - { - yyVAL.labelMatchers = metric.LabelMatchers{} - } - case 21: - //line parser.y:132 - { - yyVAL.labelMatchers = metric.LabelMatchers{} - } - case 22: - //line parser.y:134 - { - yyVAL.labelMatchers = yyS[yypt-1].labelMatchers - } - case 23: - //line parser.y:138 - { - yyVAL.labelMatchers = metric.LabelMatchers{yyS[yypt-0].labelMatcher} - } - case 24: - //line parser.y:140 - { - yyVAL.labelMatchers = append(yyVAL.labelMatchers, yyS[yypt-0].labelMatcher) - } - case 25: - //line parser.y:144 - { - var err error - yyVAL.labelMatcher, err = newLabelMatcher(yyS[yypt-1].str, clientmodel.LabelName(yyS[yypt-2].str), clientmodel.LabelValue(yyS[yypt-0].str)) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - } - case 26: - //line parser.y:152 - { - yyVAL.str = "=" - } - case 27: - //line parser.y:154 - { - yyVAL.str = yyS[yypt-0].str - } - case 28: - //line parser.y:158 - { - yyVAL.str = "0s" - } - case 29: - //line parser.y:160 - { - yyVAL.str = yyS[yypt-0].str - } - case 30: - //line parser.y:164 - { - yyVAL.ruleNode = yyS[yypt-1].ruleNode - } - case 31: - //line parser.y:166 - { - var err error - yyVAL.ruleNode, err = NewVectorSelector(yyS[yypt-2].labelMatchers, yyS[yypt-0].str) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - } - case 32: - //line parser.y:172 - { - var err error - m, err := metric.NewLabelMatcher(metric.Equal, clientmodel.MetricNameLabel, clientmodel.LabelValue(yyS[yypt-2].str)) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - yyS[yypt-1].labelMatchers = append(yyS[yypt-1].labelMatchers, m) - yyVAL.ruleNode, err = NewVectorSelector(yyS[yypt-1].labelMatchers, yyS[yypt-0].str) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - } - case 33: - //line parser.y:181 - { - var err error - yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-3].str, yyS[yypt-1].ruleNodeSlice) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - } - case 34: - //line parser.y:187 - { - var err error - yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-2].str, []ast.Node{}) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - } - case 35: - //line parser.y:193 - { - var err error - yyVAL.ruleNode, err = NewMatrixSelector(yyS[yypt-4].ruleNode, yyS[yypt-2].str, yyS[yypt-0].str) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - } - case 36: - //line parser.y:199 - { - var err error - yyVAL.ruleNode, err = NewVectorAggregation(yyS[yypt-5].str, yyS[yypt-3].ruleNode, yyS[yypt-1].labelNameSlice, yyS[yypt-0].boolean) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - } - case 37: - //line parser.y:205 - { - var err error - yyVAL.ruleNode, err = NewVectorAggregation(yyS[yypt-5].str, yyS[yypt-1].ruleNode, yyS[yypt-4].labelNameSlice, yyS[yypt-3].boolean) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - } - case 38: - //line parser.y:213 - { - var err error - yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-2].str, yyS[yypt-3].ruleNode, yyS[yypt-0].ruleNode, yyS[yypt-1].vectorMatching) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - } - case 39: - //line parser.y:219 - { - var err error - yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-2].str, yyS[yypt-3].ruleNode, yyS[yypt-0].ruleNode, yyS[yypt-1].vectorMatching) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - } - case 40: - //line parser.y:225 - { - var err error - yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-2].str, yyS[yypt-3].ruleNode, yyS[yypt-0].ruleNode, yyS[yypt-1].vectorMatching) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - } - case 41: - //line parser.y:231 - { - yyVAL.ruleNode = NewScalarLiteral(yyS[yypt-0].num, "+") - } - case 42: - //line parser.y:233 - { - yyVAL.ruleNode = NewScalarLiteral(yyS[yypt-0].num, yyS[yypt-1].str) - } - case 43: - //line parser.y:237 - { - yyVAL.boolean = false - } - case 44: - //line parser.y:239 - { - yyVAL.boolean = true - } - case 45: - //line parser.y:243 - { - yyVAL.vectorMatching = nil - } - case 46: - //line parser.y:245 - { - var err error - yyVAL.vectorMatching, err = newVectorMatching("", yyS[yypt-1].labelNameSlice, nil) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - } - case 47: - //line parser.y:251 - { - var err error - yyVAL.vectorMatching, err = newVectorMatching(yyS[yypt-3].str, yyS[yypt-5].labelNameSlice, yyS[yypt-1].labelNameSlice) - if err != nil { - yylex.Error(err.Error()) - return 1 - } - } - case 48: - //line parser.y:259 - { - yyVAL.labelNameSlice = clientmodel.LabelNames{} - } - case 49: - //line parser.y:261 - { - yyVAL.labelNameSlice = yyS[yypt-1].labelNameSlice - } - case 50: - //line parser.y:265 - { - yyVAL.labelNameSlice = clientmodel.LabelNames{clientmodel.LabelName(yyS[yypt-0].str)} - } - case 51: - //line parser.y:267 - { - yyVAL.labelNameSlice = append(yyVAL.labelNameSlice, clientmodel.LabelName(yyS[yypt-0].str)) - } - case 52: - //line parser.y:271 - { - yyVAL.ruleNodeSlice = []ast.Node{yyS[yypt-0].ruleNode} - } - case 53: - //line parser.y:273 - { - yyVAL.ruleNodeSlice = append(yyVAL.ruleNodeSlice, yyS[yypt-0].ruleNode) - } - case 54: - //line parser.y:277 - { - yyVAL.ruleNode = yyS[yypt-0].ruleNode - } - case 55: - //line parser.y:279 - { - yyVAL.ruleNode = ast.NewStringLiteral(yyS[yypt-0].str) - } - } - goto yystack /* stack new state and value */ -} diff --git a/rules/recording.go b/rules/recording.go index e693566891..2402fa84f5 100644 --- a/rules/recording.go +++ b/rules/recording.go @@ -20,30 +20,32 @@ import ( clientmodel "github.com/prometheus/client_golang/model" - "github.com/prometheus/prometheus/rules/ast" - "github.com/prometheus/prometheus/stats" - "github.com/prometheus/prometheus/storage/local" + "github.com/prometheus/prometheus/promql" + "github.com/prometheus/prometheus/utility" ) // A RecordingRule records its vector expression into new timeseries. type RecordingRule struct { - name string - vector ast.VectorNode - labels clientmodel.LabelSet - permanent bool + name string + vector promql.Expr + labels clientmodel.LabelSet } // Name returns the rule name. func (rule RecordingRule) Name() string { return rule.name } // EvalRaw returns the raw value of the rule expression. -func (rule RecordingRule) EvalRaw(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) { - return ast.EvalVectorInstant(rule.vector, timestamp, storage, stats.NewTimerGroup()) +func (rule RecordingRule) EvalRaw(timestamp clientmodel.Timestamp, engine *promql.Engine) (promql.Vector, error) { + query, err := engine.NewInstantQuery(rule.vector.String(), timestamp) + if err != nil { + return nil, err + } + return query.Exec().Vector() } // Eval evaluates the rule and then overrides the metric names and labels accordingly. -func (rule RecordingRule) Eval(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) { - vector, err := rule.EvalRaw(timestamp, storage) +func (rule RecordingRule) Eval(timestamp clientmodel.Timestamp, engine *promql.Engine) (promql.Vector, error) { + vector, err := rule.EvalRaw(timestamp, engine) if err != nil { return nil, err } @@ -63,8 +65,8 @@ func (rule RecordingRule) Eval(timestamp clientmodel.Timestamp, storage local.St return vector, nil } -// ToDotGraph returns the text representation of a dot graph. -func (rule RecordingRule) ToDotGraph() string { +// DotGraph returns the text representation of a dot graph. +func (rule RecordingRule) DotGraph() string { graph := fmt.Sprintf( `digraph "Rules" { %#p[shape="box",label="%s = "]; @@ -73,7 +75,7 @@ func (rule RecordingRule) ToDotGraph() string { }`, &rule, rule.name, &rule, reflect.ValueOf(rule.vector).Pointer(), - rule.vector.NodeTreeToDotGraph(), + rule.vector.DotGraph(), ) return graph } @@ -87,9 +89,9 @@ func (rule RecordingRule) HTMLSnippet() template.HTML { ruleExpr := rule.vector.String() return template.HTML(fmt.Sprintf( `%s%s = %s`, - GraphLinkForExpression(rule.name), + utility.GraphLinkForExpression(rule.name), rule.name, rule.labels, - GraphLinkForExpression(ruleExpr), + utility.GraphLinkForExpression(ruleExpr), ruleExpr)) } diff --git a/rules/rules.go b/rules/rules.go index 61d774e994..9485bc5337 100644 --- a/rules/rules.go +++ b/rules/rules.go @@ -18,8 +18,7 @@ import ( clientmodel "github.com/prometheus/client_golang/model" - "github.com/prometheus/prometheus/rules/ast" - "github.com/prometheus/prometheus/storage/local" + "github.com/prometheus/prometheus/promql" ) // A Rule encapsulates a vector expression which is evaluated at a specified @@ -29,11 +28,11 @@ type Rule interface { Name() string // EvalRaw evaluates the rule's vector expression without triggering any // other actions, like recording or alerting. - EvalRaw(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) + EvalRaw(clientmodel.Timestamp, *promql.Engine) (promql.Vector, error) // Eval evaluates the rule, including any associated recording or alerting actions. - Eval(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) - // ToDotGraph returns a Graphviz dot graph of the rule. - ToDotGraph() string + Eval(clientmodel.Timestamp, *promql.Engine) (promql.Vector, error) + // DotGraph returns a Graphviz dot graph of the rule. + DotGraph() string // String returns a human-readable string representation of the rule. String() string // HTMLSnippet returns a human-readable string representation of the rule, diff --git a/rules/rules_test.go b/rules/rules_test.go index 5966c21ec0..113d200b99 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -15,41 +15,63 @@ package rules import ( "fmt" - "math" - "path" - "regexp" - "strconv" "strings" "testing" "time" clientmodel "github.com/prometheus/client_golang/model" - "github.com/prometheus/prometheus/rules/ast" - "github.com/prometheus/prometheus/stats" + "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/storage/local" "github.com/prometheus/prometheus/storage/metric" - "github.com/prometheus/prometheus/utility/test" ) var ( - testEvalTime = testStartTime.Add(testSampleInterval * 10) - fixturesPath = "fixtures" - - reSample = regexp.MustCompile(`^(.*)(?: \=\>|:) (\-?\d+\.?\d*(?:e-?\d*)?|[+-]Inf|NaN) \@\[(\d+)\]$`) - minNormal = math.Float64frombits(0x0010000000000000) // The smallest positive normal value of type float64. + testSampleInterval = time.Duration(5) * time.Minute + testStartTime = clientmodel.Timestamp(0) ) -const ( - epsilon = 0.000001 // Relative error allowed for sample values. -) - -func annotateWithTime(lines []string, timestamp clientmodel.Timestamp) []string { - annotatedLines := []string{} - for _, line := range lines { - annotatedLines = append(annotatedLines, fmt.Sprintf(line, timestamp)) +func getTestValueStream(startVal clientmodel.SampleValue, endVal clientmodel.SampleValue, stepVal clientmodel.SampleValue, startTime clientmodel.Timestamp) (resultValues metric.Values) { + currentTime := startTime + for currentVal := startVal; currentVal <= endVal; currentVal += stepVal { + sample := metric.SamplePair{ + Value: currentVal, + Timestamp: currentTime, + } + resultValues = append(resultValues, sample) + currentTime = currentTime.Add(testSampleInterval) } - return annotatedLines + return resultValues +} + +func getTestVectorFromTestMatrix(matrix promql.Matrix) promql.Vector { + vector := promql.Vector{} + for _, sampleStream := range matrix { + lastSample := sampleStream.Values[len(sampleStream.Values)-1] + vector = append(vector, &promql.Sample{ + Metric: sampleStream.Metric, + Value: lastSample.Value, + Timestamp: lastSample.Timestamp, + }) + } + return vector +} + +func storeMatrix(storage local.Storage, matrix promql.Matrix) { + pendingSamples := clientmodel.Samples{} + for _, sampleStream := range matrix { + for _, sample := range sampleStream.Values { + pendingSamples = append(pendingSamples, &clientmodel.Sample{ + Metric: sampleStream.Metric.Metric, + Value: sample.Value, + Timestamp: sample.Timestamp, + }) + } + } + for _, s := range pendingSamples { + storage.Append(s) + } + storage.WaitForIndexing() } func vectorComparisonString(expected []string, actual []string) string { @@ -63,1604 +85,59 @@ func vectorComparisonString(expected []string, actual []string) string { separator) } -// samplesAlmostEqual returns true if the two sample lines only differ by a -// small relative error in their sample value. -func samplesAlmostEqual(a, b string) bool { - if a == b { - // Fast path if strings are equal. - return true +func annotateWithTime(lines []string, timestamp clientmodel.Timestamp) []string { + annotatedLines := []string{} + for _, line := range lines { + annotatedLines = append(annotatedLines, fmt.Sprintf(line, timestamp)) } - aMatches := reSample.FindStringSubmatch(a) - if aMatches == nil { - panic(fmt.Errorf("sample %q did not match regular expression", a)) - } - bMatches := reSample.FindStringSubmatch(b) - if bMatches == nil { - panic(fmt.Errorf("sample %q did not match regular expression", b)) - } - if aMatches[1] != bMatches[1] { - return false // Labels don't match. - } - if aMatches[3] != bMatches[3] { - return false // Timestamps don't match. - } - // If we are here, we have the diff in the floats. - // We have to check if they are almost equal. - aVal, err := strconv.ParseFloat(aMatches[2], 64) - if err != nil { - panic(err) - } - bVal, err := strconv.ParseFloat(bMatches[2], 64) - if err != nil { - panic(err) - } - - // Cf. http://floating-point-gui.de/errors/comparison/ - if aVal == bVal { - return true - } - - diff := math.Abs(aVal - bVal) - - if aVal == 0 || bVal == 0 || diff < minNormal { - return diff < epsilon*minNormal - } - return diff/(math.Abs(aVal)+math.Abs(bVal)) < epsilon + return annotatedLines } -func newTestStorage(t testing.TB) (storage local.Storage, closer test.Closer) { - storage, closer = local.NewTestStorage(t, 1) - storeMatrix(storage, testMatrix) - return storage, closer -} - -func TestExpressions(t *testing.T) { - // Labels in expected output need to be alphabetically sorted. - expressionTests := []struct { - expr string - output []string - shouldFail bool - checkOrder bool - }{ - { - expr: `SUM(http_requests)`, - output: []string{`{} => 3600 @[%v]`}, - }, { - expr: `SUM(http_requests{instance="0"}) BY(job)`, - output: []string{ - `{job="api-server"} => 400 @[%v]`, - `{job="app-server"} => 1200 @[%v]`, - }, - }, { - expr: `SUM(http_requests{instance="0"}) BY(job) KEEPING_EXTRA`, - output: []string{ - `{instance="0", job="api-server"} => 400 @[%v]`, - `{instance="0", job="app-server"} => 1200 @[%v]`, - }, - }, { - expr: `SUM(http_requests) BY (job)`, - output: []string{ - `{job="api-server"} => 1000 @[%v]`, - `{job="app-server"} => 2600 @[%v]`, - }, - }, { - // Non-existent labels mentioned in BY-clauses shouldn't propagate to output. - expr: `SUM(http_requests) BY (job, nonexistent)`, - output: []string{ - `{job="api-server"} => 1000 @[%v]`, - `{job="app-server"} => 2600 @[%v]`, - }, - }, { - expr: ` - // Test comment. - SUM(http_requests) BY /* comments shouldn't - have any effect */ (job) // another comment`, - output: []string{ - `{job="api-server"} => 1000 @[%v]`, - `{job="app-server"} => 2600 @[%v]`, - }, - }, { - expr: `COUNT(http_requests) BY (job)`, - output: []string{ - `{job="api-server"} => 4 @[%v]`, - `{job="app-server"} => 4 @[%v]`, - }, - }, { - expr: `SUM(http_requests) BY (job, group)`, - output: []string{ - `{group="canary", job="api-server"} => 700 @[%v]`, - `{group="canary", job="app-server"} => 1500 @[%v]`, - `{group="production", job="api-server"} => 300 @[%v]`, - `{group="production", job="app-server"} => 1100 @[%v]`, - }, - }, { - expr: `AVG(http_requests) BY (job)`, - output: []string{ - `{job="api-server"} => 250 @[%v]`, - `{job="app-server"} => 650 @[%v]`, - }, - }, { - expr: `MIN(http_requests) BY (job)`, - output: []string{ - `{job="api-server"} => 100 @[%v]`, - `{job="app-server"} => 500 @[%v]`, - }, - }, { - expr: `MAX(http_requests) BY (job)`, - output: []string{ - `{job="api-server"} => 400 @[%v]`, - `{job="app-server"} => 800 @[%v]`, - }, - }, { - expr: `SUM(http_requests) BY (job) - COUNT(http_requests) BY (job)`, - output: []string{ - `{job="api-server"} => 996 @[%v]`, - `{job="app-server"} => 2596 @[%v]`, - }, - }, { - expr: `2 - SUM(http_requests) BY (job)`, - output: []string{ - `{job="api-server"} => -998 @[%v]`, - `{job="app-server"} => -2598 @[%v]`, - }, - }, { - expr: `1000 / SUM(http_requests) BY (job)`, - output: []string{ - `{job="api-server"} => 1 @[%v]`, - `{job="app-server"} => 0.38461538461538464 @[%v]`, - }, - }, { - expr: `SUM(http_requests) BY (job) - 2`, - output: []string{ - `{job="api-server"} => 998 @[%v]`, - `{job="app-server"} => 2598 @[%v]`, - }, - }, { - expr: `SUM(http_requests) BY (job) % 3`, - output: []string{ - `{job="api-server"} => 1 @[%v]`, - `{job="app-server"} => 2 @[%v]`, - }, - }, { - expr: `SUM(http_requests) BY (job) / 0`, - output: []string{ - `{job="api-server"} => +Inf @[%v]`, - `{job="app-server"} => +Inf @[%v]`, - }, - }, { - expr: `SUM(http_requests) BY (job) > 1000`, - output: []string{ - `{job="app-server"} => 2600 @[%v]`, - }, - }, { - expr: `1000 < SUM(http_requests) BY (job)`, - output: []string{ - `{job="app-server"} => 1000 @[%v]`, - }, - }, { - expr: `SUM(http_requests) BY (job) <= 1000`, - output: []string{ - `{job="api-server"} => 1000 @[%v]`, - }, - }, { - expr: `SUM(http_requests) BY (job) != 1000`, - output: []string{ - `{job="app-server"} => 2600 @[%v]`, - }, - }, { - expr: `SUM(http_requests) BY (job) == 1000`, - output: []string{ - `{job="api-server"} => 1000 @[%v]`, - }, - }, { - expr: `SUM(http_requests) BY (job) + SUM(http_requests) BY (job)`, - output: []string{ - `{job="api-server"} => 2000 @[%v]`, - `{job="app-server"} => 5200 @[%v]`, - }, - }, { - expr: `http_requests{job="api-server", group="canary"}`, - output: []string{ - `http_requests{group="canary", instance="0", job="api-server"} => 300 @[%v]`, - `http_requests{group="canary", instance="1", job="api-server"} => 400 @[%v]`, - }, - }, { - expr: `http_requests{job="api-server", group="canary"} + rate(http_requests{job="api-server"}[5m]) * 5 * 60`, - output: []string{ - `{group="canary", instance="0", job="api-server"} => 330 @[%v]`, - `{group="canary", instance="1", job="api-server"} => 440 @[%v]`, - }, - }, { - expr: `rate(http_requests[25m]) * 25 * 60`, - output: []string{ - `{group="canary", instance="0", job="api-server"} => 150 @[%v]`, - `{group="canary", instance="0", job="app-server"} => 350 @[%v]`, - `{group="canary", instance="1", job="api-server"} => 200 @[%v]`, - `{group="canary", instance="1", job="app-server"} => 400 @[%v]`, - `{group="production", instance="0", job="api-server"} => 50 @[%v]`, - `{group="production", instance="0", job="app-server"} => 249.99999999999997 @[%v]`, - `{group="production", instance="1", job="api-server"} => 100 @[%v]`, - `{group="production", instance="1", job="app-server"} => 300 @[%v]`, - }, - }, { - expr: `delta(http_requests[25m], 1)`, - output: []string{ - `{group="canary", instance="0", job="api-server"} => 150 @[%v]`, - `{group="canary", instance="0", job="app-server"} => 350 @[%v]`, - `{group="canary", instance="1", job="api-server"} => 200 @[%v]`, - `{group="canary", instance="1", job="app-server"} => 400 @[%v]`, - `{group="production", instance="0", job="api-server"} => 50 @[%v]`, - `{group="production", instance="0", job="app-server"} => 250 @[%v]`, - `{group="production", instance="1", job="api-server"} => 100 @[%v]`, - `{group="production", instance="1", job="app-server"} => 300 @[%v]`, - }, - }, { - expr: `sort(http_requests)`, - output: []string{ - `http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, - `http_requests{group="canary", instance="0", job="api-server"} => 300 @[%v]`, - `http_requests{group="canary", instance="1", job="api-server"} => 400 @[%v]`, - `http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`, - `http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`, - `http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`, - `http_requests{group="canary", instance="1", job="app-server"} => 800 @[%v]`, - }, - checkOrder: true, - }, { - expr: `sort_desc(http_requests)`, - output: []string{ - `http_requests{group="canary", instance="1", job="app-server"} => 800 @[%v]`, - `http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`, - `http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`, - `http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`, - `http_requests{group="canary", instance="1", job="api-server"} => 400 @[%v]`, - `http_requests{group="canary", instance="0", job="api-server"} => 300 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, - `http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, - }, - checkOrder: true, - }, { - expr: `topk(3, http_requests)`, - output: []string{ - `http_requests{group="canary", instance="1", job="app-server"} => 800 @[%v]`, - `http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`, - `http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`, - }, - checkOrder: true, - }, { - expr: `topk(5, http_requests{group="canary",job="app-server"})`, - output: []string{ - `http_requests{group="canary", instance="1", job="app-server"} => 800 @[%v]`, - `http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`, - }, - checkOrder: true, - }, { - expr: `bottomk(3, http_requests)`, - output: []string{ - `http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, - `http_requests{group="canary", instance="0", job="api-server"} => 300 @[%v]`, - }, - checkOrder: true, - }, { - expr: `bottomk(5, http_requests{group="canary",job="app-server"})`, - output: []string{ - `http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`, - `http_requests{group="canary", instance="1", job="app-server"} => 800 @[%v]`, - }, - checkOrder: true, - }, { - // Single-letter label names and values. - expr: `x{y="testvalue"}`, - output: []string{ - `x{y="testvalue"} => 100 @[%v]`, - }, - }, { - // Lower-cased aggregation operators should work too. - expr: `sum(http_requests) by (job) + min(http_requests) by (job) + max(http_requests) by (job) + avg(http_requests) by (job)`, - output: []string{ - `{job="app-server"} => 4550 @[%v]`, - `{job="api-server"} => 1750 @[%v]`, - }, - }, { - // Deltas should be adjusted for target interval vs. samples under target interval. - expr: `delta(http_requests{group="canary", instance="1", job="app-server"}[18m])`, - output: []string{`{group="canary", instance="1", job="app-server"} => 288 @[%v]`}, - }, { - // Deltas should perform the same operation when 2nd argument is 0. - expr: `delta(http_requests{group="canary", instance="1", job="app-server"}[18m], 0)`, - output: []string{`{group="canary", instance="1", job="app-server"} => 288 @[%v]`}, - }, { - // Rates should calculate per-second rates. - expr: `rate(http_requests{group="canary", instance="1", job="app-server"}[60m])`, - output: []string{`{group="canary", instance="1", job="app-server"} => 0.26666666666666666 @[%v]`}, - }, { - // Deriv should return the same as rate in simple cases. - expr: `deriv(http_requests{group="canary", instance="1", job="app-server"}[60m])`, - output: []string{`{group="canary", instance="1", job="app-server"} => 0.26666666666666666 @[%v]`}, - }, { - // Counter resets at in the middle of range are handled correctly by rate(). - expr: `rate(testcounter_reset_middle[60m])`, - output: []string{`{} => 0.03 @[%v]`}, - }, { - // Counter resets at end of range are ignored by rate(). - expr: `rate(testcounter_reset_end[5m])`, - output: []string{`{} => 0 @[%v]`}, - }, { - // Deriv should return correct result. - expr: `deriv(testcounter_reset_middle[100m])`, - output: []string{`{} => 0.010606060606060607 @[%v]`}, - }, { - // count_scalar for a non-empty vector should return scalar element count. - expr: `count_scalar(http_requests)`, - output: []string{`scalar: 8 @[%v]`}, - }, { - // count_scalar for an empty vector should return scalar 0. - expr: `count_scalar(nonexistent)`, - output: []string{`scalar: 0 @[%v]`}, - }, { - // Empty expressions shouldn't parse. - expr: ``, - shouldFail: true, - }, { - // Interval durations can't be in quotes. - expr: `http_requests["1m"]`, - shouldFail: true, - }, { - // Binop arguments need to be scalar or vector. - expr: `http_requests - http_requests[1m]`, - shouldFail: true, - }, { - expr: `http_requests{group!="canary"}`, - output: []string{ - `http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`, - `http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, - `http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, - }, - }, { - expr: `http_requests{job=~"server",group!="canary"}`, - output: []string{ - `http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`, - `http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, - `http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, - }, - }, { - expr: `http_requests{job!~"api",group!="canary"}`, - output: []string{ - `http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`, - `http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`, - }, - }, { - expr: `count_scalar(http_requests{job=~"^server$"})`, - output: []string{`scalar: 0 @[%v]`}, - }, { - expr: `http_requests{group="production",job=~"^api"}`, - output: []string{ - `http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, - }, - }, - { - expr: `abs(-1 * http_requests{group="production",job="api-server"})`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 100 @[%v]`, - `{group="production", instance="1", job="api-server"} => 200 @[%v]`, - }, - }, - { - expr: `floor(0.004 * http_requests{group="production",job="api-server"})`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 0 @[%v]`, - `{group="production", instance="1", job="api-server"} => 0 @[%v]`, - }, - }, - { - expr: `ceil(0.004 * http_requests{group="production",job="api-server"})`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 1 @[%v]`, - `{group="production", instance="1", job="api-server"} => 1 @[%v]`, - }, - }, - { - expr: `round(0.004 * http_requests{group="production",job="api-server"})`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 0 @[%v]`, - `{group="production", instance="1", job="api-server"} => 1 @[%v]`, - }, - }, - { // Round should correctly handle negative numbers. - expr: `round(-1 * (0.004 * http_requests{group="production",job="api-server"}))`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 0 @[%v]`, - `{group="production", instance="1", job="api-server"} => -1 @[%v]`, - }, - }, - { // Round should round half up. - expr: `round(0.005 * http_requests{group="production",job="api-server"})`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 1 @[%v]`, - `{group="production", instance="1", job="api-server"} => 1 @[%v]`, - }, - }, - { - expr: `round(-1 * (0.005 * http_requests{group="production",job="api-server"}))`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 0 @[%v]`, - `{group="production", instance="1", job="api-server"} => -1 @[%v]`, - }, - }, - { - expr: `round(1 + 0.005 * http_requests{group="production",job="api-server"})`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 2 @[%v]`, - `{group="production", instance="1", job="api-server"} => 2 @[%v]`, - }, - }, - { - expr: `round(-1 * (1 + 0.005 * http_requests{group="production",job="api-server"}))`, - output: []string{ - `{group="production", instance="0", job="api-server"} => -1 @[%v]`, - `{group="production", instance="1", job="api-server"} => -2 @[%v]`, - }, - }, - { // Round should accept the number to round nearest to. - expr: `round(0.0005 * http_requests{group="production",job="api-server"}, 0.1)`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 0.1 @[%v]`, - `{group="production", instance="1", job="api-server"} => 0.1 @[%v]`, - }, - }, - { - expr: `round(2.1 + 0.0005 * http_requests{group="production",job="api-server"}, 0.1)`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 2.2 @[%v]`, - `{group="production", instance="1", job="api-server"} => 2.2 @[%v]`, - }, - }, - { - expr: `round(5.2 + 0.0005 * http_requests{group="production",job="api-server"}, 0.1)`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 5.3 @[%v]`, - `{group="production", instance="1", job="api-server"} => 5.3 @[%v]`, - }, - }, - { // Round should work correctly with negative numbers and multiple decimal places. - expr: `round(-1 * (5.2 + 0.0005 * http_requests{group="production",job="api-server"}), 0.1)`, - output: []string{ - `{group="production", instance="0", job="api-server"} => -5.2 @[%v]`, - `{group="production", instance="1", job="api-server"} => -5.3 @[%v]`, - }, - }, - { // Round should work correctly with big toNearests. - expr: `round(0.025 * http_requests{group="production",job="api-server"}, 5)`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 5 @[%v]`, - `{group="production", instance="1", job="api-server"} => 5 @[%v]`, - }, - }, - { - expr: `round(0.045 * http_requests{group="production",job="api-server"}, 5)`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 5 @[%v]`, - `{group="production", instance="1", job="api-server"} => 10 @[%v]`, - }, - }, - { - expr: `avg_over_time(http_requests{group="production",job="api-server"}[1h])`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 50 @[%v]`, - `{group="production", instance="1", job="api-server"} => 100 @[%v]`, - }, - }, - { - expr: `count_over_time(http_requests{group="production",job="api-server"}[1h])`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 11 @[%v]`, - `{group="production", instance="1", job="api-server"} => 11 @[%v]`, - }, - }, - { - expr: `max_over_time(http_requests{group="production",job="api-server"}[1h])`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 100 @[%v]`, - `{group="production", instance="1", job="api-server"} => 200 @[%v]`, - }, - }, - { - expr: `min_over_time(http_requests{group="production",job="api-server"}[1h])`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 0 @[%v]`, - `{group="production", instance="1", job="api-server"} => 0 @[%v]`, - }, - }, - { - expr: `sum_over_time(http_requests{group="production",job="api-server"}[1h])`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 550 @[%v]`, - `{group="production", instance="1", job="api-server"} => 1100 @[%v]`, - }, - }, - { - expr: `time()`, - output: []string{`scalar: 3000 @[%v]`}, - }, - { - expr: `drop_common_labels(http_requests{group="production",job="api-server"})`, - output: []string{ - `http_requests{instance="0"} => 100 @[%v]`, - `http_requests{instance="1"} => 200 @[%v]`, - }, - }, - { - expr: `{` + string(clientmodel.MetricNameLabel) + `=~".*"}`, - output: []string{ - `http_requests{group="canary", instance="0", job="api-server"} => 300 @[%v]`, - `http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`, - `http_requests{group="canary", instance="1", job="api-server"} => 400 @[%v]`, - `http_requests{group="canary", instance="1", job="app-server"} => 800 @[%v]`, - `http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, - `http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, - `http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`, - `testcounter_reset_end => 0 @[%v]`, - `testcounter_reset_middle => 50 @[%v]`, - `x{y="testvalue"} => 100 @[%v]`, - `label_grouping_test{a="a", b="abb"} => 200 @[%v]`, - `label_grouping_test{a="aa", b="bb"} => 100 @[%v]`, - `testhistogram_bucket{le="0.1", start="positive"} => 50 @[%v]`, - `testhistogram_bucket{le=".2", start="positive"} => 70 @[%v]`, - `testhistogram_bucket{le="1e0", start="positive"} => 110 @[%v]`, - `testhistogram_bucket{le="+Inf", start="positive"} => 120 @[%v]`, - `testhistogram_bucket{le="-.2", start="negative"} => 10 @[%v]`, - `testhistogram_bucket{le="-0.1", start="negative"} => 20 @[%v]`, - `testhistogram_bucket{le="0.3", start="negative"} => 20 @[%v]`, - `testhistogram_bucket{le="+Inf", start="negative"} => 30 @[%v]`, - `request_duration_seconds_bucket{instance="ins1", job="job1", le="0.1"} => 10 @[%v]`, - `request_duration_seconds_bucket{instance="ins1", job="job1", le="0.2"} => 30 @[%v]`, - `request_duration_seconds_bucket{instance="ins1", job="job1", le="+Inf"} => 40 @[%v]`, - `request_duration_seconds_bucket{instance="ins2", job="job1", le="0.1"} => 20 @[%v]`, - `request_duration_seconds_bucket{instance="ins2", job="job1", le="0.2"} => 50 @[%v]`, - `request_duration_seconds_bucket{instance="ins2", job="job1", le="+Inf"} => 60 @[%v]`, - `request_duration_seconds_bucket{instance="ins1", job="job2", le="0.1"} => 30 @[%v]`, - `request_duration_seconds_bucket{instance="ins1", job="job2", le="0.2"} => 40 @[%v]`, - `request_duration_seconds_bucket{instance="ins1", job="job2", le="+Inf"} => 60 @[%v]`, - `request_duration_seconds_bucket{instance="ins2", job="job2", le="0.1"} => 40 @[%v]`, - `request_duration_seconds_bucket{instance="ins2", job="job2", le="0.2"} => 70 @[%v]`, - `request_duration_seconds_bucket{instance="ins2", job="job2", le="+Inf"} => 90 @[%v]`, - `vector_matching_a{l="x"} => 10 @[%v]`, - `vector_matching_a{l="y"} => 20 @[%v]`, - `vector_matching_b{l="x"} => 40 @[%v]`, - `cpu_count{instance="1", type="smp"} => 200 @[%v]`, - `cpu_count{instance="0", type="smp"} => 100 @[%v]`, - `cpu_count{instance="0", type="numa"} => 300 @[%v]`, - }, - }, - { - expr: `{job=~"server", job!~"api"}`, - output: []string{ - `http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`, - `http_requests{group="canary", instance="1", job="app-server"} => 800 @[%v]`, - `http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`, - `http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`, - }, - }, - { - // Test alternative "by"-clause order. - expr: `sum by (group) (http_requests{job="api-server"})`, - output: []string{ - `{group="canary"} => 700 @[%v]`, - `{group="production"} => 300 @[%v]`, - }, - }, - { - // Test alternative "by"-clause order with "keeping_extra". - expr: `sum by (group) keeping_extra (http_requests{job="api-server"})`, - output: []string{ - `{group="canary", job="api-server"} => 700 @[%v]`, - `{group="production", job="api-server"} => 300 @[%v]`, - }, - }, - { - // Test both alternative "by"-clause orders in one expression. - // Public health warning: stick to one form within an expression (or even - // in an organization), or risk serious user confusion. - expr: `sum(sum by (group) keeping_extra (http_requests{job="api-server"})) by (job)`, - output: []string{ - `{job="api-server"} => 1000 @[%v]`, - }, - }, - { - expr: `http_requests{group="canary"} and http_requests{instance="0"}`, - output: []string{ - `http_requests{group="canary", instance="0", job="api-server"} => 300 @[%v]`, - `http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`, - }, - }, - { - expr: `(http_requests{group="canary"} + 1) and http_requests{instance="0"}`, - output: []string{ - `{group="canary", instance="0", job="api-server"} => 301 @[%v]`, - `{group="canary", instance="0", job="app-server"} => 701 @[%v]`, - }, - }, - { - expr: `(http_requests{group="canary"} + 1) and on(instance, job) http_requests{instance="0", group="production"}`, - output: []string{ - `{group="canary", instance="0", job="api-server"} => 301 @[%v]`, - `{group="canary", instance="0", job="app-server"} => 701 @[%v]`, - }, - }, - { - expr: `(http_requests{group="canary"} + 1) and on(instance) http_requests{instance="0", group="production"}`, - output: []string{ - `{group="canary", instance="0", job="api-server"} => 301 @[%v]`, - `{group="canary", instance="0", job="app-server"} => 701 @[%v]`, - }, - }, - { - expr: `http_requests{group="canary"} or http_requests{group="production"}`, - output: []string{ - `http_requests{group="canary", instance="0", job="api-server"} => 300 @[%v]`, - `http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`, - `http_requests{group="canary", instance="1", job="api-server"} => 400 @[%v]`, - `http_requests{group="canary", instance="1", job="app-server"} => 800 @[%v]`, - `http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, - `http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, - `http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`, - }, - }, - { - // On overlap the rhs samples must be dropped. - expr: `(http_requests{group="canary"} + 1) or http_requests{instance="1"}`, - output: []string{ - `{group="canary", instance="0", job="api-server"} => 301 @[%v]`, - `{group="canary", instance="0", job="app-server"} => 701 @[%v]`, - `{group="canary", instance="1", job="api-server"} => 401 @[%v]`, - `{group="canary", instance="1", job="app-server"} => 801 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, - `http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`, - }, - }, - { - // Matching only on instance excludes everything that has instance=0/1 but includes - // entries without the instance label. - expr: `(http_requests{group="canary"} + 1) or on(instance) (http_requests or cpu_count or vector_matching_a)`, - output: []string{ - `{group="canary", instance="0", job="api-server"} => 301 @[%v]`, - `{group="canary", instance="0", job="app-server"} => 701 @[%v]`, - `{group="canary", instance="1", job="api-server"} => 401 @[%v]`, - `{group="canary", instance="1", job="app-server"} => 801 @[%v]`, - `vector_matching_a{l="x"} => 10 @[%v]`, - `vector_matching_a{l="y"} => 20 @[%v]`, - }, - }, - { - expr: `http_requests{group="canary"} / on(instance,job) http_requests{group="production"}`, - output: []string{ - `{instance="0", job="api-server"} => 3 @[%v]`, - `{instance="0", job="app-server"} => 1.4 @[%v]`, - `{instance="1", job="api-server"} => 2 @[%v]`, - `{instance="1", job="app-server"} => 1.3333333333333333 @[%v]`, - }, - }, - { - // Include labels must guarantee uniquely identifiable time series. - expr: `http_requests{group="production"} / on(instance) group_left(group) cpu_count{type="smp"}`, - output: []string{}, // Empty result returned on error (see TODOs). - }, - { - // Many-to-many matching is not allowed. - expr: `http_requests{group="production"} / on(instance) group_left(job,type) cpu_count`, - output: []string{}, // Empty result returned on error (see TODOs). - }, - { - // Many-to-one matching must be explicit. - expr: `http_requests{group="production"} / on(instance) cpu_count{type="smp"}`, - output: []string{}, // Empty result returned on error (see TODOs). - }, - { - expr: `http_requests{group="production"} / on(instance) group_left(job) cpu_count{type="smp"}`, - output: []string{ - `{instance="1", job="api-server"} => 1 @[%v]`, - `{instance="0", job="app-server"} => 5 @[%v]`, - `{instance="1", job="app-server"} => 3 @[%v]`, - `{instance="0", job="api-server"} => 1 @[%v]`, - }, - }, - { - // Ensure sidedness of grouping preserves operand sides. - expr: `cpu_count{type="smp"} / on(instance) group_right(job) http_requests{group="production"}`, - output: []string{ - `{instance="1", job="app-server"} => 0.3333333333333333 @[%v]`, - `{instance="0", job="app-server"} => 0.2 @[%v]`, - `{instance="1", job="api-server"} => 1 @[%v]`, - `{instance="0", job="api-server"} => 1 @[%v]`, - }, - }, - { - // Include labels from both sides. - expr: `http_requests{group="production"} / on(instance) group_left(job) cpu_count{type="smp"}`, - output: []string{ - `{instance="1", job="api-server"} => 1 @[%v]`, - `{instance="0", job="app-server"} => 5 @[%v]`, - `{instance="1", job="app-server"} => 3 @[%v]`, - `{instance="0", job="api-server"} => 1 @[%v]`, - }, - }, - { - expr: `http_requests{group="production"} < on(instance,job) http_requests{group="canary"}`, - output: []string{ - `{instance="1", job="app-server"} => 600 @[%v]`, - `{instance="0", job="app-server"} => 500 @[%v]`, - `{instance="1", job="api-server"} => 200 @[%v]`, - `{instance="0", job="api-server"} => 100 @[%v]`, - }, - }, - { - expr: `http_requests{group="production"} > on(instance,job) http_requests{group="canary"}`, - output: []string{}, - }, - { - expr: `http_requests{group="production"} == on(instance,job) http_requests{group="canary"}`, - output: []string{}, - }, - { - expr: `http_requests > on(instance) group_left(group,job) cpu_count{type="smp"}`, - output: []string{ - `{group="canary", instance="0", job="app-server"} => 700 @[%v]`, - `{group="canary", instance="1", job="app-server"} => 800 @[%v]`, - `{group="canary", instance="0", job="api-server"} => 300 @[%v]`, - `{group="canary", instance="1", job="api-server"} => 400 @[%v]`, - `{group="production", instance="0", job="app-server"} => 500 @[%v]`, - `{group="production", instance="1", job="app-server"} => 600 @[%v]`, - }, - }, - { - expr: `http_requests / on(instance) 3`, - shouldFail: true, - }, - { - expr: `3 / on(instance) http_requests_total`, - shouldFail: true, - }, - { - expr: `3 / on(instance) 3`, - shouldFail: true, - }, - { - // Missing label list for grouping mod. - expr: `http_requests{group="production"} / on(instance) group_left cpu_count{type="smp"}`, - shouldFail: true, - }, - { - // No group mod allowed for logical operations. - expr: `http_requests{group="production"} or on(instance) group_left(type) cpu_count{type="smp"}`, - shouldFail: true, - }, - { - // No group mod allowed for logical operations. - expr: `http_requests{group="production"} and on(instance) group_left(type) cpu_count{type="smp"}`, - shouldFail: true, - }, - { - // No duplicate use of label. - expr: `http_requests{group="production"} + on(instance) group_left(job,instance) cpu_count{type="smp"}`, - shouldFail: true, - }, - { - expr: `{l="x"} + on(__name__) {l="y"}`, - output: []string{ - `vector_matching_a => 30 @[%v]`, - }, - }, - { - expr: `absent(nonexistent)`, - output: []string{ - `{} => 1 @[%v]`, - }, - }, - { - expr: `absent(nonexistent{job="testjob", instance="testinstance", method=~".*"})`, - output: []string{ - `{instance="testinstance", job="testjob"} => 1 @[%v]`, - }, - }, - { - expr: `count_scalar(absent(http_requests))`, - output: []string{ - `scalar: 0 @[%v]`, - }, - }, - { - expr: `count_scalar(absent(sum(http_requests)))`, - output: []string{ - `scalar: 0 @[%v]`, - }, - }, - { - expr: `absent(sum(nonexistent{job="testjob", instance="testinstance"}))`, - output: []string{ - `{} => 1 @[%v]`, - }, - }, - { - expr: `http_requests{group="production",job="api-server"} offset 5m`, - output: []string{ - `http_requests{group="production", instance="0", job="api-server"} => 90 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 180 @[%v]`, - }, - }, - { - expr: `rate(http_requests{group="production",job="api-server"}[10m] offset 5m)`, - output: []string{ - `{group="production", instance="0", job="api-server"} => 0.03333333333333333 @[%v]`, - `{group="production", instance="1", job="api-server"} => 0.06666666666666667 @[%v]`, - }, - }, - { - expr: `rate(http_requests[10m]) offset 5m`, - shouldFail: true, - }, - { - expr: `sum(http_requests) offset 5m`, - shouldFail: true, - }, - // Regression test for missing separator byte in labelsToGroupingKey. - { - expr: `sum(label_grouping_test) by (a, b)`, - output: []string{ - `{a="a", b="abb"} => 200 @[%v]`, - `{a="aa", b="bb"} => 100 @[%v]`, - }, - }, - // Quantile too low. - { - expr: `histogram_quantile(-0.1, testhistogram_bucket)`, - output: []string{ - `{start="positive"} => -Inf @[%v]`, - `{start="negative"} => -Inf @[%v]`, - }, - }, - // Quantile too high. - { - expr: `histogram_quantile(1.01, testhistogram_bucket)`, - output: []string{ - `{start="positive"} => +Inf @[%v]`, - `{start="negative"} => +Inf @[%v]`, - }, - }, - // Quantile value in lowest bucket, which is positive. - { - expr: `histogram_quantile(0, testhistogram_bucket{start="positive"})`, - output: []string{ - `{start="positive"} => 0 @[%v]`, - }, - }, - // Quantile value in lowest bucket, which is negative. - { - expr: `histogram_quantile(0, testhistogram_bucket{start="negative"})`, - output: []string{ - `{start="negative"} => -0.2 @[%v]`, - }, - }, - // Quantile value in highest bucket. - { - expr: `histogram_quantile(1, testhistogram_bucket)`, - output: []string{ - `{start="positive"} => 1 @[%v]`, - `{start="negative"} => 0.3 @[%v]`, - }, - }, - // Finally some useful quantiles. - { - expr: `histogram_quantile(0.2, testhistogram_bucket)`, - output: []string{ - `{start="positive"} => 0.048 @[%v]`, - `{start="negative"} => -0.2 @[%v]`, - }, - }, - { - expr: `histogram_quantile(0.5, testhistogram_bucket)`, - output: []string{ - `{start="positive"} => 0.15 @[%v]`, - `{start="negative"} => -0.15 @[%v]`, - }, - }, - { - expr: `histogram_quantile(0.8, testhistogram_bucket)`, - output: []string{ - `{start="positive"} => 0.72 @[%v]`, - `{start="negative"} => 0.3 @[%v]`, - }, - }, - // More realistic with rates. - { - expr: `histogram_quantile(0.2, rate(testhistogram_bucket[5m]))`, - output: []string{ - `{start="positive"} => 0.048 @[%v]`, - `{start="negative"} => -0.2 @[%v]`, - }, - }, - { - expr: `histogram_quantile(0.5, rate(testhistogram_bucket[5m]))`, - output: []string{ - `{start="positive"} => 0.15 @[%v]`, - `{start="negative"} => -0.15 @[%v]`, - }, - }, - { - expr: `histogram_quantile(0.8, rate(testhistogram_bucket[5m]))`, - output: []string{ - `{start="positive"} => 0.72 @[%v]`, - `{start="negative"} => 0.3 @[%v]`, - }, - }, - // Aggregated histogram: Everything in one. - { - expr: `histogram_quantile(0.3, sum(rate(request_duration_seconds_bucket[5m])) by (le))`, - output: []string{ - `{} => 0.075 @[%v]`, - }, - }, - { - expr: `histogram_quantile(0.5, sum(rate(request_duration_seconds_bucket[5m])) by (le))`, - output: []string{ - `{} => 0.1277777777777778 @[%v]`, - }, - }, - // Aggregated histogram: Everything in one. Now with avg, which does not change anything. - { - expr: `histogram_quantile(0.3, avg(rate(request_duration_seconds_bucket[5m])) by (le))`, - output: []string{ - `{} => 0.075 @[%v]`, - }, - }, - { - expr: `histogram_quantile(0.5, avg(rate(request_duration_seconds_bucket[5m])) by (le))`, - output: []string{ - `{} => 0.12777777777777778 @[%v]`, - }, - }, - // Aggregated histogram: By job. - { - expr: `histogram_quantile(0.3, sum(rate(request_duration_seconds_bucket[5m])) by (le, instance))`, - output: []string{ - `{instance="ins1"} => 0.075 @[%v]`, - `{instance="ins2"} => 0.075 @[%v]`, - }, - }, - { - expr: `histogram_quantile(0.5, sum(rate(request_duration_seconds_bucket[5m])) by (le, instance))`, - output: []string{ - `{instance="ins1"} => 0.1333333333 @[%v]`, - `{instance="ins2"} => 0.125 @[%v]`, - }, - }, - // Aggregated histogram: By instance. - { - expr: `histogram_quantile(0.3, sum(rate(request_duration_seconds_bucket[5m])) by (le, job))`, - output: []string{ - `{job="job1"} => 0.1 @[%v]`, - `{job="job2"} => 0.0642857142857143 @[%v]`, - }, - }, - { - expr: `histogram_quantile(0.5, sum(rate(request_duration_seconds_bucket[5m])) by (le, job))`, - output: []string{ - `{job="job1"} => 0.14 @[%v]`, - `{job="job2"} => 0.1125 @[%v]`, - }, - }, - // Aggregated histogram: By job and instance. - { - expr: `histogram_quantile(0.3, sum(rate(request_duration_seconds_bucket[5m])) by (le, job, instance))`, - output: []string{ - `{instance="ins1", job="job1"} => 0.11 @[%v]`, - `{instance="ins2", job="job1"} => 0.09 @[%v]`, - `{instance="ins1", job="job2"} => 0.06 @[%v]`, - `{instance="ins2", job="job2"} => 0.0675 @[%v]`, - }, - }, - { - expr: `histogram_quantile(0.5, sum(rate(request_duration_seconds_bucket[5m])) by (le, job, instance))`, - output: []string{ - `{instance="ins1", job="job1"} => 0.15 @[%v]`, - `{instance="ins2", job="job1"} => 0.1333333333333333 @[%v]`, - `{instance="ins1", job="job2"} => 0.1 @[%v]`, - `{instance="ins2", job="job2"} => 0.1166666666666667 @[%v]`, - }, - }, - // The unaggregated histogram for comparison. Same result as the previous one. - { - expr: `histogram_quantile(0.3, rate(request_duration_seconds_bucket[5m]))`, - output: []string{ - `{instance="ins1", job="job1"} => 0.11 @[%v]`, - `{instance="ins2", job="job1"} => 0.09 @[%v]`, - `{instance="ins1", job="job2"} => 0.06 @[%v]`, - `{instance="ins2", job="job2"} => 0.0675 @[%v]`, - }, - }, - { - expr: `histogram_quantile(0.5, rate(request_duration_seconds_bucket[5m]))`, - output: []string{ - `{instance="ins1", job="job1"} => 0.15 @[%v]`, - `{instance="ins2", job="job1"} => 0.13333333333333333 @[%v]`, - `{instance="ins1", job="job2"} => 0.1 @[%v]`, - `{instance="ins2", job="job2"} => 0.11666666666666667 @[%v]`, - }, - }, - { - expr: `12.34e6`, - output: []string{`scalar: 12340000 @[%v]`}, - }, - { - expr: `12.34e+6`, - output: []string{`scalar: 12340000 @[%v]`}, - }, - { - expr: `12.34e-6`, - output: []string{`scalar: 0.00001234 @[%v]`}, - }, - { - expr: `1+1`, - output: []string{`scalar: 2 @[%v]`}, - }, - { - expr: `1-1`, - output: []string{`scalar: 0 @[%v]`}, - }, - { - expr: `1 - -1`, - output: []string{`scalar: 2 @[%v]`}, - }, - { - expr: `.2`, - output: []string{`scalar: 0.2 @[%v]`}, - }, - { - expr: `+0.2`, - output: []string{`scalar: 0.2 @[%v]`}, - }, - { - expr: `-0.2e-6`, - output: []string{`scalar: -0.0000002 @[%v]`}, - }, - { - expr: `+Inf`, - output: []string{`scalar: +Inf @[%v]`}, - }, - { - expr: `inF`, - output: []string{`scalar: +Inf @[%v]`}, - }, - { - expr: `-inf`, - output: []string{`scalar: -Inf @[%v]`}, - }, - { - expr: `NaN`, - output: []string{`scalar: NaN @[%v]`}, - }, - { - expr: `nan`, - output: []string{`scalar: NaN @[%v]`}, - }, - { - expr: `2.`, - output: []string{`scalar: 2 @[%v]`}, - }, - { - expr: `999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999`, - output: []string{`scalar: +Inf @[%v]`}, - }, - { - expr: `1 / 0`, - output: []string{`scalar: +Inf @[%v]`}, - }, - { - expr: `-1 / 0`, - output: []string{`scalar: -Inf @[%v]`}, - }, - { - expr: `0 / 0`, - output: []string{`scalar: NaN @[%v]`}, - }, - { - expr: `1 % 0`, - output: []string{`scalar: NaN @[%v]`}, - }, - { - expr: `http_requests{group="canary", instance="0", job="api-server"} / 0`, - output: []string{ - `{group="canary", instance="0", job="api-server"} => +Inf @[%v]`, - }, - }, - { - expr: `-1 * http_requests{group="canary", instance="0", job="api-server"} / 0`, - output: []string{ - `{group="canary", instance="0", job="api-server"} => -Inf @[%v]`, - }, - }, - { - expr: `0 * http_requests{group="canary", instance="0", job="api-server"} / 0`, - output: []string{ - `{group="canary", instance="0", job="api-server"} => NaN @[%v]`, - }, - }, - { - expr: `0 * http_requests{group="canary", instance="0", job="api-server"} % 0`, - output: []string{ - `{group="canary", instance="0", job="api-server"} => NaN @[%v]`, - }, - }, - { - expr: `exp(vector_matching_a)`, - output: []string{ - `{l="x"} => 22026.465794806718 @[%v]`, - `{l="y"} => 485165195.4097903 @[%v]`, - }, - }, - { - expr: `exp(vector_matching_a - 10)`, - output: []string{ - `{l="y"} => 22026.465794806718 @[%v]`, - `{l="x"} => 1 @[%v]`, - }, - }, - { - expr: `exp(vector_matching_a - 20)`, - output: []string{ - `{l="x"} => 4.5399929762484854e-05 @[%v]`, - `{l="y"} => 1 @[%v]`, - }, - }, - { - expr: `ln(vector_matching_a)`, - output: []string{ - `{l="x"} => 2.302585092994046 @[%v]`, - `{l="y"} => 2.995732273553991 @[%v]`, - }, - }, - { - expr: `ln(vector_matching_a - 10)`, - output: []string{ - `{l="y"} => 2.302585092994046 @[%v]`, - `{l="x"} => -Inf @[%v]`, - }, - }, - { - expr: `ln(vector_matching_a - 20)`, - output: []string{ - `{l="y"} => -Inf @[%v]`, - `{l="x"} => NaN @[%v]`, - }, - }, - { - expr: `exp(ln(vector_matching_a))`, - output: []string{ - `{l="y"} => 20 @[%v]`, - `{l="x"} => 10 @[%v]`, - }, - }, - { - expr: `log2(vector_matching_a)`, - output: []string{ - `{l="x"} => 3.3219280948873626 @[%v]`, - `{l="y"} => 4.321928094887363 @[%v]`, - }, - }, - { - expr: `log2(vector_matching_a - 10)`, - output: []string{ - `{l="y"} => 3.3219280948873626 @[%v]`, - `{l="x"} => -Inf @[%v]`, - }, - }, - { - expr: `log2(vector_matching_a - 20)`, - output: []string{ - `{l="x"} => NaN @[%v]`, - `{l="y"} => -Inf @[%v]`, - }, - }, - { - expr: `log10(vector_matching_a)`, - output: []string{ - `{l="x"} => 1 @[%v]`, - `{l="y"} => 1.301029995663981 @[%v]`, - }, - }, - { - expr: `log10(vector_matching_a - 10)`, - output: []string{ - `{l="y"} => 1 @[%v]`, - `{l="x"} => -Inf @[%v]`, - }, - }, - { - expr: `log10(vector_matching_a - 20)`, - output: []string{ - `{l="x"} => NaN @[%v]`, - `{l="y"} => -Inf @[%v]`, - }, - }, - } - - storage, closer := newTestStorage(t) - defer closer.Close() - - for i, exprTest := range expressionTests { - expectedLines := annotateWithTime(exprTest.output, testEvalTime) - - testExpr, err := LoadExprFromString(exprTest.expr) - - if err != nil { - if exprTest.shouldFail { - continue - } - t.Errorf("%d. Error during parsing: %v", i, err) - t.Errorf("%d. Expression: %v", i, exprTest.expr) - } else { - if exprTest.shouldFail { - t.Errorf("%d. Test should fail, but didn't", i) - } - failed := false - - resultStr := ast.EvalToString(testExpr, testEvalTime, ast.Text, storage, stats.NewTimerGroup()) - resultLines := strings.Split(resultStr, "\n") - - if len(exprTest.output) == 0 && strings.Trim(resultStr, "\n") == "" { - // expected and received empty vector, everything is fine - continue - } else if len(exprTest.output) != len(resultLines) { - t.Errorf("%d. Number of samples in expected and actual output don't match", i) - failed = true - } - - if exprTest.checkOrder { - for j, expectedSample := range expectedLines { - if resultLines[j] != expectedSample { - t.Errorf("%d.%d. Expected sample '%v', got '%v'", i, j, resultLines[j], expectedSample) - failed = true - } - } - } else { - for j, expectedSample := range expectedLines { - found := false - for _, actualSample := range resultLines { - if samplesAlmostEqual(actualSample, expectedSample) { - found = true - } - } - if !found { - t.Errorf("%d.%d. Couldn't find expected sample in output: '%v'", i, j, expectedSample) - failed = true - } - } - } - - if failed { - t.Errorf("%d. Expression: %v\n%v", i, exprTest.expr, vectorComparisonString(expectedLines, resultLines)) - } - } - } -} - -func TestRangedEvaluationRegressions(t *testing.T) { - scenarios := []struct { - in ast.Matrix - out ast.Matrix - expr string - }{ - { - // Testing COWMetric behavior in drop_common_labels. - in: ast.Matrix{ - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testmetric", - "testlabel": "1", - }, - }, - Values: metric.Values{ - { - Timestamp: testStartTime, - Value: 1, - }, - { - Timestamp: testStartTime.Add(time.Hour), - Value: 1, - }, - }, - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testmetric", - "testlabel": "2", - }, - }, - Values: metric.Values{ - { - Timestamp: testStartTime.Add(time.Hour), - Value: 2, - }, - }, - }, - }, - out: ast.Matrix{ - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testmetric", - }, - }, - Values: metric.Values{ - { - Timestamp: testStartTime, - Value: 1, - }, - }, - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testmetric", - "testlabel": "1", - }, - }, - Values: metric.Values{ - { - Timestamp: testStartTime.Add(time.Hour), - Value: 1, - }, - }, - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testmetric", - "testlabel": "2", - }, - }, - Values: metric.Values{ - { - Timestamp: testStartTime.Add(time.Hour), - Value: 2, - }, - }, - }, - }, - expr: "drop_common_labels(testmetric)", - }, - { - // Testing COWMetric behavior in vector aggregation. - in: ast.Matrix{ - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testmetric", - "testlabel": "1", - }, - }, - Values: metric.Values{ - { - Timestamp: testStartTime, - Value: 1, - }, - { - Timestamp: testStartTime.Add(time.Hour), - Value: 1, - }, - }, - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testmetric", - "testlabel": "2", - }, - }, - Values: metric.Values{ - { - Timestamp: testStartTime, - Value: 2, - }, - }, - }, - }, - out: ast.Matrix{ - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{}, - }, - Values: metric.Values{ - { - Timestamp: testStartTime, - Value: 3, - }, - }, - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - "testlabel": "1", - }, - }, - Values: metric.Values{ - { - Timestamp: testStartTime.Add(time.Hour), - Value: 1, - }, - }, - }, - }, - expr: "sum(testmetric) keeping_extra", - }, - { - // Testing metric fingerprint grouping behavior. - in: ast.Matrix{ - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testmetric", - "aa": "bb", - }, - }, - Values: metric.Values{ - { - Timestamp: testStartTime, - Value: 1, - }, - }, - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testmetric", - "a": "abb", - }, - }, - Values: metric.Values{ - { - Timestamp: testStartTime, - Value: 2, - }, - }, - }, - }, - out: ast.Matrix{ - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testmetric", - "aa": "bb", - }, - }, - Values: metric.Values{ - { - Timestamp: testStartTime, - Value: 1, - }, - }, - }, - { - Metric: clientmodel.COWMetric{ - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "testmetric", - "a": "abb", - }, - }, - Values: metric.Values{ - { - Timestamp: testStartTime, - Value: 2, - }, - }, - }, - }, - expr: "testmetric", - }, - } - - for i, s := range scenarios { - storage, closer := local.NewTestStorage(t, 1) - storeMatrix(storage, s.in) - - expr, err := LoadExprFromString(s.expr) - if err != nil { - t.Fatalf("%d. Error parsing expression: %v", i, err) - } - - got, err := ast.EvalVectorRange( - expr.(ast.VectorNode), - testStartTime, - testStartTime.Add(time.Hour), - time.Hour, - storage, - stats.NewTimerGroup(), - ) - if err != nil { - t.Fatalf("%d. Error evaluating expression: %v", i, err) - } - - if got.String() != s.out.String() { - t.Fatalf("%d. Expression: %s\n\ngot:\n=====\n%v\n====\n\nwant:\n=====\n%v\n=====\n", i, s.expr, got.String(), s.out.String()) - } - - closer.Close() - } -} - -var ruleTests = []struct { - inputFile string - shouldFail bool - errContains string - numRecordingRules int - numAlertingRules int -}{ +var testMatrix = promql.Matrix{ { - inputFile: "empty.rules", - numRecordingRules: 0, - numAlertingRules: 0, - }, { - inputFile: "mixed.rules", - numRecordingRules: 2, - numAlertingRules: 2, + Metric: clientmodel.COWMetric{ + Metric: clientmodel.Metric{ + clientmodel.MetricNameLabel: "http_requests", + clientmodel.JobLabel: "api-server", + "instance": "0", + "group": "canary", + }, + }, + Values: getTestValueStream(0, 300, 30, testStartTime), }, { - inputFile: "syntax_error.rules", - shouldFail: true, - errContains: "Error parsing rules at line 5", + Metric: clientmodel.COWMetric{ + Metric: clientmodel.Metric{ + clientmodel.MetricNameLabel: "http_requests", + clientmodel.JobLabel: "api-server", + "instance": "1", + "group": "canary", + }, + }, + Values: getTestValueStream(0, 400, 40, testStartTime), }, { - inputFile: "non_vector.rules", - shouldFail: true, - errContains: "does not evaluate to vector type", + Metric: clientmodel.COWMetric{ + Metric: clientmodel.Metric{ + clientmodel.MetricNameLabel: "http_requests", + clientmodel.JobLabel: "app-server", + "instance": "0", + "group": "canary", + }, + }, + Values: getTestValueStream(0, 700, 70, testStartTime), + }, + { + Metric: clientmodel.COWMetric{ + Metric: clientmodel.Metric{ + clientmodel.MetricNameLabel: "http_requests", + clientmodel.JobLabel: "app-server", + "instance": "1", + "group": "canary", + }, + }, + Values: getTestValueStream(0, 800, 80, testStartTime), }, -} - -func TestRules(t *testing.T) { - for i, ruleTest := range ruleTests { - testRules, err := LoadRulesFromFile(path.Join(fixturesPath, ruleTest.inputFile)) - - if err != nil { - if !ruleTest.shouldFail { - t.Fatalf("%d. Error parsing rules file %v: %v", i, ruleTest.inputFile, err) - } else { - if !strings.Contains(err.Error(), ruleTest.errContains) { - t.Fatalf("%d. Expected error containing '%v', got: %v", i, ruleTest.errContains, err) - } - } - } else { - numRecordingRules := 0 - numAlertingRules := 0 - - for j, rule := range testRules { - switch rule.(type) { - case *RecordingRule: - numRecordingRules++ - case *AlertingRule: - numAlertingRules++ - default: - t.Fatalf("%d.%d. Unknown rule type!", i, j) - } - } - - if numRecordingRules != ruleTest.numRecordingRules { - t.Fatalf("%d. Expected %d recording rules, got %d", i, ruleTest.numRecordingRules, numRecordingRules) - } - if numAlertingRules != ruleTest.numAlertingRules { - t.Fatalf("%d. Expected %d alerting rules, got %d", i, ruleTest.numAlertingRules, numAlertingRules) - } - - // TODO(julius): add more complex checks on the parsed rules here. - } - } } func TestAlertingRule(t *testing.T) { @@ -1688,27 +165,34 @@ func TestAlertingRule(t *testing.T) { }, } - storage, closer := newTestStorage(t) + storage, closer := local.NewTestStorage(t, 1) defer closer.Close() - alertExpr, err := LoadExprFromString(`http_requests{group="canary", job="app-server"} < 100`) + storeMatrix(storage, testMatrix) + + engine := promql.NewEngine(storage) + defer engine.Stop() + + expr, err := promql.ParseExpr("test", `http_requests{group="canary", job="app-server"} < 100`) if err != nil { t.Fatalf("Unable to parse alert expression: %s", err) } - alertName := "HttpRequestRateLow" + alertLabels := clientmodel.LabelSet{ "severity": "critical", } - rule := NewAlertingRule(alertName, alertExpr.(ast.VectorNode), time.Minute, alertLabels, "summary", "description") + rule := NewAlertingRule("HttpRequestRateLow", expr, time.Minute, alertLabels, "summary", "description") - for i, expected := range evalOutputs { + for i, expectedLines := range evalOutputs { evalTime := testStartTime.Add(testSampleInterval * time.Duration(i)) - actual, err := rule.Eval(evalTime, storage) + + res, err := rule.Eval(evalTime, engine) if err != nil { t.Fatalf("Error during alerting rule evaluation: %s", err) } - actualLines := strings.Split(actual.String(), "\n") - expectedLines := annotateWithTime(expected, evalTime) + + actualLines := strings.Split(res.String(), "\n") + expectedLines := annotateWithTime(expectedLines, evalTime) if actualLines[0] == "" { actualLines = []string{} } diff --git a/templates/templates.go b/templates/templates.go index b072584827..aca5bff758 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -27,10 +27,8 @@ import ( clientmodel "github.com/prometheus/client_golang/model" - "github.com/prometheus/prometheus/rules" - "github.com/prometheus/prometheus/rules/ast" - "github.com/prometheus/prometheus/stats" - "github.com/prometheus/prometheus/storage/local" + "github.com/prometheus/prometheus/promql" + "github.com/prometheus/prometheus/utility" ) // A version of vector that's easier to use from templates. @@ -57,18 +55,17 @@ func (q queryResultByLabelSorter) Swap(i, j int) { q.results[i], q.results[j] = q.results[j], q.results[i] } -func query(q string, timestamp clientmodel.Timestamp, storage local.Storage) (queryResult, error) { - exprNode, err := rules.LoadExprFromString(q) +func query(q string, timestamp clientmodel.Timestamp, queryEngine *promql.Engine) (queryResult, error) { + query, err := queryEngine.NewInstantQuery(q, timestamp) if err != nil { return nil, err } - queryStats := stats.NewTimerGroup() - vector, err := ast.EvalToVector(exprNode, timestamp, storage, queryStats) + vector, err := query.Exec().Vector() if err != nil { return nil, err } - // ast.Vector is hard to work with in templates, so convert to + // promql.Vector is hard to work with in templates, so convert to // base data types. var result = make(queryResult, len(vector)) for n, v := range vector { @@ -92,14 +89,14 @@ type templateExpander struct { } // NewTemplateExpander returns a template expander ready to use. -func NewTemplateExpander(text string, name string, data interface{}, timestamp clientmodel.Timestamp, storage local.Storage, pathPrefix string) *templateExpander { +func NewTemplateExpander(text string, name string, data interface{}, timestamp clientmodel.Timestamp, queryEngine *promql.Engine, pathPrefix string) *templateExpander { return &templateExpander{ text: text, name: name, data: data, funcMap: text_template.FuncMap{ "query": func(q string) (queryResult, error) { - return query(q, timestamp, storage) + return query(q, timestamp, queryEngine) }, "first": func(v queryResult) (*sample, error) { if len(v) > 0 { @@ -132,8 +129,8 @@ func NewTemplateExpander(text string, name string, data interface{}, timestamp c }, "match": regexp.MatchString, "title": strings.Title, - "graphLink": rules.GraphLinkForExpression, - "tableLink": rules.TableLinkForExpression, + "graphLink": utility.GraphLinkForExpression, + "tableLink": utility.TableLinkForExpression, "sortByLabel": func(label string, v queryResult) queryResult { sorter := queryResultByLabelSorter{v[:], label} sort.Stable(sorter) @@ -219,7 +216,7 @@ func NewTemplateExpander(text string, name string, data interface{}, timestamp c return fmt.Sprintf("%.4g%ss", v, prefix) }, "pathPrefix": func() string { - return pathPrefix; + return pathPrefix }, }, } diff --git a/templates/templates_test.go b/templates/templates_test.go index f4c769b503..b65e237d75 100644 --- a/templates/templates_test.go +++ b/templates/templates_test.go @@ -19,6 +19,7 @@ import ( clientmodel "github.com/prometheus/client_golang/model" + "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/storage/local" ) @@ -175,10 +176,12 @@ func TestTemplateExpansion(t *testing.T) { }) storage.WaitForIndexing() + engine := promql.NewEngine(storage) + for i, s := range scenarios { var result string var err error - expander := NewTemplateExpander(s.text, "test", s.input, time, storage, "/") + expander := NewTemplateExpander(s.text, "test", s.input, time, engine, "/") if s.html { result, err = expander.ExpandHTML(nil) } else { diff --git a/tools/rule_checker/main.go b/tools/rule_checker/main.go index a4ecf90a50..f53991d5f4 100644 --- a/tools/rule_checker/main.go +++ b/tools/rule_checker/main.go @@ -20,9 +20,10 @@ import ( "flag" "fmt" "io" + "io/ioutil" "os" - "github.com/prometheus/prometheus/rules" + "github.com/prometheus/prometheus/promql" ) var ( @@ -33,7 +34,12 @@ var ( // checkRules reads rules from in. Sucessfully read rules // are printed to out. func checkRules(filename string, in io.Reader, out io.Writer) error { - rules, err := rules.LoadRulesFromReader(in) + content, err := ioutil.ReadAll(in) + if err != nil { + return err + } + + rules, err := promql.ParseStmts(filename, string(content)) if err != nil { return err } diff --git a/utility/strconv.go b/utility/strconv.go index 5e2dd8d75a..0ebfa1587f 100644 --- a/utility/strconv.go +++ b/utility/strconv.go @@ -15,8 +15,10 @@ package utility import ( "fmt" + "net/url" "regexp" "strconv" + "strings" "time" ) @@ -80,3 +82,24 @@ func StringToDuration(durationStr string) (duration time.Duration, err error) { } return } + +// TableLinkForExpression creates an escaped relative link to the table view of +// the provided expression. +func TableLinkForExpression(expr string) string { + // url.QueryEscape percent-escapes everything except spaces, for which it + // uses "+". However, in the non-query part of a URI, only percent-escaped + // spaces are legal, so we need to manually replace "+" with "%20" after + // query-escaping the string. + // + // See also: + // http://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20. + urlData := url.QueryEscape(fmt.Sprintf(`[{"expr":%q,"tab":1}]`, expr)) + return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1)) +} + +// GraphLinkForExpression creates an escaped relative link to the graph view of +// the provided expression. +func GraphLinkForExpression(expr string) string { + urlData := url.QueryEscape(fmt.Sprintf(`[{"expr":%q,"tab":0}]`, expr)) + return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1)) +} diff --git a/web/alerts.go b/web/alerts.go index 864e289fe7..5744b074dc 100644 --- a/web/alerts.go +++ b/web/alerts.go @@ -19,7 +19,6 @@ import ( "sync" "github.com/prometheus/prometheus/rules" - "github.com/prometheus/prometheus/rules/manager" ) // AlertStatus bundles alerting rules and the mapping of alert states to row @@ -47,9 +46,10 @@ func (s byAlertStateSorter) Swap(i, j int) { // AlertsHandler implements http.Handler. type AlertsHandler struct { - mutex sync.Mutex - RuleManager manager.RuleManager + RuleManager rules.RuleManager PathPrefix string + + mutex sync.Mutex } func (h *AlertsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { diff --git a/web/api/api.go b/web/api/api.go index 90d1c1453d..40da8d83e6 100644 --- a/web/api/api.go +++ b/web/api/api.go @@ -20,14 +20,16 @@ import ( clientmodel "github.com/prometheus/client_golang/model" + "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/storage/local" "github.com/prometheus/prometheus/web/httputils" ) // MetricsService manages the /api HTTP endpoint. type MetricsService struct { - Now func() clientmodel.Timestamp - Storage local.Storage + Now func() clientmodel.Timestamp + Storage local.Storage + QueryEngine *promql.Engine } // RegisterHandler registers the handler for the various endpoints below /api. @@ -37,13 +39,13 @@ func (msrv *MetricsService) RegisterHandler(pathPrefix string) { Handler: http.HandlerFunc(h), } } - http.Handle(pathPrefix + "api/query", prometheus.InstrumentHandler( - pathPrefix + "api/query", handler(msrv.Query), + http.Handle(pathPrefix+"api/query", prometheus.InstrumentHandler( + pathPrefix+"api/query", handler(msrv.Query), )) - http.Handle(pathPrefix + "api/query_range", prometheus.InstrumentHandler( - pathPrefix + "api/query_range", handler(msrv.QueryRange), + http.Handle(pathPrefix+"api/query_range", prometheus.InstrumentHandler( + pathPrefix+"api/query_range", handler(msrv.QueryRange), )) - http.Handle(pathPrefix + "api/metrics", prometheus.InstrumentHandler( - pathPrefix + "api/metrics", handler(msrv.Metrics), + http.Handle(pathPrefix+"api/metrics", prometheus.InstrumentHandler( + pathPrefix+"api/metrics", handler(msrv.Metrics), )) } diff --git a/web/api/api_test.go b/web/api/api_test.go index e185fbb9dd..44f52d8f06 100644 --- a/web/api/api_test.go +++ b/web/api/api_test.go @@ -23,6 +23,7 @@ import ( clientmodel "github.com/prometheus/client_golang/model" + "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/storage/local" ) @@ -51,7 +52,7 @@ func TestQuery(t *testing.T) { { queryStr: "", status: http.StatusOK, - bodyRe: "syntax error", + bodyRe: `{"type":"error","value":"query:1,1 no expression found in input","version":1}`, }, { queryStr: "expr=testmetric", @@ -76,7 +77,7 @@ func TestQuery(t *testing.T) { { queryStr: "expr=(badexpression", status: http.StatusOK, - bodyRe: "syntax error", + bodyRe: `{"type":"error","value":"query:1,15 unexpected unclosed left parenthesis in paren expression","version":1}`, }, } @@ -92,8 +93,9 @@ func TestQuery(t *testing.T) { storage.WaitForIndexing() api := MetricsService{ - Now: testNow, - Storage: storage, + Now: testNow, + Storage: storage, + QueryEngine: promql.NewEngine(storage), } api.RegisterHandler("/") diff --git a/web/api/query.go b/web/api/query.go index c4e8f4c81b..ef86128512 100644 --- a/web/api/query.go +++ b/web/api/query.go @@ -26,9 +26,6 @@ import ( clientmodel "github.com/prometheus/client_golang/model" - "github.com/prometheus/prometheus/rules" - "github.com/prometheus/prometheus/rules/ast" - "github.com/prometheus/prometheus/stats" "github.com/prometheus/prometheus/web/httputils" ) @@ -41,9 +38,8 @@ func setAccessControlHeaders(w http.ResponseWriter) { } func httpJSONError(w http.ResponseWriter, err error, code int) { - w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) - fmt.Fprintln(w, ast.ErrorToJSON(err)) + httputils.ErrorJSON(w, err) } func parseTimestampOrNow(t string, now clientmodel.Timestamp) (clientmodel.Timestamp, error) { @@ -80,16 +76,19 @@ func (serv MetricsService) Query(w http.ResponseWriter, r *http.Request) { return } - exprNode, err := rules.LoadExprFromString(expr) + query, err := serv.QueryEngine.NewInstantQuery(expr, timestamp) if err != nil { - fmt.Fprint(w, ast.ErrorToJSON(err)) + httpJSONError(w, err, http.StatusOK) return } + res := query.Exec() + if res.Err != nil { + httpJSONError(w, res.Err, http.StatusOK) + return + } + glog.V(1).Infof("Instant query: %s\nQuery stats:\n%s\n", expr, query.Stats()) - queryStats := stats.NewTimerGroup() - result := ast.EvalToString(exprNode, timestamp, ast.JSON, serv.Storage, queryStats) - glog.V(1).Infof("Instant query: %s\nQuery stats:\n%s\n", expr, queryStats) - fmt.Fprint(w, result) + httputils.RespondJSON(w, res.Value) } // QueryRange handles the /api/query_range endpoint. @@ -125,50 +124,31 @@ func (serv MetricsService) QueryRange(w http.ResponseWriter, r *http.Request) { end = serv.Now() } - exprNode, err := rules.LoadExprFromString(expr) - if err != nil { - fmt.Fprint(w, ast.ErrorToJSON(err)) - return - } - if exprNode.Type() != ast.VectorType { - fmt.Fprint(w, ast.ErrorToJSON(errors.New("expression does not evaluate to vector type"))) - return - } - // For safety, limit the number of returned points per timeseries. // This is sufficient for 60s resolution for a week or 1h resolution for a year. if duration/step > 11000 { - fmt.Fprint(w, ast.ErrorToJSON(errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)"))) + err := errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)") + httpJSONError(w, err, http.StatusBadRequest) return } // Align the start to step "tick" boundary. end = end.Add(-time.Duration(end.UnixNano() % int64(step))) + start := end.Add(-duration) - queryStats := stats.NewTimerGroup() - - matrix, err := ast.EvalVectorRange( - exprNode.(ast.VectorNode), - end.Add(-duration), - end, - step, - serv.Storage, - queryStats) + query, err := serv.QueryEngine.NewRangeQuery(expr, start, end, step) if err != nil { - fmt.Fprint(w, ast.ErrorToJSON(err)) + httpJSONError(w, err, http.StatusOK) + return + } + matrix, err := query.Exec().Matrix() + if err != nil { + httpJSONError(w, err, http.StatusOK) return } - sortTimer := queryStats.GetTimer(stats.ResultSortTime).Start() - sort.Sort(matrix) - sortTimer.Stop() - - jsonTimer := queryStats.GetTimer(stats.JSONEncodeTime).Start() - result := ast.TypedValueToJSON(matrix, "matrix") - jsonTimer.Stop() - - glog.V(1).Infof("Range query: %s\nQuery stats:\n%s\n", expr, queryStats) - fmt.Fprint(w, result) + glog.V(1).Infof("Range query: %s\nQuery stats:\n%s\n", expr, query.Stats()) + httputils.RespondJSON(w, matrix) } // Metrics handles the /api/metrics endpoint. diff --git a/web/consoles.go b/web/consoles.go index d8874aaae0..9c2da52cf0 100644 --- a/web/consoles.go +++ b/web/consoles.go @@ -22,7 +22,7 @@ import ( "path/filepath" clientmodel "github.com/prometheus/client_golang/model" - "github.com/prometheus/prometheus/storage/local" + "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/templates" ) @@ -33,8 +33,8 @@ var ( // ConsolesHandler implements http.Handler. type ConsolesHandler struct { - Storage local.Storage - PathPrefix string + QueryEngine *promql.Engine + PathPrefix string } func (h *ConsolesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -70,7 +70,7 @@ func (h *ConsolesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { Path: r.URL.Path, } - template := templates.NewTemplateExpander(string(text), "__console_"+r.URL.Path, data, clientmodel.Now(), h.Storage, h.PathPrefix) + template := templates.NewTemplateExpander(string(text), "__console_"+r.URL.Path, data, clientmodel.Now(), h.QueryEngine, h.PathPrefix) filenames, err := filepath.Glob(*consoleLibrariesPath + "/*.lib") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/web/httputils/httputils.go b/web/httputils/httputils.go index d110de8ef6..c0141d0ece 100644 --- a/web/httputils/httputils.go +++ b/web/httputils/httputils.go @@ -14,8 +14,12 @@ package httputils import ( + "encoding/json" + "io" "net/http" "net/url" + + "github.com/prometheus/prometheus/promql" ) // GetQueryParams calls r.ParseForm and returns r.Form. @@ -23,3 +27,39 @@ func GetQueryParams(r *http.Request) url.Values { r.ParseForm() return r.Form } + +var jsonFormatVersion = 1 + +// ErrorJSON writes the given error JSON-formatted to w. +func ErrorJSON(w io.Writer, err error) error { + data := struct { + Type string `json:"type"` + Value string `json:"value"` + Version int `json:"version"` + }{ + Type: "error", + Value: err.Error(), + Version: jsonFormatVersion, + } + enc := json.NewEncoder(w) + return enc.Encode(data) +} + +// RespondJSON converts the given data value to JSON and writes it to w. +func RespondJSON(w io.Writer, val promql.Value) error { + data := struct { + Type string `json:"type"` + Value interface{} `json:"value"` + Version int `json:"version"` + }{ + Type: val.Type().String(), + Value: val, + Version: jsonFormatVersion, + } + // TODO(fabxc): Adding MarshalJSON to promql.Values might be a good idea. + if sc, ok := val.(*promql.Scalar); ok { + data.Value = sc.Value + } + enc := json.NewEncoder(w) + return enc.Encode(data) +} diff --git a/web/status.go b/web/status.go index 2e905dd2b4..4180874ed2 100644 --- a/web/status.go +++ b/web/status.go @@ -19,7 +19,7 @@ import ( "time" "github.com/prometheus/prometheus/retrieval" - "github.com/prometheus/prometheus/rules/manager" + "github.com/prometheus/prometheus/rules" ) // PrometheusStatusHandler implements http.Handler. @@ -29,7 +29,7 @@ type PrometheusStatusHandler struct { BuildInfo map[string]string Config string Flags map[string]string - RuleManager manager.RuleManager + RuleManager rules.RuleManager TargetPools map[string]*retrieval.TargetPool Birth time.Time