Files
karma/models/alert.go
Łukasz Mierzwa 81ce5d3098 Speed up alert fingerprint generation
Dynamic fingerprints made the code much slower, pprof shows they are responsible for ~70% of all cpu usage for any API call. To make it worse they are applied to all alerts, since dedup layer doesn't know which alerts will be filtered later, it operates on all of them. This PR will:
1. add benchmarks to so it's easier to track performance
2. Keep current methods for accessing fingerprints, but use precomputed static fields in those
3. Refactor Alert methods to use pointers, so we're not working on a copy

Benchmark timing went down from ~4000ns to 0.4ns for fingerprint calls and API response times from 1.3s (for my test sample) to 0.2s, which puts it back to the same level as before moving fingerprints to be dynamic. I still don't like this code much, it's all over the place, but I don't have a good idea how to better structure this, let's hope I'll be wiser in the future.
2017-07-05 23:35:00 -07:00

86 lines
2.9 KiB
Go

package models
import (
"fmt"
"time"
"github.com/cnf/structhash"
)
// 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,
}
// Alert is vanilla alert + some additional attributes
// unsee 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,
// unsee UI used this to show links differently than other annotations
type Alert struct {
Annotations map[string]string `json:"annotations"`
Labels map[string]string `json:"labels"`
StartsAt time.Time `json:"startsAt"`
EndsAt time.Time `json:"endsAt"`
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
GeneratorURL string `json:"-" hash:"-"`
SilencedBy []string `json:"-" hash:"-"`
InhibitedBy []string `json:"-" hash:"-"`
// unsee fields
Alertmanager []AlertmanagerInstance `json:"alertmanager"`
Receiver string `json:"receiver"`
Links map[string]string `json:"links"`
// fingerprints are precomputed for speed
labelsFP string `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, 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
}
// IsSilenced will return true if alert should be considered silenced
func (a *Alert) IsSilenced() bool {
return (a.State == AlertStateSuppressed && len(a.SilencedBy) > 0)
}
// IsInhibited will return true if alert should be considered silenced
func (a *Alert) IsInhibited() bool {
return (a.State == AlertStateSuppressed && len(a.InhibitedBy) > 0)
}
// IsActive will return true if alert is not suppressed in any way
func (a *Alert) IsActive() bool {
return (a.State == AlertStateActive)
}