Files
karma/internal/models/alert.go
2021-11-01 11:00:46 +00:00

162 lines
4.0 KiB
Go

package models
import (
"fmt"
"strings"
"time"
"github.com/cnf/structhash"
"github.com/fvbommel/sortorder"
"github.com/prymitive/karma/internal/config"
)
// AlertStateUnprocessed means that Alertmanager notify didn't yet process it
// and AM doesn't know if alert is active or suppressed
const AlertStateUnprocessed = "unprocessed"
// AlertStateActive is the state in which we know that the alert should fire
const AlertStateActive = "active"
// AlertStateSuppressed means that we know that alert is silenced or inhibited
const AlertStateSuppressed = "suppressed"
// AlertStateList exports all alert states so other packages can get this list
var AlertStateList = []string{
AlertStateUnprocessed,
AlertStateActive,
AlertStateSuppressed,
}
type Label struct {
Name string `json:"name"`
Value string `json:"value"`
}
func (l Label) String() string {
return fmt.Sprintf("%s=\"%s\"", l.Name, l.Value)
}
type Labels []Label
func (ls Labels) String() string {
var s []string
for _, l := range ls {
s = append(s, l.String())
}
return strings.Join(s, ",")
}
func (ls Labels) Map() map[string]string {
m := make(map[string]string, len(ls))
for _, l := range ls {
m[l.Name] = l.Value
}
return m
}
func (ls Labels) Len() int {
return len(ls)
}
func (ls Labels) Swap(i, j int) {
ls[i], ls[j] = ls[j], ls[i]
}
func (ls Labels) Less(i, j int) bool {
ai, aj := -1, -1
for index, name := range config.Config.Labels.Order {
if ls[i].Name == name {
ai = index
} else if ls[j].Name == name {
aj = index
}
if ai >= 0 && aj >= 0 {
return ai < aj
}
}
if ai != aj {
return aj < ai
}
if ls[i].Name == ls[j].Name {
return sortorder.NaturalLess(ls[i].Value, ls[j].Value)
}
return sortorder.NaturalLess(ls[i].Name, ls[j].Name)
}
func (ls Labels) Get(name string) *Label {
for _, l := range ls {
l := l
if l.Name == name {
return &l
}
}
return nil
}
func (ls Labels) GetValue(name string) string {
for _, l := range ls {
if l.Name == name {
return l.Value
}
}
return ""
}
func (ls Labels) Add(l Label) Labels {
if ls.Get(l.Name) != nil {
return ls
}
return append(ls, l)
}
func (ls Labels) Set(name, value string) Labels {
if ls.Get(name) != nil {
return ls
}
return append(ls, Label{Name: name, Value: value})
}
// Alert is vanilla alert + some additional attributes
// karma extends an alert object with:
// * Links map, it's generated from annotations if annotation value is an url
// it's pulled out of annotation map and returned under links field,
// karma UI used this to show links differently than other annotations
type Alert struct {
Annotations Annotations `json:"annotations"`
Labels Labels `json:"labels"`
StartsAt time.Time `json:"startsAt"`
State string `json:"state"`
// those are not exposed in JSON, Alertmanager specific value will be in kept
// in the Alertmanager slice
// skip those when generating alert fingerprint too
Fingerprint string `json:"-" hash:"-"`
GeneratorURL string `json:"-" hash:"-"`
SilencedBy []string `json:"-" hash:"-"`
InhibitedBy []string `json:"-" hash:"-"`
// karma fields
Alertmanager []AlertmanagerInstance `json:"alertmanager"`
Receiver string `json:"receiver"`
// fingerprints are precomputed for speed
LabelsFP string `json:"id" hash:"-"`
contentFP string `hash:"-"`
}
// UpdateFingerprints will generate a new set of fingerprints for this alert
// it should be called after modifying any field that isn't tagged with hash:"-"
func (a *Alert) UpdateFingerprints() {
a.LabelsFP = fmt.Sprintf("%x", structhash.Sha1(a.Labels.Map(), 1))
a.contentFP = fmt.Sprintf("%x", structhash.Sha1(a, 1))
}
// LabelsFingerprint is a checksum computed only from labels which should be
// unique for every alert
func (a *Alert) LabelsFingerprint() string {
return a.LabelsFP
}
// ContentFingerprint is a checksum computed from entire alert object
// except some blacklisted fields tagged with hash:"-"
func (a *Alert) ContentFingerprint() string {
return a.contentFP
}