mirror of
https://github.com/prymitive/karma
synced 2026-08-23 11:56:20 +00:00
Support multiple receivers
This commit is contained in:
@@ -7,7 +7,7 @@ ALERTMANAGER_URI := file://$(MOCK_PATH)
|
||||
# Listen port when running locally
|
||||
PORT := 8080
|
||||
|
||||
SOURCES := $(wildcard *.go) $(wildcard */*.go)
|
||||
SOURCES := $(wildcard *.go) $(wildcard */*.go) $(wildcard */*/*.go)
|
||||
ASSET_SOURCES := $(wildcard assets/*/* assets/*/*/*)
|
||||
|
||||
GO_BINDATA_MODE := prod
|
||||
@@ -50,7 +50,7 @@ clean:
|
||||
.PHONY: run
|
||||
run: $(NAME)
|
||||
ALERTMANAGER_URI=$(ALERTMANAGER_URI) \
|
||||
COLOR_LABELS_UNIQUE="instance cluster" \
|
||||
COLOR_LABELS_UNIQUE="@receiver instance cluster" \
|
||||
COLOR_LABELS_STATIC="job" \
|
||||
DEBUG="$(GIN_DEBUG)" \
|
||||
FILTER_DEFAULT="@state=active" \
|
||||
|
||||
@@ -76,6 +76,10 @@
|
||||
<% }) %>
|
||||
<% var attrs = Alerts.GetLabelAttrs("@state", alert.state) %>
|
||||
<%= Templates.Render('buttonLabel', {elem: 'span', attrs: attrs, label: {key: '@state', value: alert.state, text: '@state: ' + alert.state}}) %>
|
||||
|
||||
<% var attrs = Alerts.GetLabelAttrs("@receiver", alert.receiver) %>
|
||||
<%= Templates.Render('buttonLabel', {elem: 'span', attrs: attrs, label: {key: '@receiver', value: alert.receiver, text: '@receiver: ' + alert.receiver}}) %>
|
||||
|
||||
<% if (alert.silencedBy.length == 0) { %>
|
||||
<% var labels = [] %>
|
||||
<% _.each(Alerts.SortMapByKey(alert.labels), function(label) { %>
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,46 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudflare/unsee/models"
|
||||
)
|
||||
|
||||
type receiverFilter struct {
|
||||
alertFilter
|
||||
}
|
||||
|
||||
func (filter *receiverFilter) Match(alert *models.Alert, matches int) bool {
|
||||
if filter.IsValid {
|
||||
isMatch := filter.Matcher.Compare(alert.Receiver, filter.Value)
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
}
|
||||
|
||||
func newreceiverFilter() FilterT {
|
||||
f := receiverFilter{}
|
||||
return &f
|
||||
}
|
||||
|
||||
func receiverAutocomplete(name string, operators []string, alerts []models.Alert) []models.Autocomplete {
|
||||
tokens := []models.Autocomplete{}
|
||||
for _, operator := range operators {
|
||||
for _, alert := range alerts {
|
||||
tokens = append(tokens, makeAC(
|
||||
name+operator+alert.Receiver,
|
||||
[]string{
|
||||
name,
|
||||
strings.TrimPrefix(name, "@"),
|
||||
name + operator,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
@@ -47,6 +47,13 @@ var AllFilters = []filterConfig{
|
||||
Factory: newstateFilter,
|
||||
Autocomplete: stateAutocomplete,
|
||||
},
|
||||
filterConfig{
|
||||
Label: "@receiver",
|
||||
LabelRe: regexp.MustCompile("^@receiver$"),
|
||||
SupportedOperators: []string{regexpOperator, negativeRegexOperator, equalOperator, notEqualOperator},
|
||||
Factory: newreceiverFilter,
|
||||
Autocomplete: receiverAutocomplete,
|
||||
},
|
||||
filterConfig{
|
||||
Label: "@age",
|
||||
LabelRe: regexp.MustCompile("^@age$"),
|
||||
|
||||
+35
-11
@@ -34,16 +34,24 @@ type alert struct {
|
||||
type alertsGroups struct {
|
||||
Labels map[string]string `json:"labels"`
|
||||
Blocks []struct {
|
||||
Alerts []alert `json:"alerts"`
|
||||
Alerts []alert `json:"alerts"`
|
||||
RouteOps struct {
|
||||
Receiver string `json:"receiver"`
|
||||
} `json:"routeOpts"`
|
||||
} `json:"blocks"`
|
||||
}
|
||||
|
||||
type alertsGroupsAPISchema struct {
|
||||
Status string `json:"status"`
|
||||
Groups []alertsGroups `json:"data"`
|
||||
Data []alertsGroups `json:"data"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type alertsGroupReceiver struct {
|
||||
Name string
|
||||
Groups []models.AlertGroup
|
||||
}
|
||||
|
||||
// AlertMapper implements Alertmanager API schema
|
||||
type AlertMapper struct {
|
||||
mapper.AlertMapper
|
||||
@@ -59,6 +67,7 @@ func (m AlertMapper) IsSupported(version string) bool {
|
||||
// It will only return alerts or error (if any)
|
||||
func (m AlertMapper) GetAlerts() ([]models.AlertGroup, error) {
|
||||
groups := []models.AlertGroup{}
|
||||
receivers := map[string]alertsGroupReceiver{}
|
||||
resp := alertsGroupsAPISchema{}
|
||||
|
||||
url, err := transport.JoinURL(config.Config.AlertmanagerURI, "api/v1/alerts/groups")
|
||||
@@ -75,9 +84,16 @@ func (m AlertMapper) GetAlerts() ([]models.AlertGroup, error) {
|
||||
return groups, errors.New(resp.Error)
|
||||
}
|
||||
|
||||
for _, g := range resp.Groups {
|
||||
alertList := models.AlertList{}
|
||||
for _, b := range g.Blocks {
|
||||
for _, d := range resp.Data {
|
||||
for _, b := range d.Blocks {
|
||||
rcv, found := receivers[b.RouteOps.Receiver]
|
||||
if !found {
|
||||
rcv = alertsGroupReceiver{
|
||||
Name: b.RouteOps.Receiver,
|
||||
}
|
||||
receivers[b.RouteOps.Receiver] = rcv
|
||||
}
|
||||
alertList := models.AlertList{}
|
||||
for _, a := range b.Alerts {
|
||||
inhibitedBy := []string{}
|
||||
if a.Status.InhibitedBy != nil {
|
||||
@@ -87,7 +103,8 @@ func (m AlertMapper) GetAlerts() ([]models.AlertGroup, error) {
|
||||
if a.Status.SilencedBy != nil {
|
||||
silencedBy = a.Status.SilencedBy
|
||||
}
|
||||
us := models.Alert{
|
||||
a := models.Alert{
|
||||
Receiver: rcv.Name,
|
||||
Annotations: a.Annotations,
|
||||
Labels: a.Labels,
|
||||
StartsAt: a.StartsAt,
|
||||
@@ -97,14 +114,21 @@ func (m AlertMapper) GetAlerts() ([]models.AlertGroup, error) {
|
||||
InhibitedBy: inhibitedBy,
|
||||
SilencedBy: silencedBy,
|
||||
}
|
||||
alertList = append(alertList, us)
|
||||
alertList = append(alertList, a)
|
||||
}
|
||||
ug := models.AlertGroup{
|
||||
Receiver: rcv.Name,
|
||||
Labels: d.Labels,
|
||||
Alerts: alertList,
|
||||
}
|
||||
rcv.Groups = append(rcv.Groups, ug)
|
||||
receivers[rcv.Name] = rcv
|
||||
}
|
||||
ug := models.AlertGroup{
|
||||
Labels: g.Labels,
|
||||
Alerts: alertList,
|
||||
}
|
||||
for _, rcv := range receivers {
|
||||
for _, ag := range rcv.Groups {
|
||||
groups = append(groups, ag)
|
||||
}
|
||||
groups = append(groups, ug)
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ type Alert struct {
|
||||
SilencedBy []string `json:"silencedBy"`
|
||||
InhibitedBy []string `json:"inhibitedBy"`
|
||||
// unsee fields
|
||||
Receiver string `json:"receiver"`
|
||||
Links map[string]string `json:"links"`
|
||||
Fingerprint string `json:"-"`
|
||||
}
|
||||
@@ -100,6 +101,7 @@ 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"`
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
type dataStore struct {
|
||||
Lock sync.RWMutex
|
||||
Alerts []models.AlertGroup
|
||||
Groups []models.AlertGroup
|
||||
Silences map[string]models.Silence
|
||||
Colors models.LabelsColorMap
|
||||
Autocomplete []models.Autocomplete
|
||||
@@ -35,10 +35,10 @@ func (ds *dataStore) SetSilences(s map[string]models.Silence) {
|
||||
}
|
||||
|
||||
// Update will lock the store and update internal data
|
||||
func (ds *dataStore) Update(alerts []models.AlertGroup, colors models.LabelsColorMap, autocomplete []models.Autocomplete) {
|
||||
func (ds *dataStore) Update(groups []models.AlertGroup, colors models.LabelsColorMap, autocomplete []models.Autocomplete) {
|
||||
ds.Lock.Lock()
|
||||
defer ds.Lock.Unlock()
|
||||
ds.Alerts = alerts
|
||||
ds.Groups = groups
|
||||
ds.Colors = colors
|
||||
ds.Autocomplete = autocomplete
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
"github.com/cloudflare/unsee/models"
|
||||
"github.com/cloudflare/unsee/store"
|
||||
"github.com/cloudflare/unsee/transform"
|
||||
"github.com/cnf/structhash"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cnf/structhash"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
@@ -66,53 +66,28 @@ func PullFromAlertmanager() {
|
||||
metricAlerts.With(prometheus.Labels{"state": state}).Set(0)
|
||||
}
|
||||
|
||||
uniqueAlerts := map[string]bool{}
|
||||
|
||||
log.Infof("Processing alert groups (%d)", len(alertGroups))
|
||||
for _, ag := range alertGroups {
|
||||
// used to generate group content hash
|
||||
agHasher := sha1.New()
|
||||
|
||||
alerts := map[string]models.Alert{}
|
||||
|
||||
ignoredLabels := []string{}
|
||||
for _, il := range config.Config.StripLabels {
|
||||
ignoredLabels = append(ignoredLabels, il)
|
||||
}
|
||||
|
||||
alerts := models.AlertList{}
|
||||
for _, alert := range ag.Alerts {
|
||||
// generate alert fingerprint from a raw, unaltered alert object
|
||||
alert.Fingerprint = fmt.Sprintf("%x", structhash.Sha1(alert, 1))
|
||||
|
||||
// skip global duplicated alerts (shared between multiple groups)
|
||||
if _, found := uniqueAlerts[alert.Fingerprint]; found {
|
||||
continue
|
||||
}
|
||||
// skip group duplicated alerts (shared between multiple blocks)
|
||||
if _, found := alerts[alert.Fingerprint]; found {
|
||||
continue
|
||||
}
|
||||
|
||||
// mark this alert as seen
|
||||
uniqueAlerts[alert.Fingerprint] = true
|
||||
|
||||
alert.Annotations, alert.Links = transform.DetectLinks(alert.Annotations)
|
||||
alert.Labels = transform.StripLables(ignoredLabels, alert.Labels)
|
||||
|
||||
alerts[alert.Fingerprint] = alert
|
||||
alert.Labels = transform.StripLables(config.Config.StripLabels, alert.Labels)
|
||||
|
||||
io.WriteString(agHasher, alert.Fingerprint) // alert group hasher
|
||||
|
||||
transform.ColorLabel(colorStore, "@receiver", alert.Receiver)
|
||||
for k, v := range alert.Labels {
|
||||
transform.ColorLabel(colorStore, k, v)
|
||||
}
|
||||
alerts = append(alerts, alert)
|
||||
|
||||
}
|
||||
|
||||
// reset alerts, we need to deduplicate
|
||||
ag.Alerts = []models.Alert{}
|
||||
for _, alert := range alerts {
|
||||
ag.Alerts = append(ag.Alerts, alert)
|
||||
// update internal metrics
|
||||
metricAlerts.With(prometheus.Labels{"state": alert.State}).Inc()
|
||||
}
|
||||
|
||||
@@ -126,6 +101,7 @@ func PullFromAlertmanager() {
|
||||
ag.ID = fmt.Sprintf("%x", structhash.Sha1(ag.Labels, 1))
|
||||
// Hash is a checksum of all alerts, used to tell when any alert in the group changed
|
||||
ag.Hash = fmt.Sprintf("%x", agHasher.Sum(nil))
|
||||
|
||||
alertStore = append(alertStore, ag)
|
||||
}
|
||||
|
||||
|
||||
@@ -133,9 +133,10 @@ func alerts(c *gin.Context) {
|
||||
store.Store.Lock.RLock()
|
||||
|
||||
var matches int
|
||||
for _, ag := range store.Store.Alerts {
|
||||
for _, ag := range store.Store.Groups {
|
||||
agCopy := models.AlertGroup{
|
||||
ID: ag.ID,
|
||||
Receiver: ag.Receiver,
|
||||
Labels: ag.Labels,
|
||||
Alerts: []models.Alert{},
|
||||
StateCount: map[string]int{},
|
||||
@@ -175,6 +176,16 @@ func alerts(c *gin.Context) {
|
||||
|
||||
countLabel(counters, "@state", alert.State)
|
||||
|
||||
countLabel(counters, "@receiver", alert.Receiver)
|
||||
if ck, foundKey := store.Store.Colors["@receiver"]; foundKey {
|
||||
if cv, foundVal := ck[alert.Receiver]; foundVal {
|
||||
if _, found := colors["@receiver"]; !found {
|
||||
colors["@receiver"] = map[string]models.LabelColors{}
|
||||
}
|
||||
colors["@receiver"][alert.Receiver] = cv
|
||||
}
|
||||
}
|
||||
|
||||
agCopy.StateCount[alert.State]++
|
||||
|
||||
for key, value := range alert.Labels {
|
||||
|
||||
Reference in New Issue
Block a user