From abae4bda282e329e93cf1bf660147adb3d3c4b4f Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 8 Jul 2026 07:38:54 +0000 Subject: [PATCH] Add netem smoke fixture for benchmark simulation --- test/bench-network/Dockerfile.netem | 8 ++++ test/bench-network/README.md | 24 ++++++++++ test/bench-network/compose.yml | 20 ++++++++ test/bench-network/netem-smoke.sh | 71 +++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+) create mode 100644 test/bench-network/Dockerfile.netem create mode 100644 test/bench-network/netem-smoke.sh diff --git a/test/bench-network/Dockerfile.netem b/test/bench-network/Dockerfile.netem new file mode 100644 index 0000000..108bb82 --- /dev/null +++ b/test/bench-network/Dockerfile.netem @@ -0,0 +1,8 @@ +FROM alpine:3.22 + +RUN apk add --no-cache iproute2 + +COPY test/bench-network/netem-smoke.sh /usr/local/bin/livesync-netem-smoke +RUN chmod +x /usr/local/bin/livesync-netem-smoke + +CMD ["livesync-netem-smoke"] diff --git a/test/bench-network/README.md b/test/bench-network/README.md index 0cc89f4..e0fe6b7 100644 --- a/test/bench-network/README.md +++ b/test/bench-network/README.md @@ -106,3 +106,27 @@ docker compose -f test/bench-network/compose.yml run --rm bench-runner This sweep is useful for finding where the remote CouchDB path falls behind the local direct P2P path in the current HTTP-proxy latency model. It should not be presented as a full smartphone/VPN model. + +## Network emulation smoke + +The optional `netem` profile checks whether a Linux runner can apply traffic +shaping inside a Compose-managed container. This is a fixture smoke test for a +second-tier simulation design; it does not produce synchronisation performance +results by itself. + +```bash +docker compose -f test/bench-network/compose.yml --profile netem run --rm netem-smoke +``` + +The smoke writes `tc qdisc`, route, and interface details under +`test/bench-network/bench-results/`. Profile parameters can be overridden: + +```bash +NETEM_PROFILE=tethering-vpn \ +NETEM_DELAY_MS=140 \ +NETEM_JITTER_MS=50 \ +NETEM_LOSS_PERCENT=1.0 \ +NETEM_BANDWIDTH_MBIT=10 \ +NETEM_MTU=1380 \ +docker compose -f test/bench-network/compose.yml --profile netem run --rm netem-smoke +``` diff --git a/test/bench-network/compose.yml b/test/bench-network/compose.yml index 3427d48..23c91eb 100644 --- a/test/bench-network/compose.yml +++ b/test/bench-network/compose.yml @@ -97,3 +97,23 @@ services: BENCH_LIVESYNC_TEST_TEE: ${BENCH_LIVESYNC_TEST_TEE:-0} volumes: - ./bench-results:/workspace/src/apps/cli/testdeno/bench-results + + netem-smoke: + build: + context: ../.. + dockerfile: test/bench-network/Dockerfile.netem + profiles: + - netem + cap_add: + - NET_ADMIN + environment: + NETEM_PROFILE: ${NETEM_PROFILE:-home-wifi} + NETEM_INTERFACE: ${NETEM_INTERFACE:-eth0} + NETEM_DELAY_MS: ${NETEM_DELAY_MS:-20} + NETEM_JITTER_MS: ${NETEM_JITTER_MS:-5} + NETEM_LOSS_PERCENT: ${NETEM_LOSS_PERCENT:-0.1} + NETEM_BANDWIDTH_MBIT: ${NETEM_BANDWIDTH_MBIT:-100} + NETEM_MTU: ${NETEM_MTU:-1500} + NETEM_RESULT_ROOT: /bench-results + volumes: + - ./bench-results:/bench-results diff --git a/test/bench-network/netem-smoke.sh b/test/bench-network/netem-smoke.sh new file mode 100644 index 0000000..96e5e01 --- /dev/null +++ b/test/bench-network/netem-smoke.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env sh +set -eu + +profile="${NETEM_PROFILE:-home-wifi}" +iface="${NETEM_INTERFACE:-eth0}" +delay_ms="${NETEM_DELAY_MS:-20}" +jitter_ms="${NETEM_JITTER_MS:-5}" +loss_percent="${NETEM_LOSS_PERCENT:-0.1}" +bandwidth_mbit="${NETEM_BANDWIDTH_MBIT:-100}" +mtu="${NETEM_MTU:-1500}" +out_root="${NETEM_RESULT_ROOT:-/bench-results}" +timestamp="$(date -u +%Y%m%d-%H%M%S)" +out_dir="${out_root}/netem-smoke-${timestamp}" +out_file="${out_dir}/summary.json" + +mkdir -p "$out_dir" + +if ! ip link show "$iface" >/dev/null 2>&1; then + echo "Network interface '$iface' was not found" >&2 + ip addr >&2 + exit 2 +fi + +ip link set dev "$iface" mtu "$mtu" +tc qdisc del dev "$iface" root >/dev/null 2>&1 || true +tc qdisc add dev "$iface" root netem \ + delay "${delay_ms}ms" "${jitter_ms}ms" \ + loss "${loss_percent}%" \ + rate "${bandwidth_mbit}mbit" + +json_lines() { + awk ' + { + gsub(/\\/, "\\\\"); + gsub(/"/, "\\\""); + printf "%s \"%s\"", (NR == 1 ? "" : ",\n"), $0; + } + ' +} + +ip_addr="$(ip addr show "$iface" | json_lines)" +ip_route="$(ip route | json_lines)" +tc_qdisc="$(tc qdisc show dev "$iface" | json_lines)" + +cat > "$out_file" <