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  }