feat(api): expose common label map per group

This commit is contained in:
Łukasz Mierzwa
2021-07-16 18:15:08 +01:00
committed by Łukasz Mierzwa
parent 121e8698f9
commit a2ee4812f7
3 changed files with 246 additions and 182 deletions

View File

@@ -0,0 +1,190 @@
/* snapshot: SharedMaps */
{
"receiver": "",
"labels": {
"alertname": "FakeAlert"
},
"alerts": [
{
"annotations": [
{
"name": "foo",
"value": "bar",
"visible": false,
"isLink": false,
"isAction": false
}
],
"labels": {
"instance": "1",
"job": "node_exporter"
},
"startsAt": "0001-01-01T00:00:00Z",
"state": "suppressed",
"alertmanager": [
{
"fingerprint": "1",
"name": "am",
"cluster": "fakeCluster",
"state": "",
"startsAt": "0001-01-01T00:00:00Z",
"source": "https://am.example.com/graph",
"silencedBy": [
"fakeSilence1",
"fakeSilence2"
],
"inhibitedBy": null
},
{
"fingerprint": "1",
"name": "am",
"cluster": "fakeCluster",
"state": "",
"startsAt": "0001-01-01T00:00:00Z",
"source": "https://am.example.com/graph",
"silencedBy": [
"fakeSilence1",
"fakeSilence2"
],
"inhibitedBy": null
}
],
"receiver": "",
"id": ""
},
{
"annotations": [],
"labels": {
"instance": "2",
"job": "node_exporter"
},
"startsAt": "0001-01-01T00:00:00Z",
"state": "active",
"alertmanager": [
{
"fingerprint": "1",
"name": "am",
"cluster": "fakeCluster",
"state": "",
"startsAt": "0001-01-01T00:00:00Z",
"source": "https://am.example.com/graph",
"silencedBy": [
"fakeSilence1",
"fakeSilence2"
],
"inhibitedBy": null
},
{
"fingerprint": "1",
"name": "am",
"cluster": "fakeCluster",
"state": "",
"startsAt": "0001-01-01T00:00:00Z",
"source": "https://am.example.com/graph",
"silencedBy": [
"fakeSilence1",
"fakeSilence2"
],
"inhibitedBy": null
}
],
"receiver": "",
"id": ""
},
{
"annotations": [],
"labels": {
"extra": "ignore",
"instance": "3",
"job": "blackbox"
},
"startsAt": "0001-01-01T00:00:00Z",
"state": "suppressed",
"alertmanager": [
{
"fingerprint": "1",
"name": "am",
"cluster": "fakeCluster",
"state": "",
"startsAt": "0001-01-01T00:00:00Z",
"source": "https://am.example.com/graph",
"silencedBy": [
"fakeSilence1",
"fakeSilence2"
],
"inhibitedBy": null
},
{
"fingerprint": "1",
"name": "am",
"cluster": "fakeCluster",
"state": "",
"startsAt": "0001-01-01T00:00:00Z",
"source": "https://am.example.com/graph",
"silencedBy": [
"fakeSilence1",
"fakeSilence2"
],
"inhibitedBy": null
}
],
"receiver": "",
"id": ""
}
],
"id": "",
"alertmanagerCount": null,
"stateCount": null,
"totalAlerts": 0,
"shared": {
"annotations": [
{
"name": "summary",
"value": "this is summary",
"visible": false,
"isLink": false,
"isAction": false
}
],
"labels": {},
"silences": {
"fakeCluster": [
"fakeSilence1",
"fakeSilence2"
]
},
"sources": [
"https://am.example.com"
],
"clusters": [
"fakeCluster"
]
},
"allLabels": {
"active": {
"alertname": [
"FakeAlert"
],
"instance": [
"2"
],
"job": [
"node_exporter"
]
},
"suppressed": {
"alertname": [
"FakeAlert"
],
"instance": [
"1",
"3"
],
"job": [
"blackbox",
"node_exporter"
]
},
"unprocessed": {}
}
}

View File

@@ -105,8 +105,9 @@ type APIAlertGroupSharedMaps struct {
// annotations that are unique to that instance
type APIAlertGroup struct {
AlertGroup
TotalAlerts int `json:"totalAlerts"`
Shared APIAlertGroupSharedMaps `json:"shared"`
TotalAlerts int `json:"totalAlerts"`
Shared APIAlertGroupSharedMaps `json:"shared"`
AllLabels map[string]map[string][]string `json:"allLabels"`
}
func (ag *APIAlertGroup) dedupLabels() {
@@ -293,9 +294,55 @@ func (ag *APIAlertGroup) dedupClusters() {
sort.Strings(ag.Shared.Clusters)
}
func (ag *APIAlertGroup) populateAllLabels() {
ag.AllLabels = map[string]map[string][]string{
AlertStateActive: {},
AlertStateSuppressed: {},
AlertStateUnprocessed: {},
}
labels := map[string]int{}
for _, alert := range ag.Alerts {
for k := range alert.Labels {
if _, ok := labels[k]; !ok {
labels[k] = 0
}
labels[k]++
}
}
labelNames := map[string]struct{}{}
totalAlerts := len(ag.Alerts)
for k, totalValues := range labels {
if totalValues == totalAlerts {
labelNames[k] = struct{}{}
}
}
for _, alert := range ag.Alerts {
for k, v := range alert.Labels {
if _, ok := labelNames[k]; !ok {
continue
}
if _, ok := ag.AllLabels[alert.State][k]; !ok {
ag.AllLabels[alert.State][k] = []string{}
}
if !slices.StringInSlice(ag.AllLabels[alert.State][k], v) {
ag.AllLabels[alert.State][k] = append(ag.AllLabels[alert.State][k], v)
}
}
}
for state := range ag.AllLabels {
for k := range ag.AllLabels[state] {
sort.Strings(ag.AllLabels[state][k])
}
}
}
// DedupSharedMaps will find all labels and annotations shared by all alerts
// in this group and moved them to Shared namespace
func (ag *APIAlertGroup) DedupSharedMaps() {
ag.populateAllLabels()
// remove all labels that are used for grouping
ag.removeGroupingLabels()
// don't dedup if we only have a single alert in this group

View File

@@ -1,11 +1,12 @@
package models_test
import (
"bytes"
"encoding/json"
"sort"
"testing"
"github.com/pmezard/go-difflib/difflib"
"github.com/beme/abide"
"github.com/prymitive/karma/internal/models"
)
@@ -44,7 +45,7 @@ func TestDedupSharedMaps(t *testing.T) {
Alertmanager: []models.AlertmanagerInstance{am, am},
},
models.Alert{
State: models.AlertStateSuppressed,
State: models.AlertStateActive,
Annotations: models.Annotations{
models.Annotation{
Name: "summary",
@@ -70,6 +71,7 @@ func TestDedupSharedMaps(t *testing.T) {
"alertname": "FakeAlert",
"job": "blackbox",
"instance": "3",
"extra": "ignore",
},
Alertmanager: []models.AlertmanagerInstance{am, am},
},
@@ -78,191 +80,16 @@ func TestDedupSharedMaps(t *testing.T) {
}
ag.DedupSharedMaps()
expectedJSON := `{
"receiver": "",
"labels": {
"alertname": "FakeAlert"
},
"alerts": [
{
"annotations": [
{
"name": "foo",
"value": "bar",
"visible": false,
"isLink": false,
"isAction": false
}
],
"labels": {
"instance": "1",
"job": "node_exporter"
},
"startsAt": "0001-01-01T00:00:00Z",
"state": "suppressed",
"alertmanager": [
{
"fingerprint": "1",
"name": "am",
"cluster": "fakeCluster",
"state": "",
"startsAt": "0001-01-01T00:00:00Z",
"source": "https://am.example.com/graph",
"silencedBy": [
"fakeSilence1",
"fakeSilence2"
],
"inhibitedBy": null
},
{
"fingerprint": "1",
"name": "am",
"cluster": "fakeCluster",
"state": "",
"startsAt": "0001-01-01T00:00:00Z",
"source": "https://am.example.com/graph",
"silencedBy": [
"fakeSilence1",
"fakeSilence2"
],
"inhibitedBy": null
}
],
"receiver": "",
"id": ""
},
{
"annotations": [],
"labels": {
"instance": "2",
"job": "node_exporter"
},
"startsAt": "0001-01-01T00:00:00Z",
"state": "suppressed",
"alertmanager": [
{
"fingerprint": "1",
"name": "am",
"cluster": "fakeCluster",
"state": "",
"startsAt": "0001-01-01T00:00:00Z",
"source": "https://am.example.com/graph",
"silencedBy": [
"fakeSilence1",
"fakeSilence2"
],
"inhibitedBy": null
},
{
"fingerprint": "1",
"name": "am",
"cluster": "fakeCluster",
"state": "",
"startsAt": "0001-01-01T00:00:00Z",
"source": "https://am.example.com/graph",
"silencedBy": [
"fakeSilence1",
"fakeSilence2"
],
"inhibitedBy": null
}
],
"receiver": "",
"id": ""
},
{
"annotations": [],
"labels": {
"instance": "3",
"job": "blackbox"
},
"startsAt": "0001-01-01T00:00:00Z",
"state": "suppressed",
"alertmanager": [
{
"fingerprint": "1",
"name": "am",
"cluster": "fakeCluster",
"state": "",
"startsAt": "0001-01-01T00:00:00Z",
"source": "https://am.example.com/graph",
"silencedBy": [
"fakeSilence1",
"fakeSilence2"
],
"inhibitedBy": null
},
{
"fingerprint": "1",
"name": "am",
"cluster": "fakeCluster",
"state": "",
"startsAt": "0001-01-01T00:00:00Z",
"source": "https://am.example.com/graph",
"silencedBy": [
"fakeSilence1",
"fakeSilence2"
],
"inhibitedBy": null
}
],
"receiver": "",
"id": ""
}
],
"id": "",
"alertmanagerCount": null,
"stateCount": null,
"totalAlerts": 0,
"shared": {
"annotations": [
{
"name": "summary",
"value": "this is summary",
"visible": false,
"isLink": false,
"isAction": false
}
],
"labels": {},
"silences": {
"fakeCluster": [
"fakeSilence1",
"fakeSilence2"
]
},
"sources": [
"https://am.example.com"
],
"clusters": [
"fakeCluster"
]
}
}`
agJSON, _ := json.MarshalIndent(ag, "", " ")
if string(agJSON) != expectedJSON {
diff := difflib.UnifiedDiff{
A: difflib.SplitLines(expectedJSON),
B: difflib.SplitLines(string(agJSON)),
FromFile: "Expected",
ToFile: "Current",
Context: 3,
}
text, err := difflib.GetUnifiedDiffString(diff)
if err != nil {
t.Error(err)
}
t.Errorf("JSON mismatch:\n%s", text)
}
abide.AssertReader(t, "SharedMaps", bytes.NewReader(agJSON))
}
func TestDedupSharedMapsSingleGroup(t *testing.T) {
ag := models.APIAlertGroup{
AlertGroup: models.AlertGroup{
Alerts: models.AlertList{
models.Alert{Labels: map[string]string{"foo": "bar"}},
models.Alert{Labels: map[string]string{"foo": "bar"}},
models.Alert{State: models.AlertStateActive, Labels: map[string]string{"foo": "bar"}},
models.Alert{State: models.AlertStateUnprocessed, Labels: map[string]string{"foo": "bar"}},
},
},
}