func (expr scalarExpression) Evaluate(context function.EvaluationContext) (function.Value, error) { return function.ScalarValue(expr.value), nil }
func TestTransformTimeseries(t *testing.T) { testCases := []struct { values []float64 tagSet api.TagSet parameters []function.Value scale float64 tests []struct { fun transform expected []float64 useParam bool } }{ { values: []float64{0, 1, 2, 3, 4, 5}, tagSet: api.TagSet{ "dc": "A", "host": "B", "env": "C", }, scale: 30, parameters: []function.Value{function.ScalarValue(100)}, tests: []struct { fun transform expected []float64 useParam bool }{ { fun: Derivative, expected: []float64{0.0, 1.0 / 30.0, 1.0 / 30.0, 1.0 / 30.0, 1.0 / 30.0, 1.0 / 30.0}, useParam: false, }, { fun: Integral, expected: []float64{0.0, 1.0 * 30.0, 3.0 * 30.0, 6.0 * 30.0, 10.0 * 30.0, 15.0 * 30.0}, useParam: false, }, { fun: MapMaker(func(x float64) float64 { return -x }), expected: []float64{0, -1, -2, -3, -4, -5}, useParam: false, }, { fun: NaNKeepLast, expected: []float64{0, 1, 2, 3, 4, 5}, useParam: false, }, }, }, } epsilon := 1e-10 for _, test := range testCases { series := api.Timeseries{ Values: test.values, TagSet: test.tagSet, } for _, transform := range test.tests { params := test.parameters if !transform.useParam { params = []function.Value{} } result, err := transformTimeseries(series, transform.fun, params, test.scale) if err != nil { t.Error(err) continue } if !result.TagSet.Equals(test.tagSet) { t.Errorf("Expected tagset to be unchanged by transform, changed %+v into %+v", test.tagSet, result.TagSet) continue } if len(result.Values) != len(transform.expected) { t.Errorf("Expected result to have length %d but has length %d", len(transform.expected), len(result.Values)) continue } // Now check that the values are approximately equal for i := range result.Values { if math.Abs(result.Values[i]-transform.expected[i]) > epsilon { t.Errorf("Expected %+v but got %+v", transform.expected, result.Values) break } } } } }
func TestApplyBound(t *testing.T) { a := assert.New(t) testTimerange, err := api.NewTimerange(758400000, 758400000+30000*5, 30000) //{2, nan, nan, nan, 3, 3}, if err != nil { t.Fatal("invalid timerange used for testcase") return } list := api.SeriesList{ Series: []api.Timeseries{ { Values: []float64{1, 2, 3, 4, 5, 6}, TagSet: api.TagSet{ "name": "A", }, }, { Values: []float64{5, 5, 3, -7, math.NaN(), -20}, TagSet: api.TagSet{ "name": "B", }, }, { Values: []float64{math.NaN(), 100, 90, 0, 0, 3}, TagSet: api.TagSet{ "name": "C", }, }, }, Timerange: testTimerange, Name: "test", } tests := []struct { lower float64 upper float64 expectBound map[string][]float64 expectLower map[string][]float64 expectUpper map[string][]float64 }{ { lower: 2, upper: 5, expectBound: map[string][]float64{ "A": {2, 2, 3, 4, 5, 5}, "B": {5, 5, 3, 2, math.NaN(), 2}, "C": {math.NaN(), 5, 5, 2, 2, 3}, }, expectLower: map[string][]float64{ "A": {2, 2, 3, 4, 5, 6}, "B": {5, 5, 3, 2, math.NaN(), 2}, "C": {math.NaN(), 100, 90, 2, 2, 3}, }, expectUpper: map[string][]float64{ "A": {1, 2, 3, 4, 5, 5}, "B": {5, 5, 3, -7, math.NaN(), -20}, "C": {math.NaN(), 5, 5, 0, 0, 3}, }, }, { lower: -10, upper: 40, expectBound: map[string][]float64{ "A": {1, 2, 3, 4, 5, 6}, "B": {5, 5, 3, -7, math.NaN(), -10}, "C": {math.NaN(), 40, 40, 0, 0, 3}, }, expectLower: map[string][]float64{ "A": {1, 2, 3, 4, 5, 6}, "B": {5, 5, 3, -7, math.NaN(), -10}, "C": {math.NaN(), 100, 90, 0, 0, 3}, }, expectUpper: map[string][]float64{ "A": {1, 2, 3, 4, 5, 6}, "B": {5, 5, 3, -7, math.NaN(), -20}, "C": {math.NaN(), 40, 40, 0, 0, 3}, }, }, } for _, test := range tests { bounders := []struct { bounder func(ctx *function.EvaluationContext, series api.Timeseries, parameters []function.Value, scale float64) ([]float64, error) params []function.Value expected map[string][]float64 name string }{ {bounder: Bound, params: []function.Value{function.ScalarValue(test.lower), function.ScalarValue(test.upper)}, expected: test.expectBound, name: "bound"}, {bounder: LowerBound, params: []function.Value{function.ScalarValue(test.lower)}, expected: test.expectLower, name: "lower"}, {bounder: UpperBound, params: []function.Value{function.ScalarValue(test.upper)}, expected: test.expectUpper, name: "upper"}, } for _, bounder := range bounders { ctx := function.EvaluationContext{EvaluationNotes: []string{}} bounded, err := ApplyTransform(&ctx, list, bounder.bounder, bounder.params) if err != nil { t.Errorf(err.Error()) continue } if len(bounded.Series) != len(list.Series) { t.Errorf("Expected to get %d results but got %d in %+v", len(list.Series), len(bounded.Series), bounded) continue } // Next, check they're all unique and such alreadyUsed := map[string]bool{} for _, series := range bounded.Series { if alreadyUsed[series.TagSet["name"]] { t.Fatalf("Repeating name `%s`", series.TagSet["name"]) } alreadyUsed[series.TagSet["name"]] = true // Next, verify that it's what we expect a.EqFloatArray(series.Values, bounder.expected[series.TagSet["name"]], 3e-7) } } } ctx := function.EvaluationContext{EvaluationNotes: []string{}} if _, err = ApplyTransform(&ctx, list, Bound, []function.Value{function.ScalarValue(18), function.ScalarValue(17)}); err == nil { t.Fatalf("Expected error on invalid bounds") } if _, err = ApplyTransform(&ctx, list, Bound, []function.Value{function.ScalarValue(-17), function.ScalarValue(-18)}); err == nil { t.Fatalf("Expected error on invalid bounds") } }
func TestApplyTransformNaN(t *testing.T) { var testTimerange, err = api.NewTimerange(758400000, 758400000+30000*5, 30000) if err != nil { t.Fatalf("invalid timerange used for testcase") return } nan := math.NaN() list := api.SeriesList{ Series: []api.Timeseries{ { Values: []float64{0, 1, nan, 3, 4, 5}, TagSet: api.TagSet{ "series": "A", }, }, { Values: []float64{2, nan, nan, nan, 3, 3}, TagSet: api.TagSet{ "series": "B", }, }, { Values: []float64{0, 1, 2, nan, 2, 1}, TagSet: api.TagSet{ "series": "C", }, }, }, Timerange: testTimerange, Name: "test", } tests := []struct { transform transform parameters []function.Value expected map[string][]float64 }{ { transform: Derivative, parameters: []function.Value{}, expected: map[string][]float64{ "A": {0, 1.0 / 30, nan, nan, 1.0 / 30, 1.0 / 30}, "B": {0, nan, nan, nan, nan, 0.0}, "C": {0, 1.0 / 30, 1.0 / 30, nan, nan, -1.0 / 30}, }, }, { transform: Integral, parameters: []function.Value{}, expected: map[string][]float64{ "A": {0, 1 * 30, 1 * 30, 4 * 30, 8 * 30, 13 * 30}, "B": {2 * 30, 2 * 30, 2 * 30, 2 * 30, 5 * 30, 8 * 30}, "C": {0, 1 * 30, 3 * 30, 3 * 30, 5 * 30, 6 * 30}, }, }, { transform: Rate, parameters: []function.Value{}, expected: map[string][]float64{ "A": {0, 1 / 30.0, nan, nan, 1 / 30.0, 1 / 30.0}, "B": {0, nan, nan, nan, nan, 0}, "C": {0, 1 / 30.0, 1 / 30.0, nan, nan, 0}, }, }, { transform: Cumulative, parameters: []function.Value{}, expected: map[string][]float64{ "A": {0, 1, 1, 4, 8, 13}, "B": {2, 2, 2, 2, 5, 8}, "C": {0, 1, 3, 3, 5, 6}, }, }, { transform: Default, parameters: []function.Value{function.ScalarValue(17)}, expected: map[string][]float64{ "A": {0, 1, 17, 3, 4, 5}, "B": {2, 17, 17, 17, 3, 3}, "C": {0, 1, 2, 17, 2, 1}, }, }, { transform: NaNKeepLast, parameters: []function.Value{}, expected: map[string][]float64{ "A": {0, 1, 1, 3, 4, 5}, "B": {2, 2, 2, 2, 3, 3}, "C": {0, 1, 2, 2, 2, 1}, }, }, } for _, test := range tests { result, err := ApplyTransform(list, test.transform, test.parameters) if err != nil { t.Fatalf(fmt.Sprintf("error applying transformation %s", err)) return } for _, series := range result.Series { values := series.Values expected := test.expected[series.TagSet["series"]] if len(values) != len(expected) { t.Errorf("values != expected; %+v != %+v", values, expected) continue } for i := range values { v := values[i] e := expected[i] if (math.IsNaN(e) != math.IsNaN(v)) || (!math.IsNaN(e) && math.Abs(v-e) > 1e-7) { t.Errorf("(actual) %+v != %+v (expected)", values, expected) break } } } } }
func TestTransformTimeseries(t *testing.T) { //This is to make sure that the scale of all the data //is interpreted as 30 seconds (30000 milliseconds) timerange, _ := api.NewTimerange(0, int64(30000*5), int64(30000)) testCases := []struct { series api.Timeseries values []float64 tagSet api.TagSet parameters []function.Value timerange api.Timerange tests []struct { fun transform expected []float64 useParam bool } }{ { values: []float64{0, 1, 2, 3, 4, 5}, tagSet: api.TagSet{ "dc": "A", "host": "B", "env": "C", }, timerange: timerange, parameters: []function.Value{function.ScalarValue(100)}, tests: []struct { fun transform expected []float64 useParam bool }{ { fun: derivative, expected: []float64{1.0 / 30.0, 1.0 / 30.0, 1.0 / 30.0, 1.0 / 30.0, 1.0 / 30.0}, useParam: false, }, { fun: Integral, expected: []float64{0.0, 1.0 * 30.0, 3.0 * 30.0, 6.0 * 30.0, 10.0 * 30.0, 15.0 * 30.0}, useParam: false, }, { fun: MapMaker(func(x float64) float64 { return -x }), expected: []float64{0, -1, -2, -3, -4, -5}, useParam: false, }, { fun: NaNKeepLast, expected: []float64{0, 1, 2, 3, 4, 5}, useParam: false, }, { fun: rate, expected: []float64{1.0 / 30.0, 1.0 / 30.0, 1.0 / 30.0, 1.0 / 30.0, 1.0 / 30.0}, useParam: false, }, }, }, } epsilon := 1e-10 for _, test := range testCases { series := api.Timeseries{ Values: test.values, TagSet: test.tagSet, } for _, transform := range test.tests { params := test.parameters if !transform.useParam { params = []function.Value{} } ctx := function.EvaluationContext{EvaluationNotes: []string{}} seriesList := api.SeriesList{ Series: []api.Timeseries{series}, Timerange: timerange, } a, err := ApplyTransform(&ctx, seriesList, transform.fun, params) result := a.Series[0] if err != nil { t.Error(err) continue } if !result.TagSet.Equals(test.tagSet) { t.Errorf("Expected tagset to be unchanged by transform, changed %+v into %+v", test.tagSet, result.TagSet) continue } if len(result.Values) != len(transform.expected) { t.Errorf("Expected result to have length %d but has length %d", len(transform.expected), len(result.Values)) continue } // Now check that the values are approximately equal for i := range result.Values { if math.Abs(result.Values[i]-transform.expected[i]) > epsilon { t.Errorf("Expected %+v but got %+v", transform.expected, result.Values) break } } } } }