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.