github.com/m3db/m3@v1.5.0/src/cmd/services/m3comparator/main/querier_test.go (about) 1 // Copyright (c) 2020 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 main 22 23 import ( 24 "context" 25 "testing" 26 "time" 27 28 "github.com/m3db/m3/src/cmd/services/m3comparator/main/parser" 29 "github.com/m3db/m3/src/dbnode/encoding" 30 "github.com/m3db/m3/src/query/models" 31 "github.com/m3db/m3/src/query/storage" 32 "github.com/m3db/m3/src/x/ident" 33 xtest "github.com/m3db/m3/src/x/test" 34 xtime "github.com/m3db/m3/src/x/time" 35 36 "github.com/golang/mock/gomock" 37 "github.com/stretchr/testify/assert" 38 "github.com/stretchr/testify/require" 39 ) 40 41 type testSeriesLoadHandler struct { 42 iters encoding.SeriesIterators 43 } 44 45 func (h *testSeriesLoadHandler) getSeriesIterators( 46 name string) (encoding.SeriesIterators, error) { 47 return h.iters, nil 48 } 49 50 var _ seriesLoadHandler = (*testSeriesLoadHandler)(nil) 51 52 type tagMap map[string]string 53 54 var ( 55 iteratorOpts = parser.Options{ 56 EncoderPool: encoderPool, 57 IteratorPools: iterPools, 58 TagOptions: tagOptions, 59 InstrumentOptions: iOpts, 60 } 61 metricNameTag = string(iteratorOpts.TagOptions.MetricName()) 62 ) 63 64 const ( 65 blockSize = time.Hour * 12 66 defaultResolution = time.Second * 30 67 metricsName = "preloaded" 68 predefinedSeriesCount = 10 69 histogramBucketCount = 4 70 ) 71 72 func TestFetchCompressed(t *testing.T) { 73 tests := []struct { 74 name string 75 queryTagName string 76 queryTagValue string 77 expectedCount int 78 }{ 79 { 80 name: "querying by metric name returns preloaded data", 81 queryTagName: metricNameTag, 82 queryTagValue: metricsName, 83 expectedCount: predefinedSeriesCount, 84 }, 85 { 86 name: "querying without metric name just by other tag returns preloaded data", 87 queryTagName: "tag1", 88 queryTagValue: "test2", 89 expectedCount: 4, 90 }, 91 } 92 93 for _, tt := range tests { 94 t.Run(tt.name, func(t *testing.T) { 95 ctrl := xtest.NewController(t) 96 defer ctrl.Finish() 97 98 query := matcherQuery(t, tt.queryTagName, tt.queryTagValue) 99 querier := setupQuerier(ctrl, query) 100 101 result, cleanup, err := querier.FetchCompressedResult(context.TODO(), query, nil) 102 assert.NoError(t, err) 103 defer cleanup() 104 105 assert.Equal(t, tt.expectedCount, len(result.SeriesIterators())) 106 }) 107 } 108 } 109 110 func TestGenerateRandomSeries(t *testing.T) { 111 tests := []struct { 112 name string 113 givenQuery *storage.FetchQuery 114 wantSeries []tagMap 115 }{ 116 { 117 name: "querying nonexistent_metric returns empty", 118 givenQuery: matcherQuery(t, metricNameTag, "nonexistent_metric"), 119 wantSeries: []tagMap{}, 120 }, 121 { 122 name: "querying nonexistant returns empty", 123 givenQuery: matcherQuery(t, metricNameTag, "nonexistant"), 124 wantSeries: []tagMap{}, 125 }, 126 { 127 name: "random data for known metrics", 128 givenQuery: matcherQuery(t, metricNameTag, "quail"), 129 wantSeries: []tagMap{ 130 { 131 metricNameTag: "quail", 132 "foobar": "qux", 133 "name": "quail", 134 }, 135 }, 136 }, 137 { 138 name: "a hardcoded list of metrics", 139 givenQuery: matcherQuery(t, metricNameTag, "unknown"), 140 wantSeries: []tagMap{ 141 { 142 metricNameTag: "foo", 143 "foobar": "qux", 144 "name": "foo", 145 }, 146 { 147 metricNameTag: "bar", 148 "foobar": "qux", 149 "name": "bar", 150 }, 151 { 152 metricNameTag: "quail", 153 "foobar": "qux", 154 "name": "quail", 155 }, 156 }, 157 }, 158 { 159 name: "a given number of single series metrics", 160 givenQuery: matcherQuery(t, "gen", "2"), 161 wantSeries: []tagMap{ 162 { 163 metricNameTag: "foo_0", 164 "foobar": "qux", 165 "name": "foo_0", 166 }, 167 { 168 metricNameTag: "foo_1", 169 "foobar": "qux", 170 "name": "foo_1", 171 }, 172 }, 173 }, 174 { 175 name: "single metrics with a given number of series", 176 givenQuery: matcherQuery(t, metricNameTag, "multi_4"), 177 wantSeries: []tagMap{ 178 { 179 metricNameTag: "multi_4", 180 "const": "x", 181 "id": "0", 182 "parity": "0", 183 }, 184 { 185 metricNameTag: "multi_4", 186 "const": "x", 187 "id": "1", 188 "parity": "1", 189 }, 190 { 191 metricNameTag: "multi_4", 192 "const": "x", 193 "id": "2", 194 "parity": "0", 195 }, 196 { 197 metricNameTag: "multi_4", 198 "const": "x", 199 "id": "3", 200 "parity": "1", 201 }, 202 }, 203 }, 204 { 205 name: "histogram metrics", 206 givenQuery: matcherQuery(t, metricNameTag, "histogram_2_bucket"), 207 wantSeries: []tagMap{ 208 { 209 metricNameTag: "histogram_2_bucket", 210 "const": "x", 211 "id": "0", 212 "parity": "0", 213 "le": "1", 214 }, 215 { 216 metricNameTag: "histogram_2_bucket", 217 "const": "x", 218 "id": "0", 219 "parity": "0", 220 "le": "10", 221 }, 222 { 223 metricNameTag: "histogram_2_bucket", 224 "const": "x", 225 "id": "0", 226 "parity": "0", 227 "le": "100", 228 }, 229 { 230 metricNameTag: "histogram_2_bucket", 231 "const": "x", 232 "id": "0", 233 "parity": "0", 234 "le": "+Inf", 235 }, 236 237 { 238 metricNameTag: "histogram_2_bucket", 239 "const": "x", 240 "id": "1", 241 "parity": "1", 242 "le": "1", 243 }, 244 { 245 metricNameTag: "histogram_2_bucket", 246 "const": "x", 247 "id": "1", 248 "parity": "1", 249 "le": "10", 250 }, 251 { 252 metricNameTag: "histogram_2_bucket", 253 "const": "x", 254 "id": "1", 255 "parity": "1", 256 "le": "100", 257 }, 258 { 259 metricNameTag: "histogram_2_bucket", 260 "const": "x", 261 "id": "1", 262 "parity": "1", 263 "le": "+Inf", 264 }, 265 }, 266 }, 267 { 268 name: "apply tag filter", 269 givenQuery: and( 270 matcherQuery(t, metricNameTag, "multi_5"), 271 matcherQuery(t, "parity", "1")), 272 wantSeries: []tagMap{ 273 { 274 metricNameTag: "multi_5", 275 "const": "x", 276 "id": "1", 277 "parity": "1", 278 }, 279 { 280 metricNameTag: "multi_5", 281 "const": "x", 282 "id": "3", 283 "parity": "1", 284 }, 285 }, 286 }, 287 } 288 289 for _, tt := range tests { 290 t.Run(tt.name, func(t *testing.T) { 291 ctrl := xtest.NewController(t) 292 defer ctrl.Finish() 293 294 querier, err := setupRandomGenQuerier(ctrl) 295 assert.NoError(t, err) 296 297 result, cleanup, err := querier.FetchCompressedResult(context.TODO(), tt.givenQuery, nil) 298 assert.NoError(t, err) 299 defer cleanup() 300 301 iters := result.SeriesIterators() 302 require.Equal(t, len(tt.wantSeries), len(iters)) 303 for i, expectedTags := range tt.wantSeries { 304 iter := iters[i] 305 assert.Equal(t, expectedTags, extractTags(iter)) 306 assert.True(t, iter.Next(), "Must have some datapoints generated.") 307 } 308 }) 309 } 310 } 311 312 func TestHistogramBucketsAddUp(t *testing.T) { 313 ctrl := xtest.NewController(t) 314 defer ctrl.Finish() 315 316 querier, err := setupRandomGenQuerier(ctrl) 317 assert.NoError(t, err) 318 319 histogramQuery := matcherQuery(t, metricNameTag, "histogram_1_bucket") 320 result, cleanup, err := querier.FetchCompressedResult(context.TODO(), histogramQuery, nil) 321 assert.NoError(t, err) 322 defer cleanup() 323 324 iters := result.SeriesIterators() 325 require.Equal(t, histogramBucketCount, 326 len(iters), "number of histogram buckets") 327 328 iter0 := iters[0] 329 for iter0.Next() { 330 v0, t1, _ := iter0.Current() 331 for i := 1; i < histogramBucketCount; i++ { 332 iter := iters[i] 333 require.True(t, iter.Next(), "all buckets must have the same length") 334 vi, ti, _ := iter.Current() 335 assert.True(t, vi.Value >= v0.Value, "bucket values must be non decreasing") 336 assert.Equal(t, v0.TimestampNanos, vi.TimestampNanos, "bucket values timestamps must match") 337 assert.Equal(t, t1, ti) 338 } 339 } 340 341 for _, iter := range iters { 342 require.False(t, iter.Next(), "all buckets must have the same length") 343 } 344 } 345 346 func matcherQuery(t *testing.T, matcherName, matcherValue string) *storage.FetchQuery { 347 matcher, err := models.NewMatcher(models.MatchEqual, []byte(matcherName), []byte(matcherValue)) 348 assert.NoError(t, err) 349 350 now := time.Now() 351 352 return &storage.FetchQuery{ 353 TagMatchers: []models.Matcher{matcher}, 354 Start: now.Add(-time.Hour), 355 End: now, 356 } 357 } 358 359 func and(query1, query2 *storage.FetchQuery) *storage.FetchQuery { 360 return &storage.FetchQuery{ 361 TagMatchers: append(query1.TagMatchers, query2.TagMatchers...), 362 Start: query1.Start, 363 End: query1.End, 364 } 365 } 366 367 func extractTags(seriesIter encoding.SeriesIterator) tagMap { 368 tagsIter := seriesIter.Tags().Duplicate() 369 defer tagsIter.Close() 370 371 tags := make(tagMap) 372 for tagsIter.Next() { 373 tag := tagsIter.Current() 374 tags[tag.Name.String()] = tag.Value.String() 375 } 376 377 return tags 378 } 379 380 func setupQuerier(ctrl *gomock.Controller, query *storage.FetchQuery) *querier { 381 metricsTag := ident.NewTags(ident.Tag{ 382 Name: ident.BytesID(tagOptions.MetricName()), 383 Value: ident.BytesID(metricsName), 384 }, 385 ident.Tag{ 386 Name: ident.BytesID("tag1"), 387 Value: ident.BytesID("test"), 388 }, 389 ) 390 metricsTag2 := ident.NewTags(ident.Tag{ 391 Name: ident.BytesID(tagOptions.MetricName()), 392 Value: ident.BytesID(metricsName), 393 }, 394 ident.Tag{ 395 Name: ident.BytesID("tag1"), 396 Value: ident.BytesID("test2"), 397 }, 398 ) 399 400 iters := make([]encoding.SeriesIterator, 0, predefinedSeriesCount) 401 for i := 0; i < predefinedSeriesCount; i++ { 402 m := metricsTag 403 if i > 5 { 404 m = metricsTag2 405 } 406 iters = append(iters, encoding.NewSeriesIterator( 407 encoding.SeriesIteratorOptions{ 408 Namespace: ident.StringID("ns"), 409 Tags: ident.NewTagsIterator(m), 410 StartInclusive: xtime.ToUnixNano(query.Start), 411 EndExclusive: xtime.ToUnixNano(query.End), 412 }, nil)) 413 } 414 415 seriesIterators := encoding.NewMockSeriesIterators(ctrl) 416 seriesIterators.EXPECT().Len().Return(predefinedSeriesCount).MinTimes(1) 417 seriesIterators.EXPECT().Iters().Return(iters).Times(1) 418 seriesIterators.EXPECT().Close() 419 420 seriesLoader := &testSeriesLoadHandler{seriesIterators} 421 422 return &querier{iteratorOpts: iteratorOpts, handler: seriesLoader} 423 } 424 425 func setupRandomGenQuerier(ctrl *gomock.Controller) (*querier, error) { 426 iters := encoding.NewMockSeriesIterators(ctrl) 427 iters.EXPECT().Len().Return(0).AnyTimes() 428 429 emptySeriesLoader := &testSeriesLoadHandler{iters} 430 431 return newQuerier(iteratorOpts, emptySeriesLoader, blockSize, defaultResolution, histogramBucketCount) 432 }