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  }