diff --git a/rules/ast/ast.go b/rules/ast/ast.go index 1c69261c6..b36385ab9 100644 --- a/rules/ast/ast.go +++ b/rules/ast/ast.go @@ -40,6 +40,7 @@ type SampleStream struct { Values metric.Values } +// Sample is a single sample belonging to a COWMetric. type Sample struct { Metric clientmodel.COWMetric Value clientmodel.SampleValue @@ -413,7 +414,7 @@ func EvalVectorRange(node VectorNode, start clientmodel.Timestamp, end clientmod // TODO implement watchdog timer for long-running queries. evalTimer := queryStats.GetTimer(stats.InnerEvalTime).Start() sampleStreams := map[uint64]*SampleStream{} - for t := start; t.Before(end); t = t.Add(interval) { + for t := start; !t.After(end); t = t.Add(interval) { vector := node.Eval(t) for _, sample := range vector { samplePair := metric.SamplePair{ diff --git a/rules/ast/printer.go b/rules/ast/printer.go index 7a0560c72..0155a5838 100644 --- a/rules/ast/printer.go +++ b/rules/ast/printer.go @@ -91,11 +91,12 @@ func (vector Vector) String() string { func (matrix Matrix) String() string { metricStrings := make([]string, 0, len(matrix)) for _, sampleStream := range matrix { - metricName, ok := sampleStream.Metric.Metric[clientmodel.MetricNameLabel] - if !ok { - panic("Tried to print matrix without metric name") + metricName, hasName := sampleStream.Metric.Metric[clientmodel.MetricNameLabel] + numLabels := len(sampleStream.Metric.Metric) + if hasName { + numLabels-- } - labelStrings := make([]string, 0, len(sampleStream.Metric.Metric)-1) + 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)) diff --git a/rules/rules_test.go b/rules/rules_test.go index 094965255..028da4006 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -25,6 +25,7 @@ import ( "github.com/prometheus/prometheus/rules/ast" "github.com/prometheus/prometheus/stats" "github.com/prometheus/prometheus/storage/local" + "github.com/prometheus/prometheus/storage/metric" "github.com/prometheus/prometheus/utility/test" ) @@ -60,7 +61,7 @@ func newTestStorage(t testing.TB) (storage local.Storage, closer test.Closer) { func TestExpressions(t *testing.T) { // Labels in expected output need to be alphabetically sorted. - var expressionTests = []struct { + expressionTests := []struct { expr string output []string shouldFail bool @@ -705,6 +706,188 @@ func TestExpressions(t *testing.T) { } } +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", + }, + } + + for i, s := range scenarios { + storage, closer := local.NewTestStorage(t) + 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