Guide

Benchmarking

Ze includes le perf, a tool for measuring BGP route propagation latency through a device under test (DUT). It works with any BGP implementation, including Ze.

le perf send runs one benchmark against a DUT, le perf report compares result files, and le perf track follows the history. Every word after the verb reaches the program unchanged, and the program's exit code is le's.

Architecture

                  +-----------+
  le perf send    |           |         le perf send
  (sender)  ----> |    DUT    | ---->  (receiver)
  AS 65001        |  AS 65000 |         AS 65002
                  +-----------+

The sender establishes a BGP session with the DUT, injects routes, and the receiver measures when those routes arrive. Both sessions are managed by le perf send in a single process. Timing starts when the sender writes the first UPDATE and stops when the receiver has collected all expected prefixes.

Quick Start

Run a benchmark against Ze on localhost:

# Start ze with a config that accepts peers from 127.0.0.1 and 127.0.0.2
ze start test-config.conf &

# Run the benchmark
./le perf send --dut-addr 127.0.0.1 --dut-asn 65000 --dut-name ze --routes 1000

With JSON output saved to a file:

./le perf send --dut-addr 127.0.0.1 --dut-asn 65000 --dut-name ze \
  --routes 1000 --output result-ze.json

Running Benchmarks

For a complete flag reference, see le perf send.

Encoding Modes

Three encoding modes measure different code paths through the DUT:

Mode Flag What It Tests
IPv4 inline NLRI --family ipv4/unicast (default) Standard IPv4 unicast path with NLRI at the end of the UPDATE
IPv4 force-MP --family ipv4/unicast --force-mp IPv4 encoded via MP_REACH_NLRI attribute (exercises multiprotocol path)
IPv6 MP --family ipv6/unicast IPv6 unicast via MP_REACH_NLRI (standard for non-IPv4 families)

Multi-Iteration

By default, le perf send executes 5 iterations with 1 warmup run. The warmup run is discarded, and outliers beyond 2 standard deviations from the median convergence time are removed (minimum 3 iterations kept). Final results report median and standard deviation across the kept iterations.

Flag Default Purpose
--repeat 5 Total iterations (including warmup)
--warmup-runs 1 Warmup iterations discarded from results
--iter-delay 3s Pause between iterations for clean separation

More iterations improve statistical confidence. For reliable results, use at least --repeat 10:

./le perf send --dut-addr 172.31.0.2 --dut-asn 65000 --repeat 10 --warmup-runs 2

Timing

Flag Default Purpose
--warmup 2s Delay after session establishment before injecting routes
--connect-timeout 10s TCP connection timeout
--duration 60s Maximum wait time for convergence per iteration
--iter-delay 3s Delay between iterations

The --iter-delay flag controls whether iterations run back-to-back or with a pause. Longer delays give the DUT time to settle between measurements.

Cross-Implementation Comparison

Automated Docker Runner

The included test runner benchmarks all eight supported implementations in Docker:

DUT Image Config Forwarding mechanism
Ze ze-interop (built) test/perf/configs/ze.conf bgp-rs plugin
FRR quay.io/frrouting/frr:10.3.1 test/perf/configs/frr.conf route-map PERMIT
BIRD bird-interop (built) test/perf/configs/bird.conf import/export all
GoBGP gobgp-interop (built) test/perf/configs/gobgp.toml default accept policy
rustbgpd rustbgpd-interop (built from source) test/perf/configs/rustbgpd.toml route_server_client
rustybgp rustybgp-interop (built) test/perf/configs/rustybgp.toml default accept policy
FreeRtr freertr-interop (built) test/perf/configs/freertr-sw.txt eBGP + redistribute connected
OpenBGPD openbgpd-interop (built) test/perf/configs/openbgpd.conf allow from/to any
# Build every DUT image, measure every DUT, and record the run.
./le perf run

# Measure one DUT, or several.
./le perf run dut ze
./le perf run dut "ze bird rustbgpd"

# Run one step: build the images only, or measure with the images that exist.
./le perf run step build
DUT_ROUTES=10000 DUT_REPEAT=5 ./le perf run step test dut ze

# Append the results of the last measurement to the committed NDJSON history.
./le perf history-record

# The release evidence gate: measure ze, append the result, fail on a regression.
./le perf evidence-record

A run with no step keyword builds the images and measures. step build records nothing, because it measured nothing. Any other step value exits 2 and names the value. A run that measured records the commit, so the perf nudge (./le perf suggest) stops asking for one.

The runner builds no host benchmark program. It cross-builds le for linux with CGO_ENABLED=0 into tmp/perf-run/linux-<arch>/le, and prints how long that build took. It mounts the file at /usr/local/bin/le in the sender container, which runs le perf send. The le that started the run renders the report as le perf report. The terminal-demo recorder container runs a linux le built by the same recipe (docs/contributing/gh-pages.md).

Results are written to test/perf/results/ as JSON files. An HTML comparison report is generated automatically.

Manual Reports

After running benchmarks, generate reports from the result files:

# Markdown report (default)
./le perf report result-ze.json result-gobgp.json result-rustbgpd.json

# HTML report
./le perf report --html result-ze.json result-gobgp.json > comparison.html

For the full flag reference, see le perf report.

Tracking Performance Over Time

NDJSON History

Each le perf send --json invocation produces a single JSON object. Append results to an NDJSON (newline-delimited JSON) file to build a history:

./le perf send --dut-addr 127.0.0.1 --dut-asn 65000 --json >> history.ndjson

Trend Reports

Generate a trend report from a history file:

./le perf track history.ndjson
./le perf track --html history.ndjson > trend.html

Regression Detection

Use --check in CI to detect performance regressions. The tool compares the most recent entry against the previous one using stddev-aware thresholds:

./le perf track --check history.ndjson

Exit code 0 means no regression. Exit code 1 means a regression was detected.

Regressions are flagged when a metric exceeds its threshold percentage AND the delta exceeds the combined standard deviation of the two measurements. This prevents false positives from noisy measurements.

Default thresholds:

Metric Default Threshold Direction
Convergence time 20% Higher is worse
Throughput (avg) 20% Lower is worse
P99 latency 30% Higher is worse
Routes lost Any loss Always flagged

Custom thresholds:

./le perf track --check --threshold-convergence 15 --threshold-throughput 10 --threshold-p99 25 history.ndjson

Limit the comparison window to the last N entries with --last:

./le perf track --check --last 5 history.ndjson

For the full flag reference, see le perf track.

Understanding Results

JSON Output Fields

The JSON result object contains these key fields:

Field Unit Description
convergence-ms ms Time from first UPDATE sent to last route received (median)
convergence-stddev-ms ms Standard deviation of convergence across iterations
first-route-ms ms Time to first route arrival
throughput-avg routes/s Average route propagation rate
throughput-avg-stddev routes/s Standard deviation of throughput
throughput-peak routes/s Peak routes/s in any 1-second window
latency-p50-ms ms 50th percentile per-route latency
latency-p90-ms ms 90th percentile per-route latency
latency-p99-ms ms 99th percentile per-route latency
latency-p99-stddev-ms ms Standard deviation of P99 across iterations
latency-max-ms ms Maximum per-route latency
routes-sent count Routes injected by sender
routes-received count Routes received by receiver
routes-lost count Difference between sent and received
repeat count Total iterations run
repeat-kept count Iterations kept after outlier removal

Interpreting Results

Convergence time is the primary metric. It measures how long the DUT takes to forward all injected routes from sender to receiver. Lower is better.

Standard deviation indicates measurement stability. A high stddev relative to the median suggests noisy measurements. Increase --repeat and --iter-delay for more stable results.

Throughput shows sustained forwarding rate. The average is computed over the full convergence window. The peak shows the maximum 1-second burst rate.

Latency percentiles show per-route propagation time distribution. P50 is the typical latency, P99 captures tail latency, and max shows the worst case.

Routes lost should always be zero. Any loss indicates the DUT dropped routes, which the regression checker always flags.