mirror of
https://github.com/prymitive/karma
synced 2026-08-19 11:36:24 +00:00
fix(api): use slices for labels instead of maps
This commit is contained in:
committed by
Łukasz Mierzwa
parent
7d24a7ece0
commit
8f7cca40cc
+12
-12
@@ -141,14 +141,14 @@ func resolveLabelValue(name, value string) string {
|
||||
}
|
||||
|
||||
func getGroupLabel(group *models.APIAlertGroup, label string) string {
|
||||
if v, found := group.Labels[label]; found {
|
||||
return resolveLabelValue(label, v)
|
||||
if v := group.Labels.Get(label); v != nil {
|
||||
return resolveLabelValue(label, v.Value)
|
||||
}
|
||||
if v, found := group.Shared.Labels[label]; found {
|
||||
return resolveLabelValue(label, v)
|
||||
if v := group.Shared.Labels.Get(label); v != nil {
|
||||
return resolveLabelValue(label, v.Value)
|
||||
}
|
||||
if v, found := group.Alerts[0].Labels[label]; found {
|
||||
return resolveLabelValue(label, v)
|
||||
if v := group.Alerts[0].Labels.Get(label); v != nil {
|
||||
return resolveLabelValue(label, v.Value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -283,14 +283,14 @@ func autoGridLabel(dedupedAlerts []models.AlertGroup) string {
|
||||
for _, ag := range dedupedAlerts {
|
||||
alertsCount += ag.Alerts.Len()
|
||||
for _, alert := range ag.Alerts {
|
||||
for key, val := range alert.Labels {
|
||||
if _, ok := labelToAlertCount[key]; !ok {
|
||||
labelToAlertCount[key] = map[string]int{}
|
||||
for _, l := range alert.Labels {
|
||||
if _, ok := labelToAlertCount[l.Name]; !ok {
|
||||
labelToAlertCount[l.Name] = map[string]int{}
|
||||
}
|
||||
if _, ok := labelToAlertCount[key][val]; !ok {
|
||||
labelToAlertCount[key][val] = 0
|
||||
if _, ok := labelToAlertCount[l.Name][l.Value]; !ok {
|
||||
labelToAlertCount[l.Name][l.Value] = 0
|
||||
}
|
||||
labelToAlertCount[key][val]++
|
||||
labelToAlertCount[l.Name][l.Value]++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+127
-125
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
type groupTest struct {
|
||||
labels map[string]string
|
||||
labels models.Labels
|
||||
receiver string
|
||||
alerts []models.Alert
|
||||
id string
|
||||
@@ -28,8 +29,8 @@ type groupTest struct {
|
||||
var groupTests = []groupTest{
|
||||
{
|
||||
receiver: "by-name",
|
||||
labels: map[string]string{
|
||||
"alertname": "Memory_Usage_Too_High",
|
||||
labels: models.Labels{
|
||||
{Name: "alertname", Value: "Memory_Usage_Too_High"},
|
||||
},
|
||||
alerts: []models.Alert{
|
||||
{
|
||||
@@ -38,10 +39,10 @@ var groupTests = []groupTest{
|
||||
models.Annotation{Visible: true, Name: "alert", Value: "Memory usage exceeding threshold"},
|
||||
models.Annotation{Visible: true, Name: "dashboard", Value: "http://localhost/dashboard.html", IsLink: true},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"cluster": "prod",
|
||||
"instance": "server2",
|
||||
"job": "node_exporter",
|
||||
Labels: models.Labels{
|
||||
{Name: "cluster", Value: "prod"},
|
||||
{Name: "instance", Value: "server2"},
|
||||
{Name: "job", Value: "node_exporter"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
@@ -64,9 +65,9 @@ var groupTests = []groupTest{
|
||||
},
|
||||
{
|
||||
receiver: "by-cluster-service",
|
||||
labels: map[string]string{
|
||||
"alertname": "Memory_Usage_Too_High",
|
||||
"cluster": "prod",
|
||||
labels: models.Labels{
|
||||
{Name: "alertname", Value: "Memory_Usage_Too_High"},
|
||||
{Name: "cluster", Value: "prod"},
|
||||
},
|
||||
alerts: []models.Alert{
|
||||
{
|
||||
@@ -83,9 +84,9 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "server2",
|
||||
"job": "node_exporter",
|
||||
Labels: models.Labels{
|
||||
{Name: "instance", Value: "server2"},
|
||||
{Name: "job", Value: "node_exporter"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Receiver: "by-cluster-service",
|
||||
@@ -100,9 +101,9 @@ var groupTests = []groupTest{
|
||||
},
|
||||
{
|
||||
receiver: "by-cluster-service",
|
||||
labels: map[string]string{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "staging",
|
||||
labels: models.Labels{
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "staging"},
|
||||
},
|
||||
alerts: []models.Alert{
|
||||
{
|
||||
@@ -116,9 +117,9 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "server3",
|
||||
"ip": "127.0.0.3",
|
||||
Labels: models.Labels{
|
||||
{Name: "instance", Value: "server3"},
|
||||
{Name: "ip", Value: "127.0.0.3"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Receiver: "by-cluster-service",
|
||||
@@ -133,9 +134,9 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "server4",
|
||||
"ip": "127.0.0.4",
|
||||
Labels: models.Labels{
|
||||
{Name: "instance", Value: "server4"},
|
||||
{Name: "ip", Value: "127.0.0.4"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Receiver: "by-cluster-service",
|
||||
@@ -150,9 +151,9 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "server5",
|
||||
"ip": "127.0.0.5",
|
||||
Labels: models.Labels{
|
||||
{Name: "instance", Value: "server5"},
|
||||
{Name: "ip", Value: "127.0.0.5"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Receiver: "by-cluster-service",
|
||||
@@ -167,9 +168,9 @@ var groupTests = []groupTest{
|
||||
},
|
||||
{
|
||||
receiver: "by-cluster-service",
|
||||
labels: map[string]string{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "dev",
|
||||
labels: models.Labels{
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "dev"},
|
||||
},
|
||||
alerts: []models.Alert{
|
||||
{
|
||||
@@ -183,9 +184,9 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{"168f139d-77e4-41d6-afb5-8fe2cfd0cc9d"},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "server6",
|
||||
"ip": "127.0.0.6",
|
||||
Labels: models.Labels{
|
||||
{Name: "instance", Value: "server6"},
|
||||
{Name: "ip", Value: "127.0.0.6"},
|
||||
},
|
||||
State: models.AlertStateSuppressed,
|
||||
Receiver: "by-cluster-service",
|
||||
@@ -201,9 +202,9 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{"168f139d-77e4-41d6-afb5-8fe2cfd0cc9d", "378eaa69-097d-41c4-a8c2-fe6568c3abfc"},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "server7",
|
||||
"ip": "127.0.0.7",
|
||||
Labels: models.Labels{
|
||||
{Name: "instance", Value: "server7"},
|
||||
{Name: "ip", Value: "127.0.0.7"},
|
||||
},
|
||||
State: models.AlertStateSuppressed,
|
||||
Receiver: "by-cluster-service",
|
||||
@@ -219,9 +220,9 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{"168f139d-77e4-41d6-afb5-8fe2cfd0cc9d"},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "server8",
|
||||
"ip": "127.0.0.8",
|
||||
Labels: models.Labels{
|
||||
{Name: "instance", Value: "server8"},
|
||||
{Name: "ip", Value: "127.0.0.8"},
|
||||
},
|
||||
State: models.AlertStateSuppressed,
|
||||
Receiver: "by-cluster-service",
|
||||
@@ -236,8 +237,8 @@ var groupTests = []groupTest{
|
||||
},
|
||||
{
|
||||
receiver: "by-name",
|
||||
labels: map[string]string{
|
||||
"alertname": "Host_Down",
|
||||
labels: models.Labels{
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
},
|
||||
alerts: []models.Alert{
|
||||
{
|
||||
@@ -245,10 +246,10 @@ var groupTests = []groupTest{
|
||||
Annotations: models.Annotations{
|
||||
models.Annotation{Visible: true, Name: "url", Value: "http://localhost/example.html", IsLink: true},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"cluster": "prod",
|
||||
"instance": "server1",
|
||||
"ip": "127.0.0.1",
|
||||
Labels: models.Labels{
|
||||
{Name: "cluster", Value: "prod"},
|
||||
{Name: "instance", Value: "server1"},
|
||||
{Name: "ip", Value: "127.0.0.1"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
@@ -264,10 +265,10 @@ var groupTests = []groupTest{
|
||||
{
|
||||
StartsAt: time.Date(2019, time.January, 1, 0, 1, 0, 0, time.UTC),
|
||||
Annotations: models.Annotations{},
|
||||
Labels: map[string]string{
|
||||
"cluster": "prod",
|
||||
"instance": "server2",
|
||||
"ip": "127.0.0.2",
|
||||
Labels: models.Labels{
|
||||
{Name: "cluster", Value: "prod"},
|
||||
{Name: "instance", Value: "server2"},
|
||||
{Name: "ip", Value: "127.0.0.2"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
@@ -283,10 +284,10 @@ var groupTests = []groupTest{
|
||||
{
|
||||
StartsAt: time.Date(2019, time.January, 1, 0, 1, 0, 1, time.UTC),
|
||||
Annotations: models.Annotations{},
|
||||
Labels: map[string]string{
|
||||
"cluster": "staging",
|
||||
"instance": "server3",
|
||||
"ip": "127.0.0.3",
|
||||
Labels: models.Labels{
|
||||
{Name: "cluster", Value: "staging"},
|
||||
{Name: "instance", Value: "server3"},
|
||||
{Name: "ip", Value: "127.0.0.3"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
@@ -302,10 +303,10 @@ var groupTests = []groupTest{
|
||||
{
|
||||
StartsAt: time.Date(2019, time.January, 1, 0, 0, 59, 0, time.UTC),
|
||||
Annotations: models.Annotations{},
|
||||
Labels: map[string]string{
|
||||
"cluster": "staging",
|
||||
"instance": "server4",
|
||||
"ip": "127.0.0.4",
|
||||
Labels: models.Labels{
|
||||
{Name: "cluster", Value: "staging"},
|
||||
{Name: "instance", Value: "server4"},
|
||||
{Name: "ip", Value: "127.0.0.4"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
@@ -321,10 +322,10 @@ var groupTests = []groupTest{
|
||||
{
|
||||
StartsAt: time.Date(2019, time.January, 10, 0, 0, 0, 0, time.UTC),
|
||||
Annotations: models.Annotations{},
|
||||
Labels: map[string]string{
|
||||
"cluster": "staging",
|
||||
"instance": "server5",
|
||||
"ip": "127.0.0.5",
|
||||
Labels: models.Labels{
|
||||
{Name: "cluster", Value: "staging"},
|
||||
{Name: "instance", Value: "server5"},
|
||||
{Name: "ip", Value: "127.0.0.5"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
@@ -340,10 +341,10 @@ var groupTests = []groupTest{
|
||||
{
|
||||
StartsAt: time.Date(2019, time.January, 10, 1, 0, 0, 0, time.UTC),
|
||||
Annotations: models.Annotations{},
|
||||
Labels: map[string]string{
|
||||
"cluster": "dev",
|
||||
"instance": "server6",
|
||||
"ip": "127.0.0.6",
|
||||
Labels: models.Labels{
|
||||
{Name: "cluster", Value: "dev"},
|
||||
{Name: "instance", Value: "server6"},
|
||||
{Name: "ip", Value: "127.0.0.6"},
|
||||
},
|
||||
State: models.AlertStateSuppressed,
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
@@ -359,10 +360,10 @@ var groupTests = []groupTest{
|
||||
{
|
||||
StartsAt: time.Date(2019, time.January, 10, 0, 20, 0, 0, time.UTC),
|
||||
Annotations: models.Annotations{},
|
||||
Labels: map[string]string{
|
||||
"cluster": "dev",
|
||||
"instance": "server7",
|
||||
"ip": "127.0.0.7",
|
||||
Labels: models.Labels{
|
||||
{Name: "cluster", Value: "dev"},
|
||||
{Name: "instance", Value: "server7"},
|
||||
{Name: "ip", Value: "127.0.0.7"},
|
||||
},
|
||||
State: models.AlertStateSuppressed,
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
@@ -378,10 +379,10 @@ var groupTests = []groupTest{
|
||||
{
|
||||
StartsAt: time.Date(2019, time.January, 10, 0, 21, 0, 0, time.UTC),
|
||||
Annotations: models.Annotations{},
|
||||
Labels: map[string]string{
|
||||
"cluster": "dev",
|
||||
"instance": "server8",
|
||||
"ip": "127.0.0.8",
|
||||
Labels: models.Labels{
|
||||
{Name: "cluster", Value: "dev"},
|
||||
{Name: "instance", Value: "server8"},
|
||||
{Name: "ip", Value: "127.0.0.8"},
|
||||
},
|
||||
State: models.AlertStateSuppressed,
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
@@ -404,9 +405,9 @@ var groupTests = []groupTest{
|
||||
},
|
||||
{
|
||||
receiver: "by-cluster-service",
|
||||
labels: map[string]string{
|
||||
"alertname": "Free_Disk_Space_Too_Low",
|
||||
"cluster": "staging",
|
||||
labels: models.Labels{
|
||||
{Name: "alertname", Value: "Free_Disk_Space_Too_Low"},
|
||||
{Name: "cluster", Value: "staging"},
|
||||
},
|
||||
alerts: []models.Alert{
|
||||
{
|
||||
@@ -423,10 +424,10 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "server5",
|
||||
"job": "node_exporter",
|
||||
"disk": "sda",
|
||||
Labels: models.Labels{
|
||||
{Name: "disk", Value: "sda"},
|
||||
{Name: "instance", Value: "server5"},
|
||||
{Name: "job", Value: "node_exporter"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Receiver: "by-cluster-service",
|
||||
@@ -441,9 +442,9 @@ var groupTests = []groupTest{
|
||||
},
|
||||
{
|
||||
receiver: "by-cluster-service",
|
||||
labels: map[string]string{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "prod",
|
||||
labels: models.Labels{
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "prod"},
|
||||
},
|
||||
alerts: []models.Alert{
|
||||
{
|
||||
@@ -459,9 +460,9 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "server1",
|
||||
"ip": "127.0.0.1",
|
||||
Labels: models.Labels{
|
||||
{Name: "instance", Value: "server1"},
|
||||
{Name: "ip", Value: "127.0.0.1"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Receiver: "by-cluster-service",
|
||||
@@ -476,9 +477,9 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "server2",
|
||||
"ip": "127.0.0.2",
|
||||
Labels: models.Labels{
|
||||
{Name: "instance", Value: "server2"},
|
||||
{Name: "ip", Value: "127.0.0.2"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Receiver: "by-cluster-service",
|
||||
@@ -493,8 +494,8 @@ var groupTests = []groupTest{
|
||||
},
|
||||
{
|
||||
receiver: "by-name",
|
||||
labels: map[string]string{
|
||||
"alertname": "HTTP_Probe_Failed",
|
||||
labels: models.Labels{
|
||||
{Name: "alertname", Value: "HTTP_Probe_Failed"},
|
||||
},
|
||||
alerts: []models.Alert{
|
||||
{
|
||||
@@ -510,8 +511,8 @@ var groupTests = []groupTest{
|
||||
Source: "http://localhost/prometheus",
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "web1",
|
||||
Labels: models.Labels{
|
||||
{Name: "instance", Value: "web1"},
|
||||
},
|
||||
State: models.AlertStateSuppressed,
|
||||
Receiver: "by-name",
|
||||
@@ -527,8 +528,8 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "web2",
|
||||
Labels: models.Labels{
|
||||
{Name: "instance", Value: "web2"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Receiver: "by-name",
|
||||
@@ -543,8 +544,8 @@ var groupTests = []groupTest{
|
||||
},
|
||||
{
|
||||
receiver: "by-name",
|
||||
labels: map[string]string{
|
||||
"alertname": "Free_Disk_Space_Too_Low",
|
||||
labels: models.Labels{
|
||||
{Name: "alertname", Value: "Free_Disk_Space_Too_Low"},
|
||||
},
|
||||
alerts: []models.Alert{
|
||||
{
|
||||
@@ -561,11 +562,11 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"cluster": "staging",
|
||||
"instance": "server5",
|
||||
"job": "node_exporter",
|
||||
"disk": "sda",
|
||||
Labels: models.Labels{
|
||||
{Name: "cluster", Value: "staging"},
|
||||
{Name: "disk", Value: "sda"},
|
||||
{Name: "instance", Value: "server5"},
|
||||
{Name: "job", Value: "node_exporter"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Receiver: "by-name",
|
||||
@@ -580,9 +581,9 @@ var groupTests = []groupTest{
|
||||
},
|
||||
{
|
||||
receiver: "by-cluster-service",
|
||||
labels: map[string]string{
|
||||
"alertname": "HTTP_Probe_Failed",
|
||||
"cluster": "dev",
|
||||
labels: models.Labels{
|
||||
{Name: "alertname", Value: "HTTP_Probe_Failed"},
|
||||
{Name: "cluster", Value: "dev"},
|
||||
},
|
||||
alerts: []models.Alert{
|
||||
{
|
||||
@@ -599,8 +600,8 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{"0804764c-6163-4c64-b0a9-08feebe2db4b"},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "web1",
|
||||
Labels: models.Labels{
|
||||
{Name: "instance", Value: "web1"},
|
||||
},
|
||||
State: models.AlertStateSuppressed,
|
||||
Receiver: "by-cluster-service",
|
||||
@@ -616,8 +617,8 @@ var groupTests = []groupTest{
|
||||
SilencedBy: []string{},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"instance": "web2",
|
||||
Labels: models.Labels{
|
||||
{Name: "instance", Value: "web2"},
|
||||
},
|
||||
State: models.AlertStateActive,
|
||||
Receiver: "by-cluster-service",
|
||||
@@ -641,12 +642,12 @@ func compareAlertGroups(testCase groupTest, group models.APIAlertGroup) bool {
|
||||
if len(testCase.labels) != len(group.Labels) {
|
||||
return false
|
||||
}
|
||||
for key, val := range testCase.labels {
|
||||
v, found := group.Labels[key]
|
||||
if !found {
|
||||
for _, l := range testCase.labels {
|
||||
v := group.Labels.Get(l.Name)
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
if val != v {
|
||||
if l.Value != v.Value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -660,12 +661,12 @@ func compareAlerts(expectedAlert, gotAlert models.Alert) bool {
|
||||
if len(gotAlert.Labels) != len(expectedAlert.Labels) {
|
||||
return false
|
||||
}
|
||||
for key, val := range expectedAlert.Labels {
|
||||
v, found := gotAlert.Labels[key]
|
||||
if !found {
|
||||
for _, l := range expectedAlert.Labels {
|
||||
v := gotAlert.Labels.Get(l.Name)
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
if val != v {
|
||||
if l.Value != v.Value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1000,22 +1001,23 @@ func TestSortOrder(t *testing.T) {
|
||||
} else {
|
||||
values := []string{}
|
||||
for _, ag := range ur.Grids[0].AlertGroups {
|
||||
v := ag.Labels[testCase.expectedLabel]
|
||||
v := ag.Labels.GetValue(testCase.expectedLabel)
|
||||
if v == "" {
|
||||
v = ag.Shared.Labels[testCase.expectedLabel]
|
||||
v = ag.Shared.Labels.GetValue(testCase.expectedLabel)
|
||||
}
|
||||
if v != "" {
|
||||
values = append(values, v)
|
||||
} else {
|
||||
for _, alert := range ag.Alerts {
|
||||
v = alert.Labels[testCase.expectedLabel]
|
||||
v = alert.Labels.GetValue(testCase.expectedLabel)
|
||||
values = append(values, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(testCase.expectedValues, values); diff != "" {
|
||||
t.Errorf("Incorrectly sorted values (-want +got):\n%s", diff)
|
||||
t.Errorf("Incorrectly sorted values for filter=%q order=%s label=%q reverse=%v (-want +got):\n%s",
|
||||
strings.Join(testCase.filter, " "), testCase.sortOrder, testCase.sortLabel, testCase.sortReverse, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1023,22 +1025,22 @@ func TestSortOrder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func verifyStrippedLabels(t *testing.T, labels map[string]string, keep, strip []string) {
|
||||
func verifyStrippedLabels(t *testing.T, labels models.Labels, keep, strip []string) {
|
||||
for _, l := range strip {
|
||||
if val, ok := labels[l]; ok {
|
||||
t.Errorf("Found stripped label %s=%s on %v", l, val, labels)
|
||||
if val := labels.Get(l); val != nil {
|
||||
t.Errorf("Found stripped label %s=%s on %v", val.Name, val.Value, labels)
|
||||
}
|
||||
}
|
||||
if len(keep) > 0 && len(strip) == 0 {
|
||||
for k, v := range labels {
|
||||
for _, ll := range labels {
|
||||
ok := false
|
||||
for _, l := range keep {
|
||||
if k == l {
|
||||
if ll.Name == l {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
t.Errorf("Found label %s=%s that's not on the keep list: %v", k, v, keep)
|
||||
t.Errorf("Found label %s=%s that's not on the keep list: %v", ll.Name, ll.Value, keep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-29
@@ -224,7 +224,6 @@ func alerts(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// use full URI (including query args) as cache key
|
||||
cacheKey := fmt.Sprintf("%x", structhash.Sha1(request, 1))
|
||||
|
||||
data, found := apiCache.Get(cacheKey)
|
||||
@@ -265,14 +264,14 @@ func alerts(w http.ResponseWriter, r *http.Request) {
|
||||
for _, ag := range filtered {
|
||||
perGridAlertGroup := map[string]*models.AlertGroup{}
|
||||
|
||||
for k := range ag.Labels {
|
||||
labelMap[k] = struct{}{}
|
||||
for _, l := range ag.Labels {
|
||||
labelMap[l.Name] = struct{}{}
|
||||
}
|
||||
for _, alert := range ag.Alerts {
|
||||
alert := alert // scopelint pin
|
||||
|
||||
for k := range alert.Labels {
|
||||
labelMap[k] = struct{}{}
|
||||
for _, l := range alert.Labels {
|
||||
labelMap[l.Name] = struct{}{}
|
||||
}
|
||||
|
||||
allReceivers[alert.Receiver] = true
|
||||
@@ -292,7 +291,7 @@ func alerts(w http.ResponseWriter, r *http.Request) {
|
||||
alertGridLabelValues[am.Cluster] = true
|
||||
}
|
||||
default:
|
||||
alertGridLabelValues[alert.Labels[gridLabel]] = true
|
||||
alertGridLabelValues[alert.Labels.GetValue(gridLabel)] = true
|
||||
}
|
||||
|
||||
for alertGridLabelValue := range alertGridLabelValues {
|
||||
@@ -390,13 +389,13 @@ func alerts(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range alert.Labels {
|
||||
if keyMap, foundKey := dedupedColors[key]; foundKey {
|
||||
if color, foundColor := keyMap[value]; foundColor {
|
||||
if _, found := colors[key]; !found {
|
||||
colors[key] = map[string]models.LabelColors{}
|
||||
for _, l := range alert.Labels {
|
||||
if keyMap, foundKey := dedupedColors[l.Name]; foundKey {
|
||||
if color, foundColor := keyMap[l.Value]; foundColor {
|
||||
if _, found := colors[l.Name]; !found {
|
||||
colors[l.Name] = map[string]models.LabelColors{}
|
||||
}
|
||||
colors[key][value] = color
|
||||
colors[l.Name][l.Value] = color
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -684,7 +683,7 @@ func silences(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
type AlertList struct {
|
||||
Alerts []map[string]string `json:"alerts"`
|
||||
Alerts []models.Labels `json:"alerts"`
|
||||
}
|
||||
|
||||
func alertList(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -711,12 +710,9 @@ func alertList(w http.ResponseWriter, r *http.Request) {
|
||||
labelMap := map[string]map[string]string{}
|
||||
for _, ag := range filtered {
|
||||
for _, alert := range ag.Alerts {
|
||||
labels := map[string]string{}
|
||||
for k, v := range ag.Labels {
|
||||
labels[k] = v
|
||||
}
|
||||
for k, v := range alert.Labels {
|
||||
labels[k] = v
|
||||
labels := ag.Labels.Map()
|
||||
for _, l := range alert.Labels {
|
||||
labels[l.Name] = l.Value
|
||||
}
|
||||
h := fmt.Sprintf("%x", structhash.Sha1(labels, 1))
|
||||
labelMap[h] = labels
|
||||
@@ -736,10 +732,15 @@ func alertList(w http.ResponseWriter, r *http.Request) {
|
||||
sort.Strings(sortKeys)
|
||||
|
||||
al := AlertList{
|
||||
Alerts: []map[string]string{},
|
||||
Alerts: []models.Labels{},
|
||||
}
|
||||
for _, labels := range labelMap {
|
||||
al.Alerts = append(al.Alerts, labels)
|
||||
alert := models.Labels{}
|
||||
for k, v := range labels {
|
||||
alert = alert.Set(k, v)
|
||||
}
|
||||
sort.Sort(alert)
|
||||
al.Alerts = append(al.Alerts, alert)
|
||||
}
|
||||
sortSliceOfLabels(al.Alerts, sortKeys, "alertname")
|
||||
|
||||
@@ -751,20 +752,22 @@ func alertList(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write(data)
|
||||
}
|
||||
|
||||
func sortSliceOfLabels(labels []map[string]string, sortKeys []string, fallback string) {
|
||||
func sortSliceOfLabels(labels []models.Labels, sortKeys []string, fallback string) {
|
||||
sort.SliceStable(labels, func(i, j int) bool {
|
||||
for _, k := range sortKeys {
|
||||
if labels[i][k] != "" && labels[j][k] == "" {
|
||||
vi := labels[i].GetValue(k)
|
||||
vj := labels[j].GetValue(k)
|
||||
if vi != "" && vj == "" {
|
||||
return true
|
||||
}
|
||||
if labels[i][k] == "" && labels[j][k] != "" {
|
||||
if vi == "" && vj != "" {
|
||||
return false
|
||||
}
|
||||
if labels[i][k] != labels[j][k] {
|
||||
return sortorder.NaturalLess(labels[i][k], labels[j][k])
|
||||
if vi != vj {
|
||||
return sortorder.NaturalLess(vi, vj)
|
||||
}
|
||||
}
|
||||
return sortorder.NaturalLess(labels[i][fallback], labels[j][fallback])
|
||||
return sortorder.NaturalLess(labels[i].GetValue(fallback), labels[j].GetValue(fallback))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -814,8 +817,8 @@ func counters(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
countLabel(counters, "@state", alert.State)
|
||||
countLabel(counters, "@receiver", alert.Receiver)
|
||||
for key, value := range alert.Labels {
|
||||
countLabel(counters, key, value)
|
||||
for _, l := range alert.Labels {
|
||||
countLabel(counters, l.Name, l.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+135
-135
@@ -3474,87 +3474,87 @@ func TestAlertList(t *testing.T) {
|
||||
{
|
||||
args: "",
|
||||
alerts: AlertList{
|
||||
Alerts: []map[string]string{
|
||||
Alerts: []models.Labels{
|
||||
{
|
||||
"alertname": "Free_Disk_Space_Too_Low",
|
||||
"cluster": "staging",
|
||||
"disk": "sda",
|
||||
"instance": "server5",
|
||||
"job": "node_exporter",
|
||||
{Name: "alertname", Value: "Free_Disk_Space_Too_Low"},
|
||||
{Name: "cluster", Value: "staging"},
|
||||
{Name: "disk", Value: "sda"},
|
||||
{Name: "instance", Value: "server5"},
|
||||
{Name: "job", Value: "node_exporter"},
|
||||
},
|
||||
{
|
||||
"alertname": "HTTP_Probe_Failed",
|
||||
"cluster": "dev",
|
||||
"instance": "web1",
|
||||
"job": "node_exporter",
|
||||
{Name: "alertname", Value: "HTTP_Probe_Failed"},
|
||||
{Name: "cluster", Value: "dev"},
|
||||
{Name: "instance", Value: "web1"},
|
||||
{Name: "job", Value: "node_exporter"},
|
||||
},
|
||||
{
|
||||
"alertname": "HTTP_Probe_Failed",
|
||||
"cluster": "dev",
|
||||
"instance": "web2",
|
||||
"job": "node_exporter",
|
||||
{Name: "alertname", Value: "HTTP_Probe_Failed"},
|
||||
{Name: "cluster", Value: "dev"},
|
||||
{Name: "instance", Value: "web2"},
|
||||
{Name: "job", Value: "node_exporter"},
|
||||
},
|
||||
{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "dev",
|
||||
"instance": "server6",
|
||||
"ip": "127.0.0.6",
|
||||
"job": "node_ping",
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "dev"},
|
||||
{Name: "instance", Value: "server6"},
|
||||
{Name: "ip", Value: "127.0.0.6"},
|
||||
{Name: "job", Value: "node_ping"},
|
||||
},
|
||||
{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "dev",
|
||||
"instance": "server7",
|
||||
"ip": "127.0.0.7",
|
||||
"job": "node_ping",
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "dev"},
|
||||
{Name: "instance", Value: "server7"},
|
||||
{Name: "ip", Value: "127.0.0.7"},
|
||||
{Name: "job", Value: "node_ping"},
|
||||
},
|
||||
{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "dev",
|
||||
"instance": "server8",
|
||||
"ip": "127.0.0.8",
|
||||
"job": "node_ping",
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "dev"},
|
||||
{Name: "instance", Value: "server8"},
|
||||
{Name: "ip", Value: "127.0.0.8"},
|
||||
{Name: "job", Value: "node_ping"},
|
||||
},
|
||||
{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "prod",
|
||||
"instance": "server1",
|
||||
"ip": "127.0.0.1",
|
||||
"job": "node_ping",
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "prod"},
|
||||
{Name: "instance", Value: "server1"},
|
||||
{Name: "ip", Value: "127.0.0.1"},
|
||||
{Name: "job", Value: "node_ping"},
|
||||
},
|
||||
{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "prod",
|
||||
"instance": "server2",
|
||||
"ip": "127.0.0.2",
|
||||
"job": "node_ping",
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "prod"},
|
||||
{Name: "instance", Value: "server2"},
|
||||
{Name: "ip", Value: "127.0.0.2"},
|
||||
{Name: "job", Value: "node_ping"},
|
||||
},
|
||||
{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "staging",
|
||||
"instance": "server3",
|
||||
"ip": "127.0.0.3",
|
||||
"job": "node_ping",
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "staging"},
|
||||
{Name: "instance", Value: "server3"},
|
||||
{Name: "ip", Value: "127.0.0.3"},
|
||||
{Name: "job", Value: "node_ping"},
|
||||
},
|
||||
{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "staging",
|
||||
"instance": "server4",
|
||||
"ip": "127.0.0.4",
|
||||
"job": "node_ping",
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "staging"},
|
||||
{Name: "instance", Value: "server4"},
|
||||
{Name: "ip", Value: "127.0.0.4"},
|
||||
{Name: "job", Value: "node_ping"},
|
||||
},
|
||||
{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "staging",
|
||||
"instance": "server5",
|
||||
"ip": "127.0.0.5",
|
||||
"job": "node_ping",
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "staging"},
|
||||
{Name: "instance", Value: "server5"},
|
||||
{Name: "ip", Value: "127.0.0.5"},
|
||||
{Name: "job", Value: "node_ping"},
|
||||
},
|
||||
{
|
||||
"alertname": "Memory_Usage_Too_High",
|
||||
"cluster": "prod",
|
||||
"instance": "server2",
|
||||
"job": "node_exporter",
|
||||
{Name: "alertname", Value: "Memory_Usage_Too_High"},
|
||||
{Name: "cluster", Value: "prod"},
|
||||
{Name: "instance", Value: "server2"},
|
||||
{Name: "job", Value: "node_exporter"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -3562,13 +3562,13 @@ func TestAlertList(t *testing.T) {
|
||||
{
|
||||
args: "q=alertname=Free_Disk_Space_Too_Low",
|
||||
alerts: AlertList{
|
||||
Alerts: []map[string]string{
|
||||
Alerts: []models.Labels{
|
||||
{
|
||||
"alertname": "Free_Disk_Space_Too_Low",
|
||||
"cluster": "staging",
|
||||
"disk": "sda",
|
||||
"instance": "server5",
|
||||
"job": "node_exporter",
|
||||
{Name: "alertname", Value: "Free_Disk_Space_Too_Low"},
|
||||
{Name: "cluster", Value: "staging"},
|
||||
{Name: "disk", Value: "sda"},
|
||||
{Name: "instance", Value: "server5"},
|
||||
{Name: "job", Value: "node_exporter"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -3576,18 +3576,18 @@ func TestAlertList(t *testing.T) {
|
||||
{
|
||||
args: "q=alertname=HTTP_Probe_Failed",
|
||||
alerts: AlertList{
|
||||
Alerts: []map[string]string{
|
||||
Alerts: []models.Labels{
|
||||
{
|
||||
"alertname": "HTTP_Probe_Failed",
|
||||
"cluster": "dev",
|
||||
"instance": "web1",
|
||||
"job": "node_exporter",
|
||||
{Name: "alertname", Value: "HTTP_Probe_Failed"},
|
||||
{Name: "cluster", Value: "dev"},
|
||||
{Name: "instance", Value: "web1"},
|
||||
{Name: "job", Value: "node_exporter"},
|
||||
},
|
||||
{
|
||||
"alertname": "HTTP_Probe_Failed",
|
||||
"cluster": "dev",
|
||||
"instance": "web2",
|
||||
"job": "node_exporter",
|
||||
{Name: "alertname", Value: "HTTP_Probe_Failed"},
|
||||
{Name: "cluster", Value: "dev"},
|
||||
{Name: "instance", Value: "web2"},
|
||||
{Name: "job", Value: "node_exporter"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -3595,19 +3595,19 @@ func TestAlertList(t *testing.T) {
|
||||
{
|
||||
args: "q=instance=server2",
|
||||
alerts: AlertList{
|
||||
Alerts: []map[string]string{
|
||||
Alerts: []models.Labels{
|
||||
{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "prod",
|
||||
"instance": "server2",
|
||||
"ip": "127.0.0.2",
|
||||
"job": "node_ping",
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "prod"},
|
||||
{Name: "instance", Value: "server2"},
|
||||
{Name: "ip", Value: "127.0.0.2"},
|
||||
{Name: "job", Value: "node_ping"},
|
||||
},
|
||||
{
|
||||
"alertname": "Memory_Usage_Too_High",
|
||||
"cluster": "prod",
|
||||
"instance": "server2",
|
||||
"job": "node_exporter",
|
||||
{Name: "alertname", Value: "Memory_Usage_Too_High"},
|
||||
{Name: "cluster", Value: "prod"},
|
||||
{Name: "instance", Value: "server2"},
|
||||
{Name: "job", Value: "node_exporter"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -3615,20 +3615,20 @@ func TestAlertList(t *testing.T) {
|
||||
{
|
||||
args: "q=alertname=Host_Down&q=cluster=prod",
|
||||
alerts: AlertList{
|
||||
Alerts: []map[string]string{
|
||||
Alerts: []models.Labels{
|
||||
{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "prod",
|
||||
"instance": "server1",
|
||||
"ip": "127.0.0.1",
|
||||
"job": "node_ping",
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "prod"},
|
||||
{Name: "instance", Value: "server1"},
|
||||
{Name: "ip", Value: "127.0.0.1"},
|
||||
{Name: "job", Value: "node_ping"},
|
||||
},
|
||||
{
|
||||
"alertname": "Host_Down",
|
||||
"cluster": "prod",
|
||||
"instance": "server2",
|
||||
"ip": "127.0.0.2",
|
||||
"job": "node_ping",
|
||||
{Name: "alertname", Value: "Host_Down"},
|
||||
{Name: "cluster", Value: "prod"},
|
||||
{Name: "instance", Value: "server2"},
|
||||
{Name: "ip", Value: "127.0.0.2"},
|
||||
{Name: "job", Value: "node_ping"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -3636,7 +3636,7 @@ func TestAlertList(t *testing.T) {
|
||||
{
|
||||
args: "q=foo=bar",
|
||||
alerts: AlertList{
|
||||
Alerts: []map[string]string{},
|
||||
Alerts: []models.Labels{},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -3675,85 +3675,85 @@ func TestAlertList(t *testing.T) {
|
||||
|
||||
func TestSortSliceOfLabels(t *testing.T) {
|
||||
type testCaseT struct {
|
||||
labels []map[string]string
|
||||
labels []models.Labels
|
||||
sortKeys []string
|
||||
fallback string
|
||||
output []map[string]string
|
||||
output []models.Labels
|
||||
}
|
||||
|
||||
testCases := []testCaseT{
|
||||
{
|
||||
labels: []map[string]string{
|
||||
{"alertname": "alert2"},
|
||||
{"alertname": "alert1"},
|
||||
labels: []models.Labels{
|
||||
{{Name: "alertname", Value: "alert2"}},
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
},
|
||||
sortKeys: []string{},
|
||||
fallback: "",
|
||||
output: []map[string]string{
|
||||
{"alertname": "alert2"},
|
||||
{"alertname": "alert1"},
|
||||
output: []models.Labels{
|
||||
{{Name: "alertname", Value: "alert2"}},
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
labels: []map[string]string{
|
||||
{"alertname": "alert2"},
|
||||
{"alertname": "alert1"},
|
||||
labels: []models.Labels{
|
||||
{{Name: "alertname", Value: "alert2"}},
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
},
|
||||
sortKeys: []string{"alertname"},
|
||||
fallback: "alertname",
|
||||
output: []map[string]string{
|
||||
{"alertname": "alert1"},
|
||||
{"alertname": "alert2"},
|
||||
output: []models.Labels{
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
{{Name: "alertname", Value: "alert2"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
labels: []map[string]string{
|
||||
{"alertname": "alert2"},
|
||||
{"alertname": "alert1"},
|
||||
labels: []models.Labels{
|
||||
{{Name: "alertname", Value: "alert2"}},
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
},
|
||||
sortKeys: []string{},
|
||||
fallback: "alertname",
|
||||
output: []map[string]string{
|
||||
{"alertname": "alert1"},
|
||||
{"alertname": "alert2"},
|
||||
output: []models.Labels{
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
{{Name: "alertname", Value: "alert2"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
labels: []map[string]string{
|
||||
{"alertname": "alert2"},
|
||||
{"alertname": "alert1"},
|
||||
labels: []models.Labels{
|
||||
{{Name: "alertname", Value: "alert2"}},
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
},
|
||||
sortKeys: []string{"foo"},
|
||||
fallback: "alertname",
|
||||
output: []map[string]string{
|
||||
{"alertname": "alert1"},
|
||||
{"alertname": "alert2"},
|
||||
output: []models.Labels{
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
{{Name: "alertname", Value: "alert2"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
labels: []map[string]string{
|
||||
{"alertname": "alert1"},
|
||||
{"alertname": "alert1"},
|
||||
labels: []models.Labels{
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
},
|
||||
sortKeys: []string{"alertname"},
|
||||
fallback: "alertname",
|
||||
output: []map[string]string{
|
||||
{"alertname": "alert1"},
|
||||
{"alertname": "alert1"},
|
||||
output: []models.Labels{
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
labels: []map[string]string{
|
||||
{"alertname": "alert2", "job": "a"},
|
||||
{"alertname": "alert1"},
|
||||
{"alertname": "alert3", "job": "b"},
|
||||
labels: []models.Labels{
|
||||
{{Name: "alertname", Value: "alert2"}, {Name: "job", Value: "a"}},
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
{{Name: "alertname", Value: "alert3"}, {Name: "job", Value: "b"}},
|
||||
},
|
||||
sortKeys: []string{"job"},
|
||||
fallback: "alertname",
|
||||
output: []map[string]string{
|
||||
{"alertname": "alert2", "job": "a"},
|
||||
{"alertname": "alert3", "job": "b"},
|
||||
{"alertname": "alert1"},
|
||||
output: []models.Labels{
|
||||
{{Name: "alertname", Value: "alert2"}, {Name: "job", Value: "a"}},
|
||||
{{Name: "alertname", Value: "alert3"}, {Name: "job", Value: "b"}},
|
||||
{{Name: "alertname", Value: "alert1"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -236,8 +236,9 @@ func DedupKnownLabelValues(name string) []string {
|
||||
for _, am := range upstreams {
|
||||
for _, ag := range am.Alerts() {
|
||||
for _, alert := range ag.Alerts {
|
||||
if val, found := alert.Labels[name]; found {
|
||||
dedupedValues[val] = true
|
||||
if v := alert.Labels.Get(name); v != nil {
|
||||
dedupedValues[v.Value] = true
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,8 +253,8 @@ func (am *Alertmanager) pullAlerts(version string) error {
|
||||
if _, found := uniqueAlerts[agID][alertCFP]; !found {
|
||||
uniqueAlerts[agID][alertCFP] = alert
|
||||
}
|
||||
for key := range alert.Labels {
|
||||
knownLabelsMap[key] = true
|
||||
for _, l := range alert.Labels {
|
||||
knownLabelsMap[l.Name] = true
|
||||
}
|
||||
|
||||
if name, hc := am.IsHealthCheckAlert(&alert); hc != nil {
|
||||
@@ -305,8 +305,8 @@ func (am *Alertmanager) pullAlerts(version string) error {
|
||||
transform.ColorLabel(colors, "@alertmanager", am.Name)
|
||||
transform.ColorLabel(colors, "@cluster", am.Cluster)
|
||||
}
|
||||
for k, v := range alert.Labels {
|
||||
transform.ColorLabel(colors, k, v)
|
||||
for _, l := range alert.Labels {
|
||||
transform.ColorLabel(colors, l.Name, l.Value)
|
||||
}
|
||||
|
||||
alert.UpdateFingerprints()
|
||||
|
||||
@@ -31,9 +31,9 @@ var acTests = []acTest{
|
||||
Alerts: []models.Alert{
|
||||
{
|
||||
State: models.AlertStateActive,
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
"number": "1",
|
||||
Labels: models.Labels{
|
||||
{Name: "foo", Value: "bar"},
|
||||
{Name: "number", Value: "1"},
|
||||
},
|
||||
Receiver: "default",
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
@@ -43,9 +43,9 @@ var acTests = []acTest{
|
||||
},
|
||||
{
|
||||
State: models.AlertStateSuppressed,
|
||||
Labels: map[string]string{
|
||||
"foo": "bar baz",
|
||||
"number": "5",
|
||||
Labels: models.Labels{
|
||||
{Name: "foo", Value: "bar baz"},
|
||||
{Name: "number", Value: "5"},
|
||||
},
|
||||
Receiver: "not default",
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
|
||||
@@ -33,8 +33,8 @@ func (filter *fuzzyFilter) Match(alert *models.Alert, matches int) bool {
|
||||
}
|
||||
}
|
||||
|
||||
for _, val := range alert.Labels {
|
||||
if filter.Matcher.Compare(val, filter.Value) {
|
||||
for _, l := range alert.Labels {
|
||||
if filter.Matcher.Compare(l.Value, filter.Value) {
|
||||
filter.Hits++
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ type labelFilter struct {
|
||||
|
||||
func (filter *labelFilter) Match(alert *models.Alert, matches int) bool {
|
||||
if filter.IsValid {
|
||||
isMatch := filter.Matcher.Compare(alert.Labels[filter.Matched], filter.Value)
|
||||
isMatch := filter.Matcher.Compare(alert.Labels.GetValue(filter.Matched), filter.Value)
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
@@ -32,44 +32,44 @@ func newLabelFilter() FilterT {
|
||||
func labelAutocomplete(name string, operators []string, alerts []models.Alert) []models.Autocomplete {
|
||||
tokens := map[string]models.Autocomplete{}
|
||||
for _, alert := range alerts {
|
||||
for key, value := range alert.Labels {
|
||||
for _, l := range alert.Labels {
|
||||
for _, operator := range operators {
|
||||
switch operator {
|
||||
case equalOperator, notEqualOperator:
|
||||
token := fmt.Sprintf("%s%s%s", key, operator, value)
|
||||
token := fmt.Sprintf("%s%s%s", l.Name, operator, l.Value)
|
||||
tokens[token] = makeAC(
|
||||
token,
|
||||
[]string{
|
||||
key,
|
||||
fmt.Sprintf("%s%s", key, operator),
|
||||
value,
|
||||
l.Name,
|
||||
fmt.Sprintf("%s%s", l.Name, operator),
|
||||
l.Value,
|
||||
},
|
||||
)
|
||||
case regexpOperator, negativeRegexOperator:
|
||||
substrings := strings.Split(value, " ")
|
||||
substrings := strings.Split(l.Value, " ")
|
||||
if len(substrings) > 1 {
|
||||
for _, substring := range substrings {
|
||||
token := fmt.Sprintf("%s%s%s", key, operator, substring)
|
||||
token := fmt.Sprintf("%s%s%s", l.Name, operator, substring)
|
||||
tokens[token] = makeAC(
|
||||
token,
|
||||
[]string{
|
||||
key,
|
||||
fmt.Sprintf("%s%s", key, operator),
|
||||
value,
|
||||
l.Name,
|
||||
fmt.Sprintf("%s%s", l.Name, operator),
|
||||
l.Value,
|
||||
substring,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
case moreThanOperator, lessThanOperator:
|
||||
if _, err := strconv.Atoi(value); err == nil {
|
||||
token := fmt.Sprintf("%s%s%s", key, operator, value)
|
||||
if _, err := strconv.Atoi(l.Value); err == nil {
|
||||
token := fmt.Sprintf("%s%s%s", l.Name, operator, l.Value)
|
||||
tokens[token] = makeAC(
|
||||
token,
|
||||
[]string{
|
||||
key,
|
||||
fmt.Sprintf("%s%s", key, operator),
|
||||
value,
|
||||
l.Name,
|
||||
fmt.Sprintf("%s%s", l.Name, operator),
|
||||
l.Value,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -496,7 +496,7 @@ var tests = []filterTest{
|
||||
{
|
||||
Expression: "node=vps1",
|
||||
IsValid: true,
|
||||
Alert: models.Alert{Labels: map[string]string{"node": "vps1"}},
|
||||
Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
{
|
||||
@@ -508,68 +508,68 @@ var tests = []filterTest{
|
||||
{
|
||||
Expression: "node!=vps1",
|
||||
IsValid: true,
|
||||
Alert: models.Alert{Labels: map[string]string{"node": "vps1"}},
|
||||
Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}},
|
||||
IsMatch: false,
|
||||
},
|
||||
{
|
||||
Expression: "node!=vps1",
|
||||
IsValid: true,
|
||||
Alert: models.Alert{Labels: map[string]string{"node": "vps2"}},
|
||||
Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps2"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
{
|
||||
Expression: "node=~vps",
|
||||
IsValid: true,
|
||||
Alert: models.Alert{Labels: map[string]string{"node": "vps1"}},
|
||||
Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
{
|
||||
Expression: "node!~vps",
|
||||
IsValid: true,
|
||||
Alert: models.Alert{Labels: map[string]string{"node": "vps1"}},
|
||||
Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}},
|
||||
IsMatch: false,
|
||||
},
|
||||
{
|
||||
Expression: "node!~abc",
|
||||
IsValid: true,
|
||||
Alert: models.Alert{Labels: map[string]string{"node": "vps1"}},
|
||||
Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
{
|
||||
Expression: "node!~",
|
||||
IsValid: false,
|
||||
Alert: models.Alert{Labels: map[string]string{"node": "vps1"}},
|
||||
Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}},
|
||||
IsMatch: false,
|
||||
},
|
||||
{
|
||||
Expression: "node=",
|
||||
IsValid: false,
|
||||
Alert: models.Alert{Labels: map[string]string{"node": "vps1"}},
|
||||
Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}},
|
||||
IsMatch: false,
|
||||
},
|
||||
{
|
||||
Expression: "node===",
|
||||
IsValid: false,
|
||||
Alert: models.Alert{Labels: map[string]string{"node": "vps1"}},
|
||||
Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}},
|
||||
IsMatch: false,
|
||||
},
|
||||
|
||||
{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.Alert{Labels: map[string]string{"key": "abc"}},
|
||||
Alert: models.Alert{Labels: models.Labels{{Name: "key", Value: "abc"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.Alert{Labels: map[string]string{"key": "XXXabcx"}},
|
||||
Alert: models.Alert{Labels: models.Labels{{Name: "key", Value: "XXXabcx"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.Alert{Labels: map[string]string{"abc": "xxxab"}},
|
||||
Alert: models.Alert{Labels: models.Labels{{Name: "abc", Value: "xxxab"}}},
|
||||
IsMatch: false,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -49,17 +49,27 @@ func groups(c *client.AlertmanagerAPI, timeout time.Duration) ([]models.AlertGro
|
||||
ret := make([]models.AlertGroup, 0, len(groups.Payload))
|
||||
|
||||
for _, group := range groups.Payload {
|
||||
ls := models.Labels{}
|
||||
for k, v := range group.Labels {
|
||||
ls = ls.Set(k, v)
|
||||
}
|
||||
sort.Sort(ls)
|
||||
g := models.AlertGroup{
|
||||
Receiver: *group.Receiver.Name,
|
||||
Labels: group.Labels,
|
||||
Labels: ls,
|
||||
Alerts: make(models.AlertList, 0, len(group.Alerts)),
|
||||
}
|
||||
for _, alert := range group.Alerts {
|
||||
ls := models.Labels{}
|
||||
for k, v := range alert.Labels {
|
||||
ls = ls.Set(k, v)
|
||||
}
|
||||
sort.Sort(ls)
|
||||
a := models.Alert{
|
||||
Fingerprint: *alert.Fingerprint,
|
||||
Receiver: *group.Receiver.Name,
|
||||
Annotations: models.AnnotationsFromMap(alert.Annotations),
|
||||
Labels: alert.Labels,
|
||||
Labels: ls,
|
||||
StartsAt: time.Time(*alert.StartsAt),
|
||||
GeneratorURL: alert.GeneratorURL.String(),
|
||||
State: *alert.Status.State,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/* snapshot: SharedMaps */
|
||||
{
|
||||
"receiver": "",
|
||||
"labels": {
|
||||
"alertname": "FakeAlert"
|
||||
},
|
||||
"labels": [
|
||||
{
|
||||
"name": "alertname",
|
||||
"value": "FakeAlert"
|
||||
}
|
||||
],
|
||||
"alerts": [
|
||||
{
|
||||
"annotations": [
|
||||
@@ -15,10 +18,16 @@
|
||||
"isAction": false
|
||||
}
|
||||
],
|
||||
"labels": {
|
||||
"instance": "1",
|
||||
"job": "node_exporter"
|
||||
},
|
||||
"labels": [
|
||||
{
|
||||
"name": "job",
|
||||
"value": "node_exporter"
|
||||
},
|
||||
{
|
||||
"name": "instance",
|
||||
"value": "1"
|
||||
}
|
||||
],
|
||||
"startsAt": "0001-01-01T00:00:00Z",
|
||||
"state": "suppressed",
|
||||
"alertmanager": [
|
||||
@@ -54,10 +63,16 @@
|
||||
},
|
||||
{
|
||||
"annotations": [],
|
||||
"labels": {
|
||||
"instance": "2",
|
||||
"job": "node_exporter"
|
||||
},
|
||||
"labels": [
|
||||
{
|
||||
"name": "job",
|
||||
"value": "node_exporter"
|
||||
},
|
||||
{
|
||||
"name": "instance",
|
||||
"value": "2"
|
||||
}
|
||||
],
|
||||
"startsAt": "0001-01-01T00:00:00Z",
|
||||
"state": "active",
|
||||
"alertmanager": [
|
||||
@@ -93,11 +108,20 @@
|
||||
},
|
||||
{
|
||||
"annotations": [],
|
||||
"labels": {
|
||||
"extra": "ignore",
|
||||
"instance": "3",
|
||||
"job": "blackbox"
|
||||
},
|
||||
"labels": [
|
||||
{
|
||||
"name": "job",
|
||||
"value": "blackbox"
|
||||
},
|
||||
{
|
||||
"name": "instance",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"name": "extra",
|
||||
"value": "ignore"
|
||||
}
|
||||
],
|
||||
"startsAt": "0001-01-01T00:00:00Z",
|
||||
"state": "suppressed",
|
||||
"alertmanager": [
|
||||
@@ -146,7 +170,7 @@
|
||||
"isAction": false
|
||||
}
|
||||
],
|
||||
"labels": {},
|
||||
"labels": [],
|
||||
"silences": {
|
||||
"fakeCluster": [
|
||||
"fakeSilence1",
|
||||
|
||||
@@ -2,9 +2,11 @@ package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cnf/structhash"
|
||||
"github.com/fvbommel/sortorder"
|
||||
)
|
||||
|
||||
// AlertStateUnprocessed means that Alertmanager notify didn't yet process it
|
||||
@@ -24,16 +26,91 @@ var AlertStateList = []string{
|
||||
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 {
|
||||
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 map[string]string `json:"labels"`
|
||||
StartsAt time.Time `json:"startsAt"`
|
||||
State string `json:"state"`
|
||||
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
|
||||
@@ -52,7 +129,7 @@ type Alert struct {
|
||||
// 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.LabelsFP = fmt.Sprintf("%x", structhash.Sha1(a.Labels.Map(), 1))
|
||||
a.contentFP = fmt.Sprintf("%x", structhash.Sha1(a, 1))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
func TestLabelsSet(t *testing.T) {
|
||||
l := models.Labels{}
|
||||
if l.String() != "" {
|
||||
t.Errorf("Invalid labels: %s", l)
|
||||
}
|
||||
|
||||
l = l.Set("foo", "bar")
|
||||
if l.String() != `foo="bar"` {
|
||||
t.Errorf("Invalid labels: %s", l)
|
||||
}
|
||||
|
||||
l = l.Set("foo", "bar")
|
||||
if l.String() != `foo="bar"` {
|
||||
t.Errorf("Invalid labels: %s", l)
|
||||
}
|
||||
|
||||
l = l.Set("bar", "foo")
|
||||
if l.String() != `foo="bar",bar="foo"` {
|
||||
t.Errorf("Invalid labels: %s", l)
|
||||
}
|
||||
|
||||
l = l.Set("bar", "foo")
|
||||
if l.String() != `foo="bar",bar="foo"` {
|
||||
t.Errorf("Invalid labels: %s", l)
|
||||
}
|
||||
|
||||
l = l.Set("foo", "bar")
|
||||
if l.String() != `foo="bar",bar="foo"` {
|
||||
t.Errorf("Invalid labels: %s", l)
|
||||
}
|
||||
}
|
||||
|
||||
type sortLabelsTestCase struct {
|
||||
in models.Labels
|
||||
out models.Labels
|
||||
}
|
||||
|
||||
func TestSortLabels(t *testing.T) {
|
||||
testCases := []sortLabelsTestCase{
|
||||
{
|
||||
in: models.Labels{
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
out: models.Labels{
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
in: models.Labels{
|
||||
{Name: "foo", Value: "bar"},
|
||||
{Name: "bar", Value: "foo"},
|
||||
},
|
||||
out: models.Labels{
|
||||
{Name: "bar", Value: "foo"},
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
in: models.Labels{
|
||||
{Name: "bar", Value: "foo"},
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
out: models.Labels{
|
||||
{Name: "bar", Value: "foo"},
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
in: models.Labels{
|
||||
{Name: "foo", Value: "foo"},
|
||||
{Name: "bar", Value: "foo"},
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
out: models.Labels{
|
||||
{Name: "bar", Value: "foo"},
|
||||
{Name: "foo", Value: "bar"},
|
||||
{Name: "foo", Value: "foo"},
|
||||
},
|
||||
},
|
||||
{
|
||||
in: models.Labels{
|
||||
{Name: "1", Value: "a12"},
|
||||
{Name: "1", Value: "1"},
|
||||
{Name: "1", Value: "a2"},
|
||||
},
|
||||
out: models.Labels{
|
||||
{Name: "1", Value: "1"},
|
||||
{Name: "1", Value: "a2"},
|
||||
{Name: "1", Value: "a12"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
sort.Sort(testCase.in)
|
||||
if diff := cmp.Diff(testCase.in, testCase.out); diff != "" {
|
||||
t.Errorf("Incorrectly sorted labels (-want +got):\n%s", diff)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,14 +35,14 @@ func (a AlertList) Less(i, j int) bool {
|
||||
// There is a hash computed from all alerts, it's used by UI to quickly tell
|
||||
// if there was any change in a group and it needs to refresh it
|
||||
type AlertGroup struct {
|
||||
Receiver string `json:"receiver"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
Alerts AlertList `json:"alerts"`
|
||||
ID string `json:"id"`
|
||||
Hash string `json:"-"`
|
||||
AlertmanagerCount map[string]int `json:"alertmanagerCount"`
|
||||
StateCount map[string]int `json:"stateCount"`
|
||||
LatestStartsAt time.Time `json:"-"`
|
||||
Receiver string `json:"receiver"`
|
||||
Labels Labels `json:"labels"`
|
||||
Alerts AlertList `json:"alerts"`
|
||||
ID string `json:"id"`
|
||||
Hash string `json:"-"`
|
||||
AlertmanagerCount map[string]int `json:"alertmanagerCount"`
|
||||
StateCount map[string]int `json:"stateCount"`
|
||||
LatestStartsAt time.Time `json:"-"`
|
||||
}
|
||||
|
||||
// LabelsFingerprint is a checksum of this AlertGroup labels and the receiver
|
||||
@@ -50,7 +50,7 @@ type AlertGroup struct {
|
||||
func (ag AlertGroup) LabelsFingerprint() string {
|
||||
agIDHasher := sha1.New()
|
||||
_, _ = io.WriteString(agIDHasher, ag.Receiver)
|
||||
_, _ = io.WriteString(agIDHasher, fmt.Sprintf("%x", structhash.Sha1(ag.Labels, 1)))
|
||||
_, _ = io.WriteString(agIDHasher, fmt.Sprintf("%x", structhash.Sha1(ag.Labels.Map(), 1)))
|
||||
return fmt.Sprintf("%x", agIDHasher.Sum(nil))
|
||||
}
|
||||
|
||||
|
||||
@@ -97,8 +97,10 @@ var agFPTests = []agFPTest{
|
||||
{
|
||||
name: "different Labels shouldn't change content fingerprint",
|
||||
ag: models.AlertGroup{
|
||||
Receiver: "default",
|
||||
Labels: map[string]string{"foo": "bar"},
|
||||
Receiver: "default",
|
||||
Labels: models.Labels{
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
StateCount: map[string]int{"default": 0},
|
||||
},
|
||||
fpChange: false,
|
||||
@@ -107,10 +109,12 @@ var agFPTests = []agFPTest{
|
||||
name: "different set of alerts should change content fingerprint",
|
||||
ag: models.AlertGroup{
|
||||
Receiver: "default",
|
||||
Labels: map[string]string{"foo": "bar"},
|
||||
Labels: models.Labels{{Name: "foo", Value: "bar"}},
|
||||
Alerts: models.AlertList{
|
||||
models.Alert{
|
||||
Labels: map[string]string{"foo1": "bar"},
|
||||
Labels: models.Labels{
|
||||
{Name: "foo1", Value: "bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
StateCount: map[string]int{"default": 0},
|
||||
@@ -121,10 +125,12 @@ var agFPTests = []agFPTest{
|
||||
name: "another different set of alerts should change content fingerprint",
|
||||
ag: models.AlertGroup{
|
||||
Receiver: "default",
|
||||
Labels: map[string]string{"bar": "foo"},
|
||||
Labels: models.Labels{{Name: "bar", Value: "foo"}},
|
||||
Alerts: models.AlertList{
|
||||
models.Alert{
|
||||
Labels: map[string]string{"bar": "foo"},
|
||||
Labels: models.Labels{
|
||||
{Name: "bar", Value: "foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
StateCount: map[string]int{"default": 0},
|
||||
@@ -135,10 +141,14 @@ var agFPTests = []agFPTest{
|
||||
name: "repeating last set of alerts shouldn't change content fingerprint",
|
||||
ag: models.AlertGroup{
|
||||
Receiver: "default",
|
||||
Labels: map[string]string{"bar": "foo"},
|
||||
Labels: models.Labels{
|
||||
{Name: "bar", Value: "foo"},
|
||||
},
|
||||
Alerts: models.AlertList{
|
||||
models.Alert{
|
||||
Labels: map[string]string{"bar": "foo"},
|
||||
Labels: models.Labels{
|
||||
{Name: "bar", Value: "foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
StateCount: map[string]int{"default": 0},
|
||||
|
||||
+29
-29
@@ -94,7 +94,7 @@ func (lnsl LabelNameStatsList) Less(i, j int) bool {
|
||||
// APIAlertGroupSharedMaps defines shared part of APIAlertGroup
|
||||
type APIAlertGroupSharedMaps struct {
|
||||
Annotations Annotations `json:"annotations"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
Labels Labels `json:"labels"`
|
||||
Silences map[string][]string `json:"silences"`
|
||||
Sources []string `json:"sources"`
|
||||
Clusters []string `json:"clusters"`
|
||||
@@ -117,8 +117,8 @@ func (ag *APIAlertGroup) dedupLabels() {
|
||||
labelCounts := make(map[string]int, len(ag.Alerts))
|
||||
|
||||
for _, alert := range ag.Alerts {
|
||||
for name, val := range alert.Labels {
|
||||
key := fmt.Sprintf("%s\n%s", name, val)
|
||||
for _, l := range alert.Labels {
|
||||
key := fmt.Sprintf("%s\n%s", l.Name, l.Value)
|
||||
_, found := labelCounts[key]
|
||||
if found {
|
||||
labelCounts[key]++
|
||||
@@ -128,16 +128,16 @@ func (ag *APIAlertGroup) dedupLabels() {
|
||||
}
|
||||
}
|
||||
|
||||
sharedLabels := map[string]string{}
|
||||
sharedLabels := Labels{}
|
||||
|
||||
for i, alert := range ag.Alerts {
|
||||
newAlertLabels := map[string]string{}
|
||||
for name, val := range alert.Labels {
|
||||
key := fmt.Sprintf("%s\n%s", name, val)
|
||||
newAlertLabels := Labels{}
|
||||
for _, l := range alert.Labels {
|
||||
key := fmt.Sprintf("%s\n%s", l.Name, l.Value)
|
||||
if labelCounts[key] == totalAlerts {
|
||||
sharedLabels[name] = val
|
||||
sharedLabels = sharedLabels.Add(l)
|
||||
} else {
|
||||
newAlertLabels[name] = val
|
||||
newAlertLabels = newAlertLabels.Add(l)
|
||||
}
|
||||
}
|
||||
ag.Alerts[i].Labels = newAlertLabels
|
||||
@@ -148,27 +148,27 @@ func (ag *APIAlertGroup) dedupLabels() {
|
||||
}
|
||||
|
||||
func (ag *APIAlertGroup) removeGroupingLabels(dropNames []string) {
|
||||
newGroupLabels := map[string]string{}
|
||||
for name, val := range ag.Labels {
|
||||
if slices.StringInSlice(dropNames, name) {
|
||||
newGroupLabels := Labels{}
|
||||
for _, l := range ag.Labels {
|
||||
if slices.StringInSlice(dropNames, l.Name) {
|
||||
continue
|
||||
}
|
||||
newGroupLabels[name] = val
|
||||
newGroupLabels = newGroupLabels.Add(l)
|
||||
}
|
||||
ag.Labels = newGroupLabels
|
||||
|
||||
for i, alert := range ag.Alerts {
|
||||
newAlertLabels := map[string]string{}
|
||||
for name, val := range alert.Labels {
|
||||
if slices.StringInSlice(dropNames, name) {
|
||||
newAlertLabels := Labels{}
|
||||
for _, l := range alert.Labels {
|
||||
if slices.StringInSlice(dropNames, l.Name) {
|
||||
// skip all labels from the drop list
|
||||
continue
|
||||
}
|
||||
if _, found := ag.Labels[name]; found {
|
||||
if v := ag.Labels.Get(l.Name); v != nil {
|
||||
// skip all labels that are used for grouping
|
||||
continue
|
||||
}
|
||||
newAlertLabels[name] = val
|
||||
newAlertLabels = newAlertLabels.Add(l)
|
||||
}
|
||||
ag.Alerts[i].Labels = newAlertLabels
|
||||
}
|
||||
@@ -322,11 +322,11 @@ func (ag *APIAlertGroup) populateAllLabels() {
|
||||
|
||||
labels := map[string]int{}
|
||||
for _, alert := range ag.Alerts {
|
||||
for k := range alert.Labels {
|
||||
if _, ok := labels[k]; !ok {
|
||||
labels[k] = 0
|
||||
for _, l := range alert.Labels {
|
||||
if _, ok := labels[l.Name]; !ok {
|
||||
labels[l.Name] = 0
|
||||
}
|
||||
labels[k]++
|
||||
labels[l.Name]++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,15 +339,15 @@ func (ag *APIAlertGroup) populateAllLabels() {
|
||||
}
|
||||
|
||||
for _, alert := range ag.Alerts {
|
||||
for k, v := range alert.Labels {
|
||||
if _, ok := labelNames[k]; !ok {
|
||||
for _, l := range alert.Labels {
|
||||
if _, ok := labelNames[l.Name]; !ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := ag.AllLabels[alert.State][k]; !ok {
|
||||
ag.AllLabels[alert.State][k] = []string{}
|
||||
if _, ok := ag.AllLabels[alert.State][l.Name]; !ok {
|
||||
ag.AllLabels[alert.State][l.Name] = []string{}
|
||||
}
|
||||
if !slices.StringInSlice(ag.AllLabels[alert.State][k], v) {
|
||||
ag.AllLabels[alert.State][k] = append(ag.AllLabels[alert.State][k], v)
|
||||
if !slices.StringInSlice(ag.AllLabels[alert.State][l.Name], l.Value) {
|
||||
ag.AllLabels[alert.State][l.Name] = append(ag.AllLabels[alert.State][l.Name], l.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,7 +372,7 @@ func (ag *APIAlertGroup) DedupSharedMaps(ignoredLabels []string) {
|
||||
ag.dedupClusters()
|
||||
} else {
|
||||
ag.Shared = APIAlertGroupSharedMaps{
|
||||
Labels: map[string]string{},
|
||||
Labels: Labels{},
|
||||
Annotations: Annotations{},
|
||||
Silences: map[string][]string{},
|
||||
Clusters: []string{},
|
||||
|
||||
+17
-17
@@ -14,8 +14,8 @@ import (
|
||||
func TestDedupSharedMaps(t *testing.T) {
|
||||
ag := models.APIAlertGroup{
|
||||
AlertGroup: models.AlertGroup{
|
||||
Labels: map[string]string{
|
||||
"alertname": "FakeAlert",
|
||||
Labels: models.Labels{
|
||||
{"alertname", "FakeAlert"},
|
||||
},
|
||||
Alerts: models.AlertList{
|
||||
models.Alert{
|
||||
@@ -30,10 +30,10 @@ func TestDedupSharedMaps(t *testing.T) {
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"alertname": "FakeAlert",
|
||||
"job": "node_exporter",
|
||||
"instance": "1",
|
||||
Labels: models.Labels{
|
||||
{"alertname", "FakeAlert"},
|
||||
{"job", "node_exporter"},
|
||||
{"instance", "1"},
|
||||
},
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
{
|
||||
@@ -60,10 +60,10 @@ func TestDedupSharedMaps(t *testing.T) {
|
||||
Value: "this is summary",
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"alertname": "FakeAlert",
|
||||
"job": "node_exporter",
|
||||
"instance": "2",
|
||||
Labels: models.Labels{
|
||||
{"alertname", "FakeAlert"},
|
||||
{"job", "node_exporter"},
|
||||
{"instance", "2"},
|
||||
},
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
{
|
||||
@@ -90,11 +90,11 @@ func TestDedupSharedMaps(t *testing.T) {
|
||||
Value: "this is summary",
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"alertname": "FakeAlert",
|
||||
"job": "blackbox",
|
||||
"instance": "3",
|
||||
"extra": "ignore",
|
||||
Labels: models.Labels{
|
||||
{"alertname", "FakeAlert"},
|
||||
{"job", "blackbox"},
|
||||
{"instance", "3"},
|
||||
{"extra", "ignore"},
|
||||
},
|
||||
Alertmanager: []models.AlertmanagerInstance{
|
||||
{
|
||||
@@ -126,8 +126,8 @@ func TestDedupSharedMapsSingleGroup(t *testing.T) {
|
||||
ag := models.APIAlertGroup{
|
||||
AlertGroup: models.AlertGroup{
|
||||
Alerts: models.AlertList{
|
||||
models.Alert{State: models.AlertStateActive, Labels: map[string]string{"foo": "bar"}},
|
||||
models.Alert{State: models.AlertStateUnprocessed, Labels: map[string]string{"foo": "bar"}},
|
||||
models.Alert{State: models.AlertStateActive, Labels: models.Labels{{"foo", "bar"}}},
|
||||
models.Alert{State: models.AlertStateUnprocessed, Labels: models.Labels{{"foo", "bar"}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package transform
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
@@ -12,21 +13,22 @@ import (
|
||||
// it takes the list of label keys to ignore and alert label map
|
||||
// it will return label map without labels found on the ignore list
|
||||
func StripLables(keptLabels, ignoredLabels []string, keptLabelsRegex, ignoredLabelsRegex []*regexp.Regexp,
|
||||
sourceLabels map[string]string) map[string]string {
|
||||
sourceLabels models.Labels) models.Labels {
|
||||
// empty keep lists means keep everything by default
|
||||
keepAll := len(keptLabels) == 0 && len(keptLabelsRegex) == 0
|
||||
labels := map[string]string{}
|
||||
for label, value := range sourceLabels {
|
||||
labels := models.Labels{}
|
||||
for _, label := range sourceLabels {
|
||||
// is explicitly marked to be kept
|
||||
inKeep := slices.StringInSlice(keptLabels, label) || matchesAnyRegex(label, keptLabelsRegex)
|
||||
inKeep := slices.StringInSlice(keptLabels, label.Name) || matchesAnyRegex(label.Name, keptLabelsRegex)
|
||||
// is explicitly marked to be stripped
|
||||
inStrip := slices.StringInSlice(ignoredLabels, label) || matchesAnyRegex(label, ignoredLabelsRegex)
|
||||
inStrip := slices.StringInSlice(ignoredLabels, label.Name) || matchesAnyRegex(label.Name, ignoredLabelsRegex)
|
||||
if (keepAll || inKeep) && !inStrip {
|
||||
// strip leading and trailing space in label value
|
||||
// this is to normalize values in case space is added by Alertmanager rules
|
||||
labels[label] = strings.TrimSpace(value)
|
||||
labels = labels.Set(label.Name, strings.TrimSpace(label.Value))
|
||||
}
|
||||
}
|
||||
sort.Sort(labels)
|
||||
return labels
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package transform_test
|
||||
|
||||
import (
|
||||
"github.com/prymitive/karma/internal/regex"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
"github.com/prymitive/karma/internal/regex"
|
||||
"github.com/prymitive/karma/internal/transform"
|
||||
)
|
||||
|
||||
@@ -15,8 +15,8 @@ type stripLabelTest struct {
|
||||
keep []string
|
||||
stripRegex []string
|
||||
keepRegex []string
|
||||
before map[string]string
|
||||
after map[string]string
|
||||
before models.Labels
|
||||
after models.Labels
|
||||
}
|
||||
|
||||
var stripLabelTests = []stripLabelTest{
|
||||
@@ -25,14 +25,14 @@ var stripLabelTests = []stripLabelTest{
|
||||
keep: []string{},
|
||||
stripRegex: []string{},
|
||||
keepRegex: []string{},
|
||||
before: map[string]string{
|
||||
"host": "localhost",
|
||||
"env": "production",
|
||||
"level": "info",
|
||||
before: models.Labels{
|
||||
{Name: "host", Value: "localhost"},
|
||||
{Name: "env", Value: "production"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
after: map[string]string{
|
||||
"host": "localhost",
|
||||
"level": "info",
|
||||
after: models.Labels{
|
||||
{Name: "host", Value: "localhost"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -40,15 +40,15 @@ var stripLabelTests = []stripLabelTest{
|
||||
keep: []string{},
|
||||
stripRegex: []string{},
|
||||
keepRegex: []string{},
|
||||
before: map[string]string{
|
||||
"host": "localhost",
|
||||
"env": "production",
|
||||
"level": "info",
|
||||
before: models.Labels{
|
||||
{Name: "host", Value: "localhost"},
|
||||
{Name: "env", Value: "production"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
after: map[string]string{
|
||||
"host": "localhost",
|
||||
"env": "production",
|
||||
"level": "info",
|
||||
after: models.Labels{
|
||||
{Name: "env", Value: "production"},
|
||||
{Name: "host", Value: "localhost"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -56,15 +56,15 @@ var stripLabelTests = []stripLabelTest{
|
||||
keep: []string{},
|
||||
stripRegex: []string{},
|
||||
keepRegex: []string{},
|
||||
before: map[string]string{
|
||||
"host": "localhost",
|
||||
"env": "production",
|
||||
"level": "info",
|
||||
before: models.Labels{
|
||||
{Name: "env", Value: "production"},
|
||||
{Name: "host", Value: "localhost"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
after: map[string]string{
|
||||
"host": "localhost",
|
||||
"env": "production",
|
||||
"level": "info",
|
||||
after: models.Labels{
|
||||
{Name: "env", Value: "production"},
|
||||
{Name: "host", Value: "localhost"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -72,23 +72,23 @@ var stripLabelTests = []stripLabelTest{
|
||||
keep: []string{},
|
||||
stripRegex: []string{},
|
||||
keepRegex: []string{},
|
||||
before: map[string]string{
|
||||
"host": "localhost",
|
||||
before: models.Labels{
|
||||
{Name: "host", Value: "localhost"},
|
||||
},
|
||||
after: map[string]string{},
|
||||
after: models.Labels{},
|
||||
},
|
||||
{
|
||||
strip: []string{},
|
||||
keep: []string{"env"},
|
||||
stripRegex: []string{},
|
||||
keepRegex: []string{},
|
||||
before: map[string]string{
|
||||
"host": "localhost",
|
||||
"env": "production",
|
||||
"level": "info",
|
||||
before: models.Labels{
|
||||
{Name: "host", Value: "localhost"},
|
||||
{Name: "env", Value: "production"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
after: map[string]string{
|
||||
"env": "production",
|
||||
after: models.Labels{
|
||||
{Name: "env", Value: "production"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -96,13 +96,13 @@ var stripLabelTests = []stripLabelTest{
|
||||
keep: []string{"host"},
|
||||
stripRegex: []string{},
|
||||
keepRegex: []string{},
|
||||
before: map[string]string{
|
||||
"host": "localhost",
|
||||
"env": "production",
|
||||
"level": "info",
|
||||
before: models.Labels{
|
||||
{Name: "host", Value: "localhost"},
|
||||
{Name: "env", Value: "production"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
after: map[string]string{
|
||||
"host": "localhost",
|
||||
after: models.Labels{
|
||||
{Name: "host", Value: "localhost"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -110,24 +110,24 @@ var stripLabelTests = []stripLabelTest{
|
||||
keep: []string{"env"},
|
||||
stripRegex: []string{},
|
||||
keepRegex: []string{},
|
||||
before: map[string]string{
|
||||
"host": "localhost",
|
||||
"level": "info",
|
||||
before: models.Labels{
|
||||
{Name: "host", Value: "localhost"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
after: map[string]string{},
|
||||
after: models.Labels{},
|
||||
},
|
||||
{
|
||||
strip: []string{},
|
||||
keep: []string{},
|
||||
stripRegex: []string{".*e.*"},
|
||||
keepRegex: []string{},
|
||||
before: map[string]string{
|
||||
"host": "localhost",
|
||||
"env": "production",
|
||||
"level": "info",
|
||||
before: models.Labels{
|
||||
{Name: "host", Value: "localhost"},
|
||||
{Name: "env", Value: "production"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
after: map[string]string{
|
||||
"host": "localhost",
|
||||
after: models.Labels{
|
||||
{Name: "host", Value: "localhost"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -135,14 +135,14 @@ var stripLabelTests = []stripLabelTest{
|
||||
keep: []string{},
|
||||
stripRegex: []string{},
|
||||
keepRegex: []string{".*e.*"},
|
||||
before: map[string]string{
|
||||
"host": "localhost",
|
||||
"env": "production",
|
||||
"level": "info",
|
||||
before: models.Labels{
|
||||
{Name: "host", Value: "localhost"},
|
||||
{Name: "env", Value: "production"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
after: map[string]string{
|
||||
"env": "production",
|
||||
"level": "info",
|
||||
after: models.Labels{
|
||||
{Name: "env", Value: "production"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -150,13 +150,13 @@ var stripLabelTests = []stripLabelTest{
|
||||
keep: []string{"env", "level"},
|
||||
stripRegex: []string{".*el"},
|
||||
keepRegex: []string{},
|
||||
before: map[string]string{
|
||||
"host": "localhost",
|
||||
"env": "production",
|
||||
"level": "info",
|
||||
before: models.Labels{
|
||||
{Name: "host", Value: "localhost"},
|
||||
{Name: "env", Value: "production"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
after: map[string]string{
|
||||
"env": "production",
|
||||
after: models.Labels{
|
||||
{Name: "env", Value: "production"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -164,13 +164,13 @@ var stripLabelTests = []stripLabelTest{
|
||||
keep: []string{},
|
||||
stripRegex: []string{},
|
||||
keepRegex: []string{".*e.*"},
|
||||
before: map[string]string{
|
||||
"host": "localhost",
|
||||
"env": "production",
|
||||
"level": "info",
|
||||
before: models.Labels{
|
||||
{Name: "host", Value: "localhost"},
|
||||
{Name: "env", Value: "production"},
|
||||
{Name: "level", Value: "info"},
|
||||
},
|
||||
after: map[string]string{
|
||||
"env": "production",
|
||||
after: models.Labels{
|
||||
{Name: "env", Value: "production"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user