Skip to content

Commit 4d54761

Browse files
carrieedwardsnpazosmendezVladimir Smirnov
authored
Merge upstream changes (#120)
* Fix exponentialMovingAverage panic The panic was happening when the window size was greater than the length of the values. Now if that happens no panics occur and the average is returned. This matches graphiteweb. * Add support for Graphite web toLowerCase function * Add tests for toLowerCase function * Update glue.go * Update COMPATIBILITY.md to show support of toLowerCase * Add support for Graphite web toUpperCase function * Add tests for toUpperCase function * Update glue.go * Update COMPATIBILITY.md to include toUpperCase function * Add support for Graphite web sumSeriesLists function * Add tests for sumSeriesLists * Update glue.go * Update COMPATIBILITY.md to include sumSeriesLists * Fix problems that I accidentally introduced while trying to resolve merge conflict * Another attempt to fix previous problem Apparantly there is a directory that gen.go wasn't expecting and `go generate` in expr/functions produced `glue.go` file that didn't compile fix both generator and glue * Adds Drone and Github workflow for Grafana's fork This commit adds the files that are specific to Grafana's fork that are not intended to be included in the upstream. This files are already present in the commit history (they were deleted), but this is an attempt of a fresh start on top of the upstream's `main`. At the moment, this should the only commit we expect to skip when sending our changes. Co-authored-by: Nicolás Pazos <[email protected]> Co-authored-by: Vladimir Smirnov <[email protected]>
1 parent 811adef commit 4d54761

File tree

11 files changed

+430
-14
lines changed

11 files changed

+430
-14
lines changed

COMPATIBILITY.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ limit: default value mismatch: got "INF", should be "Infinity" |
149149
| removeAbovePercentile | n: type mismatch: got integer, should be float |
150150
| removeAboveValue | n: type mismatch: got integer, should be float |
151151
| removeBelowPercentile | n: type mismatch: got integer, should be float |
152-
| removeBelowValue | n: type mismatch: got integer, should be float |
152+
| removeBelowValue | n: type mismatch: got integer, sho/usr/bin/nvidia-dockeruld be float |
153153
| round | precision: default value mismatch: got (empty), should be 0 |
154154
| scaleToSeconds | seconds: type mismatch: got integer, should be float |
155155
| smartSummarize | func: different amount of parameters, `[current rangeOf]` are missing
@@ -162,6 +162,7 @@ reverse: default value mismatch: got (empty), should be false |
162162
| timeShift | parameter not supported: alignDst |
163163
| timeSlice | endSliceAt: type mismatch: got interval, should be date |
164164

165+
## Supported functions
165166
## Supported functions
166167
| Function | Carbonapi-only |
167168
| :-------------|:--------------------------------------------------------- |
@@ -293,6 +294,7 @@ reverse: default value mismatch: got (empty), should be false |
293294
| substr(seriesList, start=0, stop=0) | no |
294295
| sum(*seriesLists) | no |
295296
| sumSeries(*seriesLists) | no |
297+
| sumSeriesLists(seriesListFirstPos, seriesListSecondPos) | no |
296298
| sumSeriesWithWildcards(seriesList, *position) | no |
297299
| summarize(seriesList, intervalString, func='sum', alignToFrom=False) | no |
298300
| threshold(value, label=None, color=None) | no |
@@ -301,6 +303,8 @@ reverse: default value mismatch: got (empty), should be false |
301303
| timeShift(seriesList, timeShift, resetEnd=True, alignDST=False) | no |
302304
| timeSlice(seriesList, startSliceAt, endSliceAt='now') | no |
303305
| timeStack(seriesList, timeShiftUnit='1d', timeShiftStart=0, timeShiftEnd=7) | no |
306+
| toLowerCase(seriesList) | no |
307+
| toUpperCase(seriesList, *pos) | no |
304308
| transformNull(seriesList, default=0, referenceSeries=None) | no |
305309
| unique(*seriesLists) | no |
306310
| useSeriesAbove(seriesList, value, search, replace) | no |

expr/functions/exponentialMovingAverage/function.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ func (f *exponentialMovingAverage) Do(ctx context.Context, e parser.Expr, from,
6565
var results []*types.MetricData
6666
windowSize := n
6767

68+
if windowSize < 1 {
69+
return nil, fmt.Errorf("invalid window size %d", windowSize)
70+
}
71+
6872
start := from
6973

7074
arg, err := helper.GetSeriesArg(ctx, e.Args()[0], start, until, values)
@@ -83,20 +87,21 @@ func (f *exponentialMovingAverage) Do(ctx context.Context, e parser.Expr, from,
8387

8488
var vals []float64
8589

86-
if windowSize < 1 && windowSize > len(a.Values) {
87-
return nil, fmt.Errorf("invalid window size %d", windowSize)
88-
}
89-
90-
ema := consolidations.AggMean(a.Values[:windowSize])
90+
if windowSize > len(a.Values) {
91+
mean := consolidations.AggMean(a.Values)
92+
vals = append(vals, helper.SafeRound(mean, 6))
93+
} else {
94+
ema := consolidations.AggMean(a.Values[:windowSize])
9195

92-
vals = append(vals, helper.SafeRound(ema, 6))
93-
for _, v := range a.Values[windowSize:] {
94-
if math.IsNaN(v) {
95-
vals = append(vals, math.NaN())
96-
continue
97-
}
98-
ema = constant*v + (1-constant)*ema
9996
vals = append(vals, helper.SafeRound(ema, 6))
97+
for _, v := range a.Values[windowSize:] {
98+
if math.IsNaN(v) {
99+
vals = append(vals, math.NaN())
100+
continue
101+
}
102+
ema = constant*v + (1-constant)*ema
103+
vals = append(vals, helper.SafeRound(ema, 6))
104+
}
100105
}
101106

102107
r.Tags[e.Target()] = fmt.Sprintf("%d", windowSize)

expr/functions/exponentialMovingAverage/function_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ func TestExponentialMovingAverage(t *testing.T) {
3333
types.MakeMetricData("exponentialMovingAverage(metric1,3)", []float64{4, 6, 9, 11.5, 13.75, 15.875, 17.9375}, 1, 0),
3434
},
3535
},
36+
{
37+
// if the window is larger than the length of the values, the result should just be the average.
38+
// this matches graphiteweb's behavior
39+
"exponentialMovingAverage(metric1,100)",
40+
map[parser.MetricRequest][]*types.MetricData{
41+
{"metric1", 0, 1}: {types.MakeMetricData("metric1", []float64{1, 2, 3}, 1, startTime)},
42+
},
43+
[]*types.MetricData{
44+
types.MakeMetricData("exponentialMovingAverage(metric1,100)", []float64{2}, 1, 0),
45+
},
46+
},
3647
}
3748

3849
for _, tt := range tests {

expr/functions/glue.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ import (
110110
"github.com/go-graphite/carbonapi/expr/functions/timeShiftByMetric"
111111
"github.com/go-graphite/carbonapi/expr/functions/timeSlice"
112112
"github.com/go-graphite/carbonapi/expr/functions/timeStack"
113+
"github.com/go-graphite/carbonapi/expr/functions/toLowerCase"
114+
"github.com/go-graphite/carbonapi/expr/functions/toUpperCase"
113115
"github.com/go-graphite/carbonapi/expr/functions/transformNull"
114116
"github.com/go-graphite/carbonapi/expr/functions/tukey"
115117
"github.com/go-graphite/carbonapi/expr/functions/unique"
@@ -234,6 +236,8 @@ func New(configs map[string]string) {
234236
{name: "timeShiftByMetric", filename: "timeShiftByMetric", order: timeShiftByMetric.GetOrder(), f: timeShiftByMetric.New},
235237
{name: "timeSlice", filename: "timeSlice", order: timeSlice.GetOrder(), f: timeSlice.New},
236238
{name: "timeStack", filename: "timeStack", order: timeStack.GetOrder(), f: timeStack.New},
239+
{name: "toLowerCase", filename: "toLowerCase", order: toLowerCase.GetOrder(), f: toLowerCase.New},
240+
{name: "toUpperCase", filename: "toUpperCase", order: toUpperCase.GetOrder(), f: toUpperCase.New},
237241
{name: "transformNull", filename: "transformNull", order: transformNull.GetOrder(), f: transformNull.New},
238242
{name: "tukey", filename: "tukey", order: tukey.GetOrder(), f: tukey.New},
239243
{name: "unique", filename: "unique", order: unique.GetOrder(), f: unique.New},

expr/functions/seriesList/function.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,18 @@ func GetOrder() interfaces.Order {
2323
func New(configFile string) []interfaces.FunctionMetadata {
2424
res := make([]interfaces.FunctionMetadata, 0)
2525
f := &seriesList{}
26-
functions := []string{"divideSeriesLists", "diffSeriesLists", "multiplySeriesLists", "powSeriesLists"}
26+
functions := []string{"divideSeriesLists", "diffSeriesLists", "multiplySeriesLists", "powSeriesLists", "sumSeriesLists"}
2727
for _, n := range functions {
2828
res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
2929
}
3030
return res
3131
}
3232

3333
func (f *seriesList) Do(ctx context.Context, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
34+
if e.ArgsLen() < 2 {
35+
return nil, parser.ErrMissingArgument
36+
}
37+
3438
useConstant := false
3539
useDenom := false
3640

@@ -85,6 +89,8 @@ func (f *seriesList) Do(ctx context.Context, e parser.Expr, from, until int64, v
8589
compute = func(l, r float64) float64 { return l - r }
8690
case "powSeriesLists":
8791
compute = math.Pow
92+
case "sumSeriesLists":
93+
compute = func(l, r float64) float64 { return l + r }
8894
}
8995

9096
if useConstant {
@@ -306,5 +312,33 @@ func (f *seriesList) Description() map[string]types.FunctionDescription {
306312
TagsChange: true, // name tag changed
307313
ValuesChange: true, // values changed
308314
},
315+
"sumSeriesLists": {
316+
Description: "Iterates over a two lists and subtracts series lists 2 through n from series 1 list1[0] to list2[0], list1[1] to list2[1] and so on. \n The lists will need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing Example:\n\n.. code-block:: none\n\n &target=sumSeriesLists(mining.{carbon,graphite,diamond}.extracted,mining.{carbon,graphite,diamond}.shipped)\n\n",
317+
Function: "sumSeriesLists(seriesListFirstPos, seriesListSecondPos)",
318+
Group: "Combine",
319+
Module: "graphite.render.functions.custom",
320+
Name: "sumSeriesLists",
321+
Params: []types.FunctionParam{
322+
{
323+
Name: "seriesListFirstPos",
324+
Required: true,
325+
Type: types.SeriesList,
326+
},
327+
{
328+
Name: "seriesListSecondPos",
329+
Required: true,
330+
Type: types.SeriesList,
331+
},
332+
{
333+
Name: "default",
334+
Required: false,
335+
Type: types.Float,
336+
},
337+
},
338+
SeriesChange: true, // function aggregate metrics or change series items count
339+
NameChange: true, // name changed
340+
TagsChange: true, // name tag changed
341+
ValuesChange: true, // values changed
342+
},
309343
}
310344
}

expr/functions/seriesList/function_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ func TestFunction(t *testing.T) {
3535
[]*types.MetricData{types.MakeMetricData("diffSeries(metric1,metric2)",
3636
[]float64{-1, math.NaN(), math.NaN(), math.NaN(), 4, 6}, 1, now32)},
3737
},
38+
{
39+
"sumSeriesLists(metric1,metric2)",
40+
map[parser.MetricRequest][]*types.MetricData{
41+
{"metric1", 0, 1}: {types.MakeMetricData("metric1", []float64{1, math.NaN(), math.NaN(), 3, 4, 12}, 1, now32)},
42+
{"metric2", 0, 1}: {types.MakeMetricData("metric2", []float64{2, math.NaN(), 3, math.NaN(), 0, 6}, 1, now32)},
43+
},
44+
[]*types.MetricData{types.MakeMetricData("sumSeries(metric1,metric2)",
45+
[]float64{3, math.NaN(), math.NaN(), math.NaN(), 4, 18}, 1, now32)},
46+
},
3847
}
3948

4049
for _, tt := range tests {
@@ -109,6 +118,20 @@ func TestSeriesListMultiReturn(t *testing.T) {
109118
"diffSeries(metric1,metric1)": {types.MakeMetricData("diffSeries(metric1,metric1)", []float64{0, 0, 0, 0, 0}, 1, now32)},
110119
},
111120
},
121+
{
122+
"sumSeriesLists(metric[12],metric[12])",
123+
map[parser.MetricRequest][]*types.MetricData{
124+
{"metric[12]", 0, 1}: {
125+
types.MakeMetricData("metric1", []float64{1, 2, 3, 4, 5}, 1, now32),
126+
types.MakeMetricData("metric2", []float64{2, 4, 6, 8, 10}, 1, now32),
127+
},
128+
},
129+
"sumSeriesListSameGroups",
130+
map[string][]*types.MetricData{
131+
"sumSeries(metric1,metric1)": {types.MakeMetricData("sumSeries(metric1,metric1)", []float64{2, 4, 6, 8, 10}, 1, now32)},
132+
"sumSeries(metric2,metric2)": {types.MakeMetricData("sumSeries(metric2,metric2)", []float64{4, 8, 12, 16, 20}, 1, now32)},
133+
},
134+
},
112135
}
113136

114137
for _, tt := range tests {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package toLowerCase
2+
3+
import (
4+
"context"
5+
"github.com/go-graphite/carbonapi/expr/helper"
6+
"strings"
7+
8+
"github.com/go-graphite/carbonapi/expr/interfaces"
9+
"github.com/go-graphite/carbonapi/expr/types"
10+
"github.com/go-graphite/carbonapi/pkg/parser"
11+
)
12+
13+
type toLowerCase struct {
14+
interfaces.FunctionBase
15+
}
16+
17+
func GetOrder() interfaces.Order {
18+
return interfaces.Any
19+
}
20+
21+
func New(configFile string) []interfaces.FunctionMetadata {
22+
res := make([]interfaces.FunctionMetadata, 0)
23+
f := &toLowerCase{}
24+
functions := []string{"toLowerCase"}
25+
for _, n := range functions {
26+
res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
27+
}
28+
return res
29+
}
30+
31+
// toLowerCase(seriesList, *pos)
32+
func (f *toLowerCase) Do(ctx context.Context, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
33+
args, err := helper.GetSeriesArg(ctx, e.Args()[0], from, until, values)
34+
if err != nil {
35+
return nil, err
36+
}
37+
38+
var pos []int
39+
40+
if e.ArgsLen() >= 2 {
41+
pos, err = e.GetIntArgs(1)
42+
if err != nil {
43+
return nil, err
44+
}
45+
}
46+
47+
results := make([]*types.MetricData, 0, len(args)+1)
48+
49+
for _, a := range args {
50+
r := a.CopyLink()
51+
52+
if len(pos) == 0 {
53+
r.Name = strings.ToLower(a.Name)
54+
} else {
55+
for _, i := range pos {
56+
if i < 0 { // Handle negative indices by indexing backwards
57+
i = len(r.Name) + i
58+
}
59+
lowered := strings.ToLower(string(r.Name[i]))
60+
r.Name = r.Name[:i] + lowered + r.Name[i+1:]
61+
}
62+
}
63+
64+
results = append(results, r)
65+
}
66+
67+
return results, nil
68+
}
69+
70+
// Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
71+
func (f *toLowerCase) Description() map[string]types.FunctionDescription {
72+
return map[string]types.FunctionDescription{
73+
"toLowerCase": {
74+
Description: "Takes one metric or a wildcard seriesList and lowers the case of each letter. \n Optionally, a letter position to lower case can be specified, in which case only the letter at the specified position gets lower-cased.\n The position parameter may be given multiple times. The position parameter may be negative to define a position relative to the end of the metric name.",
75+
Function: "toLowerCase(seriesList, *pos)",
76+
Group: "Alias",
77+
Module: "graphite.render.functions",
78+
Name: "toLowerCase",
79+
Params: []types.FunctionParam{
80+
{
81+
Name: "seriesList",
82+
Required: true,
83+
Type: types.SeriesList,
84+
},
85+
{
86+
Multiple: true,
87+
Name: "pos",
88+
Type: types.Node,
89+
Required: false,
90+
},
91+
},
92+
NameChange: true, // name changed
93+
ValuesChange: true, // values changed
94+
},
95+
}
96+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package toLowerCase
2+
3+
import (
4+
"math"
5+
"testing"
6+
"time"
7+
8+
"github.com/go-graphite/carbonapi/expr/helper"
9+
"github.com/go-graphite/carbonapi/expr/metadata"
10+
"github.com/go-graphite/carbonapi/expr/types"
11+
"github.com/go-graphite/carbonapi/pkg/parser"
12+
th "github.com/go-graphite/carbonapi/tests"
13+
)
14+
15+
func init() {
16+
md := New("")
17+
evaluator := th.EvaluatorFromFunc(md[0].F)
18+
metadata.SetEvaluator(evaluator)
19+
helper.SetEvaluator(evaluator)
20+
for _, m := range md {
21+
metadata.RegisterFunction(m.Name, m.F)
22+
}
23+
}
24+
25+
func TestToLowerCaseFunction(t *testing.T) {
26+
now32 := int64(time.Now().Unix())
27+
28+
tests := []th.EvalTestItem{
29+
{
30+
"toLowerCase(METRIC.TEST.FOO)",
31+
map[parser.MetricRequest][]*types.MetricData{
32+
{"METRIC.TEST.FOO", 0, 1}: {types.MakeMetricData("METRIC.TEST.FOO", []float64{1, 2, 0, 7, 8, 20, 30, math.NaN()}, 1, now32)},
33+
},
34+
[]*types.MetricData{types.MakeMetricData("metric.test.foo",
35+
[]float64{1, 2, 0, 7, 8, 20, 30, math.NaN()}, 1, now32)},
36+
},
37+
{
38+
"toLowerCase(METRIC.TEST.FOO,7)",
39+
map[parser.MetricRequest][]*types.MetricData{
40+
{"METRIC.TEST.FOO", 0, 1}: {types.MakeMetricData("METRIC.TEST.FOO", []float64{1, 2, 0, 7, 8, 20, 30, math.NaN()}, 1, now32)},
41+
},
42+
[]*types.MetricData{types.MakeMetricData("METRIC.tEST.FOO",
43+
[]float64{1, 2, 0, 7, 8, 20, 30, math.NaN()}, 1, now32)},
44+
},
45+
{
46+
"toLowerCase(METRIC.TEST.FOO,-3)",
47+
map[parser.MetricRequest][]*types.MetricData{
48+
{"METRIC.TEST.FOO", 0, 1}: {types.MakeMetricData("METRIC.TEST.FOO", []float64{1, 2, 0, 7, 8, 20, 30, math.NaN()}, 1, now32)},
49+
},
50+
[]*types.MetricData{types.MakeMetricData("METRIC.TEST.fOO",
51+
[]float64{1, 2, 0, 7, 8, 20, 30, math.NaN()}, 1, now32)},
52+
},
53+
{
54+
"toLowerCase(METRIC.TEST.FOO,0,7,12)",
55+
map[parser.MetricRequest][]*types.MetricData{
56+
{"METRIC.TEST.FOO", 0, 1}: {types.MakeMetricData("METRIC.TEST.FOO", []float64{1, 2, 0, 7, 8, 20, 30, math.NaN()}, 1, now32)},
57+
},
58+
[]*types.MetricData{types.MakeMetricData("mETRIC.tEST.fOO",
59+
[]float64{1, 2, 0, 7, 8, 20, 30, math.NaN()}, 1, now32)},
60+
},
61+
}
62+
63+
for _, tt := range tests {
64+
testName := tt.Target
65+
t.Run(testName, func(t *testing.T) {
66+
th.TestEvalExpr(t, &tt)
67+
})
68+
}
69+
}

0 commit comments

Comments
 (0)