Skip to content

Instantly share code, notes, and snippets.

@xrat
Last active Jul 27, 2022
Embed
What would you like to do?
Calculate time needed to send 1 byte back and forth within an SSH connection
#!/bin/bash
#
me=sshpingpong
#
# Measure close to minimal packet latency (RTT) of an SSH connection
#
prgversion="$me * 2022-07-27 (c) Andreas Schamanek"
#
# @author Andreas Schamanek <https://andreas.schamanek.net>
# @license GPL <https://www.gnu.org/licenses/gpl.html>
# @copyright (c) 2022 Andreas Schamanek
#
usage="$prgversion
$me sshpingpong [-L] [-t] [-c N] [-i S] {sshlogin@host}
-c N ... quit after N measurements
-i S ... wait S seconds between pings; can be fractional, default 5s
-t ... terse/compact mode using _, 1, 2, ... for hundreds of ms
where 1 means 100<=RTT<200, 2 means 200<=RTT<300, ...
-L ... do not write to logfile
Cf. https://serverfault.com/q/807910/23900
"
while [[ $1 == -* ]] ; do
case $1 in
-t) compactmode=yes; shift;;
-c) [[ $2 != *[!0-9]* ]] || { echo "invalid count" >&2; exit 1; }
count="$2"; shift 2;;
-i) [[ $2 != *[!0-9.-]* ]] || { echo "invalid interval" >&2; exit 1; }
sleep="$2"; shift 2;;
-L) logfile=/dev/null; shift;;
-\?|-h|--help) echo "$usage"; exit 0;;
esac
done
ssh="${1:?$me error: missing argument}"
if [[ -z $logfile ]] ; then
logfile="${ssh#*@}"; logfile="spp_${logfile//[: .]/_}".log
echo "Logfile is $logfile"
elif [[ $logfile == /dev/null ]] ; then
logfile=
fi
declare -i c=-1 now=0 sent=0 rcvd=0 rtt=0 count="${count:-0}"
declare rttms=0.0
: "${sleep:=5}"
: "${initialsleep:=2}"
# compact mode factor 1 or 10: cf=10 will make _, 1, 2, 3, ... indicate RTTs
# of <10, <20, <30, ...; w/ cf=1 it will be <100, <200, <300, ...
: "${cf:=1}"
: "${trapsigs:="INT TERM EXIT"}"
if [[ -d $XDG_RUNTIME_DIR ]] ; then
mkdir -p "$XDG_RUNTIME_DIR/$me"
fifo="$XDG_RUNTIME_DIR/$me/$$"
else
fifo="$HOME/fifo$$"
fi
set_now() { now=$(date +%s%N); now="${now%???}"; } # microseconds
compactmodeoutput() {
if ((cf*rtt<100000)) ; then echo -n _ >&2 ; return; fi
if ((cf*rtt>999999)) ; then echo -n "#" >&2 ; return; fi
echo -n "$((cf*rtt/100000))" >&2
}
# logline() also printing "seq=$c" at the time it was sent
#logline() { printf '%(%Y-%m-%d %T)T seq=%d %.3f\n' "${sent%??????}" "$c" "$rttms" ; }
# logline() just printing the RTT at the time the data was received
logline() { printf '%(%Y-%m-%d %T)T %.3f\n' "${rcvd%??????}" "$rttms" ; }
trap "trapexit" $trapsigs
trapexit() {
trap '' $trapsigs
[[ ! -e $fifo ]] || rm "$fifo"
if [[ -z "$logfile" ]] ; then
printf '\n' >&2
else
printf '\nLogfile is %s\n' "$logfile" >&2
fi
exit
}
[[ ! -e $fifo ]] || rm "$fifo"
mkfifo "$fifo"
# compact mode header and output redirection
if [[ -z $compactmode ]] ; then
[[ -z "$logfile" ]] || exec 1> >(tee -a "$logfile")
else
# in compact mode, if no logging is requested we need to redirect to null
# so that logline() is silenced
if [[ -z "$logfile" ]] ; then
exec 1>>/dev/null
else
exec 1>>"$logfile"
fi
if ((cf==10)) ; then
echo "0 _ 10 1 20 2 30 3 40 4 50 5 60 6 70 7 80 8 90 9 99 # ..." >&2
else
echo "0 _ 100 1 200 2 300 3 400 4 500 5 600 6 700 7 800 8 900 9 999 # ..." >&2
fi
fi
mepid="$$"
( sleep "$initialsleep"; echo "ignore initial ping" >"$fifo"; ) &
cat 0<> "$fifo" | ssh $sshargs $ssh cat \
| while read -r R ; do
set_now; rcvd="$now"
rtt="$((rcvd-sent))"; rttms="${rtt%???}.${rtt: -3}"
if [[ $R == o ]] ; then
logline
[[ -z $compactmode ]] || compactmodeoutput
sleep "$sleep"
fi
if ((count>0 && c>=count)) ; then
# found no nicer way to make sure $ssh ends
# and w/o kill the subshell would never exit
pkill -2 --parent "$mepid"
exit
fi
c=$((c+1))
echo 'o' >"$fifo"
set_now; sent="$now"
done
@xrat
Copy link
Author

xrat commented Jul 27, 2022

In Version 2022-07-27 I mostly changed option -c (compact mode) to -t (terse/compact mode) and introduced -c N to quit the script after N ping-pongs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment