2024-06-13 14:08:02 -07:00
|
|
|
package rules
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"log/slog"
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
|
2024-07-30 10:18:03 -07:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
)
|
2024-06-13 14:08:02 -07:00
|
|
|
|
|
|
|
// FileStore implements the AlertStore interface.
|
|
|
|
type FileStore struct {
|
|
|
|
alertsByRule map[uint64][]*Alert
|
2024-12-10 09:44:57 -08:00
|
|
|
logger *slog.Logger
|
2024-07-16 10:15:28 -07:00
|
|
|
// protects the `alertsByRule` map.
|
2024-07-30 10:18:03 -07:00
|
|
|
stateMtx sync.RWMutex
|
|
|
|
path string
|
|
|
|
storeInitErrors prometheus.Counter
|
|
|
|
alertStoreErrors *prometheus.CounterVec
|
2024-06-13 14:08:02 -07:00
|
|
|
}
|
|
|
|
|
2024-12-10 09:44:57 -08:00
|
|
|
type FileData struct {
|
|
|
|
Alerts map[uint64][]*Alert `json:"alerts"`
|
|
|
|
}
|
|
|
|
|
2024-07-30 10:18:03 -07:00
|
|
|
func NewFileStore(l *slog.Logger, storagePath string, registerer prometheus.Registerer) *FileStore {
|
2024-06-13 14:08:02 -07:00
|
|
|
s := &FileStore{
|
|
|
|
logger: l,
|
|
|
|
alertsByRule: make(map[uint64][]*Alert),
|
|
|
|
path: storagePath,
|
|
|
|
}
|
2024-07-30 10:18:03 -07:00
|
|
|
s.storeInitErrors = prometheus.NewCounter(
|
|
|
|
prometheus.CounterOpts{
|
|
|
|
Namespace: namespace,
|
|
|
|
Name: "alert_store_init_errors_total",
|
|
|
|
Help: "The total number of errors starting alert store.",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
s.alertStoreErrors = prometheus.NewCounterVec(
|
|
|
|
prometheus.CounterOpts{
|
|
|
|
Namespace: namespace,
|
|
|
|
Name: "rule_group_alert_store_errors_total",
|
|
|
|
Help: "The total number of errors in alert store.",
|
|
|
|
},
|
|
|
|
[]string{"rule_group"},
|
|
|
|
)
|
2024-12-10 09:44:57 -08:00
|
|
|
s.initState(registerer)
|
2024-06-13 14:08:02 -07:00
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2024-07-16 10:15:28 -07:00
|
|
|
// initState reads the state from file storage into the alertsByRule map.
|
2024-12-10 09:44:57 -08:00
|
|
|
func (s *FileStore) initState(registerer prometheus.Registerer) {
|
|
|
|
if registerer != nil {
|
|
|
|
registerer.MustRegister(s.alertStoreErrors, s.storeInitErrors)
|
2024-07-30 10:18:03 -07:00
|
|
|
}
|
2024-12-10 09:44:57 -08:00
|
|
|
file, err := os.OpenFile(s.path, os.O_RDWR|os.O_CREATE, 0o644)
|
2024-06-13 14:08:02 -07:00
|
|
|
if err != nil {
|
|
|
|
s.logger.Error("Failed reading alerts state from file", "err", err)
|
2024-07-30 10:18:03 -07:00
|
|
|
s.storeInitErrors.Inc()
|
2024-06-13 14:08:02 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
2024-12-10 09:44:57 -08:00
|
|
|
var data *FileData
|
|
|
|
err = json.NewDecoder(file).Decode(&data)
|
2024-06-13 14:08:02 -07:00
|
|
|
if err != nil {
|
2024-12-10 09:44:57 -08:00
|
|
|
data = nil
|
2024-06-13 14:08:02 -07:00
|
|
|
s.logger.Error("Failed reading alerts state from file", "err", err)
|
2024-07-30 10:18:03 -07:00
|
|
|
s.storeInitErrors.Inc()
|
2024-06-13 14:08:02 -07:00
|
|
|
}
|
2024-12-10 09:44:57 -08:00
|
|
|
alertsByRule := make(map[uint64][]*Alert)
|
|
|
|
if data != nil && data.Alerts != nil {
|
|
|
|
alertsByRule = data.Alerts
|
2024-06-13 14:08:02 -07:00
|
|
|
}
|
|
|
|
s.alertsByRule = alertsByRule
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAlerts returns the stored alerts for an alerting rule
|
2024-07-16 10:15:28 -07:00
|
|
|
// Alert state is read from the in memory map which is populated during initialization.
|
2024-06-13 14:08:02 -07:00
|
|
|
func (s *FileStore) GetAlerts(key uint64) (map[uint64]*Alert, error) {
|
|
|
|
s.stateMtx.RLock()
|
|
|
|
defer s.stateMtx.RUnlock()
|
|
|
|
|
|
|
|
restoredAlerts, ok := s.alertsByRule[key]
|
|
|
|
if !ok {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
alerts := make(map[uint64]*Alert)
|
|
|
|
for _, alert := range restoredAlerts {
|
|
|
|
if alert == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
h := alert.Labels.Hash()
|
|
|
|
alerts[h] = alert
|
|
|
|
}
|
|
|
|
return alerts, nil
|
|
|
|
}
|
|
|
|
|
2024-07-16 10:15:28 -07:00
|
|
|
// SetAlerts updates the stateByRule map and writes state to file storage.
|
2024-07-30 10:18:03 -07:00
|
|
|
func (s *FileStore) SetAlerts(key uint64, groupKey string, alerts []*Alert) error {
|
2024-06-13 14:08:02 -07:00
|
|
|
s.stateMtx.Lock()
|
|
|
|
defer s.stateMtx.Unlock()
|
|
|
|
|
|
|
|
// Update in memory
|
|
|
|
if alerts != nil {
|
|
|
|
s.alertsByRule[key] = alerts
|
2024-12-10 09:44:57 -08:00
|
|
|
} else {
|
|
|
|
return nil
|
2024-06-13 14:08:02 -07:00
|
|
|
}
|
|
|
|
// flush in memory state to file storage
|
|
|
|
file, err := os.Create(s.path)
|
|
|
|
if err != nil {
|
2024-07-30 10:18:03 -07:00
|
|
|
s.alertStoreErrors.WithLabelValues(groupKey).Inc()
|
2024-06-13 14:08:02 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
encoder := json.NewEncoder(file)
|
2024-12-10 09:44:57 -08:00
|
|
|
data := FileData{
|
|
|
|
Alerts: s.alertsByRule,
|
|
|
|
}
|
|
|
|
err = encoder.Encode(data)
|
2024-06-13 14:08:02 -07:00
|
|
|
if err != nil {
|
2024-07-30 10:18:03 -07:00
|
|
|
s.alertStoreErrors.WithLabelValues(groupKey).Inc()
|
2024-06-13 14:08:02 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|