Files
routewatch/internal/routetable/originchanges.go
T
clawbot e58c6635e7
check / check (push) Successful in 1m3s
Record origin changes and show them on the prefix page (closes #161)
The routing table records a row each time a prefix and origin AS gets its first peer (announced) or loses its last (withdrawn), with the time and how many peers carry the prefix just after. The prefix handler writes the rows to a new origin_changes table, apart from routes. Every hour the database maintainer keeps each prefix's rows from the last 72 hours or its newest 500, whichever keeps more. The prefix page lists them newest first, at most 5,000.

At most 100,000 rows wait in memory to be written; past that the oldest are dropped and logged. A stop loses the rows past its last write.

Model: deepseek-v4-pro (implementation); opus-5-5 (commit message)
2026-11-05 06:57:41 +01:00

104 lines
3.0 KiB
Go

package routetable
import (
"net/netip"
"slices"
"time"
)
// MaxWaitingOriginChanges is the most origin changes the table keeps waiting
// for the prefix handler to write. The oldest change past it is dropped and
// counted.
const MaxWaitingOriginChanges = 100000
// OriginChange is one change of a prefix and origin AS pair: the origin AS
// gained its first peer, or lost its last.
type OriginChange struct {
Prefix netip.Prefix
OriginASN int
// Announced says the origin AS gained its first peer. When false, it
// lost its last peer.
Announced bool
// PeerCount is how many peers carry the prefix just after the change,
// counting every origin AS.
PeerCount int
// Time is when RouteWatch applied the change.
Time time.Time
}
// recordOriginChange records that the pair of prefix and asn gained its first
// peer, when announced, or lost its last, when not. The caller holds the lock.
// peerCount is how many peers carry the prefix just after the change.
func (t *Table) recordOriginChange(
prefix netip.Prefix, asn int, announced bool, peerCount int,
) {
change := OriginChange{
Prefix: prefix,
OriginASN: asn,
Announced: announced,
PeerCount: peerCount,
Time: t.now(),
}
t.originChanges = append(t.originChanges, change)
t.trimOriginChanges()
}
// TakeOriginChanges returns at most limit of the origin changes that wait to
// be written, oldest first, and how many were dropped since the last call. It
// then forgets what it returned.
func (t *Table) TakeOriginChanges(limit int) ([]OriginChange, int) {
t.mu.Lock()
defer t.mu.Unlock()
n := min(limit, len(t.originChanges))
changes := slices.Clone(t.originChanges[:n])
t.originChanges = t.originChanges[n:]
dropped := t.originChangesDropped
t.originChangesDropped = 0
return changes, dropped
}
// PutBackOriginChanges records again what one TakeOriginChanges call returned,
// for a caller that could not write it. The next TakeOriginChanges returns
// those changes first, before the ones that arrived since. The oldest changes
// past the cap are dropped and counted.
func (t *Table) PutBackOriginChanges(changes []OriginChange) {
t.mu.Lock()
defer t.mu.Unlock()
t.originChanges = append(changes, t.originChanges...)
t.trimOriginChanges()
}
// trimOriginChanges drops the oldest origin changes past the cap and counts
// them. The caller holds the lock.
func (t *Table) trimOriginChanges() {
if extra := len(t.originChanges) - MaxWaitingOriginChanges; extra > 0 {
t.originChangesDropped += extra
t.originChanges = t.originChanges[extra:]
}
}
// OriginChangeCount returns how many origin changes wait for TakeOriginChanges.
func (t *Table) OriginChangeCount() int {
t.mu.RLock()
defer t.mu.RUnlock()
return len(t.originChanges)
}
// prefixPeerCount returns how many peers carry prefix, counting every origin
// AS. The caller holds the lock.
func (t *Table) prefixPeerCount(prefix netip.Prefix) int {
count := 0
for _, o := range t.origins(prefix) {
count += int(o.peers.Count())
}
return count
}