From aed3c93fd8ff612940247c05e6e5cb676afdc0c0 Mon Sep 17 00:00:00 2001 From: atakavci <58048133+atakavci@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:28:58 +0300 Subject: [PATCH] Support reducers in RedisTimeSeries 1.8 GA (#348) Add new reducers in RedisTimeSeries 1.8 GA --- .../TimeSeries/Extensions/ReduceExtensions.cs | 7 + .../TimeSeries/Literals/Enums/Reduce.cs | 35 ++++ .../TimeSeries/TestAPI/TestMRange.cs | 177 +++++++++++++++++- 3 files changed, 218 insertions(+), 1 deletion(-) diff --git a/src/NRedisStack/TimeSeries/Extensions/ReduceExtensions.cs b/src/NRedisStack/TimeSeries/Extensions/ReduceExtensions.cs index c5db06b4..204d64a3 100644 --- a/src/NRedisStack/TimeSeries/Extensions/ReduceExtensions.cs +++ b/src/NRedisStack/TimeSeries/Extensions/ReduceExtensions.cs @@ -9,6 +9,13 @@ internal static class ReduceExtensions TsReduce.Sum => "SUM", TsReduce.Min => "MIN", TsReduce.Max => "MAX", + TsReduce.Avg => "AVG", + TsReduce.Range => "RANGE", + TsReduce.Count => "COUNT", + TsReduce.StdP => "STD.P", + TsReduce.StdS => "STD.S", + TsReduce.VarP => "VAR.P", + TsReduce.VarS => "VAR.S", _ => throw new ArgumentOutOfRangeException(nameof(reduce), "Invalid Reduce type"), }; } diff --git a/src/NRedisStack/TimeSeries/Literals/Enums/Reduce.cs b/src/NRedisStack/TimeSeries/Literals/Enums/Reduce.cs index ffe020ee..da3f94ac 100644 --- a/src/NRedisStack/TimeSeries/Literals/Enums/Reduce.cs +++ b/src/NRedisStack/TimeSeries/Literals/Enums/Reduce.cs @@ -19,5 +19,40 @@ public enum TsReduce /// A maximum sample of all samples in the group /// Max, + + /// + /// Arithmetic mean of all non-NaN values (since RedisTimeSeries v1.8) + /// + Avg, + + /// + /// Difference between maximum non-NaN value and minimum non-NaN value (since RedisTimeSeries v1.8) + /// + Range, + + /// + /// Number of non-NaN values (since RedisTimeSeries v1.8) + /// + Count, + + /// + /// Population standard deviation of all non-NaN values (since RedisTimeSeries v1.8) + /// + StdP, + + /// + /// Sample standard deviation of all non-NaN values (since RedisTimeSeries v1.8) + /// + StdS, + + /// + /// Population variance of all non-NaN values (since RedisTimeSeries v1.8) + /// + VarP, + + /// + /// Sample variance of all non-NaN values (since RedisTimeSeries v1.8) + /// + VarS } } diff --git a/tests/NRedisStack.Tests/TimeSeries/TestAPI/TestMRange.cs b/tests/NRedisStack.Tests/TimeSeries/TestAPI/TestMRange.cs index 1c12a4be..cd95109a 100644 --- a/tests/NRedisStack.Tests/TimeSeries/TestAPI/TestMRange.cs +++ b/tests/NRedisStack.Tests/TimeSeries/TestAPI/TestMRange.cs @@ -257,7 +257,7 @@ public void TestMRangeGroupby() } [SkipIfRedis(Is.OSSCluster, Is.Enterprise)] - public void TestMRangeReduce() + public void TestMRangeReduceSum() { IDatabase db = redisFixture.Redis.GetDatabase(); db.Execute("FLUSHALL"); @@ -281,6 +281,181 @@ public void TestMRangeReduce() } } + [SkipIfRedis(Is.OSSCluster, Is.Enterprise)] + public void TestMRangeReduceAvg() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ts = db.TS(); + foreach (var key in _keys) + { + var label = new TimeSeriesLabel("key", "MRangeReduce"); + ts.Create(key, labels: new List { label }); + } + + var tuples = CreateData(ts, 50); + var results = ts.MRange("-", "+", new List { "key=MRangeReduce" }, withLabels: true, groupbyTuple: ("key", TsReduce.Avg)); + Assert.Equal(1, results.Count); + Assert.Equal("key=MRangeReduce", results[0].key); + Assert.Equal(new TimeSeriesLabel("key", "MRangeReduce"), results[0].labels[0]); + Assert.Equal(new TimeSeriesLabel("__reducer__", "avg"), results[0].labels[1]); + Assert.Equal(new TimeSeriesLabel("__source__", string.Join(",", _keys)), results[0].labels[2]); + for (int i = 0; i < results[0].values.Count; i++) + { + Assert.Equal(tuples[i].Val, results[0].values[i].Val); + } + } + + [SkipIfRedis(Is.OSSCluster, Is.Enterprise)] + public void TestMRangeReduceRange() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ts = db.TS(); + foreach (var key in _keys) + { + var label = new TimeSeriesLabel("key", "MRangeReduce"); + ts.Create(key, labels: new List { label }); + } + + var tuples = CreateData(ts, 50); + var results = ts.MRange("-", "+", new List { "key=MRangeReduce" }, withLabels: true, groupbyTuple: ("key", TsReduce.Range)); + Assert.Equal(1, results.Count); + Assert.Equal("key=MRangeReduce", results[0].key); + Assert.Equal(new TimeSeriesLabel("key", "MRangeReduce"), results[0].labels[0]); + Assert.Equal(new TimeSeriesLabel("__reducer__", "range"), results[0].labels[1]); + Assert.Equal(new TimeSeriesLabel("__source__", string.Join(",", _keys)), results[0].labels[2]); + for (int i = 0; i < results[0].values.Count; i++) + { + Assert.Equal(0, results[0].values[i].Val); + } + } + + [SkipIfRedis(Is.OSSCluster, Is.Enterprise)] + public void TestMRangeReduceCount() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ts = db.TS(); + foreach (var key in _keys) + { + var label = new TimeSeriesLabel("key", "MRangeReduce"); + ts.Create(key, labels: new List { label }); + } + + var tuples = CreateData(ts, 50); + var results = ts.MRange("-", "+", new List { "key=MRangeReduce" }, withLabels: true, groupbyTuple: ("key", TsReduce.Count)); + Assert.Equal(1, results.Count); + Assert.Equal("key=MRangeReduce", results[0].key); + Assert.Equal(new TimeSeriesLabel("key", "MRangeReduce"), results[0].labels[0]); + Assert.Equal(new TimeSeriesLabel("__reducer__", "count"), results[0].labels[1]); + Assert.Equal(new TimeSeriesLabel("__source__", string.Join(",", _keys)), results[0].labels[2]); + for (int i = 0; i < results[0].values.Count; i++) + { + Assert.Equal(2, results[0].values[i].Val); + } + } + + [SkipIfRedis(Is.OSSCluster, Is.Enterprise)] + public void TestMRangeReduceStdP() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ts = db.TS(); + foreach (var key in _keys) + { + var label = new TimeSeriesLabel("key", "MRangeReduce"); + ts.Create(key, labels: new List { label }); + } + + var tuples = CreateData(ts, 50); + var results = ts.MRange("-", "+", new List { "key=MRangeReduce" }, withLabels: true, groupbyTuple: ("key", TsReduce.StdP)); + Assert.Equal(1, results.Count); + Assert.Equal("key=MRangeReduce", results[0].key); + Assert.Equal(new TimeSeriesLabel("key", "MRangeReduce"), results[0].labels[0]); + Assert.Equal(new TimeSeriesLabel("__reducer__", "std.p"), results[0].labels[1]); + Assert.Equal(new TimeSeriesLabel("__source__", string.Join(",", _keys)), results[0].labels[2]); + for (int i = 0; i < results[0].values.Count; i++) + { + Assert.Equal(0, results[0].values[i].Val); + } + } + + [SkipIfRedis(Is.OSSCluster, Is.Enterprise)] + public void TestMRangeReduceStdS() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ts = db.TS(); + foreach (var key in _keys) + { + var label = new TimeSeriesLabel("key", "MRangeReduce"); + ts.Create(key, labels: new List { label }); + } + + var tuples = CreateData(ts, 50); + var results = ts.MRange("-", "+", new List { "key=MRangeReduce" }, withLabels: true, groupbyTuple: ("key", TsReduce.StdS)); + Assert.Equal(1, results.Count); + Assert.Equal("key=MRangeReduce", results[0].key); + Assert.Equal(new TimeSeriesLabel("key", "MRangeReduce"), results[0].labels[0]); + Assert.Equal(new TimeSeriesLabel("__reducer__", "std.s"), results[0].labels[1]); + Assert.Equal(new TimeSeriesLabel("__source__", string.Join(",", _keys)), results[0].labels[2]); + for (int i = 0; i < results[0].values.Count; i++) + { + Assert.Equal(0, results[0].values[i].Val); + } + } + + [SkipIfRedis(Is.OSSCluster, Is.Enterprise)] + public void TestMRangeReduceVarP() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ts = db.TS(); + foreach (var key in _keys) + { + var label = new TimeSeriesLabel("key", "MRangeReduce"); + ts.Create(key, labels: new List { label }); + } + + var tuples = CreateData(ts, 50); + var results = ts.MRange("-", "+", new List { "key=MRangeReduce" }, withLabels: true, groupbyTuple: ("key", TsReduce.VarP)); + Assert.Equal(1, results.Count); + Assert.Equal("key=MRangeReduce", results[0].key); + Assert.Equal(new TimeSeriesLabel("key", "MRangeReduce"), results[0].labels[0]); + Assert.Equal(new TimeSeriesLabel("__reducer__", "var.p"), results[0].labels[1]); + Assert.Equal(new TimeSeriesLabel("__source__", string.Join(",", _keys)), results[0].labels[2]); + for (int i = 0; i < results[0].values.Count; i++) + { + Assert.Equal(0, results[0].values[i].Val); + } + } + + [SkipIfRedis(Is.OSSCluster, Is.Enterprise)] + public void TestMRangeReduceVarS() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ts = db.TS(); + foreach (var key in _keys) + { + var label = new TimeSeriesLabel("key", "MRangeReduce"); + ts.Create(key, labels: new List { label }); + } + + var tuples = CreateData(ts, 50); + var results = ts.MRange("-", "+", new List { "key=MRangeReduce" }, withLabels: true, groupbyTuple: ("key", TsReduce.VarS)); + Assert.Equal(1, results.Count); + Assert.Equal("key=MRangeReduce", results[0].key); + Assert.Equal(new TimeSeriesLabel("key", "MRangeReduce"), results[0].labels[0]); + Assert.Equal(new TimeSeriesLabel("__reducer__", "var.s"), results[0].labels[1]); + Assert.Equal(new TimeSeriesLabel("__source__", string.Join(",", _keys)), results[0].labels[2]); + for (int i = 0; i < results[0].values.Count; i++) + { + Assert.Equal(0, results[0].values[i].Val); + } + } + [SkipIfRedis(Is.OSSCluster, Is.Enterprise)] public void TestMRangeFilterBy() {