Skip to content

Commit 56497b7

Browse files
committed
fix(reduceSeries): error on out-of-range reduceNode, add tests
Builds on #933 (reduce on series.Name). Adds a bounds guard so an out-of-range reduceNode returns an error instead of panicking, and supports negative indices counted from the end. Adds a reduce package test covering the aliased-name grouping (the #933 fix) and the out-of-range cases, and updates the existing reduceSeries expectations for the name tag now set by CopyNameWithVal.
1 parent 278cb1c commit 56497b7

3 files changed

Lines changed: 130 additions & 5 deletions

File tree

expr/expr_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,8 @@ func TestEvalExpression(t *testing.T) {
258258
},
259259
},
260260
[]*types.MetricData{
261-
types.MakeMetricData("devops.service.server1.filter.received.reduce.asPercent.count", []float64{25, 200, 200}, 1, now32).SetNameTag("devops.service.server1.filter.received.valid.count"),
262-
types.MakeMetricData("devops.service.server2.filter.received.reduce.asPercent.count", []float64{25, 100, 400}, 1, now32).SetNameTag("devops.service.server2.filter.received.valid.count"),
261+
types.MakeMetricData("devops.service.server1.filter.received.reduce.asPercent.count", []float64{25, 200, 200}, 1, now32),
262+
types.MakeMetricData("devops.service.server2.filter.received.reduce.asPercent.count", []float64{25, 100, 400}, 1, now32),
263263
},
264264
},
265265
{
@@ -272,7 +272,7 @@ func TestEvalExpression(t *testing.T) {
272272
},
273273
},
274274
[]*types.MetricData{
275-
types.MakeMetricData("devops.service.server2.filter.received.reduce.asPercent.count", []float64{25, 100, 400}, 1, now32).SetNameTag("devops.service.server2.filter.received.valid.count"),
275+
types.MakeMetricData("devops.service.server2.filter.received.reduce.asPercent.count", []float64{25, 100, 400}, 1, now32),
276276
},
277277
},
278278
{

expr/functions/reduce/function.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package reduce
33
import (
44
"context"
55

6+
"github.com/ansel1/merry"
7+
68
"github.com/go-graphite/carbonapi/expr/helper"
79
"github.com/go-graphite/carbonapi/expr/interfaces"
810
"github.com/go-graphite/carbonapi/expr/types"
@@ -70,8 +72,15 @@ func (f *reduce) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Exp
7072
for _, series := range seriesList {
7173
metric := types.ExtractName(series.Name)
7274
nodes := strings.Split(metric, ".")
73-
reduceNodeKey := nodes[reduceNode]
74-
nodes[reduceNode] = "reduce." + reduceFunction
75+
node := reduceNode
76+
if node < 0 {
77+
node += len(nodes)
78+
}
79+
if node < 0 || node >= len(nodes) {
80+
return nil, merry.WithMessagef(parser.ErrInvalidArg, "reduceNode %d out of range for metric %q", reduceNode, metric)
81+
}
82+
reduceNodeKey := nodes[node]
83+
nodes[node] = "reduce." + reduceFunction
7584
aliasName := strings.Join(nodes, ".")
7685
_, exist := reduceGroups[aliasName]
7786
if !exist {
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package reduce
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/go-graphite/carbonapi/expr/interfaces"
8+
"github.com/go-graphite/carbonapi/expr/metadata"
9+
"github.com/go-graphite/carbonapi/expr/types"
10+
"github.com/go-graphite/carbonapi/pkg/parser"
11+
th "github.com/go-graphite/carbonapi/tests"
12+
13+
"github.com/go-graphite/carbonapi/expr/functions/alias"
14+
"github.com/go-graphite/carbonapi/expr/functions/aliasByNode"
15+
"github.com/go-graphite/carbonapi/expr/functions/asPercent"
16+
)
17+
18+
var (
19+
md []interfaces.FunctionMetadata = New("")
20+
)
21+
22+
func init() {
23+
for _, m := range md {
24+
metadata.RegisterFunction(m.Name, m.F)
25+
}
26+
for _, m := range alias.New("") {
27+
metadata.RegisterFunction(m.Name, m.F)
28+
}
29+
for _, m := range aliasByNode.New("") {
30+
metadata.RegisterFunction(m.Name, m.F)
31+
}
32+
for _, m := range asPercent.New("") {
33+
metadata.RegisterFunction(m.Name, m.F)
34+
}
35+
}
36+
37+
func TestReduce(t *testing.T) {
38+
now32 := int64(time.Now().Unix())
39+
40+
tests := []th.EvalTestItem{
41+
{
42+
// mimics graphite-web test_reduceSeries_asPercent
43+
Target: `reduceSeries(group.server*.*, "asPercent", 2, "bytes_used", "total_bytes")`,
44+
M: map[parser.MetricRequest][]*types.MetricData{
45+
{Metric: "group.server*.*", From: 0, Until: 1}: {
46+
types.MakeMetricData("group.server1.bytes_used", []float64{1}, 1, now32),
47+
types.MakeMetricData("group.server1.total_bytes", []float64{2}, 1, now32),
48+
types.MakeMetricData("group.server2.bytes_used", []float64{3}, 1, now32),
49+
types.MakeMetricData("group.server2.total_bytes", []float64{4}, 1, now32),
50+
},
51+
},
52+
Want: []*types.MetricData{
53+
types.MakeMetricData("group.server1.reduce.asPercent", []float64{50}, 1, now32),
54+
types.MakeMetricData("group.server2.reduce.asPercent", []float64{75}, 1, now32),
55+
},
56+
},
57+
{
58+
// reduceSeries must group by the aliased name, not the original "name" tag
59+
Target: `reduceSeries(aliasByNode(servers.*.cpu.*, 1, 3), "asPercent", 1, "raw_used", "raw_total")`,
60+
M: map[parser.MetricRequest][]*types.MetricData{
61+
{Metric: "servers.*.cpu.*", From: 0, Until: 1}: {
62+
types.MakeMetricData("servers.host1.cpu.raw_used", []float64{1}, 1, now32),
63+
types.MakeMetricData("servers.host1.cpu.raw_total", []float64{2}, 1, now32),
64+
types.MakeMetricData("servers.host2.cpu.raw_used", []float64{3}, 1, now32),
65+
types.MakeMetricData("servers.host2.cpu.raw_total", []float64{4}, 1, now32),
66+
},
67+
},
68+
Want: []*types.MetricData{
69+
types.MakeMetricData("host1.reduce.asPercent", []float64{50}, 1, now32),
70+
types.MakeMetricData("host2.reduce.asPercent", []float64{75}, 1, now32),
71+
},
72+
},
73+
}
74+
75+
for _, tt := range tests {
76+
t.Run(tt.Target, func(t *testing.T) {
77+
eval := th.EvaluatorFromFuncWithMetadata(metadata.FunctionMD.Functions)
78+
th.TestEvalExpr(t, eval, &tt)
79+
})
80+
}
81+
}
82+
83+
func TestReduceErrors(t *testing.T) {
84+
now32 := int64(time.Now().Unix())
85+
86+
tests := []th.EvalTestItemWithError{
87+
{
88+
// reduceNode out of range for the series name must not panic
89+
Target: `reduceSeries(group.*, "asPercent", 4, "bytes_used", "total_bytes")`,
90+
M: map[parser.MetricRequest][]*types.MetricData{
91+
{Metric: "group.*", From: 0, Until: 1}: {
92+
types.MakeMetricData("group.bytes_used", []float64{1}, 1, now32),
93+
types.MakeMetricData("group.total_bytes", []float64{2}, 1, now32),
94+
},
95+
},
96+
Error: parser.ErrInvalidArg,
97+
},
98+
{
99+
Target: `reduceSeries(group.*, "asPercent", -5, "bytes_used", "total_bytes")`,
100+
M: map[parser.MetricRequest][]*types.MetricData{
101+
{Metric: "group.*", From: 0, Until: 1}: {
102+
types.MakeMetricData("group.bytes_used", []float64{1}, 1, now32),
103+
types.MakeMetricData("group.total_bytes", []float64{2}, 1, now32),
104+
},
105+
},
106+
Error: parser.ErrInvalidArg,
107+
},
108+
}
109+
110+
for _, tt := range tests {
111+
t.Run(tt.Target, func(t *testing.T) {
112+
eval := th.EvaluatorFromFuncWithMetadata(metadata.FunctionMD.Functions)
113+
th.TestEvalExprWithError(t, eval, &tt)
114+
})
115+
}
116+
}

0 commit comments

Comments
 (0)