Last active
March 16, 2023 21:44
-
-
Save xrat/a3df5ff7c63f5c648708825f45372ea9 to your computer and use it in GitHub Desktop.
Calculate time needed to send 1 byte back and forth within an SSH connection
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 The best thing is to use "#!/usr/bin/env bash" because I have a bash 5.5 installed from macports, and bash doesn't even exist in macOS anymore.
For the date, that is fine, for mac users with macPorts and the coreutils installed they just need to replace "date" with "gdate"
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@gullevek I have no macOS available nor any experience with it. My script is written for Bash on Linux only, I am afraid. I know that
%N
is not available everywhere, though it's trending ;) However, the%()T
format forprintf
is a Bashism. It was introduced with Bash 4.4 and should be available on platforms.