]>
git.slaskete.net - einar-bin/blob - prettyping.sh
3 # Written by Denilson Figueiredo de Sá <denilsonsa@gmail.com>
7 # * bash (tested on 4.20, should work on older versions too)
8 # * gawk (GNU awk, tested on 4.0.1, should work on older versions too)
9 # * ping (from iputils)
11 # TODO: Detect the following kind of message and avoid printing it repeatedly.
12 # From 192.168.1.11: icmp_seq=4 Destination Host Unreachable
14 # TODO: print the destination (also) at the bottom bar. Useful after leaving
15 # the script running for quite some time.
17 # TODO: Implement audible ping.
19 # TODO: Autodetect the width of printf numbers, so they will always line up correctly.
21 # TODO: Test the behavior of this script upon receiving out-of-order packets, like these:
22 # http://www.blug.linux.no/rfc1149/pinglogg.txt
24 # TODO? How will prettyping behave if it receives a duplicate response?
28 Usage: $MYNAME [prettyping parameters] <standard ping parameters>
30 This script is a wrapper around the system's "ping" tool. It will substitute
31 each ping response line by a colored character, giving a very compact overview
32 of the ping responses.
34 prettyping parameters:
35 --[no]color Enable/disable color output. (default: enabled)
36 --[no]multicolor Enable/disable multi-color unicode output. Has no effect if
37 either color or unicode is disabled. (default: enabled)
38 --[no]unicode Enable/disable unicode characters. (default: enabled)
39 --[no]terminal Force the output designed to a terminal. (default: auto)
40 --last <n> Use the last "n" pings at the statistics line. (default: 60)
41 --columns <n> Override auto-detection of terminal dimensions.
42 --lines <n> Override auto-detection of terminal dimensions.
43 --rttmin <n> Minimum RTT represented in the unicode graph. (default: auto)
44 --rttmax <n> Maximum RTT represented in the unicode graph. (default: auto)
46 ping parameters handled by prettyping:
47 -a Audible ping is not implemented yet.
48 -f Flood mode is not allowed in prettyping.
49 -q Quiet output is not allowed in prettyping.
50 -R Record route mode is not allowed in prettyping.
51 -v Verbose output seems to be the default mode in ping.
53 Tested with Linux ping tool from "iputils" package:
54 http://www.linuxfoundation.org/collaborate/workgroups/networking/iputils
58 # Thanks to people at #bash who pointed me at
59 # http://bash-hackers.org/wiki/doku.php/scripting/posparams
79 while [[ $# != 0 ]] ; do
86 # Forbidden ping parameters within prettyping:
88 echo "${MYNAME}: You can't use the -f (flood) option."
92 # -R prints extra information at each ping response.
93 echo "${MYNAME}: You can't use the -R (record route) option."
97 echo "${MYNAME}: You can't use the -q (quiet) option."
101 # -v enables verbose output. However, it seems the output with
102 # or without this option is the same. Anyway, prettyping will
103 # strip this parameter.
106 # Small values for -s parameter prevents ping from being able to
111 # TODO: Implement audible ping for responses or for missing packets
114 -color | --color ) USE_COLOR
=1 ;;
115 -nocolor | --nocolor ) USE_COLOR
=0 ;;
116 -multicolor | --multicolor ) USE_MULTICOLOR
=1 ;;
117 -nomulticolor | --nomulticolor ) USE_MULTICOLOR
=0 ;;
118 -unicode | --unicode ) USE_UNICODE
=1 ;;
119 -nounicode | --nounicode ) USE_UNICODE
=0 ;;
120 -terminal | --terminal ) IS_TERMINAL
=1 ;;
121 -noterminal | --noterminal ) IS_TERMINAL
=0 ;;
123 #TODO: Check if these parameters are numbers.
124 -last | --last ) LAST_N
="$2" ; shift ;;
125 -columns | --columns ) OVERRIDE_COLUMNS
="$2" ; shift ;;
126 -lines | --lines ) OVERRIDE_LINES
="$2" ; shift ;;
127 -rttmin | --rttmin ) RTT_MIN
="$2" ; shift ;;
128 -rttmax | --rttmax ) RTT_MAX
="$2" ; shift ;;
137 if [[ "${RTT_MIN}" -gt 0 && "${RTT_MAX}" -gt 0 && "${RTT_MIN}" -ge "${RTT_MAX}" ]] ; then
138 echo "${MYNAME}: Invalid --rttmin and -rttmax values."
142 if [[ "${#PING_PARAMS[@]}" = 0 ]] ; then
143 echo "${MYNAME}: Missing parameters, use --help for instructions."
148 MYNAME
=`basename "$0"`
154 # Warning! Ugly code ahead!
155 # The code is so ugly that the comments explaining it are
156 # bigger than the code itself!
162 # I need the PID of cmd_a. How can I get it?
163 # In bash, $! will give me the PID of cmd_b.
165 # So, I came up with this ugly solution: open a subshell, like this:
169 # echo "This is the PID I want $!"
174 # Ignore Ctrl+C here.
175 # If I don't do this, this shell script is killed before
176 # ping and gawk can finish their work.
181 ping "${PING_PARAMS[@]}" &
182 # Commented out, because it looks like this line is not needed
183 #trap "kill -2 $! ; exit 1" 2 # Catch Ctrl+C here
186 # Weird that awk does not come with abs(), so I need to implement it.
188 return ( (x < 0) ? -x : x )
191 # Ditto for ceiling function.
193 return (x == int(x)) ? x : int(x) + 1
196 # Currently, this function is called once, at the beginning of this
197 # script, but it is also possible to call this more than once, to
198 # handle window size changes while this program is running.
200 # Local variables MUST be declared in argument list, else they are
201 # seen as global. Ugly, but that is how awk works.
202 function get_terminal_size(SIZE,SIZEA) {
204 if( (STTY_CMD | getline SIZE) == 1 ) {
205 split(SIZE, SIZEA, " ")
213 if ( int('"${OVERRIDE_COLUMNS}"') ) { COLUMNS = int('"${OVERRIDE_COLUMNS}"') }
214 if ( int('"${OVERRIDE_LINES}"') ) { LINES = int('"${OVERRIDE_LINES}"') }
217 ############################################################
218 # Functions related to cursor handling
220 # Function called whenever a non-dotted line is printed.
222 # It will move the cursor to the line next to the statistics and
223 # restore the default color.
224 function other_line_is_printed() {
225 if( IS_PRINTING_DOTS ) {
226 if( '"${IS_TERMINAL}"' ) {
227 printf( ESC_DEFAULT ESC_NEXTLINE ESC_NEXTLINE "\n" )
229 printf( ESC_DEFAULT "\n" )
230 print_statistics_bar()
237 # Function called whenever a non-dotted line is repeated.
238 function other_line_is_repeated() {
239 if (other_line_times < 2) {
242 if( '"${IS_TERMINAL}"' ) {
243 printf( ESC_DEFAULT ESC_ERASELINE "\r" )
245 printf( "Last message repeated %d times.", other_line_times )
246 if( ! '"${IS_TERMINAL}"' ) {
251 # Prints the newlines required for the live statistics.
253 # I need to print some newlines and then return the cursor back to its position
254 # to make sure the terminal will scroll.
256 # If the output is not a terminal, break lines on every LAST_N dots.
257 function print_newlines_if_needed() {
258 if( '"${IS_TERMINAL}"' ) {
259 # COLUMNS-1 because I want to avoid bugs with the cursor at the last column
260 if( CURR_COL >= COLUMNS-1 ) {
263 if( CURR_COL == 0 ) {
264 if( IS_PRINTING_DOTS ) {
267 #printf( "\n" "\n" ESC_PREVLINE ESC_PREVLINE ESC_ERASELINE )
268 printf( ESC_DEFAULT "\n" "\n" ESC_CURSORUP ESC_CURSORUP ESC_ERASELINE )
271 if( CURR_COL >= LAST_N ) {
273 printf( ESC_DEFAULT "\n" )
274 print_statistics_bar()
281 ############################################################
282 # Functions related to the data structure of "Last N" statistics.
284 # Clears the data structure.
286 d["index"] = 0 # The next position to store a value
287 d["size"] = 0 # The array size, goes up to LAST_N
290 # This function stores the value to the passed data structure.
291 # The data structure holds at most LAST_N values. When it is full,
292 # a new value overwrite the oldest one.
293 function store(d, value) {
294 d[d["index"]] = value
296 if( d["index"] >= d["size"] ) {
297 if( d["size"] < LAST_N ) {
305 ############################################################
306 # Functions related to processing the received response
308 function process_rtt(rtt) {
312 if( last_seq == 0 ) {
313 min_rtt = max_rtt = rtt
315 if( rtt < min_rtt ) min_rtt = rtt
316 if( rtt > max_rtt ) max_rtt = rtt
319 # "Last N" statistics
323 ############################################################
324 # Functions related to printing the fancy ping response
326 # block_index is just a local variable.
327 function print_response_legend(i) {
328 if( '"${USE_UNICODE}"' ) {
329 printf( BLOCK[0] ESC_DEFAULT "%4d ", 0)
330 for( i=1 ; i<BLOCK_LEN ; i++ ) {
331 printf( BLOCK[i] ESC_DEFAULT "%4d ",
332 BLOCK_RTT_MIN + ceil((i-1) * BLOCK_RTT_RANGE / (BLOCK_LEN - 2)) )
337 # Useful code for debugging.
338 #for( i=0 ; i<=BLOCK_RTT_MAX ; i++ ) {
339 # print_received_response(i)
340 # printf( ESC_DEFAULT "%4d\n", i )
344 # block_index is just a local variable.
345 function print_received_response(rtt, block_index) {
346 if( '"${USE_UNICODE}"' ) {
347 if( rtt < BLOCK_RTT_MIN ) {
349 } else if( rtt >= BLOCK_RTT_MAX ) {
350 block_index = BLOCK_LEN - 1
352 block_index = 1 + int((rtt - BLOCK_RTT_MIN) * (BLOCK_LEN - 2) / BLOCK_RTT_RANGE)
354 printf( BLOCK[block_index] )
356 printf( ESC_GREEN "." )
360 function print_missing_response(rtt) {
361 printf( ESC_RED "!" )
364 ############################################################
365 # Functions related to printing statistics
367 function print_overall() {
368 if( '"${IS_TERMINAL}"' ) {
369 printf( "%2d/%3d (%2d%%) lost; %4.0f/" ESC_BOLD "%4.0f" ESC_DEFAULT "/%4.0fms; last: " ESC_BOLD "%4.0f" ESC_DEFAULT "ms",
372 (lost*100/(lost+received)),
374 (total_rtt/received),
378 printf( "%2d/%3d (%2d%%) lost; %4.0f/" ESC_BOLD "%4.0f" ESC_DEFAULT "/%4.0fms",
381 (lost*100/(lost+received)),
383 (total_rtt/received),
388 function print_last_n(i, sum, min, avg, max, diffs) {
389 # Calculate and print the lost packets statistics
391 for( i=0 ; i<lastn_lost["size"] ; i++ ) {
394 printf( "%2d/%3d (%2d%%) lost; ",
397 sum*100/lastn_lost["size"] )
399 # Calculate the min/avg/max rtt times
401 min = max = lastn_rtt[0]
402 for( i=0 ; i<lastn_rtt["size"] ; i++ ) {
404 if( lastn_rtt[i] < min ) min = lastn_rtt[i]
405 if( lastn_rtt[i] > max ) max = lastn_rtt[i]
407 avg = sum/lastn_rtt["size"]
409 # Calculate mdev (mean absolute deviation)
410 for( i=0 ; i<lastn_rtt["size"] ; i++ ) {
411 diffs += abs(lastn_rtt[i] - avg)
413 diffs /= lastn_rtt["size"]
415 # Print the rtt statistics
416 printf( "%4.0f/" ESC_BOLD "%4.0f" ESC_DEFAULT "/%4.0f/%4.0fms (last %d)",
424 function print_statistics_bar() {
425 if( '"${IS_TERMINAL}"' ) {
426 printf( ESC_SAVEPOS ESC_DEFAULT )
428 printf( ESC_NEXTLINE ESC_ERASELINE )
430 printf( ESC_NEXTLINE ESC_ERASELINE )
433 printf( ESC_UNSAVEPOS )
442 ############################################################
445 # Easy way to get each value from ping output
448 ############################################################
449 # General internal variables
451 # This is needed to keep track of lost packets
454 # The previously printed non-ping-response line
458 # Variables to keep the screen clean
462 ############################################################
463 # Variables related to "overall" statistics
471 ############################################################
472 # Variables related to "last N" statistics
473 LAST_N = int('"${LAST_N}"')
475 # Data structures for the "last N" statistics
479 ############################################################
480 # Terminal height and width
482 # These are sane defaults, in case we cannot query the actual terminal size
486 # Auto-detecting the terminal size
488 STTY_CMD = "stty size --file=/dev/tty 2> /dev/null"
490 if( '"${IS_TERMINAL}"' && COLUMNS <= 50 ) {
491 print "Warning: terminal width is too small."
494 ############################################################
497 # Color escape codes.
498 # Fortunately, awk defaults any unassigned variable to an empty string.
499 if( '"${USE_COLOR}"' ) {
500 ESC_DEFAULT = "\033[0m"
502 #ESC_BLACK = "\033[0;30m"
503 #ESC_GRAY = "\033[1;30m"
504 ESC_RED = "\033[0;31m"
505 ESC_GREEN = "\033[0;32m"
506 ESC_YELLOW = "\033[0;33m"
507 ESC_BLUE = "\033[0;34m"
508 ESC_MAGENTA = "\033[0;35m"
509 ESC_CYAN = "\033[0;36m"
510 ESC_WHITE = "\033[0;37m"
511 ESC_YELLOW_ON_GREEN = "\033[42;33m"
512 ESC_RED_ON_YELLOW = "\033[43;31m"
514 # Other escape codes, see:
515 # http://en.wikipedia.org/wiki/ANSI_escape_code
516 # http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
518 ESC_CURSORUP = "\033[A"
519 ESC_SCROLLUP = "\033[S"
520 ESC_SCROLLDOWN = "\033[T"
521 ESC_ERASELINE = "\033[2K"
522 ESC_SAVEPOS = "\0337"
523 ESC_UNSAVEPOS = "\0338"
525 # I am avoiding these escapes as they are not listed in:
526 # http://vt100.net/docs/vt100-ug/chapter3.html
527 #ESC_PREVLINE = "\033[F"
528 #ESC_SAVEPOS = "\033[s"
529 #ESC_UNSAVEPOS = "\033[u"
531 # I am avoiding this to improve compatibility with (older versions of) tmux
532 #ESC_NEXTLINE = "\033[E"
534 ############################################################
535 # Unicode characters (based on https://github.com/holman/spark )
536 if( '"${USE_UNICODE}"' ) {
537 BLOCK[ 0] = ESC_GREEN "▁"
538 BLOCK[ 1] = ESC_GREEN "▂"
539 BLOCK[ 2] = ESC_GREEN "▃"
540 BLOCK[ 3] = ESC_GREEN "▄"
541 BLOCK[ 4] = ESC_GREEN "▅"
542 BLOCK[ 5] = ESC_GREEN "▆"
543 BLOCK[ 6] = ESC_GREEN "▇"
544 BLOCK[ 7] = ESC_GREEN "█"
545 BLOCK[ 8] = ESC_YELLOW_ON_GREEN "▁"
546 BLOCK[ 9] = ESC_YELLOW_ON_GREEN "▂"
547 BLOCK[10] = ESC_YELLOW_ON_GREEN "▃"
548 BLOCK[11] = ESC_YELLOW_ON_GREEN "▄"
549 BLOCK[12] = ESC_YELLOW_ON_GREEN "▅"
550 BLOCK[13] = ESC_YELLOW_ON_GREEN "▆"
551 BLOCK[14] = ESC_YELLOW_ON_GREEN "▇"
552 BLOCK[15] = ESC_YELLOW_ON_GREEN "█"
553 BLOCK[16] = ESC_RED_ON_YELLOW "▁"
554 BLOCK[17] = ESC_RED_ON_YELLOW "▂"
555 BLOCK[18] = ESC_RED_ON_YELLOW "▃"
556 BLOCK[19] = ESC_RED_ON_YELLOW "▄"
557 BLOCK[20] = ESC_RED_ON_YELLOW "▅"
558 BLOCK[21] = ESC_RED_ON_YELLOW "▆"
559 BLOCK[22] = ESC_RED_ON_YELLOW "▇"
560 BLOCK[23] = ESC_RED_ON_YELLOW "█"
561 if( '"${USE_MULTICOLOR}"' && '"${USE_COLOR}"' ) {
562 # Multi-color version:
573 if( int('"${RTT_MIN}"') > 0 && int('"${RTT_MAX}"') > 0 ) {
574 BLOCK_RTT_MIN = int('"${RTT_MIN}"')
575 BLOCK_RTT_MAX = int('"${RTT_MAX}"')
576 } else if( int('"${RTT_MIN}"') > 0 ) {
577 BLOCK_RTT_MIN = int('"${RTT_MIN}"')
578 BLOCK_RTT_MAX = BLOCK_RTT_MIN * (BLOCK_LEN - 1)
579 } else if( int('"${RTT_MAX}"') > 0 ) {
580 BLOCK_RTT_MAX = int('"${RTT_MAX}"')
581 BLOCK_RTT_MIN = int(BLOCK_RTT_MAX / (BLOCK_LEN - 1))
584 BLOCK_RTT_RANGE = BLOCK_RTT_MAX - BLOCK_RTT_MIN
585 print_response_legend()
589 ############################################################
593 # 64 bytes from 8.8.8.8: icmp_seq=1 ttl=49 time=184 ms
594 if( $0 ~ /^[0-9]+ bytes from .*: icmp_[rs]eq=[0-9]+ ttl=[0-9]+ time=[0-9.]+ *ms/ ) {
595 if( other_line_times >= 2 ) {
596 if( '"${IS_TERMINAL}"' ) {
599 other_line_is_repeated()
605 # $1 = useless prefix string
610 # This must be called before incrementing the last_seq variable!
616 while( last_seq < seq - 1 ) {
618 print_newlines_if_needed()
619 print_missing_response()
627 print_newlines_if_needed()
628 print_received_response(rtt)
634 if( '"${IS_TERMINAL}"' ) {
635 print_statistics_bar()
637 } else if ( $0 == "" ) {
638 # Do nothing on blank lines.
640 other_line_is_printed()
641 if ( $0 == other_line ) {
643 if( '"${IS_TERMINAL}"' ) {
644 other_line_is_repeated()
653 # Not needed when the output is a terminal, but does not hurt either.