Et Cetera
Et Cetera
Blog
As I mentioned before, I wrote a perl script to ping several locations and write the output to a log. This initial script was very simple - take several addresses, loop calls to BSD ping, parse the result and write to a log:
#!/usr/bin/perl -w
# Get the destination for the ping flood
$numpings = 1000;
# Set the variables for the cable modem signal levels
$cableIP = "192.168.100.1";
$signalPage = "signaldata.htm";
$datetime = `date "+%Y%m%d %H:%M:%S"`;
chomp $datetime;
$results = "$datetime";
$cablediagnostics = `curl -s http://$cableIP/$signalPage`;
$cablediagnostics =~ m|<TD>Signal to Noise Ratio</TD>\s+<TD>(\-?[0-9.]+) dB|si;
$cablesnr = $1;
$cablediagnostics =~ m|<TD>Power Level</TD>\s*<TD>(\-?[0-9.]+) dBmV. |si;
$cablesignaldown = $1;
$cablediagnostics =~ m|<TD>Power Level</TD>\s*<TD>(\-?[0-9.]+) dBmV\s+</TD>|si;
$cablesignalup = $1;
$results .= "\t" . "$cablesnr" . "\t" . "$cablesignaldown" . "\t" . "$cablesignalup";
foreach $pingdest (@ARGV)
{
$pingresults = `ping -q -f -c $numpings $pingdest`;
$pingresults =~ m/ (\d+)% packet loss/si;
$packetloss = $1;
$pingresults =~ m|([0-9.]+)/([0-9.]+)/([0-9.]+)/([0-9.]+) ms|si;
$latency_avg = $2;
$results .= "\t\t" . "$pingdest" . "\t" . "$packetloss\%" . "\t" . "$latency_avg";
}
print "$results\n";
This worked well enough for Comcast, spitting out a new line of data every time it was run. The next challenge was running it constantly in the background - as a daemon. Fortunately I have the aforementioned Mac Mini as a home server, which is perfect for such monitoring tasks. A quick launchd script in /Library/LaunchDaemons and I was off to the races:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>local.mini.networktest</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/networktest-launchd.pl</string>
<string>10.0.1.1</string>
<string>192.168.100.1</string>
<string>67.184.32.1</string>
<string>www.google.com</string>
</array>
<key>StartInterval</key>
<integer>600</integer>
<key>StandardOutPath</key>
<string>/usr/local/log/networktest.out</string>
</dict>
</plist>
This will run the script every ten minutes and ping my router, cable modem, gateway, and Google. The great thing was I could add a new target by adding a line to this XML file, and then simply:
# sudo launchctl unload /Library/LaunchDaemons/local.mini.networktest.plist
# sudo launchctl load /Library/LaunchDaemons/local.mini.networktest.plist
After I switched to Wide Open West, I was initially dismayed to see that I had an astonishing 97% packet loss consistently! At the same time, all normal network use was unaffected - it turns out that their network guys are more on top of things than Comcast, and they rate limit pings to a couple a second, so floods are out of the question.
This presented a bit of a problem, since the script above runs the floods sequentially, one after the other. If I’m limited to the standard one ping per second and want 100 pings for some accuracy, it’s going to take several minutes to run all of my pings, instead of the couple seconds before. If network conditions change over this period, I can’t necessarily relate the results of one ping test with another.
The solution as I saw it was concurrency - run all of the pings simultaneously. Due to the rate limiting it would still take the same length of time, but all results would be collected together and could be properly compared.
The obvious solution is to use fork to spawn child processes, run the pings concurrently, and report the data back to the parent process to be logged. The only hiccup left is by their very nature, these concurrent processes don’t have to end in the same order they were launched, which makes outputting the data consistently tricky. I solved it by collecting the data in a hash table, using the address as a key, then outputting it in the original argument order. The significantly more elaborate script follows (nicely commented):
#!/usr/bin/perl -w
# Grab the current date
$datetime = `date "+%Y%m%d %H:%M:%S"`;
chomp $datetime;
# Ping parameters
$numpings = 100;
# Ping rate can either be an interval or "-f" for flood
$pingrate = "-i 2";
# Create a hash to store the ping results
%pingresults = ();
# Set up interprocess I/O
use IO::Handle;
pipe(PARENT_IN, CHILD_OUT);
CHILD_OUT->autoflush(1);
# Ping all destinations
foreach $pingdest (@ARGV)
{
# Fork to run all of them at once
my $pid = fork();
if (not defined $pid)
{
# Undefined pid means fork failed completely
print "Fork failed - resources not available.\n";
}
elsif ($pid != 0)
{
# Parent
# Keep a list of PIDs for later reaping
push(@children, $pid);
}
else
{
# Child
close PARENT_IN;
# Send out the ping test
$pingresults = `ping -q $pingrate -c $numpings $pingdest`;
$pingresults =~ m/ (\d+)% packet loss/si;
$packetloss = $1;
$pingresults =~ m|([0-9.]+)/([0-9.]+)/([0-9.]+)/([0-9.]+) ms|si;
$latencyavg = $2;
# Send output to the parent process
print CHILD_OUT "$pingdest" . "\t" . "$packetloss\%" . "\t" . "$latencyavg" . "\n";
close CHILD_OUT;
exit(0);
}
}
close CHILD_OUT;
# Reap all of the zombie processes
foreach (@children)
{
# Get the data from the child processes (no guarantee of order, so put it in a hash)
chomp($line = <PARENT_IN>);
($pingdest,) = split ("\t", $line);
$pingresults{$pingdest} = $line;
# Reap the child process
waitpid($_,0);
}
close PARENT_IN;
# Output all of the ping results in order
$results = "$datetime";
foreach $pingdest (@ARGV)
{
$results .= "\t" . $pingresults{$pingdest}
}
print "$results\n";
Voila - my ping tests worked (albeit a bit slower), and I could show conclusively that the new connection was performing whereas the old one was not.
Until I saw this:
192.168.100.10%2.54864.53.208.10%36.938
192.168.100.10%2.33764.53.208.10%37.711
192.168.100.10%2.56364.53.208.11%51.646
192.168.100.112%793.91864.53.208.111%826.719
192.168.100.13%828.75064.53.208.110%858.640
192.168.100.111%519.23664.53.208.15%564.271
I was aghast - I thought my brand new pipe had sprung a leak as it were. More information than ping was needed here.
Network Monitoring 101
January 21, 2008