mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
499 lines
16 KiB
Bash
Executable File
499 lines
16 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Copyright 2018 The gVisor Authors.
|
|
#
|
|
# Licensed 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.
|
|
|
|
# TCP benchmark; see README.md for documentation.
|
|
|
|
# Fixed parameters.
|
|
iperf_port=45201 # Not likely to be privileged.
|
|
proxy_port=44000 # Ditto.
|
|
mask=8
|
|
client_addr=10.0.0.1
|
|
client_proxy_addr=10.0.0.2
|
|
server_proxy_addr=10.0.0.3
|
|
server_addr=10.0.0.4
|
|
full_server_addr=${server_addr}:${iperf_port}
|
|
full_server_proxy_addr=${server_proxy_addr}:${proxy_port}
|
|
iperf_binary_name=iperf3
|
|
iperf_version_arg=
|
|
|
|
# Defaults; this provides a reasonable approximation of a decent internet link.
|
|
# Parameters can be varied independently from this set to see response to
|
|
# various changes in the kind of link available.
|
|
client=false
|
|
server=false
|
|
linux_client=false
|
|
verbose=false
|
|
gso=0
|
|
swgso=false
|
|
mtu=1280 # 1280 is a reasonable lowest-common-denominator.
|
|
latency=10 # 10ms approximates a fast, dedicated connection.
|
|
latency_variation=1 # +/- 1ms is a relatively low amount of jitter.
|
|
loss=0.1 # 0.1% loss is non-zero, but not extremely high.
|
|
duplicate=0.1 # 0.1% means duplicates are 1/10x as frequent as losses.
|
|
duration=30 # 30s is enough time to consistent results (experimentally).
|
|
helper_dir="$(dirname "$0")"
|
|
netstack_opts=
|
|
disable_linux_gso=
|
|
disable_linux_gro=
|
|
gro=false
|
|
num_client_threads=1
|
|
sniff=false
|
|
xdp=false
|
|
declare -a unshare_opts=( -U -r )
|
|
|
|
# Check for netem support.
|
|
lsmod_output=$(lsmod | grep sch_netem)
|
|
if [[ "$?" != "0" ]]; then
|
|
echo "warning: sch_netem may not be installed." >&2
|
|
fi
|
|
|
|
function checktmp() {
|
|
if [[ "$1" =~ ^/tmp/ ]]; then
|
|
echo "Don't use /tmp for output files ('$1') -- tcp_benchmark mounts over /tmp and your file will never make it to the root /tmp."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--client)
|
|
client=true
|
|
;;
|
|
--linux-client)
|
|
linux_client=true
|
|
;;
|
|
--client_tcp_probe_file)
|
|
shift
|
|
netstack_opts="${netstack_opts} -client_tcp_probe_file=$1"
|
|
;;
|
|
--server)
|
|
server=true
|
|
;;
|
|
--verbose)
|
|
verbose=true
|
|
;;
|
|
--gso)
|
|
shift
|
|
gso=$1
|
|
;;
|
|
--swgso)
|
|
swgso=true
|
|
;;
|
|
--server_tcp_probe_file)
|
|
shift
|
|
netstack_opts="${netstack_opts} -server_tcp_probe_file=$1"
|
|
;;
|
|
--ideal)
|
|
mtu=1500 # Standard ethernet.
|
|
latency=0 # No latency.
|
|
latency_variation=0 # No jitter.
|
|
loss=0 # No loss.
|
|
duplicate=0 # No duplicates.
|
|
;;
|
|
--mtu)
|
|
shift
|
|
[[ "$#" -le 0 ]] && echo "no mtu provided" && exit 1
|
|
mtu=$1
|
|
;;
|
|
--sack)
|
|
netstack_opts="${netstack_opts} -sack"
|
|
;;
|
|
--rack)
|
|
netstack_opts="${netstack_opts} -rack"
|
|
;;
|
|
--cubic)
|
|
netstack_opts="${netstack_opts} -cubic"
|
|
;;
|
|
--moderate-recv-buf)
|
|
netstack_opts="${netstack_opts} -moderate_recv_buf"
|
|
;;
|
|
--duration)
|
|
shift
|
|
[[ "$#" -le 0 ]] && echo "no duration provided" && exit 1
|
|
duration=$1
|
|
;;
|
|
--latency)
|
|
shift
|
|
[[ "$#" -le 0 ]] && echo "no latency provided" && exit 1
|
|
latency=$1
|
|
;;
|
|
--latency-variation)
|
|
shift
|
|
[[ "$#" -le 0 ]] && echo "no latency variation provided" && exit 1
|
|
latency_variation=$1
|
|
;;
|
|
--loss)
|
|
shift
|
|
[[ "$#" -le 0 ]] && echo "no loss probability provided" && exit 1
|
|
loss=$1
|
|
;;
|
|
--duplicate)
|
|
shift
|
|
[[ "$#" -le 0 ]] && echo "no duplicate provided" && exit 1
|
|
duplicate=$1
|
|
;;
|
|
--cpuprofile)
|
|
shift
|
|
netstack_opts="${netstack_opts} -cpuprofile=$1"
|
|
checktmp "$1"
|
|
;;
|
|
--memprofile)
|
|
shift
|
|
netstack_opts="${netstack_opts} -memprofile=$1"
|
|
checktmp "$1"
|
|
;;
|
|
--blockprofile)
|
|
shift
|
|
netstack_opts="${netstack_opts} -blockprofile=$1"
|
|
checktmp "$1"
|
|
;;
|
|
--mutexprofile)
|
|
shift
|
|
netstack_opts="${netstack_opts} -mutexprofile=$1"
|
|
checktmp "$1"
|
|
;;
|
|
--traceprofile)
|
|
shift
|
|
netstack_opts="${netstack_opts} -traceprofile=$1"
|
|
checktmp "$1"
|
|
;;
|
|
--disable-linux-gso)
|
|
disable_linux_gso=1
|
|
;;
|
|
--disable-linux-gro)
|
|
disable_linux_gro=1
|
|
;;
|
|
--gro)
|
|
gro=true
|
|
;;
|
|
--ipv6)
|
|
client_addr=fd::1
|
|
client_proxy_addr=fd::2
|
|
server_proxy_addr=fd::3
|
|
server_addr=fd::4
|
|
full_server_addr=[${server_addr}]:${iperf_port}
|
|
full_server_proxy_addr=[${server_proxy_addr}]:${proxy_port}
|
|
iperf_version_arg=-V
|
|
netstack_opts="${netstack_opts} -ipv6"
|
|
;;
|
|
--num-client-threads)
|
|
shift
|
|
num_client_threads=$1
|
|
;;
|
|
--helpers)
|
|
shift
|
|
[[ "$#" -le 0 ]] && echo "no helper dir provided" && exit 1
|
|
helper_dir=$1
|
|
;;
|
|
--iperf-binary)
|
|
shift
|
|
[[ "$#" -le 0 ]] && echo "no iperf name provided" && exit 1
|
|
iperf_binary_name=$1
|
|
;;
|
|
--sniff)
|
|
netstack_opts="${netstack_opts} -sniff"
|
|
;;
|
|
--xdp)
|
|
xdp=true
|
|
;;
|
|
--no-user-ns)
|
|
unshare_opts=()
|
|
;;
|
|
*)
|
|
echo "unknown option: $1"
|
|
echo ""
|
|
echo "usage: $0 [options]"
|
|
echo "options:"
|
|
echo " --help show this message"
|
|
echo " --verbose verbose output"
|
|
echo " --client use netstack as the client"
|
|
echo " --linux-client print client stats in linux case"
|
|
echo " --ideal reset all network emulation"
|
|
echo " --server use netstack as the server"
|
|
echo " --mtu set the mtu (bytes)"
|
|
echo " --sack enable SACK support"
|
|
echo " --rack enable RACK support"
|
|
echo " --moderate-recv-buf enable TCP receive buffer auto-tuning"
|
|
echo " --cubic enable CUBIC congestion control for Netstack"
|
|
echo " --duration set the test duration (s)"
|
|
echo " --latency set the latency (ms)"
|
|
echo " --latency-variation set the latency variation"
|
|
echo " --loss set the loss probability (%)"
|
|
echo " --duplicate set the duplicate probability (%)"
|
|
echo " --helpers set the helper directory"
|
|
echo " --num-client-threads number of parallel client threads to run"
|
|
echo " --disable-linux-gso disable segmentation offload (TSO, GSO, GRO) in the Linux network stack"
|
|
echo " --disable-linux-gro disable GRO in the Linux network stack"
|
|
echo " --gro enable gVisor GRO"
|
|
echo " --ipv6 use ipv6 for benchmarks"
|
|
echo " --iperf-binary name of the iperf binary to call"
|
|
echo " --sniff sniff and output packet logs"
|
|
echo " --xdp use AF_XDP socket instead of AF_PACKET"
|
|
echo " --no-user-ns don't run in a new user namespace. Useful for testing as root"
|
|
echo ""
|
|
echo "The output will of the script will be:"
|
|
echo " <throughput> <client-cpu-usage> <server-cpu-usage>"
|
|
exit 1
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [[ ${verbose} == "true" ]]; then
|
|
set -x
|
|
fi
|
|
|
|
# Latency needs to be halved, since it's applied on both ways.
|
|
half_latency=$(echo "${latency}"/2 | bc -l | awk '{printf "%1.2f", $0}')
|
|
half_loss=$(echo "${loss}"/2 | bc -l | awk '{printf "%1.6f", $0}')
|
|
half_duplicate=$(echo "${duplicate}"/2 | bc -l | awk '{printf "%1.6f", $0}')
|
|
helper_dir="${helper_dir#$(pwd)/}" # Use relative paths.
|
|
proxy_binary="${helper_dir}/tcp_proxy"
|
|
nsjoin_binary="${helper_dir}/nsjoin"
|
|
|
|
if [[ ! -e ${proxy_binary} ]]; then
|
|
echo "Could not locate ${proxy_binary}, please make sure you've built the binary"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -e ${nsjoin_binary} ]]; then
|
|
echo "Could not locate ${nsjoin_binary}, please make sure you've built the binary"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$(echo "${latency_variation}" | awk '{printf "%1.2f", $0}')" != "0.00" ]]; then
|
|
# As long as there's some jitter, then we use the paretonormal distribution.
|
|
# This will preserve the minimum RTT, but add a realistic amount of jitter to
|
|
# the connection and cause re-ordering, etc. The regular pareto distribution
|
|
# appears to an unreasonable level of delay (we want only small spikes.)
|
|
distribution="distribution paretonormal"
|
|
else
|
|
distribution=""
|
|
fi
|
|
|
|
# Client proxy that will listen on the client's iperf target forward traffic
|
|
# using the host networking stack.
|
|
client_args="${proxy_binary} -port ${proxy_port} -forward ${full_server_proxy_addr}"
|
|
if ${client}; then
|
|
# Client proxy that will listen on the client's iperf target
|
|
# and forward traffic using netstack.
|
|
client_args="${proxy_binary} ${netstack_opts} -port ${proxy_port} -client \\
|
|
-mtu ${mtu} -iface client.0 -addr ${client_proxy_addr} -mask ${mask} \\
|
|
-forward ${full_server_proxy_addr} -gso=${gso} -swgso=${swgso} --gro=${gro} \\
|
|
--xdp=${xdp}"
|
|
fi
|
|
|
|
# Server proxy that will listen on the proxy port and forward to the server's
|
|
# iperf server using the host networking stack.
|
|
server_args="${proxy_binary} -port ${proxy_port} -forward ${full_server_addr}"
|
|
if ${server}; then
|
|
# Server proxy that will listen on the proxy port and forward to the servers'
|
|
# iperf server using netstack.
|
|
server_args="${proxy_binary} ${netstack_opts} -port ${proxy_port} -server \\
|
|
-mtu ${mtu} -iface server.0 -addr ${server_proxy_addr} -mask ${mask} \\
|
|
-forward ${full_server_addr} -gso=${gso} -swgso=${swgso} --gro=${gro} \\
|
|
--xdp=${xdp}"
|
|
fi
|
|
|
|
# Specify loss and duplicate parameters only if they are non-zero
|
|
loss_opt=""
|
|
if [[ "$(echo "$half_loss" | bc -q)" != "0" ]]; then
|
|
loss_opt="loss random ${half_loss}%"
|
|
fi
|
|
duplicate_opt=""
|
|
if [[ "$(echo "$half_duplicate" | bc -q)" != "0" ]]; then
|
|
duplicate_opt="duplicate ${half_duplicate}%"
|
|
fi
|
|
|
|
exec unshare "${unshare_opts[@]}" -m -n -f -p --mount-proc /bin/bash << EOF
|
|
set -e -m
|
|
|
|
if [[ ${verbose} == "true" ]]; then
|
|
set -x
|
|
fi
|
|
|
|
mount -t tmpfs netstack-bench /tmp
|
|
|
|
# We may have reset the path in the unshare if the shell loaded some public
|
|
# profiles. Ensure that tools are discoverable via the parent's PATH.
|
|
export PATH=${PATH}
|
|
|
|
# Add client, server interfaces.
|
|
ip link add client.0 type veth peer name client.1
|
|
ip link add server.0 type veth peer name server.1
|
|
|
|
# Add network emulation devices.
|
|
ip link add wan.0 type veth peer name wan.1
|
|
ip link set wan.0 up
|
|
ip link set wan.1 up
|
|
|
|
# Enroll on the bridge.
|
|
ip link add name br0 type bridge
|
|
ip link add name br1 type bridge
|
|
ip link set client.1 master br0
|
|
ip link set server.1 master br1
|
|
ip link set wan.0 master br0
|
|
ip link set wan.1 master br1
|
|
ip link set br0 up
|
|
ip link set br1 up
|
|
|
|
# Set the MTU appropriately.
|
|
ip link set client.0 mtu ${mtu}
|
|
ip link set server.0 mtu ${mtu}
|
|
ip link set wan.0 mtu ${mtu}
|
|
ip link set wan.1 mtu ${mtu}
|
|
|
|
# Add appropriate latency, loss and duplication.
|
|
#
|
|
# This is added in at the point of bridge connection.
|
|
for device in wan.0 wan.1; do
|
|
# NOTE: We don't support a loss correlation as testing has shown that it
|
|
# actually doesn't work. The man page actually has a small comment about this
|
|
# "It is also possible to add a correlation, but this option is now deprecated
|
|
# due to the noticed bad behavior." For more information see netem(8).
|
|
tc qdisc add dev \$device root netem \\
|
|
delay ${half_latency}ms ${latency_variation}ms ${distribution} \\
|
|
${loss_opt} ${duplicate_opt}
|
|
done
|
|
|
|
# Start a client proxy.
|
|
touch /tmp/client.netns
|
|
unshare -n mount --bind /proc/self/ns/net /tmp/client.netns
|
|
|
|
# Move the endpoint into the namespace.
|
|
while ip link | grep client.0 > /dev/null; do
|
|
ip link set dev client.0 netns /tmp/client.netns
|
|
done
|
|
|
|
if ! ${client}; then
|
|
# Only add the address to NIC if netstack is not in use. Otherwise the host
|
|
# will also process the inbound SYN and send a RST back.
|
|
${nsjoin_binary} /tmp/client.netns ip addr add ${client_proxy_addr}/${mask} dev client.0
|
|
fi
|
|
|
|
# Start a server proxy.
|
|
touch /tmp/server.netns
|
|
unshare -n mount --bind /proc/self/ns/net /tmp/server.netns
|
|
# Move the endpoint into the namespace.
|
|
while ip link | grep server.0 > /dev/null; do
|
|
ip link set dev server.0 netns /tmp/server.netns
|
|
done
|
|
if ! ${server}; then
|
|
# Only add the address to NIC if netstack is not in use. Otherwise the host
|
|
# will also process the inbound SYN and send a RST back.
|
|
${nsjoin_binary} /tmp/server.netns ip addr add ${server_proxy_addr}/${mask} dev server.0
|
|
fi
|
|
|
|
# Add client and server addresses, and bring everything up.
|
|
${nsjoin_binary} /tmp/client.netns ip addr add ${client_addr}/${mask} dev client.0
|
|
${nsjoin_binary} /tmp/server.netns ip addr add ${server_addr}/${mask} dev server.0
|
|
if [[ "${disable_linux_gso}" == "1" ]]; then
|
|
${nsjoin_binary} /tmp/client.netns ethtool -K client.0 tso off
|
|
${nsjoin_binary} /tmp/client.netns ethtool -K client.0 gso off
|
|
${nsjoin_binary} /tmp/server.netns ethtool -K server.0 tso off
|
|
${nsjoin_binary} /tmp/server.netns ethtool -K server.0 gso off
|
|
fi
|
|
if [[ "${disable_linux_gro}" == "1" ]]; then
|
|
${nsjoin_binary} /tmp/client.netns ethtool -K client.0 gro off
|
|
${nsjoin_binary} /tmp/server.netns ethtool -K server.0 gro off
|
|
fi
|
|
${nsjoin_binary} /tmp/client.netns ip link set client.0 up
|
|
${nsjoin_binary} /tmp/client.netns ip link set lo up
|
|
${nsjoin_binary} /tmp/server.netns ip link set server.0 up
|
|
${nsjoin_binary} /tmp/server.netns ip link set lo up
|
|
ip link set dev client.1 up
|
|
ip link set dev server.1 up
|
|
|
|
${nsjoin_binary} /tmp/server.netns ${server_args} &
|
|
server_pid=\$!
|
|
|
|
# Start the iperf server.
|
|
${nsjoin_binary} /tmp/server.netns ${iperf_binary_name} ${iperf_version_arg} -p ${iperf_port} -s >&2 &
|
|
iperf_pid=\$!
|
|
|
|
# Give services time to start.
|
|
sleep 5
|
|
|
|
${nsjoin_binary} /tmp/client.netns ${client_args} &
|
|
client_pid=\$!
|
|
|
|
# Show traffic information.
|
|
if ! ${client} && ! ${server}; then
|
|
${nsjoin_binary} /tmp/client.netns ping -c 100 -i 0.001 -W 1 ${server_addr} >&2 || true
|
|
fi
|
|
|
|
results_file=\$(mktemp)
|
|
function cleanup {
|
|
rm -f \$results_file
|
|
kill -TERM \$client_pid
|
|
kill -TERM \$server_pid
|
|
wait \$client_pid
|
|
wait \$server_pid
|
|
kill -9 \$iperf_pid 2>/dev/null
|
|
}
|
|
|
|
# Allow failure from this point.
|
|
set +e
|
|
trap cleanup EXIT
|
|
|
|
# Run the benchmark, recording the results file.
|
|
while ${nsjoin_binary} /tmp/client.netns ${iperf_binary_name} \\
|
|
${iperf_version_arg} -p ${proxy_port} -c ${client_addr} -t ${duration} -f m -P ${num_client_threads} 2>&1 \\
|
|
| tee \$results_file \\
|
|
| grep -E "connect failed|unable to connect" >/dev/null; do
|
|
sleep 0.1 # Wait for all services.
|
|
done
|
|
|
|
# Unlink all relevant devices from the bridge. This is because when the bridge
|
|
# is deleted, the kernel may hang. It appears that this problem is fixed in
|
|
# upstream commit 1ce5cce895309862d2c35d922816adebe094fe4a.
|
|
ip link set client.1 nomaster
|
|
ip link set server.1 nomaster
|
|
ip link set wan.0 nomaster
|
|
ip link set wan.1 nomaster
|
|
|
|
# Emit raw results.
|
|
cat \$results_file >&2
|
|
|
|
# Emit a useful result (final throughput).
|
|
mbits=\$(grep Mbits/sec \$results_file \\
|
|
| grep 'sender' | awk '{print \$7};')
|
|
client_cpu_ticks=\$(cat /proc/\$client_pid/stat \\
|
|
| awk '{print (\$14+\$15);}')
|
|
server_cpu_ticks=\$(cat /proc/\$server_pid/stat \\
|
|
| awk '{print (\$14+\$15);}')
|
|
ticks_per_sec=\$(getconf CLK_TCK)
|
|
client_cpu_load=\$(bc -l <<< \$client_cpu_ticks/\$ticks_per_sec/${duration})
|
|
server_cpu_load=\$(bc -l <<< \$server_cpu_ticks/\$ticks_per_sec/${duration})
|
|
|
|
hostgso=true
|
|
if [[ "${disable_linux_gso}" ]]; then
|
|
hostgso=false
|
|
fi
|
|
hostgro=true
|
|
if [[ "${disable_linux_gro}" ]]; then
|
|
hostgro=false
|
|
fi
|
|
|
|
if ${client}; then
|
|
echo "BenchmarkTCP/role=client/host-gso=\$hostgso/host-gro=\$hostgro 1 \$mbits Mb/s \$client_cpu_load cpu-time"
|
|
exit 0
|
|
elif ${linux_client}; then
|
|
echo "BenchmarkTCP/role=client/host-gso=\$hostgso/host-gro=\$hostgro 1 \$mbits Mb/s \$client_cpu_load cpu-time"
|
|
exit 0
|
|
fi
|
|
echo "BenchmarkTCP/role=server/host-gso=\$hostgso/host-gro=\$hostgro 1 \$mbits Mb/s \$server_cpu_load cpu-time"
|
|
EOF
|