github.com/m3db/m3@v1.5.0/src/query/graphite/native/alias_functions_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/golang/mock/gomock"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"github.com/m3db/m3/src/query/graphite/common"
    32  	"github.com/m3db/m3/src/query/graphite/storage"
    33  	"github.com/m3db/m3/src/query/graphite/ts"
    34  	xgomock "github.com/m3db/m3/src/x/test"
    35  )
    36  
    37  func TestAlias(t *testing.T) {
    38  	ctx := common.NewTestContext()
    39  	defer ctx.Close()
    40  
    41  	now := time.Now()
    42  	values := ts.NewConstantValues(ctx, 10.0, 1000, 10)
    43  	series := []*ts.Series{
    44  		ts.NewSeries(ctx, "bender", now, values),
    45  		ts.NewSeries(ctx, "fry", now, values),
    46  		ts.NewSeries(ctx, "leela", now, values),
    47  	}
    48  	a := "farnsworth"
    49  
    50  	results, err := alias(nil, singlePathSpec{
    51  		Values: series,
    52  	}, a)
    53  	require.NoError(t, err)
    54  	require.Equal(t, len(series), results.Len())
    55  	for _, s := range results.Values {
    56  		assert.Equal(t, a, s.Name())
    57  	}
    58  }
    59  
    60  func TestAliasSubWithNoBackReference(t *testing.T) {
    61  	ctx := common.NewTestContext()
    62  	defer ctx.Close()
    63  
    64  	now := time.Now()
    65  	values := ts.NewConstantValues(ctx, 10.0, 1000, 10)
    66  	series := []*ts.Series{
    67  		ts.NewSeries(ctx, "stats.foo.counts.bar.baz.quail08-foo.qux.qaz.calls.success.quux.john.Frank--submit", now, values),
    68  		ts.NewSeries(ctx, "stats.foo.counts.bar.baz.quail15-foo.qux.qaz.calls.success.quux.bob.BOB--nosub", now, values),
    69  		ts.NewSeries(ctx, "stats.foo.counts.bar.baz.quail01-foo.qux.qaz.calls.success.quux.bob.BOB--woop", now, values),
    70  		ts.NewSeries(ctx, "stats.foo.counts.bar.baz.quail08-foo.qux.qaz.calls.success.quacks.john.Frank--submit", now, values),
    71  		ts.NewSeries(ctx, "stats.foo.counts.bar.baz.quail08-foo.qux.qaz.calls.success.arbiter", now, values), // doesn't match regex
    72  	}
    73  
    74  	results, err := aliasSub(ctx, singlePathSpec{
    75  		Values: series,
    76  	}, "success\\.([-_\\w]+)\\.([-_\\w]+)\\.([-_\\w]+)", "$1-$3")
    77  	require.NoError(t, err)
    78  	require.Equal(t, 5, results.Len())
    79  
    80  	var names, pathExpr []string
    81  	for _, s := range results.Values {
    82  		names = append(names, s.Name())
    83  		pathExpr = append(pathExpr, s.Specification)
    84  	}
    85  
    86  	assert.Equal(t, []string{
    87  		"stats.foo.counts.bar.baz.quail08-foo.qux.qaz.calls.quux-Frank--submit",
    88  		"stats.foo.counts.bar.baz.quail15-foo.qux.qaz.calls.quux-BOB--nosub",
    89  		"stats.foo.counts.bar.baz.quail01-foo.qux.qaz.calls.quux-BOB--woop",
    90  		"stats.foo.counts.bar.baz.quail08-foo.qux.qaz.calls.quacks-Frank--submit",
    91  		"stats.foo.counts.bar.baz.quail08-foo.qux.qaz.calls.success.arbiter", // unchanged
    92  	}, names)
    93  
    94  	// Path expressions should remain unchanged
    95  	assert.Equal(t, []string{
    96  		"stats.foo.counts.bar.baz.quail08-foo.qux.qaz.calls.success.quux.john.Frank--submit",
    97  		"stats.foo.counts.bar.baz.quail15-foo.qux.qaz.calls.success.quux.bob.BOB--nosub",
    98  		"stats.foo.counts.bar.baz.quail01-foo.qux.qaz.calls.success.quux.bob.BOB--woop",
    99  		"stats.foo.counts.bar.baz.quail08-foo.qux.qaz.calls.success.quacks.john.Frank--submit",
   100  		"stats.foo.counts.bar.baz.quail08-foo.qux.qaz.calls.success.arbiter",
   101  	}, pathExpr)
   102  }
   103  
   104  func TestAliasSubWithBackReferences(t *testing.T) {
   105  	ctx := common.NewTestContext()
   106  	defer ctx.Close()
   107  
   108  	now := time.Now()
   109  	values := []float64{1.0, 2.0, 3.0, 4.0}
   110  
   111  	input := struct {
   112  		name     string
   113  		pattern  string
   114  		replace  string
   115  		expected string
   116  	}{
   117  		"stats.foo.timers.qaz.qaz.views.quail.end_to_end_latency.p95",
   118  		`stats.(.*).timers.\w+.qaz.views.quail.(.*)`,
   119  		`\1.\2`,
   120  		"foo.end_to_end_latency.p95",
   121  	}
   122  	series := []*ts.Series{ts.NewSeries(ctx, input.name, now, common.NewTestSeriesValues(ctx, 1000, values))}
   123  	results, err := aliasSub(ctx, singlePathSpec{
   124  		Values: series,
   125  	}, input.pattern, input.replace)
   126  	require.NoError(t, err)
   127  	expected := []common.TestSeries{
   128  		{Name: input.expected, Data: values},
   129  	}
   130  	common.CompareOutputsAndExpected(t, 1000, now, expected, results.Values)
   131  
   132  	results, err = aliasSub(ctx, singlePathSpec{
   133  		Values: series,
   134  	}, input.pattern, `\1.\3`)
   135  	require.Error(t, err)
   136  	require.Nil(t, results.Values)
   137  }
   138  
   139  func TestAliasByMetric(t *testing.T) {
   140  	ctx := common.NewTestContext()
   141  	defer ctx.Close()
   142  
   143  	now := time.Now()
   144  	values := ts.NewConstantValues(ctx, 10.0, 1000, 10)
   145  
   146  	series := []*ts.Series{
   147  		ts.NewSeries(ctx, "foo.bar.baz.foo01-foo.writes.success", now, values),
   148  		ts.NewSeries(ctx, "foo.bar.baz.foo02-foo.writes.success.P99", now, values),
   149  		ts.NewSeries(ctx, "foo.bar.baz.foo03-foo.writes.success.P75", now, values),
   150  		ts.NewSeries(ctx, "scale(stats.foobar.gauges.quazqux.latency_minutes.foo, 60.123)", now, values),
   151  	}
   152  
   153  	results, err := aliasByMetric(ctx, singlePathSpec{
   154  		Values: series,
   155  	})
   156  	require.NoError(t, err)
   157  	require.Equal(t, len(series), len(results.Values))
   158  	assert.Equal(t, "success", results.Values[0].Name())
   159  	assert.Equal(t, "P99", results.Values[1].Name())
   160  	assert.Equal(t, "P75", results.Values[2].Name())
   161  	assert.Equal(t, "foo", results.Values[3].Name())
   162  }
   163  
   164  func TestAliasByNode(t *testing.T) {
   165  	ctx := common.NewTestContext()
   166  	defer ctx.Close()
   167  
   168  	now := time.Now()
   169  	values := ts.NewConstantValues(ctx, 10.0, 1000, 10)
   170  
   171  	series := []*ts.Series{
   172  		ts.NewSeries(ctx, "foo.bar.baz.foo01-foo.writes.success", now, values),
   173  		ts.NewSeries(ctx, "foo.bar.baz.foo02-foo.writes.success.P99", now, values),
   174  		ts.NewSeries(ctx, "foo.bar.baz.foo03-foo.writes.success.P75", now, values),
   175  	}
   176  
   177  	results, err := aliasByNode(ctx, singlePathSpec{
   178  		Values: series,
   179  	}, 3, 5, 6)
   180  	require.NoError(t, err)
   181  	require.Equal(t, len(series), results.Len())
   182  	assert.Equal(t, "foo01-foo.success", results.Values[0].Name())
   183  	assert.Equal(t, "foo02-foo.success.P99", results.Values[1].Name())
   184  	assert.Equal(t, "foo03-foo.success.P75", results.Values[2].Name())
   185  
   186  	results, err = aliasByNode(nil, singlePathSpec{
   187  		Values: series,
   188  	}, -1)
   189  	require.NoError(t, err)
   190  	require.Equal(t, len(series), results.Len())
   191  	assert.Equal(t, "success", results.Values[0].Name())
   192  	assert.Equal(t, "P99", results.Values[1].Name())
   193  	assert.Equal(t, "P75", results.Values[2].Name())
   194  }
   195  
   196  func TestAliasByNodeWithComposition(t *testing.T) {
   197  	ctx := common.NewTestContext()
   198  	defer ctx.Close()
   199  
   200  	now := time.Now()
   201  	values := ts.NewConstantValues(ctx, 10.0, 1000, 10)
   202  	series := []*ts.Series{
   203  		ts.NewSeries(ctx, "derivative(servers.bob02-foo.cpu.load_5)", now, values),
   204  		ts.NewSeries(ctx, "derivative(derivative(servers.bob02-foo.cpu.load_5))", now, values),
   205  		ts.NewSeries(ctx, "fooble", now, values),
   206  	}
   207  	results, err := aliasByNode(ctx, singlePathSpec{
   208  		Values: series,
   209  	}, 0, 1)
   210  	require.NoError(t, err)
   211  	require.Equal(t, len(series), results.Len())
   212  	assert.Equal(t, "servers.bob02-foo", results.Values[0].Name())
   213  	assert.Equal(t, "servers.bob02-foo", results.Values[1].Name())
   214  	assert.Equal(t, "fooble", results.Values[2].Name())
   215  }
   216  
   217  func TestAliasByNodeWithManyPathExpressions(t *testing.T) {
   218  	ctx := common.NewTestContext()
   219  	defer func() { _ = ctx.Close() }()
   220  
   221  	now := time.Now()
   222  	values := ts.NewConstantValues(ctx, 10.0, 1000, 10)
   223  	series := []*ts.Series{
   224  		ts.NewSeries(ctx, "sumSeries(servers.bob02-foo.cpu.load_5,servers.bob01-foo.cpu.load_5)", now, values),
   225  		ts.NewSeries(ctx, "sumSeries(sumSeries(servers.bob04-foo.cpu.load_5,servers.bob03-foo.cpu.load_5))", now, values),
   226  	}
   227  	results, err := aliasByNode(ctx, singlePathSpec{
   228  		Values: series,
   229  	}, 0, 1)
   230  	require.NoError(t, err)
   231  	require.Equal(t, len(series), results.Len())
   232  	assert.Equal(t, "servers.bob02-foo", results.Values[0].Name())
   233  	assert.Equal(t, "servers.bob04-foo", results.Values[1].Name())
   234  }
   235  
   236  func TestAliasByNodeWitCallSubExpressions(t *testing.T) {
   237  	ctx := common.NewTestContext()
   238  	defer func() { _ = ctx.Close() }()
   239  
   240  	now := time.Now()
   241  	values := ts.NewConstantValues(ctx, 10.0, 1000, 10)
   242  	series := []*ts.Series{
   243  		ts.NewSeries(ctx, "asPercent(foo01,sumSeries(bar,baz))", now, values),
   244  		ts.NewSeries(ctx, "asPercent(foo02,sumSeries(bar,baz))", now, values),
   245  	}
   246  	results, err := aliasByNode(ctx, singlePathSpec{
   247  		Values: series,
   248  	}, 0)
   249  	require.NoError(t, err)
   250  	require.Equal(t, len(series), results.Len())
   251  	assert.Equal(t, "foo01", results.Values[0].Name())
   252  	assert.Equal(t, "foo02", results.Values[1].Name())
   253  }
   254  
   255  // TestAliasByNodeAndTimeShift tests that the output of timeshift properly
   256  // quotes the time shift arg so that it appears as a string and can be used to find
   257  // the inner path expression without failing compilation when aliasByNode finds the
   258  // first path element.
   259  // nolint: dupl
   260  func TestAliasByNodeAndTimeShift(t *testing.T) {
   261  	ctrl := xgomock.NewController(t)
   262  	defer ctrl.Finish()
   263  
   264  	store := storage.NewMockStorage(ctrl)
   265  
   266  	engine := NewEngine(store, CompileOptions{})
   267  
   268  	ctx := common.NewContext(common.ContextOptions{Start: time.Now().Add(-1 * time.Hour), End: time.Now(), Engine: engine})
   269  
   270  	stepSize := int((10 * time.Minute) / time.Millisecond)
   271  	store.EXPECT().FetchByQuery(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
   272  		buildTestSeriesFn(stepSize, "foo.bar.q.zed", "foo.bar.g.zed",
   273  			"foo.bar.x.zed"))
   274  
   275  	expr, err := engine.Compile("aliasByNode(timeShift(foo.bar.*.zed,'-7d'), 2)")
   276  	require.NoError(t, err)
   277  
   278  	_, err = expr.Execute(ctx)
   279  	require.NoError(t, err)
   280  }
   281  
   282  // TestAliasByNodeAndPatternThatMatchesFunctionName test the case where the
   283  // return of a sub-expression ends up as a function name (i.e. identity) but
   284  // is not a function call it's simply a pattern returned from result of
   285  // something like groupByNodes.
   286  // nolint: dupl
   287  func TestAliasByNodeAndPatternThatMatchesFunctionName(t *testing.T) {
   288  	ctrl := xgomock.NewController(t)
   289  	defer ctrl.Finish()
   290  
   291  	store := storage.NewMockStorage(ctrl)
   292  
   293  	engine := NewEngine(store, CompileOptions{})
   294  
   295  	ctx := common.NewContext(common.ContextOptions{Start: time.Now().Add(-1 * time.Hour), End: time.Now(), Engine: engine})
   296  
   297  	stepSize := int((10 * time.Minute) / time.Millisecond)
   298  	store.EXPECT().FetchByQuery(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
   299  		buildTestSeriesFn(stepSize,
   300  			"foo.bar.a.zed",
   301  			"foo.bar.b.zed",
   302  			"foo.bar.identity.zed",
   303  		))
   304  
   305  	expr, err := engine.Compile("aliasByNode(summarize(groupByNode(foo.bar.*.zed, 2, 'average'), '5min', 'avg'), 0)")
   306  	require.NoError(t, err)
   307  
   308  	_, err = expr.Execute(ctx)
   309  	require.NoError(t, err)
   310  }
   311  
   312  // TestGroupByNodeAndAliasMetric tests the case when compiling an identifier
   313  // immediately in groupByNode when doing meta series grouping and finding the
   314  // first inner metrics path.
   315  // It tries to compile as function call but needs to back out when if a function
   316  // matches but it's actually just a pattern instead.
   317  // nolint: dupl
   318  func TestGroupByNodeAndAliasMetric(t *testing.T) {
   319  	ctrl := xgomock.NewController(t)
   320  	defer ctrl.Finish()
   321  
   322  	store := storage.NewMockStorage(ctrl)
   323  
   324  	engine := NewEngine(store, CompileOptions{})
   325  
   326  	ctx := common.NewContext(common.ContextOptions{Start: time.Now().Add(-1 * time.Hour), End: time.Now(), Engine: engine})
   327  
   328  	stepSize := int((10 * time.Minute) / time.Millisecond)
   329  	store.EXPECT().FetchByQuery(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
   330  		buildTestSeriesFn(stepSize,
   331  			"handler.rpc.foo.request.lat.p95",
   332  			"handler.rpc.foo.request.lat.max",
   333  			"handler.rpc.foo.request.lat.mean",
   334  		))
   335  
   336  	expr, err := engine.Compile("groupByNode(aliasByMetric(handler.rpc.*.request.lat.{p*,max,mean}), -1, 'max')")
   337  	require.NoError(t, err)
   338  
   339  	_, err = expr.Execute(ctx)
   340  	require.NoError(t, err)
   341  }
   342  
   343  // TestGroupByNodeAndAliasSubAndScopeMetric ensures that partial expressions
   344  // that should not be able to be successfully compiled can still have their path
   345  // expression extracted for use in functions like groupByNode.
   346  // nolint: dupl
   347  func TestGroupByNodeAndAliasSubAndScopeMetric(t *testing.T) {
   348  	ctrl := xgomock.NewController(t)
   349  	defer ctrl.Finish()
   350  
   351  	store := storage.NewMockStorage(ctrl)
   352  
   353  	engine := NewEngine(store, CompileOptions{})
   354  
   355  	ctx := common.NewContext(common.ContextOptions{Start: time.Now().Add(-1 * time.Hour), End: time.Now(), Engine: engine})
   356  
   357  	stepSize := int((10 * time.Minute) / time.Millisecond)
   358  	store.EXPECT().FetchByQuery(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
   359  		buildTestSeriesFn(stepSize,
   360  			"foo.bar.a.zed",
   361  			"foo.bar.b.zed",
   362  		))
   363  
   364  	// Note: After the aliasSub call the result will be "a.zed,0.1)" and "b.zed,0.1)"
   365  	// for each series name, this test ensures that partial expressions can still
   366  	// successfully have their first fetch expression extracted.
   367  	expr, err := engine.Compile("groupByNode(aliasSub(scale(foo.bar.*.zed, 0.1), \".*bar.(.*)\", '\\1'),0)")
   368  	require.NoError(t, err)
   369  
   370  	seriesList, err := expr.Execute(ctx)
   371  	require.NoError(t, err)
   372  
   373  	require.Equal(t, 2, seriesList.Len())
   374  	// Sort before check names.
   375  	seriesList, err = sortByName(ctx, singlePathSpec(seriesList), false, false)
   376  	require.NoError(t, err)
   377  	require.Equal(t, seriesList.Values[0].Name(), "a")
   378  	require.Equal(t, seriesList.Values[1].Name(), "b")
   379  }