From 97e3728dabde0ec2deb3a1b266b1298d38422047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 25 Jun 2017 11:01:36 -0700 Subject: [PATCH] Compute alert content fingerprints on the fly This will be more expensive but will simplify the code --- alertmanager/models.go | 1 - models/alert.go | 14 +++++++--- models/alertgroup.go | 4 +-- models/alertgroup_test.go | 54 +++++++++++++++------------------------ 4 files changed, 34 insertions(+), 39 deletions(-) diff --git a/alertmanager/models.go b/alertmanager/models.go index 29cbb4cf6..8b5125f96 100644 --- a/alertmanager/models.go +++ b/alertmanager/models.go @@ -128,7 +128,6 @@ func (am *Alertmanager) pullAlerts(version string) error { uniqueAlerts[agID] = map[string]models.Alert{} } if _, found := uniqueAlerts[agID][fp]; !found { - alert.Fingerprint = fp uniqueAlerts[agID][fp] = alert } } diff --git a/models/alert.go b/models/alert.go index 0770ab5c2..efdff33fb 100644 --- a/models/alert.go +++ b/models/alert.go @@ -1,6 +1,11 @@ package models -import "time" +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 @@ -24,7 +29,6 @@ var AlertStateList = []string{ // * 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 -// * Fingerprint, which is a sha1 of the entire alert type Alert struct { Annotations map[string]string `json:"annotations"` Labels map[string]string `json:"labels"` @@ -39,7 +43,11 @@ type Alert struct { Receiver string `json:"receiver"` Links map[string]string `json:"links"` ID string `json:"-"` - Fingerprint string `json:"-"` +} + +// ContentFingerprint is a checksum computed from entire alert object +func (a Alert) ContentFingerprint() string { + return fmt.Sprintf("%x", structhash.Sha1(a, 1)) } // IsSilenced will return true if alert should be considered silenced diff --git a/models/alertgroup.go b/models/alertgroup.go index b72e4c0fc..114cdfd9f 100644 --- a/models/alertgroup.go +++ b/models/alertgroup.go @@ -24,7 +24,7 @@ func (a AlertList) Less(i, j int) bool { if a[i].StartsAt.Before(a[j].StartsAt) { return false } - return a[i].Fingerprint < a[j].Fingerprint + return a[i].ContentFingerprint() < a[j].ContentFingerprint() } // AlertGroup is vanilla Alertmanager group, but alerts are flattened @@ -43,7 +43,7 @@ type AlertGroup struct { func (ag AlertGroup) ContentFingerprint() string { h := sha1.New() for _, alert := range ag.Alerts { - io.WriteString(h, alert.Fingerprint) + io.WriteString(h, alert.ContentFingerprint()) } return fmt.Sprintf("%x", h.Sum(nil)) } diff --git a/models/alertgroup_test.go b/models/alertgroup_test.go index 5db7d8dd7..1f7a4ca29 100644 --- a/models/alertgroup_test.go +++ b/models/alertgroup_test.go @@ -9,56 +9,45 @@ import ( ) type alertListSortTest struct { - startsAt time.Time - fingerprint string - position int + alert models.Alert + position int } var alertListSortTests = []alertListSortTest{ alertListSortTest{ - startsAt: time.Date(2017, time.January, 10, 0, 0, 0, 5, time.UTC), - fingerprint: "abcdefg", - position: 0, + alert: models.Alert{StartsAt: time.Date(2017, time.January, 10, 0, 0, 0, 5, time.UTC)}, + position: 0, }, alertListSortTest{ - startsAt: time.Date(2017, time.January, 10, 0, 0, 0, 1, time.UTC), - fingerprint: "bbbbbb", - position: 1, + alert: models.Alert{StartsAt: time.Date(2017, time.January, 10, 0, 0, 0, 1, time.UTC)}, + position: 1, }, alertListSortTest{ - startsAt: time.Date(2017, time.January, 10, 0, 0, 0, 0, time.UTC), - fingerprint: "cdfddfg", - position: 2, + alert: models.Alert{StartsAt: time.Date(2017, time.January, 10, 0, 0, 0, 0, time.UTC)}, + position: 2, }, alertListSortTest{ - startsAt: time.Date(2015, time.March, 10, 0, 0, 0, 0, time.UTC), - fingerprint: "xlfjdf", - position: 6, + alert: models.Alert{StartsAt: time.Date(2015, time.March, 10, 0, 0, 0, 0, time.UTC)}, + position: 6, }, alertListSortTest{ - startsAt: time.Date(2016, time.December, 10, 0, 0, 0, 0, time.UTC), - fingerprint: "011m", - position: 4, + alert: models.Alert{StartsAt: time.Date(2016, time.December, 10, 0, 0, 0, 0, time.UTC)}, + position: 4, }, alertListSortTest{ - startsAt: time.Date(2017, time.January, 10, 0, 0, 0, 0, time.UTC), - fingerprint: "cxzfg", - position: 3, + alert: models.Alert{StartsAt: time.Date(2017, time.January, 10, 0, 0, 0, 0, time.UTC)}, + position: 3, }, alertListSortTest{ - startsAt: time.Date(2015, time.March, 10, 0, 0, 0, 0, time.UTC), - fingerprint: "abv", - position: 5, + alert: models.Alert{StartsAt: time.Date(2015, time.March, 10, 0, 0, 0, 0, time.UTC)}, + position: 5, }, } func TestUnseeAlertListSort(t *testing.T) { al := models.AlertList{} for _, testCase := range alertListSortTests { - a := models.Alert{} - a.StartsAt = testCase.startsAt - a.Fingerprint = testCase.fingerprint - al = append(al, a) + al = append(al, testCase.alert) } // repeat sort 100 times to ensure we're always sorting same way @@ -67,7 +56,7 @@ func TestUnseeAlertListSort(t *testing.T) { for i := 1; i <= iterations; i++ { sort.Sort(al) for _, testCase := range alertListSortTests { - if al[testCase.position].Fingerprint != testCase.fingerprint { + if al[testCase.position].ContentFingerprint() != testCase.alert.ContentFingerprint() { failures++ } } @@ -119,14 +108,13 @@ var agFPTests = []agFPTest{ Labels: map[string]string{"foo": "bar"}, Alerts: models.AlertList{ models.Alert{ - Labels: map[string]string{"foo1": "bar"}, - State: models.AlertStateActive, - Fingerprint: "xxx", + Labels: map[string]string{"foo1": "bar"}, + State: models.AlertStateActive, }, }, StateCount: map[string]int{"default": 0}, }, - fingerprint: "b60d121b438a380c343d5ec3c2037564b82ffef3", + fingerprint: "c4a76e2d59f7ef3d49b20da06c4a89f63fdf9e3f", }, }