func TestMovingAverage(t *testing.T) { fakeAPI := mocks.NewFakeApi() fakeAPI.AddPair(api.TaggedMetric{"series", api.NewTagSet()}, "series") fakeBackend := movingAverageBackend{} timerange, err := api.NewTimerange(1200, 1500, 100) if err != nil { t.Fatalf(err.Error()) } expression := &functionExpression{ functionName: "transform.moving_average", groupBy: []string{}, arguments: []function.Expression{ &metricFetchExpression{"series", api.TruePredicate}, durationExpression{"300ms", 300 * time.Millisecond}, }, } result, err := evaluateToSeriesList(expression, function.EvaluationContext{ API: fakeAPI, MultiBackend: backend.NewSequentialMultiBackend(fakeBackend), Timerange: timerange, SampleMethod: api.SampleMean, FetchLimit: function.NewFetchCounter(1000), Registry: registry.Default(), }) if err != nil { t.Errorf(err.Error()) } expected := []float64{4, 3, 11.0 / 3, 5} if len(result.Series) != 1 { t.Fatalf("expected exactly 1 returned series") } if len(result.Series[0].Values) != len(expected) { t.Fatalf("expected exactly %d values in returned series", len(expected)) } const eps = 1e-7 for i := range expected { if math.Abs(result.Series[0].Values[i]-expected[i]) > eps { t.Fatalf("expected %+v but got %+v", expected, result.Series[0].Values) } } }
func main() { flag.Parse() common.SetupLogger() config := common.LoadConfig() apiInstance := common.NewAPI(config.API) myBackend := blueflood.NewBlueflood(config.Blueflood) l := liner.NewLiner() defer l.Close() for { input, err := l.Prompt("> ") if err != nil { return } l.AppendHistory(input) cmd, err := query.Parse(input) if err != nil { fmt.Println("parsing error", err.Error()) continue } n, ok := cmd.(query.Node) if !ok { fmt.Printf("error: %#v doesn't implement Node\n", cmd) continue } fmt.Println(query.PrintNode(n)) result, err := cmd.Execute(query.ExecutionContext{Backend: backend.NewSequentialMultiBackend(myBackend), API: apiInstance, FetchLimit: 1000}) if err != nil { fmt.Println("execution error:", err.Error()) continue } encoded, err := json.MarshalIndent(result, "", " ") if err != nil { fmt.Println("encoding error:", err.Error()) return } fmt.Println("success:") fmt.Println(string(encoded)) } }
func Test_ScalarExpression(t *testing.T) { timerangeA, err := api.NewTimerange(0, 10, 2) if err != nil { t.Fatalf("invalid timerange used for testcase") return } for _, test := range []struct { expr scalarExpression timerange api.Timerange expectedSeries []api.Timeseries }{ { scalarExpression{5}, timerangeA, []api.Timeseries{ api.Timeseries{ []float64{5.0, 5.0, 5.0, 5.0, 5.0, 5.0}, api.NewTagSet(), }, }, }, } { a := assert.New(t).Contextf("%+v", test) result, err := evaluateToSeriesList(test.expr, function.EvaluationContext{ MultiBackend: backend.NewSequentialMultiBackend(FakeBackend{}), Timerange: test.timerange, SampleMethod: api.SampleMean, FetchLimit: function.NewFetchCounter(1000), Registry: registry.Default(), }) if err != nil { t.Fatalf("failed to convert number into serieslist") } a.EqInt(len(result.Series), len(test.expectedSeries)) for i := 0; i < len(result.Series); i += 1 { a.Eq(result.Series[i].Values, test.expectedSeries[i].Values) } } }
func TestProfilerIntegration(t *testing.T) { myAPI := fakeAPI{ tagSets: map[string][]api.TagSet{"A": []api.TagSet{ {"x": "1", "y": "2"}, {"x": "2", "y": "2"}, {"x": "3", "y": "1"}, }, "B": []api.TagSet{ {"q": "foo"}, {"q": "bar"}, }, "C": []api.TagSet{ {"c": "1"}, {"c": "2"}, {"c": "3"}, {"c": "4"}, {"c": "5"}, {"c": "6"}, }, }, } myBackend := api.ProfilingBackend{fakeBackend{}} multiBackend := api.ProfilingMultiBackend{backend.NewSequentialMultiBackend(myBackend)} testCases := []struct { query string expected map[string]int }{ { query: "describe all", expected: map[string]int{ "describe all.Execute": 1, "api.GetAllMetrics": 1, }, }, { query: "select A from 0 to 0", expected: map[string]int{ "select.Execute": 1, "fetchMultipleSeries": 1, "api.GetAllTags": 1, "fetchSingleSeries": 3, }, }, { query: "select A+A from 0 to 0", expected: map[string]int{ "select.Execute": 1, "fetchMultipleSeries": 2, "api.GetAllTags": 2, "fetchSingleSeries": 6, }, }, { query: "select A+2 from 0 to 0", expected: map[string]int{ "select.Execute": 1, "fetchMultipleSeries": 1, "api.GetAllTags": 1, "fetchSingleSeries": 3, }, }, { query: "select A where y = '2' from 0 to 0", expected: map[string]int{ "select.Execute": 1, "fetchMultipleSeries": 1, "api.GetAllTags": 1, "fetchSingleSeries": 2, }, }, { query: "describe A", expected: map[string]int{ "describe.Execute": 1, "api.GetAllTags": 1, }, }, { query: "describe metrics where y='2'", expected: map[string]int{ "describe metrics.Execute": 1, "api.GetMetricsForTag": 1, }, }, { query: "describe all", expected: map[string]int{ "describe all.Execute": 1, "api.GetAllMetrics": 1, }, }, } for _, test := range testCases { cmd, err := Parse(test.query) if err != nil { t.Error(err.Error()) continue } profilingCommand, profiler := NewProfilingCommand(cmd) _, err = profilingCommand.Execute(ExecutionContext{ Backend: multiBackend, API: myAPI, FetchLimit: 10000, Timeout: time.Second * 4, }) if err != nil { t.Fatal(err.Error()) } list := profiler.All() counts := map[string]int{} for _, node := range list { counts[node.Name()]++ } for name, count := range counts { if test.expected[name] != count { t.Errorf("Expected %+v but got %+v (from %+v)", test.expected, counts, list) break } } } }
func TestTag(t *testing.T) { fakeApi := mocks.NewFakeApi() fakeBackend := backend.NewSequentialMultiBackend(fakeApiBackend{}) tests := []struct { query string expected api.SeriesList }{ { query: "select series_2 | tag.drop('dc') from 0 to 120", expected: api.SeriesList{ Series: []api.Timeseries{ { Values: []float64{1, 2, 3, 4, 5}, TagSet: api.TagSet{}, }, { Values: []float64{3, 0, 3, 6, 2}, TagSet: api.TagSet{}, }, }, }, }, { query: "select series_2 | tag.drop('none') from 0 to 120", expected: api.SeriesList{ Series: []api.Timeseries{ { Values: []float64{1, 2, 3, 4, 5}, TagSet: api.TagSet{"dc": "west"}, }, { Values: []float64{3, 0, 3, 6, 2}, TagSet: api.TagSet{"dc": "east"}, }, }, }, }, { query: "select series_2 | tag.set('dc', 'north') from 0 to 120", expected: api.SeriesList{ Series: []api.Timeseries{ { Values: []float64{1, 2, 3, 4, 5}, TagSet: api.TagSet{"dc": "north"}, }, { Values: []float64{3, 0, 3, 6, 2}, TagSet: api.TagSet{"dc": "north"}, }, }, }, }, { query: "select series_2 | tag.set('none', 'north') from 0 to 120", expected: api.SeriesList{ Series: []api.Timeseries{ { Values: []float64{1, 2, 3, 4, 5}, TagSet: api.TagSet{"dc": "west", "none": "north"}, }, { Values: []float64{3, 0, 3, 6, 2}, TagSet: api.TagSet{"dc": "east", "none": "north"}, }, }, }, }, } for _, test := range tests { command, err := Parse(test.query) if err != nil { t.Fatalf("Unexpected error while parsing") return } if command.Name() != "select" { t.Errorf("Expected select command but got %s", command.Name()) continue } rawResult, err := command.Execute(ExecutionContext{Backend: fakeBackend, API: fakeApi, FetchLimit: 1000, Timeout: 0}) if err != nil { t.Errorf("Unexpected error while execution: %s", err.Error()) continue } seriesListList, ok := rawResult.([]api.SeriesList) if !ok || len(seriesListList) != 1 { t.Errorf("expected query `%s` to produce []value; got %+v :: %T", test.query, rawResult, rawResult) continue } list := seriesListList[0] if err != nil { t.Fatal(err) } a := assert.New(t) expectedSeries := test.expected.Series for i, series := range list.Series { a.EqFloatArray(series.Values, expectedSeries[i].Values, 1e-100) if !series.TagSet.Equals(expectedSeries[i].TagSet) { t.Errorf("expected tagset %+v but got %+v for series %d of query %s", expectedSeries[i].TagSet, series.TagSet, i, test.query) } } } }
func TestNaming(t *testing.T) { fakeApi := mocks.NewFakeApi() fakeBackend := backend.NewSequentialMultiBackend(fakeApiBackend{}) tests := []struct { query string expected string }{ { query: "select series_1 from 0 to 0", expected: "series_1", }, { query: "select series_1 + 17 from 0 to 0", expected: "(series_1 + 17)", }, { query: "select series_1 + 2342.32 from 0 to 0", expected: "(series_1 + 2342.32)", }, { query: "select series_1*17 from 0 to 0", expected: "(series_1 * 17)", }, { query: "select aggregate.sum(series_1) from 0 to 0", expected: "aggregate.sum(series_1)", }, { query: "select aggregate.sum(series_1 group by dc) from 0 to 0", expected: "aggregate.sum(series_1 group by dc)", }, { query: "select aggregate.sum(series_1 group by dc,env) from 0 to 0", expected: "aggregate.sum(series_1 group by dc, env)", }, { query: "select aggregate.sum(series_1 collapse by dc) from 0 to 0", expected: "aggregate.sum(series_1 collapse by dc)", }, { query: "select aggregate.sum(series_1 collapse by dc,env) from 0 to 0", expected: "aggregate.sum(series_1 collapse by dc, env)", }, { query: "select transform.alias(aggregate.sum(series_1 group by dc,env), 'hello') from 0 to 0", expected: "hello", }, { query: "select transform.moving_average(series_2, 2h) from 0 to 0", expected: "transform.moving_average(series_2, 2h)", }, { query: "select filter.lowest_max(series_2, 6) from 0 to 0", expected: "filter.lowest_max(series_2, 6)", }, } for _, test := range tests { command, err := Parse(test.query) if err != nil { t.Fatalf("Unexpected error while parsing") return } if command.Name() != "select" { t.Errorf("Expected select command but got %s", command.Name()) continue } rawResult, err := command.Execute(ExecutionContext{Backend: fakeBackend, API: fakeApi, FetchLimit: 1000, Timeout: 0}) if err != nil { t.Errorf("Unexpected error while execution: %s", err.Error()) continue } seriesListList, ok := rawResult.([]api.SeriesList) if !ok || len(seriesListList) != 1 { t.Errorf("expected query `%s` to produce []value; got %+v :: %T", test.query, rawResult, rawResult) continue } actual := seriesListList[0].Name if actual != test.expected { t.Errorf("Expected `%s` but got `%s` for query `%s`", test.expected, actual, test.query) continue } } }
func TestCommand_Select(t *testing.T) { epsilon := 1e-10 fakeApi := mocks.NewFakeApi() fakeApi.AddPair(api.TaggedMetric{"series_1", api.ParseTagSet("dc=west")}, emptyGraphiteName) fakeApi.AddPair(api.TaggedMetric{"series_2", api.ParseTagSet("dc=east")}, emptyGraphiteName) fakeApi.AddPair(api.TaggedMetric{"series_2", api.ParseTagSet("dc=west")}, emptyGraphiteName) fakeApi.AddPair(api.TaggedMetric{"series_3", api.ParseTagSet("dc=west")}, emptyGraphiteName) fakeApi.AddPair(api.TaggedMetric{"series_3", api.ParseTagSet("dc=east")}, emptyGraphiteName) fakeApi.AddPair(api.TaggedMetric{"series_3", api.ParseTagSet("dc=north")}, emptyGraphiteName) fakeApi.AddPair(api.TaggedMetric{"series_timeout", api.ParseTagSet("dc=west")}, emptyGraphiteName) var fakeBackend fakeApiBackend testTimerange, err := api.NewTimerange(0, 120, 30) if err != nil { t.Errorf("Invalid test timerange") return } earlyTimerange, err := api.NewTimerange(0, 60, 30) if err != nil { t.Errorf("Invalid test timerange") } lateTimerange, err := api.NewTimerange(60, 120, 30) if err != nil { t.Errorf("Invalid test timerange") } for _, test := range []struct { query string expectError bool expected api.SeriesList }{ {"select does_not_exist from 0 to 120 resolution 30ms", true, api.SeriesList{}}, {"select series_1 from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{{ []float64{1, 2, 3, 4, 5}, api.ParseTagSet("dc=west"), }}, Timerange: testTimerange, Name: "series_1", }}, {"select series_timeout from 0 to 120 resolution 30ms", true, api.SeriesList{}}, {"select series_1 + 1 from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{{ []float64{2, 3, 4, 5, 6}, api.ParseTagSet("dc=west"), }}, Timerange: testTimerange, Name: "", }}, {"select series_1 * 2 from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{{ []float64{2, 4, 6, 8, 10}, api.ParseTagSet("dc=west"), }}, Timerange: testTimerange, Name: "", }}, {"select aggregate.max(series_2) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{{ []float64{3, 2, 3, 6, 5}, api.NewTagSet(), }}, Timerange: testTimerange, Name: "series_2", }}, {"select (1 + series_2) | aggregate.max from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{{ []float64{4, 3, 4, 7, 6}, api.NewTagSet(), }}, Timerange: testTimerange, Name: "series_2", }}, {"select series_1 from 0 to 60 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{{ []float64{1, 2, 3}, api.ParseTagSet("dc=west"), }}, Timerange: earlyTimerange, Name: "series_1", }}, {"select transform.timeshift(series_1,31ms) from 0 to 60 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{{ []float64{2, 3, 4}, api.ParseTagSet("dc=west"), }}, Timerange: earlyTimerange, Name: "series_1", }}, {"select transform.timeshift(series_1,62ms) from 0 to 60 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{{ []float64{3, 4, 5}, api.ParseTagSet("dc=west"), }}, Timerange: earlyTimerange, Name: "series_1", }}, {"select transform.timeshift(series_1,29ms) from 0 to 60 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{{ []float64{2, 3, 4}, api.ParseTagSet("dc=west"), }}, Timerange: earlyTimerange, Name: "series_1", }}, {"select transform.timeshift(series_1,-31ms) from 60 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{{ []float64{2, 3, 4}, api.ParseTagSet("dc=west"), }}, Timerange: lateTimerange, Name: "series_1", }}, {"select transform.timeshift(series_1,-29ms) from 60 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{{ []float64{2, 3, 4}, api.ParseTagSet("dc=west"), }}, Timerange: lateTimerange, Name: "series_1", }}, {"select series_3 from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{1, 1, 1, 4, 4}, api.ParseTagSet("dc=west"), }, { []float64{5, 5, 5, 2, 2}, api.ParseTagSet("dc=east"), }, { []float64{3, 3, 3, 3, 3}, api.ParseTagSet("dc=north"), }, }, }}, {"select series_3 | filter.recent_highest_max(3, 30ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{1, 1, 1, 4, 4}, api.ParseTagSet("dc=west"), }, { []float64{3, 3, 3, 3, 3}, api.ParseTagSet("dc=north"), }, { []float64{5, 5, 5, 2, 2}, api.ParseTagSet("dc=east"), }, }, }}, {"select series_3 | filter.recent_highest_max(2, 30ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{1, 1, 1, 4, 4}, api.ParseTagSet("dc=west"), }, { []float64{3, 3, 3, 3, 3}, api.ParseTagSet("dc=north"), }, }, }}, {"select series_3 | filter.recent_highest_max(1, 30ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{1, 1, 1, 4, 4}, api.ParseTagSet("dc=west"), }, }, }}, {"select series_3 | filter.recent_lowest_max(3, 30ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{5, 5, 5, 2, 2}, api.ParseTagSet("dc=east"), }, { []float64{3, 3, 3, 3, 3}, api.ParseTagSet("dc=north"), }, { []float64{1, 1, 1, 4, 4}, api.ParseTagSet("dc=west"), }, }, }}, {"select series_3 | filter.recent_lowest_max(4, 30ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{5, 5, 5, 2, 2}, api.ParseTagSet("dc=east"), }, { []float64{3, 3, 3, 3, 3}, api.ParseTagSet("dc=north"), }, { []float64{1, 1, 1, 4, 4}, api.ParseTagSet("dc=west"), }, }, }}, {"select series_3 | filter.recent_highest_max(70, 30ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{1, 1, 1, 4, 4}, api.ParseTagSet("dc=west"), }, { []float64{3, 3, 3, 3, 3}, api.ParseTagSet("dc=north"), }, { []float64{5, 5, 5, 2, 2}, api.ParseTagSet("dc=east"), }, }, }}, {"select series_3 | filter.recent_lowest_max(2, 30ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{5, 5, 5, 2, 2}, api.ParseTagSet("dc=east"), }, { []float64{3, 3, 3, 3, 3}, api.ParseTagSet("dc=north"), }, }, }}, {"select series_3 | filter.recent_lowest_max(1, 30ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{5, 5, 5, 2, 2}, api.ParseTagSet("dc=east"), }, }, }}, {"select series_3 | filter.recent_highest_max(3, 3000ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{5, 5, 5, 2, 2}, api.ParseTagSet("dc=east"), }, { []float64{1, 1, 1, 4, 4}, api.ParseTagSet("dc=west"), }, { []float64{3, 3, 3, 3, 3}, api.ParseTagSet("dc=north"), }, }, }}, {"select series_3 | filter.recent_highest_max(2, 3000ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{5, 5, 5, 2, 2}, api.ParseTagSet("dc=east"), }, { []float64{1, 1, 1, 4, 4}, api.ParseTagSet("dc=west"), }, }, }}, {"select series_3 | filter.recent_highest_max(1, 3000ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{5, 5, 5, 2, 2}, api.ParseTagSet("dc=east"), }, }, }}, {"select series_3 | filter.recent_lowest_max(3, 3000ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{3, 3, 3, 3, 3}, api.ParseTagSet("dc=north"), }, { []float64{1, 1, 1, 4, 4}, api.ParseTagSet("dc=west"), }, { []float64{5, 5, 5, 2, 2}, api.ParseTagSet("dc=east"), }, }, }}, {"select series_3 | filter.recent_lowest_max(2, 3000ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{3, 3, 3, 3, 3}, api.ParseTagSet("dc=north"), }, { []float64{1, 1, 1, 4, 4}, api.ParseTagSet("dc=west"), }, }, }}, {"select series_3 | filter.recent_lowest_max(1, 3000ms) from 0 to 120 resolution 30ms", false, api.SeriesList{ Series: []api.Timeseries{ { []float64{3, 3, 3, 3, 3}, api.ParseTagSet("dc=north"), }, }, }}, {"select series_1 from -1000d to now resolution 30s", true, api.SeriesList{}}, } { a := assert.New(t).Contextf("query=%s", test.query) expected := test.expected command, err := Parse(test.query) if err != nil { a.Errorf("Unexpected error while parsing") continue } a.EqString(command.Name(), "select") rawResult, err := command.Execute(ExecutionContext{ Backend: backend.NewSequentialMultiBackend(fakeBackend), API: fakeApi, FetchLimit: 1000, Timeout: 10 * time.Millisecond, }) if err != nil { if !test.expectError { a.Errorf("Unexpected error while executing: %s", err.Error()) } } else { casted := rawResult.([]function.Value) actual, _ := casted[0].ToSeriesList(api.Timerange{}) a.EqInt(len(actual.Series), len(expected.Series)) if len(actual.Series) == len(expected.Series) { for i := 0; i < len(expected.Series); i++ { a.Eq(actual.Series[i].TagSet, expected.Series[i].TagSet) actualLength := len(actual.Series[i].Values) expectedLength := len(actual.Series[i].Values) a.Eq(actualLength, expectedLength) if actualLength == expectedLength { for j := 0; j < actualLength; j++ { a.EqFloat(actual.Series[i].Values[j], expected.Series[i].Values[j], epsilon) } } } } } } // Test that the limit is correct command, err := Parse("select series_1, series_2 from 0 to 120 resolution 30ms") if err != nil { t.Fatalf("Unexpected error while parsing") return } context := ExecutionContext{Backend: backend.NewSequentialMultiBackend(fakeBackend), API: fakeApi, FetchLimit: 3, Timeout: 0} _, err = command.Execute(context) if err != nil { t.Fatalf("expected success with limit 3 but got err = %s", err.Error()) return } context.FetchLimit = 2 _, err = command.Execute(context) if err == nil { t.Fatalf("expected failure with limit = 2") return } command, err = Parse("select series2 from 0 to 120 resolution 30ms") if err != nil { t.Fatalf("Unexpected error while parsing") return } _, err = command.Execute(context) if err != nil { t.Fatalf("expected success with limit = 2 but got %s", err.Error()) } }
func Test_evaluateBinaryOperation(t *testing.T) { emptyContext := function.EvaluationContext{ MultiBackend: backend.NewSequentialMultiBackend(FakeBackend{}), API: nil, Timerange: api.Timerange{}, SampleMethod: api.SampleMean, Predicate: nil, FetchLimit: function.NewFetchCounter(1000), Cancellable: api.NewCancellable(), } for _, test := range []struct { context function.EvaluationContext functionName string left api.SeriesList right api.SeriesList evalFunction func(float64, float64) float64 expectSuccess bool expectedResultValues [][]float64 }{ { emptyContext, "add", api.SeriesList{ []api.Timeseries{ { Values: []float64{1, 2, 3}, TagSet: api.TagSet{}, }, }, api.Timerange{}, "", }, api.SeriesList{ []api.Timeseries{ { Values: []float64{4, 5, 1}, TagSet: api.TagSet{}, }, }, api.Timerange{}, "", }, func(left, right float64) float64 { return left + right }, true, [][]float64{{5, 7, 4}}, }, { emptyContext, "subtract", api.SeriesList{ []api.Timeseries{ { Values: []float64{1, 2, 3}, }, }, api.Timerange{}, "", }, api.SeriesList{ []api.Timeseries{ { Values: []float64{4, 5, 1}, }, }, api.Timerange{}, "", }, func(left, right float64) float64 { return left - right }, true, [][]float64{{-3, -3, 2}}, }, { emptyContext, "add", api.SeriesList{ []api.Timeseries{ api.Timeseries{ []float64{1, 2, 3}, api.TagSet{ "env": "production", "host": "#1", }, }, api.Timeseries{ []float64{7, 7, 7}, api.TagSet{ "env": "staging", "host": "#2", }, }, api.Timeseries{ []float64{1, 0, 2}, api.TagSet{ "env": "staging", "host": "#3", }, }, }, api.Timerange{}, "", }, api.SeriesList{ []api.Timeseries{ api.Timeseries{ []float64{5, 5, 5}, api.TagSet{ "env": "staging", }, }, api.Timeseries{ []float64{10, 100, 1000}, api.TagSet{ "env": "production", }, }, }, api.Timerange{}, "", }, func(left, right float64) float64 { return left + right }, true, [][]float64{{11, 102, 1003}, {12, 12, 12}, {6, 5, 7}}, }, { emptyContext, "add", api.SeriesList{ []api.Timeseries{ api.Timeseries{ []float64{1, 2, 3}, api.TagSet{ "env": "production", "host": "#1", }, }, api.Timeseries{ []float64{4, 5, 6}, api.TagSet{ "env": "staging", "host": "#2", }, }, api.Timeseries{ []float64{7, 8, 9}, api.TagSet{ "env": "staging", "host": "#3", }, }, }, api.Timerange{}, "", }, api.SeriesList{ []api.Timeseries{ api.Timeseries{ []float64{2, 2, 2}, api.TagSet{ "env": "staging", }, }, api.Timeseries{ []float64{3, 3, 3}, api.TagSet{ "env": "staging", }, }, }, api.Timerange{}, "", }, func(left, right float64) float64 { return left * right }, true, [][]float64{{8, 10, 12}, {14, 16, 18}, {12, 15, 18}, {21, 24, 27}}, }, { emptyContext, "add", api.SeriesList{ []api.Timeseries{ api.Timeseries{ []float64{103, 103, 103}, api.TagSet{ "env": "production", "host": "#1", }, }, api.Timeseries{ []float64{203, 203, 203}, api.TagSet{ "env": "staging", "host": "#2", }, }, api.Timeseries{ []float64{303, 303, 303}, api.TagSet{ "env": "staging", "host": "#3", }, }, }, api.Timerange{}, "", }, api.SeriesList{ []api.Timeseries{ api.Timeseries{ []float64{1, 2, 3}, api.TagSet{ "env": "staging", }, }, api.Timeseries{ []float64{3, 0, 3}, api.TagSet{ "env": "production", }, }, }, api.Timerange{}, "", }, func(left, right float64) float64 { return left - right }, true, [][]float64{{100, 103, 100}, {202, 201, 200}, {302, 301, 300}}, }, } { a := assert.New(t).Contextf("%+v", test) metricFun := registry.NewOperator(test.functionName, test.evalFunction) value, err := metricFun.Evaluate(test.context, []function.Expression{&LiteralSeriesExpression{test.left}, &LiteralSeriesExpression{test.right}}, []string{}, false) if err != nil { a.EqBool(err == nil, test.expectSuccess) continue } result, err := value.ToSeriesList(test.context.Timerange) if err != nil { a.EqBool(err == nil, test.expectSuccess) continue } // Our expected list should be the same length as the actual one: a.EqInt(len(result.Series), len(test.expectedResultValues)) // The "expected" results are only true up to permutation (since guessing the order they'll come out of `join()` is hard) // Provided that they're all unique then we just need to check that every member that's expected can be found // This is a bit more annoying: equal := func(left, right []float64) bool { if len(left) != len(right) { return false } for i := range left { if left[i] != right[i] { return false } } return true } for _, expectedMember := range test.expectedResultValues { found := false // check that expectedMember is inside our result list // look for it inside result.Series for _, resultMember := range result.Series { if equal(resultMember.Values, expectedMember) { found = true break } } if !found { t.Fatalf("got %+v for test %+v", result, test) } } } }