mirror of
https://github.com/prymitive/karma
synced 2026-05-07 03:26:52 +00:00
217 lines
5.2 KiB
Go
217 lines
5.2 KiB
Go
package models
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cespare/xxhash/v2"
|
|
"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:"-"`
|
|
GeneratorURL string `json:"-"`
|
|
SilencedBy []string `json:"-"`
|
|
InhibitedBy []string `json:"-"`
|
|
// karma fields
|
|
Alertmanager []AlertmanagerInstance `json:"alertmanager"`
|
|
Receiver string `json:"receiver"`
|
|
// fingerprints are precomputed for speed
|
|
LabelsFP string `json:"id"`
|
|
contentFP string
|
|
}
|
|
|
|
var seps = []byte{'\xff'}
|
|
|
|
// UpdateFingerprints will generate a new set of fingerprints for this alert
|
|
func (a *Alert) UpdateFingerprints() {
|
|
h := xxhash.New()
|
|
for _, l := range a.Labels {
|
|
_, _ = h.WriteString(l.Name)
|
|
_, _ = h.Write(seps)
|
|
_, _ = h.WriteString(l.Value)
|
|
_, _ = h.Write(seps)
|
|
}
|
|
a.LabelsFP = fmt.Sprintf("%x", h.Sum64())
|
|
|
|
h.Reset()
|
|
for _, a := range a.Annotations {
|
|
_, _ = h.WriteString(a.Name)
|
|
_, _ = h.Write(seps)
|
|
_, _ = h.WriteString(a.Value)
|
|
_, _ = h.Write(seps)
|
|
_, _ = h.WriteString(strconv.FormatBool(a.IsAction))
|
|
_, _ = h.Write(seps)
|
|
_, _ = h.WriteString(strconv.FormatBool(a.IsLink))
|
|
_, _ = h.Write(seps)
|
|
_, _ = h.WriteString(strconv.FormatBool(a.Visible))
|
|
_, _ = h.Write(seps)
|
|
|
|
}
|
|
for _, l := range a.Labels {
|
|
_, _ = h.WriteString(l.Name)
|
|
_, _ = h.Write(seps)
|
|
_, _ = h.WriteString(l.Value)
|
|
_, _ = h.Write(seps)
|
|
}
|
|
_, _ = h.WriteString(a.StartsAt.Format(time.RFC3339))
|
|
_, _ = h.WriteString(a.State)
|
|
for _, am := range a.Alertmanager {
|
|
_, _ = h.WriteString(am.Fingerprint)
|
|
_, _ = h.Write(seps)
|
|
_, _ = h.WriteString(am.Name)
|
|
_, _ = h.Write(seps)
|
|
_, _ = h.WriteString(am.Cluster)
|
|
_, _ = h.Write(seps)
|
|
_, _ = h.WriteString(am.State)
|
|
_, _ = h.Write(seps)
|
|
_, _ = h.WriteString(am.StartsAt.Format(time.RFC3339))
|
|
_, _ = h.Write(seps)
|
|
_, _ = h.WriteString(am.Source)
|
|
_, _ = h.Write(seps)
|
|
for _, s := range am.SilencedBy {
|
|
_, _ = h.WriteString(s)
|
|
_, _ = h.Write(seps)
|
|
}
|
|
for _, s := range am.InhibitedBy {
|
|
_, _ = h.WriteString(s)
|
|
_, _ = h.Write(seps)
|
|
}
|
|
}
|
|
_, _ = h.WriteString(a.Receiver)
|
|
a.contentFP = fmt.Sprintf("%x", h.Sum64())
|
|
}
|
|
|
|
// 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
|
|
func (a *Alert) ContentFingerprint() string {
|
|
return a.contentFP
|
|
}
|