github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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/m3db/m3/src/query/graphite/common" 28 "github.com/m3db/m3/src/query/graphite/storage" 29 "github.com/m3db/m3/src/query/graphite/ts" 30 xgomock "github.com/m3db/m3/src/x/test" 31 32 "github.com/golang/mock/gomock" 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 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 }