github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/storage/stores/tsdb/index/index_test.go (about) 1 // Copyright 2017 The Prometheus Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package index 15 16 import ( 17 "context" 18 "fmt" 19 "hash/crc32" 20 "io/ioutil" 21 "math/rand" 22 "os" 23 "path/filepath" 24 "sort" 25 "testing" 26 27 "github.com/pkg/errors" 28 "github.com/stretchr/testify/require" 29 "go.uber.org/goleak" 30 31 "github.com/prometheus/common/model" 32 "github.com/prometheus/prometheus/model/labels" 33 "github.com/prometheus/prometheus/storage" 34 "github.com/prometheus/prometheus/tsdb/encoding" 35 "github.com/prometheus/prometheus/util/testutil" 36 ) 37 38 func TestMain(m *testing.M) { 39 goleak.VerifyTestMain(m) 40 } 41 42 type series struct { 43 l labels.Labels 44 chunks []ChunkMeta 45 } 46 47 type mockIndex struct { 48 series map[storage.SeriesRef]series 49 postings map[labels.Label][]storage.SeriesRef 50 symbols map[string]struct{} 51 } 52 53 func newMockIndex() mockIndex { 54 ix := mockIndex{ 55 series: make(map[storage.SeriesRef]series), 56 postings: make(map[labels.Label][]storage.SeriesRef), 57 symbols: make(map[string]struct{}), 58 } 59 ix.postings[allPostingsKey] = []storage.SeriesRef{} 60 return ix 61 } 62 63 func (m mockIndex) Symbols() (map[string]struct{}, error) { 64 return m.symbols, nil 65 } 66 67 func (m mockIndex) AddSeries(ref storage.SeriesRef, l labels.Labels, chunks ...ChunkMeta) error { 68 if _, ok := m.series[ref]; ok { 69 return errors.Errorf("series with reference %d already added", ref) 70 } 71 for _, lbl := range l { 72 m.symbols[lbl.Name] = struct{}{} 73 m.symbols[lbl.Value] = struct{}{} 74 if _, ok := m.postings[lbl]; !ok { 75 m.postings[lbl] = []storage.SeriesRef{} 76 } 77 m.postings[lbl] = append(m.postings[lbl], ref) 78 } 79 m.postings[allPostingsKey] = append(m.postings[allPostingsKey], ref) 80 81 s := series{l: l} 82 // Actual chunk data is not stored in the index. 83 s.chunks = append(s.chunks, chunks...) 84 m.series[ref] = s 85 86 return nil 87 } 88 89 func (m mockIndex) Close() error { 90 return nil 91 } 92 93 func (m mockIndex) LabelValues(name string) ([]string, error) { 94 values := []string{} 95 for l := range m.postings { 96 if l.Name == name { 97 values = append(values, l.Value) 98 } 99 } 100 return values, nil 101 } 102 103 func (m mockIndex) Postings(name string, values ...string) (Postings, error) { 104 p := []Postings{} 105 for _, value := range values { 106 l := labels.Label{Name: name, Value: value} 107 p = append(p, NewListPostings(m.postings[l])) 108 } 109 return Merge(p...), nil 110 } 111 112 func (m mockIndex) Series(ref storage.SeriesRef, lset *labels.Labels, chks *[]ChunkMeta) error { 113 s, ok := m.series[ref] 114 if !ok { 115 return errors.New("not found") 116 } 117 *lset = append((*lset)[:0], s.l...) 118 *chks = append((*chks)[:0], s.chunks...) 119 120 return nil 121 } 122 123 func TestIndexRW_Create_Open(t *testing.T) { 124 dir := t.TempDir() 125 126 fn := filepath.Join(dir, IndexFilename) 127 128 // An empty index must still result in a readable file. 129 iw, err := NewWriter(context.Background(), fn) 130 require.NoError(t, err) 131 require.NoError(t, iw.Close()) 132 133 ir, err := NewFileReader(fn) 134 require.NoError(t, err) 135 require.NoError(t, ir.Close()) 136 137 // Modify magic header must cause open to fail. 138 f, err := os.OpenFile(fn, os.O_WRONLY, 0o666) 139 require.NoError(t, err) 140 _, err = f.WriteAt([]byte{0, 0}, 0) 141 require.NoError(t, err) 142 f.Close() 143 144 _, err = NewFileReader(dir) 145 require.Error(t, err) 146 } 147 148 func TestIndexRW_Postings(t *testing.T) { 149 dir := t.TempDir() 150 151 fn := filepath.Join(dir, IndexFilename) 152 153 iw, err := NewWriter(context.Background(), fn) 154 require.NoError(t, err) 155 156 series := []labels.Labels{ 157 labels.FromStrings("a", "1", "b", "1"), 158 labels.FromStrings("a", "1", "b", "2"), 159 labels.FromStrings("a", "1", "b", "3"), 160 labels.FromStrings("a", "1", "b", "4"), 161 } 162 163 require.NoError(t, iw.AddSymbol("1")) 164 require.NoError(t, iw.AddSymbol("2")) 165 require.NoError(t, iw.AddSymbol("3")) 166 require.NoError(t, iw.AddSymbol("4")) 167 require.NoError(t, iw.AddSymbol("a")) 168 require.NoError(t, iw.AddSymbol("b")) 169 170 // Postings lists are only written if a series with the respective 171 // reference was added before. 172 require.NoError(t, iw.AddSeries(1, series[0], model.Fingerprint(series[0].Hash()))) 173 require.NoError(t, iw.AddSeries(2, series[1], model.Fingerprint(series[1].Hash()))) 174 require.NoError(t, iw.AddSeries(3, series[2], model.Fingerprint(series[2].Hash()))) 175 require.NoError(t, iw.AddSeries(4, series[3], model.Fingerprint(series[3].Hash()))) 176 177 require.NoError(t, iw.Close()) 178 179 ir, err := NewFileReader(fn) 180 require.NoError(t, err) 181 182 p, err := ir.Postings("a", nil, "1") 183 require.NoError(t, err) 184 185 var l labels.Labels 186 var c []ChunkMeta 187 188 for i := 0; p.Next(); i++ { 189 _, err := ir.Series(p.At(), &l, &c) 190 191 require.NoError(t, err) 192 require.Equal(t, 0, len(c)) 193 require.Equal(t, series[i], l) 194 } 195 require.NoError(t, p.Err()) 196 197 // The label indices are no longer used, so test them by hand here. 198 labelIndices := map[string][]string{} 199 require.NoError(t, ReadOffsetTable(ir.b, ir.toc.LabelIndicesTable, func(key []string, off uint64, _ int) error { 200 if len(key) != 1 { 201 return errors.Errorf("unexpected key length for label indices table %d", len(key)) 202 } 203 204 d := encoding.NewDecbufAt(ir.b, int(off), castagnoliTable) 205 vals := []string{} 206 nc := d.Be32int() 207 if nc != 1 { 208 return errors.Errorf("unexpected number of label indices table names %d", nc) 209 } 210 for i := d.Be32(); i > 0; i-- { 211 v, err := ir.lookupSymbol(d.Be32()) 212 if err != nil { 213 return err 214 } 215 vals = append(vals, v) 216 } 217 labelIndices[key[0]] = vals 218 return d.Err() 219 })) 220 require.Equal(t, map[string][]string{ 221 "a": {"1"}, 222 "b": {"1", "2", "3", "4"}, 223 }, labelIndices) 224 225 require.NoError(t, ir.Close()) 226 } 227 228 func TestPostingsMany(t *testing.T) { 229 dir := t.TempDir() 230 231 fn := filepath.Join(dir, IndexFilename) 232 233 iw, err := NewWriter(context.Background(), fn) 234 require.NoError(t, err) 235 236 // Create a label in the index which has 999 values. 237 symbols := map[string]struct{}{} 238 series := []labels.Labels{} 239 for i := 1; i < 1000; i++ { 240 v := fmt.Sprintf("%03d", i) 241 series = append(series, labels.FromStrings("i", v, "foo", "bar")) 242 symbols[v] = struct{}{} 243 } 244 symbols["i"] = struct{}{} 245 symbols["foo"] = struct{}{} 246 symbols["bar"] = struct{}{} 247 syms := []string{} 248 for s := range symbols { 249 syms = append(syms, s) 250 } 251 sort.Strings(syms) 252 for _, s := range syms { 253 require.NoError(t, iw.AddSymbol(s)) 254 } 255 256 sort.Slice(series, func(i, j int) bool { 257 return series[i].Hash() < series[j].Hash() 258 }) 259 260 for i, s := range series { 261 require.NoError(t, iw.AddSeries(storage.SeriesRef(i), s, model.Fingerprint(s.Hash()))) 262 } 263 require.NoError(t, iw.Close()) 264 265 ir, err := NewFileReader(fn) 266 require.NoError(t, err) 267 defer func() { require.NoError(t, ir.Close()) }() 268 269 cases := []struct { 270 in []string 271 }{ 272 // Simple cases, everything is present. 273 {in: []string{"002"}}, 274 {in: []string{"031", "032", "033"}}, 275 {in: []string{"032", "033"}}, 276 {in: []string{"127", "128"}}, 277 {in: []string{"127", "128", "129"}}, 278 {in: []string{"127", "129"}}, 279 {in: []string{"128", "129"}}, 280 {in: []string{"998", "999"}}, 281 {in: []string{"999"}}, 282 // Before actual values. 283 {in: []string{"000"}}, 284 {in: []string{"000", "001"}}, 285 {in: []string{"000", "002"}}, 286 // After actual values. 287 {in: []string{"999a"}}, 288 {in: []string{"999", "999a"}}, 289 {in: []string{"998", "999", "999a"}}, 290 // In the middle of actual values. 291 {in: []string{"126a", "127", "128"}}, 292 {in: []string{"127", "127a", "128"}}, 293 {in: []string{"127", "127a", "128", "128a", "129"}}, 294 {in: []string{"127", "128a", "129"}}, 295 {in: []string{"128", "128a", "129"}}, 296 {in: []string{"128", "129", "129a"}}, 297 {in: []string{"126a", "126b", "127", "127a", "127b", "128", "128a", "128b", "129", "129a", "129b"}}, 298 } 299 300 for _, c := range cases { 301 it, err := ir.Postings("i", nil, c.in...) 302 require.NoError(t, err) 303 304 got := []string{} 305 var lbls labels.Labels 306 var metas []ChunkMeta 307 for it.Next() { 308 _, err := ir.Series(it.At(), &lbls, &metas) 309 require.NoError(t, err) 310 got = append(got, lbls.Get("i")) 311 } 312 require.NoError(t, it.Err()) 313 exp := []string{} 314 for _, e := range c.in { 315 if _, ok := symbols[e]; ok && e != "l" { 316 exp = append(exp, e) 317 } 318 } 319 320 // sort expected values by label hash instead of lexicographically by labelset 321 sort.Slice(exp, func(i, j int) bool { 322 return labels.FromStrings("i", exp[i], "foo", "bar").Hash() < labels.FromStrings("i", exp[j], "foo", "bar").Hash() 323 }) 324 325 require.Equal(t, exp, got, fmt.Sprintf("input: %v", c.in)) 326 } 327 } 328 329 func TestPersistence_index_e2e(t *testing.T) { 330 dir := t.TempDir() 331 332 lbls, err := labels.ReadLabels(filepath.Join("..", "testdata", "20kseries.json"), 20000) 333 require.NoError(t, err) 334 335 // Sort labels as the index writer expects series in sorted order by fingerprint. 336 sort.Slice(lbls, func(i, j int) bool { 337 return lbls[i].Hash() < lbls[j].Hash() 338 }) 339 340 symbols := map[string]struct{}{} 341 for _, lset := range lbls { 342 for _, l := range lset { 343 symbols[l.Name] = struct{}{} 344 symbols[l.Value] = struct{}{} 345 } 346 } 347 348 var input indexWriterSeriesSlice 349 350 // Generate ChunkMetas for every label set. 351 for i, lset := range lbls { 352 var metas []ChunkMeta 353 354 for j := 0; j <= (i % 20); j++ { 355 metas = append(metas, ChunkMeta{ 356 MinTime: int64(j * 10000), 357 MaxTime: int64((j + 1) * 10000), 358 Checksum: rand.Uint32(), 359 }) 360 } 361 input = append(input, &indexWriterSeries{ 362 labels: lset, 363 chunks: metas, 364 }) 365 } 366 367 iw, err := NewWriter(context.Background(), filepath.Join(dir, IndexFilename)) 368 require.NoError(t, err) 369 370 syms := []string{} 371 for s := range symbols { 372 syms = append(syms, s) 373 } 374 sort.Strings(syms) 375 for _, s := range syms { 376 require.NoError(t, iw.AddSymbol(s)) 377 } 378 379 // Population procedure as done by compaction. 380 var ( 381 postings = NewMemPostings() 382 values = map[string]map[string]struct{}{} 383 ) 384 385 mi := newMockIndex() 386 387 for i, s := range input { 388 err = iw.AddSeries(storage.SeriesRef(i), s.labels, model.Fingerprint(s.labels.Hash()), s.chunks...) 389 require.NoError(t, err) 390 require.NoError(t, mi.AddSeries(storage.SeriesRef(i), s.labels, s.chunks...)) 391 392 for _, l := range s.labels { 393 valset, ok := values[l.Name] 394 if !ok { 395 valset = map[string]struct{}{} 396 values[l.Name] = valset 397 } 398 valset[l.Value] = struct{}{} 399 } 400 postings.Add(storage.SeriesRef(i), s.labels) 401 } 402 403 err = iw.Close() 404 require.NoError(t, err) 405 406 ir, err := NewFileReader(filepath.Join(dir, IndexFilename)) 407 require.NoError(t, err) 408 409 for p := range mi.postings { 410 gotp, err := ir.Postings(p.Name, nil, p.Value) 411 require.NoError(t, err) 412 413 expp, err := mi.Postings(p.Name, p.Value) 414 require.NoError(t, err) 415 416 var lset, explset labels.Labels 417 var chks, expchks []ChunkMeta 418 419 for gotp.Next() { 420 require.True(t, expp.Next()) 421 422 ref := gotp.At() 423 424 _, err := ir.Series(ref, &lset, &chks) 425 require.NoError(t, err) 426 427 err = mi.Series(expp.At(), &explset, &expchks) 428 require.NoError(t, err) 429 require.Equal(t, explset, lset) 430 require.Equal(t, expchks, chks) 431 } 432 require.False(t, expp.Next(), "Expected no more postings for %q=%q", p.Name, p.Value) 433 require.NoError(t, gotp.Err()) 434 } 435 436 labelPairs := map[string][]string{} 437 for l := range mi.postings { 438 labelPairs[l.Name] = append(labelPairs[l.Name], l.Value) 439 } 440 for k, v := range labelPairs { 441 sort.Strings(v) 442 443 res, err := ir.SortedLabelValues(k) 444 require.NoError(t, err) 445 446 require.Equal(t, len(v), len(res)) 447 for i := 0; i < len(v); i++ { 448 require.Equal(t, v[i], res[i]) 449 } 450 } 451 452 gotSymbols := []string{} 453 it := ir.Symbols() 454 for it.Next() { 455 gotSymbols = append(gotSymbols, it.At()) 456 } 457 require.NoError(t, it.Err()) 458 expSymbols := []string{} 459 for s := range mi.symbols { 460 expSymbols = append(expSymbols, s) 461 } 462 sort.Strings(expSymbols) 463 require.Equal(t, expSymbols, gotSymbols) 464 465 require.NoError(t, ir.Close()) 466 } 467 468 func TestDecbufUvarintWithInvalidBuffer(t *testing.T) { 469 b := RealByteSlice([]byte{0x81, 0x81, 0x81, 0x81, 0x81, 0x81}) 470 471 db := encoding.NewDecbufUvarintAt(b, 0, castagnoliTable) 472 require.Error(t, db.Err()) 473 } 474 475 func TestReaderWithInvalidBuffer(t *testing.T) { 476 b := RealByteSlice([]byte{0x81, 0x81, 0x81, 0x81, 0x81, 0x81}) 477 478 _, err := NewReader(b) 479 require.Error(t, err) 480 } 481 482 // TestNewFileReaderErrorNoOpenFiles ensures that in case of an error no file remains open. 483 func TestNewFileReaderErrorNoOpenFiles(t *testing.T) { 484 dir := testutil.NewTemporaryDirectory("block", t) 485 486 idxName := filepath.Join(dir.Path(), "index") 487 err := ioutil.WriteFile(idxName, []byte("corrupted contents"), 0o666) 488 require.NoError(t, err) 489 490 _, err = NewFileReader(idxName) 491 require.Error(t, err) 492 493 // dir.Close will fail on Win if idxName fd is not closed on error path. 494 dir.Close() 495 } 496 497 func TestSymbols(t *testing.T) { 498 buf := encoding.Encbuf{} 499 500 // Add prefix to the buffer to simulate symbols as part of larger buffer. 501 buf.PutUvarintStr("something") 502 503 symbolsStart := buf.Len() 504 buf.PutBE32int(204) // Length of symbols table. 505 buf.PutBE32int(100) // Number of symbols. 506 for i := 0; i < 100; i++ { 507 // i represents index in unicode characters table. 508 buf.PutUvarintStr(string(rune(i))) // Symbol. 509 } 510 checksum := crc32.Checksum(buf.Get()[symbolsStart+4:], castagnoliTable) 511 buf.PutBE32(checksum) // Check sum at the end. 512 513 s, err := NewSymbols(RealByteSlice(buf.Get()), FormatV2, symbolsStart) 514 require.NoError(t, err) 515 516 // We store only 4 offsets to symbols. 517 require.Equal(t, 32, s.Size()) 518 519 for i := 99; i >= 0; i-- { 520 s, err := s.Lookup(uint32(i)) 521 require.NoError(t, err) 522 require.Equal(t, string(rune(i)), s) 523 } 524 _, err = s.Lookup(100) 525 require.Error(t, err) 526 527 for i := 99; i >= 0; i-- { 528 r, err := s.ReverseLookup(string(rune(i))) 529 require.NoError(t, err) 530 require.Equal(t, uint32(i), r) 531 } 532 _, err = s.ReverseLookup(string(rune(100))) 533 require.Error(t, err) 534 535 iter := s.Iter() 536 i := 0 537 for iter.Next() { 538 require.Equal(t, string(rune(i)), iter.At()) 539 i++ 540 } 541 require.NoError(t, iter.Err()) 542 } 543 544 func TestDecoder_Postings_WrongInput(t *testing.T) { 545 _, _, err := (&Decoder{}).Postings([]byte("the cake is a lie")) 546 require.Error(t, err) 547 }