github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/functions/temporal/base_test.go (about)

     1  // Copyright (c) 2018 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 temporal
    22  
    23  import (
    24  	"fmt"
    25  	"math"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/query/block"
    30  	"github.com/m3db/m3/src/query/executor/transform"
    31  	"github.com/m3db/m3/src/query/models"
    32  	"github.com/m3db/m3/src/query/parser"
    33  	"github.com/m3db/m3/src/query/test"
    34  	"github.com/m3db/m3/src/query/test/compare"
    35  	"github.com/m3db/m3/src/query/test/executor"
    36  	"github.com/m3db/m3/src/query/test/transformtest"
    37  	"github.com/m3db/m3/src/query/ts"
    38  	xtest "github.com/m3db/m3/src/x/test"
    39  	xtime "github.com/m3db/m3/src/x/time"
    40  
    41  	"github.com/golang/mock/gomock"
    42  	"github.com/stretchr/testify/assert"
    43  	"github.com/stretchr/testify/require"
    44  )
    45  
    46  var nan = math.NaN()
    47  
    48  type testCase struct {
    49  	name        string
    50  	opType      string
    51  	vals        [][]float64
    52  	expected    [][]float64
    53  	withWarning bool
    54  }
    55  
    56  type opGenerator func(t *testing.T, tc testCase) transform.Params
    57  
    58  const expectedWarning = "resolution larger than query range_" +
    59  	"range: 1m0s, resolutions: 1h0m0s, 1m1s"
    60  
    61  func buildMetadata() block.ResultMetadata {
    62  	resultMeta := block.NewResultMetadata()
    63  	resultMeta.Resolutions = []time.Duration{time.Second, time.Minute}
    64  
    65  	return resultMeta
    66  }
    67  
    68  func buildWarningMetadata() block.ResultMetadata {
    69  	resultMeta := buildMetadata()
    70  	resultMeta.Resolutions = append(resultMeta.Resolutions,
    71  		time.Second*61, time.Hour)
    72  	return resultMeta
    73  }
    74  
    75  func verifyResultMetadata(t *testing.T, m block.ResultMetadata, exWarn bool) {
    76  	warnings := m.WarningStrings()
    77  	if !exWarn {
    78  		assert.Equal(t, 0, len(warnings))
    79  		return
    80  	}
    81  
    82  	require.Equal(t, 1, len(warnings))
    83  	assert.Equal(t, expectedWarning, warnings[0])
    84  }
    85  
    86  func testTemporalFunc(t *testing.T, opGen opGenerator, tests []testCase) {
    87  	for _, tt := range tests {
    88  		for _, runBatched := range []bool{true, false} {
    89  			name := tt.name + "_unbatched"
    90  			if runBatched {
    91  				name = tt.name + "_batched"
    92  			}
    93  			t.Run(name, func(t *testing.T) {
    94  				values, bounds := test.GenerateValuesAndBounds(tt.vals, nil)
    95  				boundStart := bounds.Start
    96  
    97  				seriesMetas := []block.SeriesMeta{
    98  					{
    99  						Name: []byte("s1"),
   100  						Tags: models.EmptyTags().AddTags([]models.Tag{{
   101  							Name:  []byte("t1"),
   102  							Value: []byte("v1"),
   103  						}}).SetName([]byte("foobar")),
   104  					},
   105  					{
   106  						Name: []byte("s2"),
   107  						Tags: models.EmptyTags().AddTags([]models.Tag{{
   108  							Name:  []byte("t1"),
   109  							Value: []byte("v2"),
   110  						}}).SetName([]byte("foobar")),
   111  					},
   112  				}
   113  
   114  				resultMeta := buildMetadata()
   115  				if tt.withWarning {
   116  					resultMeta = buildWarningMetadata()
   117  				}
   118  
   119  				bl := test.NewUnconsolidatedBlockFromDatapointsWithMeta(models.Bounds{
   120  					Start:    bounds.Start.Add(-2 * bounds.Duration),
   121  					Duration: bounds.Duration * 2,
   122  					StepSize: bounds.StepSize,
   123  				}, seriesMetas, resultMeta, values, runBatched)
   124  
   125  				c, sink := executor.NewControllerWithSink(parser.NodeID(rune(1)))
   126  				baseOp := opGen(t, tt)
   127  				node := baseOp.Node(c, transformtest.Options(t, transform.OptionsParams{
   128  					TimeSpec: transform.TimeSpec{
   129  						Start: boundStart.Add(-2 * bounds.Duration),
   130  						End:   bounds.End(),
   131  						Step:  time.Second,
   132  					},
   133  				}))
   134  
   135  				err := node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)), bl)
   136  				require.NoError(t, err)
   137  
   138  				compare.EqualsWithNansWithDelta(t, tt.expected, sink.Values, 0.0001)
   139  				metaOne := block.SeriesMeta{
   140  					Name: []byte("{t1=\"v1\"}"),
   141  					Tags: models.EmptyTags().AddTags([]models.Tag{{
   142  						Name:  []byte("t1"),
   143  						Value: []byte("v1"),
   144  					}}),
   145  				}
   146  
   147  				metaTwo := block.SeriesMeta{
   148  					Name: []byte("{t1=\"v2\"}"),
   149  					Tags: models.EmptyTags().AddTags([]models.Tag{{
   150  						Name:  []byte("t1"),
   151  						Value: []byte("v2"),
   152  					}})}
   153  
   154  				// The last_over_time function acts like offset;
   155  				// thus, it should keep the metric name.
   156  				// For all other functions,
   157  				// name should be dropped from series tags,
   158  				// and the name should be the updated ID.
   159  				var expectedSeriesMetas []block.SeriesMeta
   160  				if tt.opType != LastType {
   161  					expectedSeriesMetas = []block.SeriesMeta{metaOne, metaTwo}
   162  				} else {
   163  					expectedSeriesMetas = seriesMetas
   164  				}
   165  				require.Equal(t, expectedSeriesMetas, sink.Metas)
   166  			})
   167  		}
   168  	}
   169  }
   170  
   171  func TestGetIndicesError(t *testing.T) {
   172  	size := 10
   173  	now := xtime.Now().Truncate(time.Minute)
   174  	dps := make([]ts.Datapoint, size)
   175  	s := int64(time.Second)
   176  	for i := range dps {
   177  		dps[i] = ts.Datapoint{
   178  			Timestamp: now.Add(time.Duration(int(s) * i)),
   179  			Value:     float64(i),
   180  		}
   181  	}
   182  
   183  	l, r, ok := getIndices(dps, 0, 0, -1)
   184  	require.Equal(t, -1, l)
   185  	require.Equal(t, -1, r)
   186  	require.False(t, ok)
   187  
   188  	l, r, ok = getIndices(dps, 0, 0, size)
   189  	require.Equal(t, -1, l)
   190  	require.Equal(t, -1, r)
   191  	require.False(t, ok)
   192  
   193  	pastBound := now.Add(time.Hour)
   194  	l, r, ok = getIndices(dps, pastBound, pastBound+10, 0)
   195  	require.Equal(t, 0, l)
   196  	require.Equal(t, 10, r)
   197  	require.False(t, ok)
   198  }
   199  
   200  var _ block.SeriesIter = (*dummySeriesIter)(nil)
   201  
   202  type dummySeriesIter struct {
   203  	metas []block.SeriesMeta
   204  	vals  []float64
   205  	idx   int
   206  }
   207  
   208  func (it *dummySeriesIter) SeriesMeta() []block.SeriesMeta {
   209  	return it.metas
   210  }
   211  
   212  func (it *dummySeriesIter) SeriesCount() int {
   213  	return len(it.metas)
   214  }
   215  
   216  func (it *dummySeriesIter) Current() block.UnconsolidatedSeries {
   217  	return block.NewUnconsolidatedSeries(
   218  		ts.Datapoints{ts.Datapoint{Value: it.vals[it.idx]}},
   219  		it.metas[it.idx],
   220  		block.UnconsolidatedSeriesStats{},
   221  	)
   222  }
   223  
   224  func (it *dummySeriesIter) Next() bool {
   225  	if it.idx >= len(it.metas)-1 {
   226  		return false
   227  	}
   228  
   229  	it.idx++
   230  	return true
   231  }
   232  
   233  func (it *dummySeriesIter) Err() error {
   234  	return nil
   235  }
   236  
   237  func (it *dummySeriesIter) Close() {
   238  	//no-op
   239  }
   240  
   241  func TestParallelProcess(t *testing.T) {
   242  	t.Run("no expected warning", func(t *testing.T) { testParallelProcess(t, false) })
   243  	t.Run("expected warning", func(t *testing.T) { testParallelProcess(t, true) })
   244  }
   245  
   246  func testParallelProcess(t *testing.T, warning bool) {
   247  	ctrl := xtest.NewController(t)
   248  	defer ctrl.Finish()
   249  
   250  	tagName := "tag"
   251  	c, sink := executor.NewControllerWithSink(parser.NodeID(rune(1)))
   252  	aggProcess := aggProcessor{
   253  		aggFunc: func(fs []float64) float64 {
   254  			require.Equal(t, 1, len(fs))
   255  			return fs[0]
   256  		},
   257  	}
   258  
   259  	node := baseNode{
   260  		controller:    c,
   261  		op:            baseOp{duration: time.Minute},
   262  		makeProcessor: aggProcess,
   263  		transformOpts: transform.Options{},
   264  	}
   265  
   266  	stepSize := time.Minute
   267  	bl := block.NewMockBlock(ctrl)
   268  	resultMeta := buildMetadata()
   269  	if warning {
   270  		resultMeta = buildWarningMetadata()
   271  	}
   272  
   273  	bl.EXPECT().Meta().Return(block.Metadata{
   274  		ResultMetadata: resultMeta,
   275  		Bounds: models.Bounds{
   276  			StepSize: stepSize,
   277  			Duration: stepSize,
   278  		}}).AnyTimes()
   279  
   280  	numSeries := 10
   281  	seriesMetas := make([]block.SeriesMeta, 0, numSeries)
   282  	vals := make([]float64, 0, numSeries)
   283  	for i := 0; i < numSeries; i++ {
   284  		number := fmt.Sprint(i)
   285  		name := []byte(fmt.Sprintf("%d_should_not_appear_after_func_applied", i))
   286  		meta := block.SeriesMeta{
   287  			Name: []byte(number),
   288  			Tags: models.MustMakeTags(tagName, number).SetName(name),
   289  		}
   290  
   291  		seriesMetas = append(seriesMetas, meta)
   292  		vals = append(vals, float64(i))
   293  	}
   294  
   295  	fullIter := &dummySeriesIter{
   296  		idx:   -1,
   297  		vals:  vals,
   298  		metas: seriesMetas,
   299  	}
   300  
   301  	bl.EXPECT().SeriesIter().Return(fullIter, nil).MaxTimes(1)
   302  
   303  	numBatches := 3
   304  	blockMetas := make([][]block.SeriesMeta, 0, numBatches)
   305  	blockVals := make([][]float64, 0, numBatches)
   306  	for i := 0; i < numBatches; i++ {
   307  		l := numSeries/numBatches + 1
   308  		blockMetas = append(blockMetas, make([]block.SeriesMeta, 0, l))
   309  		blockVals = append(blockVals, make([]float64, 0, l))
   310  	}
   311  
   312  	for i, meta := range seriesMetas {
   313  		idx := i % numBatches
   314  		blockMetas[idx] = append(blockMetas[idx], meta)
   315  		blockVals[idx] = append(blockVals[idx], float64(i))
   316  	}
   317  
   318  	batches := make([]block.SeriesIterBatch, 0, numBatches)
   319  	for i := 0; i < numBatches; i++ {
   320  		iter := &dummySeriesIter{
   321  			idx:   -1,
   322  			vals:  blockVals[i],
   323  			metas: blockMetas[i],
   324  		}
   325  
   326  		batches = append(batches, block.SeriesIterBatch{
   327  			Iter: iter,
   328  			Size: len(blockVals[i]),
   329  		})
   330  	}
   331  
   332  	bl.EXPECT().MultiSeriesIter(gomock.Any()).Return(batches, nil).MaxTimes(1)
   333  	bl.EXPECT().Close().Times(1)
   334  
   335  	err := node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)), bl)
   336  	require.NoError(t, err)
   337  
   338  	expected := []float64{
   339  		0, 3, 6, 9,
   340  		1, 4, 7,
   341  		2, 5, 8,
   342  	}
   343  
   344  	for i, v := range sink.Values {
   345  		assert.Equal(t, expected[i], v[0])
   346  	}
   347  
   348  	for i, m := range sink.Metas {
   349  		expected := fmt.Sprint(expected[i])
   350  		expectedName := fmt.Sprintf("{tag=\"%s\"}", expected)
   351  		assert.Equal(t, expectedName, string(m.Name))
   352  		require.Equal(t, 1, m.Tags.Len())
   353  		tag, found := m.Tags.Get([]byte(tagName))
   354  		require.True(t, found)
   355  		assert.Equal(t, expected, string(tag))
   356  	}
   357  
   358  	verifyResultMetadata(t, sink.Meta.ResultMetadata, warning)
   359  }