From fb6970a7e4c9febabf365c7a6fbcc0e4e0028745 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Fri, 12 Apr 2024 13:42:28 +0200 Subject: [PATCH 1/2] Add StatusAt method for Alert struct This commit adds the StatusAt method for the Alert struct. It calls the ResolvedAt method while Status calls the Resolved method. This method will be used in Alertmanager to fix issue prometheus/alertmanager#3351. Signed-off-by: George Robinson --- model/alert.go | 27 ++++++++++++- model/alert_test.go | 95 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 115 insertions(+), 7 deletions(-) diff --git a/model/alert.go b/model/alert.go index 178fdbaf..21dcf867 100644 --- a/model/alert.go +++ b/model/alert.go @@ -75,7 +75,12 @@ func (a *Alert) ResolvedAt(ts time.Time) bool { // Status returns the status of the alert. func (a *Alert) Status() AlertStatus { - if a.Resolved() { + return a.StatusAt(time.Now()) +} + +// StatusAt returns the status of the alert before the given timestamp. +func (a *Alert) StatusAt(ts time.Time) AlertStatus { + if a.ResolvedAt(ts) { return AlertResolved } return AlertFiring @@ -127,6 +132,17 @@ func (as Alerts) HasFiring() bool { return false } +// HasFiringAt returns true iff one of the alerts is not resolved +// at the time ts. +func (as Alerts) HasFiringAt(ts time.Time) bool { + for _, a := range as { + if !a.ResolvedAt(ts) { + return true + } + } + return false +} + // Status returns StatusFiring iff at least one of the alerts is firing. func (as Alerts) Status() AlertStatus { if as.HasFiring() { @@ -134,3 +150,12 @@ func (as Alerts) Status() AlertStatus { } return AlertResolved } + +// StatusAt returns StatusFiring iff at least one of the alerts is firing +// at the time ts. +func (as Alerts) StatusAt(ts time.Time) AlertStatus { + if as.HasFiringAt(ts) { + return AlertFiring + } + return AlertResolved +} diff --git a/model/alert_test.go b/model/alert_test.go index c140b15e..2a8d7bba 100644 --- a/model/alert_test.go +++ b/model/alert_test.go @@ -133,8 +133,8 @@ func TestAlert(t *testing.T) { t.Errorf("expected %s, but got %s", expected, actual) } - actualStatus := string(alert.Status()) - expectedStatus := "firing" + actualStatus := alert.Status() + expectedStatus := AlertStatus("firing") if actualStatus != expectedStatus { t.Errorf("expected alertStatus %s, but got %s", expectedStatus, actualStatus) @@ -150,6 +150,10 @@ func TestAlert(t *testing.T) { EndsAt: ts2, } + if !alert.Resolved() { + t.Error("expected alert to be resolved, but it was not") + } + actual = fmt.Sprint(alert) expected = "[d181d0f][resolved]" @@ -157,12 +161,44 @@ func TestAlert(t *testing.T) { t.Errorf("expected %s, but got %s", expected, actual) } - actualStatus = string(alert.Status()) + actualStatus = alert.Status() expectedStatus = "resolved" if actualStatus != expectedStatus { t.Errorf("expected alertStatus %s, but got %s", expectedStatus, actualStatus) } + + // Verifying that ResolvedAt works for different times + if alert.ResolvedAt(ts1) { + t.Error("unexpected alert was resolved at start time") + } + if alert.ResolvedAt(ts2.Add(-time.Millisecond)) { + t.Error("unexpected alert was resolved before it ended") + } + if !alert.ResolvedAt(ts2) { + t.Error("expected alert to be resolved at end time") + } + if !alert.ResolvedAt(ts2.Add(time.Millisecond)) { + t.Error("expected alert to be resolved after it ended") + } + + // Verifying that StatusAt works for different times + actualStatus = alert.StatusAt(ts1) + if actualStatus != "firing" { + t.Errorf("expected alert to be firing at start time, but got %s", actualStatus) + } + actualStatus = alert.StatusAt(ts1.Add(-time.Millisecond)) + if actualStatus != "firing" { + t.Errorf("expected alert to be firing before it ended, but got %s", actualStatus) + } + actualStatus = alert.StatusAt(ts2) + if actualStatus != "resolved" { + t.Errorf("expected alert to be resolved at end time, but got %s", actualStatus) + } + actualStatus = alert.StatusAt(ts2.Add(time.Millisecond)) + if actualStatus != "resolved" { + t.Errorf("expected alert to be resolved after it ended, but got %s", actualStatus) + } } func TestSortAlerts(t *testing.T) { @@ -228,18 +264,19 @@ func TestSortAlerts(t *testing.T) { } func TestAlertsStatus(t *testing.T) { + ts := time.Now() firingAlerts := Alerts{ { Labels: LabelSet{ "foo": "bar", }, - StartsAt: time.Now(), + StartsAt: ts, }, { Labels: LabelSet{ "bar": "baz", }, - StartsAt: time.Now(), + StartsAt: ts, }, } @@ -250,7 +287,12 @@ func TestAlertsStatus(t *testing.T) { t.Errorf("expected status %s, but got %s", expectedStatus, actualStatus) } - ts := time.Now() + actualStatus = firingAlerts.StatusAt(ts) + if actualStatus != expectedStatus { + t.Errorf("expected status %s, but got %s", expectedStatus, actualStatus) + } + + ts = time.Now() resolvedAlerts := Alerts{ { Labels: LabelSet{ @@ -270,7 +312,48 @@ func TestAlertsStatus(t *testing.T) { actualStatus = resolvedAlerts.Status() expectedStatus = AlertResolved + if actualStatus != expectedStatus { + t.Errorf("expected status %s, but got %s", expectedStatus, actualStatus) + } + + actualStatus = resolvedAlerts.StatusAt(ts) + expectedStatus = AlertResolved + if actualStatus != expectedStatus { + t.Errorf("expected status %s, but got %s", expectedStatus, actualStatus) + } + ts = time.Now() + mixedAlerts := Alerts{ + { + Labels: LabelSet{ + "foo": "bar", + }, + StartsAt: ts.Add(-1 * time.Minute), + EndsAt: ts.Add(5 * time.Minute), + }, + { + Labels: LabelSet{ + "bar": "baz", + }, + StartsAt: ts.Add(-1 * time.Minute), + EndsAt: ts, + }, + } + + actualStatus = mixedAlerts.Status() + expectedStatus = AlertFiring + if actualStatus != expectedStatus { + t.Errorf("expected status %s, but got %s", expectedStatus, actualStatus) + } + + actualStatus = mixedAlerts.StatusAt(ts) + expectedStatus = AlertFiring + if actualStatus != expectedStatus { + t.Errorf("expected status %s, but got %s", expectedStatus, actualStatus) + } + + actualStatus = mixedAlerts.StatusAt(ts.Add(5 * time.Minute)) + expectedStatus = AlertResolved if actualStatus != expectedStatus { t.Errorf("expected status %s, but got %s", expectedStatus, actualStatus) } From 506a12c25e1f71c3102e1287fe072a8aba1149fb Mon Sep 17 00:00:00 2001 From: George Robinson Date: Fri, 12 Apr 2024 14:14:01 +0200 Subject: [PATCH 2/2] Fix comment Signed-off-by: George Robinson --- model/alert.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/alert.go b/model/alert.go index 21dcf867..80d1fe94 100644 --- a/model/alert.go +++ b/model/alert.go @@ -78,7 +78,7 @@ func (a *Alert) Status() AlertStatus { return a.StatusAt(time.Now()) } -// StatusAt returns the status of the alert before the given timestamp. +// StatusAt returns the status of the alert at the given timestamp. func (a *Alert) StatusAt(ts time.Time) AlertStatus { if a.ResolvedAt(ts) { return AlertResolved