diff --git a/prettyping b/prettyping index b6e7881..3b57ddf 100755 --- a/prettyping +++ b/prettyping @@ -7,40 +7,6 @@ # * bash (tested on 4.20, should work on older versions too) # * awk (works with GNU awk, nawk, busybox awk, mawk) # * ping (from iputils) -# -# More information: -# https://denilsonsa.github.io/prettyping/ -# https://github.com/denilsonsa/prettyping -# https://old.reddit.com/r/linux/comments/1op98a/prettypingsh_a_better_ui_for_watching_ping/ -# Third-party demonstration video: https://www.youtube.com/watch?v=ziEMY1BcikM - -# TODO: Test in original-awk: -# https://aur.archlinux.org/packages/original-awk -# https://sources.debian.org/src/original-awk/ -# TODO: Adjust how many items in the legend are printed based on the terminal width. -# -# TODO: Detect the following kinds of message and avoid printing it repeatedly. -# From 192.168.1.11: icmp_seq=4 Destination Host Unreachable -# Request timeout for icmp_seq 378 -# -# TODO: Handle when a single message is spread over multiple lines. Specially, -# like in this case: https://bitbucket.org/denilsonsa/small_scripts/issue/5 -# -# TODO: Print the destination (also) at the bottom bar. Useful after leaving -# the script running for quite some time. -# -# TODO: Print the destination as escape codes to xterm title. -# -# TODO: Print the current time in the beginning of each line. -# -# TODO: Implement audible ping. -# -# TODO: Autodetect the width of printf numbers, so they will always line up correctly. -# -# TODO: Test the behavior of this script upon receiving out-of-order packets, like these: -# https://web.archive.org/web/20130531083142/http://www.blug.linux.no/rfc1149/pinglogg.txt -# -# TODO? How will prettyping behave if it receives a duplicate response? print_help() { cat << EOF @@ -51,24 +17,24 @@ each ping response line by a colored character, giving a very compact overview of the ping responses. prettyping parameters: - --[no]color Enable/disable color output. (default: enabled) - --[no]multicolor Enable/disable multi-color unicode output. Has no effect if - either color or unicode is disabled. (default: enabled) - --[no]unicode Enable/disable unicode characters. (default: enabled) - --[no]legend Enable/disable the latency legend. (default: enabled) - --[no]globalstats Enable/disable the global statistics line. (default: enabled) - --[no]recentstats Enable/disable the "last n" statistics line. (default: enabled) - --[no]terminal Force the output designed to a terminal. (default: auto) - --last Use the last "n" pings at the statistics line. (default: 60) - --columns Override auto-detection of terminal dimensions. - --lines Override auto-detection of terminal dimensions. - --rttmin Minimum RTT represented in the unicode graph. (default: auto) - --rttmax Maximum RTT represented in the unicode graph. (default: auto) - --awkbin Override the awk interpreter. (default: awk) - --pingbin Override the ping tool. (default: ping) + --[no]color Enable/disable color output. (default: enabled) + --[no]multicolor Enable/disable multi-color unicode output. Has no effect if + either color or unicode is disabled. (default: enabled) + --[no]unicode Enable/disable unicode characters. (default: enabled) + --[no]legend Enable/disable the latency legend. (default: enabled) + --[no]terminal Force the output designed to a terminal. (default: auto) + --last Use the last "n" pings at the statistics line. (default: 60) + --columns Override auto-detection of terminal dimensions. + --lines Override auto-detection of terminal dimensions. + --rttmin Minimum RTT represented in the unicode graph. (default: auto) + --rttmax Maximum RTT represented in the unicode graph. (default: auto) + --awkbin Override the awk interpreter. (default: awk) + --pingbin Override the ping tool. (default: ping) + --time Print the current time at the beginning of each line (requires gawk). + -a, --audible Play the terminal bell on successful ping. + -6 Shortcut for: --pingbin ping6 ping parameters handled by prettyping: - -a Audible ping is not implemented yet. -f Flood mode is not allowed in prettyping. -q Quiet output is not allowed in prettyping. -R Record route mode is not allowed in prettyping. @@ -78,15 +44,13 @@ All other parameters are passed directly to ping. EOF } -# Thanks to people at #bash who pointed me at -# https://web.archive.org/web/20100301171512/https://bash-hackers.org/wiki/doku.php/scripting/posparams parse_arguments() { USE_COLOR=1 USE_MULTICOLOR=1 USE_UNICODE=1 USE_LEGEND=1 - USE_GLOBALSTATS=1 - USE_RECENTSTATS=1 + PRINT_TIME=0 + AUDIBLE=0 if [ -t 1 ]; then IS_TERMINAL=1 @@ -101,7 +65,6 @@ parse_arguments() { RTT_MAX=auto PING_BIN="ping" - #PING_BIN="./mockping.awk" PING_PARAMS=( ) AWK_BIN="awk" @@ -114,152 +77,100 @@ parse_arguments() { exit ;; - # Forbidden ping parameters within prettyping: - -f ) - echo "${MYNAME}: You can't use the -f (flood) option." - exit 1 - ;; - -R ) - # -R prints extra information at each ping response. - echo "${MYNAME}: You can't use the -R (record route) option." - exit 1 - ;; - -q ) - echo "${MYNAME}: You can't use the -q (quiet) option." - exit 1 - ;; - -v ) - # -v enables verbose output. However, it seems the output with - # or without this option is the same. Anyway, prettyping will - # strip this parameter. - ;; - # Note: - # Small values for -s parameter prevents ping from being able to - # calculate RTT. - - # New parameters: - -a ) - # TODO: Implement audible ping for responses or for missing packets - ;; - - -color | --color ) USE_COLOR=1 ;; - -nocolor | --nocolor ) USE_COLOR=0 ;; - -multicolor | --multicolor ) USE_MULTICOLOR=1 ;; - -nomulticolor | --nomulticolor ) USE_MULTICOLOR=0 ;; - -unicode | --unicode ) USE_UNICODE=1 ;; - -nounicode | --nounicode ) USE_UNICODE=0 ;; - -legend | --legend ) USE_LEGEND=1 ;; - -nolegend | --nolegend ) USE_LEGEND=0 ;; - -globalstats | --globalstats ) USE_GLOBALSTATS=1 ;; - -noglobalstats | --noglobalstats ) USE_GLOBALSTATS=0 ;; - -recentstats | --recentstats ) USE_RECENTSTATS=1 ;; - -norecentstats | --norecentstats ) USE_RECENTSTATS=0 ;; - -terminal | --terminal ) IS_TERMINAL=1 ;; - -noterminal | --noterminal ) IS_TERMINAL=0 ;; - - -awkbin | --awkbin ) AWK_BIN="$2" ; shift ;; - -pingbin | --pingbin ) PING_BIN="$2" ; shift ;; - - #TODO: Check if these parameters are numbers. - -last | --last ) LAST_N="$2" ; shift ;; - -columns | --columns ) OVERRIDE_COLUMNS="$2" ; shift ;; - -lines | --lines ) OVERRIDE_LINES="$2" ; shift ;; - -rttmin | --rttmin ) RTT_MIN="$2" ; shift ;; - -rttmax | --rttmax ) RTT_MAX="$2" ; shift ;; - - * ) - PING_PARAMS+=("$1") - ;; + -f ) echo "${MYNAME}: You can't use the -f (flood) option." >&2; exit 1 ;; + -R ) echo "${MYNAME}: You can't use the -R (record route) option." >&2; exit 1 ;; + -q ) echo "${MYNAME}: You can't use the -q (quiet) option." >&2; exit 1 ;; + -v ) ;; + + -a | --audible ) AUDIBLE=1 ;; + --time ) PRINT_TIME=1 ;; + + -color | --color ) USE_COLOR=1 ;; + -nocolor | --nocolor ) USE_COLOR=0 ;; + -multicolor | --multicolor ) USE_MULTICOLOR=1 ;; + -nomulticolor | --nomulticolor ) USE_MULTICOLOR=0 ;; + -unicode | --unicode ) USE_UNICODE=1 ;; + -nounicode | --nounicode ) USE_UNICODE=0 ;; + -legend | --legend ) USE_LEGEND=1 ;; + -nolegend | --nolegend ) USE_LEGEND=0 ;; + -terminal | --terminal ) IS_TERMINAL=1 ;; + -noterminal | --noterminal ) IS_TERMINAL=0 ;; + -6 ) PING_BIN="ping6" ;; + + -awkbin | --awkbin ) if [[ -z "${2:-}" ]]; then echo "${MYNAME}: $1 requires an argument." >&2; exit 1; fi; AWK_BIN="$2"; shift ;; + -pingbin | --pingbin ) if [[ -z "${2:-}" ]]; then echo "${MYNAME}: $1 requires an argument." >&2; exit 1; fi; PING_BIN="$2"; shift ;; + -last | --last ) if [[ -z "${2:-}" ]]; then echo "${MYNAME}: $1 requires an argument." >&2; exit 1; fi; LAST_N="$2"; shift ;; + -columns | --columns ) if [[ -z "${2:-}" ]]; then echo "${MYNAME}: $1 requires an argument." >&2; exit 1; fi; OVERRIDE_COLUMNS="$2"; shift ;; + -lines | --lines ) if [[ -z "${2:-}" ]]; then echo "${MYNAME}: $1 requires an argument." >&2; exit 1; fi; OVERRIDE_LINES="$2"; shift ;; + -rttmin | --rttmin ) if [[ -z "${2:-}" ]]; then echo "${MYNAME}: $1 requires an argument." >&2; exit 1; fi; RTT_MIN="$2"; shift ;; + -rttmax | --rttmax ) if [[ -z "${2:-}" ]]; then echo "${MYNAME}: $1 requires an argument." >&2; exit 1; fi; RTT_MAX="$2"; shift ;; + + * ) PING_PARAMS+=("$1") ;; esac shift done - if [[ "${RTT_MIN}" -gt 0 && "${RTT_MAX}" -gt 0 && "${RTT_MIN}" -ge "${RTT_MAX}" ]] ; then - echo "${MYNAME}: Invalid --rttmin and -rttmax values." - exit 1 + if [[ "${RTT_MIN}" =~ ^[0-9]+$ && "${RTT_MAX}" =~ ^[0-9]+$ ]] ; then + if [[ "${RTT_MIN}" -ge "${RTT_MAX}" ]] ; then + echo "${MYNAME}: Invalid --rttmin and --rttmax values." >&2 + exit 1 + fi fi if [[ "${#PING_PARAMS[@]}" = 0 ]] ; then - echo "${MYNAME}: Missing parameters, use --help for instructions." + echo "${MYNAME}: Missing parameters, use --help for instructions." >&2 exit 1 fi - # Workaround for mawk: - # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=593504 local version="$(echo | "${AWK_BIN}" -W version 2>&1)" if [[ "${version}" == mawk* ]] ; then AWK_PARAMS+=(-W interactive) fi } -MYNAME=`basename "$0"` +MYNAME=$(basename "$0") parse_arguments "$@" - export LC_ALL=C +export USE_COLOR USE_MULTICOLOR USE_UNICODE USE_LEGEND IS_TERMINAL LAST_N +export OVERRIDE_COLUMNS OVERRIDE_LINES RTT_MIN RTT_MAX -# Warning! Ugly code ahead! -# The code is so ugly that the comments explaining it are -# bigger than the code itself! -# -# Suppose this: -# -# cmd_a | cmd_b & -# -# I need the PID of cmd_a. How can I get it? -# In bash, $! will give me the PID of cmd_b. -# -# So, I came up with this ugly solution: open a subshell, like this: -# -# ( -# cmd_a & -# echo "This is the PID I want $!" -# wait -# ) | cmd_b - - -# Ignore Ctrl+C here. -# If I don't do this, this shell script is killed before -# ping and gawk can finish their work. trap '' 2 -# Now the ugly code. -( +run_ping() { "${PING_BIN}" "${PING_PARAMS[@]}" & PING_PID="$!" + trap 'kill "$PING_PID" 2>/dev/null' EXIT + wait "$PING_PID" +} - # Commented out, because it looks like this line is not needed - #trap "kill -2 $PING_PID ; exit 1" 2 # Catch Ctrl+C here - - wait -) 2>&1 | ( +filter_winch() { if [ "${IS_TERMINAL}" = 1 ]; then - # Print a message to notify the awk script about terminal size change. - trap "echo SIGWINCH" 28 + ( + trap "echo SIGWINCH" WINCH + while IFS= read -r line; do + printf "%s\n" "$line" + done + ) + else + cat fi +} + +run_ping 2>&1 | filter_winch 2>&1 | "${AWK_BIN}" "${AWK_PARAMS[@]}" -v PRINT_TIME="$PRINT_TIME" -v AUDIBLE="$AUDIBLE" ' - # The trap must be in another subshell because otherwise it will interrupt - # the "wait" commmand. - while read line; do - echo -E "$line" - done -) 2>&1 | "${AWK_BIN}" "${AWK_PARAMS[@]}" ' -# Weird that awk does not come with abs(), so I need to implement it. function abs(x) { return ( (x < 0) ? -x : x ) } -# Ditto for ceiling function. function ceil(x) { return (x == int(x)) ? x : int(x) + 1 } -# Local variables MUST be declared in argument list, else they are -# seen as global. Ugly, but that is how awk works. function get_terminal_size(SIZE, SIZEA, HAS_DETECTED, CMD) { HAS_DETECTED = 0 - CMD = "stty -f /dev/tty size 2> /dev/null" + CMD = "stty size < /dev/tty 2> /dev/null" if ( (CMD | getline SIZE) == 1 ) { split(SIZE, SIZEA, " ") @@ -285,20 +196,13 @@ function get_terminal_size(SIZE, SIZEA, HAS_DETECTED, CMD) { close(CMD) } - if ( int('"${OVERRIDE_COLUMNS}"') ) { COLUMNS = int('"${OVERRIDE_COLUMNS}"') } - if ( int('"${OVERRIDE_LINES}"') ) { LINES = int('"${OVERRIDE_LINES}"') } + if ( int(ENVIRON["OVERRIDE_COLUMNS"]) ) { COLUMNS = int(ENVIRON["OVERRIDE_COLUMNS"]) } + if ( int(ENVIRON["OVERRIDE_LINES"]) ) { LINES = int(ENVIRON["OVERRIDE_LINES"]) } } -############################################################ -# Functions related to cursor handling - -# Function called whenever a non-dotted line is printed. -# -# It will move the cursor to the line next to the statistics and -# restore the default color. function other_line_is_printed() { if ( IS_PRINTING_DOTS ) { - if ( '"${IS_TERMINAL}"' ) { + if ( IS_TERMINAL ) { printf( ESC_DEFAULT ESC_NEXTLINE ESC_NEXTLINE "\n" ) } else { printf( ESC_DEFAULT "\n" ) @@ -309,24 +213,22 @@ function other_line_is_printed() { CURR_COL = 0 } -# Function called whenever a non-dotted line is repeated. function other_line_is_repeated() { if ( other_line_times < 2 ) { return } - if ( '"${IS_TERMINAL}"' ) { + if ( IS_TERMINAL ) { printf( ESC_DEFAULT ESC_ERASELINE "\r" ) } printf( "Last message repeated %d times.", other_line_times ) - if ( ! '"${IS_TERMINAL}"' ) { + if ( ! IS_TERMINAL ) { printf( "\n" ) } } -# Function called whenever the repeating line has changed. function other_line_finished_repeating() { if ( other_line_times >= 2 ) { - if ( '"${IS_TERMINAL}"' ) { + if ( IS_TERMINAL ) { printf( "\n" ) } else { other_line_is_repeated() @@ -336,15 +238,8 @@ function other_line_finished_repeating() { other_line_times = 0 } -# Prints the newlines required for the live statistics. -# -# I need to print some newlines and then return the cursor back to its position -# to make sure the terminal will scroll. -# -# If the output is not a terminal, break lines on every LAST_N dots. function print_newlines_if_needed() { - if ( '"${IS_TERMINAL}"' ) { - # COLUMNS-1 because I want to avoid bugs with the cursor at the last column + if ( IS_TERMINAL ) { if ( CURR_COL >= COLUMNS-1 ) { CURR_COL = 0 } @@ -352,18 +247,16 @@ function print_newlines_if_needed() { if ( IS_PRINTING_DOTS ) { printf( "\n" ) } - - # Reserve the extra lines for printing the statistics. - if ( '"${USE_GLOBALSTATS}"' ) { printf( ESC_DEFAULT "\n" ) } - if ( '"${USE_RECENTSTATS}"' && LAST_N > 0 ) { printf( ESC_DEFAULT "\n" ) } - if ( '"${USE_RECENTSTATS}"' && LAST_N > 0 ) { printf( ESC_CURSORUP ) } - if ( '"${USE_GLOBALSTATS}"' ) { printf( ESC_CURSORUP ) } - - # Erase the (new) current line, to make it clean for the next response dots. - printf( ESC_ERASELINE ) + printf( ESC_DEFAULT "\n" "\n" ESC_CURSORUP ESC_CURSORUP ESC_ERASELINE ) + + if ( PRINT_TIME ) { + ts = strftime("[%H:%M:%S] ") + printf( "%s", ts ) + CURR_COL += length(ts) + } } } else { - if ( CURR_COL >= LAST_N && LAST_N > 0 ) { + if ( CURR_COL >= LAST_N ) { CURR_COL = 0 printf( ESC_DEFAULT "\n" ) print_statistics_bar() @@ -372,18 +265,11 @@ function print_newlines_if_needed() { IS_PRINTING_DOTS = 1 } -############################################################ -# Functions related to the data structure of "Last N" statistics. - -# Clears the data structure. function clear(d) { - d["index"] = 0 # The next position to store a value - d["size"] = 0 # The array size, goes up to LAST_N + d["index"] = 0 + d["size"] = 0 } -# This function stores the value to the passed data structure. -# The data structure holds at most LAST_N values. When it is full, -# a new value overwrite the oldest one. function store(d, value) { d[d["index"]] = value d["index"]++ @@ -396,22 +282,23 @@ function store(d, value) { } } -############################################################ -# Functions related to processing the received response - function process_rtt(rtt) { - # Overall statistics last_rtt = rtt total_rtt += rtt - if ( last_seq == 0 ) { + + if ( !first_received ) { min_rtt = max_rtt = rtt + first_received = 1 } else { if ( rtt < min_rtt ) min_rtt = rtt if ( rtt > max_rtt ) max_rtt = rtt } - # "Last N" statistics - store(lastn_rtt,rtt) + store(lastn_rtt, rtt) + + if ( AUDIBLE ) { + printf( "\a" ) + } } function lost_a_packet() { @@ -423,33 +310,22 @@ function lost_a_packet() { store(lastn_lost, 1) } -############################################################ -# Functions related to printing the fancy ping response - -# block_index, n, w are just local variables. function print_response_legend(i, n, w) { - if ( ! '"${USE_LEGEND}"' ) { + if ( ! USE_LEGEND ) { return } if ( BLOCK_LEN > 1 ) { - # w counts the cursor position in the current line. Because of the - # escape codes, I need to jump through some hoops in order to count the - # position correctly. w = 0 n = "0 " w += length(n) + 1 - printf( n BLOCK[0] ESC_DEFAULT ) + printf( "%s%s%s", n, BLOCK[0], ESC_DEFAULT ) for ( i=1 ; i= COLUMNS ) { + if ( IS_TERMINAL && w + 1 >= COLUMNS ) { printf( "\n" ) w = length(n) + 1 } else { @@ -457,19 +333,12 @@ function print_response_legend(i, n, w) { w += 1 } - printf( n BLOCK[i] ESC_DEFAULT ) + printf( "%s%s%s", n, BLOCK[i], ESC_DEFAULT ) } printf( " ∞\n" ) } - - # Useful code for debugging. - #for ( i=0 ; i<=BLOCK_RTT_MAX ; i++ ) { - # print_received_response(i) - # printf( ESC_DEFAULT "%4d\n", i ) - #} } -# block_index is just a local variable. function print_received_response(rtt, block_index) { if ( rtt < BLOCK_RTT_MIN ) { block_index = 0 @@ -482,58 +351,63 @@ function print_received_response(rtt, block_index) { CURR_COL++ } -function print_missing_response(rtt) { - printf( ESC_RED "!" ) +function print_received_response_colored(rtt, color_override, block_index, glyph) { + if ( rtt < BLOCK_RTT_MIN ) { + block_index = 0 + } else if ( rtt >= BLOCK_RTT_MAX ) { + block_index = BLOCK_LEN - 1 + } else { + block_index = 1 + int((rtt - BLOCK_RTT_MIN) * (BLOCK_LEN - 2) / BLOCK_RTT_RANGE) + } + + glyph = BLOCK[block_index] + gsub(/\033\[[0-9;]*m/, "", glyph) + + printf( "%s%s%s", color_override, glyph, ESC_DEFAULT ) CURR_COL++ } -############################################################ -# Functions related to printing statistics - -# All arguments are just local variables. -function print_global_stats(percentage_lost, avg_rtt) { - # Handling division by zero. - # Note that mawk does not consider division by zero an error, while all - # other awk implementations abort in such case. - # https://stackoverflow.com/questions/14581966/why-does-awk-produce-different-results-for-division-by-zero +function print_missing_response() { + printf( ESC_RED "!" ) + CURR_COL++ +} +function print_overall(percentage_lost, avg_rtt) { avg_rtt = ( received > 0 ) ? (total_rtt/received) : 0 percentage_lost = ( lost+received > 0 ) ? (lost*100/(lost+received)) : 0 - if ( '"${IS_TERMINAL}"' ) { - printf( "%2d/%3d (%2d%%) lost; %4.0f/" ESC_BOLD "%4.0f" ESC_DEFAULT "/%4.0fms; last: " ESC_BOLD "%4.0f" ESC_DEFAULT "ms", - lost, - lost+received, - percentage_lost, - min_rtt, - avg_rtt, - max_rtt, - last_rtt ) + if ( IS_TERMINAL ) { + printf( "Target: " ESC_BOLD "%s" ESC_DEFAULT \ + " | %2d/%3d (%2d%%) lost" \ + "; %4.0f/" ESC_BOLD "%4.0f" ESC_DEFAULT "/%4.0fms" \ + "; last: " ESC_BOLD "%4.0f" ESC_DEFAULT "ms" \ + " [" ESC_CYAN "dup:%d" ESC_DEFAULT " " ESC_MAGENTA "oop:%d" ESC_DEFAULT "]", + DESTINATION, lost, lost+received, percentage_lost, + min_rtt, avg_rtt, max_rtt, last_rtt, + dup_count, oop_count ) } else { - printf( "%2d/%3d (%2d%%) lost; %4.0f/" ESC_BOLD "%4.0f" ESC_DEFAULT "/%4.0fms", - lost, - lost+received, - percentage_lost, - min_rtt, - avg_rtt, - max_rtt ) + printf( "Target: %s | %2d/%3d (%2d%%) lost; %4.0f/" ESC_BOLD "%4.0f" ESC_DEFAULT "/%4.0fms [dup:%d oop:%d]", + DESTINATION, lost, lost+received, percentage_lost, + min_rtt, avg_rtt, max_rtt, dup_count, oop_count ) } } -# All arguments are just local variables. -function print_recent_stats(i, percentage_lost, sum, min, avg, max, diffs) { - # Calculate and print the lost packets statistics +function print_last_n(i, percentage_lost, sum, min, avg, max, diffs, pad_str, pad_len) { sum = 0 for ( i=0 ; i 0) ? (sum*100/lastn_lost["size"]) : 0 - printf( "%2d/%3d (%2d%%) lost; ", + + pad_len = 11 + length(DESTINATION) + pad_str = sprintf("%-*s", pad_len, "") + + printf( "%s%2d/%3d (%2d%%) lost; ", + pad_str, sum, lastn_lost["size"], percentage_lost ) - # Calculate the min/avg/max rtt times sum = diffs = 0 min = max = lastn_rtt[0] for ( i=0 ; i 0 ) { - printf( ESC_NEXTLINE ESC_ERASELINE ) - print_recent_stats() - } - + printf( ESC_NEXTLINE ESC_ERASELINE ) + print_overall() + printf( ESC_NEXTLINE ESC_ERASELINE ) + print_last_n() printf( ESC_UNSAVEPOS ) } else { - if ( '"${USE_GLOBALSTATS}"' ) { - print_global_stats() - printf( "\n" ) - } - if ( '"${USE_RECENTSTATS}"' && LAST_N > 0 ) { - print_recent_stats() - printf( "\n" ) - } + print_overall() + printf( "\n" ) + print_last_n() + printf( "\n" ) } } function print_statistics_bar_if_terminal() { - if ( '"${IS_TERMINAL}"' ) { + if ( IS_TERMINAL ) { print_statistics_bar() } } -############################################################ -# Initializations BEGIN { - # Easy way to get each value from ping output FS = "=" - - ############################################################ - # General internal variables - - # This is needed to keep track of lost packets last_seq = 0 - - # The previously printed non-ping-response line + first_received = 0 + dup_count = 0 + oop_count = 0 + other_line = "" other_line_times = 0 - - # Variables to keep the screen clean IS_PRINTING_DOTS = 0 CURR_COL = 0 - - ############################################################ - # Variables related to "overall" statistics received = 0 lost = 0 total_rtt = 0 min_rtt = 0 max_rtt = 0 last_rtt = 0 - - ############################################################ - # Variables related to "last N" statistics - LAST_N = int('"${LAST_N}"') - - # Data structures for the "last N" statistics + DESTINATION = "Unknown" + + USE_COLOR = int(ENVIRON["USE_COLOR"]) + USE_MULTICOLOR = int(ENVIRON["USE_MULTICOLOR"]) + USE_UNICODE = int(ENVIRON["USE_UNICODE"]) + USE_LEGEND = int(ENVIRON["USE_LEGEND"]) + IS_TERMINAL = int(ENVIRON["IS_TERMINAL"]) + LAST_N = int(ENVIRON["LAST_N"]) + clear(lastn_lost) clear(lastn_rtt) - ############################################################ - # Terminal height and width - - # These are sane defaults, in case we cannot query the actual terminal size LINES = 24 COLUMNS = 80 - # Auto-detecting the terminal size get_terminal_size() - if ( '"${IS_TERMINAL}"' && COLUMNS <= 50 ) { + if ( IS_TERMINAL && COLUMNS <= 50 ) { print "Warning: terminal width is too small." } - ############################################################ - # ANSI escape codes - - # Color escape codes. - # Fortunately, awk defaults any unassigned variable to an empty string. - if ( '"${USE_COLOR}"' ) { + if ( USE_COLOR ) { ESC_DEFAULT = "\033[0m" ESC_BOLD = "\033[1m" - #ESC_BLACK = "\033[0;30m" - #ESC_GRAY = "\033[1;30m" ESC_RED = "\033[0;31m" ESC_GREEN = "\033[0;32m" ESC_YELLOW = "\033[0;33m" @@ -662,9 +504,7 @@ BEGIN { ESC_YELLOW_ON_GREEN = "\033[42;33m" ESC_RED_ON_YELLOW = "\033[43;31m" } - # Other escape codes, see: - # https://en.wikipedia.org/wiki/ANSI_escape_code - # https://invisible-island.net/xterm/ctlseqs/ctlseqs.html + ESC_NEXTLINE = "\n" ESC_CURSORUP = "\033[A" ESC_CURSORDOWN = "\033[B" @@ -675,18 +515,7 @@ BEGIN { ESC_SAVEPOS = "\0337" ESC_UNSAVEPOS = "\0338" - # I am avoiding these escapes as they are not listed in: - # https://vt100.net/docs/vt100-ug/chapter3.html - #ESC_PREVLINE = "\033[F" - #ESC_SAVEPOS = "\033[s" - #ESC_UNSAVEPOS = "\033[u" - - # I am avoiding this to improve compatibility with (older versions of) tmux - #ESC_NEXTLINE = "\033[E" - - ############################################################ - # Unicode characters (based on https://github.com/holman/spark ) - if ( '"${USE_UNICODE}"' ) { + if ( USE_UNICODE ) { BLOCK[ 0] = ESC_GREEN "▁" BLOCK[ 1] = ESC_GREEN "▂" BLOCK[ 2] = ESC_GREEN "▃" @@ -711,13 +540,11 @@ BEGIN { BLOCK[21] = ESC_RED_ON_YELLOW "▆" BLOCK[22] = ESC_RED_ON_YELLOW "▇" BLOCK[23] = ESC_RED_ON_YELLOW "█" - if ( '"${USE_MULTICOLOR}"' && '"${USE_COLOR}"' ) { - # Multi-color version: + if ( USE_MULTICOLOR && USE_COLOR ) { BLOCK_LEN = 24 BLOCK_RTT_MIN = 10 BLOCK_RTT_MAX = 230 } else { - # Simple version: BLOCK_LEN = 8 BLOCK_RTT_MIN = 25 BLOCK_RTT_MAX = 175 @@ -735,27 +562,28 @@ BEGIN { BLOCK[ 9] = ESC_RED "." BLOCK[10] = ESC_RED "o" BLOCK[11] = ESC_RED "O" - if ( '"${USE_MULTICOLOR}"' && '"${USE_COLOR}"' ) { - # Multi-color version: + if ( USE_MULTICOLOR && USE_COLOR ) { BLOCK_LEN = 12 BLOCK_RTT_MIN = 20 BLOCK_RTT_MAX = 220 } else { - # Simple version: BLOCK_LEN = 4 BLOCK_RTT_MIN = 75 BLOCK_RTT_MAX = 225 } } - if ( int('"${RTT_MIN}"') > 0 && int('"${RTT_MAX}"') > 0 ) { - BLOCK_RTT_MIN = int('"${RTT_MIN}"') - BLOCK_RTT_MAX = int('"${RTT_MAX}"') - } else if ( int('"${RTT_MIN}"') > 0 ) { - BLOCK_RTT_MIN = int('"${RTT_MIN}"') + RTT_MIN = ENVIRON["RTT_MIN"] + RTT_MAX = ENVIRON["RTT_MAX"] + + if ( int(RTT_MIN) > 0 && int(RTT_MAX) > 0 ) { + BLOCK_RTT_MIN = int(RTT_MIN) + BLOCK_RTT_MAX = int(RTT_MAX) + } else if ( int(RTT_MIN) > 0 ) { + BLOCK_RTT_MIN = int(RTT_MIN) BLOCK_RTT_MAX = BLOCK_RTT_MIN * (BLOCK_LEN - 1) - } else if ( int('"${RTT_MAX}"') > 0 ) { - BLOCK_RTT_MAX = int('"${RTT_MAX}"') + } else if ( int(RTT_MAX) > 0 ) { + BLOCK_RTT_MAX = int(RTT_MAX) BLOCK_RTT_MIN = int(BLOCK_RTT_MAX / (BLOCK_LEN - 1)) } @@ -763,49 +591,53 @@ BEGIN { print_response_legend() } -############################################################ -# Main loop { - if ( $0 ~ /^[0-9]+ bytes from .*[:,] icmp_[rs]eq=[0-9]+ (ttl|hlim)=[0-9]+ time=[0-9.]+ *ms/ ) { - # Sample line from ping: - # 64 bytes from 8.8.8.8: icmp_seq=1 ttl=49 time=184 ms - if ( other_line_times >= 2 ) { - other_line_finished_repeating() + if ( $0 ~ /^PING / && DESTINATION == "Unknown" ) { + split($0, parts, " ") + DESTINATION = parts[2] + if ( IS_TERMINAL ) { + printf( "\033]0;prettyping %s\007", DESTINATION ) } + } - # $1 = useless prefix string - # $2 = icmp_seq - # $3 = ttl/hlim - # $4 = time - - # This must be called before incrementing the last_seq variable! - rtt = int($4) - process_rtt(rtt) + if ( $0 ~ /^[0-9]+ bytes from .*[:,] icmp_[rs]eq=[0-9]+ (ttl|hlim)=[0-9]+ time=[0-9.]+ *ms/ ) { + rtt = $4 + 0 seq = int($2) - while ( last_seq < seq - 1 ) { - lost_a_packet() - } + other_line_finished_repeating() - # Received a packet - print_newlines_if_needed() - print_received_response(rtt) + if ( $0 ~ /DUP!/ ) { + print_newlines_if_needed() + print_received_response_colored(rtt, ESC_CYAN) + dup_count++ + store(lastn_lost, 0) - # In case of receiving multiple responses with the same seq number, it - # is better to use "last_seq = seq" than to increment last_seq. - last_seq = seq + } else if ( seq <= last_seq && seq != 0 ) { + print_newlines_if_needed() + print_received_response_colored(rtt, ESC_MAGENTA) + oop_count++ + process_rtt(rtt) + received++ + store(lastn_lost, 0) + last_seq = seq - received++ - store(lastn_lost, 0) + } else { + while ( last_seq < seq - 1 ) { + lost_a_packet() + } + print_newlines_if_needed() + print_received_response(rtt) + last_seq = seq + process_rtt(rtt) + received++ + store(lastn_lost, 0) + } print_statistics_bar_if_terminal() - } else if ( $0 ~ /^.*onnected to.*, seq=[0-9]+ time= *[0-9.]+ *ms/ ) { - # Sample line from httping: - # connected to 200.149.119.168:80 (273 bytes), seq=0 time=129.86 ms - if ( other_line_times >= 2 ) { - other_line_finished_repeating() - } + + } else if ( $0 ~ /^.*onnected to.*, seq=[0-9]+ time=[0-9.]+ *ms/ ) { + other_line_finished_repeating() seq = $0 sub(/.* seq=/, "", seq) @@ -813,7 +645,7 @@ BEGIN { rtt = $0 sub(/.* time=/, "", rtt) - rtt = rtt + 0.0 + rtt = rtt + 0 process_rtt(rtt) @@ -821,48 +653,39 @@ BEGIN { lost_a_packet() } - # Received a packet print_newlines_if_needed() print_received_response(rtt) - # In case of receiving multiple responses with the same seq number, it - # is better to use "last_seq = seq" than to increment last_seq. last_seq = seq - received++ store(lastn_lost, 0) print_statistics_bar_if_terminal() + } else if ( $0 == "" ) { - # Do nothing on blank lines. + # Do nothing } else if ( $0 == "error shutting down ssl" ) { - # Common error message when using httping, ignore it. + # Ignore httping ssl errors } else if ( $0 ~ /^Request timeout for icmp_seq [0-9]+/ ) { - # Reply timeout is printed on Mac OS X. - - if ( other_line_times >= 2 ) { - other_line_finished_repeating() - } + other_line_finished_repeating() - lost_a_packet() - - # Making sure the last_seq number is correct. gsub(/.* icmp_seq /, "") seq = int($0) - last_seq = seq - print_newlines_if_needed() + while ( last_seq < seq ) { + lost_a_packet() + } + print_statistics_bar_if_terminal() + } else if ( $0 ~ /^SIGWINCH$/ ) { get_terminal_size() if ( IS_PRINTING_DOTS ) { if ( CURR_COL >= COLUMNS-1 ) { - # Not enough space anyway. + # Not enough space } else { - # Making up room in case the number of lines has changed. printf( ESC_NEXTLINE ESC_NEXTLINE ESC_CURSORUP ESC_CURSORUP ) - # Moving to the correct column and erasing the rest of the line. printf( "\033[" (CURR_COL+1) "G" ESC_DEFAULT ESC_ERASELINEEND ) } @@ -875,7 +698,7 @@ BEGIN { gsub(/icmp_seq[= ][0-9]+/, "") if ( $0 == other_line ) { other_line_times++ - if ( '"${IS_TERMINAL}"' ) { + if ( IS_TERMINAL ) { other_line_is_repeated() } } else { @@ -886,6 +709,5 @@ BEGIN { } } - # Not needed when the output is a terminal, but does not hurt either. fflush() }'