#!/bin/sh
# @(#) sping.sh 1.30@(#) 95/01/22 #

# Script to draw continuously-updated histogram of ping delays to a 
# given remote host.  Designed for Solaris 1.

# This code is copyright (c) ExNet Systems Ltd 1994, but you may freely
# use, modify or redistribute it providing you retain this copyright notice
# intact and note in the table below any changes you have made.
#
# THIS PRODUCT IS PROVIDED ``AS IS'' AND WE MAKE NO REPRESENTATIONS AS
# TO ITS USABILITY AND WILL NOT ACCEPT LIABILITY FOR ANY CONSEQUENCES OF
# ITS USE.
#
# -----------------+----------+------------------------------------------------
# Changed By       | When     | Description of change(s) made and
# Name and/or email| YY/MM/DD | reasons for change.
# =================+==========+================================================
# Damon Hart-Davis | 94/10/02 | Example change report, stored
#                  |          | oldest first.  Note how the change
#                  |          | report can take multiple lines.
# -----------------+----------+------------------------------------------------
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
#                  |          | 
# -----------------+----------+------------------------------------------------

# Usage:
#
#     sping host [ maxms [ buckets [ memory [ interval ] ] ] ]
#
# Where `host' is the remote host, `maxms' is the maximum rtt in ms to
# collect packets for, `buckets' is the number of buckets to collect
# packet times in (one more will be used to collect any out of range and
# one for any that go missing), `memory' is the number of ping results to
# collect for the calculations (RTT values older than this are
# discarded), and `interval' is the number of ping results to collect
# before updating the display.
#
# If interval is 0 then `memory' ping results are collected and
# one display is produced, and the script exits, else after every
# interval ping the screen is cleared and a new display is sent.

# Defaults are supplied for everything but the `host' if needed.

# The script has an exit value of 2 for some `system' failure like
# not being able to find the ping command or being passed the
# wrong number of arguments.
#
# The script returns an exit value of 1 in the case of some
# run-time (possibly temporary failure) like the remote host
# being down altogether.
#
# The script returns 0 in case of success, or may not exit at all.
#
# BUGS
#
# For now the memory feature is not implemented---old values are
# not forgotten.
#

if [ $# -lt 1 ]; then
    echo $# arguments passed---between 1 and 5 expected.
    echo usage: $0 'host [ maxms [ buckets [ memory [ interval ] ] ] ]'
    exit 2
fi

MAXMS=1900          # Approx max acceptable RTT for interactive connections.
BUCKETS=19          # Chosen to work with 24-line (eg VT100) display.
MEMORY=120          # Last 2-minutes' worth.
INTERVAL=10         # Update the screen approx every 20 sec.

HOST=$1
test $# -gt 1 && MAXMS=$2
test $# -gt 2 && BUCKETS=$3
test $# -gt 3 && MEMORY=$4
test $# -gt 4 && INTERVAL=$5

echo 'Sping V1.30 (c) ExNet Systems Ltd 1994.'
echo Parameters: $HOST $MAXMS $BUCKETS $MEMORY $INTERVAL

if [ -f /usr/sbin/ping ]; then
    PING=/usr/sbin/ping;
elif [ -f /usr/etc/ping ]; then
    PING=/usr/etc/ping;
else
    echo Cannot find ping.;
    exit 2;
fi

# Send a continuous stream of pings, 1 per second, minimal size
# for ping to be able to compute the round-trip time.
#
# We expect useful lines from ping to be in the format:
#
#    16 bytes from 158.43.71.1: icmp_seq=46. time=140. ms
#
# and in particular we expect $(NF - 1) == "ms",
# $(NF - 2) to be the round-trip time in ms preceeded by "time=", and
# $(NF - 3) to be the packet sequence number preceded by "icmp_seq=".
#
$PING -s $HOST 8 | \
# We use (n)awk to collect and process the statistics for us in real time.
#
# NEXT_SEQ is the next sequence number we expect for packet announced
# by ping; anything less is ignored, and anything higher is taken to
# indicate one or more missing packets.
#
# LAST_DISP is the sequence number at which we last did a display;
# if the new sequence number is >= LAST_DISP + INTERVAL, do a
# (re)display after updating the stats.
#
# THIS_SEQ is the sequence number of the current ping result, and
# THIS_RTT is the RTT of the current ping result; these two are
# computed by compute_rtt_seq().
#
# MAX_RTT and AVE_RTT are the maxmium and average RTTs on the
# current remembered set of values.  TOTAL_PKT is the total
# number of packets counted (ignoring dropped backets) and
# TOTAL_RTT is the total RTT of the last TOTAL_PKT packets.
# AVE_RTT is only recalculated just before each display.
#
# We remember old values in the memory[] array, storing the
# RTT in ms (+1), M_UNINIT being used to indicate an uninitialised
# value and M_MISSING a probe that went missing.  The position of a
# value in the memory array is its sequence number modulo the
# array size (MEMORY).  When we get a new value we remove the
# effect of any existing value in the given slot (and any slots
# for missing probes we have skipped) and put the new value
# into the slot.  Rounding errors might cause us some fun.
#
# M_BUCKET is the bucket in which we count missing packets, and
# O_BUCKET is the bucket in which we count out-of-range packets.
# The buckets are stored in bucket[], with values numbered from
# -2 to BUCKETS-1.
#
# WIDTH is the presumed screen width.
#
    nawk 'BEGIN { FAIL=-1; OK=0;
            NEXT_SEQ=0;
            LAST_DISP=-1;
            M_BUCKET=-2; O_BUCKET=-1;
            M_UNINIT = 0; M_MISSING = -1;
            WIDTH=80;
            TOTAL_PKT = TOTAL_RTT = 0;
            NORMAL_BAR =  "---------+";
            SPECIAL_BAR = "=========*"
            }
        function hist_bar(width, maxval, val, barstr) {
            hist_bar_length = int((width - 1) * (val / maxval) + 0.5);
            if(hist_bar_length >= width) { hist_bar_length = width - 1; }
            if(hist_bar_length == 0) { return(""); }
            hist_bar_str = barstr "";
            if(hist_bar_str == "") { hist_bar_str = "**********************"; }
            while(length(hist_bar_str) < hist_bar_length) {
                hist_bar_str = hist_bar_str hist_bar_str;
                }
            hist_bar_str = substr(hist_bar_str, 1, hist_bar_length);
            return(hist_bar_str);
            }
        function print_bar(barnum, label, maxval, barstr) {
            printf("%9.9s %3d%% %s\n", \
                label, \
                TOTAL_PKT?int((bucket[barnum] * 100) / TOTAL_PKT + 0.5):0, \
                hist_bar(WIDTH - 15, maxval, bucket[barnum], barstr));
            }
        function which_bucket(rtt) {
            which_bucket_tmp = int((rtt * BUCKETS) / (MAXMS + 1));
            if(which_bucket_tmp >= BUCKETS) { return(O_BUCKET); }
            return(which_bucket_tmp);
            }
        function display() {
            bucket_width = MAXMS / BUCKETS;
            ave_bucket = which_bucket(AVE_RTT);
            printf("\f");
            printf("Pings attempted: %d.  ", THIS_SEQ + 1);
                printf("Ave RTT: %.3fms.  Max RTT: %.3fms.\n", \
                    AVE_RTT, MAX_RTT);
            maxbar = 0;
            for(i = -2; i < BUCKETS; ++i) {
                if(bucket[i] > maxbar) { maxbar = bucket[i]; }
                }
            for(i = -2; i <= BUCKETS; ++i) {
                if(i == ave_bucket) { t = SPECIAL_BAR; }
                else { t = NORMAL_BAR; }
                if(i == M_BUCKET) { print_bar(i, "Missing", maxbar, ""); }
                else if(i == BUCKETS) {
                    s = sprintf(">%dms", MAXMS);
                    print_bar(O_BUCKET, s, maxbar, t);
                    }
                else if(i == O_BUCKET) { continue; }
                else {
                    s = sprintf("--%dms", int(bucket_width * (i+1)));
                    print_bar(i, s, maxbar, t);
                    }
                }
            printf("Host: %s.  ", HOST);
                printf("Display interval: %d.  ", INTERVAL);
                printf("Memory %d pings.\n", MEMORY);
            }
        function compute_rtt_seq() {
            if ($NF != "ms") {print "Line not understood: " $0; return(FAIL);}
            THIS_RTT = 0 + substr($(NF-1), 6);
            THIS_SEQ = 0 + int(substr($(NF-2), 10));
            return(OK);
            }
        /time=/ {
            if(compute_rtt_seq() != OK) { next; }
            if(THIS_SEQ < NEXT_SEQ) { next; }
            if(THIS_RTT < 0) { next; }
            if(THIS_SEQ > NEXT_SEQ) {bucket[M_BUCKET] += THIS_SEQ - NEXT_SEQ;}
            for(i = NEXT_SEQ; i <= THIS_SEQ; ++i)
                {
                slot = i % MEMORY;
                if(memory[slot] == M_MISSING) { --bucket[M_BUCKET]; }
                else if(memory[slot] != M_UNINIT) {
                    old_rtt = memory[slot] - 1;
                    --bucket[which_bucket(old_rtt)];
                    --TOTAL_PKT; TOTAL_RTT -= old_rtt;
                    }
                memory[slot] = M_MISSING;
                }
            memory[THIS_SEQ % MEMORY] = THIS_RTT + 1;
            ++bucket[which_bucket(THIS_RTT)];
            ++TOTAL_PKT; TOTAL_RTT += THIS_RTT;
            ++REAL_TOTAL_PKT;
            if(THIS_RTT > MAX_RTT) { MAX_RTT = THIS_RTT; }
            if(THIS_SEQ >= LAST_DISP + INTERVAL) {
                AVE_RTT = TOTAL_RTT / TOTAL_PKT;
                display(); 
                LAST_DISP = THIS_SEQ;
                }
            NEXT_SEQ = 1 + THIS_SEQ;
            }' \
    HOST=$HOST \
    MAXMS=$MAXMS \
    BUCKETS=$BUCKETS \
    MEMORY=$MEMORY \
    INTERVAL=$INTERVAL
