check / check (push) Canceled after 0s
A new package holds every prefix some peer carries. For each prefix and origin AS it keeps the set of peers that carry it, one bit per peer, and the two newest of their routes in full. It applies announcements, withdrawals and peers' sessions going down by the rule confirmed on the issue, and records which pairs changed for the database writes to come. It answers the IP lookup, an AS's prefixes, a prefix's origins, a sample of one mask length, and the counts the stats show. Nothing uses it yet. The peer sets use github.com/bits-and-blooms/bitset. The kept routes' times are held in order in github.com/google/btree, so the oldest and newest need no scan. Model: opus-5-5
155 lines
4.5 KiB
Go
155 lines
4.5 KiB
Go
package routetable_test
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand/v2"
|
|
"net/netip"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.eeqj.de/sneak/routewatch/internal/routetable"
|
|
)
|
|
|
|
// Parameters for BenchmarkApplyMixedBatch.
|
|
const (
|
|
// benchPeers is how many peers there are, about as many as RIS has.
|
|
benchPeers = 1400
|
|
// benchPrefixes is how many IPv4 /24 prefixes the table holds, and
|
|
// benchPeersPerPrefix how many peers, picked at random, carry each.
|
|
benchPrefixes = 100000
|
|
benchPeersPerPrefix = 20
|
|
// benchBatchSize is how many updates the timed batch holds, as many as
|
|
// the prefix handler's largest batch (prefixBatchMaxSize in
|
|
// internal/routewatch).
|
|
benchBatchSize = 250000
|
|
// In the timed batch every benchWithdrawalEvery-th update is a
|
|
// withdrawal, and every benchNewOriginEvery-th announcement has an
|
|
// origin AS other than the prefix's.
|
|
benchWithdrawalEvery = 5
|
|
benchNewOriginEvery = 7
|
|
// benchOrigins is how many origin ASes the prefixes are spread over,
|
|
// from benchFirstASN on: 20 prefixes each, about as many as an origin AS
|
|
// has on average in the full table.
|
|
benchOrigins = 5000
|
|
benchFirstASN = 64500
|
|
// benchUpdatesPerSecond is how many updates share one time.
|
|
benchUpdatesPerSecond = 1000
|
|
benchMaskLength = 24
|
|
)
|
|
|
|
// benchUpdate is one update of the benchmark.
|
|
type benchUpdate struct {
|
|
prefix netip.Prefix
|
|
origin int
|
|
route routetable.Route
|
|
withdraw bool
|
|
}
|
|
|
|
// BenchmarkApplyMixedBatch times one batch of 250,000 updates of routes picked
|
|
// at random: announcements, withdrawals and announcements with a new origin AS.
|
|
// The table it applies them to holds 100,000 IPv4 prefixes from 5,000 origin
|
|
// ASes, each carried by 20 of 1,400 peers. It reports the heap bytes that table
|
|
// takes per prefix and origin AS pair. It is run by hand, not by make test, for
|
|
// example in the test image:
|
|
//
|
|
// go test -run '^$' -bench MixedBatch -benchtime 10x ./internal/routetable/
|
|
func BenchmarkApplyMixedBatch(b *testing.B) {
|
|
//nolint:gosec // The benchmark needs the same updates every run.
|
|
rng := rand.New(rand.NewPCG(1, 2))
|
|
peers := make([]string, benchPeers)
|
|
|
|
for i := range peers {
|
|
peers[i] = fmt.Sprintf("198.18.%d.%d", i/256, i%256)
|
|
}
|
|
|
|
before := heapBytes()
|
|
table := routetable.New(routetable.Params{})
|
|
updates := 0
|
|
|
|
for prefix := range benchPrefixes {
|
|
for range benchPeersPerPrefix {
|
|
peer := peers[rng.IntN(benchPeers)]
|
|
apply(table, benchAnnouncement(rng, prefix, peer, updates, false))
|
|
updates++
|
|
}
|
|
}
|
|
|
|
// The record of changes is handed over every second while routewatch
|
|
// runs, so it is not part of the table's size.
|
|
table.TakeChanges()
|
|
|
|
bytesPerPair := float64(heapBytes()-before) / benchPrefixes
|
|
|
|
batch := make([]benchUpdate, 0, benchBatchSize)
|
|
for i := range benchBatchSize {
|
|
update := benchAnnouncement(rng, rng.IntN(benchPrefixes),
|
|
peers[rng.IntN(benchPeers)], updates+i, i%benchNewOriginEvery == 0)
|
|
update.withdraw = i%benchWithdrawalEvery == 0
|
|
batch = append(batch, update)
|
|
}
|
|
|
|
for b.Loop() {
|
|
for _, update := range batch {
|
|
apply(table, update)
|
|
}
|
|
}
|
|
|
|
// After the loop: b.Loop drops the metrics reported before it starts.
|
|
b.ReportMetric(bytesPerPair, "bytes/pair")
|
|
}
|
|
|
|
// benchAnnouncement returns the announcement of the prefix numbered prefix, a
|
|
// /24 from 1.0.0.0/24 on, by peer. It is the updates-th update of the
|
|
// benchmark, and its AS path holds four ASes from the peer's AS to the origin
|
|
// AS. newOrigin gives it an origin AS other than the prefix's.
|
|
func benchAnnouncement(
|
|
rng *rand.Rand, prefix int, peer string, updates int, newOrigin bool,
|
|
) benchUpdate {
|
|
origin := benchFirstASN + prefix%benchOrigins
|
|
if newOrigin {
|
|
origin += benchOrigins
|
|
}
|
|
|
|
start := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
|
|
|
return benchUpdate{
|
|
prefix: netip.MustParsePrefix(fmt.Sprintf("%d.%d.%d.0/%d",
|
|
1+prefix>>16, (prefix>>8)%256, prefix%256, benchMaskLength)),
|
|
origin: origin,
|
|
route: routetable.Route{
|
|
Peer: peer,
|
|
ASPath: []int{
|
|
benchFirstASN + rng.IntN(benchOrigins),
|
|
benchFirstASN + rng.IntN(benchOrigins),
|
|
benchFirstASN + rng.IntN(benchOrigins),
|
|
origin,
|
|
},
|
|
Time: start.Add(time.Duration(updates/benchUpdatesPerSecond) *
|
|
time.Second),
|
|
},
|
|
}
|
|
}
|
|
|
|
// apply applies update to table.
|
|
func apply(table *routetable.Table, update benchUpdate) {
|
|
if update.withdraw {
|
|
table.Withdraw(update.prefix, update.route.Peer)
|
|
|
|
return
|
|
}
|
|
|
|
table.Announce(update.prefix, update.origin, update.route)
|
|
}
|
|
|
|
// heapBytes returns the bytes of the live heap objects, after a collection.
|
|
func heapBytes() uint64 {
|
|
runtime.GC()
|
|
|
|
var stats runtime.MemStats
|
|
|
|
runtime.ReadMemStats(&stats)
|
|
|
|
return stats.HeapAlloc
|
|
}
|