github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ruler/storage/wal/wal_test.go (about) 1 // This directory was copied and adapted from https://github.com/grafana/agent/tree/main/pkg/metrics. 2 // We cannot vendor the agent in since the agent vendors loki in, which would cause a cyclic dependency. 3 // NOTE: many changes have been made to the original code for our use-case. 4 package wal 5 6 import ( 7 "context" 8 "math" 9 "sort" 10 "testing" 11 "time" 12 13 "github.com/go-kit/log" 14 "github.com/prometheus/client_golang/prometheus" 15 "github.com/prometheus/prometheus/model/exemplar" 16 "github.com/prometheus/prometheus/model/labels" 17 "github.com/prometheus/prometheus/model/value" 18 "github.com/prometheus/prometheus/storage" 19 "github.com/prometheus/prometheus/tsdb" 20 "github.com/prometheus/prometheus/tsdb/chunks" 21 "github.com/prometheus/prometheus/tsdb/record" 22 "github.com/stretchr/testify/require" 23 ) 24 25 func newTestStorage(walDir string) (*Storage, error) { 26 metrics := NewMetrics(prometheus.DefaultRegisterer) 27 return NewStorage(log.NewNopLogger(), metrics, nil, walDir) 28 } 29 30 func TestStorage_InvalidSeries(t *testing.T) { 31 walDir := t.TempDir() 32 33 s, err := newTestStorage(walDir) 34 require.NoError(t, err) 35 defer func() { 36 require.NoError(t, s.Close()) 37 }() 38 39 app := s.Appender(context.Background()) 40 41 // Samples 42 _, err = app.Append(0, labels.Labels{}, 0, 0) 43 require.Error(t, err, "should reject empty labels") 44 45 _, err = app.Append(0, labels.Labels{{Name: "a", Value: "1"}, {Name: "a", Value: "2"}}, 0, 0) 46 require.Error(t, err, "should reject duplicate labels") 47 48 // Sanity check: valid series 49 sRef, err := app.Append(0, labels.Labels{{Name: "a", Value: "1"}}, 0, 0) 50 require.NoError(t, err, "should not reject valid series") 51 52 // Exemplars 53 _, err = app.AppendExemplar(0, nil, exemplar.Exemplar{}) 54 require.Error(t, err, "should reject unknown series ref") 55 56 e := exemplar.Exemplar{Labels: labels.Labels{{Name: "a", Value: "1"}, {Name: "a", Value: "2"}}} 57 _, err = app.AppendExemplar(sRef, nil, e) 58 require.ErrorIs(t, err, tsdb.ErrInvalidExemplar, "should reject duplicate labels") 59 60 e = exemplar.Exemplar{Labels: labels.Labels{{Name: "a_somewhat_long_trace_id", Value: "nYJSNtFrFTY37VR7mHzEE/LIDt7cdAQcuOzFajgmLDAdBSRHYPDzrxhMA4zz7el8naI/AoXFv9/e/G0vcETcIoNUi3OieeLfaIRQci2oa"}}} 61 _, err = app.AppendExemplar(sRef, nil, e) 62 require.ErrorIs(t, err, storage.ErrExemplarLabelLength, "should reject too long label length") 63 64 // Sanity check: valid exemplars 65 e = exemplar.Exemplar{Labels: labels.Labels{{Name: "a", Value: "1"}}, Value: 20, Ts: 10, HasTs: true} 66 _, err = app.AppendExemplar(sRef, nil, e) 67 require.NoError(t, err, "should not reject valid exemplars") 68 } 69 70 func TestStorage(t *testing.T) { 71 walDir := t.TempDir() 72 73 s, err := newTestStorage(walDir) 74 require.NoError(t, err) 75 defer func() { 76 require.NoError(t, s.Close()) 77 }() 78 79 app := s.Appender(context.Background()) 80 81 // Write some samples 82 payload := buildSeries([]string{"foo", "bar", "baz"}) 83 for _, metric := range payload { 84 metric.Write(t, app) 85 } 86 87 require.NoError(t, app.Commit()) 88 89 collector := walDataCollector{} 90 replayer := walReplayer{w: &collector} 91 require.NoError(t, replayer.Replay(s.wal.Dir())) 92 93 names := []string{} 94 for _, series := range collector.series { 95 names = append(names, series.Labels.Get("__name__")) 96 } 97 require.Equal(t, payload.SeriesNames(), names) 98 99 expectedSamples := payload.ExpectedSamples() 100 actualSamples := collector.samples 101 sort.Sort(byRefSample(actualSamples)) 102 require.Equal(t, expectedSamples, actualSamples) 103 104 expectedExemplars := payload.ExpectedExemplars() 105 actualExemplars := collector.exemplars 106 sort.Sort(byRefExemplar(actualExemplars)) 107 require.Equal(t, expectedExemplars, actualExemplars) 108 } 109 110 func TestStorage_ExistingWAL(t *testing.T) { 111 walDir := t.TempDir() 112 113 s, err := newTestStorage(walDir) 114 require.NoError(t, err) 115 116 app := s.Appender(context.Background()) 117 payload := buildSeries([]string{"foo", "bar", "baz", "blerg"}) 118 119 // Write half of the samples. 120 for _, metric := range payload[0 : len(payload)/2] { 121 metric.Write(t, app) 122 } 123 124 require.NoError(t, app.Commit()) 125 require.NoError(t, s.Close()) 126 127 // We need to wait a little bit for the previous store to finish 128 // flushing. 129 time.Sleep(time.Millisecond * 150) 130 131 // Create a new storage, write the other half of samples. 132 s, err = newTestStorage(walDir) 133 require.NoError(t, err) 134 defer func() { 135 require.NoError(t, s.Close()) 136 }() 137 138 // Verify that the storage picked up existing series when it 139 // replayed the WAL. 140 for series := range s.series.iterator().Channel() { 141 require.Greater(t, series.lastTs, int64(0), "series timestamp not updated") 142 } 143 144 app = s.Appender(context.Background()) 145 146 for _, metric := range payload[len(payload)/2:] { 147 metric.Write(t, app) 148 } 149 150 require.NoError(t, app.Commit()) 151 152 collector := walDataCollector{} 153 replayer := walReplayer{w: &collector} 154 require.NoError(t, replayer.Replay(s.wal.Dir())) 155 156 names := []string{} 157 for _, series := range collector.series { 158 names = append(names, series.Labels.Get("__name__")) 159 } 160 require.Equal(t, payload.SeriesNames(), names) 161 162 expectedSamples := payload.ExpectedSamples() 163 actualSamples := collector.samples 164 sort.Sort(byRefSample(actualSamples)) 165 require.Equal(t, expectedSamples, actualSamples) 166 167 expectedExemplars := payload.ExpectedExemplars() 168 actualExemplars := collector.exemplars 169 sort.Sort(byRefExemplar(actualExemplars)) 170 require.Equal(t, expectedExemplars, actualExemplars) 171 } 172 173 func TestStorage_ExistingWAL_RefID(t *testing.T) { 174 walDir := t.TempDir() 175 176 s, err := newTestStorage(walDir) 177 require.NoError(t, err) 178 179 app := s.Appender(context.Background()) 180 payload := buildSeries([]string{"foo", "bar", "baz", "blerg"}) 181 182 // Write all the samples 183 for _, metric := range payload { 184 metric.Write(t, app) 185 } 186 require.NoError(t, app.Commit()) 187 188 // Truncate the WAL to force creation of a new segment. 189 require.NoError(t, s.Truncate(0)) 190 require.NoError(t, s.Close()) 191 192 // Create a new storage and see what the ref ID is initialized to. 193 s, err = newTestStorage(walDir) 194 require.NoError(t, err) 195 defer require.NoError(t, s.Close()) 196 197 require.Equal(t, uint64(len(payload)), s.ref.Load(), "cached ref ID should be equal to the number of series written") 198 } 199 200 func TestStorage_Truncate(t *testing.T) { 201 // Same as before but now do the following: 202 // after writing all the data, forcefully create 4 more segments, 203 // then do a truncate of a timestamp for _some_ of the data. 204 // then read data back in. Expect to only get the latter half of data. 205 walDir := t.TempDir() 206 207 s, err := newTestStorage(walDir) 208 require.NoError(t, err) 209 defer func() { 210 require.NoError(t, s.Close()) 211 }() 212 213 app := s.Appender(context.Background()) 214 215 payload := buildSeries([]string{"foo", "bar", "baz", "blerg"}) 216 217 for _, metric := range payload { 218 metric.Write(t, app) 219 } 220 221 require.NoError(t, app.Commit()) 222 223 // Forefully create a bunch of new segments so when we truncate 224 // there's enough segments to be considered for truncation. 225 for i := 0; i < 5; i++ { 226 require.NoError(t, s.wal.NextSegment()) 227 } 228 229 // Truncate half of the samples, keeping only the second sample 230 // per series. 231 keepTs := payload[len(payload)-1].samples[0].ts + 1 232 err = s.Truncate(keepTs) 233 require.NoError(t, err) 234 235 payload = payload.Filter(func(s sample) bool { 236 return s.ts >= keepTs 237 }, func(e exemplar.Exemplar) bool { 238 return e.HasTs && e.Ts >= keepTs 239 }) 240 expectedSamples := payload.ExpectedSamples() 241 expectedExemplars := payload.ExpectedExemplars() 242 243 // Read back the WAL, collect series and samples. 244 collector := walDataCollector{} 245 replayer := walReplayer{w: &collector} 246 require.NoError(t, replayer.Replay(s.wal.Dir())) 247 248 names := []string{} 249 for _, series := range collector.series { 250 names = append(names, series.Labels.Get("__name__")) 251 } 252 require.Equal(t, payload.SeriesNames(), names) 253 254 actualSamples := collector.samples 255 sort.Sort(byRefSample(actualSamples)) 256 require.Equal(t, expectedSamples, actualSamples) 257 258 actualExemplars := collector.exemplars 259 sort.Sort(byRefExemplar(actualExemplars)) 260 require.Equal(t, expectedExemplars, actualExemplars) 261 } 262 263 func TestStorage_WriteStalenessMarkers(t *testing.T) { 264 walDir := t.TempDir() 265 266 s, err := newTestStorage(walDir) 267 require.NoError(t, err) 268 defer func() { 269 require.NoError(t, s.Close()) 270 }() 271 272 app := s.Appender(context.Background()) 273 274 // Write some samples 275 payload := seriesList{ 276 {name: "foo", samples: []sample{{1, 10.0}, {10, 100.0}}}, 277 {name: "bar", samples: []sample{{2, 20.0}, {20, 200.0}}}, 278 {name: "baz", samples: []sample{{3, 30.0}, {30, 300.0}}}, 279 } 280 for _, metric := range payload { 281 metric.Write(t, app) 282 } 283 284 require.NoError(t, app.Commit()) 285 286 // Write staleness markers for every series 287 require.NoError(t, s.WriteStalenessMarkers(func() int64 { 288 // Pass math.MaxInt64 so it seems like everything was written already 289 return math.MaxInt64 290 })) 291 292 // Read back the WAL, collect series and samples. 293 collector := walDataCollector{} 294 replayer := walReplayer{w: &collector} 295 require.NoError(t, replayer.Replay(s.wal.Dir())) 296 297 actual := collector.samples 298 sort.Sort(byRefSample(actual)) 299 300 staleMap := map[chunks.HeadSeriesRef]bool{} 301 for _, sample := range actual { 302 if _, ok := staleMap[sample.Ref]; !ok { 303 staleMap[sample.Ref] = false 304 } 305 if value.IsStaleNaN(sample.V) { 306 staleMap[sample.Ref] = true 307 } 308 } 309 310 for ref, v := range staleMap { 311 require.True(t, v, "ref %d doesn't have stale marker", ref) 312 } 313 } 314 315 func TestStorage_TruncateAfterClose(t *testing.T) { 316 walDir := t.TempDir() 317 318 s, err := newTestStorage(walDir) 319 require.NoError(t, err) 320 321 require.NoError(t, s.Close()) 322 require.Error(t, ErrWALClosed, s.Truncate(0)) 323 } 324 325 type sample struct { 326 ts int64 327 val float64 328 } 329 330 type series struct { 331 name string 332 samples []sample 333 exemplars []exemplar.Exemplar 334 335 ref *storage.SeriesRef 336 } 337 338 func (s *series) Write(t *testing.T, app storage.Appender) { 339 t.Helper() 340 341 lbls := labels.FromMap(map[string]string{"__name__": s.name}) 342 343 offset := 0 344 if s.ref == nil { 345 // Write first sample to get ref ID 346 ref, err := app.Append(0, lbls, s.samples[0].ts, s.samples[0].val) 347 require.NoError(t, err) 348 349 s.ref = &ref 350 offset = 1 351 } 352 353 // Write other data points with AddFast 354 for _, sample := range s.samples[offset:] { 355 _, err := app.Append(*s.ref, lbls, sample.ts, sample.val) 356 require.NoError(t, err) 357 } 358 359 sRef := *s.ref 360 for _, exemplar := range s.exemplars { 361 var err error 362 sRef, err = app.AppendExemplar(sRef, nil, exemplar) 363 require.NoError(t, err) 364 } 365 } 366 367 type seriesList []*series 368 369 // Filter creates a new seriesList with series filtered by a sample 370 // keep predicate function. 371 func (s seriesList) Filter(fn func(s sample) bool, fnExemplar func(e exemplar.Exemplar) bool) seriesList { 372 var ret seriesList 373 374 for _, entry := range s { 375 var ( 376 samples []sample 377 exemplars []exemplar.Exemplar 378 ) 379 380 for _, sample := range entry.samples { 381 if fn(sample) { 382 samples = append(samples, sample) 383 } 384 } 385 386 for _, e := range entry.exemplars { 387 if fnExemplar(e) { 388 exemplars = append(exemplars, e) 389 } 390 } 391 392 if len(samples) > 0 && len(exemplars) > 0 { 393 ret = append(ret, &series{ 394 name: entry.name, 395 ref: entry.ref, 396 samples: samples, 397 exemplars: exemplars, 398 }) 399 } 400 } 401 402 return ret 403 } 404 405 func (s seriesList) SeriesNames() []string { 406 names := make([]string, 0, len(s)) 407 for _, series := range s { 408 names = append(names, series.name) 409 } 410 return names 411 } 412 413 // ExpectedSamples returns the list of expected samples, sorted by ref ID and timestamp 414 func (s seriesList) ExpectedSamples() []record.RefSample { 415 expect := []record.RefSample{} 416 for _, series := range s { 417 for _, sample := range series.samples { 418 expect = append(expect, record.RefSample{ 419 Ref: chunks.HeadSeriesRef(*series.ref), 420 T: sample.ts, 421 V: sample.val, 422 }) 423 } 424 } 425 sort.Sort(byRefSample(expect)) 426 return expect 427 } 428 429 // ExpectedExemplars returns the list of expected exemplars, sorted by ref ID and timestamp 430 func (s seriesList) ExpectedExemplars() []record.RefExemplar { 431 expect := []record.RefExemplar{} 432 for _, series := range s { 433 for _, exemplar := range series.exemplars { 434 expect = append(expect, record.RefExemplar{ 435 Ref: chunks.HeadSeriesRef(*series.ref), 436 T: exemplar.Ts, 437 V: exemplar.Value, 438 Labels: exemplar.Labels, 439 }) 440 } 441 } 442 sort.Sort(byRefExemplar(expect)) 443 return expect 444 } 445 446 func buildSeries(nameSlice []string) seriesList { 447 s := make(seriesList, 0, len(nameSlice)) 448 for i, n := range nameSlice { 449 i++ 450 s = append(s, &series{ 451 name: n, 452 samples: []sample{{int64(i), float64(i * 10.0)}, {int64(i * 10), float64(i * 100.0)}}, 453 exemplars: []exemplar.Exemplar{ 454 {Labels: labels.Labels{{Name: "foobar", Value: "barfoo"}}, Value: float64(i * 10.0), Ts: int64(i), HasTs: true}, 455 {Labels: labels.Labels{{Name: "lorem", Value: "ipsum"}}, Value: float64(i * 100.0), Ts: int64(i * 10), HasTs: true}, 456 }, 457 }) 458 } 459 return s 460 } 461 462 type byRefSample []record.RefSample 463 464 func (b byRefSample) Len() int { return len(b) } 465 func (b byRefSample) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 466 func (b byRefSample) Less(i, j int) bool { 467 if b[i].Ref == b[j].Ref { 468 return b[i].T < b[j].T 469 } 470 return b[i].Ref < b[j].Ref 471 } 472 473 type byRefExemplar []record.RefExemplar 474 475 func (b byRefExemplar) Len() int { return len(b) } 476 func (b byRefExemplar) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 477 func (b byRefExemplar) Less(i, j int) bool { 478 if b[i].Ref == b[j].Ref { 479 return b[i].T < b[j].T 480 } 481 return b[i].Ref < b[j].Ref 482 }