Support multiple receivers

This commit is contained in:
Łukasz Mierzwa
2017-06-03 13:23:51 -07:00
parent a15cbf4e86
commit 6d15a82e89
10 changed files with 119 additions and 49 deletions
+2 -2
View File
@@ -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" \
+4
View File
@@ -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
View File
File diff suppressed because one or more lines are too long
+46
View File
@@ -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
}
+7
View File
@@ -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
View File
@@ -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
}
+2
View File
@@ -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
View File
@@ -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
}
+7 -31
View File
@@ -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)
}
+12 -1
View File
@@ -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 {