Pinger Application

What is "Pinger"?

Pinger is a smallish Tcl/Tk script for doing ping testing of an IP computer network.

see also: http://helihobby.com/netpackx/Tcl_Tk/Pinger/pinger.html

See These Wiki Pages For Other Examples


What is it for?

Both UNIX and Windoze have a built-in command called "ping" which sends an ICMP ECHO message to a specified host and listens for a reply. This makes it simple to check basic IP connectivity between two hosts.

I created Pinger to solve a different problem: Troubleshooting intermittent connectivity problems.

The specific problem I am having involves a particular server periodically not talking to me. My first response was to run an endless ping loop to try to detect when it goes off. But if you walk away from your desk, the text scrolls off the screen. So I redirected output to a file. But now

  • There is no visible output on the screen.
  • Several KB of log is generated every minute - most of it containing utterly identical text.

So I wrote a tool (imaginitively named "Pinger") which calls the OS ping program and then only logs the information I'm actually interested in.


What exactly does it do?

It started from humble beginnings, but Pinger has now become moderately complicated. It's designed to solve my particular problem, but somebody suggested to me that others may find a use for it.

Anyway, you give Pinger a target to ping, and it pings it forever. While it's doing this, it gives both real-time graphical feedback using Tk, and also logs everything to a log file. (The name is automatically generated and includes the target name and today's date.) I'd post a screenshot if I knew how... how here's a sample of the log output, which hopefully makes it clear what the program is all about:

 09:59:00 AM on Tue 29-Aug-2006 Pinger 1.21: Now pinging 'www.google.co.uk' (72.14.203.99)
   0 packet(s) failed.
 09:59:00 AM on Tue 29-Aug-2006 Successful ping!
   200 packets successful and counting...
   100 ms round trip (minimum).
   111.02 ms round trip (arithmatic mean).
   160 ms round trip (maximum).
   7.59799973677 ms standard deviation.
 10:06:09 AM on Tue 29-Aug-2006
   387 packet(s) successful.
   100 ms round trip (minimum).
   110.988326848 ms round trip (arithmatic mean).
   160 ms round trip (maximum).
   7.93283208454 ms standard deviation.
 10:12:54 AM on Tue 29-Aug-2006 Failed ping!
   1 packet(s) failed.
 10:12:56 AM on Tue 29-Aug-2006 Successful ping!
   200 packets successful and counting...
   100 ms round trip (minimum).
   111.135 ms round trip (arithmatic mean).
   160 ms round trip (maximum).
   8.45557656225 ms standard deviation.

...snipped a bit...

 04:59:04 PM on Tue 29-Aug-2006 Failed ping!
   1 packet(s) failed.
 04:59:07 PM on Tue 29-Aug-2006 Successful ping!
   32 packet(s) successful.
   100 ms round trip (minimum).
   109.21875 ms round trip (arithmatic mean).
   111 ms round trip (maximum).
   2.88026013365 ms standard deviation.
 05:00:15 PM on Tue 29-Aug-2006 User abort.
   4819 ping packet(s) sent.
   4810 reply packet(s) received.
   9 reply packet(s) lost.

Pinger attempts to do a ping every 2 seconds (roughly). Instead of recording the exact details of every individual ping attempt, it aggregates the information into a (hopefully) more useful form. It splits the ping results into runs of successful or failed pings, and shows how many packets are in each run (and, most importantly, the time). It also shows some statistics for round trip times on successful pings. (Note also that it flushes the log when it has logged something interesting, so that under Windoze at least, you can see what's in the log file without stopping the program.) The GUI has a big "stop" button on it - which also cunningly functions as a status light. (It turns red when a ping is sent, and turns green when a reply arrives.) When you stop the program, it dumps the last stats and quits.


Known Problems

  • Not very portable! This script calls the OS "ping" command, and does some crude parsing of the output to determin what the result was. It works on my Windoze NT 4.0 workstation, but probably won't work on anything else. (I know for a fact Windoze XP has a different output format.) The part that does the parsing is neatly seperated out from the rest of the code, so should be easy to alter...
  • The source code is very messy and is 0% commented. (I wasn't expecting anybody else to ever see it.)
  • Very little error checking is done. (It works on my PC, but...)
  • The time statistics are only calculated against the last 256 packets, not all packets. (The stats reset at the start of each run - by design. But the stats still don't include all the packets in the current run. I couldn't think of a better way to get a moving average.) I am also not 100% convinced the standard deviation is calculated correctly, but it seems to give plausible numbers...
  • Minor: The problem uses the default TTL, Timeout, etc. It wouldn't be hard to change that if you cared about such things.
  • Minor: I said "it pings every 2 seconds". What I really ment is "it waits 2 seconds between each call to PING". That means that if pings are failing, there is rather more than a 2 second delay between packets being sent. (2 seconds of wait + whatever the default timeout period is.)
  • Minor? By design, the script looks up the IP address of the target once, when you first run it. Thereafter, it just pings the same IP address. So if for some reason the IP changes half way through, Pinger won't "notice" this. (It works this way by design to reduce DNS load. I was also having issues with loss of connection to the DNS server making the pings not run at all, essentially locking up Pinger instead of reporting failed pings.)
  • Not a "problem", but... Pinger can ping an IP address, DNS name, or anything else the OS ping command understands as a "host". (On Windoze NT, this seems to include NetBIOS names. Presumably only if NetBIOS is installed...)

Source Code

Note: This works with my Tcl interpreter. I can't promise it will work with yours...

 set Version "1.21"
 set Date "29-Aug-2006"
 
 console show
 console title "Pinger $Version ($Date)"
 wm title . "Pinger $Version"
 
 proc TheTime {} \
 {
   return [clock format [clock seconds] -format "%I:%M:%S %p on %a %d-%h-%Y"]
 }
 
 proc TheDate {} \
 {
   return [clock format [clock seconds] -format "%Y-%h-%d"]
 }
 
 proc Ping {name} \
 {
   puts "Ping test: $name"
   puts "->Performing test..."
   set Data [exec ping -n 1 $name]
   puts "->Parsing results..."
 
   set Lines [split $Data "\n"]
   set Data [lindex $Lines 6] ; puts "($Data)"
 
   if {$Data=="Request timed out."} {return -1}
 
   set Fields [split $Data ":"]
   set Fields [split [lindex $Fields 1] " "]
 
   set Time [string range [lindex $Fields 2] 5 end]
   set TTL [string range [lindex $Fields 3] 4 end]
 
   set Time [string trimright $Time "ms"]
 
   puts "->Ping time was '$Time' milliseconds."
   puts "->TTL of reply was '$TTL' hops."
 
   return $Time
 }
 
 proc GetIP {dns} \
 {
   puts "Get IP address: '$dns'"
   puts "->Performing ping test..."
   set Data [exec ping -n 1 $dns]
   puts "->Parsing results..."
 
   set Lines [split $Data "\n"]
   set Data [lindex $Lines 6] ; puts "($Data)"
 
   if {$Data=="Request timed out."} {return ""}
 
   set Fields [split $Data ":"]
   set Data [lindex $Fields 0]
   set Fields [split $Data " "]
 
   return [lindex $Fields 2]
 }
 
 proc AddLine {name label value} \
 {
   frame $name
   label "$name.l" -text $label -font "Times 12 bold"  ; pack "$name.l" -side left
   label "$name.v" -text $value -font "Times 12 roman" ; pack "$name.v" -side right
   pack $name -side top -expand yes -fill x
 }
 
 proc AddSlider {name label min max res} \
 {
   frame $name
   label "$name.l" -text $label -font "Times 12 bold"     ; pack "$name.l" -side left
   scale "$name.v" -orient horizontal -from $min -to $max -resolution $res -length 300 ; pack "$name.v" -side right
   pack $name -side top -expand yes -fill x
 }
 
 set RUN 0
 
 proc Do {name} \
 {
   global RUN Version
   set RUN 1
 
   set IP [GetIP $name]
   if {$IP==""} {puts "DNS lookup error?" ; return ""}
   wm title . "Pinger $Version: $name ($IP)"
 
   AddLine .dst "Destination:" "$name ($IP)"
   AddLine .start "Started at:" [clock format [clock seconds] -format "%I:%M:%S %p (%Z) on %a %d-%h-%Y"]
   AddLine .count1 "Total ping packets sent:" 0
   AddLine .count2 "Total ping packets received:" 0
   AddLine .count3 "Total ping packets lost:" 0
   AddSlider .min_t "Minimum ping time (ms):" 0 500 1
   AddSlider .mid_t "Arith. mean ping time (ms):" 0 500 0.1
   AddSlider .max_t "Maximum ping time (ms):" 0 500 1
   AddSlider .now_t "Current ping time (ms):" 0 500 1
   AddSlider .sd_t  "Ping time standard deviation:" 0 200 0.01
   AddLine .prev_good_h  "Most recent run of successful pings..." ""
   AddLine .prev_good_t1 "\u2192Started:" "(No successes)"
   AddLine .prev_good_t2 "\u2192Ended:" "(No successes)"
   AddLine .prev_good_no "\u2192Total packets:" "-"
   AddLine .prev_bad_h  "Most recent run of failed pings..." ""
   AddLine .prev_bad_t1 "\u2192Started:" "(No failures)"
   AddLine .prev_bad_t2 "\u2192Ended:" "(No failures)"
   AddLine .prev_bad_no "\u2192Total packets:" "-"
   AddLine .mode "Last ping result:" "Untested"
 
   button .stop -text "Stop" -command {set RUN 0 ; puts "STOP!"}
   pack .stop -side top
 
   button .debug -text "DEBUG" -command {console show}
   pack .debug -side bottom
 
   set Count1 0
   set Count2 0
   set Count3 0
   set MinT 0
   set MaxT 0
   set ListT [list]
   set PrevGoodC 0
   set PrevBadC 0
   set Mode ""
   set PAUSE 0
 
   set LOG [open "Log--[TheDate]--$name.txt" w]
   puts $LOG "[TheTime] Pinger $Version: Now pinging '$name' ($IP)"
 
   while {$RUN==1} \
   {
     incr Count1 ; .count1.v configure -text $Count1
     .stop configure -bg #FF0000
     update
 
     set T [Ping $IP]
     .stop configure -bg #00FF00
 
     if {$T > 0} \
     {
       incr Count2 ; .count2.v configure -text $Count2
 
       .now_t.v set $T
 
       if {$MinT == 0} {set MinT $T ; .min_t.v set $MinT}
 
       if {$T < $MinT} {set MinT $T ; .min_t.v set $MinT}
       if {$T > $MaxT} {set MaxT $T ; .max_t.v set $MaxT}
 
       set ListT [lrange $ListT 0 255] ; lappend ListT $T
       set MidT 0
       set RMST 0
       foreach Time $ListT {incr MidT $Time ; incr RMST [expr $Time * $Time]}
       set MidT [expr double($MidT) / double([llength $ListT])] ; .mid_t.v set $MidT
       set RMST [expr double($RMST) / double([llength $ListT])]
       set SD [expr sqrt($RMST - $MidT*$MidT)] ; .sd_t.v set $SD
 
       .prev_good_t2.v configure -text [TheTime]
       incr PrevGoodC ; .prev_good_no.v configure -text $PrevGoodC
 
       if {$PrevGoodC % 200 == 0} \
       {
         LogGood $LOG $PrevGoodC $MinT $MaxT $MidT $SD yes
         puts $LOG [TheTime]
         flush $LOG
       }
 
       if {$Mode!="Good"} \
       {
         set Mode "Good" ; .mode.v configure -text "Success" -fg #00FF00
 
         .prev_good_t1.v configure -text [TheTime]
 
         puts $LOG "  $PrevBadC packet(s) failed."
 
         set PrevBadC 0
 
         puts $LOG "[TheTime] Successful ping!"
         flush $LOG
       }
     } \
     else \
     {
       incr Count3 ; .count3.v configure -text $Count3
 
       .prev_bad_t2.v configure -text [TheTime]
       incr PrevBadC ; .prev_bad_no.v configure -text $PrevBadC
 
       if {$Mode!="Bad"} \
       {
         set Mode "Bad" ; .mode.v configure -text "Failure" -fg #FF0000
 
         .prev_bad_t1.v configure -text [TheTime]
 
         LogGood $LOG $PrevGoodC $MinT $MaxT $MidT $SD no
 
         set PrevGoodC 0
         set MinT 0
         set MaxT 0
         set ListT [list]
 
         puts $LOG "[TheTime] Failed ping!"
         flush $LOG
       }
     }
 
     after 2000 {set PAUSE 0}
     vwait PAUSE
   }
 
   if {$Mode=="Bad"}  {puts $LOG "  $PrevBadC packet(s) failed."}
   if {$Mode=="Good"} {LogGood $LOG $PrevGoodC $MinT $MaxT $MidT $SD no}
 
   puts $LOG "[TheTime] User abort."
   puts $LOG "  $Count1 ping packet(s) sent."
   puts $LOG "  $Count2 reply packet(s) received."
   puts $LOG "  $Count3 reply packet(s) NOT received."
   close $LOG
   exit
 }
 
 proc LogGood {Handle Count Min Max Av SD Cont} \
 {
   if {$Cont==yes} \
   {puts $Handle "  $Count packets successful and counting..."} \
   {puts $Handle "  $Count packet(s) successful."}
 
   puts $Handle "  $Min ms round trip (minimum)."
   puts $Handle "  $Av ms round trip (arithmatic mean)."
   puts $Handle "  $Max ms round trip (maximum)."
   puts $Handle "  $SD ms standard deviation."
 }
 
 puts "Do <ip>"
 puts "Do <dns name>"

Credits

This script was written by The Mathematical Orchid


The TIL contains a package that provides another low-level interface to the ping command. I have tested it under Windows 2000/XP, linux and MacOSX. The package also works by analysing the output of the ping command and provides a few commands. You start by listening to a remote host using the ::hostalive::new command. This command takes -period and -alivecb as arguments, the second being a command that will be called back when the status of the remote host changes. When liveness changes are detected the callback is called with a number of additional information about the remote host, and, of course, its status. PS: You'll have to fetch the latest CVS version from sourceforge to get the package, this isn't part of any release yet (I think). EF

FrBa when calling the host O/S ping command, a little parsing is necessary to make sense of the results. The following code is my effort since 2001 to reduce the O/S ping results down to a binary yes or no. It sends one ping with a one second timeout.

# ping the host and return 1 if alive, 0 if dead
proc alive {host} {
        if {[string first Windows $::tcl_platform(os)] > -1} {
                if {[catch {exec ping $host -n 1 -w 1} result]} {return 0}
        } else {
                if {[catch {exec ping $host -c 1 -w 1} result]} {return 0}
        }
        if {[string first "abnormal" $result] > -1} {return 0}
        if {[string first "unknown" $result] > -1} {return 0}
        # Red Hat Linux 7.3 (2002)
        if {[string first " 0% packet loss" $result] > -1} {return 1}
        # Red Hat Linux 9 (2003)
        if {[string first " 0% loss" $result] > -1} {return 1}
        # Windows XP (2001) to Win10 (2021)
        if {[string first "(0% loss" $result] > -1} {return 1}
        if {[string first "time of day goes back" $result] > -1} {return 1}
        return 0
}