Skip to content

Commit aefeaed

Browse files
authored
Features/ts.mrange groupby (#304)
1 parent 153db24 commit aefeaed

File tree

11 files changed

+178
-6
lines changed

11 files changed

+178
-6
lines changed

.prettierignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
.DS_Store
2+
.dockerignore
23
.gitkeep
34
.gitattributes
45
.gitignore
56
.editorconfig
67
.prettierignore
8+
Dockerfile
79
LICENSE
810
yarn.lock
911
go.mod
@@ -12,6 +14,7 @@ go.sum
1214
.github
1315

1416
# Folders
17+
docker-compose
1518
node_modules
1619
coverage
1720
data

docker-compose/cluster.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: "3.7"
1+
version: '3.7'
22

33
networks:
44
cluster-network:
@@ -46,7 +46,7 @@ services:
4646
container_name: redis3
4747
build:
4848
context: cluster
49-
entrypoint: ["/usr/local/bin/startup_cluster.sh"]
49+
entrypoint: ['/usr/local/bin/startup_cluster.sh']
5050
ports:
5151
- '6381:6379'
5252
- '16379'

pkg/redis-time-series.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"errors"
45
"fmt"
56
"strconv"
67
"time"
@@ -97,13 +98,25 @@ func queryTsMRange(from int64, to int64, qm queryModel, client redisClient) back
9798
return response
9899
}
99100

101+
var args []interface{}
102+
//args := []interface{}{strconv.FormatInt(from, 10), to}
103+
100104
// Execute command
101105
if qm.Aggregation != "" {
102-
err = client.RunFlatCmd(&result, qm.Command, strconv.FormatInt(from, 10), to, "AGGREGATION", qm.Aggregation, qm.Bucket, "WITHLABELS", "FILTER", filter)
106+
args = []interface{}{to, "AGGREGATION", qm.Aggregation, qm.Bucket, "WITHLABELS", "FILTER", filter}
103107
} else {
104-
err = client.RunFlatCmd(&result, qm.Command, strconv.FormatInt(from, 10), to, "WITHLABELS", "FILTER", filter)
108+
args = []interface{}{to, qm.Bucket, "WITHLABELS", "FILTER", filter}
105109
}
106110

111+
if qm.TsGroupByLabel != "" {
112+
if qm.TsReducer == "" {
113+
return errorHandler(response, errors.New("reducer not provided for groups, please provide a reducer (e.g. avg, sum) and try again"))
114+
}
115+
args = append(args, "GROUPBY", qm.TsGroupByLabel, "REDUCE", qm.TsReducer)
116+
}
117+
118+
err = client.RunFlatCmd(&result, qm.Command, strconv.FormatInt(from, 10), args...)
119+
107120
// Check error
108121
if err != nil {
109122
return errorHandler(response, err)

pkg/redis-time-series_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,60 @@ func TestQueryTsMRange(t *testing.T) {
346346
"",
347347
nil,
348348
},
349+
{
350+
"test groupby/reduction",
351+
queryModel{Command: models.TimeSeriesMRange, TsReducer: "SUM", TsGroupByLabel: "reduceLabel"},
352+
[]interface{}{
353+
[]interface{}{
354+
[]byte("foo=bar"),
355+
[]interface{}{
356+
[]interface{}{
357+
[]byte("foo"),
358+
[]byte("bar"),
359+
},
360+
[]interface{}{
361+
[]byte("__reducer__"),
362+
[]byte("sum"),
363+
},
364+
[]interface{}{
365+
[]byte("__source__"),
366+
[]byte("ts:1,ts:2,ts:3"),
367+
},
368+
},
369+
[]interface{}{
370+
[]interface{}{int64(1686835010300), []byte("2102")},
371+
[]interface{}{int64(1686835011312), []byte("1882")},
372+
[]interface{}{int64(1686835013348), []byte("2378")},
373+
[]interface{}{int64(1686835014362), []byte("3007")},
374+
},
375+
},
376+
},
377+
1686835010300,
378+
1686835014362,
379+
2,
380+
4,
381+
[]valueToCheckInResponse{
382+
{frameIndex: 0, fieldIndex: 0, rowIndex: 0, value: time.Unix(0, 1686835010300*int64(time.Millisecond))},
383+
{frameIndex: 0, fieldIndex: 1, rowIndex: 0, value: float64(2102)},
384+
},
385+
"foo=bar",
386+
"",
387+
"",
388+
nil,
389+
},
390+
{"should return error because we missed an actual reducer",
391+
queryModel{Command: models.TimeSeriesMRange, Key: "test1", Filter: "filter", TsGroupByLabel: "foo"},
392+
interface{}("someString"),
393+
0,
394+
0,
395+
0,
396+
0,
397+
nil,
398+
"",
399+
"",
400+
"reducer not provided for groups, please provide a reducer (e.g. avg, sum) and try again",
401+
nil,
402+
},
349403
{
350404
"should return error if result is string",
351405
queryModel{Command: models.TimeSeriesMRange, Key: "test1", Filter: "filter"},

pkg/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ type queryModel struct {
6969
Max string `json:"max"`
7070
ZRangeQuery string `json:"zrangeQuery"`
7171
Path string `json:"path"`
72+
TsReducer string `json:"tsReducer"`
73+
TsGroupByLabel string `json:"tsGroupByLabel"`
7274
SearchQuery string `json:"searchQuery"`
7375
SortBy string `json:"sortBy"`
7476
SortDirection string `json:"sortDirection"`

src/components/QueryEditor/QueryEditor.test.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,16 @@ describe('QueryEditor', () => {
501501
queryWhenShown: { refId: '', type: QueryTypeValue.TIMESERIES, command: RedisTimeSeries.RANGE },
502502
queryWhenHidden: { refId: '', type: QueryTypeValue.REDIS, command: Redis.INFO },
503503
},
504+
{
505+
name: 'tsGroupByLabel',
506+
getComponent: (wrapper: ShallowComponent) =>
507+
wrapper.findWhere((node) => {
508+
return node.prop('onChange') === wrapper.instance().onTsGroupByLabelChange;
509+
}),
510+
type: 'string',
511+
queryWhenShown: { refId: '', type: QueryTypeValue.TIMESERIES, command: RedisTimeSeries.MRANGE },
512+
queryWhenHidden: { refId: '', type: QueryTypeValue.REDIS, command: Redis.INFO },
513+
},
504514
{
505515
name: 'zrangeQuery',
506516
getComponent: (wrapper: ShallowComponent) =>

src/components/QueryEditor/QueryEditor.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import {
3131
RedisJson,
3232
RedisQuery,
3333
RedisTimeSeries,
34+
Reducers,
35+
ReducerValue,
3436
ZRangeQuery,
3537
ZRangeQueryValue,
3638
} from '../../redis';
@@ -189,6 +191,16 @@ export class QueryEditor extends PureComponent<Props> {
189191
});
190192
};
191193

194+
/**
195+
* Ts Reducer Change
196+
*/
197+
onTsReducerChange = this.createSelectFieldHandler<ReducerValue>('tsReducer');
198+
199+
/**
200+
* Group By Change
201+
*/
202+
onTsGroupByLabelChange = this.createTextFieldHandler('tsGroupByLabel');
203+
192204
/**
193205
* Aggregation change
194206
*/
@@ -332,6 +344,8 @@ export class QueryEditor extends PureComponent<Props> {
332344
streamingInterval,
333345
streamingCapacity,
334346
streamingDataType,
347+
tsGroupByLabel,
348+
tsReducer,
335349
} = this.props.query;
336350
const { onRunQuery, datasource } = this.props;
337351

@@ -717,6 +731,30 @@ export class QueryEditor extends PureComponent<Props> {
717731
</div>
718732
)}
719733

734+
{type === QueryTypeValue.TIMESERIES &&
735+
command &&
736+
CommandParameters.tsGroupBy.includes(command as RedisTimeSeries) && (
737+
<div className="gf-form">
738+
<FormField
739+
labelWidth={8}
740+
inputWidth={10}
741+
value={tsGroupByLabel}
742+
onChange={this.onTsGroupByLabelChange}
743+
label="Group By"
744+
tooltip="The label to group your time-series by for your reduction"
745+
/>
746+
{tsGroupByLabel && (
747+
<Select
748+
options={Reducers}
749+
width={30}
750+
onChange={this.onTsReducerChange}
751+
value={tsReducer}
752+
menuPlacement="bottom"
753+
/>
754+
)}
755+
</div>
756+
)}
757+
720758
<div className="gf-form">
721759
<Switch
722760
label="Streaming"

src/redis/command.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export const CommandParameters = {
7676
zrangeQuery: [Redis.ZRANGE],
7777
path: [RedisJson.TYPE, RedisJson.OBJKEYS, RedisJson.GET, RedisJson.OBJLEN, RedisJson.ARRLEN],
7878
pyFunction: [RedisGears.PYEXECUTE],
79+
tsGroupBy: [RedisTimeSeries.MRANGE],
80+
tsReducer: [RedisTimeSeries.MRANGE],
7981
searchQuery: [RediSearch.SEARCH],
8082
offset: [RediSearch.SEARCH],
8183
returnFields: [RediSearch.SEARCH],

src/redis/time-series.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,19 @@ export enum AggregationValue {
5757
SUM = 'sum',
5858
}
5959

60+
export enum ReducerValue {
61+
AVG = 'avg',
62+
SUM = 'sum',
63+
MIN = 'min',
64+
MAX = 'max',
65+
RANGE = 'range',
66+
COUNT = 'count',
67+
STDP = 'std.p',
68+
STDS = 'std.s',
69+
VARP = 'var.p',
70+
VARS = 'var.s',
71+
}
72+
6073
/**
6174
* Aggregations
6275
*/
@@ -69,3 +82,27 @@ export const Aggregations: Array<SelectableValue<AggregationValue>> = [
6982
{ label: 'Range', description: 'Diff between maximum and minimum in the bucket', value: AggregationValue.RANGE },
7083
{ label: 'Sum', description: 'Summation', value: AggregationValue.SUM },
7184
];
85+
86+
/**
87+
* Reducers
88+
*/
89+
export const Reducers: Array<SelectableValue<ReducerValue>> = [
90+
{ label: 'Avg', description: 'Arithmetic mean of all non-NaN values', value: ReducerValue.AVG },
91+
{ label: 'Sum', description: 'Sum of all non-NaN values', value: ReducerValue.SUM },
92+
{ label: 'Min', description: 'Minimum non-NaN value', value: ReducerValue.MIN },
93+
{ label: 'Max', description: 'Maximum non-NaN value', value: ReducerValue.MAX },
94+
{
95+
label: 'Range',
96+
description: 'Difference between maximum non-Nan value and minimum non-NaN value',
97+
value: ReducerValue.RANGE,
98+
},
99+
{ label: 'Count', description: 'Number of non-NaN values', value: ReducerValue.COUNT },
100+
{
101+
label: 'Std Population',
102+
description: 'Population standard deviation of all non-NaN values',
103+
value: ReducerValue.STDP,
104+
},
105+
{ label: 'Std Sample', description: 'Sample standard deviation of all non-NaN values', value: ReducerValue.STDS },
106+
{ label: 'Var Population', description: 'Population variance of all non-NaN values', value: ReducerValue.VARP },
107+
{ label: 'Var Sample', description: 'Sample variance of all non-NaN values', value: ReducerValue.VARS },
108+
];

src/redis/types.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ZRangeQueryValue } from 'redis';
1+
import { ReducerValue, ZRangeQueryValue } from 'redis';
22
import { DataQuery } from '@grafana/data';
33
import { StreamingDataType } from '../constants';
44
import { InfoSectionValue } from './info';
@@ -97,6 +97,18 @@ export interface RedisQuery extends DataQuery {
9797
*/
9898
aggregation?: AggregationValue;
9999

100+
/**
101+
* The reduction to run to sum-up a group
102+
*
103+
* @type {ReducerValue}
104+
*/
105+
tsReducer?: ReducerValue;
106+
107+
/**
108+
* The label to group time-series by in an TS.MRANGE.
109+
*/
110+
tsGroupByLabel?: string;
111+
100112
/**
101113
* ZRANGE Query
102114
*

0 commit comments

Comments
 (0)