Merge pull request #129 from cloudflare/speedup-fingerprints

Speed up alert fingerprint generation
This commit is contained in:
Łukasz Mierzwa
2017-07-06 08:48:10 -07:00
committed by GitHub
6 changed files with 82 additions and 8 deletions

View File

@@ -88,7 +88,7 @@ endif
.PHONY: test
test: lint bindata_assetfs.go
go test -cover `go list ./... | grep -v /vendor/`
go test -bench=. -cover `go list ./... | grep -v /vendor/`
.build/vendor.ok:
go get -u github.com/kardianos/govendor

View File

@@ -168,6 +168,8 @@ func (am *Alertmanager) pullAlerts(version string) error {
for k, v := range alert.Labels {
transform.ColorLabel(colors, k, v)
}
alert.UpdateFingerprints()
alerts = append(alerts, alert)
// update internal metrics

View File

@@ -45,30 +45,41 @@ type Alert struct {
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 fmt.Sprintf("%x", structhash.Sha1(a.Labels, 1))
func (a *Alert) LabelsFingerprint() string {
return a.labelsFP
}
// ContentFingerprint is a checksum computed from entire alert object
func (a Alert) ContentFingerprint() string {
return fmt.Sprintf("%x", structhash.Sha1(a, 1))
// 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 {
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 {
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 {
func (a *Alert) IsActive() bool {
return (a.State == AlertStateActive)
}

View File

@@ -2,6 +2,7 @@ package models_test
import (
"testing"
"time"
"github.com/cloudflare/unsee/models"
)
@@ -52,3 +53,49 @@ func TestAlertState(t *testing.T) {
}
}
}
func BenchmarkLabelsFingerprint(b *testing.B) {
alert := models.Alert{
Labels: map[string]string{
"foo1": "bar1",
"foo1bar1": "545jjjssd",
"foo1xxxx": "bdjjs88ff",
"agdfdfd": "bar1",
"fossdsf3o1": "bar11111",
"fdfdgfdgoo1": "bar1",
},
}
for n := 0; n < b.N; n++ {
alert.LabelsFingerprint()
}
}
func BenchmarkLabelsContent(b *testing.B) {
alert := models.Alert{
Annotations: map[string]string{
"foo": "bar",
"abc": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...",
},
Labels: map[string]string{
"foo1": "bar1",
"foo1bar1": "545jjjssd",
"foo1xxxx": "bdjjs88ff",
"agdfdfd": "bar1",
"fossdsf3o1": "bar11111",
"fdfdgfdgoo1": "bar1",
},
State: models.AlertStateActive,
StartsAt: time.Date(2015, time.March, 10, 0, 0, 0, 0, time.UTC),
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
Name: "default",
URI: "http://localhost",
State: models.AlertStateActive,
},
},
}
alert.UpdateFingerprints()
for n := 0; n < b.N; n++ {
alert.LabelsFingerprint()
}
}

View File

@@ -47,6 +47,7 @@ var alertListSortTests = []alertListSortTest{
func TestUnseeAlertListSort(t *testing.T) {
al := models.AlertList{}
for _, testCase := range alertListSortTests {
testCase.alert.UpdateFingerprints()
al = append(al, testCase.alert)
}
@@ -56,6 +57,7 @@ func TestUnseeAlertListSort(t *testing.T) {
for i := 1; i <= iterations; i++ {
sort.Sort(al)
for _, testCase := range alertListSortTests {
testCase.alert.UpdateFingerprints()
if al[testCase.position].ContentFingerprint() != testCase.alert.ContentFingerprint() {
failures++
}
@@ -120,6 +122,13 @@ var agFPTests = []agFPTest{
func TestAlertGroupContentFingerprint(t *testing.T) {
for _, testCase := range agFPTests {
alerts := models.AlertList{}
for _, alert := range testCase.ag.Alerts {
alert.UpdateFingerprints()
alerts = append(alerts, alert)
}
sort.Sort(alerts)
testCase.ag.Alerts = alerts
if testCase.ag.ContentFingerprint() != testCase.fingerprint {
t.Errorf("Invalid AlertGroup ContentFingerprint(), expected '%s', got '%s', AlertGroup: %v",
testCase.fingerprint, testCase.ag.ContentFingerprint(), testCase.ag)

View File

@@ -133,6 +133,11 @@ func alerts(c *gin.Context) {
}
if !validFilters || (slices.BoolInSlice(results, true) && !slices.BoolInSlice(results, false)) {
matches++
// we need to update fingerprints since we've modified some fields in dedup
// and agCopy.ContentFingerprint() depends on per alert fingerprint
// we update it here rather than in dedup since here we can apply it
// only for alerts left after filtering
alert.UpdateFingerprints()
agCopy.Alerts = append(agCopy.Alerts, alert)
countLabel(counters, "@state", alert.State)