| #!/usr/bin/env bash |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # Simulate network delay/jitter/loss on the worker hosts (supervisor containers) |
| # using Linux tc/netem. The Storm image has no `tc`, so we inject the qdisc from |
| # a throwaway helper container that shares each supervisor's network namespace |
| # (--net container:...) -- no image rebuild, no compose change required. |
| # |
| # netem applies to *egress* on each supervisor's eth0, so it shapes ALL traffic |
| # leaving that container (inter-worker tuples, but also heartbeats to Nimbus/ZK). |
| # Keep the delay moderate (<= ~150ms) so heartbeats don't time out. With both |
| # supervisors delayed by D, worker<->worker round-trip latency is ~2*D. |
| # |
| # netem's default queue is only 1000 packets; under a high-throughput perf |
| # topology that buffer overflows at the added delay and drops tuples, which |
| # collapses TCP and back-pressures the spout to ~0. So we set a large `limit` |
| # (default 1,000,000 packets) -- big enough to hold rate * delay without drops. |
| # |
| # Usage: |
| # ./netsim.sh add [delay_ms] [jitter_ms] [loss_pct] [limit_pkts] # default 50 10 0 1000000 |
| # ./netsim.sh show |
| # ./netsim.sh clear |
| # ./netsim.sh ping # measure RTT between supervisors |
| # |
| # Override the shaped containers with TARGETS="..." ./netsim.sh ... |
| set -euo pipefail |
| |
| COMPOSE_SERVICES="${COMPOSE_SERVICES:-supervisor1 supervisor2}" |
| HELPER_IMAGE="${HELPER_IMAGE:-nicolaka/netshoot}" |
| IFACE="${IFACE:-eth0}" |
| |
| targets() { # echo the containers to shape (resolved once, memoized in TARGETS) |
| if [[ -z "${TARGETS:-}" ]]; then |
| TARGETS="$(docker compose ps -q ${COMPOSE_SERVICES} 2>/dev/null | tr '\n' ' ')" |
| if [[ -z "${TARGETS// /}" ]]; then |
| echo "error: no running containers for services: ${COMPOSE_SERVICES}" >&2 |
| echo " run from dev-tools/cluster with the cluster up (docker compose up -d)," >&2 |
| echo " or set TARGETS=\"<name-or-id> ...\" to shape containers explicitly." >&2 |
| exit 1 |
| fi |
| fi |
| echo "${TARGETS}" |
| } |
| |
| cname() { # human-readable container name for id/name $1 (falls back to $1) |
| local n; n="$(docker inspect -f '{{.Name}}' "$1" 2>/dev/null)" || true |
| n="${n#/}" |
| echo "${n:-$1}" |
| } |
| |
| inns() { # run a command inside container $1's network namespace with NET_ADMIN |
| local c="$1"; shift |
| docker run --rm --net "container:${c}" --cap-add NET_ADMIN "${HELPER_IMAGE}" "$@" |
| } |
| |
| cmd="${1:-show}" |
| case "${cmd}" in |
| add) |
| delay="${2:-50}"; jitter="${3:-10}"; loss="${4:-0}"; limit="${5:-1000000}" |
| if (( delay > 150 )); then |
| echo "warning: delay ${delay}ms > 150ms may cause heartbeats to Nimbus/ZK to time out" >&2 |
| fi |
| for c in $(targets); do |
| echo "==> $(cname "${c}"): netem delay ${delay}ms ${jitter}ms loss ${loss}% limit ${limit} on ${IFACE}" |
| inns "${c}" tc qdisc replace dev "${IFACE}" root netem \ |
| delay "${delay}ms" "${jitter}ms" distribution normal loss "${loss}%" limit "${limit}" |
| done |
| ;; |
| clear) |
| for c in $(targets); do |
| echo "==> $(cname "${c}"): removing netem" |
| inns "${c}" tc qdisc del dev "${IFACE}" root 2>/dev/null || true |
| done |
| ;; |
| show) |
| for c in $(targets); do |
| echo "==> $(cname "${c}"):" |
| inns "${c}" tc qdisc show dev "${IFACE}" |
| done |
| ;; |
| ping) |
| set -- $(targets) |
| src="$1"; dst_host="$(cname "${PING_DST:-${2:-supervisor2}}")" |
| echo "==> RTT from $(cname "${src}") to ${dst_host} (5 pings)" |
| inns "${src}" ping -c 5 "${dst_host}" |
| ;; |
| *) |
| echo "usage: $0 {add [delay_ms] [jitter_ms] [loss_pct] | show | clear | ping}" >&2 |
| exit 2 |
| ;; |
| esac |