mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 08:13:47 +08:00
108 lines
3.0 KiB
Go
108 lines
3.0 KiB
Go
package state
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
)
|
|
|
|
// MultiInstanceReader merges results from two InstanceReaders.
|
|
//
|
|
// For each rule, it picks the state that has the newer LastEvalTime.
|
|
type MultiInstanceReader struct {
|
|
ProtoDBReader InstanceReader
|
|
DBReader InstanceReader
|
|
logger log.Logger
|
|
}
|
|
|
|
func NewMultiInstanceReader(logger log.Logger, r1, r2 InstanceReader) *MultiInstanceReader {
|
|
return &MultiInstanceReader{
|
|
ProtoDBReader: r1,
|
|
DBReader: r2,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// ListAlertInstances fetches alert instances for a query from both readers,
|
|
// groups them by rule UID, and returns the newest instances for each rule as a
|
|
// single slice.
|
|
func (m *MultiInstanceReader) ListAlertInstances(ctx context.Context, cmd *models.ListAlertInstancesQuery) ([]*models.AlertInstance, error) {
|
|
instancesOne, err := m.ProtoDBReader.ListAlertInstances(ctx, cmd)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list alert instances from ProtoDBReader: %w", err)
|
|
}
|
|
|
|
instancesTwo, err := m.DBReader.ListAlertInstances(ctx, cmd)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list alert instances from DBReader: %w", err)
|
|
}
|
|
|
|
byRuleOne := groupByRuleUID(instancesOne)
|
|
byRuleTwo := groupByRuleUID(instancesTwo)
|
|
|
|
ruleSet := make(map[string]struct{})
|
|
for uid := range byRuleOne {
|
|
ruleSet[uid] = struct{}{}
|
|
}
|
|
for uid := range byRuleTwo {
|
|
ruleSet[uid] = struct{}{}
|
|
}
|
|
|
|
merged := make([]*models.AlertInstance, 0)
|
|
for ruleUID := range ruleSet {
|
|
sliceA := byRuleOne[ruleUID]
|
|
sliceB := byRuleTwo[ruleUID]
|
|
newer := getNewestAlertInstances(m.logger, sliceA, sliceB)
|
|
merged = append(merged, newer...)
|
|
}
|
|
|
|
return merged, nil
|
|
}
|
|
|
|
func groupByRuleUID(instances []*models.AlertInstance) map[string][]*models.AlertInstance {
|
|
result := make(map[string][]*models.AlertInstance)
|
|
for _, inst := range instances {
|
|
if inst == nil {
|
|
continue
|
|
}
|
|
result[inst.RuleUID] = append(result[inst.RuleUID], inst)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// getNewestAlertInstances returns the newest alert instances slice
|
|
// by comparing the maximum LastEvalTime in each of them. If one is empty, returns the other.
|
|
func getNewestAlertInstances(logger log.Logger, firstInstances, secondInstances []*models.AlertInstance) []*models.AlertInstance {
|
|
if len(firstInstances) == 0 {
|
|
return secondInstances
|
|
}
|
|
if len(secondInstances) == 0 {
|
|
return firstInstances
|
|
}
|
|
|
|
maxFirst := maxLastEvalTime(firstInstances)
|
|
maxSecond := maxLastEvalTime(secondInstances)
|
|
|
|
if maxSecond.After(maxFirst) {
|
|
logger.Debug("Newer alert instances found", "first_last_eval_time", maxFirst, "second_last_eval_time", maxSecond, "rule_uid", firstInstances[0].RuleUID)
|
|
return secondInstances
|
|
}
|
|
return firstInstances
|
|
}
|
|
|
|
// maxLastEvalTime finds the maximum LastEvalTime among a slice of alert instances.
|
|
func maxLastEvalTime(instances []*models.AlertInstance) time.Time {
|
|
var max time.Time
|
|
|
|
for _, i := range instances {
|
|
if i != nil && i.LastEvalTime.After(max) {
|
|
max = i.LastEvalTime
|
|
}
|
|
}
|
|
|
|
return max
|
|
}
|