From 51aaacf9837f9aca066f71b690712da8c057fc3a Mon Sep 17 00:00:00 2001 From: Cyril Tovena Date: Fri, 8 Jan 2021 10:13:23 +0100 Subject: [PATCH 1/2] Fixes absent_over_time to work with all log selector. I made a mistake forcing absent_over_time to work only for unwrap, which is not the most useful usecase. Signed-off-by: Cyril Tovena --- docs/sources/logql/_index.md | 1 + pkg/logql/functions.go | 2 +- pkg/logql/functions_test.go | 88 ++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 pkg/logql/functions_test.go diff --git a/docs/sources/logql/_index.md b/docs/sources/logql/_index.md index 1178adf002cbc..1e5f022943e8c 100644 --- a/docs/sources/logql/_index.md +++ b/docs/sources/logql/_index.md @@ -391,6 +391,7 @@ The first type uses log entries to compute values and supported functions for op - `count_over_time(log-range)`: counts the entries for each log stream within the given range. - `bytes_rate(log-range)`: calculates the number of bytes per second for each stream. - `bytes_over_time(log-range)`: counts the amount of bytes used by each log stream for a given range. +- `absent_over_time(log-range)`: returns an empty vector if the range vector passed to it has any elements and a 1-element vector with the value 1 if the range vector passed to it has no elements. (`absent_over_time` is useful for alerting on when no time series and logs stream exist for label combination for a certain amount of time.) ##### Log Examples diff --git a/pkg/logql/functions.go b/pkg/logql/functions.go index d601c7f08f967..d07a1d611ad43 100644 --- a/pkg/logql/functions.go +++ b/pkg/logql/functions.go @@ -74,7 +74,7 @@ func (r rangeAggregationExpr) extractor(override *grouping) (log.SampleExtractor } // otherwise we extract metrics from the log line. switch r.operation { - case OpRangeTypeRate, OpRangeTypeCount: + case OpRangeTypeRate, OpRangeTypeCount, OpRangeTypeAbsent: return log.NewLineSampleExtractor(log.CountExtractor, stages, groups, without, noLabels) case OpRangeTypeBytes, OpRangeTypeBytesRate: return log.NewLineSampleExtractor(log.BytesExtractor, stages, groups, without, noLabels) diff --git a/pkg/logql/functions_test.go b/pkg/logql/functions_test.go new file mode 100644 index 0000000000000..7e06a42fb187d --- /dev/null +++ b/pkg/logql/functions_test.go @@ -0,0 +1,88 @@ +package logql + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_Extractor(t *testing.T) { + t.Parallel() + for _, tc := range []string{ + `rate( ( {job="mysql"} |="error" !="timeout" ) [10s] )`, + `absent_over_time( ( {job="mysql"} |="error" !="timeout" ) [10s] )`, + `sum without(a) ( rate ( ( {job="mysql"} |="error" !="timeout" ) [10s] ) )`, + `sum by(a) (rate( ( {job="mysql"} |="error" !="timeout" ) [10s] ) )`, + `sum(count_over_time({job="mysql"}[5m]))`, + `sum(count_over_time({job="mysql"} | json [5m]))`, + `sum(count_over_time({job="mysql"} | logfmt [5m]))`, + `sum(count_over_time({job="mysql"} | regexp "(?Pfoo|bar)" [5m]))`, + `topk(10,sum(rate({region="us-east1"}[5m])) by (name))`, + `topk by (name)(10,sum(rate({region="us-east1"}[5m])))`, + `avg( rate( ( {job="nginx"} |= "GET" ) [10s] ) ) by (region)`, + `avg(min_over_time({job="nginx"} |= "GET" | unwrap foo[10s])) by (region)`, + `sum by (cluster) (count_over_time({job="mysql"}[5m]))`, + `sum by (cluster) (count_over_time({job="mysql"}[5m])) / sum by (cluster) (count_over_time({job="postgres"}[5m])) `, + ` + sum by (cluster) (count_over_time({job="postgres"}[5m])) / + sum by (cluster) (count_over_time({job="postgres"}[5m])) / + sum by (cluster) (count_over_time({job="postgres"}[5m])) + `, + `sum by (cluster) (count_over_time({job="mysql"}[5m])) / min(count_over_time({job="mysql"}[5m])) `, + `sum by (job) ( + count_over_time({namespace="tns"} |= "level=error"[5m]) + / + count_over_time({namespace="tns"}[5m]) + )`, + `stdvar_over_time({app="foo"} |= "bar" | json | latency >= 250ms or ( status_code < 500 and status_code > 200) + | line_format "blip{{ .foo }}blop {{.status_code}}" | label_format foo=bar,status_code="buzz{{.bar}}" | unwrap foo [5m])`, + `sum_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms|unwrap latency [5m])`, + `sum by (job) ( + sum_over_time({namespace="tns"} |= "level=error" | json | foo=5 and bar<25ms | unwrap latency[5m]) + / + count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m]) + )`, + `sum by (job) ( + sum_over_time({namespace="tns"} |= "level=error" | json | foo=5 and bar<25ms | unwrap bytes(latency)[5m]) + / + count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m]) + )`, + `sum by (job) ( + sum_over_time( + {namespace="tns"} |= "level=error" | json | avg=5 and bar<25ms | unwrap duration(latency) [5m] + ) + / + count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m]) + )`, + `sum_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms | unwrap latency | __error__!~".*" | foo >5[5m])`, + `absent_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms | unwrap latency | __error__!~".*" | foo >5[5m])`, + `sum by (job) ( + sum_over_time( + {namespace="tns"} |= "level=error" | json | avg=5 and bar<25ms | unwrap duration(latency) | __error__!~".*" [5m] + ) + / + count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m]) + )`, + `label_replace( + sum by (job) ( + sum_over_time( + {namespace="tns"} |= "level=error" | json | avg=5 and bar<25ms | unwrap duration(latency) | __error__!~".*" [5m] + ) + / + count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m]) + ), + "foo", + "$1", + "service", + "(.*):.*" + ) + `, + } { + t.Run(tc, func(t *testing.T) { + expr, err := ParseSampleExpr(tc) + require.Nil(t, err) + _, err = expr.Extractor() + require.Nil(t, err) + }) + } +} From bd4e45d9ddd32e4eab1e19cc1a8c7f6f213d1b1d Mon Sep 17 00:00:00 2001 From: Cyril Tovena Date: Fri, 8 Jan 2021 10:16:31 +0100 Subject: [PATCH 2/2] actually add a test that cover the previous mistake :) Signed-off-by: Cyril Tovena --- pkg/logql/functions_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/logql/functions_test.go b/pkg/logql/functions_test.go index 7e06a42fb187d..47cc804168e85 100644 --- a/pkg/logql/functions_test.go +++ b/pkg/logql/functions_test.go @@ -56,6 +56,7 @@ func Test_Extractor(t *testing.T) { )`, `sum_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms | unwrap latency | __error__!~".*" | foo >5[5m])`, `absent_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms | unwrap latency | __error__!~".*" | foo >5[5m])`, + `absent_over_time({namespace="tns"} |= "level=error" | json [5m])`, `sum by (job) ( sum_over_time( {namespace="tns"} |= "level=error" | json | avg=5 and bar<25ms | unwrap duration(latency) | __error__!~".*" [5m]