github.com/m3db/m3@v1.5.0/src/query/graphite/native/engine_test.go (about) 1 // Copyright (c) 2019 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package native 22 23 import ( 24 "testing" 25 "time" 26 27 "github.com/m3db/m3/src/query/graphite/common" 28 "github.com/m3db/m3/src/query/graphite/context" 29 "github.com/m3db/m3/src/query/graphite/storage" 30 "github.com/m3db/m3/src/query/graphite/ts" 31 xgomock "github.com/m3db/m3/src/x/test" 32 33 "github.com/golang/mock/gomock" 34 "github.com/stretchr/testify/assert" 35 "github.com/stretchr/testify/require" 36 ) 37 38 type queryTestResult struct { 39 series string 40 expected string 41 max float64 42 } 43 44 type queryTest struct { 45 query string 46 ordered bool 47 results []queryTestResult 48 } 49 50 func snapStartToStepSize(t time.Time, stepSize int) time.Time { 51 step := time.Duration(stepSize) * time.Millisecond 52 if truncated := t.Truncate(step); truncated.Before(t) { 53 return t.Add(step) 54 } 55 56 return t 57 } 58 59 func testSeries(name string, stepSize int, val float64, opts storage.FetchOptions) *ts.Series { 60 ctx := context.New() 61 numSteps := int(opts.EndTime.Sub(opts.StartTime)/time.Millisecond) / stepSize 62 vals := ts.NewConstantValues(ctx, val, numSteps, stepSize) 63 firstPoint := snapStartToStepSize(opts.StartTime, stepSize) 64 return ts.NewSeries(ctx, name, firstPoint, vals) 65 } 66 67 func buildTestSeriesFn( 68 stepSize int, 69 id ...string, 70 ) func(context.Context, string, storage.FetchOptions) (*storage.FetchResult, error) { 71 return func(_ context.Context, q string, opts storage.FetchOptions) (*storage.FetchResult, error) { 72 series := make([]*ts.Series, 0, len(id)) 73 for _, name := range id { 74 val := testValues[name] 75 series = append(series, testSeries(name, stepSize, val, opts)) 76 } 77 78 return &storage.FetchResult{SeriesList: series}, nil 79 } 80 } 81 82 var ( 83 testValues = map[string]float64{ 84 "foo.bar.q.zed": 0, 85 "foo.bar.g.zed": 1, 86 "foo.bar.x.zed": 2, 87 "san_francisco.cake": 3, 88 "new_york_city.cake": 4, 89 "chicago.cake": 5, 90 "los_angeles.cake": 6, 91 } 92 ) 93 94 func newTestStorage(ctrl *gomock.Controller) storage.Storage { 95 store := storage.NewMockStorage(ctrl) 96 store.EXPECT().FetchByQuery(gomock.Any(), gomock.Any(), gomock.Any()). 97 DoAndReturn( 98 func( 99 ctx context.Context, 100 query string, 101 opts storage.FetchOptions, 102 ) (*storage.FetchResult, error) { 103 return &storage.FetchResult{}, nil 104 }) 105 106 return store 107 } 108 109 func TestExecute(t *testing.T) { 110 ctrl := xgomock.NewController(t) 111 defer ctrl.Finish() 112 113 store := storage.NewMockStorage(ctrl) 114 engine := NewEngine(store, CompileOptions{}) 115 116 tests := []queryTest{ 117 {"foo.bar.q.zed", true, []queryTestResult{{"foo.bar.q.zed", "foo.bar.q.zed", 0}}}, 118 {"foo.bar.*.zed", false, []queryTestResult{ 119 {"foo.bar.q.zed", "foo.bar.q.zed", 0}, 120 {"foo.bar.g.zed", "foo.bar.g.zed", 1}, 121 {"foo.bar.x.zed", "foo.bar.x.zed", 2}}, 122 }, 123 {"sortByName(aliasByNode(foo.bar.*.zed, 0, 2))", true, []queryTestResult{ 124 {"foo.bar.g.zed", "foo.g", 1}, 125 {"foo.bar.q.zed", "foo.q", 0}, 126 {"foo.bar.x.zed", "foo.x", 2}, 127 }}, 128 {"groupByNodes(foo.bar.*.zed, \"sum\")", false, []queryTestResult{ 129 {"foo.bar.*.zed", "foo.bar.*.zed", 3}, 130 }}, 131 {"groupByNodes(foo.bar.*.zed, \"sum\", 2)", false, []queryTestResult{ 132 {"foo.bar.q.zed", "foo.bar.q.zed", 0}, 133 {"foo.bar.g.zed", "foo.bar.g.zed", 1}, 134 {"foo.bar.x.zed", "foo.bar.x.zed", 2}, 135 }}, 136 } 137 138 ctx := common.NewContext(common.ContextOptions{Start: time.Now().Add(-1 * time.Hour), End: time.Now(), Engine: engine}) 139 for _, test := range tests { 140 141 stepSize := 60000 142 queries := make([]string, 0, len(test.results)) 143 for _, r := range test.results { 144 queries = append(queries, r.series) 145 } 146 147 store.EXPECT().FetchByQuery(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( 148 buildTestSeriesFn(stepSize, queries...)) 149 150 expr, err := engine.Compile(test.query) 151 require.NoError(t, err) 152 153 results, err := expr.Execute(ctx) 154 require.Nil(t, err, "failed to execute %s", test.query) 155 require.Equal(t, len(test.results), len(results.Values), "invalid results for %s", test.query) 156 157 for i := range test.results { 158 if test.ordered { 159 assert.Equal(t, test.results[i].expected, results.Values[i].Name(), 160 "invalid result %d for %s", i, test.query) 161 assert.Equal(t, test.results[i].max, results.Values[i].CalcStatistics().Max, 162 "invalid result %d for %s", i, test.query) 163 } 164 } 165 } 166 } 167 168 func TestTracing(t *testing.T) { 169 ctrl := xgomock.NewController(t) 170 defer ctrl.Finish() 171 172 store := storage.NewMockStorage(ctrl) 173 174 engine := NewEngine(store, CompileOptions{}) 175 var traces []common.Trace 176 177 ctx := common.NewContext(common.ContextOptions{Start: time.Now().Add(-1 * time.Hour), End: time.Now(), Engine: engine}) 178 ctx.Trace = func(t common.Trace) { 179 traces = append(traces, t) 180 } 181 182 stepSize := 60000 183 store.EXPECT().FetchByQuery(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( 184 buildTestSeriesFn(stepSize, "foo.bar.q.zed", "foo.bar.g.zed", 185 "foo.bar.x.zed")) 186 187 expr, err := engine.Compile("groupByNode(sortByName(aliasByNode(foo.bar.*.zed, 0, 2)), 0, 'sumSeries')") 188 require.NoError(t, err) 189 190 _, err = expr.Execute(ctx) 191 require.NoError(t, err) 192 193 expectedTraces := []common.Trace{ 194 { 195 ActivityName: "fetch foo.bar.*.zed", 196 Outputs: common.TraceStats{NumSeries: 3}}, 197 { 198 ActivityName: "aliasByNode", 199 Inputs: []common.TraceStats{{NumSeries: 3}}, 200 Outputs: common.TraceStats{NumSeries: 3}}, 201 { 202 ActivityName: "sortByName", 203 Inputs: []common.TraceStats{{NumSeries: 3}}, 204 Outputs: common.TraceStats{NumSeries: 3}}, 205 { 206 ActivityName: "groupByNode", 207 Inputs: []common.TraceStats{{NumSeries: 3}}, 208 Outputs: common.TraceStats{NumSeries: 1}}, 209 } 210 require.Equal(t, len(expectedTraces), len(traces)) 211 for i, expected := range expectedTraces { 212 trace := traces[i] 213 assert.Equal(t, expected.ActivityName, trace.ActivityName, "incorrect name for trace %d", i) 214 assert.Equal(t, expected.Inputs, trace.Inputs, "incorrect inputs for trace %d", i) 215 assert.Equal(t, expected.Outputs, trace.Outputs, "incorrect outputs for trace %d", i) 216 } 217 } 218 219 func buildEmptyTestSeriesFn() func(context.Context, string, storage.FetchOptions) (*storage.FetchResult, error) { 220 return func(_ context.Context, q string, opts storage.FetchOptions) (*storage.FetchResult, error) { 221 series := make([]*ts.Series, 0, 0) 222 return &storage.FetchResult{SeriesList: series}, nil 223 } 224 } 225 226 func TestNilContextShifter(t *testing.T) { 227 ctrl := xgomock.NewController(t) 228 defer ctrl.Finish() 229 230 store := storage.NewMockStorage(ctrl) 231 232 engine := NewEngine(store, CompileOptions{}) 233 234 ctx := common.NewContext(common.ContextOptions{Start: time.Now().Add(-1 * time.Hour), End: time.Now(), Engine: engine}) 235 236 store.EXPECT().FetchByQuery(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( 237 buildEmptyTestSeriesFn()).AnyTimes() 238 239 expr, err := engine.Compile("movingSum(foo.bar.q.zed, '30s')") 240 require.NoError(t, err) 241 242 _, err = expr.Execute(ctx) 243 require.NoError(t, err) 244 }