From aaea75cf305a906aac7eebc9da69f8c68f85e17c Mon Sep 17 00:00:00 2001 From: Lukasz Mierzwa Date: Wed, 30 Oct 2024 15:54:01 +0000 Subject: [PATCH] Add dead code detection --- docs/changelog.md | 6 + internal/checks/alerts_template.go | 3 + internal/checks/alerts_template_test.go | 27 +- internal/parser/utils/source.go | 106 ++++- internal/parser/utils/source_test.go | 573 ++++++++++++++++++++++-- 5 files changed, 645 insertions(+), 70 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 567b1415..1be17bc6 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,11 @@ # Changelog +## v0.67.2 + +### Fixed + +- Improved the accuracy of [alerts/template](checks/alerts/template.md) check. + ## v0.67.1 ### Fixed diff --git a/internal/checks/alerts_template.go b/internal/checks/alerts_template.go index bc33e308..b85976b3 100644 --- a/internal/checks/alerts_template.go +++ b/internal/checks/alerts_template.go @@ -439,6 +439,9 @@ func checkQueryLabels(query, labelName, labelValue string, src []utils.Source) ( continue } for _, s := range src { + if s.IsDead { + continue + } if s.FixedLabels && !slices.Contains(s.IncludedLabels, v[1]) { problems = append(problems, textForProblem(query, v[1], "", s, Bug)) goto NEXT diff --git a/internal/checks/alerts_template_test.go b/internal/checks/alerts_template_test.go index 1fca8f9c..5a1d10c7 100644 --- a/internal/checks/alerts_template_test.go +++ b/internal/checks/alerts_template_test.go @@ -1439,7 +1439,7 @@ func TestTemplateCheck(t *testing.T) { }, }, { - description: "", + description: "ignore dead code", content: ` - alert: Foo expr: sum by (region, target, colo_name) (sum_over_time(probe_success{job="abc"}[5m]) or vector(1)) == 0 @@ -1449,30 +1449,7 @@ func TestTemplateCheck(t *testing.T) { `, checker: newTemplateCheck, prometheus: noProm, - problems: func(_ string) []checks.Problem { - return []checks.Problem{ - { - Lines: parser.LineRange{ - First: 6, - Last: 6, - }, - Reporter: checks.TemplateCheckName, - Text: "Template is using `region` label but the query results won't have this label.", - Details: "Calling `vector()` will return a vector value with no labels.\nQuery fragment causing this problem: `vector(1)`.", - Severity: checks.Bug, - }, - { - Lines: parser.LineRange{ - First: 6, - Last: 6, - }, - Reporter: checks.TemplateCheckName, - Text: "Template is using `target` label but the query results won't have this label.", - Details: "Calling `vector()` will return a vector value with no labels.\nQuery fragment causing this problem: `vector(1)`.", - Severity: checks.Bug, - }, - } - }, + problems: noProblems, }, } runTests(t, testCases) diff --git a/internal/parser/utils/source.go b/internal/parser/utils/source.go index 8e673686..c5153459 100644 --- a/internal/parser/utils/source.go +++ b/internal/parser/utils/source.go @@ -2,6 +2,7 @@ package utils import ( "fmt" + "math" "slices" "strings" @@ -32,11 +33,14 @@ type Source struct { ExcludeReason map[string]ExcludedLabel // Reason why a label was excluded Operation string Returns promParser.ValueType - IncludedLabels []string // Labels that are included by filters, they will be present if exist on source series (by). - ExcludedLabels []string // Labels guaranteed to be excluded from the results (without). - GuaranteedLabels []string // Labels guaranteed to be present on the results (matchers). + ReturnedNumbers []float64 // If AlwaysReturns=true this is the number that's returned + IncludedLabels []string // Labels that are included by filters, they will be present if exist on source series (by). + ExcludedLabels []string // Labels guaranteed to be excluded from the results (without). + GuaranteedLabels []string // Labels guaranteed to be present on the results (matchers). Type SourceType FixedLabels bool // Labels are fixed and only allowed labels can be present. + IsDead bool // True if this source cannot be reached and is dead code. + AlwaysReturns bool // True if this source always returns results. } func LabelsSource(expr string, node promParser.Node) (src []Source) { @@ -65,9 +69,11 @@ func walkNode(expr string, node promParser.Node) (src []Source) { case *promParser.NumberLiteral: s.Type = NumberSource s.Returns = promParser.ValueTypeScalar + s.ReturnedNumbers = append(s.ReturnedNumbers, n.Val) s.IncludedLabels = nil s.GuaranteedLabels = nil s.FixedLabels = true + s.AlwaysReturns = true s.ExcludeReason = setInMap( s.ExcludeReason, "", @@ -87,6 +93,7 @@ func walkNode(expr string, node promParser.Node) (src []Source) { s.IncludedLabels = nil s.GuaranteedLabels = nil s.FixedLabels = true + s.AlwaysReturns = true s.ExcludeReason = setInMap( s.ExcludeReason, "", @@ -405,6 +412,7 @@ If you're hoping to get instance specific labels this way and alert when some ta // Otherwise no change to labels. if len(s.Call.Args) == 0 { s.FixedLabels = true + s.AlwaysReturns = true s.IncludedLabels = nil s.GuaranteedLabels = nil s.ExcludeReason = setInMap( @@ -451,6 +459,7 @@ If you're hoping to get instance specific labels this way and alert when some ta s.IncludedLabels = nil s.GuaranteedLabels = nil s.FixedLabels = true + s.AlwaysReturns = true s.ExcludeReason = setInMap( s.ExcludeReason, "", @@ -465,6 +474,7 @@ If you're hoping to get instance specific labels this way and alert when some ta s.IncludedLabels = nil s.GuaranteedLabels = nil s.FixedLabels = true + s.AlwaysReturns = true s.ExcludeReason = setInMap( s.ExcludeReason, "", @@ -483,6 +493,7 @@ If you're hoping to get instance specific labels this way and alert when some ta s.IncludedLabels = nil s.GuaranteedLabels = nil s.FixedLabels = true + s.AlwaysReturns = true s.ExcludeReason = setInMap( s.ExcludeReason, "", @@ -502,6 +513,8 @@ If you're hoping to get instance specific labels this way and alert when some ta s.IncludedLabels = nil s.GuaranteedLabels = nil s.FixedLabels = true + s.AlwaysReturns = true + s.ReturnedNumbers = append(s.ReturnedNumbers, n.Args[0].(*promParser.NumberLiteral).Val) s.ExcludeReason = setInMap( s.ExcludeReason, "", @@ -522,25 +535,34 @@ If you're hoping to get instance specific labels this way and alert when some ta func parseBinOps(expr string, n *promParser.BinaryExpr) (src []Source) { var s Source switch { + + // foo{} + 1 + // 1 + foo{} + // foo{} > 1 + // 1 > foo{} case n.VectorMatching == nil: - var ok bool - for _, s = range walkNode(expr, n.LHS) { - if s.Returns != promParser.ValueTypeScalar && s.Returns != promParser.ValueTypeString { - ok = true - src = append(src, s) - } - } - if !ok { - for _, rs := range walkNode(expr, n.RHS) { - if rs.Returns != promParser.ValueTypeScalar && rs.Returns != promParser.ValueTypeString { - ok = true + lhs := walkNode(expr, n.LHS) + rhs := walkNode(expr, n.RHS) + for _, ls := range lhs { + for _, rs := range rhs { + switch { + case ls.AlwaysReturns && rs.AlwaysReturns: + // Both sides always return something + for i, lv := range ls.ReturnedNumbers { + for _, rv := range rs.ReturnedNumbers { + ls.ReturnedNumbers[i], ls.IsDead = calculateStaticReturn(lv, rv, n.Op, ls.IsDead) + } + } + src = append(src, ls) + case ls.Returns == promParser.ValueTypeVector, ls.Returns == promParser.ValueTypeMatrix: + // Use labels from LHS + src = append(src, ls) + case rs.Returns == promParser.ValueTypeVector, rs.Returns == promParser.ValueTypeMatrix: + // Use labels from RHS src = append(src, rs) } } } - if !ok { - src = append(src, s) - } // foo{} + bar{} // foo{} + on(...) bar{} @@ -646,6 +668,7 @@ func parseBinOps(expr string, n *promParser.BinaryExpr) (src []Source) { // foo{} and on(...) bar{} // foo{} and ignoring(...) bar{} case n.VectorMatching.Card == promParser.CardManyToMany: + var lhsCanBeEmpty bool // true if any of the LHS query can produce empty results. for _, s = range walkNode(expr, n.LHS) { if n.VectorMatching.On { s.IncludedLabels = appendToSlice(s.IncludedLabels, n.VectorMatching.MatchingLabels...) @@ -656,6 +679,9 @@ func parseBinOps(expr string, n *promParser.BinaryExpr) (src []Source) { if s.Operation == "" { s.Operation = n.VectorMatching.Card.String() } + if !s.AlwaysReturns { + lhsCanBeEmpty = true + } src = append(src, s) } if n.Op == promParser.LOR { @@ -663,9 +689,55 @@ func parseBinOps(expr string, n *promParser.BinaryExpr) (src []Source) { if s.Operation == "" { s.Operation = n.VectorMatching.Card.String() } + // If LHS can NOT be empty then RHS is dead code. + if !lhsCanBeEmpty { + s.IsDead = true + } src = append(src, s) } } } return src } + +func calculateStaticReturn(lv, rv float64, op promParser.ItemType, isDead bool) (float64, bool) { + switch op { + case promParser.EQLC: + if lv != rv { + return lv, true + } + case promParser.NEQ: + if lv == rv { + return lv, true + } + case promParser.LTE: + if lv > rv { + return lv, true + } + case promParser.LSS: + if lv >= rv { + return lv, true + } + case promParser.GTE: + if lv < rv { + return lv, true + } + case promParser.GTR: + if lv <= rv { + return lv, true + } + case promParser.ADD: + return lv + rv, isDead + case promParser.SUB: + return lv - rv, isDead + case promParser.MUL: + return lv * rv, isDead + case promParser.DIV: + return lv / rv, isDead + case promParser.MOD: + return math.Mod(lv, rv), isDead + case promParser.POW: + return math.Pow(lv, rv), isDead + } + return lv, isDead +} diff --git a/internal/parser/utils/source_test.go b/internal/parser/utils/source_test.go index 7fee9aa7..0a1b94c4 100644 --- a/internal/parser/utils/source_test.go +++ b/internal/parser/utils/source_test.go @@ -43,9 +43,11 @@ func TestLabelsSource(t *testing.T) { expr: "1", output: []utils.Source{ { - Type: utils.NumberSource, - Returns: promParser.ValueTypeScalar, - FixedLabels: true, + Type: utils.NumberSource, + Returns: promParser.ValueTypeScalar, + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{1}, ExcludeReason: map[string]utils.ExcludedLabel{ "": { Reason: "This returns a number value with no labels.", @@ -59,9 +61,11 @@ func TestLabelsSource(t *testing.T) { expr: "1 / 5", output: []utils.Source{ { - Type: utils.NumberSource, - Returns: promParser.ValueTypeScalar, - FixedLabels: true, + Type: utils.NumberSource, + Returns: promParser.ValueTypeScalar, + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{0.2}, ExcludeReason: map[string]utils.ExcludedLabel{ "": { Reason: "This returns a number value with no labels.", @@ -71,13 +75,267 @@ func TestLabelsSource(t *testing.T) { }, }, }, + { + expr: "(2 ^ 5) == bool 5", + output: []utils.Source{ + { + Type: utils.NumberSource, + Returns: promParser.ValueTypeScalar, + FixedLabels: true, + AlwaysReturns: true, + IsDead: true, + ReturnedNumbers: []float64{32}, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "This returns a number value with no labels.", + Fragment: "2", + }, + }, + }, + }, + }, + { + expr: "(2 ^ 5 + 11) % 5 <= bool 2", + output: []utils.Source{ + { + Type: utils.NumberSource, + Returns: promParser.ValueTypeScalar, + FixedLabels: true, + AlwaysReturns: true, + IsDead: true, + ReturnedNumbers: []float64{3}, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "This returns a number value with no labels.", + Fragment: "2", + }, + }, + }, + }, + }, + { + expr: "(2 ^ 5 + 11) % 5 >= bool 20", + output: []utils.Source{ + { + Type: utils.NumberSource, + Returns: promParser.ValueTypeScalar, + FixedLabels: true, + AlwaysReturns: true, + IsDead: true, + ReturnedNumbers: []float64{3}, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "This returns a number value with no labels.", + Fragment: "2", + }, + }, + }, + }, + }, + { + expr: "(2 ^ 5 + 11) % 5 <= bool 3", + output: []utils.Source{ + { + Type: utils.NumberSource, + Returns: promParser.ValueTypeScalar, + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{3}, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "This returns a number value with no labels.", + Fragment: "2", + }, + }, + }, + }, + }, + { + expr: "(2 ^ 5 + 11) % 5 < bool 1", + output: []utils.Source{ + { + Type: utils.NumberSource, + Returns: promParser.ValueTypeScalar, + FixedLabels: true, + AlwaysReturns: true, + IsDead: true, + ReturnedNumbers: []float64{3}, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "This returns a number value with no labels.", + Fragment: "2", + }, + }, + }, + }, + }, + { + expr: "20 - 15 < bool 1", + output: []utils.Source{ + { + Type: utils.NumberSource, + Returns: promParser.ValueTypeScalar, + FixedLabels: true, + AlwaysReturns: true, + IsDead: true, + ReturnedNumbers: []float64{5}, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "This returns a number value with no labels.", + Fragment: "20", + }, + }, + }, + }, + }, + { + expr: "2 * 5", + output: []utils.Source{ + { + Type: utils.NumberSource, + Returns: promParser.ValueTypeScalar, + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{10}, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "This returns a number value with no labels.", + Fragment: "2", + }, + }, + }, + }, + }, + { + expr: "(foo or bar) * 5", + output: []utils.Source{ + { + Type: utils.SelectorSource, + Returns: promParser.ValueTypeVector, + Operation: promParser.CardManyToMany.String(), + Selectors: []*promParser.VectorSelector{ + mustParseVector("foo", 1), + }, + }, + { + Type: utils.SelectorSource, + Returns: promParser.ValueTypeVector, + Operation: promParser.CardManyToMany.String(), + Selectors: []*promParser.VectorSelector{ + mustParseVector("bar", 8), + }, + }, + }, + }, + { + expr: "(foo or vector(2)) * 5", + output: []utils.Source{ + { + Type: utils.SelectorSource, + Returns: promParser.ValueTypeVector, + Operation: promParser.CardManyToMany.String(), + Selectors: []*promParser.VectorSelector{ + mustParseVector("foo", 1), + }, + }, + { + Type: utils.FuncSource, + Returns: promParser.ValueTypeVector, + Operation: "vector", + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{10}, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "Calling `vector()` will return a vector value with no labels.", + Fragment: "vector(2)", + }, + }, + Call: &promParser.Call{ + Func: &promParser.Function{ + Name: "vector", + ArgTypes: []promParser.ValueType{ + promParser.ValueTypeScalar, + }, + Variadic: 0, + ReturnType: promParser.ValueTypeVector, + }, + Args: promParser.Expressions{ + &promParser.NumberLiteral{ + Val: 2, + PosRange: posrange.PositionRange{ + Start: 15, + End: 16, + }, + }, + }, + PosRange: posrange.PositionRange{ + Start: 8, + End: 17, + }, + }, + }, + }, + }, + { + expr: "(foo or vector(5)) * (vector(2) or bar)", + output: []utils.Source{ + { + Type: utils.SelectorSource, + Returns: promParser.ValueTypeVector, + Operation: promParser.CardManyToMany.String(), + Selectors: []*promParser.VectorSelector{ + mustParseVector("foo", 1), + }, + }, + { + Type: utils.FuncSource, + Returns: promParser.ValueTypeVector, + Operation: "vector", + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{5}, // FIXME should be 10 really but it's one-to-one binops + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "Calling `vector()` will return a vector value with no labels.", + Fragment: "vector(5)", + }, + }, + Call: &promParser.Call{ + Func: &promParser.Function{ + Name: "vector", + ArgTypes: []promParser.ValueType{ + promParser.ValueTypeScalar, + }, + Variadic: 0, + ReturnType: promParser.ValueTypeVector, + }, + Args: promParser.Expressions{ + &promParser.NumberLiteral{ + Val: 5, + PosRange: posrange.PositionRange{ + Start: 15, + End: 16, + }, + }, + }, + PosRange: posrange.PositionRange{ + Start: 8, + End: 17, + }, + }, + }, + }, + }, { expr: `1 > bool 0`, output: []utils.Source{ { - Type: utils.NumberSource, - Returns: promParser.ValueTypeScalar, - FixedLabels: true, + Type: utils.NumberSource, + Returns: promParser.ValueTypeScalar, + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{1}, ExcludeReason: map[string]utils.ExcludedLabel{ "": { Reason: "This returns a number value with no labels.", @@ -87,13 +345,32 @@ func TestLabelsSource(t *testing.T) { }, }, }, + { + expr: `20 > bool 10`, + output: []utils.Source{ + { + Type: utils.NumberSource, + Returns: promParser.ValueTypeScalar, + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{20}, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "This returns a number value with no labels.", + Fragment: "20", + }, + }, + }, + }, + }, { expr: `"test"`, output: []utils.Source{ { - Type: utils.StringSource, - Returns: promParser.ValueTypeString, - FixedLabels: true, + Type: utils.StringSource, + Returns: promParser.ValueTypeString, + FixedLabels: true, + AlwaysReturns: true, ExcludeReason: map[string]utils.ExcludedLabel{ "": { Reason: "This returns a string value with no labels.", @@ -1194,10 +1471,11 @@ func TestLabelsSource(t *testing.T) { expr: "year()", output: []utils.Source{ { - Type: utils.FuncSource, - Returns: promParser.ValueTypeVector, - Operation: "year", - FixedLabels: true, + Type: utils.FuncSource, + Returns: promParser.ValueTypeVector, + Operation: "year", + FixedLabels: true, + AlwaysReturns: true, ExcludeReason: map[string]utils.ExcludedLabel{ "": { Reason: "Calling `year()` with no arguments will return an empty time series with no labels.", @@ -1787,10 +2065,12 @@ sum(foo:count) by(job) > 20`, expr: "vector(1)", output: []utils.Source{ { - Type: utils.FuncSource, - Returns: promParser.ValueTypeVector, - Operation: "vector", - FixedLabels: true, + Type: utils.FuncSource, + Returns: promParser.ValueTypeVector, + Operation: "vector", + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{1}, ExcludeReason: map[string]utils.ExcludedLabel{ "": { Reason: "Calling `vector()` will return a vector value with no labels.", @@ -1858,10 +2138,11 @@ sum(foo:count) by(job) > 20`, expr: `days_in_month()`, output: []utils.Source{ { - Type: utils.FuncSource, - Returns: promParser.ValueTypeVector, - Operation: "days_in_month", - FixedLabels: true, + Type: utils.FuncSource, + Returns: promParser.ValueTypeVector, + Operation: "days_in_month", + FixedLabels: true, + AlwaysReturns: true, ExcludeReason: map[string]utils.ExcludedLabel{ "": { Reason: "Calling `days_in_month()` with no arguments will return an empty time series with no labels.", @@ -2198,10 +2479,13 @@ sum by (region, target, colo_name) ( }, }, { - Type: utils.AggregateSource, - Returns: promParser.ValueTypeVector, - Operation: "sum", - FixedLabels: true, + Type: utils.AggregateSource, + Returns: promParser.ValueTypeVector, + Operation: "sum", + FixedLabels: true, + AlwaysReturns: true, + IsDead: true, + ReturnedNumbers: []float64{1}, ExcludeReason: map[string]utils.ExcludedLabel{ "": { Reason: "Calling `vector()` will return a vector value with no labels.", @@ -2211,6 +2495,239 @@ sum by (region, target, colo_name) ( }, }, }, + { + expr: `vector(1) or foo`, + output: []utils.Source{ + { + Type: utils.FuncSource, + Returns: promParser.ValueTypeVector, + Operation: "vector", + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{1}, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "Calling `vector()` will return a vector value with no labels.", + Fragment: "vector(1)", + }, + }, + Call: &promParser.Call{ + Func: &promParser.Function{ + Name: "vector", + ArgTypes: []promParser.ValueType{ + promParser.ValueTypeScalar, + }, + Variadic: 0, + ReturnType: promParser.ValueTypeVector, + }, + Args: promParser.Expressions{ + &promParser.NumberLiteral{ + Val: 1, + PosRange: posrange.PositionRange{ + Start: 7, + End: 8, + }, + }, + }, + PosRange: posrange.PositionRange{ + Start: 0, + End: 9, + }, + }, + }, + { + Type: utils.SelectorSource, + Operation: promParser.CardManyToMany.String(), + Returns: promParser.ValueTypeVector, + Selectors: []*promParser.VectorSelector{ + mustParseVector("foo", 13), + }, + IsDead: true, + }, + }, + }, + { + expr: `vector(0) > 0`, + output: []utils.Source{ + { + Type: utils.FuncSource, + Returns: promParser.ValueTypeVector, + Operation: "vector", + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{0}, + IsDead: true, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "Calling `vector()` will return a vector value with no labels.", + Fragment: "vector(0)", + }, + }, + Call: &promParser.Call{ + Func: &promParser.Function{ + Name: "vector", + ArgTypes: []promParser.ValueType{ + promParser.ValueTypeScalar, + }, + Variadic: 0, + ReturnType: promParser.ValueTypeVector, + }, + Args: promParser.Expressions{ + &promParser.NumberLiteral{ + Val: 0, + PosRange: posrange.PositionRange{ + Start: 7, + End: 8, + }, + }, + }, + PosRange: posrange.PositionRange{ + Start: 0, + End: 9, + }, + }, + }, + }, + }, + { + expr: `sum(foo or vector(0)) > 0`, + output: []utils.Source{ + { + Type: utils.AggregateSource, + Returns: promParser.ValueTypeVector, + Operation: "sum", + Selectors: []*promParser.VectorSelector{ + mustParseVector(`foo`, 4), + }, + FixedLabels: true, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "Query is using aggregation that removes all labels.", + Fragment: `sum(foo or vector(0))`, + }, + }, + }, + { + Type: utils.AggregateSource, + Returns: promParser.ValueTypeVector, + Operation: "sum", + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{0}, + IsDead: true, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "Query is using aggregation that removes all labels.", + Fragment: "sum(foo or vector(0))", + }, + }, + }, + }, + }, + { + expr: `(sum(foo or vector(1)) > 0) == 2`, + output: []utils.Source{ + { + Type: utils.AggregateSource, + Returns: promParser.ValueTypeVector, + Operation: "sum", + Selectors: []*promParser.VectorSelector{ + mustParseVector(`foo`, 5), + }, + FixedLabels: true, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "Query is using aggregation that removes all labels.", + Fragment: `sum(foo or vector(1))`, + }, + }, + }, + { + Type: utils.AggregateSource, + Returns: promParser.ValueTypeVector, + Operation: "sum", + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{1}, + IsDead: true, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "Query is using aggregation that removes all labels.", + Fragment: "sum(foo or vector(1))", + }, + }, + }, + }, + }, + { + expr: `(sum(foo or vector(1)) > 0) != 2`, + output: []utils.Source{ + { + Type: utils.AggregateSource, + Returns: promParser.ValueTypeVector, + Operation: "sum", + Selectors: []*promParser.VectorSelector{ + mustParseVector(`foo`, 5), + }, + FixedLabels: true, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "Query is using aggregation that removes all labels.", + Fragment: `sum(foo or vector(1))`, + }, + }, + }, + { + Type: utils.AggregateSource, + Returns: promParser.ValueTypeVector, + Operation: "sum", + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{1}, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "Query is using aggregation that removes all labels.", + Fragment: "sum(foo or vector(1))", + }, + }, + }, + }, + }, + { + expr: `(sum(foo or vector(2)) > 0) != 2`, + output: []utils.Source{ + { + Type: utils.AggregateSource, + Returns: promParser.ValueTypeVector, + Operation: "sum", + Selectors: []*promParser.VectorSelector{ + mustParseVector(`foo`, 5), + }, + FixedLabels: true, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "Query is using aggregation that removes all labels.", + Fragment: `sum(foo or vector(2))`, + }, + }, + }, + { + Type: utils.AggregateSource, + Returns: promParser.ValueTypeVector, + Operation: "sum", + FixedLabels: true, + AlwaysReturns: true, + ReturnedNumbers: []float64{2}, + IsDead: true, + ExcludeReason: map[string]utils.ExcludedLabel{ + "": { + Reason: "Query is using aggregation that removes all labels.", + Fragment: "sum(foo or vector(2))", + }, + }, + }, + }, + }, } for _, tc := range testCases {