github.com/m3db/m3@v1.5.0/src/dbnode/storage/index/aggregate_results_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 index 22 23 import ( 24 "sort" 25 "testing" 26 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 "github.com/uber-go/tally" 30 31 "github.com/m3db/m3/src/x/ident" 32 "github.com/m3db/m3/src/x/instrument" 33 xtest "github.com/m3db/m3/src/x/test" 34 ) 35 36 func entries(entries ...AggregateResultsEntry) []AggregateResultsEntry { return entries } 37 38 func genResultsEntry(field string, terms ...string) AggregateResultsEntry { 39 entryTerms := make([]ident.ID, 0, len(terms)) 40 for _, term := range terms { 41 entryTerms = append(entryTerms, ident.StringID(term)) 42 } 43 44 return AggregateResultsEntry{ 45 Field: ident.StringID(field), 46 Terms: entryTerms, 47 } 48 } 49 50 func toMap(res AggregateResults) map[string][]string { 51 entries := res.Map().Iter() 52 resultMap := make(map[string][]string, len(entries)) 53 for _, entry := range entries { //nolint:gocritic 54 terms := entry.value.Map().Iter() 55 resultTerms := make([]string, 0, len(terms)) 56 for _, term := range terms { 57 resultTerms = append(resultTerms, term.Key().String()) 58 } 59 60 sort.Strings(resultTerms) 61 resultMap[entry.Key().String()] = resultTerms 62 } 63 64 return resultMap 65 } 66 67 func TestWithLimits(t *testing.T) { 68 tests := []struct { 69 name string 70 entries []AggregateResultsEntry 71 sizeLimit int 72 docLimit int 73 exSeries int 74 exDocs int 75 expected map[string][]string 76 exMetrics map[string]int64 77 }{ 78 { 79 name: "single term", 80 entries: entries(genResultsEntry("foo")), 81 exSeries: 1, 82 exDocs: 1, 83 expected: map[string][]string{"foo": {}}, 84 85 exMetrics: map[string]int64{ 86 "total": 1, "total-fields": 1, "deduped-fields": 1, 87 "total-terms": 0, "deduped-terms": 0, 88 }, 89 }, 90 { 91 name: "same term", 92 entries: entries(genResultsEntry("foo"), genResultsEntry("foo")), 93 exSeries: 1, 94 exDocs: 2, 95 expected: map[string][]string{"foo": {}}, 96 exMetrics: map[string]int64{ 97 "total": 2, "total-fields": 2, "deduped-fields": 1, 98 "total-terms": 0, "deduped-terms": 0, 99 }, 100 }, 101 { 102 name: "multiple terms", 103 entries: entries(genResultsEntry("foo"), genResultsEntry("bar")), 104 exSeries: 2, 105 exDocs: 2, 106 expected: map[string][]string{"foo": {}, "bar": {}}, 107 exMetrics: map[string]int64{ 108 "total": 2, "total-fields": 2, "deduped-fields": 2, 109 "total-terms": 0, "deduped-terms": 0, 110 }, 111 }, 112 { 113 name: "single entry", 114 entries: entries(genResultsEntry("foo", "bar")), 115 exSeries: 2, 116 exDocs: 2, 117 expected: map[string][]string{"foo": {"bar"}}, 118 exMetrics: map[string]int64{ 119 "total": 2, "total-fields": 1, "deduped-fields": 1, 120 "total-terms": 1, "deduped-terms": 1, 121 }, 122 }, 123 { 124 name: "single entry multiple fields", 125 entries: entries(genResultsEntry("foo", "bar", "baz", "baz", "baz", "qux")), 126 exSeries: 4, 127 exDocs: 6, 128 expected: map[string][]string{"foo": {"bar", "baz", "qux"}}, 129 exMetrics: map[string]int64{ 130 "total": 6, "total-fields": 1, "deduped-fields": 1, 131 "total-terms": 5, "deduped-terms": 3, 132 }, 133 }, 134 { 135 name: "multiple entry multiple fields", 136 entries: entries( 137 genResultsEntry("foo", "bar", "baz"), 138 genResultsEntry("foo", "baz", "baz", "qux")), 139 exSeries: 4, 140 exDocs: 7, 141 expected: map[string][]string{"foo": {"bar", "baz", "qux"}}, 142 exMetrics: map[string]int64{ 143 "total": 7, "total-fields": 2, "deduped-fields": 1, 144 "total-terms": 5, "deduped-terms": 3, 145 }, 146 }, 147 { 148 name: "multiple entries", 149 entries: entries(genResultsEntry("foo", "baz"), genResultsEntry("bar", "baz", "qux")), 150 exSeries: 5, 151 exDocs: 5, 152 expected: map[string][]string{"foo": {"baz"}, "bar": {"baz", "qux"}}, 153 exMetrics: map[string]int64{ 154 "total": 5, "total-fields": 2, "deduped-fields": 2, 155 "total-terms": 3, "deduped-terms": 3, 156 }, 157 }, 158 159 { 160 name: "single entry query at size limit", 161 entries: entries(genResultsEntry("foo", "bar", "baz", "baz", "qux")), 162 sizeLimit: 4, 163 exSeries: 4, 164 exDocs: 5, 165 expected: map[string][]string{"foo": {"bar", "baz", "qux"}}, 166 exMetrics: map[string]int64{ 167 "total": 5, "total-fields": 1, "deduped-fields": 1, 168 "total-terms": 4, "deduped-terms": 3, 169 }, 170 }, 171 { 172 name: "single entry query at doc limit", 173 entries: entries(genResultsEntry("foo", "bar", "baz", "baz", "qux")), 174 docLimit: 5, 175 exSeries: 4, 176 exDocs: 5, 177 expected: map[string][]string{"foo": {"bar", "baz", "qux"}}, 178 exMetrics: map[string]int64{ 179 "total": 5, "total-fields": 1, "deduped-fields": 1, 180 "total-terms": 4, "deduped-terms": 3, 181 }, 182 }, 183 184 { 185 name: "single entry query below size limit", 186 entries: entries(genResultsEntry("foo", "bar", "baz", "qux")), 187 sizeLimit: 3, 188 exSeries: 3, 189 exDocs: 4, 190 expected: map[string][]string{"foo": {"bar", "baz"}}, 191 exMetrics: map[string]int64{ 192 "total": 4, "total-fields": 1, "deduped-fields": 1, 193 "total-terms": 3, "deduped-terms": 2, 194 }, 195 }, 196 { 197 name: "single entry query below doc limit", 198 entries: entries(genResultsEntry("foo", "bar", "bar", "bar", "baz")), 199 docLimit: 3, 200 exSeries: 2, 201 exDocs: 3, 202 expected: map[string][]string{"foo": {"bar"}}, 203 exMetrics: map[string]int64{ 204 "total": 5, "total-fields": 1, "deduped-fields": 1, 205 "total-terms": 4, "deduped-terms": 1, 206 }, 207 }, 208 { 209 name: "multiple entry query below series limit", 210 entries: entries(genResultsEntry("foo", "bar"), genResultsEntry("baz", "qux")), 211 sizeLimit: 3, 212 exSeries: 3, 213 exDocs: 4, 214 expected: map[string][]string{"foo": {"bar"}, "baz": {}}, 215 exMetrics: map[string]int64{ 216 "total": 4, "total-fields": 2, "deduped-fields": 2, 217 "total-terms": 2, "deduped-terms": 1, 218 }, 219 }, 220 { 221 name: "multiple entry query below doc limit", 222 entries: entries(genResultsEntry("foo", "bar"), genResultsEntry("baz", "qux")), 223 docLimit: 3, 224 exSeries: 3, 225 exDocs: 3, 226 expected: map[string][]string{"foo": {"bar"}, "baz": {}}, 227 exMetrics: map[string]int64{ 228 "total": 4, "total-fields": 2, "deduped-fields": 2, 229 "total-terms": 2, "deduped-terms": 1, 230 }, 231 }, 232 { 233 name: "multiple entry query both limits", 234 entries: entries(genResultsEntry("foo", "bar"), genResultsEntry("baz", "qux")), 235 docLimit: 3, 236 sizeLimit: 10, 237 exSeries: 3, 238 exDocs: 3, 239 expected: map[string][]string{"foo": {"bar"}, "baz": {}}, 240 exMetrics: map[string]int64{ 241 "total": 4, "total-fields": 2, "deduped-fields": 2, 242 "total-terms": 2, "deduped-terms": 1, 243 }, 244 }, 245 } 246 247 for _, tt := range tests { 248 t.Run(tt.name, func(t *testing.T) { 249 scope := tally.NewTestScope("", nil) 250 iOpts := instrument.NewOptions().SetMetricsScope(scope) 251 res := NewAggregateResults(ident.StringID("ns"), AggregateResultsOptions{ 252 SizeLimit: tt.sizeLimit, 253 DocsLimit: tt.docLimit, 254 AggregateUsageMetrics: NewAggregateUsageMetrics(ident.StringID("ns"), iOpts), 255 }, testOpts) 256 257 size, docsCount := res.AddFields(tt.entries) 258 assert.Equal(t, tt.exSeries, size) 259 assert.Equal(t, tt.exDocs, docsCount) 260 assert.Equal(t, tt.exSeries, res.Size()) 261 assert.Equal(t, tt.exDocs, res.TotalDocsCount()) 262 263 assert.Equal(t, tt.expected, toMap(res)) 264 265 counters := scope.Snapshot().Counters() 266 actualCounters := make(map[string]int64, len(counters)) 267 for _, v := range counters { 268 actualCounters[v.Tags()["type"]] = v.Value() 269 } 270 271 assert.Equal(t, tt.exMetrics, actualCounters) 272 }) 273 } 274 } 275 276 func TestAggResultsReset(t *testing.T) { 277 res := NewAggregateResults(ident.StringID("qux"), 278 AggregateResultsOptions{}, testOpts) 279 size, docsCount := res.AddFields(entries(genResultsEntry("foo", "bar"))) 280 require.Equal(t, 2, size) 281 require.Equal(t, 2, docsCount) 282 283 aggVals, ok := res.Map().Get(ident.StringID("foo")) 284 require.True(t, ok) 285 require.Equal(t, 1, aggVals.Size()) 286 287 // Check result options correct. 288 aggResults, ok := res.(*aggregatedResults) 289 require.True(t, ok) 290 require.Equal(t, 0, aggResults.aggregateOpts.SizeLimit) 291 require.Equal(t, ident.StringID("qux"), aggResults.nsID) 292 293 newID := ident.StringID("qaz") 294 res.Reset(newID, AggregateResultsOptions{SizeLimit: 100}) 295 _, ok = res.Map().Get(ident.StringID("foo")) 296 require.False(t, ok) 297 require.Equal(t, 0, aggVals.Size()) 298 require.Equal(t, 0, res.Size()) 299 300 // Check result options change. 301 aggResults, ok = res.(*aggregatedResults) 302 require.True(t, ok) 303 require.Equal(t, 100, aggResults.aggregateOpts.SizeLimit) 304 require.Equal(t, newID.Bytes(), aggResults.nsID.Bytes()) 305 306 // Ensure new NS is cloned 307 require.False(t, 308 xtest.ByteSlicesBackedBySameData(newID.Bytes(), aggResults.nsID.Bytes())) 309 } 310 311 func TestAggResultsResetNamespaceClones(t *testing.T) { 312 res := NewAggregateResults(nil, AggregateResultsOptions{}, testOpts) 313 require.Equal(t, nil, res.Namespace()) 314 nsID := ident.StringID("something") 315 res.Reset(nsID, AggregateResultsOptions{}) 316 nsID.Finalize() 317 require.Equal(t, nsID.Bytes(), res.Namespace().Bytes()) 318 319 // Ensure new NS is cloned 320 require.False(t, 321 xtest.ByteSlicesBackedBySameData(nsID.Bytes(), res.Namespace().Bytes())) 322 } 323 324 func TestAggResultFinalize(t *testing.T) { 325 // Create a Results and insert some data. 326 res := NewAggregateResults(nil, AggregateResultsOptions{}, testOpts) 327 size, docsCount := res.AddFields(entries(genResultsEntry("foo", "bar"))) 328 require.Equal(t, 2, size) 329 require.Equal(t, 2, docsCount) 330 331 // Ensure the data is present. 332 rMap := res.Map() 333 aggVals, ok := rMap.Get(ident.StringID("foo")) 334 require.True(t, ok) 335 require.Equal(t, 1, aggVals.Size()) 336 337 // Call Finalize() to reset the Results. 338 res.Finalize() 339 340 // Ensure data was removed by call to Finalize(). 341 aggVals, ok = rMap.Get(ident.StringID("foo")) 342 require.False(t, ok) 343 require.Nil(t, aggVals.Map()) 344 require.Equal(t, 0, res.Size()) 345 346 for _, entry := range rMap.Iter() { 347 id := entry.Key() 348 require.False(t, id.IsNoFinalize()) 349 } 350 } 351 352 func TestResetUpdatesMetics(t *testing.T) { 353 scope := tally.NewTestScope("", nil) 354 iOpts := instrument.NewOptions().SetMetricsScope(scope) 355 testOpts = testOpts.SetInstrumentOptions(iOpts) 356 res := NewAggregateResults(nil, AggregateResultsOptions{ 357 AggregateUsageMetrics: NewAggregateUsageMetrics(ident.StringID("ns1"), iOpts), 358 }, testOpts) 359 res.AddFields(entries(genResultsEntry("foo"))) 360 res.Reset(ident.StringID("ns2"), AggregateResultsOptions{}) 361 res.AddFields(entries(genResultsEntry("bar"))) 362 363 counters := scope.Snapshot().Counters() 364 seenNamespaces := make(map[string]struct{}) 365 for _, v := range counters { 366 ns := v.Tags()["namespace"] 367 seenNamespaces[ns] = struct{}{} 368 } 369 370 assert.Equal(t, map[string]struct{}{ 371 "ns1": {}, 372 "ns2": {}, 373 }, seenNamespaces) 374 375 res.Finalize() 376 }