Skip to content

Commit e683b99

Browse files
Merge branch 'main' into cedwards/improve-error-handling
2 parents 5a92d2b + 4d54761 commit e683b99

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
@@ -66,6 +66,10 @@ func (f *exponentialMovingAverage) Do(ctx context.Context, e parser.Expr, from,
6666
var results []*types.MetricData
6767
windowSize := n
6868

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

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

8589
var vals []float64
8690

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

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

103108
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)