1 Commits
Author SHA1 Message Date
clawbot f021cbd7c6 Show route churn as a whole count per minute (closes #160)
check / check (push) Canceled after 0s
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 the prefixes announced or withdrawn
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
2026-10-26 22:51:45 +00:00
5 changed files with 12 additions and 18 deletions
+2 -2
View File
@@ -82,8 +82,8 @@ Every route answers `HEAD` as it answers `GET`, without the body.
- `GET /api/v1/stats` - The `/status.json` figures plus connection, garbage - `GET /api/v1/stats` - The `/status.json` figures plus connection, garbage
collector, stream and handler queue statistics. `stream.route_churn_per_min` collector, stream and handler queue statistics. `stream.route_churn_per_min`
is the number of prefix announcements and withdrawals, from all peers, in the counts the prefixes announced or withdrawn in the last full minute of the
last full clock minute. clock.
- `GET /api/v1/ip/{ip}` - Looks up each address: its most specific live route - `GET /api/v1/ip/{ip}` - Looks up each address: its most specific live route
(`netblock`), that route's origin AS with its WHOIS details, and the address's (`netblock`), that route's origin AS with its WHOIS details, and the address's
PTR names. `{ip}` is an IP address, a host name, or a comma-separated list of PTR names. `{ip}` is an IP address, a host name, or a comma-separated list of
+5 -5
View File
@@ -33,11 +33,11 @@ tested.
# Completed Steps # Completed Steps
- 2026-10-26: the status page shows "Route Churn/min", the number of prefix - 2026-10-26: the status page shows "Route Churn/min", the whole number of
announcements and withdrawals, from all peers, in the last full clock minute. prefixes announced or withdrawn in the last full minute. `/api/v1/stats`
`/api/v1/stats` reports it as `route_churn_per_min` in `stream`, which reports it as `route_churn_per_min` in `stream`, which replaces the moving
replaces the moving average `route_churn_per_sec`. The messages and updates average `route_churn_per_sec`. The messages and updates per second on `/` and
per second on `/` and `/status` are whole numbers (closes #160) `/status` are whole numbers (closes #160)
- 2026-10-26: the WHOIS fetcher backs off more slowly. A failure makes its wait - 2026-10-26: the WHOIS fetcher backs off more slowly. A failure makes its wait
1.5 times as long, up to one minute instead of five. Its queries to RIPE, 1.5 times as long, up to one minute instead of five. Its queries to RIPE,
APNIC and AFRINIC leave out person and role objects, which RIPE counts against APNIC and AFRINIC leave out person and role objects, which RIPE counts against
+3 -3
View File
@@ -34,7 +34,7 @@ type Tracker struct {
// Announcement/withdrawal metrics // Announcement/withdrawal metrics
announcementCounter metrics.Counter announcementCounter metrics.Counter
withdrawalCounter metrics.Counter withdrawalCounter metrics.Counter
churnPerMinute minuteCounter // prefix announcements and withdrawals churnPerMinute minuteCounter // announced and withdrawn prefixes
// BGP peer tracking // BGP peer tracking
bgpPeerCount atomic.Int32 bgpPeerCount atomic.Int32
@@ -198,8 +198,8 @@ func (t *Tracker) GetWithdrawalCount() uint64 {
return uint64(count) return uint64(count)
} }
// GetChurnPerMinute returns the number of prefix announcements and // GetChurnPerMinute returns the number of prefixes announced or withdrawn in
// withdrawals, from all peers, in the last full clock minute. // the last full minute of the clock.
func (t *Tracker) GetChurnPerMinute() int { func (t *Tracker) GetChurnPerMinute() int {
return t.churnPerMinute.lastFullMinute() return t.churnPerMinute.lastFullMinute()
} }
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"time" "time"
) )
// minuteCounter counts events in each clock minute and reports the count of // minuteCounter counts events in each minute of the clock and reports the
// the last full minute. It is safe for concurrent use. // count of the last full minute. It is safe for concurrent use.
type minuteCounter struct { type minuteCounter struct {
mu sync.Mutex mu sync.Mutex
now func() time.Time now func() time.Time
@@ -44,12 +44,6 @@ func TestChurnPerMinuteCountsLastFullMinute(t *testing.T) {
now = time.Date(2026, 10, 26, 12, 4, 0, 0, time.UTC) now = time.Date(2026, 10, 26, 12, 4, 0, 0, time.UTC)
expectChurn(0, "12:03 had none") 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 // TestChurnPerMinuteCountsConcurrentRecords announces prefixes from several