Skip to content
This repository was archived by the owner on Aug 23, 2023. It is now read-only.

Commit 001015d

Browse files
authored
Merge pull request #1863 from grafana/add-sum-series-with-wildcards
Adds aggregateWithWildcards (and sum / multiply / avg)
2 parents 3b20cae + ec27748 commit 001015d

7 files changed

+325
-79
lines changed

api/models/series.go

+9
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,15 @@ func (s *Series) buildTargetFromTags() {
201201
s.Target = buf.String()
202202
}
203203

204+
// SeriesCopy returns a deep copy of the series slice
205+
func SeriesCopy(series []Series) []Series {
206+
out := make([]Series, len(series))
207+
for i, s := range series {
208+
out[i] = s.Copy(nil)
209+
}
210+
return out
211+
}
212+
204213
// Copy returns a deep copy.
205214
// The returned value does not link to the same memory space for any of the properties
206215
func (s Series) Copy(emptyDatapoints []schema.Point) Series {

docs/graphite.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ See also:
4141
| absolute | | Stable |
4242
| aggregate | | Stable |
4343
| aggregateLine | | No |
44-
| aggregateWithWildcards | | No |
44+
| aggregateWithWildcards | | Stable |
4545
| alias(seriesList, alias) seriesList | | Stable |
4646
| aliasByMetric | | Stable |
4747
| aliasByNode(seriesList, nodeList) seriesList | aliasByTags | Stable |
@@ -56,7 +56,7 @@ See also:
5656
| averageBelow | | Stable |
5757
| averageOutsidePercentile | | No |
5858
| averageSeries(seriesLists) series | avg | Stable |
59-
| averageSeriesWithWildcards | | No |
59+
| averageSeriesWithWildcards | | Stable |
6060
| cactiStyle | | No |
6161
| changed | | No |
6262
| color | | No |
@@ -123,7 +123,7 @@ See also:
123123
| movingSum | | No |
124124
| movingWindow | | No |
125125
| multiplySeries(seriesList) series | | Stable |
126-
| multiplySeriesWithWildcards | | No |
126+
| multiplySeriesWithWildcards | | Stable |
127127
| nonNegatievDerivative(seriesList, maxValue) seriesList | | Stable |
128128
| nPercentile | | No |
129129
| offset | | No |
@@ -164,7 +164,7 @@ See also:
164164
| substr | | No |
165165
| summarize(seriesList) seriesList | | Stable |
166166
| sumSeries(seriesLists) series | sum | Stable |
167-
| sumSeriesWithWildcards | | No |
167+
| sumSeriesWithWildcards | | Stable |
168168
| threshold | | No |
169169
| timeFunction | time | No |
170170
| timeShift | | No |

expr/func_aggregatewithwildcards.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package expr
2+
3+
import (
4+
"github.com/grafana/metrictank/api/models"
5+
)
6+
7+
type FuncAggregateWithWildcards struct {
8+
in GraphiteFunc
9+
fn string
10+
nodes []expr
11+
}
12+
13+
func NewAggregateWithWildcardsConstructor(fn string) func() GraphiteFunc {
14+
return func() GraphiteFunc {
15+
return &FuncAggregateWithWildcards{fn: fn}
16+
}
17+
}
18+
19+
func NewAggregateWithWildcards() GraphiteFunc {
20+
return &FuncAggregateWithWildcards{}
21+
}
22+
23+
func (s *FuncAggregateWithWildcards) Signature() ([]Arg, []Arg) {
24+
if s.fn == "" {
25+
return []Arg{
26+
ArgSeriesList{val: &s.in},
27+
ArgString{key: "func", val: &s.fn, validator: []Validator{IsAggFunc}},
28+
ArgStringsOrInts{key: "positions", val: &s.nodes, validator: []Validator{IntZeroOrPositive}},
29+
}, []Arg{ArgSeriesList{}}
30+
}
31+
return []Arg{
32+
ArgSeriesList{val: &s.in},
33+
ArgStringsOrInts{key: "positions", val: &s.nodes, validator: []Validator{IntZeroOrPositive}},
34+
}, []Arg{ArgSeriesList{}}
35+
}
36+
37+
func (s *FuncAggregateWithWildcards) Context(context Context) Context {
38+
return context
39+
}
40+
41+
func (s *FuncAggregateWithWildcards) Exec(dataMap DataMap) ([]models.Series, error) {
42+
series, err := s.in.Exec(dataMap)
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
if len(series) == 0 {
48+
return series, nil
49+
}
50+
51+
agg := seriesAggregator{function: getCrossSeriesAggFunc(s.fn), name: s.fn}
52+
53+
groups := make(map[string][]models.Series)
54+
var keys []string
55+
56+
for _, serie := range series {
57+
key := filterNodesByPositions(serie.Target, s.nodes)
58+
_, ok := groups[key]
59+
if !ok {
60+
keys = append(keys, key)
61+
}
62+
groups[key] = append(groups[key], serie)
63+
}
64+
out := make([]models.Series, 0, len(groups))
65+
66+
for _, key := range keys {
67+
res, err := aggregate(dataMap, groups[key], []string{key}, agg, 0)
68+
if err != nil {
69+
return nil, err
70+
}
71+
res[0].Target = key
72+
res[0].QueryPatt = key
73+
out = append(out, res...)
74+
}
75+
76+
return out, nil
77+
}
+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package expr
2+
3+
import (
4+
"testing"
5+
6+
"github.com/grafana/metrictank/api/models"
7+
)
8+
9+
func TestAggregateWithWildcardsZero(t *testing.T) {
10+
f := makeAggregateWithWildcards("sum", []models.Series{}, 0, []expr{{etype: etInt, int: 1}})
11+
got, err := f.Exec(make(map[Req][]models.Series))
12+
if err := equalOutput([]models.Series{}, got, nil, err); err != nil {
13+
t.Fatal(err)
14+
}
15+
}
16+
17+
func TestAggregateWithWildcardsIdentity(t *testing.T) {
18+
testAggregateWithWildcards(
19+
"identity",
20+
"average",
21+
[]models.Series{
22+
getSeriesNamed("single", a),
23+
},
24+
getSeriesNamed("single", a),
25+
t,
26+
0,
27+
[]expr{},
28+
)
29+
testAggregateWithWildcards(
30+
"identity",
31+
"sum",
32+
[]models.Series{
33+
getSeriesNamed("single", a),
34+
},
35+
getSeriesNamed("single", a),
36+
t,
37+
0,
38+
[]expr{},
39+
)
40+
}
41+
func TestAggregateWithWildcardsAvg(t *testing.T) {
42+
testAggregateWithWildcards(
43+
"avgSeries",
44+
"average",
45+
[]models.Series{
46+
getSeriesNamed("foo.fighters.bar", a),
47+
getSeriesNamed("foo.rock.bar", b),
48+
},
49+
getSeriesNamed("foo.bar", avgab),
50+
t,
51+
0,
52+
[]expr{{etype: etInt, int: 1}},
53+
)
54+
}
55+
56+
func TestAggregateWithWildcardsSum(t *testing.T) {
57+
testAggregateWithWildcards(
58+
"sumSeries",
59+
"sum",
60+
[]models.Series{
61+
getSeriesNamed("foo.hello.bar", a),
62+
getSeriesNamed("foo.world.bar", b),
63+
},
64+
getSeriesNamed("foo.bar", sumab),
65+
t,
66+
0,
67+
[]expr{{etype: etInt, int: 1}},
68+
)
69+
}
70+
71+
func TestAggregateWithWildcardsMultiply(t *testing.T) {
72+
testAggregateWithWildcards(
73+
"multiplySeries",
74+
"multiply",
75+
[]models.Series{
76+
getSeriesNamed("foo.hello.world.bar", a),
77+
getSeriesNamed("foo.something.else.bar", b),
78+
},
79+
getSeriesNamed("foo.bar", multab),
80+
t,
81+
0,
82+
[]expr{
83+
{etype: etInt, int: 1},
84+
{etype: etInt, int: 2},
85+
},
86+
)
87+
}
88+
89+
func makeAggregateWithWildcards(agg string, in []models.Series, xFilesFactor float64, nodes []expr) GraphiteFunc {
90+
f := NewAggregateWithWildcardsConstructor(agg)()
91+
aggww := f.(*FuncAggregateWithWildcards)
92+
aggww.in = NewMock(in)
93+
aggww.nodes = nodes
94+
return f
95+
}
96+
97+
func testAggregateWithWildcards(name, agg string, in []models.Series, out models.Series, t *testing.T, xFilesFactor float64, nodes []expr) {
98+
inputCopy := models.SeriesCopy(in) // to later verify that it is unchanged
99+
100+
f := makeAggregateWithWildcards(agg, in, xFilesFactor, nodes)
101+
102+
dataMap := initDataMap(in)
103+
104+
got, err := f.Exec(dataMap)
105+
if err := equalOutput([]models.Series{out}, got, nil, err); err != nil {
106+
t.Fatalf("Case %s: %s", name, err)
107+
}
108+
109+
t.Run("DidNotModifyInput", func(t *testing.T) {
110+
if err := equalOutput(inputCopy, in, nil, nil); err != nil {
111+
t.Fatalf("Case %s: Input was modified, err = %s", name, err)
112+
}
113+
})
114+
115+
t.Run("DoesNotDoubleReturnPoints", func(t *testing.T) {
116+
if err := dataMap.CheckForOverlappingPoints(); err != nil {
117+
t.Fatalf("Case %s: Point slices in datamap overlap, err = %s", name, err)
118+
}
119+
})
120+
t.Run("OutputIsCanonical", func(t *testing.T) {
121+
for i, s := range got {
122+
if !s.IsCanonical() {
123+
t.Fatalf("Case %s: output series %d is not canonical: %v", name, i, s)
124+
}
125+
}
126+
})
127+
128+
}

0 commit comments

Comments
 (0)