summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNiko Tyni <ntyni@iki.fi>2005-09-04 18:23:12 +0200
committerNiko Tyni <ntyni@iki.fi>2005-09-04 18:23:12 +0200
commit52de638f8e7e13dd62a4e7906387be72969a4168 (patch)
tree129599b69e7323fb6500336e0baaffcf55335bea
parent09504475c41653b151b02605ea1ea7b53f86e222 (diff)
parent0fb4bc74b24ea96e80d4e27428b8c2451fbf0eb5 (diff)
downloadsmokeping-52de638f8e7e13dd62a4e7906387be72969a4168.tar.gz
smokeping-52de638f8e7e13dd62a4e7906387be72969a4168.tar.xz
Copied branches/2.0 to trunk/software
-rw-r--r--CHANGES53
-rw-r--r--CONTRIBUTORS4
-rw-r--r--Makefile37
-rw-r--r--TODO34
-rwxr-xr-xbin/smokeping.dist4
-rwxr-xr-xbin/tSmoke.dist540
-rw-r--r--doc/smokeping_extend.pod16
-rw-r--r--doc/smokeping_upgrade.pod39
-rw-r--r--etc/config.dist1
-rw-r--r--etc/smokemail.dist2
-rw-r--r--etc/tmail.dist132
-rwxr-xr-xhtdocs/smokeping.cgi.dist2
-rw-r--r--lib/Config/Grammar.pm (renamed from lib/ISG/ParseConfig.pm)92
-rw-r--r--lib/Smokeping.pm471
-rw-r--r--lib/Smokeping/RRDtools.pm73
-rw-r--r--lib/Smokeping/matchers/Avgratio.pm2
-rw-r--r--lib/Smokeping/probes/AnotherDNS.pm18
-rw-r--r--lib/Smokeping/probes/CiscoRTTMonEchoICMP.pm41
-rw-r--r--lib/Smokeping/probes/Curl.pm56
-rw-r--r--lib/Smokeping/probes/base.pm51
-rw-r--r--lib/Smokeping/probes/passwordchecker.pm2
-rwxr-xr-xutil/fix-pod2html.pl73
22 files changed, 1446 insertions, 297 deletions
diff --git a/CHANGES b/CHANGES
index 0383933..4d2d4f9 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,55 @@
+* don't create directories in "datadir" when running as a CGI
+ -- niko
+* the DYNAMIC-related files (.adr and .snmp) can now be located outside "datadir"
+ by specifying the new configuration variable "dyndir"
+ -- niko, suggested by Marc Haber <mh+smokeping-users *zugschlus.de>
+* return '404 not found' when DYNAMIC updates fail
+ -- niko, suggested by Marc Haber <mh+smokeping-users *zugschlus.de>
+* make errors in DYNAMIC updates appear in the web server error log
+ -- niko
+* remove a quotemeta() call in Config::Grammar to allow metacharacters
+ at the right side of '@define'
+ -- niko, reported by Warrick FitzGerald <lists.smokeping.wfitzgerald *crtman.com>
+* set LC_NUMERIC to C so that users who work with a locale like french that
+ would use , as a decimal separator do not trip over failing regexp-matches
+ (tobi)
+* bugfix for CiscoRTTMonEchoICMP packetsize variable
+ -- niko, from Sam Stickland <sam_ml *spacething.org>
+* tune DS properties min/max/heartbeat instead of just complaing about
+ them not matching (tobi)
+
+2005/05/31 - publish rc5 (tobi)
+* Officially include the tSmoke script from the contrib download directory.
+ Note that this needs the new 'tmail' variable to be defined in the config file.
+ -- niko, original script by Dan McGinn-Combs <d.mcginn-combs *mindspring.com>
+* Perl 5.8.0 compatibility fix ("missing max for DS uptime")
+ -- niko, reported by Steve Wickert and Kennedy Clark <hkclark *gmail.com>
+* RRDtool 1.2.x compatibility fix ("unknown RRD version: 0003" on restart)
+ -- niko, reported by Sam Stickland <sam_ml *spacething.org>
+
+2005/05/11 - publish rc4 (tobi)
+* Curl now has a new 'insecure_ssl' option for those not caring about CA paths
+ -- niko, original patch by Marc Spitzer <mspitzer *gmail.com>
+* document '@include' and its friends in smokeping_config in addition to
+ Config::Grammar -- niko, from Marc Haber (Debian bug #307955)
+* AnotherDNS: Double check the answer from the dns server and optionally enforce a
+ NOERROR response code -- Christoph.Heine in HaDiKo.DE
+* NEW Feature: when clicking on the graphs in detail view
+ you can select different time ranges for the graph. The creation of this
+ feature has been sponsored by BeverlyCorp.com -- tobi
+
+2005/03/10 - 2.0rc3 (tobi)
+
+* Curl now has a new "extraargs" option for any extra arguments, like "--header"
+ -- niko, requested by Warrick FitzGerald <lists.smokeping.wfitzgerald *crtman.com>
+* change ISG::ParseConfig references to its new name, Config::Grammar -- niko
+
+2005/02/28 - 2.0rc2 (tobi)
+
+* SYNOPSYS is really spelled SYNOPSIS. Shame on me. -- niko
+
+2005/02/22 - 2.0rc1 (tobi)
+
* don't create any RRD files if running as a CGI -- niko
* Curl timeouts work better now -- niko, reported by Chris Wilson <chris *aidworld.org>
* Curl User-Agent string doesn't need quotes anymore -- niko
@@ -20,6 +72,7 @@
* new commandline options '--config=X' and '--check' -- niko
* FPing: support "-t", "-p" and "-i" fping params -- niko, suggested by Chris Wilson <chris *aidworld.org>
* FPing6: test against ::1 instead of localhost -- Sebastian Wiesinger <smokeping *tracker.fire-world.de>
+* make sure mailsetup works before using it -- tobi
* fix for basefork.pm IO::Select property has_exception is very platform dependent
by ignoring it altogether things actually work better. Especially on Solaris which
does have propper support for has_exception as oposed to linux. -- niko, reported by Jim Morris <wrdnet *earthlink.net>
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 90973d3..c9d0ed6 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -3,7 +3,7 @@ Contributors
* Tobias Oetiker <tobi@oetiker.ch> => Main Author
-* Niko Tyni <ntyni@cc.helsinki.fi> => Many Patches
+* Niko Tyni <ntyni@iki.fi> => Many Patches
* Simon Leinen <leinen@switch.ch> => SNMP_Session.pm
-* David Schweikert <dws@ee.ethz.ch> => ISG::ParseConfig.pm
+* David Schweikert <dws@ee.ethz.ch> => Config::Grammar
* Jack Cummings <jack@mudshark.org> => Proper graphs with pings > 10s.
diff --git a/Makefile b/Makefile
index bb56142..6998998 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,7 @@
SHELL = /bin/sh
-VERSION = 1.38
-IGNORE = ~|CVS|var/|smokeping-$(VERSION)/smokeping-$(VERSION)|cvsignore|rej|orig|DEAD|pod2htm[di]\.tmp
+VERSION = 2.0rc5
+NUMVERSION = 1.99006
+IGNORE = ~|CVS|var/|smokeping-$(VERSION)/smokeping-$(VERSION)|cvsignore|rej|orig|DEAD|pod2htm[di]\.tmp|.svn
GROFF = groff
.PHONY: man html txt ref examples check-examples patch killdoc doc tar rename-man symlinks remove-symlinks
.SUFFIXES:
@@ -8,7 +9,7 @@ GROFF = groff
DOCS = $(filter-out doc/smokeping_config.pod doc/smokeping.pod doc/smokeping.cgi.pod,$(wildcard doc/*.pod)) doc/smokeping_examples.pod # section 7
DOCSCONFIG := doc/smokeping_config.pod # section 5
-PM := lib/ISG/ParseConfig.pm lib/Smokeping.pm lib/Smokeping/Examples.pm lib/Smokeping/RRDtools.pm
+PM := lib/Config/Grammar.pm lib/Smokeping.pm lib/Smokeping/Examples.pm lib/Smokeping/RRDtools.pm
PODPROBE := $(wildcard lib/Smokeping/probes/*.pm)
PODMATCH := $(wildcard lib/Smokeping/matchers/*.pm)
@@ -16,7 +17,7 @@ DOCSBASE = $(subst .pod,,$(DOCS))
MODBASE = $(subst .pm,,$(subst lib/,doc/,$(PM))) \
$(subst .pm,,$(subst lib/,doc/,$(PODPROBE))) \
$(subst .pm,,$(subst lib/,doc/,$(PODMATCH)))
-PROGBASE = doc/smokeping doc/smokeping.cgi
+PROGBASE = doc/smokeping doc/smokeping.cgi doc/tSmoke
DOCSCONFIGBASE = doc/smokeping_config
BASE = $(DOCSBASE) $(MODBASE) $(PROGBASE) $(DOCSCONFIGBASE)
@@ -28,7 +29,7 @@ HTML= $(addsuffix .html,$(BASE))
POD2MAN = pod2man --release=$(VERSION) --center=SmokePing $<
MAN2TXT = $(GROFF) -man -Tascii $< > $@
# pod2html apparently needs to be in the target directory to get L<> links right
-POD2HTML= cd $(dir $@); top="$(shell echo $(dir $@)|sed -e 's,doc/,,' -e 's,[^/]*/,../,g' -e 's,/$$,,')"; top=$${top:-.}; pod2html --infile=$(CURDIR)/$< --outfile=$(notdir $@) --noindex --htmlroot=. --podroot=. --podpath=$${top} --title=$*
+POD2HTML= cd $(dir $@); top="$(shell echo $(dir $@)|sed -e 's,doc/,,' -e 's,[^/]*/,../,g' -e 's,/$$,,')"; top=$${top:-.}; pod2html --infile=$(CURDIR)/$< --noindex --htmlroot=. --podroot=. --podpath=$${top} --title=$* | $${top}/../util/fix-pod2html.pl > $(notdir $@)
# we go to this trouble to ensure that MAKEPOD only uses modules in the installation directory
MAKEPOD= perl -Ilib -I/usr/pack/rrdtool-1.0.47-to/lib/perl -mSmokeping -e 'Smokeping::main()' -- --makepod
GENEX= perl -Ilib -I/usr/pack/rrdtool-1.0.47-to/lib/perl -mSmokeping -e 'Smokeping::main()' -- --gen-examples
@@ -52,12 +53,14 @@ doc/Smokeping/probes/%.3: doc/Smokeping/probes/%.pod
$(POD2MAN) --section 3 > $@
doc/Smokeping/matchers/%.3: lib/Smokeping/matchers/%.pm
$(POD2MAN) --section 3 > $@
-doc/ISG/%.3: lib/ISG/%.pm
+doc/Config/%.3: lib/Config/%.pm
$(POD2MAN) --section 3 > $@
doc/smokeping.1: bin/smokeping.dist
$(POD2MAN) --section 1 > $@
doc/smokeping.cgi.1: htdocs/smokeping.cgi.dist
$(POD2MAN) --section 1 > $@
+doc/tSmoke.1: bin/tSmoke.dist
+ $(POD2MAN) --section 1 > $@
doc/%.html: doc/%.pod
$(POD2HTML)
@@ -70,12 +73,14 @@ doc/Smokeping/RRDtools.html: lib/Smokeping/RRDtools.pm
doc/Smokeping/matchers/%.html: lib/Smokeping/matchers/%.pm
$(POD2HTML)
-doc/ISG/%.html: lib/ISG/%.pm
+doc/Config/%.html: lib/Config/%.pm
$(POD2HTML)
doc/smokeping.html: bin/smokeping.dist
$(POD2HTML)
doc/smokeping.cgi.html: htdocs/smokeping.cgi.dist
$(POD2HTML)
+doc/tSmoke.html: bin/tSmoke.dist
+ $(POD2HTML)
doc/%.txt: doc/%.1
$(MAN2TXT)
@@ -95,10 +100,12 @@ txt: $(TXT)
rename-man: $(MAN)
for j in probes matchers; do \
for i in doc/Smokeping/$$j/*.3; do \
- mv $$i `echo $$i | sed s,$$j/,$$j/Smokeping::$$j::,`; \
+ if ! echo $$i | grep -q Smokeping::$$j; then \
+ mv $$i `echo $$i | sed s,$$j/,$$j/Smokeping::$$j::,`; \
+ fi; \
done; \
done
- mv doc/ISG/ParseConfig.3 doc/ISG/ISG::ParseConfig.3
+ mv doc/Config/Grammar.3 doc/Config/Config::Grammar.3
mv doc/Smokeping/Examples.3 doc/Smokeping/Smokeping::Examples.3
mv doc/Smokeping/RRDtools.3 doc/Smokeping/Smokeping::RRDtools.3
@@ -123,11 +130,13 @@ doc/smokeping_config.pod: lib/Smokeping.pm
doc/smokeping_examples.pod: lib/Smokeping/Examples.pm etc/config.dist
$(GENEX)
patch:
- perl -i~ -p -e 's/VERSION="\d.*?"/VERSION="$(VERSION)"/' lib/Smokeping.pm
- perl -i~ -p -e 's/Smokeping \d.*?;/Smokeping $(VERSION);/' bin/smokeping.dist htdocs/smokeping.cgi.dist
+ perl -i~ -p -e 's/VERSION="\d.*?"/VERSION="$(NUMVERSION)"/' lib/Smokeping.pm
+ perl -i~ -p -e 's/Smokeping \d.*?;/Smokeping $(NUMVERSION);/' bin/smokeping.dist htdocs/smokeping.cgi.dist bin/tSmoke.dist
+ perl -i~ -p -e 'do { my @d = localtime; my $$d = (1900+$$d[5])."/".(1+$$d[4])."/".$$d[3]; print "$$d -- released version $(VERSION)\n\n" } unless $$done++ || /version $(VERSION)/' CHANGES
+ svn commit -m "prepare for the release of smokeping-$(VERSION)"
killdoc:
- -rm doc/*.[1357] doc/*.txt doc/*.html doc/Smokeping/* doc/Smokeping/probes/* doc/Smokeping/matchers/* doc/ISG/* doc/examples/* doc/smokeping_examples.pod doc/smokeping_config.pod doc/smokeping.pod doc/smokeping.cgi.pod
+ -rm doc/*.[1357] doc/*.txt doc/*.html doc/Smokeping/* doc/Smokeping/probes/* doc/Smokeping/matchers/* doc/Config/* doc/examples/* doc/smokeping_examples.pod doc/smokeping_config.pod doc/smokeping.pod doc/smokeping.cgi.pod
doc: killdoc ref examples man html txt rename-man
@@ -139,3 +148,7 @@ tar: doc patch
dist: tar
mv smokeping-$(VERSION).tar.gz /home/oetiker/public_html/webtools/smokeping/pub/
cp CHANGES /home/oetiker/public_html/webtools/smokeping/pub/CHANGES
+
+tag: dist
+ svn ls svn://svn.ee.ethz.ch/smokeping/tags/$(VERSION) || \
+ svn copy -m "tagging version $(VERSION)" svn://svn.ee.ethz.ch/smokeping/branches/2.0 svn://svn.ee.ethz.ch/smokeping/tags/$(VERSION)
diff --git a/TODO b/TODO
index 6915ef5..9c13ca2 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,7 @@
* UPTIME
- define update via snmp pointer per device
+ - define update via snmp pointer per device
+ - possibility to call an external script
+ -> generic uptime plugin?
* ATTENTION
allow to define a thereshold rule by looking at
@@ -9,3 +11,33 @@
allow to have atarget which points to a different target
only targets with host are considered
+* ALERTS
+ only send alerts when the state changes
+ - suggested by Marc Haber,
+ <http://lists.ee.ethz.ch/smokeping-users/msg01439.html>
+
+* DAEMON
+ reread config periodically or with SIGHUP so that
+ no measurement is interrupted
+ - suggested by Taisuke Yamada,
+ <http://lists.ee.ethz.ch/smokeping-users/msg01445.html>
+ - concurrent probe processes need a signal anyway,
+ they have to exit and new ones started so that
+ we don't have to compare to the old config
+
+* REMOTE EXECUTION
+ generic remote probe
+ - a possibility for basefork-derived probes to reuse the same
+ SSH connection with shell for() loops for all the pings to a given
+ target
+
+* GENERIC EXEC PROBE
+ - almost every probe has a different way of calling system(), exec()
+ or similar. This should be in an inheritable module.
+ - the module should also support extra commandline arguments
+
+* RRD
+ configurable RRD parameters per target?
+ - suggested by Leos Bitto,
+ <http://lists.ee.ethz.ch/smokeping-users/msg01632.html>
+
diff --git a/bin/smokeping.dist b/bin/smokeping.dist
index 02deda1..8d15a28 100755
--- a/bin/smokeping.dist
+++ b/bin/smokeping.dist
@@ -4,7 +4,7 @@
use lib qw(/usr/pack/rrdtool-1.0.49-to/lib/perl);
use lib qw(lib);
-use Smokeping 1.99001;
+use Smokeping 1.99006;
Smokeping::main("etc/config.dist");
@@ -42,7 +42,7 @@ B<smokeping> [ B<--email> | B<--makepod> | B<--version> | B<--restart> ]
--filter=x Only measure entries which pass the filter x
- --logfile Append warnings to this logfile
+ --logfile=x Append warnings to logfile x.
--static[=x] Generates a static website in directory x. If x is left out,
pagedir from the config is used.
diff --git a/bin/tSmoke.dist b/bin/tSmoke.dist
new file mode 100755
index 0000000..d8a8089
--- /dev/null
+++ b/bin/tSmoke.dist
@@ -0,0 +1,540 @@
+#!/usr/bin/perl
+#
+#-----------------------------------------------
+# tSmoke.pl
+# Dan McGinn-Combs, Sep 2003
+# tSmoke.v 0.4 2004/03 McGinn-Combs
+#-----------------------------------------------
+#
+# Modified for Smokeping official distribution since 20050526
+# Original README follows
+#
+# tSmoke.v04.README
+# - added downtime report (--downtime)
+# - a few tweaks to the calculations to ensure it's consistent
+#
+# tSmoke.v03.README
+# - Initial Release
+# - The script, started through cron, will cull through a config file and
+# determine which hosts are down at a point in time (Morning report) and
+# send out an smtp message to a mobile phone (for example).
+#
+# - It will also cull through the same config file and, using an included html
+# file (small change to General section of the config file), send an html
+# message which shows the availability over the past day, week, month
+# and quarter.
+#
+# - It can also show detail data depending on the setting of
+# command line option "detail".
+#
+# tSmoke.v02.README
+# - Local testing version
+#-----------------------------------------------
+#
+# 1) This program is run via CRON or the command line
+# 2) It extracts RRD information from a smokeping config file
+# 3) It pulls data from RRD files to determine if anything is offline, that is returning 0 PINGs
+# 4) tSmoke reports status via an SMTP alert
+# 5) tSmoke also generates an SMTP mail showing historical view of availability
+#
+# Many thanks to the following people for their help and guidance:
+# Jim Horwath of Agere Systems Inc. for his examples and pointers to Spreadsheet::WriteExcel
+# Frank Harper the author of SLAMon, a tool for tracking Service Level Agreements
+# Tobias Oeticker, or course, the author of Smokeping, RRDTool and MRTG
+#
+use strict;
+
+# We need to use
+# -- Smokeping libraries
+# -- RRDTool
+# -- Getopt::Long
+#
+# Point the lib variables to your implementation
+use lib qw(lib);
+use lib "/usr/local/rrdtool-1.0.39/lib/perl";
+
+use Smokeping 1.99006;
+use Net::SMTP;
+use Getopt::Long;
+use Pod::Usage;
+use RRDs;
+
+# Point to your Smokeping config file
+my $cfgfile = "etc/config.dist";
+
+# global variables
+my $cfg;
+
+#this is designed to work on IPv4 only
+my $havegetaddrinfo = 0;
+
+# we want opts everywhere
+my %opt;
+
+#Hashes for the data
+my (%Daily,%Weekly,%Monthly,%Quarterly); # the entries
+my (%DailyC,%WeeklyC,%MonthlyC,%QuarterlyC); # a count of the entries
+
+######################
+### Moving Average ###
+######################
+# Just a reminder of how to do a moving average if you ever want to
+# PREV,UN,<DS>,UN,1,<DS>,IF,PREV,IF,<DS>,UN,1,<DS>,IF,-,<WEIGHT>,*,A,UN,1,A,IF,+
+
+# Change Log:
+# DMC - Added Quarterly Status
+# DMC - Added HTML mail reporting and consolidated functions
+# DMC = Added an external HTML mail template, tMail
+my $RCS_VERSION = '$id: tSmoke.v 0.4 2004/03 McGinn-Combs';
+
+sub test_mail($) {
+ my $cfg = shift;
+ print "Mail will be sent to $cfg->{Alerts}{to}\n";
+ print "Mail will be sent from $cfg->{Alerts}{from}\n";
+};
+
+sub sendmail ($$$$){
+ my $from = shift;
+ my $to = shift;
+ my $subject = shift;
+ my $body = shift;
+ if ($cfg->{General}{mailhost}){
+ my $smtp = Net::SMTP->new($cfg->{General}{mailhost});
+ $smtp->mail($from);
+ $smtp->to($to);
+ $smtp->data();
+ $smtp->datasend("Subject: $subject\n");
+ $smtp->datasend("To: $to\n");
+ $smtp->datasend($body);
+ $smtp->dataend();
+ $smtp->quit;
+ } elsif ($cfg->{General}{sendmail} or -f "/usr/lib/sendmail"){
+ open (M, "|-") || exec (($cfg->{General}{sendmail} || "/usr/lib/sendmail"),"-f",$from,$to);
+ print M "Subject: $subject\n";
+ print M $body;
+ close M;
+ }
+}
+
+sub morning_update($) {
+ # Send out a morning summary of devices that are down
+ my $cfg = shift;
+ my $Body = "";
+ my $TmpBody = "";
+ my $To = "";
+ if ( $opt{to} ) { $To = $opt{to}; } else { $To = $cfg->{Alerts}{to}; }
+
+ # Get a list of the existing RRD Files
+ my @rrds = split ( /\n/,list_rrds($cfg->{Targets},"","") );
+ my $Count = $#rrds + 1;
+ my $Down = 0;
+
+ foreach my $target (@rrds) {
+ my $Loss = 0;
+ my ($start,$step,$names,$data) = RRDs::fetch "$target","AVERAGE","--start","-300";
+ my $ERR=RRDs::error;
+ die "ERROR while reading $_: $ERR\n" if $ERR;
+ foreach my $line (@$data) {
+ $Loss += $$line[3];
+ }
+ $Down += 1 if $Loss == 0;
+ $target =~ s/^([a-zA-Z0-9]*\/)*//;
+ $target =~ s/.rrd//;
+ $TmpBody .= "$target\n" if $Loss == 0;
+ }
+ $Body = "Of $Count Hosts, $Down Down:\n" . $TmpBody;
+ sendmail $cfg->{Alerts}{from},$To,"Of $Count Hosts, $Down Down",$Body;
+}
+
+sub weekly_update($) {
+ # Send out a formatted HTML Table of the
+ # Previous Day, Week, Month and Quarter Availability
+ # Get a list of the existing RRD Files
+ my @rrds = split ( /\n/,list_rrds($cfg->{Targets},"","") );
+
+ my $To = "";
+ if ( $opt{to} ) { $To = $opt{to}; } else { $To = $cfg->{Alerts}{to}; }
+
+ my $Body ='';
+
+# Calculations Based on the following:
+# RRDs::graph "fake.png",
+# '--start','-86400',
+# '-end','-300',
+# "DEF:loss=${rrd}:loss:AVERAGE",
+# "CDEF:avail=loss,0,100,IF", or more precisely "CDEF:avail=loss,2,GE,0,100,IF"
+# and adding in the check for unknown for systems just coming on line
+# "CDEF:avail=loss,UN,0,loss,IF,$pings,GE,0,100,IF"
+ # Arbitrarily a loss of 10% of Pings means the system was down
+ my $pings = $cfg->{Database}{pings} * .1;
+
+ foreach my $target (@rrds) {
+ # Get an average Availability for each RRD file
+ my $ERR;
+
+ my ($DAverage,$Dxsize,$Dysize) = RRDs::graph "fake.png",
+ "--start","-86400",
+ "--end","-600",
+ "--step","1008",
+ "DEF:loss=$target:loss:AVERAGE",
+ "CDEF:avail=loss,UN,0,loss,IF,$pings,GE,0,100,IF",
+ "PRINT:avail:AVERAGE:%.2lf";
+ $ERR=RRDs::error;
+ die "ERROR while reading $_: $ERR\n" if $ERR;
+
+ my ($WAverage,$Wxsize,$Wysize) = RRDs::graph "fake.png",
+ "--start","-604800",
+ "--end","-600",
+ "--step","4320",
+ "DEF:loss=$target:loss:AVERAGE",
+ "CDEF:avail=loss,UN,0,loss,IF,$pings,GE,0,100,IF",
+ "PRINT:avail:AVERAGE:%.2lf";
+ $ERR=RRDs::error;
+ die "ERROR while reading $_: $ERR\n" if $ERR;
+
+ my ($MAverage,$Mxsize,$Mysize) = RRDs::graph "fake.png",
+ "--start","-2592000",
+ "--end","-600",
+ "--step","4320",
+ "DEF:loss=$target:loss:AVERAGE",
+ "CDEF:avail=loss,UN,0,loss,IF,$pings,GE,0,100,IF",
+ "PRINT:avail:AVERAGE:%.2lf";
+ $ERR=RRDs::error;
+ die "ERROR while reading $_: $ERR\n" if $ERR;
+
+ my ($QAverage,$Qxsize,$Qysize) = RRDs::graph "fake.png",
+ "--start","-7776000",
+ "--end","-600",
+ "--step","4320",
+ "DEF:loss=$target:loss:AVERAGE",
+ "CDEF:avail=loss,UN,0,loss,IF,$pings,GE,0,100,IF",
+ "PRINT:avail:AVERAGE:%.2lf";
+ $ERR=RRDs::error;
+ die "ERROR while reading $_: $ERR\n" if $ERR;
+
+ $target =~ s/$cfg->{General}{datadir}\///;
+ $target =~ s/.rrd//;
+ my @Path;
+ push @Path,split/\//,$target;
+ update_stats ( \@Path, @$DAverage[0], @$WAverage[0], @$MAverage[0], @$QAverage[0]);
+ }
+
+ # Prepare the e-mail message
+ open tSMOKE, $cfg->{General}{tmail} or die "ERROR: can't read $cfg->{General}{tmail}\n";
+ while (<tSMOKE>){
+ my $Summary = Summary_Sheet();
+ s/<##SUMMARY##>/$Summary/ig;
+ my $Daily = DetailSheet(86400);
+ s/<##DAYDETAIL##>/$Daily/ig;
+ my $Weekly = DetailSheet(604800);
+ s/<##WEEKDETAIL##>/$Weekly/ig;
+ my $Monthly = DetailSheet(2592000);
+ s/<##MONTHDETAIL##>/$Monthly/ig;
+ my $Quarterly = DetailSheet(7776000);
+ s/<##QUARTERDETAIL##>/$Quarterly/ig;
+ $Body .= $_;
+ }
+ close tSMOKE;
+ sendmail ( $cfg->{Alerts}{from}, $To, "IT System Availability", $Body );
+}
+
+sub update_stats($$$$$);
+sub update_stats($$$$$) {
+ # Update the uptime percentages in the Hash Arrays
+ my $Path = shift;
+ my $DAverage = shift;
+ my $WAverage = shift;
+ my $MAverage = shift;
+ my $QAverage = shift;
+
+ #Enter everything once as it exists
+ #Trim off the rightmost component (hostname) and reenter the code
+ #If there is only one component, this is the final level
+ #This is an average of averages
+
+ my $Ticket = join ( ".",@$Path);
+ $Daily { $Ticket } += $DAverage;
+ $Weekly { $Ticket } += $WAverage;
+ $Monthly { $Ticket } += $MAverage;
+ $Quarterly {$Ticket } += $QAverage;
+ $DailyC { $Ticket }++;
+ $WeeklyC { $Ticket }++;
+ $MonthlyC { $Ticket }++;
+ $QuarterlyC { $Ticket }++;
+ my $Length = @$Path;
+ @$Path = @$Path [ 0 .. $Length - 2 ];
+ update_stats(\@$Path,$DAverage,$WAverage,$MAverage,$QAverage) if $Length > 1;
+}
+
+sub Summary_Sheet() {
+ my $Body = '';
+
+ $Body .= "<table border='1' bordercolor=#111111>\n";
+ $Body .= "<tr>\n";
+ $Body .= "<td class ='appHeader' colspan='5'>IT Network Systems Availability Summary</td></tr>\n";
+ $Body .= "<tr>\n";
+ $Body .= "<td class ='appHeader' colspan='5'>Compiled: ". scalar(localtime) . "</td></tr>\n";
+ $Body .= "<tr>\n";
+ $Body .= "<td class = 'subhead' width='20%'>Service</td>
+ <td class = 'subhead' witdh='20%'>Past Quarter</td>
+ <td class = 'subhead' width='20%'>Past Month</td>
+ <td class = 'subhead' width='20%'>Past Week</td>
+ <td class = 'subhead' width='20%'>Past Day</td></tr>\n";
+ foreach (sort { $a cmp $b } keys %Monthly) {
+ next if ( $_ =~ /\./ );
+ # this is a major section heading
+ $Body .= "<tr>\n";
+ $Body .= "<td class = 'SubHead'>$_</td>";
+ $Body .= "<td class = 'Up99'>" . sprintf('%.2f',$Quarterly{$_}/$QuarterlyC{$_}) . "%</td>"
+ if $Quarterly{$_}/$QuarterlyC{$_} >= 99 ;
+ $Body .= "<td class = 'Up95'>" . sprintf('%.2f',$Quarterly{$_}/$QuarterlyC{$_}) . "%</td>"
+ if $Quarterly{$_}/$QuarterlyC{$_} > 95 and $Quarterly{$_}/$QuarterlyC{$_} < 99 ;
+ $Body .= "<td class = 'Up90'>" . sprintf('%.2f',$Quarterly{$_}/$QuarterlyC{$_}) . "%</td>"
+ if $Quarterly{$_}/$QuarterlyC{$_} > 90 and $Quarterly{$_}/$QuarterlyC{$_} < 95 ;
+ $Body .= "<td class = 'UpNo'>" . sprintf('%.2f',$Quarterly{$_}/$QuarterlyC{$_}) . "%</td>"
+ if $Quarterly{$_}/$QuarterlyC{$_} < 90 ;
+ $Body .= "<td class = 'Up99'>" . sprintf('%.2f',$Monthly{$_}/$MonthlyC{$_}) . "%</td>"
+ if $Monthly{$_}/$MonthlyC{$_} >= 99 ;
+ $Body .= "<td class = 'Up95'>" . sprintf('%.2f',$Monthly{$_}/$MonthlyC{$_}) . "%</td>"
+ if $Monthly{$_}/$MonthlyC{$_} > 95 and $Monthly{$_}/$MonthlyC{$_} < 99 ;
+ $Body .= "<td class = 'Up90'>" . sprintf('%.2f',$Monthly{$_}/$MonthlyC{$_}) . "%</td>"
+ if $Monthly{$_}/$MonthlyC{$_} > 90 and $Monthly{$_}/$MonthlyC{$_} < 95 ;
+ $Body .= "<td class = 'UpNo'>" . sprintf('%.2f',$Monthly{$_}/$MonthlyC{$_}) . "%</td>"
+ if $Monthly{$_}/$MonthlyC{$_} < 90 ;
+ $Body .= "<td class = 'Up99'>" . sprintf('%.2f',$Weekly{$_}/$WeeklyC{$_}) . "%</td>"
+ if $Weekly{$_}/$WeeklyC{$_} >= 99;
+ $Body .= "<td class = 'Up95'>" . sprintf('%.2f',$Weekly{$_}/$WeeklyC{$_}) . "%</td>"
+ if $Weekly{$_}/$WeeklyC{$_} > 95 and $Weekly{$_}/$WeeklyC{$_} < 99 ;
+ $Body .= "<td class = 'Up90'>" . sprintf('%.2f',$Weekly{$_}/$WeeklyC{$_}) . "%</td>"
+ if $Weekly{$_}/$WeeklyC{$_} > 90 and $Weekly{$_}/$WeeklyC{$_} < 95 ;
+ $Body .= "<td class = 'UpNo'>" . sprintf('%.2f',$Weekly{$_}/$WeeklyC{$_}) . "%</td>"
+ if $Weekly{$_}/$WeeklyC{$_} < 90 ;
+ $Body .= "<td class = 'Up99'>" . sprintf('%.2f',$Daily{$_}/$DailyC{$_}) . "%</td>"
+ if $Daily{$_}/$DailyC{$_} >= 99;
+ $Body .= "<td class = 'Up95'>" . sprintf('%.2f',$Daily{$_}/$DailyC{$_}) . "%</td>"
+ if $Daily{$_}/$DailyC{$_} > 95 and $Daily{$_}/$DailyC{$_} < 99 ;
+ $Body .= "<td class = 'Up90'>" . sprintf('%.2f',$Daily{$_}/$DailyC{$_}) . "%</td>"
+ if $Daily{$_}/$DailyC{$_} > 90 and $Daily{$_}/$DailyC{$_} < 95 ;
+ $Body .= "<td class = 'UpNo'>" . sprintf('%.2f',$Daily{$_}/$DailyC{$_}) . "%</td>"
+ if $Daily{$_}/$DailyC{$_} < 90 ;
+ $Body .= "</tr>\n";
+ }
+ $Body .= "</table>";
+ $Body .= "<P><P><P>\n";
+ $Body .= "<table border='1' bordercolor=#111111><tr><td class ='appHeader'>Legend:</td>\n";
+ $Body .= "<tr><td class = 'Up99'>if uptime > 99% then GREEN</td></tr>\n";
+ $Body .= "<tr><td class = 'Up95'>if uptime > 95% but < 99% then BLUE</td></tr>\n";
+ $Body .= "<tr><td class = 'Up90'>if uptime > 90% but < 95% then YELLOW</td></tr>\n";
+ $Body .= "<tr><td class = 'UpNo'>if uptime < 90% then RED</td></tr>\n";
+ $Body .= "</table>\n";
+ return $Body;
+}
+
+sub NumDots($) {
+ # Count the number of dots in a string
+ # There's probably a better way to do this
+ my $DNA = shift;
+ my $a = 0;
+ while($DNA =~ /\./ig){$a++}
+ return $a
+}
+
+sub DetailSheet($) {
+ # Populate the table with details depending on the value of %opts{detail}
+ my $Seconds = shift;
+ my $Body = '';
+
+ return '' unless $opt{detail};
+
+ # Monthly/Weekly/Daily
+ $Body .= "<table border='1' bordercolor=#111111>\n";
+ $Body .= "<tr>\n";
+ $Body .= "<td class ='appHeader' colspan='3'>IT Network Systems Availability Previous " . $Seconds/86400 . " Day(s)</td></tr>\n";
+ $Body .= "<tr>\n";
+ $Body .= "<td class ='appHeader' colspan='3'>Compiled: ". scalar(localtime) . "</td></tr>\n";
+ $Body .= "<tr>\n";
+ $Body .= "<td class = 'SubHead' width='40%'>Service</td>
+ <td class = 'SubHead' width='30%'>Seconds</td>
+ <td class = 'SubHead' width='30%'>Percent</td></tr>\n";
+
+ my %CornBeef;
+ my %CornBeefC;
+
+ CASE: {
+ %CornBeef = %Daily, %CornBeefC = %DailyC, print "Doing Daily\n", last CASE if $Seconds == 86400;
+ %CornBeef = %Weekly, %CornBeefC = %WeeklyC, print "Doing Weekly\n", last CASE if $Seconds == 604800;
+ %CornBeef = %Monthly, %CornBeefC = %MonthlyC, print "Doing Monthly\n", last CASE if $Seconds == 2592000;
+ %CornBeef = %Quarterly, %CornBeefC = %QuarterlyC, print "Doing Quarterly\n", last CASE if $Seconds == 7776000;
+ } # end of CASE block
+
+ foreach (sort { $a cmp $b } keys %CornBeef ) {
+ next if NumDots ($_) > $opt{detail};
+ if ( $_ =~ /\./ ) {
+ #this is a sub section
+ $Body .= "<tr>\n";
+ $Body .= "<td class = 'SubSubHead'>$_</td>\n";
+ $Body .= "<td class = 'SubDetail'>" . sprintf('%.0f',(100 - $CornBeef{$_} / $CornBeefC{$_}) * ($Seconds/100)) . "</td>\n";
+ $Body .= "<td class = 'SubDetail'>" . sprintf('%.2f',$CornBeef{$_} / $CornBeefC{$_}) . "%</td>\n";
+ $Body .= "</tr>\n";
+ } else {
+ # this is a non-sub section
+ $Body .= "<tr>\n";
+ $Body .= "<td class = 'SubHead'>" . $_ . "</td>\n";
+ $Body .= "<td class = 'SubDetail'>" . sprintf('%.0f',(100 - $CornBeef{$_} / $CornBeefC{$_}) * ($Seconds/100)) . "</td>\n";
+ $Body .= "<td class = 'SubDetail'>" . sprintf('%.2f',$CornBeef{$_} / $CornBeefC{$_}) . "%</td>\n";
+ $Body .= "</tr>";
+ }
+ }
+ $Body .= "</table>\n";
+ return $Body;
+ }
+
+sub list_rrds($$$);
+sub list_rrds($$$) {
+ # List the RRD's used by this configuration
+ my $tree = shift;
+ my $path = shift;
+ my $print = shift;
+ my $prline;
+ foreach my $rrds (keys %{$tree}) {
+ if (ref $tree->{$rrds} eq 'HASH'){
+ $prline .= list_rrds( $tree->{$rrds}, $path."/$rrds", $print );
+ }
+ if ($rrds eq 'host') {
+ $prline .= "$cfg->{General}{datadir}$path".".rrd\n";
+ }
+ }
+ return $prline;
+}
+
+sub load_cfg ($) {
+ my $cfgfile = shift;
+# my $parser = get_parser;
+ my $parser = Smokeping::get_parser;
+ $cfg = Smokeping::get_config $parser, $cfgfile;
+}
+
+###########################################################################
+# The Main Program
+###########################################################################
+
+sub main($);
+main($cfgfile);
+
+sub main ($) {
+ umask 022;
+ my $cfgfile = shift;
+ my $sendto;
+ GetOptions(\%opt, 'quiet','version','testmail','listrrds','to=s','detail=n','morning','weekly','help','man') or pod2usage(2);
+ if($opt{version}) { print "$RCS_VERSION\n"; exit(0) };
+ if($opt{help}) { pod2usage(-verbose => 1); exit 0 };
+ if($opt{man}) { pod2usage(-verbose => 2); exit 0 };
+ load_cfg $cfgfile;
+ print "tSmoke for network managed by $cfg->{General}{owner}\nat $cfg->{General}{contact}\n(c) 2003 Dan McGinn-Combs\n" unless $opt{quiet};
+ if($opt{testmail}) { test_mail($cfg) };
+ if($opt{listrrds}) { print "List of Round Robin Databases used by this implementation\n";
+ my @rrds = split ( /\n/,list_rrds($cfg->{Targets},"","") );
+ foreach (@rrds) {
+ print "RRD: $_\n"; };
+ }
+ if($opt{morning}) { morning_update($cfg) };
+ if($opt{weekly}) { weekly_update($cfg) };
+ exit 0;
+}
+
+=head1 NAME
+
+tSmoke - Commandline tool for sending SmokePing information
+
+=head1 SYNOPSIS
+
+B<tSmoke> [ B<--testmail> | B<--morning> | B<--weekly> | B<--version> | B<--help> | B<--man>]
+
+ Options:
+
+ --man Show the manpage
+ --help Help :-)
+ --version Show SmokePing Version
+ --testmail Send a test message
+ --listrrds List the RRDs used by this Smokeping
+ --morning Send a morning synopsis
+ --weekly Send a weekly status report
+ --to E-mail address to send message (i.e. '--to=xyz@company.com.invalid'
+ --detail How much detail to send in weekly report (i.e. '--detail=1')
+ --quiet Do not print welcome
+
+=head1 DESCRIPTION
+
+The B<tSmoke> tool is a commandline tool which interfaces with the SmokePing system.
+Its main function is to send a message indicating the current status of the systems
+being monitored by Smokeping or an HTML mail file containing the status over the past day,
+past week and past month including an overview.
+
+Typical crontab used to invoke this are
+
+ # Quick morning alert to see what's down
+ 0 6 * * * /usr/local/smokeping/bin/tSmoke.pl --q --to=mobilephone@att.net.invalid --morning
+ # Weekly report on the percent availability of network systems with no detail
+ 0 8 * * * /usr/local/smokeping/bin/tSmoke.pl --q --to=mailbox@company.com.invalid --weekly --detail=0
+
+=head1 SETUP
+
+When installing tSmoke, some variables must be adjusted to fit your local system.
+
+We need to use the following B<libraries>:
+
+=over
+
+=item Smokeping
+
+=item RRDTool Perl bindings
+
+=item Getopt::Long
+
+=back
+
+Set up your libraries:
+
+ use lib "/usr/local/smokeping/lib";
+ use lib "/usr/local/rrdtool-1.0.39/lib/perl";
+
+Point to your Smokeping B<config> file
+
+ my $cfgfile = "/usr/local/smokeping/etc/config";
+
+Modify the Smokeping config file to include a path for tmail in the
+General section:
+
+ tmail = /usr/local/smokeping/etc/tmail
+
+=head1 COPYRIGHT
+
+Copyright (c) 2003 by Dan McGinn-Combs. All right reserved.
+
+=head1 LICENSE
+
+This program is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied
+warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+PURPOSE. See the GNU General Public License for more
+details.
+
+You should have received a copy of the GNU General Public
+License along with this program; if not, write to the Free
+Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
+02139, USA.
+
+=head1 AUTHOR
+
+Dan McGinn-Combs E<lt>d.mcginn-combs@mindspring.comE<gt>
+
+Modified for Smokeping official distribution by Niko Tyni E<lt>ntyni@iki.fiE<gt>
+
+=cut
+
diff --git a/doc/smokeping_extend.pod b/doc/smokeping_extend.pod
index eef0bf6..0a0eb41 100644
--- a/doc/smokeping_extend.pod
+++ b/doc/smokeping_extend.pod
@@ -46,7 +46,7 @@ manpage. The supported section names are
C<name>, C<overview>, C<description>, C<authors>, C<notes>, C<bugs>, and
C<see_also>. If you don't need a particular section, just leave it out.
-The special sections C<synopsys> and C<variables> are automatically
+The special sections C<synopsis> and C<variables> are automatically
generated from the description of your variables. See below.
Note that if you use 'here documents' ('<<') that have POD markup inside,
@@ -94,13 +94,13 @@ convenience method called C<_makevars> that does this, and the common idiom is
}
The variables are declared in a syntax that comes from the module used
-for parsing the configuration file, C<ISG::ParseConfig>. Each variable
+for parsing the configuration file, C<Config::Grammar>. Each variable
should be a hash that uses the "special variable keys" documented in
-L<ISG::ParseConfig>. See C<Smokeping::probes::skel> and the other
+L<Config::Grammar>. See C<Smokeping::probes::skel> and the other
probes for examples.
For reference, here are the keys the hash should have. Much of this
-is taken straight from the C<ISG::ParseConfig> manual.
+is taken straight from the C<Config::Grammar> manual.
=over
@@ -114,7 +114,7 @@ Description of the variable.
=item _example
-An example value. This will be used in the SYNOPSYS section in the
+An example value. This will be used in the SYNOPSIS section in the
probe manual.
=back
@@ -150,11 +150,11 @@ the returned string as content.
The C<probevars> and C<targetvars> methods should return hash references
that contain the variable names as keys and the hashes described above
-as values. In addition the C<ISG::ParseConfig> special section key
+as values. In addition the C<Config::Grammar> special section key
C<_mandatory> is supported and should contain a reference to a list of
mandatory variables. The C<_makevars> method is available of this special
key and merges the mandatory lists in its arguments. Note that no other
-C<ISG::ParseConfig> special section keys are supported.
+C<Config::Grammar> special section keys are supported.
=head1 INITIALIZATION
@@ -185,7 +185,7 @@ That's it, you're done!
=head1 EXAMPLE CONFIGURATIONS
If you would like to provide a documented example configuration for your
-probe (in addition to the automatically generated SYNOPSYS section in
+probe (in addition to the automatically generated SYNOPSIS section in
the probe manual), you can do so by adding it to the L<Smokeping::Examples|Smokeping::Examples>
module. Look for the 'examples' subroutine and add your example there.
diff --git a/doc/smokeping_upgrade.pod b/doc/smokeping_upgrade.pod
index bd3b93a..df5013b 100644
--- a/doc/smokeping_upgrade.pod
+++ b/doc/smokeping_upgrade.pod
@@ -23,7 +23,7 @@ An official list of changes with each release can be found in the CHANGES
file in the Smokeping distribution. This document tries to complement
that with upgrading instructions etc.
-=head1 1.38 to 2.0
+=head1 1.40 to 2.0
The biggest change with the 2.0 release is that the configuration file
is now parsed much more strictly. This should result in (hopefully
@@ -32,6 +32,13 @@ trial-and-error variety than it used to be. It also automates the
generation of the configuration documentation from the source code,
so the docs are now more accurate.
+A smaller change worth mentioning is the inclusion of the tSmoke script
+(contributed by Dan McGinn-Combs) for sending summary emails on daily
+and weekly system status. Note that it needs the new 'tmail' variable
+to be defined in the config file.
+
+=head2 CONFIGURATION
+
The configuration syntax has stayed mostly the same, except for the
issues below.
@@ -105,13 +112,24 @@ and measuring and visualizing the variation between them.
Smokeping now checks at startup that the parameters of any existing RRD files
match those specified in the configuration file. If there is a discrepancy,
-it will give an error message and refuse to start.
+it will try to fix the situation and refuse to start if it can't.
-This situation is most likely to happen if you have modified the C<step>
-or C<pings> variables in your configuration file. You'll then have to
+This situation is most likely to happen if you have modified the
+C<pings> variable in your configuration file. You'll then have to
delete the old RRD file or somehow convert it to use the new parameters.
The C<rrdtune> command might be helpful here.
+=item Configurable location for DYNAMIC-related files
+
+There is now a new configuration variable, C<dyndir>, that can be used
+to specify the location of the DYNAMIC-related files (.adr and .snmp).
+These files used to be kept under C<datadir> along with the RRD files,
+but since they need to be writable by the web server, it may be useful
+to separate these.
+
+If C<dyndir> is not specified, Smokeping will use the C<datadir> value
+as the default. This should ensure that no existing setups will break.
+
=back
In addition to this, some probes have had minor incompatible changes to
@@ -148,6 +166,8 @@ quotes are not needed anymore around the User-Agent string (the C<agent>
parameter). Smokeping will complain if it notices quotes around the
string.
+Any extra arguments for C<curl> can now be specified in the C<extraargs> variable.
+
=item L<EchoPingHttp|Smokeping::probes::EchoPingHttp>
The default timeout of this probe has been raised to 10 seconds.
@@ -197,6 +217,17 @@ in the C<host> variable, like it is with all the other probes.
=back
+=head1 1.38 to 1.40
+
+=over
+
+=item The new navigation feature
+
+The big visible difference between 1.38 and 1.40 is the new browser navigation
+feature: when clicking on the graphs in detail view you can select
+different time ranges for the graph. The creation of this
+feature has been sponsored by BeverlyCorp.com.
+
=head1 1.34 to 1.37
=over
diff --git a/etc/config.dist b/etc/config.dist
index 71560ba..71c77ff 100644
--- a/etc/config.dist
+++ b/etc/config.dist
@@ -14,6 +14,7 @@ datadir = /home/oetiker/data/projects/AADJ-smokeping/dist/var
piddir = /home/oetiker/data/projects/AADJ-smokeping/dist/var
cgiurl = http://people.ee.ethz.ch/~oetiker/smokeping/smokeping.cgi
smokemail = /home/oetiker/data/projects/AADJ-smokeping/dist/etc/smokemail.dist
+tmail = /home/oetiker/data/projects/AADJ-smokeping/dist/etc/tmail.dist
# specify this to get syslog logging
syslogfacility = local0
# each probe is now run in its own process
diff --git a/etc/smokemail.dist b/etc/smokemail.dist
index 5b4d4da..5caa4c5 100644
--- a/etc/smokemail.dist
+++ b/etc/smokemail.dist
@@ -4,7 +4,7 @@ Subject: SmokePing Agent
Hi,
-Please execute the attache Perl Script on your computer. It will register
+Please execute the attached Perl Script on your computer. It will register
your IP with SmokePing. You have to rerun this script at least everytime
your IP changes. You can run the script as often as you want.
diff --git a/etc/tmail.dist b/etc/tmail.dist
new file mode 100644
index 0000000..a9d7bbf
--- /dev/null
+++ b/etc/tmail.dist
@@ -0,0 +1,132 @@
+MIME-Version: 1.0
+Content-Type: text/html
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<html>
+<head>
+<TITLE>IT System Availability Report</TITLE>
+<STYLE TYPE ='text/css'>
+.appHeader {
+ color: #000000;
+ font-family: Arial, Sans-Serif;
+ font-size: 12px;
+ font-weight: bold;
+ padding-left: 10px;
+ padding-top: 7px;
+ border-width: 10px;
+ border-color: #FFFFFF;
+ border-style:none;
+ border-left-width: 10px;
+ border-left-color: #8CAAE6;
+ border-left-style: solid;
+ }
+.Head {
+ color: Turquoise;
+ background-color: White;
+ font-family: Verdana, Helvetica, San-Serif;
+ font-size: 20px;
+ font-weight: bold;
+ text-decoration: underline;
+ }
+.SubSubHead {
+ color: RoyalBlue;
+ background-color: CornSilk;
+ font-family: Verdana, Helvetica, San-Serif;
+ font-size: 11px;
+ font-weight: bold;
+ }
+.SubHead {
+ color: RoyalBlue;
+ background-color: CornSilk;
+ font-family: Verdana, Helvetica, San-Serif;
+ font-size: 13px;
+ font-weight: bold;
+ text-decoration: underline;
+ }
+.SubDetail {
+ color: Black;
+ font-family: Verdana, Helvetica, San-Serif;
+ font-size: 11px;
+ background-color: CornSilk;
+ }
+.Up99 {
+ color: DarkGreen;
+ text-align: Center;
+ background-color: CornSilk;
+ font-family: Verdana, Helvetica, San-Serif;
+ font-size: 11px;
+ font-weight: bold;
+ }
+.Up95 {
+ color: DarkBlue;
+ text-align: Center;
+ background-color: CornSilk;
+ font-family: Verdana, Helvetica, San-Serif;
+ font-size: 11px;
+ font-weight: bold;
+ }
+.Up90 {
+ color: Orange;
+ text-align: Center;
+ background-color: CornSilk;
+ font-family: Verdana, Helvetica, San-Serif;
+ font-size: 11px;
+ font-weight: bold;
+ }
+.UpNo {
+ color: Red;
+ text-align: Center;
+ background-color: CornSilk;
+ font-family: Verdana, Helvetica, San-Serif;
+ font-size: 11px;
+ font-weight: bold;
+ }
+.Display-Block { display:'block' }
+.Display-None { display:'none' }
+</STYLE>
+</head>
+<body onLoad=
+"Day.style.display='none';
+ Week.style.display='none';
+ Month.style.display='none';
+ Quarter.style.display='none';" >
+<table width='100%' border='0' cellspacing='0' cellpadding='0'>
+<tr><td bgcolor='#FFFFFF'>
+<img border='0' src='http://www.xxxx.com/docs/TEMPLATE/204/xxxx_logo.gif' alt='Put your logo here'></a></td><td class='Head'>XXXX IT System Availability</td></tr>
+</table>
+<P>
+<P>
+<HR width='80%'>
+<P>
+<##SUMMARY##>
+<p>
+<CENTER>
+<table width='60%' border='1' cellspacing='0' cellpadding='0'>
+<tr>
+<td bgcolor='CornSilk' onMouseOver="Quarter.style.display='block'"
+ onMouseOut="Quarter.style.display='none'">Quarterly Detail</td>
+<td bgcolor='CornSilk' onMouseOver="Month.style.display='block'"
+ onMouseOut="Month.style.display='none'">Monthly Detail</td>
+<td bgcolor='CornSilk' onMouseOver="Week.style.display='block'"
+ onMouseOut="Week.style.display='none'">Weekly Detail</td>
+<td bgcolor='CornSilk' onMouseOver="Day.style.display='block'"
+ onMouseOut="Day.style.display='none'">Daily Detail</td>
+</tr>
+</table>
+</CENTER>
+<p>
+<div id="Day">
+<##DAYDETAIL##>
+</div>
+<p>
+<div id="Week">
+<##WEEKDETAIL##>
+</div>
+<p>
+<div id="Month">
+<##MONTHDETAIL##>
+</div>
+<p>
+<div id=Quarter>
+<##QUARTERDETAIL##>
+</div>
+</html></body>
diff --git a/htdocs/smokeping.cgi.dist b/htdocs/smokeping.cgi.dist
index f26ad5b..6a391b5 100755
--- a/htdocs/smokeping.cgi.dist
+++ b/htdocs/smokeping.cgi.dist
@@ -5,7 +5,7 @@ use lib qw(/usr/pack/rrdtool-1.0.33-to/lib/perl);
use lib qw(/home/oetiker/data/projects/AADJ-smokeping/dist/lib);
use CGI::Carp qw(fatalsToBrowser);
-use Smokeping 1.38;
+use Smokeping 1.99006;
Smokeping::cgi("/home/oetiker/data/projects/AADJ-smokeping/dist/etc/config");
diff --git a/lib/ISG/ParseConfig.pm b/lib/Config/Grammar.pm
index 9afdb11..bd3deea 100644
--- a/lib/ISG/ParseConfig.pm
+++ b/lib/Config/Grammar.pm
@@ -1,4 +1,4 @@
-package ISG::ParseConfig;
+package Config::Grammar;
# TODO:
# - _order for sections
@@ -6,7 +6,7 @@ package ISG::ParseConfig;
use strict;
use vars qw($VERSION);
-$VERSION = 1.9;
+$VERSION = '1.02';
sub new($$)
{
@@ -58,7 +58,7 @@ sub _quotesplit($)
push @items, $frag;
}
else {
- die "Internal parser error for '$line'\n";
+ die "Internal parser error for '$line'";
}
}
return @items;
@@ -93,7 +93,7 @@ sub _check_mandatory($$$$)
if (not defined $g->{$_}) {
$g->{$_} = {};
-#$self->{'err'} = "ParseConfig internal error: mandatory name $_ not found in grammar";
+#$self->{'err'} = "Config::Grammar internal error: mandatory name $_ not found in grammar";
#return 0;
}
if (not defined $c->{$_}) {
@@ -201,7 +201,7 @@ sub _next_level($$$)
my $s = $self->_search_section($name);
return 0 unless defined $s;
if (not defined $self->{grammar}{$s}) {
- $self->_make_error("ParseConfig internal error (no grammar for $s)");
+ $self->_make_error("Config::Grammar internal error (no grammar for $s)");
return 0;
}
push @{$self->{grammar_stack}}, $self->{grammar};
@@ -587,7 +587,7 @@ sub _parse_line($$$)
return 1;
};
/^\@define\s+(\S+)\s+(.*)$/ and do {
- $self->{defines}{$1}=quotemeta $2;
+ $self->{defines}{$1}=$2;
return 1;
};
@@ -1039,31 +1039,31 @@ __END__
=head1 NAME
-ISG::ParseConfig - Simple config parser
+Config::Grammar - A grammar-based, user-friendly config parser
=head1 SYNOPSIS
- use ISG::ParseConfig;
+ use Config::Grammar;
- my $parser = ISG::ParseConfig->new(\%grammar);
+ my $parser = Config::Grammar->new(\%grammar);
my $cfg = $parser->parse('app.cfg') or die "ERROR: $parser->{err}\n";
my $pod = $parser->makepod();
my $ex = $parser->maketmpl('TOP','SubNode');
=head1 DESCRIPTION
-ISG::ParseConfig is a module to parse configuration files. The
+Config::Grammar is a module to parse configuration files. The
configuration may consist of multiple-level sections with assignments
and tabular data. The parsed data will be returned as a hash
-containing the whole configuration. ISG::ParseConfig uses a grammar
-that is supplied upon creation of a ISG::ParseConfig object to parse
+containing the whole configuration. Config::Grammar uses a grammar
+that is supplied upon creation of a Config::Grammar object to parse
the configuration file and return helpful error messages in case of
-syntax errors. Using the B<makepod> methode you can generate
+syntax errors. Using the B<makepod> method you can generate
documentation of the configuration file format.
The B<maketmpl> method can generate a template configuration file. If
your grammar contains regexp matches, the template will not be all
-that helpful as ParseConfig is not smart enough to give you sensible
+that helpful as Config::Grammar is not smart enough to give you sensible
template data based in regular expressions.
=head2 Grammar Definition
@@ -1321,7 +1321,7 @@ be escaped with a backslash as well.
=head3 Sections
-ISG::ParseConfig supports hierarchical configurations through sections, whose
+Config::Grammar supports hierarchical configurations through sections, whose
syntax is as follows:
=over 15
@@ -1358,7 +1358,7 @@ The data is interpreted as one or more columns separated by spaces.
=head3 Code
- my $parser = ISG::ParseConfig->new({
+ my $parser = Config::Grammar->new({
_sections => [ 'network', 'hosts' ],
network => {
_vars => [ 'dns' ],
@@ -1426,18 +1426,18 @@ The data is interpreted as one or more columns separated by spaces.
*** network ***
- dns = 129.132.7.87
+ dns = 192.168.7.87
- + 129.132.7.64
+ + 192.168.7.64
netmask = 255.255.255.192
- gateway = 129.132.7.65
+ gateway = 192.168.7.65
*** hosts ***
- 00:50:fe:bc:65:11 129.132.7.97 plain.hades
- 00:50:fe:bc:65:12 129.132.7.98 isg.ee.hades
- 00:50:fe:bc:65:14 129.132.7.99 isg.ee.hades
+ 00:50:fe:bc:65:11 192.168.7.97 plain.hades
+ 00:50:fe:bc:65:12 192.168.7.98 isg.ee.hades
+ 00:50:fe:bc:65:14 192.168.7.99 isg.ee.hades
=head3 Result
@@ -1445,68 +1445,48 @@ The data is interpreted as one or more columns separated by spaces.
'hosts' => {
'00:50:fe:bc:65:11' => [
'00:50:fe:bc:65:11',
- '129.132.7.97',
+ '192.168.7.97',
'plain.hades'
],
'00:50:fe:bc:65:12' => [
'00:50:fe:bc:65:12',
- '129.132.7.98',
+ '192.168.7.98',
'isg.ee.hades'
],
'00:50:fe:bc:65:14' => [
'00:50:fe:bc:65:14',
- '129.132.7.99',
+ '192.168.7.99',
'isg.ee.hades'
]
},
'network' => {
- '129.132.7.64' => {
+ '192.168.7.64' => {
'netmask' => '255.255.255.192',
- 'gateway' => '129.132.7.65'
+ 'gateway' => '192.168.7.65'
},
- 'dns' => '129.132.7.87'
+ 'dns' => '192.168.7.87'
}
};
=head1 COPYRIGHT
-Copyright (c) 2000, 2001 by ETH Zurich. All rights reserved.
+Copyright (c) 2000-2005 by ETH Zurich. All rights reserved.
=head1 LICENSE
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
=head1 AUTHOR
-S<David Schweikert E<lt>dws@ee.ethz.chE<gt>>
-S<Tobias Oetiker E<lt>oetiker@ee.ethz.chE<gt>>
+David Schweikert E<lt>dws_at_ee.ethz.chE<gt>,
+Tobias Oetiker E<lt>oetiker_at_ee.ethz.chE<gt>,
+Niko Tyni E<lt>ntyni_at_iki.fiE<gt>
=head1 HISTORY
- 2001-05-11 ds 1.2 Initial Version for policy 0.3
- 2001-09-04 ds 1.3 Remove space before comments, more strict variable definition
- 2001-09-19 to 1.4 Added _sub error parsing and _doc self documentation
- 2001-10-20 to Improved Rendering of _doc information
- 2002-01-09 to Added Documentation to the _text section documentation
- 2002-01-28 to Fixed quote parsing in tables
- 2002-03-12 ds 1.5 Implemented @define, make makepod return a string and not an array
- 2002-08-28 to Added maketmpl methode
- 2002-10-10 ds 1.6 More verbatim _text sections
- 2004-02-09 to 1.7 Added _example propperty for pod and template generation
- 2004-08-17 to 1.8 Allow special input files like "program|"
- 2005-01-10 ds 1.9 Implemented _dyn, _default, _recursive, and _inherited (Niko Tyni)
+ 2001-05-11 ds Initial Version of ISG::ParseConfig
+ 2005-03-08 ds 1.00 Renamed from ISG::ParseConfig to Config::Grammar
=cut
diff --git a/lib/Smokeping.pm b/lib/Smokeping.pm
index 8cd5897..28923e7 100644
--- a/lib/Smokeping.pm
+++ b/lib/Smokeping.pm
@@ -9,18 +9,25 @@ use Digest::MD5 qw(md5_base64);
use SNMP_util;
use SNMP_Session;
use POSIX;
-use ISG::ParseConfig;
+use Config::Grammar;
use RRDs;
use Sys::Syslog qw(:DEFAULT setlogsock);
+
setlogsock('unix')
if grep /^ $^O $/xo, ("linux", "openbsd", "freebsd", "netbsd");
+
+# make sure we do not end up with , in odd places where one would expect a '.'
+# we set the environment variable so that our 'kids' get the benefit too
+$ENV{LC_NUMERIC}='C';
+POSIX::setlocale(&POSIX::LC_NUMERIC,"");
+
use File::Basename;
use Smokeping::Examples;
use Smokeping::RRDtools;
# globale persistent variables for speedy
use vars qw($cfg $probes $VERSION $havegetaddrinfo $cgimode);
-$VERSION="1.99001";
+$VERSION="1.99006";
# we want opts everywhere
my %opt;
@@ -50,6 +57,10 @@ sub find_libdir {
sub do_log(@);
sub load_probe($$$$);
+sub dummyCGI::param {
+ return wantarray ? () : "";
+}
+
sub load_probes ($){
my $cfg = shift;
my %prbs;
@@ -99,6 +110,11 @@ sub lnk ($$) {
}
}
+sub dyndir ($) {
+ my $cfg = shift;
+ return $cfg->{General}{dyndir} || $cfg->{General}{datadir};
+}
+
sub update_dynaddr ($$){
my $cfg = shift;
my $q = shift;
@@ -107,14 +123,19 @@ sub update_dynaddr ($$){
my $address = $ENV{REMOTE_ADDR};
my $targetptr = $cfg->{Targets};
foreach my $step (@target){
- return "Error: Unknown Target $step"
+ return "Error: Unknown target $step"
unless defined $targetptr->{$step};
$targetptr = $targetptr->{$step};
};
- return "Error: Invalid Target"
+ return "Error: Invalid target or secret"
unless defined $targetptr->{host} and
$targetptr->{host} eq "DYNAMIC/${secret}";
- my $file = $cfg->{General}{datadir}."/".(join "/", @target);
+ my $file = dyndir($cfg);
+ for (0..$#target-1) {
+ $file .= "/" . $target[$_];
+ ( -d $file ) || mkdir $file, 0755;
+ }
+ $file.= "/" . $target[-1];
my $prevaddress = "?";
my $snmp = snmpget_ident $address;
if (-r "$file.adr" and not -z "$file.adr"){
@@ -342,7 +363,7 @@ sub init_target_tree ($$$$) {
foreach my $prop (keys %{$tree}) {
if (ref $tree->{$prop} eq 'HASH'){
- if (not -d $name) {
+ if (not -d $name and not $cgimode) {
mkdir $name, 0755 or die "ERROR: mkdir $name: $!\n";
};
init_target_tree $cfg, $probes, $tree->{$prop}, "$name/$prop";
@@ -381,6 +402,7 @@ sub init_target_tree ($$$$) {
my $comparison = Smokeping::RRDtools::compare($name.".rrd", \@create);
die("Error: RRD parameter mismatch ('$comparison'). You must delete $name.rrd or fix the configuration parameters.\n")
if $comparison;
+ Smokeping::RRDtools::tuneds($name.".rrd", \@create);
}
}
}
@@ -542,6 +564,9 @@ sub get_overview ($$$$){
my $date = $cfg->{Presentation}{overview}{strftime} ?
POSIX::strftime($cfg->{Presentation}{overview}{strftime},
localtime(time)) : scalar localtime(time);
+ if ( $RRDs::VERSION >= 1.199908 ){
+ $date =~ s|:|\\:|g;
+ }
foreach my $prop (sort {$tree->{$a}{_order} <=> $tree->{$b}{_order}}
grep { ref $tree->{$_} eq 'HASH' and defined $tree->{$_}{host}}
keys %$tree) {
@@ -556,9 +581,11 @@ sub get_overview ($$$$){
'--start','-'.exp2seconds($cfg->{Presentation}{overview}{range}),
'--title',$tree->{$prop}{title},
'--height',$cfg->{Presentation}{overview}{height},
- '--width',,$cfg->{Presentation}{overview}{width},
+ '--width',$cfg->{Presentation}{overview}{width},
'--vertical-label',"Seconds",
'--imgformat','PNG',
+ '--alt-autoscale-max',
+ '--alt-y-grid',
'--lower-limit','0',
"DEF:median=${rrd}:median:AVERAGE",
"DEF:loss=${rrd}:loss:AVERAGE",
@@ -566,11 +593,10 @@ sub get_overview ($$$$){
"CDEF:dm=median,0,$max,LIMIT",
"CDEF:dm2=median,1.5,*,0,$max,LIMIT",
"LINE1:dm2", # this is for kicking things down a bit
- "LINE1:dm#$medc:median RTT avg\\: ",
- "GPRINT:median:AVERAGE: %0.2lf %ss ",
- "GPRINT:median:LAST: latest RTT\\: %0.2lf %ss ",
- "GPRINT:ploss:AVERAGE: avg pkg loss\\: %.2lf %% ",
- "COMMENT: $date\\j");
+ "LINE1:dm#$medc:median RTT",
+ "GPRINT:median:AVERAGE:avg RTT\\: %.2lf %ss",
+ "GPRINT:ploss:AVERAGE:avg pkt loss\\: %.2lf %%",
+ "COMMENT:$date\\j");
my $ERROR = RRDs::error();
$page .= "<div>";
if (defined $ERROR) {
@@ -645,26 +671,46 @@ sub smokecol ($) {
return \@items;
}
+sub parse_datetime($){
+ my $in = shift;
+ for ($in){
+ /^\s*(\d{4})-(\d{1,2})-(\d{1,2})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?\s*$/ &&
+ return POSIX::mktime($6||0,$5||0,$4||0,$3,$2-1,$1-1900,0,0,-1);
+ /([ -:a-z0-9]+)/ && return $1;
+ };
+}
+
sub get_detail ($$$$){
+ # when drawing the detail page there are two modes for doing it
+ # a) classic with several static graphs on the page
+ # b) with one graph and below the graph one can specify the end time
+ # and the length of the graph.
my $cfg = shift;
my $q = shift;
my $tree = shift;
my $open = shift;
+
return "" unless $tree->{host};
+
my @dirs = @{$open};
my $file = pop @dirs;
my $dir = "";
- die "ERROR: ".(join ".", @dirs)." has no probe defined\n"
+
+ return "<div>ERROR: ".(join ".", @dirs)." has no probe defined</div>"
unless $tree->{probe};
- die "ERROR: ".(join ".", @dirs)." $tree->{probe} is not known\n"
+
+ return "<div>ERROR: ".(join ".", @dirs)." $tree->{probe} is not known</div>"
unless $cfg->{__probes}{$tree->{probe}};
+
my $probe = $cfg->{__probes}{$tree->{probe}};
my $ProbeDesc = $probe->ProbeDesc();
my $step = $probe->step();
my $pings = $probe->_pings($tree);
-
my $page;
+ my $mode = $q->param('displaymode') || 's';
+ return "<div>ERROR: unknown displaymode $mode</div>"
+ unless $mode =~ /^[sn]$/;
for (@dirs) {
$dir .= "/$_";
@@ -674,142 +720,190 @@ sub get_detail ($$$$){
unless -d $cfg->{General}{imgcache}.$dir;
}
- my $rrd = $cfg->{General}{datadir}."/".(join "/", @dirs)."/${file}.rrd";
- my $img = $cfg->{General}{imgcache}."/".(join "/", @dirs)."/${file}.rrd";
-
- my %lasthight;
- if (open (HG,"<${img}.maxhight")){
- while (<HG>){
- chomp;
- my @l = split / /;
- $lasthight{$l[0]} = $l[1];
- }
- close HG;
- }
- my $max = findmax $cfg, $rrd;
- if (open (HG,">${img}.maxhight")){
- foreach my $s (keys %{$max}){
- print HG "$s $max->{$s}\n";
- }
- close HG;
- }
+ my $rrd = $cfg->{General}{datadir}."/".$dir."/${file}.rrd";
+
+ my $imgbase;
+ my $imghref;
+ my $max;
+ my @tasks;
+ my %lasthight;
+
+ if ($mode eq 's'){
+ # in nave mode there is only one graph, so the height calculation
+ # is not necessary.
+ $imgbase = $cfg->{General}{imgcache}."/".(join "/", @dirs)."/${file}";
+ $imghref = $cfg->{General}{imgurl}."/".(join "/", @dirs)."/${file}";
+ @tasks = @{$cfg->{Presentation}{detail}{_table}};
+ if (open (HG,"<${imgbase}.maxhight")){
+ while (<HG>){
+ chomp;
+ my @l = split / /;
+ $lasthight{$l[0]} = $l[1];
+ }
+ close HG;
+ }
+ $max = findmax $cfg, $rrd;
+ if (open (HG,">${imgbase}.maxhight")){
+ foreach my $s (keys %{$max}){
+ print HG "$s $max->{$s}\n";
+ }
+ close HG;
+ }
+ } else {
+ mkdir $cfg->{General}{imgcache}."/__navcache",0755 unless -d $cfg->{General}{imgcache}."/__navcache";
+ # remove old images after one hour
+ my $pattern = $cfg->{General}{imgcache}."/__navcache/*.png";
+ for (glob $pattern){
+ unlink $_ if time - (stat $_)[9] > 3600;
+ }
+ $imgbase =$cfg->{General}{imgcache}."/__navcache/".time()."$$";
+ $imghref =$cfg->{General}{imgurl}."/__navcache/".time()."$$";
+ @tasks = (["Navigator Mode", parse_datetime($q->param('start')),parse_datetime($q->param('end'))]);
+ my ($graphret,$xs,$ys) = RRDs::graph
+ ("dummy",
+ '--start', $tasks[0][1],
+ '--end',$tasks[0][2],
+ "DEF:maxping=${rrd}:median:AVERAGE",
+ 'PRINT:maxping:MAX:%le' );
+ my $ERROR = RRDs::error();
+ return "<div>RRDtool did not understand your input: $ERROR.</div>" if $ERROR;
+ my $val = $graphret->[0];
+ $val = 1 if $val =~ /nan/i;
+ $max = { $tasks[0][1] => $val * 1.5 };
+ }
+
my $smoke = $pings >= 3
- ? smokecol $pings :
- [ 'COMMENT:(Not enough pings to draw any smoke.)\s', 'COMMENT:\s' ];
- # one \s doesn't seem to be enough
+ ? smokecol $pings :
+ [ 'COMMENT:(Not enough pings to draw any smoke.)\s', 'COMMENT:\s' ];
+ # one \s doesn't seem to be enough
my @upargs;
my @upsmoke;
- my @median;
- my $date = $cfg->{Presentation}{detail}{strftime} ?
- POSIX::strftime($cfg->{Presentation}{detail}{strftime},
- localtime(time)) : scalar localtime(time);
-
- for (@{$cfg->{Presentation}{detail}{_table}}) {
- my ($desc,$start) = @{$_};
- $start = exp2seconds($start);
- do {
- @median = ("DEF:median=${rrd}:median:AVERAGE",
- "DEF:loss=${rrd}:loss:AVERAGE",
- "CDEF:ploss=loss,$pings,/,100,*",
- "GPRINT:median:AVERAGE:Median Ping RTT (avg %.1lf %ss) ",
- "LINE1:median#202020"
- );
+ my %lc;
+ if ( defined $cfg->{Presentation}{detail}{loss_colors}{_table} ) {
+ for (@{$cfg->{Presentation}{detail}{loss_colors}{_table}}) {
+ my ($num,$col,$txt) = @{$_};
+ $lc{$num} = [ $txt, "#".$col ];
+ }
+ } else {
my $p = $pings;
+ %lc = (0 => ['0', '#26ff00'],
+ 1 => ["1/$p", '#00b8ff'],
+ 2 => ["2/$p", '#0059ff'],
+ 3 => ["3/$p", '#5e00ff'],
+ 4 => ["4/$p", '#7e00ff'],
+ int($p/2) => [int($p/2)."/$p", '#dd00ff'],
+ $p-1 => [($p-1)."/$p", '#ff0000'],
+ );
+ };
- my %lc;
- my $lastup = 0;
- if ( defined $cfg->{Presentation}{detail}{loss_colors}{_table} ) {
- for (@{$cfg->{Presentation}{detail}{loss_colors}{_table}}) {
- my ($num,$col,$txt) = @{$_};
- $lc{$num} = [ $txt, "#".$col ];
- }
- } else {
- %lc = (0 => ['0', '#26ff00'],
- 1 => ["1/$p", '#00b8ff'],
- 2 => ["2/$p", '#0059ff'],
- 3 => ["3/$p", '#5e00ff'],
- 4 => ["4/$p", '#7e00ff'],
- int($p/2) => [int($p/2)."/$p", '#dd00ff'],
- $p-1 => [($p-1)."/$p", '#ff0000'],
+ my %upt;
+ if ( defined $cfg->{Presentation}{detail}{uptime_colors}{_table} ) {
+ for (@{$cfg->{Presentation}{detail}{uptime_colors}{_table}}) {
+ my ($num,$col,$txt) = @{$_};
+ $upt{$num} = [ $txt, "#".$col];
+ }
+ } else {
+ %upt = ( 3600 => ['<1h', '#FFD3D3'],
+ 2*3600 => ['<2h', '#FFE4C7'],
+ 6*3600 => ['<6h', '#FFF9BA'],
+ 12*3600 => ['<12h','#F3FFC0'],
+ 24*3600 => ['<1d', '#E1FFCC'],
+ 7*24*3600 => ['<1w', '#BBFFCB'],
+ 30*24*3600 => ['<1m', '#BAFFF5'],
+ '1e100' => ['>1m', '#DAECFF']
+ );
+ }
+
+ my $date = $cfg->{Presentation}{detail}{strftime} ?
+ POSIX::strftime($cfg->{Presentation}{detail}{strftime},
+ localtime(time)) : scalar localtime(time);
+ my $BS = '';
+ if ( $RRDs::VERSION >= 1.199908 ){
+ $date =~ s|:|\\:|g;
+ $ProbeDesc =~ s|:|\\:|g;
+ $BS = '\\';
+ }
+
+ for (@tasks) {
+ my ($desc,$start,$end) = @{$_};
+ $end ||= 'last';
+ $start = exp2seconds($start) if $mode eq 's';
+
+ my $startstr = $start =~ /^\d+$/ ? POSIX::strftime("%Y-%m-%d %H:%M",localtime($mode eq 'n' ? $start : time-$start)) : $start;
+ my $endstr = $end =~ /^\d+$/ ? POSIX::strftime("%Y-%m-%d %H:%M",localtime($mode eq 'n' ? $end : time)) : $end;
+
+ my $last = -1;
+ my $swidth = $max->{$start} / $cfg->{Presentation}{detail}{height};
+ my @median = ("DEF:median=${rrd}:median:AVERAGE",
+ "DEF:loss=${rrd}:loss:AVERAGE",
+ "CDEF:ploss=loss,$pings,/,100,*",
+ "GPRINT:median:AVERAGE:Median Ping RTT (%.1lf %ss avg) ",
+ "LINE1:median#202020"
);
- };
- my $last = -1;
- my $swidth = $max->{$start} / $cfg->{Presentation}{detail}{height};
foreach my $loss (sort {$a <=> $b} keys %lc){
next if $loss >= $pings;
- my $lvar = $loss; $lvar =~ s/\./d/g ;
+ my $lvar = $loss; $lvar =~ s/\./d/g ;
push @median,
- (
- "CDEF:me$lvar=loss,$last,GT,loss,$loss,LE,*,1,UNKN,IF,median,*",
- "CDEF:meL$lvar=me$lvar,$swidth,-",
- "CDEF:meH$lvar=me$lvar,0,*,$swidth,2,*,+",
- "AREA:meL$lvar",
- "STACK:meH$lvar$lc{$loss}[1]:$lc{$loss}[0]"
- );
- $last = $loss;
+ (
+ "CDEF:me$lvar=loss,$last,GT,loss,$loss,LE,*,1,UNKN,IF,median,*",
+ "CDEF:meL$lvar=me$lvar,$swidth,-",
+ "CDEF:meH$lvar=me$lvar,0,*,$swidth,2,*,+",
+ "AREA:meL$lvar",
+ "STACK:meH$lvar$lc{$loss}[1]:$lc{$loss}[0]"
+ # "LINE2:me$lvar$lc{$loss}[1]:$lc{$loss}[0]"
+ );
+ $last = $loss;
}
- push @median, ( "GPRINT:ploss:AVERAGE: avg pkg loss\\: %.2lf %%\\l" );
-# map {print "$_<br/>"} @median;
- };
+ push @median, ( "COMMENT:\\l",
+ "GPRINT:ploss:AVERAGE:Packet Loss\\: %.2lf %% average",
+ "GPRINT:ploss:MAX:%.2lf %% maximum",
+ "GPRINT:ploss:LAST:%.2lf %% current\\l"
+ );
+
# if we have uptime draw a colorful background or the graph showing the uptime
- my $cdir=$cfg->{General}{datadir}."/".(join "/", @dirs)."/";
+ my $cdir=dyndir($cfg)."/".(join "/", @dirs)."/";
if (-f "$cdir/${file}.adr") {
- @upsmoke = ();
- @upargs = ('COMMENT:Link Up: ',
- "DEF:uptime=${rrd}:uptime:AVERAGE",
- "CDEF:duptime=uptime,86400,/",
- 'GPRINT:duptime:LAST: %0.1lf days (');
- my %upt;
- if ( defined $cfg->{Presentation}{detail}{uptime_colors}{_table} ) {
- for (@{$cfg->{Presentation}{detail}{uptime_colors}{_table}}) {
- my ($num,$col,$txt) = @{$_};
- $upt{$num} = [ $txt, "#".$col];
- }
- } else {
- %upt = ( 3600 => ['<1h', '#FFD3D3'],
- 2*3600 => ['<2h', '#FFE4C7'],
- 6*3600 => ['<6h', '#FFF9BA'],
- 12*3600 => ['<12h','#F3FFC0'],
- 24*3600 => ['<1d', '#E1FFCC'],
- 7*24*3600 => ['<1w', '#BBFFCB'],
- 30*24*3600 => ['<1m', '#BAFFF5'],
- '1e100' => ['>1m', '#DAECFF']
- );
- }
- my $lastup = 0;
- foreach my $uptime (sort {$a <=> $b} keys %upt){
- push @upargs,
- (
- "CDEF:up$uptime=uptime,$lastup,GE,uptime,$uptime,LE,*,INF,UNKN,IF",
- "AREA:up$uptime$upt{$uptime}[1]:$upt{$uptime}[0]"
- );
- push @upsmoke,
- (
- "CDEF:ups$uptime=uptime,$lastup,GE,uptime,$uptime,LE,*,cp2,UNKN,IF",
- "AREA:ups$uptime$upt{$uptime}[1]"
+ @upsmoke = ();
+ @upargs = ("COMMENT:Link Up${BS}: ",
+ "DEF:uptime=${rrd}:uptime:AVERAGE",
+ "CDEF:duptime=uptime,86400,/",
+ 'GPRINT:duptime:LAST: %0.1lf days (');
+ my $lastup = 0;
+ foreach my $uptime (sort {$a <=> $b} keys %upt){
+ push @upargs,
+ (
+ "CDEF:up$uptime=uptime,$lastup,GE,uptime,$uptime,LE,*,INF,UNKN,IF",
+ "AREA:up$uptime$upt{$uptime}[1]:$upt{$uptime}[0]"
+ );
+ push @upsmoke,
+ (
+ "CDEF:ups$uptime=uptime,$lastup,GE,uptime,$uptime,LE,*,cp2,UNKN,IF",
+ "AREA:ups$uptime$upt{$uptime}[1]"
);
- $lastup=$uptime;
- }
-
- push @upargs, 'COMMENT:)\l';
-# map {print "$_<br/>"} @upargs;
- };
+ $lastup=$uptime;
+ }
+
+ push @upargs, 'COMMENT:)\l';
+ # map {print "$_<br/>"} @upargs;
+ };
my @log = ();
push @log, "--logarithmic" if $cfg->{Presentation}{detail}{logarithmic} and
- $cfg->{Presentation}{detail}{logarithmic} eq 'yes';
-
+ $cfg->{Presentation}{detail}{logarithmic} eq 'yes';
+
my @lazy =();
- @lazy = ('--lazy') if $lasthight{$start} and $lasthight{$start} == $max->{$start};
- my ($graphret,$xs,$ys) = RRDs::graph
- ($cfg->{General}{imgcache}.$dir."/${file}_last_${start}.png",
+ @lazy = ('--lazy') if $mode eq 's' and $lasthight{$start} and $lasthight{$start} == $max->{$start};
+ $desc = "Navigator Graph" if $mode eq 'n';
+ my $timer_start = time();
+ my @task =
+ ("${imgbase}_${end}_${start}.png",
@lazy,
- '--start','-'.$start,
+ '--start',( $mode eq 's' ? '-'.$start : $start),
+ ($end ne 'last' ? ('--end',$end) : ()),
'--height',$cfg->{Presentation}{detail}{height},
'--width',,$cfg->{Presentation}{detail}{width},
'--title',$desc,
- '--rigid',
- '--upper-limit', $max->{$start},
+ '--rigid','--upper-limit', $max->{$start},
@log,
'--lower-limit',(@log ? ($max->{$start} > 0.01) ? '0.001' : '0.0001' : '0'),
'--vertical-label',"Seconds",
@@ -824,8 +918,6 @@ sub get_detail ($$$$){
@$smoke,
@upsmoke, # draw the rest of the uptime bg color
@median,
-# 'LINE3:median#ff0000:Median RTT in grey '.$cfg->{Database}{pings}.' pings sorted by RTT',
-# 'LINE1:median#ff8080',
# Gray background for times when no data was collected, so they can
# be distinguished from network being down.
( $cfg->{Presentation}{detail}{nodata_color} ? (
@@ -834,14 +926,39 @@ sub get_detail ($$$$){
()),
'HRULE:0#000000',
'COMMENT:\s',
- "COMMENT:Probe: $pings $ProbeDesc every $step seconds",
+ "COMMENT:Probe${BS}: $pings $ProbeDesc every $step seconds",
'COMMENT:created on '.$date.'\j' );
+
+ my ($graphret,$xs,$ys) = RRDs::graph @task;
my $ERROR = RRDs::error();
- $page .= "<div>".
- ( $ERROR ||
- "<IMG BORDER=\"0\" WIDTH=\"$xs\" HEIGHT=\"$ys\" ".
- "SRC=\"".$cfg->{General}{imgurl}.$dir."/${file}_last_${start}.png\">" )."</div>";
+ if ($mode eq 'n'){
+ $page .= "<div>";
+ $page .= ( $ERROR || qq|<IMG BORDER="0" WIDTH="$xs" HEIGHT="$ys" SRC="${imghref}_${end}_${start}.png">| );
+ $page .= "</div>";
+ $page .= $q->start_form(-method=>'GET')
+ . "<p>Time range: "
+ . $q->textfield(-name=>'start',-default=>$startstr)
+ . "&nbsp;&nbsp;to&nbsp;&nbsp;".$q->textfield(-name=>'end',-default=>$endstr)
+ . $q->hidden(-name=>'target' )
+ . $q->hidden(-name=>'displaymode',-default=>$mode )
+ . "&nbsp;"
+ . $q->submit(-name=>'Generate!')
+ . "</p>"
+ . $q->end_form();
+ } else {
+ $startstr =~ s/\s/%20/g;
+ $endstr =~ s/\s/%20/g;
+ $page .= "<div>";
+# $page .= (time-$timer_start)."<br/>";
+# $page .= join " ",map {"'$_'"} @task;
+ $page .= "<br/>";
+ $page .= ( $ERROR ||
+ qq{<a href="?displaymode=n;start=$startstr;end=now;}."target=".$q->param('target').'">'
+ . qq{<IMG BORDER="0" WIDTH="$xs" HEIGHT="$ys" SRC="${imghref}_${end}_${start}.png">}."</a>" );
+ $page .= "</div>";
+
+ }
}
return $page;
@@ -918,13 +1035,16 @@ sub update_rrds($$$$$) {
my $justthisprobe = shift; # if defined, update only the targets probed by this probe
my $probe = $tree->{probe};
- my $probeobj = $probes->{$probe};
foreach my $prop (keys %{$tree}) {
if (ref $tree->{$prop} eq 'HASH'){
update_rrds $cfg, $probes, $tree->{$prop}, $name."/$prop", $justthisprobe;
}
- next if defined $justthisprobe and $probe ne $justthisprobe;
+ # if we are looking down a branche where no probe propperty is set there is not sense
+ # in further exploring it
+ next unless defined $probe;
+ next if defined $justthisprobe and $probe ne $justthisprobe;
+ my $probeobj = $probes->{$probe};
if ($prop eq 'host' and check_filter($cfg,$name)) {
#print "update $name\n";
my $updatestring = $probeobj->rrdupdate_string($tree);
@@ -1383,7 +1503,7 @@ DOC
# if there is a subprobe, the top-level section
# of this probe turns into a template, and we
# need to delete its _mandatory list.
- # Note that ISG::ParseConfig does mandatory checking
+ # Note that Config::Grammar does mandatory checking
# after the whole config tree is read, so we can fiddle
# here with "_mandatory" all we want.
# see 1.3 above
@@ -1446,7 +1566,7 @@ DOC
},
}; # $PROBES
- my $parser = ISG::ParseConfig->new
+ my $parser = Config::Grammar->new
(
{
_sections => [ qw(General Database Presentation Probes Alerts Targets) ],
@@ -1457,9 +1577,9 @@ DOC
General configuration values valid for the whole SmokePing setup.
DOC
_vars =>
- [ qw(owner imgcache imgurl datadir pagedir piddir sendmail offset
+ [ qw(owner imgcache imgurl datadir dyndir pagedir piddir sendmail offset
smokemail cgiurl mailhost contact netsnpp
- syslogfacility syslogpriority concurrentprobes changeprocessnames) ],
+ syslogfacility syslogpriority concurrentprobes changeprocessnames tmail) ],
_mandatory =>
[ qw(owner imgcache imgurl datadir piddir
smokemail cgiurl contact) ],
@@ -1527,7 +1647,16 @@ DOC
The directory where SmokePing can keep its rrd files.
DOC
},
+ dyndir =>
+ {
+ %$DIRCHECK_SUB,
+ _doc => <<DOC,
+The base directory where SmokePing keeps the files related to the DYNAMIC function.
+This directory must be writeable by the WWW server.
+If this variable is not specified, the value of C<datadir> will be used instead.
+DOC
+ },
piddir =>
{
%$DIRCHECK_SUB,
@@ -1618,6 +1747,13 @@ be appended to the process name as '[probe]', eg. '/usr/bin/smokeping
If 'concurrentprobes' is not set to 'yes', this variable has no effect.
DOC
},
+ tmail =>
+ {
+ %$FILECHECK_SUB,
+ _doc => <<DOC,
+Path to your tSmoke HTML mail template file. See the tSmoke documentation for details.
+DOC
+ }
},
Database =>
{
@@ -2036,7 +2172,7 @@ let the pattern match:
>10%,*10*,>10%
-will fire if more than 10% of the packets have been losst twice over the
+will fire if more than 10% of the packets have been lost at least twice over the
last 10 samples.
A complete example
@@ -2278,6 +2414,7 @@ sub daemonize_me ($) {
sub initialize_cgilog (){
$use_cgilog = 1;
+ CGI::Carp::set_progname($0 . " [client " . ($ENV{REMOTE_ADDR}||"(unknown)") . "]");
$logging=1;
}
@@ -2305,7 +2442,7 @@ sub daemonize_me ($) {
sub do_cgilog ($){
my $str = shift;
print "<p>" , $str, "</p>\n";
- print STDERR $str,"\n"; # for the webserver log
+ warn $str, "\n"; # for the webserver log
}
sub do_debuglog ($){
@@ -2359,6 +2496,7 @@ sub load_cfg ($) {
sub makepod ($){
my $parser = shift;
my $e='=';
+ my $a='@';
my $retval = <<POD;
${e}head1 NAME
@@ -2374,7 +2512,7 @@ The contents of this manual is generated directly from the configuration
file parser.
The Parser for the Configuration file is written using David Schweikers
-ParseConfig module. Read all about it in L<ISG::ParseConfig>.
+Config::Grammar module. Read all about it in L<Config::Grammar>.
The Configuration file has a tree-like structure with section headings at
various levels. It also contains variable assignments and tables.
@@ -2384,7 +2522,29 @@ for simple configuration examples.
${e}head1 REFERENCE
-The text below describes the syntax of the SmokePing configuration file.
+${e}head2 GENERAL SYNTAX
+
+The text below describes the general syntax of the SmokePing configuration file.
+It was copied from the Config::Grammar documentation.
+
+'#' denotes a comment up to the end-of-line, empty lines are allowed and space
+at the beginning and end of lines is trimmed.
+
+'\\' at the end of the line marks a continued line on the next line. A single
+space will be inserted between the concatenated lines.
+
+'${a}include filename' is used to include another file.
+
+'${a}define a some value' will replace all occurences of 'a' in the following text
+with 'some value'.
+
+Fields in tables that contain white space can be enclosed in either C<'> or C<">.
+Whitespace can also be escaped with C<\\>. Quotes inside quotes are allowed but must
+be escaped with a backslash as well.
+
+${e}head2 SPECIFIC SYNTAX
+
+The text below describes the specific syntax of the SmokePing configuration file.
POD
@@ -2427,14 +2587,21 @@ sub cgi ($) {
umask 022;
load_cfg shift;
my $q=new CGI;
- print $q->header(-type=>'text/html',
+ initialize_cgilog();
+ if ($q->param(-name=>'secret') && $q->param(-name=>'target') ) {
+ my $ret = update_dynaddr $cfg,$q;
+ if (defined $ret and $ret ne "") {
+ print $q->header(-status => "404 Not Found");
+ do_cgilog("Updating DYNAMIC address failed: $ret");
+ } else {
+ print $q->header; # no HTML output on success
+ }
+ } else {
+ print $q->header(-type=>'text/html',
-expires=>'+'.($cfg->{Database}{step}).'s',
-charset=> ( $cfg->{Presentation}{charset} || 'iso-8859-15')
);
- if ($q->param(-name=>'secret') && $q->param(-name=>'target') ) {
- update_dynaddr $cfg,$q;
- } else {
- display_webpage $cfg,$q;
+ display_webpage $cfg,$q;
}
}
diff --git a/lib/Smokeping/RRDtools.pm b/lib/Smokeping/RRDtools.pm
index 7260cca..2b7ad2b 100644
--- a/lib/Smokeping/RRDtools.pm
+++ b/lib/Smokeping/RRDtools.pm
@@ -4,7 +4,7 @@ package Smokeping::RRDtools;
Smokeping::RRDtools - Tools for RRD file handling
-=head1 SYNOPSYS
+=head1 SYNOPSIS
use Smokeping::RRDtools;
use RRDs;
@@ -22,12 +22,15 @@ Smokeping::RRDtools - Tools for RRD file handling
my $comparison = Smokeping::RRDtools::compare($file, \@create);
print "Create arguments didn't match: $comparison\n" if $comparison;
+ Smokeping::RRDtools::tuneds($file, \@create);
+
=head1 DESCRIPTION
-This module offers two functions, C<info2create> and C<compare>.
-The first can be used to recreate the arguments that an RRD file
-was created with. The second checks if an RRD file was created
-with the given arguments.
+This module offers three functions, C<info2create>, C<compare> and
+C<tuneds>. The first can be used to recreate the arguments that an RRD file
+was created with. The second checks if an RRD file was created with the
+given arguments. The thirds tunes the DS parameters according to the
+supplied create string.
The function C<info2create> must be called with one argument:
the path to the interesting RRD file. It will return an array
@@ -35,14 +38,17 @@ reference of the argument list that can be fed to C<RRDs::create>.
Note that this list will never contain the C<start> parameter,
but it B<will> contain the C<step> parameter.
-The function C<compare> must be called with two arguments: the path
-to the interesting RRD file, and a reference to an argument list that
-could be fed to C<RRDs::create>. The function will then simply compare
-the result of C<info2create> with this argument list. It will return
-C<undef> if the arguments matched, and a string indicating the difference
-if a discrepancy was found. Note that if there is a C<start> parameter in
-the argument list, C<compare> disregards it. If C<step> isn't specified,
-C<compare> will use the C<rrdtool> default of 300 seconds.
+The function C<compare> must be called with two arguments: the path to the
+interesting RRD file, and a reference to an argument list that could be fed
+to C<RRDs::create>. The function will then simply compare the result of
+C<info2create> with this argument list. It will return C<undef> if the
+arguments matched, and a string indicating the difference if a discrepancy
+was found. Note that if there is a C<start> parameter in the argument list,
+C<compare> disregards it. If C<step> isn't specified, C<compare> will use
+the C<rrdtool> default of 300 seconds. C<compare> ignores non-matching DS
+parameters since C<tuneds> will fix them.
+
+C<tuneds> talks on stderr about the parameters it fixes.
=head1 NOTES
@@ -57,6 +63,10 @@ Probably.
Copyright (c) 2005 by Niko Tyni.
+=head1 AUTHOR
+
+Niko Tyni <ntyni@iki.fi>
+
=head1 LICENSE
This program is free software; you can redistribute it
@@ -89,11 +99,13 @@ use RRDs;
sub info2create {
my $file = shift;
my @create;
+ my $buggy_perl_version = 1 if $^V and $^V eq v5.8.0;
my $info = RRDs::info($file);
my $error = RRDs::error;
die("RRDs::info $file: ERROR: $error") if $error;
die("$file: unknown RRD version: $info->{rrd_version}")
- unless $info->{rrd_version} eq '0001';
+ unless $info->{rrd_version} eq '0001'
+ or $info->{rrd_version} eq '0003';
my $cf = $info->{"rra[0].cf"};
die("$file: no RRAs found?")
unless defined $cf;
@@ -107,7 +119,8 @@ sub info2create {
my @s = ("DS", $ds);
for (qw(type minimal_heartbeat min max)) {
die("$file: missing $_ for DS $ds?")
- unless exists $info->{"ds[$ds].$_"};
+ unless exists $info->{"ds[$ds].$_"}
+ or $buggy_perl_version;
my $val = $info->{"ds[$ds].$_"};
push @s, defined $val ? $val : "U";
}
@@ -117,7 +130,8 @@ sub info2create {
my @s = ("RRA", $info->{"rra[$i].cf"});
for (qw(xff pdp_per_row rows)) {
die("$file: missing $_ for RRA $i")
- unless exists $info->{"rra[$i].$_"};
+ unless exists $info->{"rra[$i].$_"}
+ or $buggy_perl_version;
push @s, $info->{"rra[$i].$_"};
}
push @create, join(":", @s);
@@ -160,10 +174,37 @@ sub compare {
while (my $arg = shift @create) {
my $arg2 = shift @create2;
+ my @ds = split /:/, $arg;
+ my @ds2 = split /:/, $arg2;
+ next if $ds[0] eq 'DS' and $ds[0] eq $ds2[0] and $ds[1] eq $ds2[1] and $ds[2] eq $ds2[2];
return "Different arguments: $file has $arg2, create string has $arg"
unless $arg eq $arg2;
}
return undef;
}
+sub tuneds {
+ my $file = shift;
+ my $create = shift;
+ my @create2 = sort grep /^DS/, @{info2create($file)};
+ my @create = sort grep /^DS/, @$create;
+ while (@create){
+ my @ds = split /:/, shift @create;
+ my @ds2 = split /:/, shift @create2;
+ next unless $ds[1] eq $ds2[1] and $ds[2] eq $ds[2];
+ if ($ds[3] ne $ds2[3]){
+ warn "## Updating $file DS:$ds[1] heartbeat $ds2[3] -> $ds[3]\n";
+ RRDs::tune $file,"--hearbeat","$ds[1]:$ds[3]" unless $ds[3] eq $ds2[3];
+ }
+ if ($ds[4] ne $ds2[4]){
+ warn "## Updating $file DS:$ds[1] minimum $ds2[4] -> $ds[4]\n";
+ RRDs::tune $file,"--minimum","$ds[1]:$ds[4]" unless $ds[4] eq $ds2[4];
+ }
+ if ($ds[5] ne $ds2[5]){
+ warn "## Updating $file DS:$ds[1] maximum $ds2[5] -> $ds[5]\n";
+ RRDs::tune $file,"--maximum","$ds[1]:$ds[5]" unless $ds[5] eq $ds2[5];
+ }
+ }
+}
+
1;
diff --git a/lib/Smokeping/matchers/Avgratio.pm b/lib/Smokeping/matchers/Avgratio.pm
index 8679fe9..e97fcf0 100644
--- a/lib/Smokeping/matchers/Avgratio.pm
+++ b/lib/Smokeping/matchers/Avgratio.pm
@@ -5,7 +5,7 @@ package Smokeping::matchers::Avgratio;
Smokeping::matchers::Avgratio - detect changes in average median latency
=head1 OVERVIEW
-
+
The Avgratio matcher establishes a historic average median latency over
several measurement rounds. It compares this average, against a second
average latency value again build over several rounds of measurement.
diff --git a/lib/Smokeping/probes/AnotherDNS.pm b/lib/Smokeping/probes/AnotherDNS.pm
index 65a1bd4..d4f0397 100644
--- a/lib/Smokeping/probes/AnotherDNS.pm
+++ b/lib/Smokeping/probes/AnotherDNS.pm
@@ -69,6 +69,7 @@ sub pingone ($) {
my $recordtype = $target->{vars}{recordtype};
my $timeout = $target->{vars}{timeout};
my $port = $target->{vars}{port};
+ my $require_noerror = $target->{vars}{require_noerror};
$lookuphost = $target->{addr} unless defined $lookuphost;
my $packet = Net::DNS::Packet->new( $lookuphost, $recordtype )->data;
@@ -93,9 +94,20 @@ sub pingone ($) {
my $t1 = [gettimeofday];
$elapsed = tv_interval( $t0, $t1 );
if ( defined $ready ) {
- push @times, $elapsed;
my $buf = '';
$ready->recv( $buf, &Net::DNS::PACKETSZ );
+ my ($recvPacket, $err) = Net::DNS::Packet->new(\$buf);
+ if (defined $recvPacket) {
+ if (not $require_noerror) {
+ push @times, $elapsed;
+ } else {
+ # Check the Response Code for the NOERROR.
+ my $recvHeader = $recvPacket->header();
+ if ($recvHeader->rcode() eq "NOERROR") {
+ push @times, $elapsed;
+ }
+ }
+ }
}
}
@times =
@@ -127,6 +139,10 @@ DOC
_default => .5,
_re => '(\d*\.)?\d+',
},
+ require_noerror => {
+ _doc => 'Only Count Answers with Response Status NOERROR.',
+ _default => 0,
+ },
recordtype => {
_doc => 'Record type to look up.',
_default => 'A',
diff --git a/lib/Smokeping/probes/CiscoRTTMonEchoICMP.pm b/lib/Smokeping/probes/CiscoRTTMonEchoICMP.pm
index f763fde..c542ed1 100644
--- a/lib/Smokeping/probes/CiscoRTTMonEchoICMP.pm
+++ b/lib/Smokeping/probes/CiscoRTTMonEchoICMP.pm
@@ -92,8 +92,7 @@ sub new($$$)
sub ProbeDesc($){
my $self = shift;
- my $bytes = $self->{properties}{packetsize};
- return "CiscoRTTMonEchoICMP ($bytes Bytes)";
+ return "CiscoRTTMonEchoICMP";
}
sub pingone ($$) {
@@ -102,7 +101,7 @@ sub pingone ($$) {
my $pings = $self->pings($target) || 20;
my $tos = $target->{vars}{tos};
- my $bytes = $target->{properties}{packetsize};
+ my $bytes = $target->{vars}{packetsize};
# use the proces ID as as row number to make this poll distinct on the router;
my $row=$$;
@@ -260,27 +259,6 @@ sub DestroyData ($$) {
&snmpset($host, "rttMonCtrlAdminStatus.$row", 'integer', 6);
}
-sub probevars {
- my $class = shift;
- return $class->_makevars($class->SUPER::probevars, {
- packetsize => {
- _doc => <<DOC,
-The packetsize parameter lets you configure the packetsize for the pings
-sent. The minimum is 8, the maximum 16392. Use the same number as with
-fping, if you want the same packet sizes being used on the network.
-DOC
- _default => 56,
- _re => '\d+',
- _sub => sub {
- my $val = shift;
- return "ERROR: packetsize must be between 8 and 16392"
- unless $val >= 8 and $val <= 16392;
- return undef;
- },
- },
- });
-}
-
sub targetvars {
my $class = shift;
return $class->_makevars($class->SUPER::targetvars, {
@@ -315,6 +293,21 @@ corresponds to a DSCP value 40 and a Precedence value of 5. The RTTMon
MIB versions before IOS 12.0(3)T didn't support this parameter.
DOC
},
+ packetsize => {
+ _doc => <<DOC,
+The packetsize parameter lets you configure the packetsize for the pings
+sent. The minimum is 8, the maximum 16392. Use the same number as with
+fping, if you want the same packet sizes being used on the network.
+DOC
+ _default => 56,
+ _re => '\d+',
+ _sub => sub {
+ my $val = shift;
+ return "ERROR: packetsize must be between 8 and 16392"
+ unless $val >= 8 and $val <= 16392;
+ return undef;
+ },
+ },
});
}
diff --git a/lib/Smokeping/probes/Curl.pm b/lib/Smokeping/probes/Curl.pm
index 56dd338..af5be29 100644
--- a/lib/Smokeping/probes/Curl.pm
+++ b/lib/Smokeping/probes/Curl.pm
@@ -102,6 +102,48 @@ host to be probed.
DOC
_example => "http://%host%/",
},
+ insecure_ssl => {
+ _doc => <<DOC,
+The "-k" curl(1) option. Accept SSL connections that don't have a secure
+certificate chain to a trusted CA. Note that if you are going to monitor
+https targets, you'll probably have to either enable this option or specify
+the CA path to curl through extraargs below. For more info, see the
+curl(1) manual page.
+DOC
+ _example => 1,
+ },
+ extrare=> {
+ _doc => <<DOC,
+The regexp used to split the extraargs string into an argument list,
+in the "/regexp/" notation. This contains just the space character
+(" ") by default, but if you need to specify any arguments containing spaces,
+you can set this variable to a different value.
+DOC
+ _default => "/ /",
+ _example => "/ /",
+ _sub => sub {
+ my $val = shift;
+ return "extrare should be specified in the /regexp/ notation"
+ unless $val =~ m,^/.*/$,;
+ return undef;
+ },
+ },
+ extraargs => {
+ _doc => <<DOC,
+Any extra arguments you might want to hand to curl(1). The arguments
+should be separated by the regexp specified in "extrare", which
+contains just the space character (" ") by default.
+
+Note that curl will be called with the resulting list of arguments
+without any shell expansion. If you need to specify any arguments
+containing spaces, you should set "extrare" to something else.
+
+As a complicated example, to explicitly set the "Host:" header in Curl
+requests, you need to set "extrare" to something else, eg. "/;/",
+and then specify C<extraargs = --header;Host: www.example.com>.
+DOC
+ _example => "-6 --head --user user:password",
+ },
});
}
@@ -178,9 +220,22 @@ sub proto_args {
my @args = ("-o", "/dev/null", "-w", "Time: %{time_total} DNS time: %{time_namelookup}\\n");
my $ssl2 = $target->{vars}{ssl2};
push (@args, "-2") if defined($ssl2);
+ my $insecure_ssl = $target->{vars}{insecure_ssl};
+ push (@args, '-k') if defined $insecure_ssl;
+
return(@args);
}
+sub extra_args {
+ my $self = shift;
+ my $target = shift;
+ my $args = $target->{vars}{extraargs};
+ return () unless defined $args;
+ my $re = $target->{vars}{extrare};
+ ($re =~ m,^/(.*)/$,) and $re = qr{$1};
+ return split($re, $args);
+}
+
sub make_commandline {
my $self = shift;
my $target = shift;
@@ -191,6 +246,7 @@ sub make_commandline {
my $host = $target->{addr};
$url =~ s/%host%/$host/g;
push @args, $self->proto_args($target);
+ push @args, $self->extra_args($target);
return ($self->{properties}{binary}, @args, $url);
}
diff --git a/lib/Smokeping/probes/base.pm b/lib/Smokeping/probes/base.pm
index 8cc4def..c0525b6 100644
--- a/lib/Smokeping/probes/base.pm
+++ b/lib/Smokeping/probes/base.pm
@@ -42,12 +42,14 @@ sub pod {
my $class = shift;
my $pod = "";
my $podhash = $class->pod_hash;
- $podhash->{synopsys} = $class->pod_synopsys;
+ $podhash->{synopsis} = $class->pod_synopsis;
$podhash->{variables} = $class->pod_variables;
- for my $what (qw(name overview synopsys description variables authors notes bugs see_also)) {
+ for my $what (qw(name overview synopsis description variables authors notes bugs see_also)) {
my $contents = $podhash->{$what};
next if not defined $contents or $contents eq "";
- $pod .= "=head1 " . uc $what . "\n\n";
+ my $headline = uc $what;
+ $headline =~ s/_/ /; # see_also => SEE ALSO
+ $pod .= "=head1 $headline\n\n";
$pod .= $contents;
chomp $pod;
$pod .= "\n\n";
@@ -88,6 +90,23 @@ sub ProbeDesc ($) {
return "Probe which does not overrivd the ProbeDesc methode";
}
+sub target2dynfile ($$) {
+ # the targets are stored in the $self->{targets}
+ # hash as filenames pointing to the RRD files
+ #
+ # now that we use a (optionally) different dir for the
+ # . adr files, we need to derive the .adr filename
+ # from the RRD filename with a simple substitution
+
+ my $self = shift;
+ my $target = shift; # filename with <datadir> embedded
+ my $dyndir = $self->{cfg}{General}{dyndir};
+ return $target unless defined $dyndir; # nothing to do
+ my $datadir = $self->{cfg}{General}{datadir};
+ $target =~ s/^\Q$datadir\E/$dyndir/;
+ return $target;
+}
+
sub rrdupdate_string($$)
{ my $self = shift;
my $tree = shift;
@@ -105,17 +124,18 @@ sub rrdupdate_string($$)
my $upperloss = $loss - $lowerloss;
@times = ((map {'U'} 1..$lowerloss),@times, (map {'U'} 1..$upperloss));
my $age;
- if ( -f $self->{targets}{$tree}.".adr" ) {
- $age = time - (stat($self->{targets}{$tree}.".adr"))[9];
+ my $dynbase = $self->target2dynfile($self->{targets}{$tree});
+ if ( -f $dynbase.".adr" ) {
+ $age = time - (stat($dynbase.".adr"))[9];
} else {
$age = 'U';
}
if ( $entries == 0 ){
$age = 'U';
$loss = 'U';
- if ( -f $self->{targets}{$tree}.".adr"
- and not -f $self->{targets}{$tree}.".snmp" ){
- unlink $self->{targets}{$tree}.".adr";
+ if ( -f $dynbase.".adr"
+ and not -f $dynbase.".snmp" ){
+ unlink $dynbase.".adr";
}
} ;
return "${age}:${loss}:${median}:".(join ":", @times);
@@ -129,12 +149,13 @@ sub addresses($)
foreach my $tree (keys %{$self->{targets}}){
my $target = $self->{targets}{$tree};
if ($target =~ m|/|) {
- if ( open D, "<$target.adr" ) {
+ my $dynbase = $self->target2dynfile($target);
+ if ( open D, "<$dynbase.adr" ) {
my $ip;
chomp($ip = <D>);
close D;
- if ( open D, "<$target.snmp" ) {
+ if ( open D, "<$dynbase.snmp" ) {
my $snmp = <D>;
chomp($snmp);
if ($snmp ne Smokeping::snmpget_ident $ip) {
@@ -316,7 +337,7 @@ sub _makevars {
return $to;
}
-sub pod_synopsys {
+sub pod_synopsis {
my $class = shift;
my $classname = ref $class||$class;
$classname =~ s/^Smokeping::probes:://;
@@ -329,8 +350,8 @@ sub pod_synopsys {
+$classname
DOC
- $pod .= $class->_pod_synopsys($probevars);
- my $targetpod = $class->_pod_synopsys($targetvars);
+ $pod .= $class->_pod_synopsis($probevars);
+ my $targetpod = $class->_pod_synopsis($targetvars);
$pod .= "\n # The following variables can be overridden in each target section\n$targetpod"
if defined $targetpod and $targetpod ne "";
$pod .= <<DOC;
@@ -353,8 +374,8 @@ DOC
return $pod;
}
-# synopsys for one hash ref
-sub _pod_synopsys {
+# synopsis for one hash ref
+sub _pod_synopsis {
my $class = shift;
my $vars = shift;
my %mandatory;
diff --git a/lib/Smokeping/probes/passwordchecker.pm b/lib/Smokeping/probes/passwordchecker.pm
index 7633eba..d1cc128 100644
--- a/lib/Smokeping/probes/passwordchecker.pm
+++ b/lib/Smokeping/probes/passwordchecker.pm
@@ -31,7 +31,7 @@ for storing passwords and a method for accessing them.
DOC
description => <<DOC,
-${e}head2 synopsys with more detail
+${e}head2 synopsis with more detail
SmokePing main configuration file:
diff --git a/util/fix-pod2html.pl b/util/fix-pod2html.pl
new file mode 100755
index 0000000..fa51400
--- /dev/null
+++ b/util/fix-pod2html.pl
@@ -0,0 +1,73 @@
+#!/usr/bin/perl -w
+
+use strict;
+use HTML::Parser;
+
+# fix pod2html output:
+# v1.0: defer </dd> and </dt> tags until
+# the next <dd>, <dt> or </dl>
+
+# v1.1: don't nest any <a> elements;
+# end one before beginning another
+
+# v1.2: insert <dd> tags if <dl> occurs
+# inside <dt>
+
+# v1.3: <a> anchors must not start with a digit;
+# insert a letter "N" at the start if they do
+
+# v1.4: insert the "N" letter into <a href="#xxx"> too.
+
+my $p = HTML::Parser->new(api_version => 3);
+$p->handler(start => \&startsub, 'tagname, text');
+$p->handler(end => \&endsub, 'tagname, text');
+$p->handler(default => sub { print shift() }, 'text');
+$p->parse_file(shift||"-") or die("parse: $!");
+
+my @stack;
+my $a=0;
+
+sub startsub {
+ my $tag = shift;
+ my $text = shift;
+ if ($tag eq "dl") {
+ if (@stack and $stack[0] eq "dt") {
+ $stack[0] = "dd";
+ print "</dt><dd>";
+ }
+ unshift @stack, 0;
+ }
+ if (($tag eq "dt" or $tag eq "dd") and $stack[0]) {
+ print "</$stack[0]>";
+ $stack[0] = 0;
+ }
+ if ($tag eq "a") {
+ if ($a) {
+ print "</a>";
+ } else {
+ $a++;
+ }
+ $text =~ s/(name="|href="#)(\d)/$1N$2/;
+ }
+ print $text;
+}
+
+
+sub endsub {
+ my $tag = shift;
+ my $text = shift;
+ if ($tag eq "dl") {
+ print "</$stack[0]>" if $stack[0];
+ shift @stack;
+ }
+ if ($tag eq "a") {
+ if ($a) {
+ print "</a>";
+ $a--;
+ }
+ } elsif ($tag eq "dd" or $tag eq "dt") {
+ $stack[0] = $tag;
+ } else {
+ print $text;
+ }
+}