#!/usr/bin/perl =head1 NAME sqlmonitor - A database monitoring client written for Peep: The Network Auralizer. =head1 DESCRIPTION Executes one or more queries and compares the results to allowed maximum or minimum return values. This is meant as a pedagogical example of a Peep client. If you modify or improve upon it, please contact the author. I would enjoy seeing your work! =head1 USAGE ./sqlmonitor --help ./sqlmonitor --noautodiscovery --server=localhost --port=2001 --nodaemon ./sqlmonitor --dsn=dbi:Oracle:MYCONSTR -dbuser=myuser --dbpass=mypass ./sqlmonitor If you have any problems, try turning on debugging output with something like --debug=9. The option --dsn is the data source (e.g., 'dbi:mysql:mydb:localhost:3307'). The option --dbuser is the database user name. The option --dbpass is the database password. For a description of all other available command-line options, see Net::Peep::Client. =head1 CONFIGURATION To use this client, include a section like the following in peep.conf: client sqlmonitor class home port 1999 config # Event Max Min Notification SQL bad-users 0 0 warn /home/bogus-user/sql/bad-users.sql bad-count 5 10 crit /home/bogus-user/sql/bad-count.sql end config notification notification-hosts localhost notification-recipients bogus.user@bogusdomain.com notification-level crit end notification end client sqlmonitor and another section in the events block with something like events #Event Type | Path to Sound File | # of sounds to load ... bad-users /usr/local/share/peep/sounds/misc/events/rooster.* 1 bad-count /usr/local/share/peep/sounds/misc/events/doorbell.* 1 end events The file C might look something like SELECT COUNT(*) FROM users WHERE username=passwd In this case, you would be checking whether any users are dumb enough to make their password the same as their user name. (Hey, this is just an example.) The file C might look something like SELECT COUNT(*) FROM session In this case, you would be checking whether the number of sessions (e.g., users logged in) is within expectations. =head1 AUTHOR Collin Starkweather Copyright (C) 2001 =head1 SEE ALSO perl(1), peepd(1), Net::Peep, Net::Peep::Client, Net::Peep::BC, Net::Peep::Notifier, Net::Peep::Notification, DBI, DBD::Oracle http://peep.sourceforge.net =cut # Always use strict [:-)] use strict; use Net::Peep::BC; use Net::Peep::Log; use Net::Peep::Client; use Net::Peep::Notifier; use Net::Peep::Notification; use DBI; use vars qw{ %config $logger $client $dbh $conf %sth %sql }; my ($dsn,$dbuser,$dbpass) = ('dbi:Oracle:CONSTR','bogususer','boguspass'); # The options here are in Getopt::Long style. (Read the PODs for more # info.) The %options hash allows you to get your dsn, username, and # password information from the command line my %options = ( 'dsn=s' => \$dsn, 'dbuser=s' => \$dbuser, 'dbpass=s' => \$dbpass); $logger = new Net::Peep::Log; $client = new Net::Peep::Client; $client->name('sqlmonitor'); $client->initialize(%options) || $client->pods(); $dbh = DBI->connect($dsn,$dbuser,$dbpass) || die $DBI::errstr; $client->parser( \&parse ); $conf = $client->configure(); $client->callback( \&loop ); $SIG{'INT'} = $SIG{'TERM'} = \&shutdown; $client->MainLoop(60); sub parse { for my $line (@_) { if ($line =~ /^\s*([\w\-]+)\s+(\d+)\s+(\d+)\s+(info|warn|crit)\s+(.*)$/) { $logger->log("Found event [$1], max [$2], min [$3], notification [$4], and SQL file [$5].\n"); $config{$1} = { max=>$2, min=>$3, notification=>$4, sql=>$5 }; } } } # end sub parse sub loop { for my $key (sort keys %config) { $logger->log("Checking [$key] ..."); # get the SQL statement if it hasn't been loaded already ... unless (exists $sql{$key}) { if (open(SQL,$config{$key}->{'sql'})) { $sql{$key} = join '', ; chomp($sql{$key}); } else { warn "Cannot open [$config{'sql'}]: $!"; next; } } # we'll cache our statement handles for efficiency ... unless (exists $sth{$key}) { $logger->log("Preparing SQL $sql{$key}\n"); $sth{$key} = $dbh->prepare($sql{$key}) || $logger->log("Error preparing SQL: $DBI::errstr"); } if (exists $sth{$key} and defined $sth{$key}) { # execute the query my $rv = $sth{$key}->execute(); # get the results my (@results) = $sth{$key}->fetchrow_array(); $logger->log("The result of [$key] is [$results[0]]."); my ($max,$min) = ($config{$key}->{'max'},$config{$key}->{'min'}); if ($results[0] > $max || $results[0] < $min) { $logger->log("\tThe count for [$key] is out of bounds! Sending notification ...."); # The price is out of bounds! We'll start peeping ... my $broadcast = Net::Peep::BC->new('sqlmonitor',$conf); $broadcast->send('sqlmonitor', type=>0, sound=>$key, location=>128, priority=>0, volume=>255); # In case we're away from our desk, we'll also send out an # e-mail notification. Don't want to miss the action! my $notifier = new Net::Peep::Notifier; my $notification = new Net::Peep::Notification; $notification->client('sqlmonitor'); $notification->status($config{$key}->{'notification'}); $notification->datetime(time()); $notification->message(<<"eop"); The result of the query -- $sql{$key} -- is [$results[0]]. It should be less than $max and greater than $min. eop ; $notifier->notify($notification); } } } } # end sub loop sub shutdown { # we should clean up after ourselves ... print STDERR "Shutting down ...\n"; if (defined($dbh)) { print STDERR "\tDisconnecting ...\n"; for my $key (%sth) { if (defined($sth{$key})) { print STDERR "\t\tFinishing statement handle [$sth{$key}] ...\n"; $sth{$key}->finish(); } } $dbh->disconnect(); print STDERR "\tDone.\n"; } print STDERR "Done.\n"; exit(0); } # end sub shutdown __END__