Example #1
0
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
				}
			}
		}
	}
}