Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ func main() {
log.Fatalf("Error loading configuration from %s: %s", *configFile, err)
}

suppressor := manager.NewSuppressor()
defer suppressor.Close()
silencer := manager.NewSilencer()
defer silencer.Close()

notifier := manager.NewNotifier(conf.NotificationConfig)
defer notifier.Close()
Expand All @@ -48,22 +48,22 @@ func main() {
// REST API Service.
AlertManagerService: &api.AlertManagerService{
Aggregator: aggregator,
Suppressor: suppressor,
Silencer: silencer,
},

// Template-based page handlers.
AlertsHandler: &web.AlertsHandler{
Aggregator: aggregator,
IsInhibitedInterrogator: suppressor,
IsInhibitedInterrogator: silencer,
},
SilencesHandler: &web.SilencesHandler{
Suppressor: suppressor,
Silencer: silencer,
},
}
go webService.ServeForever()

aggregator.SetRules(conf.AggregationRules())

log.Println("Running summary dispatcher...")
notifier.Dispatch(suppressor)
notifier.Dispatch(silencer)
}
212 changes: 212 additions & 0 deletions manager/silencer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// Copyright 2013 Prometheus Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package manager

import (
"encoding/json"
"fmt"
"log"
"sync"
"time"
)

type SilenceId uint
type Silences []*Silence

type Silence struct {
// The numeric ID of the silence.
Id SilenceId
// Name/email of the silence creator.
CreatedBy string
// When the silence was first created (Unix timestamp).
CreatedAt time.Time
// When the silence expires (Unix timestamp).
EndsAt time.Time
// Additional comment about the silence.
Comment string
// Filters that determine which events are silenced.
Filters Filters
// Timer used to trigger the deletion of the Silence after its expiry
// time.
expiryTimer *time.Timer
}

type ApiSilence struct {
CreatedBy string
CreatedAtSeconds int64
EndsAtSeconds int64
Comment string
Filters map[string]string
}

func (s *Silence) MarshalJSON() ([]byte, error) {
filters := map[string]string{}
for _, f := range s.Filters {
name := f.Name.String()[1 : len(f.Name.String())-1]
value := f.Value.String()[1 : len(f.Value.String())-1]
filters[name] = value
}

return json.Marshal(&ApiSilence{
CreatedBy: s.CreatedBy,
CreatedAtSeconds: s.CreatedAt.Unix(),
EndsAtSeconds: s.EndsAt.Unix(),
Comment: s.Comment,
Filters: filters,
})
}

func (s *Silence) UnmarshalJSON(data []byte) error {
sc := &ApiSilence{}
json.Unmarshal(data, sc)

filters := make(Filters, 0, len(sc.Filters))
for label, value := range sc.Filters {
filters = append(filters, NewFilter(label, value))
}

if sc.EndsAtSeconds == 0 {
sc.EndsAtSeconds = time.Now().Add(time.Hour).Unix()
}

*s = Silence{
CreatedBy: sc.CreatedBy,
CreatedAt: time.Now().UTC(),
EndsAt: time.Unix(sc.EndsAtSeconds, 0).UTC(),
Comment: sc.Comment,
Filters: filters,
}
return nil
}

type Silencer struct {
// Silences managed by this Silencer.
Silences map[SilenceId]*Silence
// Used to track the next Silence Id to allocate.
nextId SilenceId

// Mutex to protect the above.
mu sync.Mutex
}

type IsInhibitedInterrogator interface {
IsInhibited(*Event) (bool, *Silence)
}

func NewSilencer() *Silencer {
return &Silencer{
Silences: make(map[SilenceId]*Silence),
}
}

func (s *Silencer) nextSilenceId() SilenceId {
// BUG: Build proper ID management. For now, as we are only keeping
// data in memory anyways, this is enough.
s.nextId++
return s.nextId
}

func (s *Silencer) setupExpiryTimer(sc *Silence) {
if sc.expiryTimer != nil {
sc.expiryTimer.Stop()
}
expDuration := sc.EndsAt.Sub(time.Now())
sc.expiryTimer = time.AfterFunc(expDuration, func() {
if err := s.DelSilence(sc.Id); err != nil {
log.Printf("Failed to delete silence %d: %s", sc.Id, err)
}
})
}

func (s *Silencer) AddSilence(sc *Silence) SilenceId {
s.mu.Lock()
defer s.mu.Unlock()

sc.Id = s.nextSilenceId()
s.setupExpiryTimer(sc)
s.Silences[sc.Id] = sc
return sc.Id
}

func (s *Silencer) UpdateSilence(sc *Silence) error {
s.mu.Lock()
defer s.mu.Unlock()

origSilence, ok := s.Silences[sc.Id]
if !ok {
return fmt.Errorf("Silence with ID %d doesn't exist", sc.Id)
}
if sc.EndsAt != origSilence.EndsAt {
origSilence.expiryTimer.Stop()
}
*origSilence = *sc
s.setupExpiryTimer(origSilence)
return nil
}

func (s *Silencer) GetSilence(id SilenceId) (*Silence, error) {
s.mu.Lock()
defer s.mu.Unlock()

sc, ok := s.Silences[id]
if !ok {
return nil, fmt.Errorf("Silence with ID %d doesn't exist", id)
}
return sc, nil
}

func (s *Silencer) DelSilence(id SilenceId) error {
s.mu.Lock()
defer s.mu.Unlock()

if _, ok := s.Silences[id]; !ok {
return fmt.Errorf("Silence with ID %d doesn't exist", id)
}
delete(s.Silences, id)
return nil
}

func (s *Silencer) SilenceSummary() Silences {
s.mu.Lock()
defer s.mu.Unlock()

silences := make(Silences, 0, len(s.Silences))
for _, sc := range s.Silences {
silences = append(silences, sc)
}
return silences
}

func (s *Silencer) IsInhibited(e *Event) (bool, *Silence) {
s.mu.Lock()
defer s.mu.Unlock()

for _, s := range s.Silences {
if s.Filters.Handles(e) {
return true, s
}
}
return false, nil
}

func (s *Silencer) Close() {
s.mu.Lock()
defer s.mu.Unlock()

for _, sc := range s.Silences {
if sc.expiryTimer != nil {
sc.expiryTimer.Stop()
}
}
}
74 changes: 37 additions & 37 deletions manager/suppressor_test.go → manager/silencer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,70 +18,70 @@ import (
"time"
)

type testSuppressorScenario struct {
suppressions Suppressions
inhibited Events
uninhibited Events
type testSilencerScenario struct {
silences Silences
inhibited Events
uninhibited Events
}

func (sc *testSuppressorScenario) test(i int, t *testing.T) {
s := NewSuppressor()
func (scenario *testSilencerScenario) test(i int, t *testing.T) {
s := NewSilencer()

for j, sup := range sc.suppressions {
id := s.AddSuppression(sup)
retrievedSup, err := s.GetSuppression(id)
for j, sc := range scenario.silences {
id := s.AddSilence(sc)
retrievedSilence, err := s.GetSilence(id)
if err != nil {
t.Fatalf("%d.%d. Error getting suppression: %s", i, j, err)
t.Fatalf("%d.%d. Error getting silence: %s", i, j, err)
}
if retrievedSup.Id != id {
t.Fatalf("%d.%d. Expected ID %d, got %d", i, j, id, retrievedSup.Id)
if retrievedSilence.Id != id {
t.Fatalf("%d.%d. Expected ID %d, got %d", i, j, id, retrievedSilence.Id)
}
sup.Id = id
if sup != retrievedSup {
t.Fatalf("%d.%d. Expected suppression %v, got %v", i, j, sup, retrievedSup)
sc.Id = id
if sc != retrievedSilence {
t.Fatalf("%d.%d. Expected silence %v, got %v", i, j, sc, retrievedSilence)
}
}

for j, ev := range sc.inhibited {
inhibited, sup := s.IsInhibited(ev)
for j, ev := range scenario.inhibited {
inhibited, sc := s.IsInhibited(ev)
if !inhibited {
t.Fatalf("%d.%d. Expected %v to be inhibited", i, j, ev)
}
if sup == nil {
t.Fatalf("%d.%d. Expected non-nil Suppression for inhibited event %v", i, j, ev)
if sc == nil {
t.Fatalf("%d.%d. Expected non-nil Silence for inhibited event %v", i, j, ev)
}
}

for j, ev := range sc.uninhibited {
inhibited, sup := s.IsInhibited(ev)
for j, ev := range scenario.uninhibited {
inhibited, sc := s.IsInhibited(ev)
if inhibited {
t.Fatalf("%d.%d. Expected %v to not be inhibited, was inhibited by %v", i, j, ev, sup)
t.Fatalf("%d.%d. Expected %v to not be inhibited, was inhibited by %v", i, j, ev, sc)
}
}

suppressions := s.SuppressionSummary()
if len(suppressions) != len(sc.suppressions) {
t.Fatalf("%d. Expected %d suppressions, got %d", i, len(sc.suppressions), len(suppressions))
silences := s.SilenceSummary()
if len(silences) != len(scenario.silences) {
t.Fatalf("%d. Expected %d silences, got %d", i, len(scenario.silences), len(silences))
}

for j, sup := range suppressions {
if err := s.DelSuppression(sup.Id); err != nil {
t.Fatalf("%d.%d. Got error while deleting suppression: %s", i, j, err)
for j, sc := range silences {
if err := s.DelSilence(sc.Id); err != nil {
t.Fatalf("%d.%d. Got error while deleting silence: %s", i, j, err)
}

newSuppressions := s.SuppressionSummary()
if len(newSuppressions) != len(suppressions)-j-1 {
t.Fatalf("%d. Expected %d suppressions, got %d", i, len(suppressions), len(newSuppressions))
newSilences := s.SilenceSummary()
if len(newSilences) != len(silences)-j-1 {
t.Fatalf("%d. Expected %d silences, got %d", i, len(silences), len(newSilences))
}
}

s.Close()
}

func TestSuppressor(t *testing.T) {
scenarios := []testSuppressorScenario{
func TestSilencer(t *testing.T) {
scenarios := []testSilencerScenario{
{
// No suppressions, one event.
// No silences, one event.
uninhibited: Events{
&Event{
Labels: map[string]string{
Expand All @@ -92,12 +92,12 @@ func TestSuppressor(t *testing.T) {
},
{
// One rule, two matching events, one non-matching.
suppressions: Suppressions{
&Suppression{
silences: Silences{
&Silence{
Filters: Filters{NewFilter("service", "test(-)?service")},
EndsAt: time.Now().Add(time.Hour),
},
&Suppression{
&Silence{
Filters: Filters{NewFilter("testlabel", ".*")},
EndsAt: time.Now().Add(time.Hour),
},
Expand Down
Loading