| #!/bin/bash | |
| # SPDX-License-Identifier: GPL-2.0 | |
| # | |
| # Copyright (c) 2019 Facebook | |
| # | |
| # This program is free software; you can redistribute it and/or | |
| # modify it under the terms of version 2 of the GNU General Public | |
| # License as published by the Free Software Foundation. | |
| Usage() { | |
| echo "Script for testing HBM (Host Bandwidth Manager) framework." | |
| echo "It creates a cgroup to use for testing and load a BPF program to limit" | |
| echo "egress or ingress bandwidth. It then uses iperf3 or netperf to create" | |
| echo "loads. The output is the goodput in Mbps (unless -D was used)." | |
| echo "" | |
| echo "USAGE: $name [out] [-b=<prog>|--bpf=<prog>] [-c=<cc>|--cc=<cc>]" | |
| echo " [-D] [-d=<delay>|--delay=<delay>] [--debug] [-E] [--edt]" | |
| echo " [-f=<#flows>|--flows=<#flows>] [-h] [-i=<id>|--id=<id >]" | |
| echo " [-l] [-N] [--no_cn] [-p=<port>|--port=<port>] [-P]" | |
| echo " [-q=<qdisc>] [-R] [-s=<server>|--server=<server]" | |
| echo " [-S|--stats] -t=<time>|--time=<time>] [-w] [cubic|dctcp]" | |
| echo " Where:" | |
| echo " out egress (default)" | |
| echo " -b or --bpf BPF program filename to load and attach." | |
| echo " Default is hbm_out_kern.o for egress," | |
| echo " -c or -cc TCP congestion control (cubic or dctcp)" | |
| echo " --debug print BPF trace buffer" | |
| echo " -d or --delay add a delay in ms using netem" | |
| echo " -D In addition to the goodput in Mbps, it also outputs" | |
| echo " other detailed information. This information is" | |
| echo " test dependent (i.e. iperf3 or netperf)." | |
| echo " -E enable ECN (not required for dctcp)" | |
| echo " --edt use fq's Earliest Departure Time (requires fq)" | |
| echo " -f or --flows number of concurrent flows (default=1)" | |
| echo " -i or --id cgroup id (an integer, default is 1)" | |
| echo " -N use netperf instead of iperf3" | |
| echo " --no_cn Do not return CN notifications" | |
| echo " -l do not limit flows using loopback" | |
| echo " -h Help" | |
| echo " -p or --port iperf3 port (default is 5201)" | |
| echo " -P use an iperf3 instance for each flow" | |
| echo " -q use the specified qdisc" | |
| echo " -r or --rate rate in Mbps (default 1s 1Gbps)" | |
| echo " -R Use TCP_RR for netperf. 1st flow has req" | |
| echo " size of 10KB, rest of 1MB. Reply in all" | |
| echo " cases is 1 byte." | |
| echo " More detailed output for each flow can be found" | |
| echo " in the files netperf.<cg>.<flow>, where <cg> is the" | |
| echo " cgroup id as specified with the -i flag, and <flow>" | |
| echo " is the flow id starting at 1 and increasing by 1 for" | |
| echo " flow (as specified by -f)." | |
| echo " -s or --server hostname of netperf server. Used to create netperf" | |
| echo " test traffic between to hosts (default is within host)" | |
| echo " netserver must be running on the host." | |
| echo " -S or --stats whether to update hbm stats (default is yes)." | |
| echo " -t or --time duration of iperf3 in seconds (default=5)" | |
| echo " -w Work conserving flag. cgroup can increase its" | |
| echo " bandwidth beyond the rate limit specified" | |
| echo " while there is available bandwidth. Current" | |
| echo " implementation assumes there is only one NIC" | |
| echo " (eth0), but can be extended to support multiple" | |
| echo " NICs." | |
| echo " cubic or dctcp specify which TCP CC to use" | |
| echo " " | |
| exit | |
| } | |
| #set -x | |
| debug_flag=0 | |
| args="$@" | |
| name="$0" | |
| netem=0 | |
| cc=x | |
| dir="-o" | |
| dir_name="out" | |
| dur=5 | |
| flows=1 | |
| id=1 | |
| prog="" | |
| port=5201 | |
| rate=1000 | |
| multi_iperf=0 | |
| flow_cnt=1 | |
| use_netperf=0 | |
| rr=0 | |
| ecn=0 | |
| details=0 | |
| server="" | |
| qdisc="" | |
| flags="" | |
| do_stats=0 | |
| BPFFS=/sys/fs/bpf | |
| function config_bpffs () { | |
| if mount | grep $BPFFS > /dev/null; then | |
| echo "bpffs already mounted" | |
| else | |
| echo "bpffs not mounted. Mounting..." | |
| mount -t bpf none $BPFFS | |
| fi | |
| } | |
| function start_hbm () { | |
| rm -f hbm.out | |
| echo "./hbm $dir -n $id -r $rate -t $dur $flags $dbg $prog" > hbm.out | |
| echo " " >> hbm.out | |
| ./hbm $dir -n $id -r $rate -t $dur $flags $dbg $prog >> hbm.out 2>&1 & | |
| echo $! | |
| } | |
| processArgs () { | |
| for i in $args ; do | |
| case $i in | |
| # Support for upcoming ingress rate limiting | |
| #in) # support for upcoming ingress rate limiting | |
| # dir="-i" | |
| # dir_name="in" | |
| # ;; | |
| out) | |
| dir="-o" | |
| dir_name="out" | |
| ;; | |
| -b=*|--bpf=*) | |
| prog="${i#*=}" | |
| ;; | |
| -c=*|--cc=*) | |
| cc="${i#*=}" | |
| ;; | |
| --no_cn) | |
| flags="$flags --no_cn" | |
| ;; | |
| --debug) | |
| flags="$flags -d" | |
| debug_flag=1 | |
| ;; | |
| -d=*|--delay=*) | |
| netem="${i#*=}" | |
| ;; | |
| -D) | |
| details=1 | |
| ;; | |
| -E) | |
| ecn=1 | |
| ;; | |
| --edt) | |
| flags="$flags --edt" | |
| qdisc="fq" | |
| ;; | |
| -f=*|--flows=*) | |
| flows="${i#*=}" | |
| ;; | |
| -i=*|--id=*) | |
| id="${i#*=}" | |
| ;; | |
| -l) | |
| flags="$flags -l" | |
| ;; | |
| -N) | |
| use_netperf=1 | |
| ;; | |
| -p=*|--port=*) | |
| port="${i#*=}" | |
| ;; | |
| -P) | |
| multi_iperf=1 | |
| ;; | |
| -q=*) | |
| qdisc="${i#*=}" | |
| ;; | |
| -r=*|--rate=*) | |
| rate="${i#*=}" | |
| ;; | |
| -R) | |
| rr=1 | |
| ;; | |
| -s=*|--server=*) | |
| server="${i#*=}" | |
| ;; | |
| -S|--stats) | |
| flags="$flags -s" | |
| do_stats=1 | |
| ;; | |
| -t=*|--time=*) | |
| dur="${i#*=}" | |
| ;; | |
| -w) | |
| flags="$flags -w" | |
| ;; | |
| cubic) | |
| cc=cubic | |
| ;; | |
| dctcp) | |
| cc=dctcp | |
| ;; | |
| *) | |
| echo "Unknown arg:$i" | |
| Usage | |
| ;; | |
| esac | |
| done | |
| } | |
| processArgs | |
| config_bpffs | |
| if [ $debug_flag -eq 1 ] ; then | |
| rm -f hbm_out.log | |
| fi | |
| hbm_pid=$(start_hbm) | |
| usleep 100000 | |
| host=`hostname` | |
| cg_base_dir=/sys/fs/cgroup/unified | |
| cg_dir="$cg_base_dir/cgroup-test-work-dir/hbm$id" | |
| echo $$ >> $cg_dir/cgroup.procs | |
| ulimit -l unlimited | |
| rm -f ss.out | |
| rm -f hbm.[0-9]*.$dir_name | |
| if [ $ecn -ne 0 ] ; then | |
| sysctl -w -q -n net.ipv4.tcp_ecn=1 | |
| fi | |
| if [ $use_netperf -eq 0 ] ; then | |
| cur_cc=`sysctl -n net.ipv4.tcp_congestion_control` | |
| if [ "$cc" != "x" ] ; then | |
| sysctl -w -q -n net.ipv4.tcp_congestion_control=$cc | |
| fi | |
| fi | |
| if [ "$netem" -ne "0" ] ; then | |
| if [ "$qdisc" != "" ] ; then | |
| echo "WARNING: Ignoring -q options because -d option used" | |
| fi | |
| tc qdisc del dev lo root > /dev/null 2>&1 | |
| tc qdisc add dev lo root netem delay $netem\ms > /dev/null 2>&1 | |
| elif [ "$qdisc" != "" ] ; then | |
| tc qdisc del dev eth0 root > /dev/null 2>&1 | |
| tc qdisc add dev eth0 root $qdisc > /dev/null 2>&1 | |
| fi | |
| n=0 | |
| m=$[$dur * 5] | |
| hn="::1" | |
| if [ $use_netperf -ne 0 ] ; then | |
| if [ "$server" != "" ] ; then | |
| hn=$server | |
| fi | |
| fi | |
| ( ping6 -i 0.2 -c $m $hn > ping.out 2>&1 ) & | |
| if [ $use_netperf -ne 0 ] ; then | |
| begNetserverPid=`ps ax | grep netserver | grep --invert-match "grep" | \ | |
| awk '{ print $1 }'` | |
| if [ "$begNetserverPid" == "" ] ; then | |
| if [ "$server" == "" ] ; then | |
| ( ./netserver > /dev/null 2>&1) & | |
| usleep 100000 | |
| fi | |
| fi | |
| flow_cnt=1 | |
| if [ "$server" == "" ] ; then | |
| np_server=$host | |
| else | |
| np_server=$server | |
| fi | |
| if [ "$cc" == "x" ] ; then | |
| np_cc="" | |
| else | |
| np_cc="-K $cc,$cc" | |
| fi | |
| replySize=1 | |
| while [ $flow_cnt -le $flows ] ; do | |
| if [ $rr -ne 0 ] ; then | |
| reqSize=1M | |
| if [ $flow_cnt -eq 1 ] ; then | |
| reqSize=10K | |
| fi | |
| if [ "$dir" == "-i" ] ; then | |
| replySize=$reqSize | |
| reqSize=1 | |
| fi | |
| ( ./netperf -H $np_server -l $dur -f m -j -t TCP_RR -- -r $reqSize,$replySize $np_cc -k P50_lATENCY,P90_LATENCY,LOCAL_TRANSPORT_RETRANS,REMOTE_TRANSPORT_RETRANS,LOCAL_SEND_THROUGHPUT,LOCAL_RECV_THROUGHPUT,REQUEST_SIZE,RESPONSE_SIZE > netperf.$id.$flow_cnt ) & | |
| else | |
| if [ "$dir" == "-i" ] ; then | |
| ( ./netperf -H $np_server -l $dur -f m -j -t TCP_RR -- -r 1,10M $np_cc -k P50_LATENCY,P90_LATENCY,LOCAL_TRANSPORT_RETRANS,LOCAL_SEND_THROUGHPUT,REMOTE_TRANSPORT_RETRANS,REMOTE_SEND_THROUGHPUT,REQUEST_SIZE,RESPONSE_SIZE > netperf.$id.$flow_cnt ) & | |
| else | |
| ( ./netperf -H $np_server -l $dur -f m -j -t TCP_STREAM -- $np_cc -k P50_lATENCY,P90_LATENCY,LOCAL_TRANSPORT_RETRANS,LOCAL_SEND_THROUGHPUT,REQUEST_SIZE,RESPONSE_SIZE > netperf.$id.$flow_cnt ) & | |
| fi | |
| fi | |
| flow_cnt=$[flow_cnt+1] | |
| done | |
| # sleep for duration of test (plus some buffer) | |
| n=$[dur+2] | |
| sleep $n | |
| # force graceful termination of netperf | |
| pids=`pgrep netperf` | |
| for p in $pids ; do | |
| kill -SIGALRM $p | |
| done | |
| flow_cnt=1 | |
| rate=0 | |
| if [ $details -ne 0 ] ; then | |
| echo "" | |
| echo "Details for HBM in cgroup $id" | |
| if [ $do_stats -eq 1 ] ; then | |
| if [ -e hbm.$id.$dir_name ] ; then | |
| cat hbm.$id.$dir_name | |
| fi | |
| fi | |
| fi | |
| while [ $flow_cnt -le $flows ] ; do | |
| if [ "$dir" == "-i" ] ; then | |
| r=`cat netperf.$id.$flow_cnt | grep -o "REMOTE_SEND_THROUGHPUT=[0-9]*" | grep -o "[0-9]*"` | |
| else | |
| r=`cat netperf.$id.$flow_cnt | grep -o "LOCAL_SEND_THROUGHPUT=[0-9]*" | grep -o "[0-9]*"` | |
| fi | |
| echo "rate for flow $flow_cnt: $r" | |
| rate=$[rate+r] | |
| if [ $details -ne 0 ] ; then | |
| echo "-----" | |
| echo "Details for cgroup $id, flow $flow_cnt" | |
| cat netperf.$id.$flow_cnt | |
| fi | |
| flow_cnt=$[flow_cnt+1] | |
| done | |
| if [ $details -ne 0 ] ; then | |
| echo "" | |
| delay=`grep "avg" ping.out | grep -o "= [0-9.]*/[0-9.]*" | grep -o "[0-9.]*$"` | |
| echo "PING AVG DELAY:$delay" | |
| echo "AGGREGATE_GOODPUT:$rate" | |
| else | |
| echo $rate | |
| fi | |
| elif [ $multi_iperf -eq 0 ] ; then | |
| (iperf3 -s -p $port -1 > /dev/null 2>&1) & | |
| usleep 100000 | |
| iperf3 -c $host -p $port -i 0 -P $flows -f m -t $dur > iperf.$id | |
| rates=`grep receiver iperf.$id | grep -o "[0-9.]* Mbits" | grep -o "^[0-9]*"` | |
| rate=`echo $rates | grep -o "[0-9]*$"` | |
| if [ $details -ne 0 ] ; then | |
| echo "" | |
| echo "Details for HBM in cgroup $id" | |
| if [ $do_stats -eq 1 ] ; then | |
| if [ -e hbm.$id.$dir_name ] ; then | |
| cat hbm.$id.$dir_name | |
| fi | |
| fi | |
| delay=`grep "avg" ping.out | grep -o "= [0-9.]*/[0-9.]*" | grep -o "[0-9.]*$"` | |
| echo "PING AVG DELAY:$delay" | |
| echo "AGGREGATE_GOODPUT:$rate" | |
| else | |
| echo $rate | |
| fi | |
| else | |
| flow_cnt=1 | |
| while [ $flow_cnt -le $flows ] ; do | |
| (iperf3 -s -p $port -1 > /dev/null 2>&1) & | |
| ( iperf3 -c $host -p $port -i 0 -P 1 -f m -t $dur | grep receiver | grep -o "[0-9.]* Mbits" | grep -o "^[0-9]*" | grep -o "[0-9]*$" > iperf3.$id.$flow_cnt ) & | |
| port=$[port+1] | |
| flow_cnt=$[flow_cnt+1] | |
| done | |
| n=$[dur+1] | |
| sleep $n | |
| flow_cnt=1 | |
| rate=0 | |
| if [ $details -ne 0 ] ; then | |
| echo "" | |
| echo "Details for HBM in cgroup $id" | |
| if [ $do_stats -eq 1 ] ; then | |
| if [ -e hbm.$id.$dir_name ] ; then | |
| cat hbm.$id.$dir_name | |
| fi | |
| fi | |
| fi | |
| while [ $flow_cnt -le $flows ] ; do | |
| r=`cat iperf3.$id.$flow_cnt` | |
| # echo "rate for flow $flow_cnt: $r" | |
| if [ $details -ne 0 ] ; then | |
| echo "Rate for cgroup $id, flow $flow_cnt LOCAL_SEND_THROUGHPUT=$r" | |
| fi | |
| rate=$[rate+r] | |
| flow_cnt=$[flow_cnt+1] | |
| done | |
| if [ $details -ne 0 ] ; then | |
| delay=`grep "avg" ping.out | grep -o "= [0-9.]*/[0-9.]*" | grep -o "[0-9.]*$"` | |
| echo "PING AVG DELAY:$delay" | |
| echo "AGGREGATE_GOODPUT:$rate" | |
| else | |
| echo $rate | |
| fi | |
| fi | |
| if [ $use_netperf -eq 0 ] ; then | |
| sysctl -w -q -n net.ipv4.tcp_congestion_control=$cur_cc | |
| fi | |
| if [ $ecn -ne 0 ] ; then | |
| sysctl -w -q -n net.ipv4.tcp_ecn=0 | |
| fi | |
| if [ "$netem" -ne "0" ] ; then | |
| tc qdisc del dev lo root > /dev/null 2>&1 | |
| fi | |
| if [ "$qdisc" != "" ] ; then | |
| tc qdisc del dev eth0 root > /dev/null 2>&1 | |
| fi | |
| sleep 2 | |
| hbmPid=`ps ax | grep "hbm " | grep --invert-match "grep" | awk '{ print $1 }'` | |
| if [ "$hbmPid" == "$hbm_pid" ] ; then | |
| kill $hbm_pid | |
| fi | |
| sleep 1 | |
| # Detach any pinned BPF programs that may have lingered | |
| rm -rf $BPFFS/hbm* | |
| if [ $use_netperf -ne 0 ] ; then | |
| if [ "$server" == "" ] ; then | |
| if [ "$begNetserverPid" == "" ] ; then | |
| netserverPid=`ps ax | grep netserver | grep --invert-match "grep" | awk '{ print $1 }'` | |
| if [ "$netserverPid" != "" ] ; then | |
| kill $netserverPid | |
| fi | |
| fi | |
| fi | |
| fi | |
| exit |