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()
{