diff --git a/bin/mesh-speedtest b/bin/mesh-speedtest new file mode 100755 index 0000000..7b4295a --- /dev/null +++ b/bin/mesh-speedtest @@ -0,0 +1,113 @@ +#!/bin/sh +# mesh-speedtest — quick throughput probe across the wg1 mesh. +# +# Default: from current host, pull a chunk from each peer over ssh and report +# Mbps + a streaming verdict. Bytes traverse the wg1 tunnel; ssh adds a thin +# layer of double-encryption, so numbers run a few % below raw wg throughput +# but are stable for "can I stream from here?" decisions. +# +# Usage: +# mesh-speedtest # download from each peer (64 MiB) +# mesh-speedtest -s 256 # use 256 MiB chunks (slower, more stable) +# mesh-speedtest -u # upload (push to each peer) instead +# mesh-speedtest -p plum,black # restrict to specific peers +# +# Peers are the wg1 mesh: apricot, black, plum, quinn-vps. Self is skipped. + +set -eu + +size_mib=64 +direction=down +peer_filter="" + +while [ $# -gt 0 ]; do + case $1 in + -s) size_mib=$2; shift 2 ;; + -u) direction=up; shift ;; + -p) peer_filter=$2; shift 2 ;; + -h|--help) sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) echo "unknown arg: $1" >&2; exit 2 ;; + esac +done + +all_peers="apricot black plum quinn-vps" + +self=$(hostname -s 2>/dev/null || hostname) +case $self in + plum|*plum*) self=plum ;; + apricot*) self=apricot ;; + black*) self=black ;; + quinn-vps*|*1984*) self=quinn-vps ;; +esac + +verdict() { + mbps=$1 + awk -v m="$mbps" 'BEGIN{ + if (m >= 40) print "4K HDR ok"; + else if (m >= 25) print "4K ok"; + else if (m >= 12) print "1080p high ok"; + else if (m >= 5) print "1080p ok"; + else if (m >= 3) print "720p ok"; + else if (m >= 1.5) print "480p / rsync first for HD"; + else print "rsync only"; + }' +} + +# Run one direction against one peer. Echo " " or " ERR". +probe() { + peer=$1 + bytes=$((size_mib * 1024 * 1024)) + start=$(date +%s.%N 2>/dev/null || date +%s) + if [ "$direction" = down ]; then + ssh -o ConnectTimeout=5 -o BatchMode=yes "$peer" \ + "dd if=/dev/zero bs=1M count=$size_mib status=none" \ + > /dev/null 2>/tmp/mesh-speedtest.$$.$peer.err || { echo "$peer ERR"; return; } + else + dd if=/dev/zero bs=1M count=$size_mib status=none 2>/dev/null \ + | ssh -o ConnectTimeout=5 -o BatchMode=yes "$peer" \ + "dd of=/dev/null bs=1M status=none" \ + 2>/tmp/mesh-speedtest.$$.$peer.err || { echo "$peer ERR"; return; } + fi + end=$(date +%s.%N 2>/dev/null || date +%s) + secs=$(awk -v s="$start" -v e="$end" 'BEGIN{d=e-s; if(d<0.001)d=0.001; printf "%.3f",d}') + mbps=$(awk -v b="$bytes" -v t="$secs" 'BEGIN{printf "%.1f", (b*8)/(t*1000000)}') + echo "$peer $mbps $secs" +} + +# Build peer list. +peers="" +if [ -n "$peer_filter" ]; then + peers=$(echo "$peer_filter" | tr ',' ' ') +else + for p in $all_peers; do + [ "$p" = "$self" ] && continue + peers="$peers $p" + done +fi + +label=$([ "$direction" = down ] && echo "download from" || echo "upload to") +printf "mesh-speedtest: %s peers, %d MiB chunks (self=%s)\n\n" "$label" "$size_mib" "$self" +printf "%-12s %10s %8s %s\n" "peer" "Mbps" "secs" "verdict" +printf "%-12s %10s %8s %s\n" "----" "----" "----" "-------" + +# Run probes sequentially; parallel would skew Mbps via shared uplink. +for p in $peers; do + out=$(probe "$p") + case $out in + *ERR) + err=$(cat /tmp/mesh-speedtest.$$.$p.err 2>/dev/null | head -1) + printf "%-12s %10s %8s %s\n" "$p" "-" "-" "ssh failed: ${err:-unreachable}" + ;; + *) + peer_n=$(echo "$out" | awk '{print $1}') + mbps=$(echo "$out" | awk '{print $2}') + secs=$(echo "$out" | awk '{print $3}') + v=$(verdict "$mbps") + printf "%-12s %10s %8s %s\n" "$peer_n" "$mbps" "$secs" "$v" + ;; + esac + rm -f /tmp/mesh-speedtest.$$.$p.err +done + +# Average across reachable peers (informational only — wildly different paths). +echo ""