export: a kilobyte less a route advertised with all_paths; memory accounted for
ci / check (push) Canceled after 0s
ci / docs (push) Canceled after 0s
ci / interop (push) Canceled after 0s
ci / fuzz (push) Canceled after 0s

A router feeding a collector every path (all_paths) was 4.8 GB resident
with a table that should take a third of that. A heap profile of the
daemon in the Linux VM, with four full tables and such a collector,
showed where it went: the exporter kept, for every route it had
advertised, the route's outgoing form, the parsed attribute list it was
dressed with, through an `Arc<Dressed>` in the per-route record. One
such form lived for every distinct attribute set advertised, about a
kilobyte each, for the life of the session, on top of the exporter's
cache of 262,144 of them. The record needs only to tell whether a route
changed, and the table's own attribute set, which is in memory anyway,
tells it that: the outgoing form is a function of the set, the peer the
route came from and the policy.

This change:

* `Sent` holds an `Arc<AttrSet>` instead of an `Arc<Dressed>`, and a
  route is sent again when its set differs. `Verdict::All` carries both
  while a flush is on.
* `DRESSED_KEPT` goes from 262,144 to 65,536: a kilobyte a set, so 64 MB
  rather than 256 MB a session at most; what a flush is sending holds
  its own.
* The status page's header shows the daemon's memory as jemalloc
  accounts for it (allocated and resident), `/api/status` has the six
  figures (`memory`: allocated, active, metadata, resident, mapped,
  retained), and `bgp_memory_bytes{kind}` exports them. jemalloc is built
  with statistics for this, and `bgp-web` reads them through
  `tikv-jemalloc-ctl`.
* `scripts/profile/full-table.sh` gains `COLLECTOR=1`, a collector
  session over IPv6 that takes both families with ADD-PATH, and `HEAP=1`,
  which builds the daemon with jemalloc's heap profiler (the new
  `heap-profiling` feature of the daemon crate, not for production) and
  turns the profile into a flame graph with jeprof; and it reports
  memory with the table in and again 30 s later. The feeder
  (`crates/bench/examples/feed.rs`) plays the collector with
  `FEED_COLLECTOR=1`.

Measured in the VM, four peers' full tables and a collector sent every
path (4.9 million routes advertised): 1,723 MiB resident before, 1,286
MiB after, with jemalloc's allocated figure from 1,500 MiB to 946 MiB.
Without the collector the same tables take 630 MiB, so the collector's
bookkeeping is now about 50 bytes a route advertised. Of the resident
figure, some 400 MiB was pages jemalloc had freed and not yet returned:
it returns them on allocator activity only, and a router that has its
tables in allocates little. jemalloc is now built with its background
thread on (the `background_threads` feature), which returns them on
time: 1,020 MiB resident 30 s after the load, against 1,286 MiB without
it.

Documentation: `memory` in the HTTP API reference, `bgp_memory_bytes`
in the metrics reference and the monitoring guide, the script's new
settings in the testing guide.

Testing: the ADD-PATH integration tests pass, including the collector
ones; the heap profile and the figures above are from
`PEERS=4 COLLECTOR=1 HEAP=1 scripts/profile/full-table.sh` in the VM.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Fable 5.1 committed 2026-10-09 22:51:11 +02:00
1 parent d7d7d39bb9
commit b0dd357986
17 files changed
+281 -26

No files matched your search

+2
View File
@@ -214,6 +214,8 @@ environment variables that control the run.
| `SCALE_DOWN` | Divides the size of the table | 1 |
| `HOLD_SECS` | How long the sessions stay open after the table is sent | 30 |
| `FREQ` | The sampling frequency in hertz | 1999 |
| `COLLECTOR` | `1` adds a route collector session that is sent every path (`all_paths`) | 0 |
| `HEAP` | `1` profiles memory instead: the daemon is built with jemalloc's heap profiler, and `heap.svg` and `heap-top.txt` show where the allocations are | 0 |
| `OUT` | The directory for the results | `~/profile` |
| `JEMALLOC_CONF` | jemalloc options for the daemon (its `MALLOC_CONF`), for example `thp:always` | none |
@@ -252,6 +252,7 @@ Table 3 lists queries for a monitoring dashboard.
| Established sessions | `sum(bgp_peer_up)`, of `count(bgp_peer_up)` |
| Routes by neighbor | `bgp_peer_routes` |
| Routes advertised to each neighbor | `bgp_peer_advertised_routes` |
| Memory in use and resident | `bgp_memory_bytes{kind="allocated"}`, `bgp_memory_bytes{kind="resident"}` |
| Share of time a session is held up by the neighbor's receive window | `rate(bgp_peer_tcp_receive_window_limited_seconds[5m]) / rate(bgp_peer_tcp_busy_seconds[5m])` |
| Round-trip time to each neighbor | `bgp_peer_tcp_rtt_seconds` |
| Octets queued towards a neighbor | `bgp_peer_tcp_send_queue_bytes` |
+1 -1
View File
@@ -29,7 +29,7 @@ server returns the following error statuses:
|---|---|
| `/` | Redirects to `/statusz`. |
| `/statusz` | Status page. |
| `/api/status` | Router ID, AS number, uptime, table sizes, and whether control communities are enabled (`control_communities`). |
| `/api/status` | Router ID, AS number, uptime, table sizes, memory use (`memory`, as jemalloc accounts for it: `allocated`, `active`, `metadata`, `resident`, `mapped`, `retained` octets), and whether control communities are enabled (`control_communities`). |
| `/api/config` | The configuration that was read at startup. |
| `/api/peers` | Configured neighbors and their sessions. |
| `/api/routes/local` | Originated routes. |
+1
View File
@@ -30,6 +30,7 @@ alerting rules based on these metrics, see
|---|---|---|
| `bgp_peer_routes{peer}` | Gauge | Number of the neighbor's routes in the routing table. |
| `bgp_peer_advertised_routes{peer}` | Gauge | Number of routes advertised to the neighbor and not withdrawn since. |
| `bgp_memory_bytes{kind}` | Gauge | The daemon's memory as jemalloc accounts for it. `kind` is `allocated` (octets in live allocations), `active` (the pages they occupy), `metadata` (the allocator's own), `resident` (active, metadata and freed pages not yet returned to the kernel), `mapped`, or `retained` (address space kept back, which costs nothing physical). |
| `bgp_peer_tcp_rtt_seconds{peer}` | Gauge | Smoothed round-trip time of the session's TCP connection. Also `bgp_peer_tcp_min_rtt_seconds` and `bgp_peer_tcp_rtt_variance_seconds`. Linux only; sampled from the kernel at each scrape, as are all `bgp_peer_tcp_*` metrics. |
| `bgp_peer_tcp_cwnd_segments{peer}` | Gauge | Congestion window, in segments. `bgp_peer_tcp_mss_bytes` is the segment size. |
| `bgp_peer_tcp_send_window_bytes{peer}` | Gauge | The neighbor's receive window: what it lets Ferrix send. Zero while the neighbor is not reading. |