Browse Source

Merge branch 'master' into master

proffust 5 months ago
parent
commit
942989dd2f
No account linked to committer's email address

+ 0
- 7
COMPATIBILITY.md View File

@@ -144,19 +144,12 @@ _When `format=png`_ (default if not specified)
144 144
 ### Partly supported functions
145 145
 | Function                 | Incompatibilities                              |
146 146
 | :------------------------|:---------------------------------------------- |
147
-| consolidateBy | consolidationFunc: different amount of parameters, `[avg_zero]` are missing |
148
-| groupByNode | callback: different amount of parameters, `[avg_zero]` are missing |
149
-| groupByNodes | callback: different amount of parameters, `[avg_zero]` are missing |
150
-| groupByTags | callback: different amount of parameters, `[avg_zero]` are missing |
151 147
 | holtWintersAberration | parameter not supported: seasonality |
152 148
 | holtWintersConfidenceBands | parameter not supported: seasonality |
153 149
 | holtWintersForecast | parameter not supported: seasonality |
154
-| legendValue | valuesTypes: different amount of parameters, `[avg_zero]` are missing |
155 150
 | nonNegativeDerivative | parameter not supported: minValue |
156 151
 | perSecond | parameter not supported: minValue |
157
-| summarize | func: different amount of parameters, `[avg_zero]` are missing |
158 152
 | timeShift | parameter not supported: alignDst |
159
-| transformNull | parameter not supported: referenceSeries |
160 153
 | useSeriesAbove | value: type mismatch: got "integer", should be "string" |
161 154
 
162 155
 ## Supported functions

+ 0
- 24
expr/expr_test.go View File

@@ -685,17 +685,6 @@ func TestEvalExpression(t *testing.T) {
685 685
 			[]*types.MetricData{types.MakeMetricData("rangeOfSeries(metric*)",
686 686
 				[]float64{1, math.NaN(), math.NaN(), 12, 5, 6, 20}, 1, now32)},
687 687
 		},
688
-		{
689
-			parser.NewExpr("transformNull",
690
-
691
-				"metric1",
692
-			),
693
-			map[parser.MetricRequest][]*types.MetricData{
694
-				{"metric1", 0, 1}: {types.MakeMetricData("metric1", []float64{1, math.NaN(), math.NaN(), 3, 4, 12}, 1, now32)},
695
-			},
696
-			[]*types.MetricData{types.MakeMetricData("transformNull(metric1)",
697
-				[]float64{1, 0, 0, 3, 4, 12}, 1, now32)},
698
-		},
699 688
 		{
700 689
 			parser.NewExpr("reduceSeries",
701 690
 				// list of arguments
@@ -736,19 +725,6 @@ func TestEvalExpression(t *testing.T) {
736 725
 				types.MakeMetricData("devops.service.server2.filter.received.reduce.asPercent.count", []float64{25, 100, 400}, 1, now32),
737 726
 			},
738 727
 		},
739
-		{
740
-			parser.NewExpr("transformNull",
741
-				"metric1",
742
-				parser.NamedArgs{
743
-					"default": 5,
744
-				},
745
-			),
746
-			map[parser.MetricRequest][]*types.MetricData{
747
-				{"metric1", 0, 1}: {types.MakeMetricData("metric1", []float64{1, math.NaN(), math.NaN(), 3, 4, 12}, 1, now32)},
748
-			},
749
-			[]*types.MetricData{types.MakeMetricData("transformNull(metric1,5)",
750
-				[]float64{1, 5, 5, 3, 4, 12}, 1, now32)},
751
-		},
752 728
 		{
753 729
 			parser.NewExpr("highestMax",
754 730
 

+ 2
- 9
expr/functions/consolidateBy/function.go View File

@@ -65,15 +65,8 @@ func (f *consolidateBy) Description() map[string]types.FunctionDescription {
65 65
 					Type:     types.SeriesList,
66 66
 				},
67 67
 				{
68
-					Name: "consolidationFunc",
69
-					Options: []string{
70
-						"max",
71
-						"min",
72
-						"sum",
73
-						"average",
74
-						"first",
75
-						"last",
76
-					},
68
+					Name:     "consolidationFunc",
69
+					Options:  types.AvailableConsolidationFuncs(),
77 70
 					Required: true,
78 71
 					Type:     types.String,
79 72
 				},

+ 5
- 29
expr/functions/groupByNode/function.go View File

@@ -137,21 +137,9 @@ func (f *groupByNode) Description() map[string]types.FunctionDescription {
137 137
 					Type:     types.NodeOrTag,
138 138
 				},
139 139
 				{
140
-					Default: types.NewSuggestion("average"),
141
-					Name:    "callback",
142
-					Options: []string{
143
-						"average",
144
-						"count",
145
-						"diff",
146
-						"last",
147
-						"max",
148
-						"median",
149
-						"min",
150
-						"multiply",
151
-						"range",
152
-						"stddev",
153
-						"sum",
154
-					},
140
+					Default:  types.NewSuggestion("average"),
141
+					Name:     "callback",
142
+					Options:  helper.AvailableSummarizers,
155 143
 					Required: true,
156 144
 					Type:     types.AggFunc,
157 145
 				},
@@ -170,20 +158,8 @@ func (f *groupByNode) Description() map[string]types.FunctionDescription {
170 158
 					Type:     types.SeriesList,
171 159
 				},
172 160
 				{
173
-					Name: "callback",
174
-					Options: []string{
175
-						"average",
176
-						"count",
177
-						"diff",
178
-						"last",
179
-						"max",
180
-						"median",
181
-						"min",
182
-						"multiply",
183
-						"range",
184
-						"stddev",
185
-						"sum",
186
-					},
161
+					Name:     "callback",
162
+					Options:  helper.AvailableSummarizers,
187 163
 					Required: true,
188 164
 					Type:     types.AggFunc,
189 165
 				},

+ 2
- 14
expr/functions/groupByTags/function.go View File

@@ -115,20 +115,8 @@ func (f *groupByTags) Description() map[string]types.FunctionDescription {
115 115
 					Type:     types.SeriesList,
116 116
 				},
117 117
 				{
118
-					Name: "callback",
119
-					Options: []string{
120
-						"average",
121
-						"count",
122
-						"diff",
123
-						"last",
124
-						"max",
125
-						"median",
126
-						"min",
127
-						"multiply",
128
-						"range",
129
-						"stddev",
130
-						"sum",
131
-					},
118
+					Name:     "callback",
119
+					Options:  helper.AvailableSummarizers,
132 120
 					Required: true,
133 121
 					Type:     types.AggFunc,
134 122
 				},

+ 86
- 0
expr/functions/groupByTags/function_test.go View File

@@ -0,0 +1,86 @@
1
+package groupByTags
2
+
3
+import (
4
+	"testing"
5
+	"time"
6
+
7
+	"github.com/go-graphite/carbonapi/expr/functions/sum"
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
+	s := sum.New("")
17
+	for _, m := range s {
18
+		metadata.RegisterFunction(m.Name, m.F)
19
+	}
20
+	md := New("")
21
+	for _, m := range md {
22
+		metadata.RegisterFunction(m.Name, m.F)
23
+	}
24
+
25
+	evaluator := th.EvaluatorFromFuncWithMetadata(metadata.FunctionMD.Functions)
26
+	metadata.SetEvaluator(evaluator)
27
+	helper.SetEvaluator(evaluator)
28
+}
29
+
30
+func TestGroupByNode(t *testing.T) {
31
+	now32 := int64(time.Now().Unix())
32
+
33
+	tests := []th.MultiReturnEvalTestItem{
34
+		{
35
+			parser.NewExpr("groupByTags",
36
+				"metric1.foo.*",
37
+				parser.ArgValue("sum"),
38
+				parser.ArgValue("dc"),
39
+			),
40
+			map[parser.MetricRequest][]*types.MetricData{
41
+				{"metric1.foo.*", 0, 1}: {
42
+					types.MakeMetricData("metric1.foo;cpu=cpu1;dc=dc1", []float64{1, 2, 3, 4, 5}, 1, now32),
43
+					types.MakeMetricData("metric1.foo;cpu=cpu2;dc=dc1", []float64{6, 7, 8, 9, 10}, 1, now32),
44
+					types.MakeMetricData("metric1.foo;cpu=cpu3;dc=dc1", []float64{11, 12, 13, 14, 15}, 1, now32),
45
+					types.MakeMetricData("metric1.foo;cpu=cpu4;dc=dc1", []float64{7, 8, 9, 10, 11}, 1, now32),
46
+				},
47
+			},
48
+			"groupByTags",
49
+			map[string][]*types.MetricData{
50
+				"metric1.foo;dc=dc1": {types.MakeMetricData("metric1.foo;dc=dc1", []float64{25, 29, 33, 37, 41}, 1, now32)},
51
+			},
52
+		},
53
+		{
54
+			parser.NewExpr("groupByTags",
55
+				"metric1.foo.*",
56
+				parser.ArgValue("sum"),
57
+				parser.ArgValue("dc"),
58
+				parser.ArgValue("cpu"),
59
+				parser.ArgValue("rack"),
60
+			),
61
+			map[parser.MetricRequest][]*types.MetricData{
62
+				{"metric1.foo.*", 0, 1}: {
63
+					types.MakeMetricData("metric1.foo;cpu=cpu1;dc=dc1", []float64{1, 2, 3, 4, 5}, 1, now32),
64
+					types.MakeMetricData("metric1.foo;cpu=cpu2;dc=dc1", []float64{6, 7, 8, 9, 10}, 1, now32),
65
+					types.MakeMetricData("metric1.foo;cpu=cpu3;dc=dc1", []float64{11, 12, 13, 14, 15}, 1, now32),
66
+					types.MakeMetricData("metric1.foo;cpu=cpu4;dc=dc1", []float64{7, 8, 9, 10, 11}, 1, now32),
67
+				},
68
+			},
69
+			"groupByTags",
70
+			map[string][]*types.MetricData{
71
+				"metric1.foo;cpu=cpu1;dc=dc1;rack=": {types.MakeMetricData("metric1.foo;cpu=cpu1;dc=dc1;rack=", []float64{1, 2, 3, 4, 5}, 1, now32)},
72
+				"metric1.foo;cpu=cpu2;dc=dc1;rack=": {types.MakeMetricData("metric1.foo;cpu=cpu2;dc=dc1;rack=", []float64{6, 7, 8, 9, 10}, 1, now32)},
73
+				"metric1.foo;cpu=cpu3;dc=dc1;rack=": {types.MakeMetricData("metric1.foo;cpu=cpu3;dc=dc1;rack=", []float64{11, 12, 13, 14, 15}, 1, now32)},
74
+				"metric1.foo;cpu=cpu4;dc=dc1;rack=": {types.MakeMetricData("metric1.foo;cpu=cpu4;dc=dc1;rack=", []float64{7, 8, 9, 10, 11}, 1, now32)},
75
+			},
76
+		},
77
+	}
78
+
79
+	for _, tt := range tests {
80
+		testName := tt.E.Target() + "(" + tt.E.RawArgs() + ")"
81
+		t.Run(testName, func(t *testing.T) {
82
+			th.TestMultiReturnEvalExpr(t, &tt)
83
+		})
84
+	}
85
+
86
+}

+ 1
- 0
expr/functions/legendValue/function.go View File

@@ -78,6 +78,7 @@ func (f *legendValue) Description() map[string]types.FunctionDescription {
78 78
 					Name:     "valuesTypes",
79 79
 					Options: []string{
80 80
 						"average",
81
+						"avg_zero",
81 82
 						"count",
82 83
 						"diff",
83 84
 						"last",

+ 2
- 14
expr/functions/summarize/function.go View File

@@ -185,20 +185,8 @@ func (f *summarize) Description() map[string]types.FunctionDescription {
185 185
 				{
186 186
 					Default: types.NewSuggestion("sum"),
187 187
 					Name:    "func",
188
-					Options: []string{
189
-						"average",
190
-						"count",
191
-						"diff",
192
-						"last",
193
-						"max",
194
-						"median",
195
-						"min",
196
-						"multiply",
197
-						"range",
198
-						"stddev",
199
-						"sum",
200
-					},
201
-					Type: types.AggFunc,
188
+					Options: helper.AvailableSummarizers,
189
+					Type:    types.AggFunc,
202 190
 				},
203 191
 				{
204 192
 					Default: types.NewSuggestion(false),

+ 51
- 12
expr/functions/transformNull/function.go View File

@@ -44,7 +44,31 @@ func (f *transformNull) Do(e parser.Expr, from, until int64, values map[parser.M
44 44
 		ok = len(e.Args()) > 1
45 45
 	}
46 46
 
47
-	// FIXME(civil): support referenceSeries
47
+	var valMap []bool
48
+	referenceSeriesExpr := e.GetNamedArg("referenceSeries")
49
+	if !referenceSeriesExpr.IsInterfaceNil() {
50
+		referenceSeries, err := helper.GetSeriesArg(referenceSeriesExpr, from, until, values)
51
+		if err != nil {
52
+			return nil, err
53
+		}
54
+
55
+		if len(referenceSeries) == 0 {
56
+			return nil, fmt.Errorf("reference series is not a valid metric")
57
+		}
58
+		length := len(referenceSeries[0].Values)
59
+		if length != len(arg[0].Values) {
60
+			return nil, fmt.Errorf("length of series and reference series must be the same")
61
+		}
62
+		valMap = make([]bool, length)
63
+
64
+		for _, a := range referenceSeries {
65
+			for i, v := range a.Values {
66
+				if !math.IsNaN(v) {
67
+					valMap[i] = true
68
+				}
69
+			}
70
+		}
71
+	}
48 72
 
49 73
 	var results []*types.MetricData
50 74
 
@@ -63,7 +87,11 @@ func (f *transformNull) Do(e parser.Expr, from, until int64, values map[parser.M
63 87
 
64 88
 		for i, v := range a.Values {
65 89
 			if math.IsNaN(v) {
66
-				v = defv
90
+				if len(valMap) == 0 {
91
+					v = defv
92
+				} else if valMap[i] {
93
+					v = defv
94
+				}
67 95
 			}
68 96
 
69 97
 			r.Values[i] = v
@@ -78,11 +106,23 @@ func (f *transformNull) Do(e parser.Expr, from, until int64, values map[parser.M
78 106
 func (f *transformNull) Description() map[string]types.FunctionDescription {
79 107
 	return map[string]types.FunctionDescription{
80 108
 		"transformNull": {
81
-			Description: "Takes a metric or wildcard seriesList and replaces null values with the value\nspecified by `default`.  The value 0 used if not specified.  The optional\nreferenceSeries, if specified, is a metric or wildcard series list that governs\nwhich time intervals nulls should be replaced.  If specified, nulls are replaced\nonly in intervals where a non-null is found for the same interval in any of\nreferenceSeries.  This method compliments the drawNullAsZero function in\ngraphical mode, but also works in text-only mode.\n\nExample:\n\n.. code-block:: none\n\n  &target=transformNull(webapp.pages.*.views,-1)\n\nThis would take any page that didn't have values and supply negative 1 as a default.\nAny other numeric value may be used as well.",
82
-			Function:    "transformNull(seriesList, default=0, referenceSeries=None)",
83
-			Group:       "Transform",
84
-			Module:      "graphite.render.functions",
85
-			Name:        "transformNull",
109
+			Description: `Takes a metric or wildcard seriesList and replaces null values with the value
110
+  specified by 'default'.  The value 0 used if not specified.  The optional
111
+  referenceSeries, if specified, is a metric or wildcard series list that governs
112
+  which time intervals nulls should be replaced.  If specified, nulls are replaced
113
+  only in intervals where a non-null is found for the same interval in any of
114
+  referenceSeries.  This method compliments the drawNullAsZero function in
115
+  graphical mode, but also works in text-only mode.
116
+  Example:
117
+  .. code-block:: none
118
+    &target=transformNull(webapp.pages.*.views,-1)
119
+  This would take any page that didn't have values and supply negative 1 as a default.
120
+  Any other numeric value may be used as well.
121
+`,
122
+			Function: "transformNull(seriesList, default=0, referenceSeries=None)",
123
+			Group:    "Transform",
124
+			Module:   "graphite.render.functions",
125
+			Name:     "transformNull",
86 126
 			Params: []types.FunctionParam{
87 127
 				{
88 128
 					Name:     "seriesList",
@@ -94,11 +134,10 @@ func (f *transformNull) Description() map[string]types.FunctionDescription {
94 134
 					Name:    "default",
95 135
 					Type:    types.Float,
96 136
 				},
97
-				/*				{
98
-									Name: "referenceSeries",
99
-									Type: types.SeriesList,
100
-								},
101
-				*/
137
+				{
138
+					Name: "referenceSeries",
139
+					Type: types.SeriesList,
140
+				},
102 141
 			},
103 142
 		},
104 143
 	}

+ 81
- 0
expr/functions/transformNull/function_test.go View File

@@ -0,0 +1,81 @@
1
+package transformNull
2
+
3
+import (
4
+	"testing"
5
+	"time"
6
+
7
+	"github.com/go-graphite/carbonapi/expr/helper"
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
+	"math"
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 TestDerivative(t *testing.T) {
26
+	now32 := int64(time.Now().Unix())
27
+
28
+	tests := []th.EvalTestItem{
29
+		{
30
+			parser.NewExpr("transformNull",
31
+
32
+				"metric1",
33
+			),
34
+			map[parser.MetricRequest][]*types.MetricData{
35
+				{"metric1", 0, 1}: {types.MakeMetricData("metric1", []float64{1, math.NaN(), math.NaN(), 3, 4, 12}, 1, now32)},
36
+			},
37
+			[]*types.MetricData{types.MakeMetricData("transformNull(metric1)",
38
+				[]float64{1, 0, 0, 3, 4, 12}, 1, now32)},
39
+		},
40
+		{
41
+			parser.NewExpr("transformNull",
42
+				"metric1",
43
+				parser.NamedArgs{
44
+					"default": 5,
45
+				},
46
+			),
47
+			map[parser.MetricRequest][]*types.MetricData{
48
+				{"metric1", 0, 1}: {types.MakeMetricData("metric1", []float64{1, math.NaN(), math.NaN(), 3, 4, 12}, 1, now32)},
49
+			},
50
+			[]*types.MetricData{types.MakeMetricData("transformNull(metric1,5)",
51
+				[]float64{1, 5, 5, 3, 4, 12}, 1, now32)},
52
+		},
53
+		{
54
+			parser.NewExpr("transformNull",
55
+				"metric1",
56
+				parser.NamedArgs{
57
+					"default": 5,
58
+				},
59
+				parser.NamedArgs{
60
+					"referenceSeries": "metric2.*",
61
+				},
62
+			),
63
+			map[parser.MetricRequest][]*types.MetricData{
64
+				{"metric1", 0, 1}: {types.MakeMetricData("metric1", []float64{1, math.NaN(), math.NaN(), math.NaN(), 4, 12}, 1, now32)},
65
+				{"metric2.*", 0, 1}: {
66
+					types.MakeMetricData("metric2.foo", []float64{math.NaN(), 3, math.NaN(), 3, math.NaN(), 12}, 1, now32),
67
+					types.MakeMetricData("metric2.bar", []float64{1, math.NaN(), math.NaN(), 3, 4, 12}, 1, now32)},
68
+			},
69
+			[]*types.MetricData{types.MakeMetricData("transformNull(metric1,5)",
70
+				[]float64{1, 5, math.NaN(), 5, 4, 12}, 1, now32)},
71
+		},
72
+	}
73
+
74
+	for _, tt := range tests {
75
+		testName := tt.E.Target() + "(" + tt.E.RawArgs() + ")"
76
+		t.Run(testName, func(t *testing.T) {
77
+			th.TestEvalExpr(t, &tt)
78
+		})
79
+	}
80
+
81
+}

+ 3
- 1
expr/helper/helper.go View File

@@ -210,6 +210,8 @@ func AggregateSeries(e parser.Expr, args []*types.MetricData, function Aggregate
210 210
 	return []*types.MetricData{&r}, nil
211 211
 }
212 212
 
213
+var AvailableSummarizers = []string{"sum", "total", "avg", "average", "avg_zero", "max", "min", "last", "range", "median", "multiply", "diff", "count", "stddev"}
214
+
213 215
 // SummarizeValues summarizes values
214 216
 func SummarizeValues(f string, values []float64) float64 {
215 217
 	rv := 0.0
@@ -224,7 +226,7 @@ func SummarizeValues(f string, values []float64) float64 {
224 226
 			rv += av
225 227
 		}
226 228
 
227
-	case "avg", "average":
229
+	case "avg", "average", "avg_zero":
228 230
 		for _, av := range values {
229 231
 			rv += av
230 232
 		}

+ 38
- 9
expr/types/types.go View File

@@ -298,15 +298,28 @@ func (r *MetricData) AggregateValues() {
298 298
 // ConsolidationToFunc contains a map of graphite-compatible consolidation functions definitions to actual functions that can do aggregation
299 299
 // TODO(civil): take into account xFilesFactor
300 300
 var ConsolidationToFunc = map[string]func([]float64) float64{
301
-	"average": AggMean,
302
-	"avg":     AggMean,
303
-	"min":     AggMin,
304
-	"minimum": AggMin,
305
-	"max":     AggMax,
306
-	"maximum": AggMax,
307
-	"sum":     AggSum,
308
-	"first":   AggFirst,
309
-	"last":    AggLast,
301
+	"average":  AggMean,
302
+	"avg_zero": AggMeanZero,
303
+	"avg":      AggMean,
304
+	"min":      AggMin,
305
+	"minimum":  AggMin,
306
+	"max":      AggMax,
307
+	"maximum":  AggMax,
308
+	"sum":      AggSum,
309
+	"first":    AggFirst,
310
+	"last":     AggLast,
311
+}
312
+
313
+var consolidateFuncs []string
314
+
315
+// AvailableConsolidationFuncs lists all available consolidation functions
316
+func AvailableConsolidationFuncs() []string {
317
+	if len(consolidateFuncs) == 0 {
318
+		for name := range ConsolidationToFunc {
319
+			consolidateFuncs = append(consolidateFuncs, name)
320
+		}
321
+	}
322
+	return consolidateFuncs
310 323
 }
311 324
 
312 325
 // AggMean computes mean (sum(v)/len(v), excluding NaN points) of values
@@ -325,6 +338,22 @@ func AggMean(v []float64) float64 {
325 338
 	return sum / float64(n)
326 339
 }
327 340
 
341
+// AggMeanZero computes mean (sum(v)/len(v), replacing NaN points with 0
342
+func AggMeanZero(v []float64) float64 {
343
+	var sum float64
344
+	var n int
345
+	for _, vv := range v {
346
+		if !math.IsNaN(vv) {
347
+			sum += vv
348
+		}
349
+		n++
350
+	}
351
+	if n == 0 {
352
+		return math.NaN()
353
+	}
354
+	return sum / float64(n)
355
+}
356
+
328 357
 // AggMax computes max of values
329 358
 func AggMax(v []float64) float64 {
330 359
 	var m = math.Inf(-1)

+ 2
- 0
pkg/parser/interface.go View File

@@ -138,6 +138,8 @@ type Expr interface {
138 138
 	// GetNodeOrTagArgs returns n-th argument as slice of NodeOrTag structures.
139 139
 	GetNodeOrTagArgs(n int) ([]NodeOrTag, error)
140 140
 
141
+	IsInterfaceNil() bool
142
+
141 143
 	toExpr() interface{}
142 144
 }
143 145
 

+ 12
- 0
pkg/parser/parser.go View File

@@ -129,6 +129,11 @@ func (e *expr) Metrics() []MetricRequest {
129 129
 		}
130 130
 
131 131
 		switch e.target {
132
+		case "transformNull":
133
+			referenceSeriesExpr := e.GetNamedArg("referenceSeries")
134
+			if !referenceSeriesExpr.IsInterfaceNil() {
135
+				r = append(r, referenceSeriesExpr.Metrics()...)
136
+			}
132 137
 		case "timeShift":
133 138
 			offs, err := e.GetIntervalArg(1, -1)
134 139
 			if err != nil {
@@ -361,6 +366,13 @@ func (e *expr) GetNodeOrTagArgs(n int) ([]NodeOrTag, error) {
361 366
 	return nodeTags, nil
362 367
 }
363 368
 
369
+func (e *expr) IsInterfaceNil() bool {
370
+	if e == nil {
371
+		return true
372
+	}
373
+	return false
374
+}
375
+
364 376
 func (e *expr) insertFirstArg(exp *expr) error {
365 377
 	if e.etype != EtFunc {
366 378
 		return fmt.Errorf("pipe to not a function")

Loading…
Cancel
Save