checker: keep 1 report per hour after the first day
Some checks are pending
continuous-integration/drone/push Build is running
Some checks are pending
continuous-integration/drone/push Build is running
Insert an hourly tier between the full-detail window and the daily bucket so users still get sub-day resolution for the first week: 0..1 day -> all 1..7 days -> 1 per hour 7..30 -> 2 per day ...
This commit is contained in:
parent
0ae9299f6a
commit
b416cb5688
2 changed files with 46 additions and 8 deletions
|
|
@ -37,7 +37,8 @@ import (
|
|||
//
|
||||
// age window | kept
|
||||
// ------------------------- | ------------------------------------------
|
||||
// 0 .. 7 days | every execution
|
||||
// 0 .. 1 day | every execution
|
||||
// 1 .. 7 days | up to 1 execution per hour per (checker,target)
|
||||
// 7 .. 30 days | up to 2 executions per day per (checker,target)
|
||||
// 30 .. D/2 days | up to 1 execution per week per (checker,target)
|
||||
// D/2 .. D days | up to 1 execution per month per (checker,target)
|
||||
|
|
@ -52,7 +53,11 @@ type RetentionPolicy struct {
|
|||
|
||||
// FullDetailDays: every execution kept under this age.
|
||||
FullDetailDays int
|
||||
// DailyBucketDays: between FullDetailDays and DailyBucketDays, keep
|
||||
// HourlyBucketDays: between FullDetailDays and HourlyBucketDays, keep
|
||||
// PerHourKept executions per UTC hour per (checker,target).
|
||||
HourlyBucketDays int
|
||||
PerHourKept int
|
||||
// DailyBucketDays: between HourlyBucketDays and DailyBucketDays, keep
|
||||
// PerDayKept executions per UTC day per (checker,target).
|
||||
DailyBucketDays int
|
||||
PerDayKept int
|
||||
|
|
@ -73,7 +78,9 @@ func DefaultRetentionPolicy(retentionDays int) RetentionPolicy {
|
|||
}
|
||||
return RetentionPolicy{
|
||||
RetentionDays: retentionDays,
|
||||
FullDetailDays: 7,
|
||||
FullDetailDays: 1,
|
||||
HourlyBucketDays: 7,
|
||||
PerHourKept: 1,
|
||||
DailyBucketDays: 30,
|
||||
PerDayKept: 2,
|
||||
WeeklyBucketDays: max(retentionDays/2, 31),
|
||||
|
|
@ -104,6 +111,7 @@ func (p RetentionPolicy) Decide(executions []*happydns.Execution, now time.Time)
|
|||
|
||||
hardCutoff := now.AddDate(0, 0, -p.RetentionDays)
|
||||
fullCutoff := now.AddDate(0, 0, -p.FullDetailDays)
|
||||
hourlyCutoff := now.AddDate(0, 0, -p.HourlyBucketDays)
|
||||
dailyCutoff := now.AddDate(0, 0, -p.DailyBucketDays)
|
||||
weeklyCutoff := now.AddDate(0, 0, -p.WeeklyBucketDays)
|
||||
|
||||
|
|
@ -113,6 +121,7 @@ func (p RetentionPolicy) Decide(executions []*happydns.Execution, now time.Time)
|
|||
return group[i].StartedAt.After(group[j].StartedAt)
|
||||
})
|
||||
|
||||
hourBuckets := map[string]int{}
|
||||
dayBuckets := map[string]int{}
|
||||
weekBuckets := map[string]int{}
|
||||
monthBuckets := map[string]int{}
|
||||
|
|
@ -125,6 +134,14 @@ func (p RetentionPolicy) Decide(executions []*happydns.Execution, now time.Time)
|
|||
case !t.Before(fullCutoff):
|
||||
// 0 .. FullDetailDays — keep everything.
|
||||
keep = append(keep, e.Id)
|
||||
case !t.Before(hourlyCutoff):
|
||||
k := t.UTC().Format("2006-01-02T15")
|
||||
if hourBuckets[k] < p.PerHourKept {
|
||||
hourBuckets[k]++
|
||||
keep = append(keep, e.Id)
|
||||
} else {
|
||||
drop = append(drop, e.Id)
|
||||
}
|
||||
case !t.Before(dailyCutoff):
|
||||
k := t.UTC().Format("2006-01-02")
|
||||
if dayBuckets[k] < p.PerDayKept {
|
||||
|
|
|
|||
|
|
@ -50,17 +50,38 @@ func TestDecide_FullDetailWindow(t *testing.T) {
|
|||
now := time.Date(2026, 4, 8, 12, 0, 0, 0, time.UTC)
|
||||
p := DefaultRetentionPolicy(365)
|
||||
|
||||
// 20 executions in the first 20 minutes — all inside 0..1 day window.
|
||||
var execs []*happydns.Execution
|
||||
for i := 0; i < 50; i++ {
|
||||
execs = append(execs, mkExec(fmt.Sprintf("e%d", i), time.Duration(i)*time.Hour, now))
|
||||
for i := 0; i < 20; i++ {
|
||||
execs = append(execs, mkExec(fmt.Sprintf("e%d", i), time.Duration(i)*time.Minute, now))
|
||||
}
|
||||
|
||||
keep, drop := p.Decide(execs, now)
|
||||
if len(drop) != 0 {
|
||||
t.Fatalf("expected no drops in <7d window, got %d", len(drop))
|
||||
t.Fatalf("expected no drops in <1d window, got %d", len(drop))
|
||||
}
|
||||
if len(keep) != 50 {
|
||||
t.Fatalf("expected 50 keeps, got %d", len(keep))
|
||||
if len(keep) != 20 {
|
||||
t.Fatalf("expected 20 keeps, got %d", len(keep))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecide_HourlyBucket(t *testing.T) {
|
||||
now := time.Date(2026, 4, 8, 12, 0, 0, 0, time.UTC)
|
||||
p := DefaultRetentionPolicy(365)
|
||||
|
||||
// 6 executions in the same hour ~3 days ago (inside hourly window).
|
||||
var execs []*happydns.Execution
|
||||
base := 3*24*time.Hour + 30*time.Minute
|
||||
for i := 0; i < 6; i++ {
|
||||
execs = append(execs, mkExec(fmt.Sprintf("e%d", i), base+time.Duration(i)*time.Minute, now))
|
||||
}
|
||||
|
||||
keep, drop := p.Decide(execs, now)
|
||||
if len(keep) != p.PerHourKept {
|
||||
t.Fatalf("expected %d keeps in hourly bucket, got %d", p.PerHourKept, len(keep))
|
||||
}
|
||||
if len(drop) != 6-p.PerHourKept {
|
||||
t.Fatalf("expected %d drops, got %d", 6-p.PerHourKept, len(drop))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue