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
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
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
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 }