From 11c66d03faf17cf39d745575b2bff757451769c8 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Sat, 23 Sep 2023 20:21:40 +0530 Subject: [PATCH] fix: alert eval for "="/"!=" combination with "at least once"/"all the times" --- Makefile | 3 +- go.mod | 14 +- go.sum | 40 +-- pkg/query-service/rules/thresholdRule.go | 16 +- pkg/query-service/rules/thresholdRule_test.go | 297 ++++++++++++++++++ 5 files changed, 341 insertions(+), 29 deletions(-) create mode 100644 pkg/query-service/rules/thresholdRule_test.go diff --git a/Makefile b/Makefile index 05b6cb27e6..7d976341c1 100644 --- a/Makefile +++ b/Makefile @@ -151,4 +151,5 @@ test: go test ./pkg/query-service/app/querier/... go test ./pkg/query-service/converter/... go test ./pkg/query-service/formatter/... - go test ./pkg/query-service/tests/integration/... \ No newline at end of file + go test ./pkg/query-service/tests/integration/... + go test ./pkg/query-service/rules/... diff --git a/go.mod b/go.mod index 7d82ac48bc..2402e92498 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module go.signoz.io/signoz go 1.21 require ( - github.com/ClickHouse/clickhouse-go/v2 v2.13.2 + github.com/ClickHouse/clickhouse-go/v2 v2.14.0 github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974 @@ -18,7 +18,7 @@ require ( github.com/go-redis/redis/v8 v8.11.5 github.com/go-redis/redismock/v8 v8.11.5 github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.3.1 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 github.com/gosimple/slug v1.10.0 @@ -45,9 +45,10 @@ require ( github.com/smartystreets/assertions v1.13.1 github.com/smartystreets/goconvey v1.8.1 github.com/soheilhy/cmux v0.1.5 + github.com/srikanthccv/ClickHouse-go-mock v0.4.0 github.com/stretchr/testify v1.8.4 go.opentelemetry.io/collector/confmap v0.70.0 - go.opentelemetry.io/otel v1.16.0 + go.opentelemetry.io/otel v1.17.0 go.opentelemetry.io/otel/sdk v1.16.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.25.0 @@ -69,6 +70,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect github.com/ClickHouse/ch-go v0.58.2 // indirect + github.com/DATA-DOG/go-sqlmock v1.5.0 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/andybalholm/brotli v1.0.5 // indirect github.com/aws/aws-sdk-go v1.44.302 // indirect @@ -135,13 +137,13 @@ require ( go.opentelemetry.io/collector/pdata v1.0.0-rcv0014 // indirect go.opentelemetry.io/collector/semconv v0.81.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 // indirect - go.opentelemetry.io/otel/metric v1.16.0 // indirect - go.opentelemetry.io/otel/trace v1.16.0 // indirect + go.opentelemetry.io/otel/metric v1.17.0 // indirect + go.opentelemetry.io/otel/trace v1.17.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/goleak v1.2.1 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.10.0 // indirect + golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 7c708cc8f2..6283ae2486 100644 --- a/go.sum +++ b/go.sum @@ -84,10 +84,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0= github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw= -github.com/ClickHouse/clickhouse-go/v2 v2.13.2 h1:LSg6670+xbd5VczO5Ei3DHZBIeGulfwhNuHCUth/qOA= -github.com/ClickHouse/clickhouse-go/v2 v2.13.2/go.mod h1:4QITCrdY/ugPYA+QGnJ92h+v7TGaZQ7l0393Q/wlM3Q= -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/ClickHouse/clickhouse-go/v2 v2.14.0 h1:7pzOLkWTc+Kn6e8Q2jtkTT9G+7RFY6QCw1Kc2nQ5pW4= +github.com/ClickHouse/clickhouse-go/v2 v2.14.0/go.mod h1:PHqbMvJTQ0EI4a1vJhmbmL/Ajr+Cin2O+WJjnYctJvg= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb h1:bneLSKPf9YUSFmafKx32bynV6QrzViL/s+ZDvQxH1E4= @@ -186,8 +188,8 @@ github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.4+incompatible h1:s/LVDftw9hjblvqIeTiGYXBCD95nOEEl7qRsRrIOuQI= -github.com/docker/docker v24.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY= +github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -367,8 +369,8 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -614,8 +616,8 @@ github.com/open-telemetry/opamp-go v0.5.0 h1:2YFbb6G4qBkq3yTRdVb5Nfz9hKHW/ldUyex github.com/open-telemetry/opamp-go v0.5.0/go.mod h1:IMdeuHGVc5CjKSu5/oNV0o+UmiXuahoHvoZ4GOmAI9M= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= +github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/ovh/go-ovh v1.4.1 h1:VBGa5wMyQtTP7Zb+w97zRCh9sLtM/2YKRyy+MEJmWaM= @@ -729,6 +731,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/srikanthccv/ClickHouse-go-mock v0.4.0 h1:tLk7qoDLg7Z5YD5mOmNqjRDbsm6ehJVXOFvSnG+gQAg= +github.com/srikanthccv/ClickHouse-go-mock v0.4.0/go.mod h1:kRG9cuhS527AMXqKYgsii/CP28L/22fyJcOBExmLpEw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -784,14 +788,14 @@ go.opentelemetry.io/collector/semconv v0.81.0 h1:lCYNNo3powDvFIaTPP2jDKIrBiV1T92 go.opentelemetry.io/collector/semconv v0.81.0/go.mod h1:TlYPtzvsXyHOgr5eATi43qEMqwSmIziivJB2uctKswo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 h1:pginetY7+onl4qN1vl0xW/V/v6OBZ0vVdH+esuJgvmM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0/go.mod h1:XiYsayHc36K3EByOO6nbAXnAWbrUxdjUROCEeeROOH8= -go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= -go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= -go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= -go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM= +go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= +go.opentelemetry.io/otel/metric v1.17.0 h1:iG6LGVz5Gh+IuO0jmgvpTB6YVrCGngi8QGm+pMd8Pdc= +go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o= go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= -go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= -go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ= +go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= @@ -1043,8 +1047,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/pkg/query-service/rules/thresholdRule.go b/pkg/query-service/rules/thresholdRule.go index f5bbef8fad..c4d6fdbf12 100644 --- a/pkg/query-service/rules/thresholdRule.go +++ b/pkg/query-service/rules/thresholdRule.go @@ -521,6 +521,7 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer if math.IsNaN(sample.Point.V) { continue } + sample.Point.Vs = append(sample.Point.Vs, sample.Point.V) // capture lables in result sample.Metric = lbls.Labels() @@ -540,6 +541,9 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer } else if r.compareOp() == ValueIsBelow { sample.Point.V = math.Max(existing.Point.V, sample.Point.V) resultMap[labelHash] = sample + } else { + sample.Point.Vs = append(existing.Point.Vs, sample.Point.V) + resultMap[labelHash] = sample } case AtleastOnce: if r.compareOp() == ValueIsAbove { @@ -548,6 +552,9 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer } else if r.compareOp() == ValueIsBelow { sample.Point.V = math.Min(existing.Point.V, sample.Point.V) resultMap[labelHash] = sample + } else { + sample.Point.Vs = append(existing.Point.Vs, sample.Point.V) + resultMap[labelHash] = sample } case OnAverage: sample.Point.V = (existing.Point.V + sample.Point.V) / 2 @@ -578,36 +585,37 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer } - if s, ok := resultMap[labelHash]; ok { - s.Point.Vs = append(s.Point.Vs, s.Point.V) - } } - for _, s := range resultMap { + for hash, s := range resultMap { if r.matchType() == AllTheTimes && r.compareOp() == ValueIsEq { for _, v := range s.Point.Vs { if v != r.targetVal() { // if any of the values is not equal to target, alert shouldn't be sent s.Point.V = v } } + resultMap[hash] = s } else if r.matchType() == AllTheTimes && r.compareOp() == ValueIsNotEq { for _, v := range s.Point.Vs { if v == r.targetVal() { // if any of the values is equal to target, alert shouldn't be sent s.Point.V = v } } + resultMap[hash] = s } else if r.matchType() == AtleastOnce && r.compareOp() == ValueIsEq { for _, v := range s.Point.Vs { if v == r.targetVal() { // if any of the values is equal to target, alert should be sent s.Point.V = v } } + resultMap[hash] = s } else if r.matchType() == AtleastOnce && r.compareOp() == ValueIsNotEq { for _, v := range s.Point.Vs { if v != r.targetVal() { // if any of the values is not equal to target, alert should be sent s.Point.V = v } } + resultMap[hash] = s } } diff --git a/pkg/query-service/rules/thresholdRule_test.go b/pkg/query-service/rules/thresholdRule_test.go new file mode 100644 index 0000000000..27ad6611f5 --- /dev/null +++ b/pkg/query-service/rules/thresholdRule_test.go @@ -0,0 +1,297 @@ +package rules + +import ( + "context" + "testing" + "time" + + cmock "github.com/srikanthccv/ClickHouse-go-mock" + "github.com/stretchr/testify/assert" + "go.signoz.io/signoz/pkg/query-service/featureManager" + v3 "go.signoz.io/signoz/pkg/query-service/model/v3" +) + +func TestThresholdRuleCombinations(t *testing.T) { + postableRule := PostableRule{ + Alert: "Tricky Condition Tests", + AlertType: "METRICS_BASED_ALERT", + RuleType: RuleTypeThreshold, + EvalWindow: Duration(5 * time.Minute), + Frequency: Duration(1 * time.Minute), + RuleCondition: &RuleCondition{ + CompositeQuery: &v3.CompositeQuery{ + QueryType: v3.QueryTypeBuilder, + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + QueryName: "A", + StepInterval: 60, + AggregateAttribute: v3.AttributeKey{ + Key: "probe_success", + }, + AggregateOperator: v3.AggregateOperatorNoOp, + DataSource: v3.DataSourceMetrics, + Expression: "A", + }, + }, + }, + }, + } + fm := featureManager.StartManager() + mock, err := cmock.NewClickHouseNative(nil) + if err != nil { + t.Errorf("an error '%s' was not expected when opening a stub database connection", err) + } + + cols := make([]cmock.ColumnType, 0) + cols = append(cols, cmock.ColumnType{Name: "value", Type: "Int32"}) + cols = append(cols, cmock.ColumnType{Name: "endpoint", Type: "String"}) + + cases := []struct { + values [][]interface{} + expectAlert bool + compareOp string + matchType string + target float64 + }{ + // Test cases for Equals Always + { + values: [][]interface{}{ + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + }, + expectAlert: true, + compareOp: "3", // Equals + matchType: "2", // Always + target: 0.0, + }, + { + values: [][]interface{}{ + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(1), "endpoint"}, + }, + expectAlert: false, + compareOp: "3", // Equals + matchType: "2", // Always + target: 0.0, + }, + { + values: [][]interface{}{ + {int32(0), "endpoint"}, + {int32(1), "endpoint"}, + {int32(0), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + }, + expectAlert: false, + compareOp: "3", // Equals + matchType: "2", // Always + target: 0.0, + }, + { + values: [][]interface{}{ + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + }, + expectAlert: false, + compareOp: "3", // Equals + matchType: "2", // Always + target: 0.0, + }, + // Test cases for Equals Once + { + values: [][]interface{}{ + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + }, + expectAlert: true, + compareOp: "3", // Equals + matchType: "1", // Once + target: 0.0, + }, + { + values: [][]interface{}{ + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(1), "endpoint"}, + }, + expectAlert: true, + compareOp: "3", // Equals + matchType: "1", // Once + target: 0.0, + }, + { + values: [][]interface{}{ + {int32(0), "endpoint"}, + {int32(1), "endpoint"}, + {int32(0), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + }, + expectAlert: true, + compareOp: "3", // Equals + matchType: "1", // Once + target: 0.0, + }, + { + values: [][]interface{}{ + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + }, + expectAlert: false, + compareOp: "3", // Equals + matchType: "1", // Once + target: 0.0, + }, + // Test cases for Not Equals Always + { + values: [][]interface{}{ + {int32(0), "endpoint"}, + {int32(1), "endpoint"}, + {int32(0), "endpoint"}, + {int32(1), "endpoint"}, + {int32(0), "endpoint"}, + }, + expectAlert: false, + compareOp: "4", // Not Equals + matchType: "2", // Always + target: 0.0, + }, + { + values: [][]interface{}{ + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(0), "endpoint"}, + }, + expectAlert: false, + compareOp: "4", // Not Equals + matchType: "2", // Always + target: 0.0, + }, + { + values: [][]interface{}{ + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + }, + expectAlert: true, + compareOp: "4", // Not Equals + matchType: "2", // Always + target: 0.0, + }, + { + values: [][]interface{}{ + {int32(1), "endpoint"}, + {int32(0), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + }, + expectAlert: false, + compareOp: "4", // Not Equals + matchType: "2", // Always + target: 0.0, + }, + // Test cases for Not Equals Once + { + values: [][]interface{}{ + {int32(0), "endpoint"}, + {int32(1), "endpoint"}, + {int32(0), "endpoint"}, + {int32(1), "endpoint"}, + {int32(0), "endpoint"}, + }, + expectAlert: true, + compareOp: "4", // Not Equals + matchType: "1", // Once + target: 0.0, + }, + { + values: [][]interface{}{ + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + }, + expectAlert: false, + compareOp: "4", // Not Equals + matchType: "1", // Once + target: 0.0, + }, + { + values: [][]interface{}{ + {int32(0), "endpoint"}, + {int32(0), "endpoint"}, + {int32(1), "endpoint"}, + {int32(0), "endpoint"}, + {int32(1), "endpoint"}, + }, + expectAlert: true, + compareOp: "4", // Not Equals + matchType: "1", // Once + target: 0.0, + }, + { + values: [][]interface{}{ + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + {int32(1), "endpoint"}, + }, + expectAlert: true, + compareOp: "4", // Not Equals + matchType: "1", // Once + target: 0.0, + }, + } + + for idx, c := range cases { + rows := cmock.NewRows(cols, c.values) + // We are testing the eval logic after the query is run + // so we don't care about the query string here + queryString := "SELECT value, endpoint FROM table" + mock. + ExpectQuery(queryString). + WillReturnRows(rows) + postableRule.RuleCondition.CompareOp = CompareOp(c.compareOp) + postableRule.RuleCondition.MatchType = MatchType(c.matchType) + postableRule.RuleCondition.Target = &c.target + + rule, err := NewThresholdRule("69", &postableRule, ThresholdRuleOpts{}, fm) + if err != nil { + assert.NoError(t, err) + } + + result, err := rule.runChQuery(context.Background(), mock, queryString) + if err != nil { + assert.NoError(t, err) + } + if c.expectAlert { + assert.Equal(t, 1, len(result), "case %d", idx) + } else { + assert.Equal(t, 0, len(result), "case %d", idx) + } + } +}