Before this change there was no way to see where the daemon's CPU time goes while it receives a full Internet routing table: the benchmarks in crates/bench time the routing table in-process, without the sessions, the policy, the kernel task or the production allocator. Profiling the daemon itself showed that a third of its CPU time during a full-table load went into `Plan::blackholes()`, which walked every installed route to count the blackhole ones each time the kernel task published its status, after every batch of changes; with 1.2 million routes installed that was 3.9 seconds from the first route to a synced kernel instead of 2.0. This change: * Keeps counts of blackhole routes and of routes kept unconfirmed from an earlier run in `Plan`, in step with the map through `Plan::put` and `Plan::take`, so that `Plan::blackholes()` and `Plan::kept()` cost nothing. `kept()` had the same scan; it ran only while routes of an earlier run were being kept, which is exactly during a full-table load after a restart. * Adds `scripts/profile/full-table.sh`, which builds the daemon with the new `profiling` Cargo profile (release with line tables) and frame pointers, runs it in a network namespace joined by a veth pair to a second namespace, feeds it the synthetic table from there as one or more neighbors, samples it with `perf`, and writes a flame graph (inferno), flat profiles by self and inclusive time, the time until the routes are in the table and in the kernel, and the daemon's resident memory. `PEERS`, `KERNEL`, `SCALE_DOWN`, `HOLD_SECS`, `FREQ` and `OUT` control it. From macOS it runs in the Linux VM through scripts/linux-vm.sh. * Adds the load generator `crates/bench/examples/feed.rs`: it connects from the peers' addresses, completes the handshakes, writes each neighbor's table, drains what the daemon sends back and holds the sessions open with KEEPALIVEs. `TestPeer::into_stream` gives it the connection after the handshake. * Updates the testing guide: the current `full_table` numbers, a "Profiling" section, and the daemon's numbers in the VM. Measured in the Linux VM (8 vCPUs) with the synthetic table of 1.23 million prefixes: one neighbor's table is in the routing table after 0.7 s and in the kernel after 2.0 s (before: 3.9 s); four neighbors' 4.9 million routes are in the table after 1.1 s and the kernel is synced after 3.0 s; 520 MB resident with one table, 680 MB with four. Without the kernel one session takes in 2.0 million routes per second on one core. A profile also attributed 14% of the session's time to `Journal::tidy`; inlining its early return changed nothing in an A/B run of the benchmark, so that was a sampling artefact and is left as it is. Testing: a unit test checks the counts against a scan of the entries through adopt, best, reset, synced, release, withdrawal, refusal and clear; the kernel test suite and the workspace tests pass; the script was run with one and four neighbors, with and without the kernel. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
75 lines
2.3 KiB
TOML
75 lines
2.3 KiB
TOML
[workspace]
|
|
default-members = ["bin/bgp"]
|
|
members = ["bin/*", "crates/*"]
|
|
# Fuzz targets need a nightly toolchain; see fuzz/README.md.
|
|
exclude = ["fuzz"]
|
|
resolver = "3"
|
|
|
|
[workspace.package]
|
|
authors = ["Rayhaan Jaufeerally <rayhaan+git@rayhaan.ch>"]
|
|
description = "A Border Gateway Protocol implementation"
|
|
edition = "2024"
|
|
homepage = "https://rayhaan.ch"
|
|
license = "Apache-2.0"
|
|
repository = "https://git.rayhaan.net/rayhaan/ferrix"
|
|
version = "0.2.5"
|
|
|
|
# Every crate opts in with `[lints] workspace = true`. CI turns warnings into
|
|
# errors, so a lint added here is enforced.
|
|
[workspace.lints.rust]
|
|
unsafe_code = "forbid"
|
|
|
|
[workspace.lints.clippy]
|
|
all = { level = "warn", priority = -1 }
|
|
dbg_macro = "warn"
|
|
todo = "warn"
|
|
unimplemented = "warn"
|
|
|
|
[workspace.dependencies]
|
|
# --- Our crates ---
|
|
bgp-fib = { path = "crates/fib" }
|
|
bgp-packet = { path = "crates/packet" }
|
|
bgp-primitives = { path = "crates/primitives" }
|
|
bgp-rib = { path = "crates/rib" }
|
|
bgp-rpki = { path = "crates/rpki" }
|
|
bgp-server = { path = "crates/server" }
|
|
bgp-web = { path = "crates/web" }
|
|
bgp-integration-tests = { path = "crates/integration-tests" }
|
|
bgp-bench = { path = "crates/bench" }
|
|
|
|
# --- General ---
|
|
bitfield = "0.19.0"
|
|
bytes = "1.10.1"
|
|
eyre = "0.6.12"
|
|
nom = "8.0.0"
|
|
serde = { version = "1.0.219", features = ["derive"] }
|
|
nix = { version = "0.31", default-features = false, features = ["net"] }
|
|
socket2 = "0.6"
|
|
serde_json = "1.0.140"
|
|
serde_repr = "0.1.20"
|
|
thiserror = "2.0.12"
|
|
tokio = { version = "1.44.2", features = ["full"] }
|
|
strum = { version = "0.27.1", features = ["derive"] }
|
|
tracing = "0.1.44"
|
|
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
|
prometheus = "0.14.0"
|
|
proptest = "1.7"
|
|
protobuf = { package = "protobuf", version = "3.7.2" }
|
|
protobuf-codegen = { package = "protobuf-codegen", version = "3.7.2" }
|
|
clap = { version = "4.5.53", features = ["derive"] }
|
|
foldhash = "0.2"
|
|
futures = "0.3"
|
|
hashbrown = "0.17"
|
|
axum = "0.8.9"
|
|
tokio-stream = { version = "0.1.19", features = ["sync"] }
|
|
reqwest = { version = "0.13.5", default-features = false, features = ["json"] }
|
|
# The daemon's memory allocator; see bin/bgp/src/main.rs.
|
|
tikv-jemallocator = "0.7.0"
|
|
|
|
# `cargo build --profile profiling`: the release build, with the line tables
|
|
# a profiler needs to name functions. See scripts/profile/.
|
|
[profile.profiling]
|
|
inherits = "release"
|
|
debug = "line-tables-only"
|
|
strip = "none"
|