check / check (push) Waiting to run
The status page showed route churn per second as a one-minute moving average, so a quiet feed gave a fraction such as 0.1. The average was correct. The metrics tracker now also counts prefix announcements and withdrawals, from all peers, in each clock minute. /api/v1/stats reports the count for the last full minute as route_churn_per_min in stream, in place of route_churn_per_sec. The status page shows it as "Route Churn/min". The status and home pages round messages and updates per second to whole numbers. Model: opus-5-5
87 lines
2.0 KiB
Go
87 lines
2.0 KiB
Go
package metrics
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestChurnPerMinuteCountsLastFullMinute announces and withdraws prefixes on
|
|
// an injected clock and expects each minute's whole count once that minute
|
|
// has ended, and 0 for a minute with none.
|
|
func TestChurnPerMinuteCountsLastFullMinute(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := time.Date(2026, 10, 26, 12, 0, 30, 0, time.UTC)
|
|
tracker := New()
|
|
tracker.churnPerMinute.now = func() time.Time { return now }
|
|
|
|
expectChurn := func(want int, why string) {
|
|
t.Helper()
|
|
|
|
got := tracker.GetChurnPerMinute()
|
|
if got != want {
|
|
t.Fatalf("at %s churn = %d, want %d: %s",
|
|
now.Format(time.TimeOnly), got, want, why)
|
|
}
|
|
}
|
|
|
|
const firstMinuteChurn = 2
|
|
|
|
tracker.RecordAnnouncement()
|
|
tracker.RecordWithdrawal()
|
|
expectChurn(0, "12:00 has not ended")
|
|
|
|
now = time.Date(2026, 10, 26, 12, 1, 59, 0, time.UTC)
|
|
|
|
tracker.RecordAnnouncement()
|
|
expectChurn(firstMinuteChurn, "12:00 had one announcement and one withdrawal")
|
|
|
|
now = time.Date(2026, 10, 26, 12, 2, 0, 0, time.UTC)
|
|
|
|
expectChurn(1, "12:01 had one announcement")
|
|
|
|
now = time.Date(2026, 10, 26, 12, 4, 0, 0, time.UTC)
|
|
|
|
expectChurn(0, "12:03 had none")
|
|
|
|
tracker.RecordWithdrawal()
|
|
|
|
now = time.Date(2026, 10, 26, 12, 6, 0, 0, time.UTC)
|
|
|
|
expectChurn(0, "12:05 had none, though 12:04 had one withdrawal")
|
|
}
|
|
|
|
// TestChurnPerMinuteCountsConcurrentRecords announces prefixes from several
|
|
// goroutines at once and expects every one counted.
|
|
func TestChurnPerMinuteCountsConcurrentRecords(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
goroutines = 8
|
|
recordsPerGoroutine = 1000
|
|
)
|
|
|
|
now := time.Date(2026, 10, 26, 12, 0, 0, 0, time.UTC)
|
|
tracker := New()
|
|
tracker.churnPerMinute.now = func() time.Time { return now }
|
|
|
|
var wg sync.WaitGroup
|
|
for range goroutines {
|
|
wg.Go(func() {
|
|
for range recordsPerGoroutine {
|
|
tracker.RecordAnnouncement()
|
|
}
|
|
})
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
now = now.Add(time.Minute)
|
|
|
|
want := goroutines * recordsPerGoroutine
|
|
if got := tracker.GetChurnPerMinute(); got != want {
|
|
t.Fatalf("churn = %d, want %d", got, want)
|
|
}
|
|
}
|