github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ruler/storage/instance/instance.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 "bytes" 8 "context" 9 "errors" 10 "flag" 11 "fmt" 12 "math" 13 "path/filepath" 14 "strings" 15 "sync" 16 "time" 17 18 "github.com/go-kit/log" 19 "github.com/go-kit/log/level" 20 "github.com/oklog/run" 21 "github.com/prometheus/client_golang/prometheus" 22 "github.com/prometheus/prometheus/config" 23 "github.com/prometheus/prometheus/model/timestamp" 24 "github.com/prometheus/prometheus/scrape" 25 "github.com/prometheus/prometheus/storage" 26 "github.com/prometheus/prometheus/storage/remote" 27 "gopkg.in/yaml.v2" 28 29 "github.com/grafana/loki/pkg/ruler/storage/util" 30 "github.com/grafana/loki/pkg/ruler/storage/wal" 31 "github.com/grafana/loki/pkg/util/build" 32 ) 33 34 func init() { 35 remote.UserAgent = fmt.Sprintf("LokiRulerWAL/%s", build.Version) 36 } 37 38 var ( 39 remoteWriteMetricName = "queue_highest_sent_timestamp_seconds" 40 ) 41 42 // Default configuration values 43 var ( 44 DefaultConfig = Config{ 45 Dir: "ruler-wal", 46 TruncateFrequency: 60 * time.Minute, 47 MinAge: 5 * time.Minute, 48 MaxAge: 4 * time.Hour, 49 RemoteFlushDeadline: 1 * time.Minute, 50 } 51 ) 52 53 // Config is a specific agent that runs within the overall Prometheus 54 // agent. It has its own set of scrape_configs and remote_write rules. 55 type Config struct { 56 Tenant string 57 Name string 58 RemoteWrite []*config.RemoteWriteConfig 59 60 Dir string `yaml:"dir"` 61 62 // How frequently the WAL should be truncated. 63 TruncateFrequency time.Duration `yaml:"truncate_frequency,omitempty"` 64 65 // Minimum and maximum time series should exist in the WAL for. 66 MinAge time.Duration `yaml:"min_age,omitempty"` 67 MaxAge time.Duration `yaml:"max_age,omitempty"` 68 69 RemoteFlushDeadline time.Duration `yaml:"remote_flush_deadline,omitempty"` 70 } 71 72 // UnmarshalYAML implements yaml.Unmarshaler. 73 func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { 74 *c = DefaultConfig 75 76 type plain Config 77 return unmarshal((*plain)(c)) 78 } 79 80 // MarshalYAML implements yaml.Marshaler. 81 func (c Config) MarshalYAML() (interface{}, error) { 82 // We want users to be able to marshal instance.Configs directly without 83 // *needing* to call instance.MarshalConfig, so we call it internally 84 // here and return a map. 85 bb, err := MarshalConfig(&c, false) 86 if err != nil { 87 return nil, err 88 } 89 90 // Use a yaml.MapSlice rather than a map[string]interface{} so 91 // order of keys is retained compared to just calling MarshalConfig. 92 var m yaml.MapSlice 93 if err := yaml.Unmarshal(bb, &m); err != nil { 94 return nil, err 95 } 96 return m, nil 97 } 98 99 // ApplyDefaults applies default configurations to the configuration to all 100 // values that have not been changed to their non-zero value. ApplyDefaults 101 // also validates the config. 102 // 103 // The value for global will saved. 104 func (c *Config) ApplyDefaults() error { 105 switch { 106 case c.Name == "": 107 return errors.New("missing instance name") 108 case c.TruncateFrequency <= 0: 109 return errors.New("wal_truncate_frequency must be greater than 0s") 110 case c.RemoteFlushDeadline <= 0: 111 return errors.New("remote_flush_deadline must be greater than 0s") 112 case c.MinAge > c.MaxAge: 113 return errors.New("min_wal_time must be less than max_wal_time") 114 } 115 116 for _, cfg := range c.RemoteWrite { 117 if cfg == nil { 118 return fmt.Errorf("empty or null remote write config section") 119 } 120 } 121 return nil 122 } 123 124 // Clone makes a deep copy of the config along with global settings. 125 func (c *Config) Clone() (Config, error) { 126 bb, err := MarshalConfig(c, false) 127 if err != nil { 128 return Config{}, err 129 } 130 cp, err := UnmarshalConfig(bytes.NewReader(bb)) 131 if err != nil { 132 return Config{}, err 133 } 134 135 // Some tests will trip up on this; the marshal/unmarshal cycle might set 136 // an empty slice to nil. Set it back to an empty slice if we detect this 137 // happening. 138 if cp.RemoteWrite == nil && c.RemoteWrite != nil { 139 cp.RemoteWrite = []*config.RemoteWriteConfig{} 140 } 141 142 return *cp, nil 143 } 144 145 func (c *Config) RegisterFlags(f *flag.FlagSet) { 146 f.StringVar(&c.Dir, "ruler.wal.dir", DefaultConfig.Dir, "Directory to store the WAL and/or recover from WAL.") 147 f.DurationVar(&c.TruncateFrequency, "ruler.wal.truncate-frequency", DefaultConfig.TruncateFrequency, "How often to run the WAL truncation.") 148 f.DurationVar(&c.MinAge, "ruler.wal.min-age", DefaultConfig.MinAge, "Minimum age that samples must exist in the WAL before being truncated.") 149 f.DurationVar(&c.MaxAge, "ruler.wal.max-age", DefaultConfig.MaxAge, "Maximum age that samples must exist in the WAL before being truncated.") 150 } 151 152 type walStorageFactory func(reg prometheus.Registerer) (walStorage, error) 153 154 // Instance is an individual metrics collector and remote_writer. 155 type Instance struct { 156 initialized bool 157 158 // All fields in the following block may be accessed and modified by 159 // concurrently running goroutines. 160 // 161 // Note that all Prometheus components listed here may be nil at any 162 // given time; methods reading them should take care to do nil checks. 163 mut sync.Mutex 164 cfg Config 165 wal walStorage 166 remoteStore *remote.Storage 167 storage storage.Storage 168 169 logger log.Logger 170 171 reg prometheus.Registerer 172 newWal walStorageFactory 173 174 vc *MetricValueCollector 175 tenant string 176 } 177 178 // New creates a new Instance with a directory for storing the WAL. The instance 179 // will not start until Run is called on the instance. 180 func New(reg prometheus.Registerer, cfg Config, metrics *wal.Metrics, logger log.Logger) (*Instance, error) { 181 logger = log.With(logger, "instance", cfg.Name) 182 183 instWALDir := filepath.Join(cfg.Dir, cfg.Tenant) 184 185 newWal := func(reg prometheus.Registerer) (walStorage, error) { 186 return wal.NewStorage(logger, metrics, reg, instWALDir) 187 } 188 189 return newInstance(cfg, reg, logger, newWal, cfg.Tenant) 190 } 191 192 func newInstance(cfg Config, reg prometheus.Registerer, logger log.Logger, newWal walStorageFactory, tenant string) (*Instance, error) { 193 vc := NewMetricValueCollector(prometheus.DefaultGatherer, remoteWriteMetricName) 194 195 i := &Instance{ 196 cfg: cfg, 197 logger: logger, 198 vc: vc, 199 200 reg: reg, 201 newWal: newWal, 202 203 tenant: tenant, 204 } 205 206 return i, nil 207 } 208 209 func (i *Instance) Storage() storage.Storage { 210 i.mut.Lock() 211 defer i.mut.Unlock() 212 213 return i.storage 214 } 215 216 // Run starts the instance, initializing Prometheus components, and will 217 // continue to run until an error happens during execution or the provided 218 // context is cancelled. 219 // 220 // Run may be re-called after exiting, as components will be reinitialized each 221 // time Run is called. 222 func (i *Instance) Run(ctx context.Context) error { 223 // i.cfg may change at any point in the middle of this method but not in a way 224 // that affects any of the code below; rather than grabbing a mutex every time 225 // we want to read the config, we'll simplify the access and just grab a copy 226 // now. 227 i.mut.Lock() 228 cfg := i.cfg 229 i.mut.Unlock() 230 231 level.Debug(i.logger).Log("msg", "initializing instance", "name", cfg.Name) 232 233 // trackingReg wraps the register for the instance to make sure that if Run 234 // exits, any metrics Prometheus registers are removed and can be 235 // re-registered if Run is called again. 236 trackingReg := util.WrapWithUnregisterer(i.reg) 237 defer trackingReg.UnregisterAll() 238 239 if err := i.initialize(ctx, trackingReg, &cfg); err != nil { 240 level.Error(i.logger).Log("msg", "failed to initialize instance", "err", err) 241 return fmt.Errorf("failed to initialize instance: %w", err) 242 } 243 244 // The actors defined here are defined in the order we want them to shut down. 245 // Primarily, we want to ensure that the following shutdown order is 246 // maintained: 247 // 1. The scrape manager stops 248 // 2. WAL storage is closed 249 // 3. Remote write storage is closed 250 // This is done to allow the instance to write stale markers for all active 251 // series. 252 rg := runGroupWithContext(ctx) 253 254 { 255 // Truncation loop 256 ctx, contextCancel := context.WithCancel(context.Background()) 257 defer contextCancel() 258 rg.Add( 259 func() error { 260 i.truncateLoop(ctx, i.wal, &cfg) 261 level.Info(i.logger).Log("msg", "truncation loop stopped") 262 return nil 263 }, 264 func(err error) { 265 level.Info(i.logger).Log("msg", "stopping truncation loop...") 266 contextCancel() 267 }, 268 ) 269 } 270 271 level.Debug(i.logger).Log("msg", "running instance", "name", cfg.Name) 272 err := rg.Run() 273 if err != nil { 274 level.Error(i.logger).Log("msg", "agent instance stopped with error", "err", err) 275 } 276 return err 277 } 278 279 type noopScrapeManager struct{} 280 281 func (n noopScrapeManager) Get() (*scrape.Manager, error) { 282 return nil, nil 283 } 284 285 // initialize sets up the various Prometheus components with their initial 286 // settings. initialize will be called each time the Instance is run. Prometheus 287 // components cannot be reused after they are stopped so we need to recreate them 288 // each run. 289 func (i *Instance) initialize(_ context.Context, reg prometheus.Registerer, cfg *Config) error { 290 i.mut.Lock() 291 defer i.mut.Unlock() 292 293 // explicitly set this in case this function is called multiple times 294 i.initialized = false 295 296 var err error 297 298 i.wal, err = i.newWal(reg) 299 if err != nil { 300 return fmt.Errorf("error creating WAL: %w", err) 301 } 302 303 // Setup the remote storage 304 remoteLogger := log.With(i.logger, "component", "remote") 305 i.remoteStore = remote.NewStorage(remoteLogger, reg, i.wal.StartTime, i.wal.Directory(), cfg.RemoteFlushDeadline, noopScrapeManager{}) 306 err = i.remoteStore.ApplyConfig(&config.Config{ 307 RemoteWriteConfigs: cfg.RemoteWrite, 308 }) 309 if err != nil { 310 return fmt.Errorf("failed applying config to remote storage: %w", err) 311 } 312 313 i.storage = storage.NewFanout(i.logger, i.wal, i.remoteStore) 314 i.initialized = true 315 316 return nil 317 } 318 319 // Update accepts a new Config for the Instance and will dynamically update any 320 // running Prometheus components with the new values from Config. Update will 321 // return an ErrInvalidUpdate if the Update could not be applied. 322 func (i *Instance) Update(c Config) (err error) { 323 i.mut.Lock() 324 defer i.mut.Unlock() 325 326 // It's only (currently) valid to update scrape_configs and remote_write, so 327 // if any other field has changed here, return the error. 328 switch { 329 // This first case should never happen in practice but it's included here for 330 // completions sake. 331 case i.cfg.Name != c.Name: 332 err = errImmutableField{Field: "name"} 333 case i.cfg.TruncateFrequency != c.TruncateFrequency: 334 err = errImmutableField{Field: "wal_truncate_frequency"} 335 case i.cfg.RemoteFlushDeadline != c.RemoteFlushDeadline: 336 err = errImmutableField{Field: "remote_flush_deadline"} 337 } 338 if err != nil { 339 return ErrInvalidUpdate{Inner: err} 340 } 341 342 // Check to see if the components exist yet. 343 if i.remoteStore == nil { 344 return ErrInvalidUpdate{ 345 Inner: fmt.Errorf("cannot dynamically update because instance is not running"), 346 } 347 } 348 349 // NOTE(rfratto): Prometheus applies configs in a specific order to ensure 350 // flow from service discovery down to the WAL continues working properly. 351 // 352 // Keep the following order below: 353 // 354 // 1. Local config 355 // 2. Remote Store 356 // 3. Scrape Manager 357 // 4. Discovery Manager 358 359 originalConfig := i.cfg 360 defer func() { 361 if err != nil { 362 i.cfg = originalConfig 363 } 364 }() 365 i.cfg = c 366 367 err = i.remoteStore.ApplyConfig(&config.Config{ 368 RemoteWriteConfigs: c.RemoteWrite, 369 }) 370 if err != nil { 371 return fmt.Errorf("error applying new remote_write configs: %w", err) 372 } 373 374 return nil 375 } 376 377 // Ready indicates if the instance is ready for processing. 378 func (i *Instance) Ready() bool { 379 i.mut.Lock() 380 defer i.mut.Unlock() 381 382 return i.initialized 383 } 384 385 // StorageDirectory returns the directory where this Instance is writing series 386 // and samples to for the WAL. 387 func (i *Instance) StorageDirectory() string { 388 return i.wal.Directory() 389 } 390 391 // Appender returns a storage.Appender from the instance's WAL 392 func (i *Instance) Appender(ctx context.Context) storage.Appender { 393 return i.wal.Appender(ctx) 394 } 395 396 // Stop stops the WAL 397 func (i *Instance) Stop() error { 398 level.Info(i.logger).Log("msg", "stopping WAL instance", "user", i.Tenant()) 399 400 // close WAL first to prevent any further appends 401 if err := i.wal.Close(); err != nil { 402 level.Error(i.logger).Log("msg", "error stopping WAL instance", "user", i.Tenant(), "err", err) 403 return err 404 } 405 406 if err := i.remoteStore.Close(); err != nil { 407 level.Error(i.logger).Log("msg", "error stopping remote storage instance", "user", i.Tenant(), "err", err) 408 return err 409 } 410 411 return nil 412 } 413 414 // Tenant returns the tenant name of the instance 415 func (i *Instance) Tenant() string { 416 return i.tenant 417 } 418 419 func (i *Instance) truncateLoop(ctx context.Context, wal walStorage, cfg *Config) { 420 // Track the last timestamp we truncated for to prevent segments from getting 421 // deleted until at least some new data has been sent. 422 var lastTs int64 = math.MinInt64 423 424 for { 425 select { 426 case <-ctx.Done(): 427 return 428 case <-time.After(cfg.TruncateFrequency): 429 // The timestamp ts is used to determine which series are not receiving 430 // samples and may be deleted from the WAL. Their most recent append 431 // timestamp is compared to ts, and if that timestamp is older then ts, 432 // they are considered inactive and may be deleted. 433 // 434 // Subtracting a duration from ts will delay when it will be considered 435 // inactive and scheduled for deletion. 436 ts := i.getRemoteWriteTimestamp() - i.cfg.MinAge.Milliseconds() 437 if ts < 0 { 438 ts = 0 439 } 440 441 // Network issues can prevent the result of getRemoteWriteTimestamp from 442 // changing. We don't want data in the WAL to grow forever, so we set a cap 443 // on the maximum age data can be. If our ts is older than this cutoff point, 444 // we'll shift it forward to start deleting very stale data. 445 if maxTS := timestamp.FromTime(time.Now().Add(-i.cfg.MaxAge)); ts < maxTS { 446 ts = maxTS 447 } 448 449 if ts == lastTs { 450 level.Debug(i.logger).Log("msg", "not truncating the WAL, remote_write timestamp is unchanged", "ts", ts) 451 continue 452 } 453 lastTs = ts 454 455 level.Debug(i.logger).Log("msg", "truncating the WAL", "ts", ts) 456 err := wal.Truncate(ts) 457 if err != nil { 458 // The only issue here is larger disk usage and a greater replay time, 459 // so we'll only log this as a warning. 460 level.Warn(i.logger).Log("msg", "could not truncate WAL", "err", err) 461 } 462 } 463 } 464 } 465 466 // getRemoteWriteTimestamp looks up the last successful remote write timestamp. 467 // This is passed to wal.Storage for its truncation. If no remote write sections 468 // are configured, getRemoteWriteTimestamp returns the current time. 469 func (i *Instance) getRemoteWriteTimestamp() int64 { 470 i.mut.Lock() 471 defer i.mut.Unlock() 472 473 if len(i.cfg.RemoteWrite) == 0 { 474 return timestamp.FromTime(time.Now()) 475 } 476 477 lbls := make([]string, len(i.cfg.RemoteWrite)) 478 for idx := 0; idx < len(lbls); idx++ { 479 lbls[idx] = i.cfg.RemoteWrite[idx].Name 480 } 481 482 vals, err := i.vc.GetValues("remote_name", lbls...) 483 if err != nil { 484 level.Error(i.logger).Log("msg", "could not get remote write timestamps", "err", err) 485 return 0 486 } 487 if len(vals) == 0 { 488 return 0 489 } 490 491 // We use the lowest value from the metric since we don't want to delete any 492 // segments from the WAL until they've been written by all of the remote_write 493 // configurations. 494 ts := int64(math.MaxInt64) 495 for _, val := range vals { 496 ival := int64(val) 497 if ival < ts { 498 ts = ival 499 } 500 } 501 502 // Convert to the millisecond precision which is used by the WAL 503 return ts * 1000 504 } 505 506 // walStorage is an interface satisfied by wal.Storage, and created for testing. 507 type walStorage interface { 508 // walStorage implements Queryable/ChunkQueryable for compatibility, but is unused. 509 storage.Queryable 510 storage.ChunkQueryable 511 512 Directory() string 513 514 StartTime() (int64, error) 515 WriteStalenessMarkers(remoteTsFunc func() int64) error 516 Appender(context.Context) storage.Appender 517 Truncate(mint int64) error 518 519 Close() error 520 } 521 522 // MetricValueCollector wraps around a Gatherer and provides utilities for 523 // pulling metric values from a given metric name and label matchers. 524 // 525 // This is used by the agent instances to find the most recent timestamp 526 // successfully remote_written to for purposes of safely truncating the WAL. 527 // 528 // MetricValueCollector is only intended for use with Gauges and Counters. 529 type MetricValueCollector struct { 530 g prometheus.Gatherer 531 match string 532 } 533 534 // NewMetricValueCollector creates a new MetricValueCollector. 535 func NewMetricValueCollector(g prometheus.Gatherer, match string) *MetricValueCollector { 536 return &MetricValueCollector{ 537 g: g, 538 match: match, 539 } 540 } 541 542 // GetValues looks through all the tracked metrics and returns all values 543 // for metrics that match some key value pair. 544 func (vc *MetricValueCollector) GetValues(label string, labelValues ...string) ([]float64, error) { 545 vals := []float64{} 546 547 families, err := vc.g.Gather() 548 if err != nil { 549 return nil, err 550 } 551 552 for _, family := range families { 553 if !strings.Contains(family.GetName(), vc.match) { 554 continue 555 } 556 557 for _, m := range family.GetMetric() { 558 matches := false 559 for _, l := range m.GetLabel() { 560 if l.GetName() != label { 561 continue 562 } 563 564 v := l.GetValue() 565 for _, match := range labelValues { 566 if match == v { 567 matches = true 568 break 569 } 570 } 571 break 572 } 573 if !matches { 574 continue 575 } 576 577 var value float64 578 if m.Gauge != nil { 579 value = m.Gauge.GetValue() 580 } else if m.Counter != nil { 581 value = m.Counter.GetValue() 582 } else if m.Untyped != nil { 583 value = m.Untyped.GetValue() 584 } else { 585 return nil, errors.New("tracking unexpected metric type") 586 } 587 588 vals = append(vals, value) 589 } 590 } 591 592 return vals, nil 593 } 594 595 type runGroupContext struct { 596 cancel context.CancelFunc 597 598 g *run.Group 599 } 600 601 // runGroupWithContext creates a new run.Group that will be stopped if the 602 // context gets canceled in addition to the normal behavior of stopping 603 // when any of the actors stop. 604 func runGroupWithContext(ctx context.Context) *runGroupContext { 605 ctx, cancel := context.WithCancel(ctx) 606 607 var g run.Group 608 g.Add(func() error { 609 <-ctx.Done() 610 return nil 611 }, func(_ error) { 612 cancel() 613 }) 614 615 return &runGroupContext{cancel: cancel, g: &g} 616 } 617 618 func (rg *runGroupContext) Add(execute func() error, interrupt func(error)) { 619 rg.g.Add(execute, interrupt) 620 } 621 622 func (rg *runGroupContext) Run() error { return rg.g.Run() } 623 func (rg *runGroupContext) Stop(_ error) { rg.cancel() }