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 }