github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ruler/storage/instance/instance_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 instance
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"strings"
    11  	"sync"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/go-kit/log"
    16  	"github.com/go-kit/log/level"
    17  	"github.com/prometheus/client_golang/prometheus"
    18  	"github.com/prometheus/prometheus/config"
    19  	"github.com/prometheus/prometheus/model/exemplar"
    20  	"github.com/prometheus/prometheus/model/labels"
    21  	"github.com/prometheus/prometheus/storage"
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  
    25  	"github.com/grafana/loki/pkg/util/test"
    26  )
    27  
    28  func TestConfig_Unmarshal_Defaults(t *testing.T) {
    29  	cfgText := `name: test`
    30  
    31  	cfg, err := UnmarshalConfig(strings.NewReader(cfgText))
    32  	require.NoError(t, err)
    33  
    34  	err = cfg.ApplyDefaults()
    35  	require.NoError(t, err)
    36  
    37  	require.Equal(t, DefaultConfig.TruncateFrequency, cfg.TruncateFrequency)
    38  	require.Equal(t, DefaultConfig.RemoteFlushDeadline, cfg.RemoteFlushDeadline)
    39  }
    40  
    41  func TestConfig_ApplyDefaults_Validations(t *testing.T) {
    42  	cfg := DefaultConfig
    43  	cfg.Name = "instance"
    44  	cfg.RemoteWrite = []*config.RemoteWriteConfig{{Name: "write"}}
    45  
    46  	tt := []struct {
    47  		name     string
    48  		mutation func(c *Config)
    49  		err      error
    50  	}{
    51  		{
    52  			"valid config",
    53  			nil,
    54  			nil,
    55  		},
    56  		{
    57  			"requires name",
    58  			func(c *Config) { c.Name = "" },
    59  			fmt.Errorf("missing instance name"),
    60  		},
    61  		{
    62  			"missing wal truncate frequency",
    63  			func(c *Config) { c.TruncateFrequency = 0 },
    64  			fmt.Errorf("wal_truncate_frequency must be greater than 0s"),
    65  		},
    66  		{
    67  			"missing remote flush deadline",
    68  			func(c *Config) { c.RemoteFlushDeadline = 0 },
    69  			fmt.Errorf("remote_flush_deadline must be greater than 0s"),
    70  		},
    71  		{
    72  			"empty remote write",
    73  			func(c *Config) { c.RemoteWrite = append(c.RemoteWrite, nil) },
    74  			fmt.Errorf("empty or null remote write config section"),
    75  		},
    76  	}
    77  
    78  	for _, tc := range tt {
    79  		t.Run(tc.name, func(t *testing.T) {
    80  
    81  			// Copy the input and all of its slices
    82  			input := cfg
    83  
    84  			var remoteWrites []*config.RemoteWriteConfig
    85  			for _, rw := range input.RemoteWrite {
    86  				rwCopy := *rw
    87  				remoteWrites = append(remoteWrites, &rwCopy)
    88  			}
    89  			input.RemoteWrite = remoteWrites
    90  
    91  			if tc.mutation != nil {
    92  				tc.mutation(&input)
    93  			}
    94  
    95  			err := input.ApplyDefaults()
    96  			if tc.err == nil {
    97  				require.NoError(t, err)
    98  			} else {
    99  				require.EqualError(t, err, tc.err.Error())
   100  			}
   101  		})
   102  	}
   103  }
   104  
   105  func TestMetricValueCollector(t *testing.T) {
   106  	r := prometheus.NewRegistry()
   107  	vc := NewMetricValueCollector(r, "this_should_be_tracked")
   108  
   109  	shouldTrack := prometheus.NewGauge(prometheus.GaugeOpts{
   110  		Name: "this_should_be_tracked",
   111  		ConstLabels: prometheus.Labels{
   112  			"foo": "bar",
   113  		},
   114  	})
   115  
   116  	shouldTrack.Set(12345)
   117  
   118  	shouldNotTrack := prometheus.NewCounter(prometheus.CounterOpts{
   119  		Name: "this_should_not_be_tracked",
   120  	})
   121  
   122  	r.MustRegister(shouldTrack, shouldNotTrack)
   123  
   124  	vals, err := vc.GetValues("foo", "bar")
   125  	require.NoError(t, err)
   126  	require.Equal(t, []float64{12345}, vals)
   127  }
   128  
   129  func TestRemoteWriteMetricInterceptor_AllValues(t *testing.T) {
   130  	r := prometheus.NewRegistry()
   131  	vc := NewMetricValueCollector(r, "track")
   132  
   133  	valueA := prometheus.NewGauge(prometheus.GaugeOpts{
   134  		Name: "this_should_be_tracked",
   135  		ConstLabels: prometheus.Labels{
   136  			"foo": "bar",
   137  		},
   138  	})
   139  	valueA.Set(12345)
   140  
   141  	valueB := prometheus.NewGauge(prometheus.GaugeOpts{
   142  		Name: "track_this_too",
   143  		ConstLabels: prometheus.Labels{
   144  			"foo": "bar",
   145  		},
   146  	})
   147  	valueB.Set(67890)
   148  
   149  	shouldNotReturn := prometheus.NewGauge(prometheus.GaugeOpts{
   150  		Name: "track_this_but_label_does_not_match",
   151  		ConstLabels: prometheus.Labels{
   152  			"foo": "nope",
   153  		},
   154  	})
   155  
   156  	r.MustRegister(valueA, valueB, shouldNotReturn)
   157  
   158  	vals, err := vc.GetValues("foo", "bar")
   159  	require.NoError(t, err)
   160  	require.Equal(t, []float64{12345, 67890}, vals)
   161  }
   162  
   163  // TestInstance tests that discovery and scraping are working by using a mock
   164  // instance of the WAL storage and testing that samples get written to it.
   165  // This test touches most of Instance and is enough for a basic integration test.
   166  func TestInstance(t *testing.T) {
   167  	walDir := t.TempDir()
   168  
   169  	mockStorage := mockWalStorage{
   170  		series:    make(map[storage.SeriesRef]int),
   171  		directory: walDir,
   172  	}
   173  	newWal := func(_ prometheus.Registerer) (walStorage, error) { return &mockStorage, nil }
   174  
   175  	logger := level.NewFilter(log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)), level.AllowInfo())
   176  	cfg := DefaultConfig
   177  	cfg.Dir = walDir
   178  	inst, err := newInstance(cfg, nil, logger, newWal, "12345")
   179  	require.NoError(t, err)
   180  	runInstance(t, inst)
   181  
   182  	// Wait until mockWalStorage is initialized.
   183  	test.Poll(t, 10*time.Second, true, func() interface{} {
   184  		mockStorage.mut.Lock()
   185  		defer mockStorage.mut.Unlock()
   186  		return inst.Ready()
   187  	})
   188  
   189  	app := inst.Appender(context.TODO())
   190  	refTime := time.Now().UnixNano()
   191  
   192  	count := 3
   193  	for i := 0; i < count; i++ {
   194  		_, err := app.Append(0, labels.Labels{
   195  			labels.Label{Name: "__name__", Value: "test"},
   196  			labels.Label{Name: "iter", Value: fmt.Sprintf("%v", i)},
   197  		}, refTime-int64(i), float64(i))
   198  
   199  		require.NoError(t, err)
   200  	}
   201  	assert.Len(t, mockStorage.series, count)
   202  }
   203  
   204  type mockWalStorage struct {
   205  	storage.Queryable
   206  	storage.ChunkQueryable
   207  
   208  	directory string
   209  	mut       sync.Mutex
   210  	series    map[storage.SeriesRef]int
   211  }
   212  
   213  func (s *mockWalStorage) Directory() string                          { return s.directory }
   214  func (s *mockWalStorage) StartTime() (int64, error)                  { return 0, nil }
   215  func (s *mockWalStorage) WriteStalenessMarkers(f func() int64) error { return nil }
   216  func (s *mockWalStorage) Close() error                               { return nil }
   217  func (s *mockWalStorage) Truncate(mint int64) error                  { return nil }
   218  
   219  func (s *mockWalStorage) Appender(context.Context) storage.Appender {
   220  	return &mockAppender{s: s}
   221  }
   222  
   223  type mockAppender struct {
   224  	s *mockWalStorage
   225  }
   226  
   227  func (a *mockAppender) Append(ref storage.SeriesRef, l labels.Labels, t int64, v float64) (storage.SeriesRef, error) {
   228  	if ref == 0 {
   229  		return a.Add(l, t, v)
   230  	}
   231  	return ref, a.AddFast(ref, t, v)
   232  }
   233  
   234  // Add adds a new series and sets its written count to 1.
   235  func (a *mockAppender) Add(l labels.Labels, t int64, v float64) (storage.SeriesRef, error) {
   236  	a.s.mut.Lock()
   237  	defer a.s.mut.Unlock()
   238  
   239  	hash := l.Hash()
   240  	a.s.series[storage.SeriesRef(hash)] = 1
   241  	return storage.SeriesRef(hash), nil
   242  }
   243  
   244  // AddFast increments the number of writes to an existing series.
   245  func (a *mockAppender) AddFast(ref storage.SeriesRef, t int64, v float64) error {
   246  	a.s.mut.Lock()
   247  	defer a.s.mut.Unlock()
   248  	_, ok := a.s.series[ref]
   249  	if !ok {
   250  		return storage.ErrNotFound
   251  	}
   252  
   253  	a.s.series[ref]++
   254  	return nil
   255  }
   256  
   257  func (a *mockAppender) AppendExemplar(ref storage.SeriesRef, l labels.Labels, e exemplar.Exemplar) (storage.SeriesRef, error) {
   258  	return 0, nil
   259  }
   260  
   261  func (a *mockAppender) Commit() error {
   262  	return nil
   263  }
   264  
   265  func (a *mockAppender) Rollback() error {
   266  	return nil
   267  }
   268  
   269  func runInstance(t *testing.T, i *Instance) {
   270  	ctx, cancel := context.WithCancel(context.Background())
   271  	t.Cleanup(func() { cancel() })
   272  	go require.NotPanics(t, func() {
   273  		_ = i.Run(ctx)
   274  	})
   275  }