Implement deterministic alert group order, cleanup

This commit is contained in:
Fabian Reinartz 2015-11-10 14:52:04 +01:00
parent dc656a44ea
commit ede4b63a91
5 changed files with 50 additions and 36 deletions

4
api.go
View File

@ -38,7 +38,7 @@ type API struct {
config string config string
uptime time.Time uptime time.Time
groups func() []*UIGroups groups func() AlertOverview
// context is an indirection for testing. // context is an indirection for testing.
context func(r *http.Request) context.Context context func(r *http.Request) context.Context
@ -46,7 +46,7 @@ type API struct {
} }
// NewAPI returns a new API. // NewAPI returns a new API.
func NewAPI(alerts provider.Alerts, silences provider.Silences, gf func() []*UIGroups) *API { func NewAPI(alerts provider.Alerts, silences provider.Silences, gf func() AlertOverview) *API {
return &API{ return &API{
context: route.Context, context: route.Context,
alerts: alerts, alerts: alerts,

View File

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"sort"
"sync" "sync"
"time" "time"
@ -59,29 +60,40 @@ func (d *Dispatcher) Run() {
close(d.done) close(d.done)
} }
// UIGroup is the representation of a group of alerts as provided by // AlertBlock contains a list of alerts associated with a set of
// the API. // routing options.
type UIGroup struct { type AlertBlock struct {
RouteOpts *RouteOpts `json:"routeOpts"` RouteOpts *RouteOpts `json:"routeOpts"`
Alerts []*UIAlert `json:"alerts"` Alerts []*APIAlert `json:"alerts"`
} }
type UIGroups struct { // APIAlert is the API representation of an alert, which is a regular alert
Labels model.LabelSet `json:"labels"` // annotated with silencing and inhibition info.
Groups []*UIGroup `json:"groups"` type APIAlert struct {
}
type UIAlert struct {
*model.Alert *model.Alert
Inhibited bool `json:"inhibited"` Inhibited bool `json:"inhibited"`
Silenced uint64 `json:"silenced,omitempty"` Silenced uint64 `json:"silenced,omitempty"`
} }
func (d *Dispatcher) Groups() []*UIGroups { // AlertGroup is a list of alert blocks grouped by the same label set.
var groups []*UIGroups type AlertGroup struct {
Labels model.LabelSet `json:"labels"`
Blocks []*AlertBlock `json:"blocks"`
}
seen := map[model.Fingerprint]*UIGroups{} // AlertOverview is a representation of all active alerts in the system.
type AlertOverview []*AlertGroup
func (ao AlertOverview) Swap(i, j int) { ao[i], ao[j] = ao[j], ao[i] }
func (ao AlertOverview) Less(i, j int) bool { return ao[i].Labels.Before(ao[j].Labels) }
func (ao AlertOverview) Len() int { return len(ao) }
// Groups populates an AlertOverview from the dispatcher's internal state.
func (d *Dispatcher) Groups() AlertOverview {
var overview AlertOverview
seen := map[model.Fingerprint]*AlertGroup{}
for route, ags := range d.aggrGroups { for route, ags := range d.aggrGroups {
for _, ag := range ags { for _, ag := range ags {
@ -90,39 +102,41 @@ func (d *Dispatcher) Groups() []*UIGroups {
alerts = append(alerts, a) alerts = append(alerts, a)
} }
uig, ok := seen[ag.labels.Fingerprint()] alertGroup, ok := seen[ag.labels.Fingerprint()]
if !ok { if !ok {
uig = &UIGroups{Labels: ag.labels} alertGroup = &AlertGroup{Labels: ag.labels}
seen[ag.labels.Fingerprint()] = uig seen[ag.labels.Fingerprint()] = alertGroup
groups = append(groups, uig) overview = append(overview, alertGroup)
} }
now := time.Now() now := time.Now()
var uiAlerts []*UIAlert var apiAlerts []*APIAlert
for _, a := range types.Alerts(alerts...) { for _, a := range types.Alerts(alerts...) {
if a.EndsAt.Before(now) { if !a.EndsAt.IsZero() && a.EndsAt.Before(now) {
continue continue
} }
sid, _ := d.marker.Silenced(a.Fingerprint()) sid, _ := d.marker.Silenced(a.Fingerprint())
uiAlerts = append(uiAlerts, &UIAlert{ apiAlerts = append(apiAlerts, &APIAlert{
Alert: a, Alert: a,
Inhibited: d.marker.Inhibited(a.Fingerprint()), Inhibited: d.marker.Inhibited(a.Fingerprint()),
Silenced: sid, Silenced: sid,
}) })
} }
uig.Groups = append(uig.Groups, &UIGroup{ alertGroup.Blocks = append(alertGroup.Blocks, &AlertBlock{
RouteOpts: &route.RouteOpts, RouteOpts: &route.RouteOpts,
Alerts: uiAlerts, Alerts: apiAlerts,
}) })
} }
} }
return groups sort.Sort(overview)
return overview
} }
func (d *Dispatcher) run(it provider.AlertIterator) { func (d *Dispatcher) run(it provider.AlertIterator) {

View File

@ -69,7 +69,7 @@ func main() {
) )
defer disp.Stop() defer disp.Stop()
api := NewAPI(alerts, silences, func() []*UIGroups { api := NewAPI(alerts, silences, func() AlertOverview {
return disp.Groups() return disp.Groups()
}) })

View File

@ -174,9 +174,9 @@ angular.module('am.controllers').controller('AlertsCtrl',
$scope.notEmpty = function(group) { $scope.notEmpty = function(group) {
var l = 0; var l = 0;
angular.forEach(group.groups, function(g) { angular.forEach(group.blocks, function(blk) {
if ($scope.receiver.indexOf(g.routeOpts.receiver) >= 0) { if ($scope.receivers.indexOf(blk.routeOpts.receiver) >= 0) {
l += g.alerts.length; l += blk.alerts.length || 0;
} }
}); });
@ -190,9 +190,9 @@ angular.module('am.controllers').controller('AlertsCtrl',
$scope.allReceivers = []; $scope.allReceivers = [];
angular.forEach($scope.groups, function(group) { angular.forEach($scope.groups, function(group) {
angular.forEach(group.groups, function(g) { angular.forEach(group.blocks, function(blk) {
if ($scope.allReceivers.indexOf(g.routeOpts.receiver) < 0) { if ($scope.allReceivers.indexOf(blk.routeOpts.receiver) < 0) {
$scope.allReceivers.push(g.routeOpts.receiver); $scope.allReceivers.push(blk.routeOpts.receiver);
} }
}) })
}); });

View File

@ -13,9 +13,9 @@
</span> </span>
</div> </div>
<div ng-repeat="g in group.groups"> <div ng-repeat="blk in group.blocks">
<div ng-show="receivers.indexOf(g.routeOpts.receiver) >= 0" ng-show"g.alerts"> <div ng-show="receivers.indexOf(blk.routeOpts.receiver) >= 0" ng-show"blk.alerts">
<div ng-repeat="a in g.alerts"> <div ng-repeat="a in blk.alerts">
<alert class="list-item" alert="a" group="group.labels"></alert> <alert class="list-item" alert="a" group="group.labels"></alert>
</div> </div>
</div> </div>