github.com/m3db/m3@v1.5.0/src/query/graphite/storage/m3_wrapper_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 storage
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/golang/mock/gomock"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  
    33  	"github.com/m3db/m3/src/dbnode/encoding"
    34  	"github.com/m3db/m3/src/m3ninx/doc"
    35  	"github.com/m3db/m3/src/query/block"
    36  	xctx "github.com/m3db/m3/src/query/graphite/context"
    37  	"github.com/m3db/m3/src/query/graphite/graphite"
    38  	"github.com/m3db/m3/src/query/models"
    39  	"github.com/m3db/m3/src/query/storage"
    40  	"github.com/m3db/m3/src/query/storage/m3"
    41  	"github.com/m3db/m3/src/query/storage/mock"
    42  	m3ts "github.com/m3db/m3/src/query/ts"
    43  	"github.com/m3db/m3/src/x/instrument"
    44  	xtest "github.com/m3db/m3/src/x/test"
    45  	xtime "github.com/m3db/m3/src/x/time"
    46  )
    47  
    48  var (
    49  	// alloc once so default pools are created just once
    50  	testM3DBOpts = m3.NewOptions(encoding.NewOptions())
    51  )
    52  
    53  func TestTranslateQuery(t *testing.T) {
    54  	query := `foo.ba[rz].q*x.terminator.will.be.*.back?`
    55  	end := time.Now()
    56  	start := end.Add(time.Hour * -2)
    57  	opts := FetchOptions{
    58  		StartTime: start,
    59  		EndTime:   end,
    60  		DataOptions: DataOptions{
    61  			Timeout: time.Minute,
    62  		},
    63  	}
    64  
    65  	translated, err := translateQuery(query, opts, M3WrappedStorageOptions{})
    66  	assert.NoError(t, err)
    67  	assert.Equal(t, end, translated.End)
    68  	assert.Equal(t, start, translated.Start)
    69  	assert.Equal(t, time.Duration(0), translated.Interval)
    70  	assert.Equal(t, query, translated.Raw)
    71  	matchers := translated.TagMatchers
    72  	expected := models.Matchers{
    73  		{Type: models.MatchEqual, Name: graphite.TagName(0), Value: []byte("foo")},
    74  		{Type: models.MatchRegexp, Name: graphite.TagName(1), Value: []byte("ba[rz]")},
    75  		{Type: models.MatchRegexp, Name: graphite.TagName(2), Value: []byte(`q[^\.]*x`)},
    76  		{Type: models.MatchEqual, Name: graphite.TagName(3), Value: []byte("terminator")},
    77  		{Type: models.MatchEqual, Name: graphite.TagName(4), Value: []byte("will")},
    78  		{Type: models.MatchEqual, Name: graphite.TagName(5), Value: []byte("be")},
    79  		{Type: models.MatchField, Name: graphite.TagName(6)},
    80  		{Type: models.MatchRegexp, Name: graphite.TagName(7), Value: []byte(`back[^\.]`)},
    81  		{Type: models.MatchNotField, Name: graphite.TagName(8)},
    82  	}
    83  
    84  	assert.Equal(t, expected, matchers)
    85  }
    86  
    87  func TestTranslateQueryStarStar(t *testing.T) {
    88  	query := `foo**bar`
    89  	end := time.Now()
    90  	start := end.Add(time.Hour * -2)
    91  	opts := FetchOptions{
    92  		StartTime: start,
    93  		EndTime:   end,
    94  		DataOptions: DataOptions{
    95  			Timeout: time.Minute,
    96  		},
    97  	}
    98  
    99  	translated, err := translateQuery(query, opts, M3WrappedStorageOptions{})
   100  	assert.NoError(t, err)
   101  	assert.Equal(t, end, translated.End)
   102  	assert.Equal(t, start, translated.Start)
   103  	assert.Equal(t, time.Duration(0), translated.Interval)
   104  	assert.Equal(t, query, translated.Raw)
   105  	matchers := translated.TagMatchers
   106  	expected := models.Matchers{
   107  		{Type: models.MatchRegexp, Name: graphite.TagName(0), Value: []byte(".*")},
   108  		{Type: models.MatchRegexp, Name: doc.IDReservedFieldName, Value: []byte("foo.*bar")},
   109  	}
   110  
   111  	assert.Equal(t, expected, matchers)
   112  }
   113  
   114  func TestTranslateQueryTrailingDot(t *testing.T) {
   115  	query := `foo.`
   116  	end := time.Now()
   117  	start := end.Add(time.Hour * -2)
   118  	opts := FetchOptions{
   119  		StartTime: start,
   120  		EndTime:   end,
   121  		DataOptions: DataOptions{
   122  			Timeout: time.Minute,
   123  		},
   124  	}
   125  
   126  	translated, err := translateQuery(query, opts, M3WrappedStorageOptions{})
   127  	assert.Nil(t, translated)
   128  	assert.Error(t, err)
   129  
   130  	matchers, _, err := TranslateQueryToMatchersWithTerminator(query)
   131  	assert.Nil(t, matchers)
   132  	assert.Error(t, err)
   133  }
   134  
   135  func buildResult(
   136  	ctrl *gomock.Controller,
   137  	resolution time.Duration,
   138  	size int,
   139  	steps int,
   140  	start time.Time,
   141  ) block.Result {
   142  	unconsolidatedSeries := make([]block.UnconsolidatedSeries, 0, size)
   143  	resos := make([]time.Duration, 0, size)
   144  	metas := make([]block.SeriesMeta, 0, size)
   145  	for i := 0; i < size; i++ {
   146  		resos = append(resos, resolution)
   147  		meta := block.SeriesMeta{Name: []byte(fmt.Sprint("a", i))}
   148  		metas = append(metas, meta)
   149  		vals := m3ts.NewFixedStepValues(resolution, steps, float64(i), xtime.ToUnixNano(start))
   150  		series := block.NewUnconsolidatedSeries(vals.Datapoints(),
   151  			meta, block.UnconsolidatedSeriesStats{})
   152  		unconsolidatedSeries = append(unconsolidatedSeries, series)
   153  	}
   154  
   155  	bl := block.NewMockBlock(ctrl)
   156  	bl.EXPECT().
   157  		SeriesIter().
   158  		DoAndReturn(func() (block.SeriesIter, error) {
   159  			return block.NewUnconsolidatedSeriesIter(unconsolidatedSeries), nil
   160  		}).
   161  		AnyTimes()
   162  	bl.EXPECT().Close().Return(nil)
   163  
   164  	return block.Result{
   165  		Blocks: []block.Block{bl},
   166  		Metadata: block.ResultMetadata{
   167  			Resolutions: resos,
   168  		},
   169  	}
   170  }
   171  
   172  func TestTranslateTimeseries(t *testing.T) {
   173  	ctrl := xtest.NewController(t)
   174  	defer ctrl.Finish()
   175  
   176  	ctx := xctx.New()
   177  	resolution := 10 * time.Second
   178  	steps := 3
   179  	start := time.Now().Truncate(resolution).Add(time.Second)
   180  	end := start.Add(time.Duration(steps) * resolution).Add(time.Second * -2)
   181  
   182  	// NB: truncated steps should have 1 less data point than input series since
   183  	// the first data point is not valid.
   184  	truncatedSteps := steps - 1
   185  	truncated := start.Truncate(resolution).Add(resolution)
   186  	truncatedEnd := truncated.Add(resolution * time.Duration(truncatedSteps))
   187  
   188  	expected := 5
   189  	result := buildResult(ctrl, resolution, expected, steps, start)
   190  	translated, err := translateTimeseries(ctx, result, start, end,
   191  		testM3DBOpts, truncateBoundsToResolutionOptions{})
   192  	require.NoError(t, err)
   193  
   194  	require.Equal(t, expected, len(translated))
   195  	for i, tt := range translated {
   196  		ex := make([]float64, truncatedSteps)
   197  		for j := range ex {
   198  			ex[j] = float64(i)
   199  		}
   200  
   201  		assert.Equal(t, truncated, tt.StartTime())
   202  		assert.Equal(t, truncatedEnd, tt.EndTime())
   203  		assert.Equal(t, ex, tt.SafeValues())
   204  		assert.Equal(t, fmt.Sprint("a", i), tt.Name())
   205  	}
   206  }
   207  
   208  func TestFetchByQuery(t *testing.T) {
   209  	ctrl := xtest.NewController(t)
   210  	defer ctrl.Finish()
   211  
   212  	store := storage.NewMockStorage(ctrl)
   213  	resolution := 10 * time.Second
   214  	start := time.Now().Add(time.Hour * -1).Truncate(resolution).Add(time.Second)
   215  	steps := 3
   216  	res := buildResult(ctrl, resolution, 1, steps, start)
   217  	res.Metadata = block.ResultMetadata{
   218  		Exhaustive:  false,
   219  		LocalOnly:   true,
   220  		Warnings:    []block.Warning{{Name: "foo", Message: "bar"}},
   221  		Resolutions: []time.Duration{resolution},
   222  	}
   223  
   224  	store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()).
   225  		Return(res, nil)
   226  
   227  	wrapper := NewM3WrappedStorage(store, testM3DBOpts,
   228  		instrument.NewOptions(), M3WrappedStorageOptions{})
   229  	ctx := xctx.New()
   230  	ctx.SetRequestContext(context.TODO())
   231  	end := start.Add(time.Duration(steps) * resolution)
   232  	opts := FetchOptions{
   233  		StartTime: start,
   234  		EndTime:   end,
   235  		DataOptions: DataOptions{
   236  			Timeout: time.Minute,
   237  		},
   238  		QueryFetchOpts: storage.NewFetchOptions(),
   239  	}
   240  
   241  	query := "a*b"
   242  	result, err := wrapper.FetchByQuery(ctx, query, opts)
   243  	require.NoError(t, err)
   244  	require.Equal(t, 1, len(result.SeriesList))
   245  	series := result.SeriesList[0]
   246  	assert.Equal(t, "a0", series.Name())
   247  	// NB: last point is expected to be truncated.
   248  	assert.Equal(t, []float64{0, 0}, series.SafeValues())
   249  
   250  	assert.False(t, result.Metadata.Exhaustive)
   251  	assert.True(t, result.Metadata.LocalOnly)
   252  	require.Equal(t, 1, len(result.Metadata.Warnings))
   253  	require.Equal(t, "foo_bar", result.Metadata.Warnings[0].Header())
   254  }
   255  
   256  func TestFetchByInvalidQuery(t *testing.T) {
   257  	store := mock.NewMockStorage()
   258  	start := time.Now().Add(time.Hour * -1)
   259  	end := time.Now()
   260  	opts := FetchOptions{
   261  		StartTime: start,
   262  		EndTime:   end,
   263  		DataOptions: DataOptions{
   264  			Timeout: time.Minute,
   265  		},
   266  	}
   267  
   268  	query := "a."
   269  	ctx := xctx.New()
   270  	wrapper := NewM3WrappedStorage(store, testM3DBOpts,
   271  		instrument.NewOptions(), M3WrappedStorageOptions{})
   272  	result, err := wrapper.FetchByQuery(ctx, query, opts)
   273  	assert.NoError(t, err)
   274  	require.Equal(t, 0, len(result.SeriesList))
   275  }
   276  
   277  func TestTruncateBoundsToResolution(t *testing.T) {
   278  	var (
   279  		resolution    = 60 * time.Second
   280  		expectedStart = xtime.ToUnixNano(time.Date(2020, time.October, 8, 30, 51, 00, 0, time.UTC))
   281  		expectedEnd   = xtime.ToUnixNano(time.Date(2020, time.October, 8, 30, 56, 00, 0, time.UTC))
   282  		opts          = truncateBoundsToResolutionOptions{
   283  			shiftStepsStartWhenAtResolutionBoundary:    intRefValue(1),
   284  			shiftStepsEndWhenAtResolutionBoundary:      intRefValue(1),
   285  			shiftStepsEndWhenStartAtResolutionBoundary: intRefValue(1),
   286  		}
   287  		tests = []struct {
   288  			start time.Time
   289  			end   time.Time
   290  		}{
   291  
   292  			{
   293  				start: time.Date(2020, time.October, 8, 30, 50, 00, 0, time.UTC),
   294  				end:   time.Date(2020, time.October, 8, 30, 55, 00, 0, time.UTC),
   295  			},
   296  
   297  			{
   298  				start: time.Date(2020, time.October, 8, 30, 50, 00, 0, time.UTC),
   299  				end:   time.Date(2020, time.October, 8, 30, 55, 39, 0, time.UTC),
   300  			},
   301  			{
   302  				start: time.Date(2020, time.October, 8, 30, 50, 12, 0, time.UTC),
   303  				end:   time.Date(2020, time.October, 8, 30, 55, 00, 0, time.UTC),
   304  			},
   305  			{
   306  				start: time.Date(2020, time.October, 8, 30, 50, 00, 0, time.UTC),
   307  				end:   time.Date(2020, time.October, 8, 30, 55, 39, 0, time.UTC),
   308  			},
   309  		}
   310  	)
   311  
   312  	for _, test := range tests {
   313  
   314  		actualStart, actualEnd := truncateBoundsToResolution(test.start, test.end, resolution, opts)
   315  		assert.Equal(t, expectedStart, actualStart)
   316  		assert.Equal(t, expectedEnd, actualEnd)
   317  	}
   318  }
   319  
   320  func TestTranslateTimeseriesWithTruncateBoundsToResolutionOptions(t *testing.T) {
   321  	var (
   322  		ctrl       = xtest.NewController(t)
   323  		resolution = 60 * time.Second
   324  		ctx        = xctx.New()
   325  	)
   326  	defer ctrl.Finish()
   327  
   328  	tests := []struct {
   329  		name                                    string
   330  		start                                   time.Time
   331  		end                                     time.Time
   332  		shiftStepsStart                         int
   333  		shiftStepsEnd                           int
   334  		shiftStepsStartWhenAtResolutionBoundary *int
   335  		shiftStepsEndWhenAtResolutionBoundary   *int
   336  		renderPartialStart                      bool
   337  		renderPartialEnd                        bool
   338  		numDataPointsFetched                    int
   339  		numDataPointsExpected                   int
   340  		expectedStart                           time.Time
   341  		expectedEnd                             time.Time
   342  	}{
   343  		{
   344  			name:                  "default behavior",
   345  			start:                 time.Date(2020, time.October, 8, 15, 0, 12, 0, time.UTC),
   346  			end:                   time.Date(2020, time.October, 8, 15, 05, 00, 0, time.UTC),
   347  			numDataPointsFetched:  7,
   348  			numDataPointsExpected: 4,
   349  			expectedStart:         time.Date(2020, time.October, 8, 15, 1, 0, 0, time.UTC),
   350  			expectedEnd:           time.Date(2020, time.October, 8, 15, 5, 0, 0, time.UTC),
   351  		},
   352  		{
   353  			name:                  "render partial start and end",
   354  			start:                 time.Date(2020, time.October, 8, 15, 0, 12, 0, time.UTC),
   355  			end:                   time.Date(2020, time.October, 8, 15, 05, 00, 0, time.UTC),
   356  			renderPartialStart:    true,
   357  			renderPartialEnd:      true,
   358  			numDataPointsFetched:  7,
   359  			numDataPointsExpected: 5,
   360  			expectedStart:         time.Date(2020, time.October, 8, 15, 0, 0, 0, time.UTC),
   361  			expectedEnd:           time.Date(2020, time.October, 8, 15, 5, 0, 0, time.UTC),
   362  		},
   363  		{
   364  			name:                  "render just end",
   365  			start:                 time.Date(2020, time.October, 8, 15, 0, 00, 0, time.UTC),
   366  			end:                   time.Date(2020, time.October, 8, 15, 05, 27, 0, time.UTC),
   367  			renderPartialEnd:      true,
   368  			numDataPointsFetched:  7,
   369  			numDataPointsExpected: 6,
   370  			expectedStart:         time.Date(2020, time.October, 8, 15, 0, 0, 0, time.UTC),
   371  			expectedEnd:           time.Date(2020, time.October, 8, 15, 6, 0, 0, time.UTC),
   372  		},
   373  		{
   374  			name:                  "no render partial, not truncated by resolution",
   375  			start:                 time.Date(2020, time.October, 8, 15, 0, 12, 0, time.UTC),
   376  			end:                   time.Date(2020, time.October, 8, 15, 05, 27, 0, time.UTC),
   377  			numDataPointsFetched:  25,
   378  			numDataPointsExpected: 5,
   379  			expectedStart:         time.Date(2020, time.October, 8, 15, 1, 0, 0, time.UTC),
   380  			expectedEnd:           time.Date(2020, time.October, 8, 15, 6, 0, 0, time.UTC),
   381  		},
   382  		{
   383  			name:                  "no render partial, truncated start by resolution",
   384  			start:                 time.Date(2020, time.October, 8, 15, 0, 0, 0, time.UTC),
   385  			end:                   time.Date(2020, time.October, 8, 15, 05, 0, 0, time.UTC),
   386  			numDataPointsFetched:  25,
   387  			numDataPointsExpected: 5,
   388  			expectedStart:         time.Date(2020, time.October, 8, 15, 0, 0, 0, time.UTC),
   389  			expectedEnd:           time.Date(2020, time.October, 8, 15, 5, 0, 0, time.UTC),
   390  		},
   391  		{
   392  			name:                  "constant shift start and end",
   393  			start:                 time.Date(2020, time.October, 8, 15, 0, 0, 0, time.UTC),
   394  			end:                   time.Date(2020, time.October, 8, 15, 05, 5, 0, time.UTC),
   395  			shiftStepsStart:       1,
   396  			shiftStepsEnd:         1,
   397  			numDataPointsFetched:  25,
   398  			numDataPointsExpected: 5,
   399  			expectedStart:         time.Date(2020, time.October, 8, 15, 1, 0, 0, time.UTC),
   400  			expectedEnd:           time.Date(2020, time.October, 8, 15, 6, 0, 0, time.UTC),
   401  		},
   402  		{
   403  			name:                                    "constant shift start and end + boundary shift start and end with start at boundary",
   404  			start:                                   time.Date(2020, time.October, 8, 15, 0, 0, 0, time.UTC),
   405  			end:                                     time.Date(2020, time.October, 8, 15, 05, 05, 0, time.UTC),
   406  			shiftStepsStart:                         1,
   407  			shiftStepsEnd:                           1,
   408  			shiftStepsStartWhenAtResolutionBoundary: intRefValue(2),
   409  			shiftStepsEndWhenAtResolutionBoundary:   intRefValue(2),
   410  			numDataPointsFetched:                    25,
   411  			numDataPointsExpected:                   4,
   412  			expectedStart:                           time.Date(2020, time.October, 8, 15, 2, 0, 0, time.UTC),
   413  			expectedEnd:                             time.Date(2020, time.October, 8, 15, 6, 0, 0, time.UTC),
   414  		},
   415  		{
   416  			name:                                    "constant shift start and end + boundary shift start and end with start and end at boundary",
   417  			start:                                   time.Date(2020, time.October, 8, 15, 0, 0, 0, time.UTC),
   418  			end:                                     time.Date(2020, time.October, 8, 15, 6, 0, 0, time.UTC),
   419  			shiftStepsStart:                         1,
   420  			shiftStepsEnd:                           1,
   421  			shiftStepsStartWhenAtResolutionBoundary: intRefValue(2),
   422  			shiftStepsEndWhenAtResolutionBoundary:   intRefValue(2),
   423  			numDataPointsFetched:                    25,
   424  			numDataPointsExpected:                   6,
   425  			expectedStart:                           time.Date(2020, time.October, 8, 15, 2, 0, 0, time.UTC),
   426  			expectedEnd:                             time.Date(2020, time.October, 8, 15, 8, 0, 0, time.UTC),
   427  		},
   428  	}
   429  
   430  	for _, test := range tests {
   431  		t.Run(test.name, func(t *testing.T) {
   432  			expected := 9
   433  			result := buildResult(ctrl, resolution, expected, test.numDataPointsFetched, test.start)
   434  			translated, err := translateTimeseries(ctx, result, test.start, test.end,
   435  				testM3DBOpts, truncateBoundsToResolutionOptions{
   436  					shiftStepsStart:                         test.shiftStepsStart,
   437  					shiftStepsEnd:                           test.shiftStepsEnd,
   438  					shiftStepsStartWhenAtResolutionBoundary: test.shiftStepsStartWhenAtResolutionBoundary,
   439  					shiftStepsEndWhenAtResolutionBoundary:   test.shiftStepsEndWhenAtResolutionBoundary,
   440  					renderPartialStart:                      test.renderPartialStart,
   441  					renderPartialEnd:                        test.renderPartialEnd,
   442  				})
   443  			require.NoError(t, err)
   444  
   445  			require.Equal(t, expected, len(translated))
   446  			for i, tt := range translated {
   447  				ex := make([]float64, test.numDataPointsExpected)
   448  				for j := range ex {
   449  					ex[j] = float64(i)
   450  				}
   451  
   452  				require.Equal(t, fmt.Sprint("a", i), tt.Name(), "unexpected name")
   453  				require.Equal(t, ex, tt.SafeValues(), "unexpected values")
   454  				require.Equal(t, test.expectedStart, tt.StartTime().UTC(), "unexpected start time")
   455  				require.Equal(t, test.expectedEnd, tt.EndTime().UTC(), "unexpected end time")
   456  			}
   457  		})
   458  	}
   459  }
   460  
   461  func intRefValue(i int) *int {
   462  	value := i
   463  	return &value
   464  }