diff --git a/go.mod b/go.mod index 7a285b80..19c80214 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/prometheus/alertmanager -go 1.21 +go 1.21.8 + +toolchain go1.22.4 require ( github.com/KimMachineGun/automemlimit v0.6.1 @@ -10,6 +12,7 @@ require ( github.com/benbjohnson/clock v1.3.5 github.com/cenkalti/backoff/v4 v4.3.0 github.com/cespare/xxhash/v2 v2.3.0 + github.com/coder/quartz v0.1.0 github.com/go-kit/log v0.2.1 github.com/go-openapi/analysis v0.23.0 github.com/go-openapi/errors v0.22.0 diff --git a/go.sum b/go.sum index d3504dd2..659e5885 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coder/quartz v0.1.0 h1:cLL+0g5l7xTf6ordRnUMMiZtRE8Sq5LxpghS63vEXrQ= +github.com/coder/quartz v0.1.0/go.mod h1:vsiCc+AHViMKH2CQpGIpFgdHIEQsxwm8yCscqKmzbRA= github.com/containerd/cgroups/v3 v3.0.1 h1:4hfGvu8rfGIwVIDd+nLzn/B9ZXx4BcCjzt5ToenJRaE= github.com/containerd/cgroups/v3 v3.0.1/go.mod h1:/vtwk1VXrtoa5AaZLkypuOJgA/6DyPMZHJPGQNtlHnw= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/silence/silence.go b/silence/silence.go index 261904cd..829d46bb 100644 --- a/silence/silence.go +++ b/silence/silence.go @@ -28,7 +28,7 @@ import ( "sync" "time" - "github.com/benbjohnson/clock" + "github.com/coder/quartz" "github.com/go-kit/log" "github.com/go-kit/log/level" uuid "github.com/gofrs/uuid" @@ -188,7 +188,7 @@ func (s *Silencer) Mutes(lset model.LabelSet) bool { // Silences holds a silence state that can be modified, queried, and snapshot. type Silences struct { - clock clock.Clock + clock quartz.Clock logger log.Logger metrics *metrics @@ -350,7 +350,7 @@ func New(o Options) (*Silences, error) { } s := &Silences{ - clock: clock.New(), + clock: quartz.NewReal(), mc: matcherCache{}, logger: log.NewNopLogger(), retention: o.Retention, @@ -397,7 +397,7 @@ func (s *Silences) Maintenance(interval time.Duration, snapf string, stopc <-cha level.Error(s.logger).Log("msg", "interval or stop signal are missing - not running maintenance") return } - t := s.clock.Ticker(interval) + t := s.clock.NewTicker(interval) defer t.Stop() var doMaintenance MaintenanceFunc diff --git a/silence/silence_bench_test.go b/silence/silence_bench_test.go index e1d39a94..189ed325 100644 --- a/silence/silence_bench_test.go +++ b/silence/silence_bench_test.go @@ -18,7 +18,7 @@ import ( "testing" "time" - "github.com/benbjohnson/clock" + "github.com/coder/quartz" "github.com/go-kit/log" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" @@ -52,7 +52,7 @@ func benchmarkMutes(b *testing.B, n int) { silences, err := New(Options{}) require.NoError(b, err) - clock := clock.NewMock() + clock := quartz.NewMock(b) silences.clock = clock now := clock.Now() @@ -108,7 +108,7 @@ func benchmarkQuery(b *testing.B, numSilences int) { s, err := New(Options{}) require.NoError(b, err) - clock := clock.NewMock() + clock := quartz.NewMock(b) s.clock = clock now := clock.Now() diff --git a/silence/silence_test.go b/silence/silence_test.go index b956093c..cb14ff45 100644 --- a/silence/silence_test.go +++ b/silence/silence_test.go @@ -24,7 +24,7 @@ import ( "testing" "time" - "github.com/benbjohnson/clock" + "github.com/coder/quartz" "github.com/go-kit/log" "github.com/matttproud/golang_protobuf_extensions/pbutil" "github.com/prometheus/client_golang/prometheus" @@ -88,7 +88,7 @@ func TestSilenceGCOverTime(t *testing.T) { t.Run("GC does not remove active silences", func(t *testing.T) { s, err := New(Options{}) require.NoError(t, err) - s.clock = clock.NewMock() + s.clock = quartz.NewMock(t) now := s.nowUTC() s.st = state{ "1": &pb.MeshSilence{Silence: &pb.Silence{Id: "1"}, ExpiresAt: now}, @@ -107,7 +107,7 @@ func TestSilenceGCOverTime(t *testing.T) { t.Run("GC does not leak cache entries", func(t *testing.T) { s, err := New(Options{}) require.NoError(t, err) - clock := clock.NewMock() + clock := quartz.NewMock(t) s.clock = clock sil1 := &pb.Silence{ Matchers: []*pb.Matcher{{ @@ -125,7 +125,7 @@ func TestSilenceGCOverTime(t *testing.T) { require.Len(t, s.mc, 1) // Move time forward and both silence and cache entry should be garbage // collected. - clock.Add(time.Minute) + clock.Advance(time.Minute) n, err := s.GC() require.NoError(t, err) require.Equal(t, 1, n) @@ -136,7 +136,7 @@ func TestSilenceGCOverTime(t *testing.T) { t.Run("replacing a silences does not leak cache entries", func(t *testing.T) { s, err := New(Options{}) require.NoError(t, err) - clock := clock.NewMock() + clock := quartz.NewMock(t) s.clock = clock sil1 := &pb.Silence{ Matchers: []*pb.Matcher{{ @@ -166,7 +166,7 @@ func TestSilenceGCOverTime(t *testing.T) { require.Len(t, s.mc, 2) // Move time forward and both silence and cache entry should be garbage // collected. - clock.Add(time.Minute) + clock.Advance(time.Minute) n, err := s.GC() require.NoError(t, err) require.Equal(t, 2, n) @@ -179,7 +179,7 @@ func TestSilenceGCOverTime(t *testing.T) { t.Run("updating a silences does not leak cache entries", func(t *testing.T) { s, err := New(Options{}) require.NoError(t, err) - clock := clock.NewMock() + clock := quartz.NewMock(t) s.clock = clock sil1 := &pb.Silence{ Id: "1", @@ -206,7 +206,7 @@ func TestSilenceGCOverTime(t *testing.T) { require.Len(t, s.mc, 1) // Move time forward and both silence and cache entry should be garbage // collected. - clock.Add(time.Minute) + clock.Advance(time.Minute) n, err := s.GC() require.NoError(t, err) require.Equal(t, 1, n) @@ -217,7 +217,7 @@ func TestSilenceGCOverTime(t *testing.T) { func TestSilencesSnapshot(t *testing.T) { // Check whether storing and loading the snapshot is symmetric. - now := clock.NewMock().Now().UTC() + now := quartz.NewMock(t).Now().UTC() cases := []struct { entries []*pb.MeshSilence @@ -297,7 +297,7 @@ func TestSilencesSnapshot(t *testing.T) { func TestSilences_Maintenance_DefaultMaintenanceFuncDoesntCrash(t *testing.T) { f, err := os.CreateTemp("", "snapshot") require.NoError(t, err, "creating temp file failed") - clock := clock.NewMock() + clock := quartz.NewMock(t) s := &Silences{st: state{}, logger: log.NewNopLogger(), clock: clock, metrics: newMetrics(nil, nil)} stopc := make(chan struct{}) @@ -308,7 +308,7 @@ func TestSilences_Maintenance_DefaultMaintenanceFuncDoesntCrash(t *testing.T) { }() runtime.Gosched() - clock.Add(100 * time.Millisecond) + clock.Advance(100 * time.Millisecond) close(stopc) <-done @@ -317,7 +317,7 @@ func TestSilences_Maintenance_DefaultMaintenanceFuncDoesntCrash(t *testing.T) { func TestSilences_Maintenance_SupportsCustomCallback(t *testing.T) { f, err := os.CreateTemp("", "snapshot") require.NoError(t, err, "creating temp file failed") - clock := clock.NewMock() + clock := quartz.NewMock(t) reg := prometheus.NewRegistry() s := &Silences{st: state{}, logger: log.NewNopLogger(), clock: clock} s.metrics = newMetrics(reg, s) @@ -337,12 +337,12 @@ func TestSilences_Maintenance_SupportsCustomCallback(t *testing.T) { gosched() // Before the first tick, no maintenance executed. - clock.Add(9 * time.Second) + clock.Advance(9 * time.Second) require.EqualValues(t, 0, calls.Load()) // Tick once. - clock.Add(1 * time.Second) - require.EqualValues(t, 1, calls.Load()) + clock.Advance(1 * time.Second) + require.Eventually(t, func() bool { return calls.Load() == 1 }, 5*time.Second, time.Second) // Stop the maintenance loop. We should get exactly one more execution of the maintenance func. close(stopc) @@ -367,7 +367,7 @@ func TestSilencesSetSilence(t *testing.T) { }) require.NoError(t, err) - clock := clock.NewMock() + clock := quartz.NewMock(t) s.clock = clock nowpb := s.nowUTC() @@ -418,7 +418,7 @@ func TestSilenceSet(t *testing.T) { }) require.NoError(t, err) - clock := clock.NewMock() + clock := quartz.NewMock(t) s.clock = clock start1 := s.nowUTC() @@ -446,7 +446,7 @@ func TestSilenceSet(t *testing.T) { require.Equal(t, want, s.st, "unexpected state after silence creation") // Insert silence with unset start time. Must be set to now. - clock.Add(time.Minute) + clock.Advance(time.Minute) start2 := s.nowUTC() sil2 := &pb.Silence{ @@ -486,7 +486,7 @@ func TestSilenceSet(t *testing.T) { // Extend sil4 to expire at a later time. This should not expire the // existing silence, and so should also keep the same ID. - clock.Add(time.Minute) + clock.Advance(time.Minute) start5 := s.nowUTC() sil5 := cloneSilence(sil4) sil5.EndsAt = start5.Add(100 * time.Minute) @@ -513,7 +513,7 @@ func TestSilenceSet(t *testing.T) { // will expire the existing silence and create a new silence. The new // silence is expected to have a different ID to preserve the history of // the previous silence. - clock.Add(time.Minute) + clock.Advance(time.Minute) start6 := s.nowUTC() sil6 := cloneSilence(sil5) @@ -550,7 +550,7 @@ func TestSilenceSet(t *testing.T) { // Re-create the silence that we just replaced. Changing the start time, // just like changing the matchers, creates a new silence with a different // ID. This is again to preserve the history of the original silence. - clock.Add(time.Minute) + clock.Advance(time.Minute) start7 := s.nowUTC() sil7 := cloneSilence(sil5) sil7.StartsAt = start1 @@ -577,13 +577,13 @@ func TestSilenceSet(t *testing.T) { // Updating an existing silence with an invalid silence should not expire // the original silence. - clock.Add(time.Millisecond) + clock.Advance(time.Millisecond) sil8 := cloneSilence(sil7) sil8.EndsAt = time.Time{} require.EqualError(t, s.Set(sil8), "invalid silence: invalid zero end timestamp") // sil7 should not be expired because the update failed. - clock.Add(time.Millisecond) + clock.Advance(time.Millisecond) sil7, err = s.QueryOne(QIDs(sil7.Id)) require.NoError(t, err) require.Equal(t, types.SilenceStateActive, getState(sil7, s.nowUTC())) @@ -733,7 +733,7 @@ func TestSetActiveSilence(t *testing.T) { }) require.NoError(t, err) - clock := clock.NewMock() + clock := quartz.NewMock(t) s.clock = clock now := clock.Now() @@ -757,7 +757,7 @@ func TestSetActiveSilence(t *testing.T) { sil2.StartsAt = newStartsAt sil2.EndsAt = newEndsAt - clock.Add(time.Minute) + clock.Advance(time.Minute) now = s.nowUTC() require.NoError(t, s.Set(sil2)) require.Equal(t, sil1.Id, sil2.Id) @@ -781,7 +781,7 @@ func TestSilencesSetFail(t *testing.T) { s, err := New(Options{}) require.NoError(t, err) - clock := clock.NewMock() + clock := quartz.NewMock(t) s.clock = clock cases := []struct { @@ -1146,7 +1146,7 @@ func TestSilenceExpire(t *testing.T) { s, err := New(Options{Retention: time.Hour}) require.NoError(t, err) - clock := clock.NewMock() + clock := quartz.NewMock(t) s.clock = clock now := s.nowUTC() @@ -1200,7 +1200,7 @@ func TestSilenceExpire(t *testing.T) { }, sil) // Let time pass... - clock.Add(time.Second) + clock.Advance(time.Second) count, err = s.CountState(types.SilenceStatePending) require.NoError(t, err) @@ -1243,7 +1243,7 @@ func TestSilenceExpireWithZeroRetention(t *testing.T) { s, err := New(Options{Retention: 0}) require.NoError(t, err) - clock := clock.NewMock() + clock := quartz.NewMock(t) s.clock = clock now := s.nowUTC() @@ -1288,7 +1288,7 @@ func TestSilenceExpireWithZeroRetention(t *testing.T) { // Advance time. The silence state management code uses update time when // merging, and the logic is "first write wins". So we must advance the clock // one tick for updates to take effect. - clock.Add(1 * time.Millisecond) + clock.Advance(1 * time.Millisecond) require.NoError(t, s.Expire("pending")) require.NoError(t, s.Expire("active")) @@ -1297,7 +1297,7 @@ func TestSilenceExpireWithZeroRetention(t *testing.T) { // Advance time again. Despite what the function name says, s.Expire() does // not expire a silence. It sets the silence to EndAt the current time. This // means that the silence is active immediately after calling Expire. - clock.Add(1 * time.Millisecond) + clock.Advance(1 * time.Millisecond) // Verify all silences have expired. count, err = s.CountState(types.SilenceStatePending) @@ -1318,7 +1318,7 @@ func TestSilenceExpireInvalid(t *testing.T) { s, err := New(Options{Retention: time.Hour}) require.NoError(t, err) - clock := clock.NewMock() + clock := quartz.NewMock(t) s.clock = clock now := s.nowUTC() @@ -1340,9 +1340,9 @@ func TestSilenceExpireInvalid(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, count) - clock.Add(time.Millisecond) + clock.Advance(time.Millisecond) require.NoError(t, s.Expire("active")) - clock.Add(time.Millisecond) + clock.Advance(time.Millisecond) // The silence should be expired. count, err = s.CountState(types.SilenceStateActive) @@ -1357,7 +1357,7 @@ func TestSilencer(t *testing.T) { ss, err := New(Options{Retention: time.Hour}) require.NoError(t, err) - clock := clock.NewMock() + clock := quartz.NewMock(t) ss.clock = clock now := ss.nowUTC() @@ -1386,7 +1386,7 @@ func TestSilencer(t *testing.T) { require.True(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert silenced by matching silence") // One hour passes, silence expires. - clock.Add(time.Hour) + clock.Advance(time.Hour) now = ss.nowUTC() require.False(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert not silenced by expired silence") @@ -1403,7 +1403,7 @@ func TestSilencer(t *testing.T) { require.False(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert not silenced by future silence") // Two hours pass, silence becomes active. - clock.Add(2 * time.Hour) + clock.Advance(2 * time.Hour) now = ss.nowUTC() // Exposes issue #2426. @@ -1420,7 +1420,7 @@ func TestSilencer(t *testing.T) { require.True(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert still silenced by activated silence") // Two hours pass, first silence expires, overlapping second silence becomes active. - clock.Add(2 * time.Hour) + clock.Advance(2 * time.Hour) // Another variant of issue #2426 (overlapping silences). require.True(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert silenced by activated second silence")