Skip to content

Commit

Permalink
Add StatusAt method for Alert struct
Browse files Browse the repository at this point in the history
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 <george.robinson@grafana.com>
  • Loading branch information
grobinson-grafana committed Apr 12, 2024
1 parent 0234594 commit fb6970a
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 7 deletions.
27 changes: 26 additions & 1 deletion model/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -127,10 +132,30 @@ 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() {
return AlertFiring
}
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
}
95 changes: 89 additions & 6 deletions model/alert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -150,19 +150,55 @@ 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]"

if actual != expected {
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) {
Expand Down Expand Up @@ -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,
},
}

Expand All @@ -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{
Expand All @@ -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)
}
Expand Down

0 comments on commit fb6970a

Please sign in to comment.