\<a href=``http://hostname/path/smokeping.cgi?foo=bar\>
\<a href=``?foo=bar''\>
\<a href=``/path/smokeping.cgi?foo=bar\>
The default is ``relative'', which hopefully works for everybody. DOC }, syslogfacility => { _re => '\w+', _re_error => ``syslogfacility must be alphanumeric'', _doc => <<DOC, The syslog facility to use, eg. local0...local7. Note: syslog logging is only used if you specify this. DOC }, syslogpriority => { _re => '\w+', _re_error => ``syslogpriority must be alphanumeric'', _doc => <<DOC, The syslog priority to use, eg. debug, notice or info. Default is $DEFAULTPRIORITY. DOC }, offset => { _re => '(\d+%|random)', _re_error => ``Use offset either in % of operation interval or 'random''', _doc => <<DOC, If you run many instances of smokeping you may want to prevent them from hitting your network all at the same time. Using the offset parameter you can change the point in time when the probes are run. Offset is specified in % of total interval, or alternatively as 'random'. I recommend to use 'random'. Note that this does NOT influence the rrds itself, it is just a matter of when data acqusition is initiated. The default offset is 'random'. DOC }, concurrentprobes => { _re => '(yes|no)', _re_error =>``this must either be 'yes' or 'no''', _doc => <<DOC, If you use multiple probes or multiple instances of the same probe and you want them to run concurrently in separate processes, set this to 'yes'. This gives you the possibility to specify probe-specific step and offset parameters (see the 'Probes' section) for each probe and makes the probes unable to block each other in cases of service outages. The default is 'yes', but if you for some reason want the old behaviour you can set this to 'no'. DOC }, changeprocessnames => { _re => '(yes|no)', _re_error =>``this must either be 'yes' or 'no''', _doc => <<DOC, When using 'concurrentprobes' (see above), this controls whether the probe subprocesses should change their argv string to indicate their probe in the process name. If set to 'yes' (the default), the probe name will be appended to the process name as '[probe]', eg. '/usr/bin/smokeping [FPing]'. If you don't like this behaviour, set this variable to 'no'. If 'concurrentprobes' is not set to 'yes', this variable has no effect. DOC _default => 'yes', }, changecgiprogramname => { _re => '(yes|no)', _re_error =>``this must either be 'yes' or 'no''', _doc => <<DOC, Usually the Smokeping CGI tries to log any possible errors with an extended program name that includes the IP address of the remote client for easier debugging. If this variable is set to 'no', the program name will not be modified. The only reason you would want this is if you have a very old version of the CGI::Carp module. See the installation document for details. DOC _default => 'yes', }, tmail => { %$FILECHECK_SUB, _doc => <<DOC, Path to your tSmoke HTML mail template file. See the tSmoke documentation for details. DOC } }, Database => { _vars => [ qw(step pings) ], _mandatory => [ qw(step pings) ], _doc => <<DOC, Describes the properties of the round robin database for storing the SmokePing data. Note that it is not possible to edit existing RRDs by changing the entries in the cfg file. DOC
step => { %$INTEGER_SUB, _doc => <<DOC, Duration of the base operation interval of SmokePing in seconds. SmokePing will venture out every B<step> seconds to ping your target hosts. If 'concurrent_probes' is set to 'yes' (see above), this variable can be overridden by each probe. Note that the step in the RRD files is fixed when they are originally generated, and if you change the step parameter afterwards, you'll have to delete the old RRD files or somehow convert them. DOC }, pings => { _re => '\d+', _sub => sub { my $val = shift; return "ERROR: The pings value must be at least 3." if $val < 3; return undef; }, _doc => <<DOC, How many pings should be sent to each target. Suggested: 20 pings. Minimum value: 3 pings. This can be overridden by each probe. Some probes (those derived from basefork.pm, ie. most except the FPing variants) will even let this be overridden target-specifically. Note that the number of pings in the RRD files is fixed when they are originally generated, and if you change this parameter afterwards, you'll have to delete the old RRD files or somehow convert them. DOC },
_table => { _doc => <<DOC, This section also contains a table describing the setup of the SmokePing database. Below are reasonable defaults. Only change them if you know rrdtool and its workings. Each row in the table describes one RRA.
# cons xff steps rows AVERAGE 0.5 1 1008 AVERAGE 0.5 12 4320 MIN 0.5 12 4320 MAX 0.5 12 4320 AVERAGE 0.5 144 720 MAX 0.5 144 720 MIN 0.5 144 720
DOC _columns => 4, 0 => { _doc => <<DOC, Consolidation method. DOC _re => '(AVERAGE|MIN|MAX)', _re_error => ``Choose a valid consolidation function'', }, 1 => { _doc => <<DOC, What part of the consolidated intervals must be known to warrant a known entry. DOC _sub => sub { return ``Xff must be between 0 and 1'' unless $_[ 0 ] > 0 and $_[ 0 ] <= 1; return undef; } }, 2 => {%$INTEGER_SUB, _doc => <<DOC, How many steps to consolidate into for each RRA entry. DOC },
3 => {%$INTEGER_SUB, _doc => <<DOC, How many B<rows> this RRA should have. DOC } } }, Presentation => { _doc => <<DOC, Defines how the SmokePing data should be presented. DOC _sections => [ qw(overview detail) ], _mandatory => [ qw(overview template detail) ], _vars => [ qw (template charset) ], template => { _doc => <<DOC, The webpage template must contain keywords of the form B<E<lt>##>I<keyword>B<##E<gt>>. There is a sample template included with SmokePing; use it as the basis for your experiments. Default template contains a pointer to the SmokePing counter and homepage. I would be glad if you would not remove this as it gives me an indication as to how widely used the tool is. DOC
_sub => sub { return "template '$_[0]' not readable" unless -r $_[ 0 ]; return undef; } }, charset => { _doc => <<DOC, By default, SmokePing assumes the 'iso-8859-15' character set. If you use something else, this is the place to speak up. DOC }, overview => { _vars => [ qw(width height range max_rtt median_color strftime) ], _mandatory => [ qw(width height) ], _doc => <<DOC, The Overview section defines how the Overview graphs should look. DOC max_rtt => { _doc => <<DOC }, Any roundtrip time larger than this value will cropped in the overview graph DOC median_color => { _doc => <<DOC, By default the median line is drawn in red. Override it here with a hex color in the format I<rrggbb>. DOC _re => '[0-9a-f]{6}', _re_error => 'use rrggbb for color', }, strftime => { _doc => <<DOC, Use posix strftime to format the timestamp in the left hand lower corner of the overview graph DOC _sub => sub { eval ( "POSIX::strftime( '$_[0]', localtime(time))" ); return $@ if $@; return undef; }, },
width => { _sub => sub { return "width must be be an integer >= 10" unless $_[ 0 ] >= 10 and int( $_[ 0 ] ) == $_[ 0 ]; return undef; }, _doc => <<DOC, Width of the Overview Graphs. DOC }, height => { _doc => <<DOC, Height of the Overview Graphs. DOC _sub => sub { return "height must be an integer >= 10" unless $_[ 0 ] >= 10 and int( $_[ 0 ] ) == $_[ 0 ]; return undef; }, }, range => { _re => '\d+[smhdwy]', _re_error => "graph range must be a number followed by [smhdwy]", _doc => <<DOC, How much time should be depicted in the Overview graph. Time must be specified as a number followed by a letter which specifies the unit of time. Known units are: B<s>econds, B<m>inutes, B<h>ours, B<d>days, B<w>eeks, B<y>ears. DOC }, }, detail => { _vars => [ qw(width height logarithmic unison_tolerance max_rtt strftime nodata_color) ], _sections => [ qw(loss_colors uptime_colors) ], _mandatory => [ qw(width height) ], _table => { _columns => 2, _doc => <<DOC, The detailed display can contain several graphs of different resolution. In this table you can specify the resolution of each graph.
Example:
"Last 3 Hours" 3h "Last 30 Hours" 30h "Last 10 Days" 10d "Last 400 Days" 400d
DOC
1 =>
{
_doc => <<DOC,
How much time should be depicted. The format is the same as for the age parameter of the Overview section.
DOC
_re => '\d+[smhdwy]',
_re_error =>
``graph age must be a number followed by [smhdwy]'',
},
0 =>
{
_doc => <<DOC,
Description of the particular resolution.
DOC
}
},
strftime => { _doc => <<DOC,
Use posix strftime to format the timestamp in the left hand
lower corner of the detail graph
DOC
_sub => sub {
eval ( ``
POSIX::strftime('$_[0]', localtime(time))
'' );
return $@ if $@;
return undef;
},
},
nodata_color => {
_re => '[0-9a-f]{6}',
_re_error => ``color must be defined with in rrggbb syntax'',
_doc => ``Paint the graph background in a special color when there is no data for this period because smokeping has not been running (#rrggbb)'',
},
logarithmic => { _doc => 'should the graphs be shown in a logarithmic scale (yes/no)',
_re => '(yes|no)',
_re_error =>``this must either be 'yes' or 'no''',
},
unison_tolerance => { _doc => ``if a graph is more than this factor of the median 'max' it drops out of the unison scaling algorithm. A factor of two would mean that any graph with a max either less than half or more than twice the median 'max' will be dropped from unison scaling'',
_sub => sub { return ``tolerance must be larger than 1'' if $_[0] <= 1; return undef},
},
max_rtt => { _doc => <<DOC },
Any roundtrip time larger than this value will cropped in the detail graph
DOC
width => { _doc => 'How many pixels wide should detail graphs be',
_sub => sub {
return ``width must be be an integer >= 10''
unless $_[ 0 ] >= 10
and int( $_[ 0 ] ) == $_[ 0 ];
return undef;
},
},
height => { _doc => 'How many pixels high should detail graphs be',
_sub => sub {
return ``height must be an integer >= 10''
unless $_[ 0 ] >= 10
and int( $_[ 0 ] ) == $_[ 0 ];
return undef;
},
},
loss_colors => { _table => { _columns => 3, _doc => <<DOC, In the Detail view, the color of the median line depends the amount of lost packets. SmokePing comes with a reasonable default setting, but you may choose to disagree. The table below lets you specify your own coloring.
Example:
Loss Color Legend 1 00ff00 "<1" 3 0000ff "<3" 100 ff0000 ">=3"
DOC 0 => { _doc => <<DOC, Activate when the lossrate (in percent) is larger of equal to this number DOC _re => '\d+.?\d*', _re_error => ``I was expecting a number'', }, 1 => { _doc => <<DOC, Color for this range. DOC _re => '[0-9a-f]+', _re_error => ``I was expecting a color of the form rrggbb'', },
2 => { _doc => <<DOC, Description for this range. DOC } }, # table }, #loss_colors uptime_colors => { _table => { _columns => 3, _doc => <<DOC, When monitoring a host with DYNAMIC addressing, SmokePing will keep track of how long the machine is able to keep the same IP address. This time is plotted as a color in the graphs background. SmokePing comes with a reasonable default setting, but you may choose to disagree. The table below lets you specify your own coloring
Example:
# Uptime Color Legend 3600 00ff00 "<1h" 86400 0000ff "<1d" 604800 ff0000 "<1w" 1000000000000 ffff00 ">1w"
Uptime is in days!
DOC 0 => { _doc => <<DOC, Activate when uptime in days is larger of equal to this number DOC _re => '\d+.?\d*', _re_error => ``I was expecting a number'', }, 1 => { _doc => <<DOC, Color for this uptime range range. DOC _re => '[0-9a-f]{6}', _re_error => ``I was expecting a color of the form rrggbb'', },
2 => { _doc => <<DOC, Description for this range. DOC } },#table }, #uptime_colors }, #detail }, #present Probes => { _sections => [ "/$KEY_RE/" ], _doc => <<DOC, The Probes Section configures Probe modules. Probe modules integrate an external ping command into SmokePing. Check the documentation of each module for more information about it. DOC "/$KEY_RE/" => $PROBES, }, Alerts => { _doc => <<DOC, The Alert section lets you setup loss and RTT pattern detectors. After each round of polling, SmokePing will examine its data and determine which detectors match. Detectors are enabled per target and get inherited by the targets children.
Detectors are not just simple thresholds which go off at first sight of a problem. They are configurable to detect special loss or RTT patterns. They let you look at a number of past readings to make a more educated decision on what kind of alert should be sent, or if an alert should be sent at all.
The patterns are numbers prefixed with an operator indicating the type of comparison required for a match.
The following RTT pattern detects if a target's RTT goes from constantly below 10ms to constantly 100ms and more:
old ------------------------------> new <10,<10,<10,<10,<10,>10,>100,>100,>100
Loss patterns work in a similar way, except that the loss is defined as the percentage the total number of received packets is of the total number of packets sent.
old ------------------------------> new ==0%,==0%,==0%,==0%,>20%,>20%,>=20%
Apart from normal numbers, patterns can also contain the values * which is true for all values regardless of the operator. And U which is true for unknown data together with the == and =! operators.
Detectors normally act on state changes. This has the disadvantage, that they will fail to find conditions which were already present when launching smokeping. For this it is possible to write detectors that begin with the special value ==S it is inserted whenever smokeping is started up.
You can write
==S,>20%,>20%
to detect lines that have been losing more than 20% of the packets for two periods after startup.
Sometimes it may be that conditions occur at irregular intervals. But still you only want to throw an alert if they occur several times within a certain amount of times. The operator *X* will ignore up to X values and still let the pattern match:
>10%,*10*,>10%
will fire if more than 10% of the packets have been lost at least twice over the last 10 samples.
A complete example
*** Alerts *** to = admin\@company.xy,peter\@home.xy from = smokealert\@company.xy
+lossdetect type = loss # in percent pattern = ==0%,==0%,==0%,==0%,>20%,>20%,>20% comment = suddenly there is packet loss
+miniloss type = loss # in percent pattern = >0%,*12*,>0%,*12*,>0% comment = detected loss 3 times over the last two hours
+rttdetect type = rtt # in milliseconds pattern = <10,<10,<10,<10,<10,<100,>100,>100,>100 comment = routing messed up again ?
+rttbadstart type = rtt # in milliseconds pattern = ==S,==U comment = offline at startup DOC
_sections => [ '/[^\s,]+/' ], _vars => [ qw(to from edgetrigger) ], _mandatory => [ qw(to from)], to => { _doc => <<DOC, Either an email address to send alerts to, or the name of a program to execute when an alert matches. To call a program, the first character of the B<to> value must be a pipe symbol "|". The program will the be called whenever an alert matches, using the following 5 arguments (except if B<edgetrigger> is 'yes'; see below): B<name-of-alert>, B<target>, B<loss-pattern>, B<rtt-pattern>, B<hostname>. You can also provide a comma separated list of addresses and programs. DOC _re => '(\|.+|.+@\S+|snpp:)', _re_error => 'put an email address or the name of a program here', }, from => { _doc => 'who should alerts appear to be coming from ?', _re => '.+@\S+', _re_error => 'put an email address here', }, edgetrigger => { _doc => <<DOC, The alert notifications and/or the programs executed are normally triggered every time the alert matches. If this variable is set to 'yes', they will be triggered only when the alert's state is changed, ie. when it's raised and when it's cleared. Subsequent matches of the same alert will thus not trigger a notification.
When this variable is set to 'yes', a notification program (see the to variable
documentation above) will get a sixth argument, raise, which has the value 1 if the alert
was just raised and 0 if it was cleared.
DOC
_re => '(yes|no)',
_re_error =>``this must either be 'yes' or 'no''',
_default => 'no',
},
'/[^\s,]+/' => {
_vars => [ qw(type pattern comment to edgetrigger) ],
_inherited => [ qw(edgetrigger)
],
_mandatory => [ qw(type pattern comment) ],
to => { _doc => 'Similar to the ``to'' parameter on the top-level except that it will only be used IN ADDITION to the value of the toplevel parameter. Same rules apply.',
_re => '(\|.+|.+@\S+|snpp:)',
_re_error => 'put an email address or the name of a program here',
},
type => { _doc => <<DOC, Currently the pattern types B<rtt> and B<loss> and B<matcher> are known.
Matchers are plugin modules that extend the alert conditions. Known matchers are @{[join (``, '', map { ``$_'' } @matcherlist)]}.
See the documentation of the corresponding matcher module
(eg. the Smokeping::matchers::$matcherlist[0] manpage) for instructions on
configuring it.
DOC
_re => '(rtt|loss|matcher)',
_re_error => 'Use loss, rtt or matcher'
},
pattern => {
_doc => ``a comma separated list of comparison operators and numbers. rtt patterns are in milliseconds, loss patterns are in percents'',
_re => '(?:([^,]+)(,[^,]+)*|\S+\(.+\s)',
_re_error => 'Could not parse pattern or matcher',
},
edgetrigger => {
_re => '(yes|no)',
_re_error =>``this must either be 'yes' or 'no''',
_default => 'no',
},
},
},
Targets => {_doc => <<DOC,
The Target Section defines the actual work of SmokePing. It contains a hierarchical list
of hosts which mark the endpoints of the network connections the system should monitor.
Each section can contain one host as well as other sections.
DOC
_vars => [ qw(probe menu title remark alerts) ],
_mandatory => [ qw(probe menu title) ],
_order => 1,
_sections => [ ``/$KEY_RE/'' ],
_recursive => [ ``/$KEY_RE/'' ],
``/$KEY_RE/'' => $TARGETCOMMON, # this is just for documentation, _dyn()
below replaces it
probe => {
_doc => <<DOC,
The name of the probe module to be used for this host. The value of
this variable gets propagated
DOC
_sub => sub {
my $val = shift;
return ``probe $val missing from the Probes section''
unless $knownprobes{$val};
return undef;
},
# create the syntax based on the selected probe.
# see 2.1 above
_dyn => sub {
my ($name, $val, $grammar) = @_;
my $targetvars = _deepcopy($storedtargetvars{$val}); my @mandatory = @{$targetvars->{_mandatory}}; delete $targetvars->{_mandatory}; my @targetvars = sort keys %$targetvars; for (@targetvars) { # the default values for targetvars are only used in the Probes section delete $targetvars->{$_}{_default}; $grammar->{$_} = $targetvars->{$_}; } push @{$grammar->{_vars}}, @targetvars; my $g = { %{_deepcopy($TARGETCOMMON)}, %{_deepcopy($targetvars)} }; $grammar->{"/$KEY_RE/"} = $g; $g->{_vars} = [ @{$g->{_vars}}, @targetvars ]; $g->{_inherited} = [ @{$g->{_inherited}}, @targetvars ]; # this makes the reference manual a bit less cluttered delete $grammar->{$_}{_doc} for @targetvars; delete $grammar->{$_}{_example} for @targetvars; delete $g->{$_}{_doc} for @targetvars; delete $g->{$_}{_example} for @targetvars; # make the mandatory variables mandatory only in sections # with 'host' defined # see 2.3 above $g->{host}{_dyn} = sub { my ($name, $val, $grammar) = @_; $grammar->{_mandatory} = [ @mandatory ]; }; }, # _dyn _dyndoc => $probelist, # all available probes }, #probe menu => { _doc => <<DOC }, Menu entry for this section. If not set this will be set to the hostname. DOC alerts => { _doc => <<DOC }, A comma separated list of alerts to check for this target. The alerts have to be setup in the Alerts section. Alerts are inherited by child nodes. Use an empty alerts definition to remove inherited alerts from the current target and its children.
DOC title => { _doc => <<DOC }, Title of the page when it is displayed. This will be set to the hostname if left empty. DOC
remark => { _doc => <<DOC }, An optional remark on the current section. It gets displayed on the webpage. DOC
}
} ); return $parser; }
sub get_config ($$){ my $parser = shift; my $cfgfile = shift;
return $parser->parse( $cfgfile ) || die "ERROR: $parser->{err}\n"; }
sub kill_smoke ($$) { my $pidfile = shift; my $signal = shift; if (defined $pidfile){ if ( -f $pidfile && open PIDFILE, ``<$pidfile'' ) { <PIDFILE> =~ /(\d+)/; my $pid = $1; if ($signal == SIGINT || $signal == SIGTERM) { kill $signal, $pid if kill 0, $pid; sleep 3; # let it die die ``ERROR: Can not stop running instance of SmokePing ($pid)\n'' if kill 0, $pid; } else { die ``ERROR: no instance of SmokePing running (pid $pid)?\n'' unless kill 0, $pid; kill $signal, $pid; } close PIDFILE; } else { die ``ERROR: Can not read pid from $pidfile: $!\n''; }; } }
sub daemonize_me ($) { my $pidfile = shift; if (defined $pidfile){ if (-f $pidfile ) { open PIDFILE, ``<$pidfile''; <PIDFILE> =~ /(\d+)/; close PIDFILE; my $pid = $1; die ``ERROR: I Quit! Another copy of $0 ($pid) seems to be running.\n''. `` Check $pidfile\n'' if kill 0, $pid; } } print ``Warning: no logging method specified. Messages will be lost.\n'' unless $logging; print ``Daemonizing $0 ...\n''; defined (my $pid = fork) or die ``Can't fork: $!''; if ($pid) { exit; } else { if(open(PIDFILE,``>$pidfile'')){ print PIDFILE ``$$\n''; close PIDFILE; } else { warn ``creating $pidfile: $!\n''; }; require 'POSIX.pm'; &POSIX::setsid or die ``Can't start a new session: $!''; open STDOUT,'>/dev/null' or die ``ERROR: Redirecting STDOUT to /dev/null: $!''; open STDIN, '</dev/null' or die ``ERROR: Redirecting STDIN from /dev/null: $!''; open STDERR, '>/dev/null' or die ``ERROR: Redirecting STDERR to /dev/null: $!''; # send warnings and die messages to log $SIG{__WARN__} = sub { do_log ((shift).``\n'') }; $SIG{__DIE__} = sub { do_log ((shift).``\n''); }; } }
# pseudo log system object { my $use_syslog; my $use_cgilog; my $use_debuglog; my $use_filelog;
my $syslog_facility; my $syslog_priority = $DEFAULTPRIORITY; sub initialize_debuglog (){ $use_debuglog = 1; }
sub initialize_cgilog (){ $use_cgilog = 1; $logging=1; return if $cfg->{General}{changecgiprogramname} eq 'no'; # set_progname() is available starting with CGI.pm-2.82 / Perl 5.8.1 # so trap this inside 'eval' # even this apparently isn't enough for older versions that try to # find out whether they are inside an eval...oh well. eval 'CGI::Carp::set_progname($0 . " [client " . ($ENV{REMOTE_ADDR}||"(unknown)") . "]")'; }
sub initialize_filelog ($){ $use_filelog = shift; $logging=1; } sub initialize_syslog ($$) { my $fac = shift; my $pri = shift; $use_syslog = 1; $logging=1; die "missing facility?" unless defined $fac; $syslog_facility = $fac if defined $fac; $syslog_priority = $pri if defined $pri; print "Note: logging to syslog as $syslog_facility/$syslog_priority.\n"; openlog(basename($0), 'pid', $syslog_facility); }
sub do_syslog ($){ my $str = shift; $str =~ s,%,%%,g; syslog("$syslog_facility|$syslog_priority", $str); }
sub do_cgilog ($){ my $str = shift; print "<p>" , $str, "</p>\n"; warn $str, "\n"; # for the webserver log }
sub do_debuglog ($){ do_log(shift) if $use_debuglog; }
sub do_filelog ($){ open X,">>$use_filelog" or return; print X scalar localtime(time)," - ",shift,"\n"; close X; }
sub do_log (@){ my $string = join(" ", @_); chomp $string; do_syslog($string) if $use_syslog; do_cgilog($string) if $use_cgilog; do_filelog($string) if $use_filelog; print STDERR $string,"\n" unless $logging; }
}
########################################################################### # The Main Program ###########################################################################
sub load_cfg ($;$) { my $cfgfile = shift; my $noinit = shift; my $cfmod = (stat $cfgfile)[9] || die ``ERROR: calling stat on $cfgfile: $!\n''; # when running under speedy this will prevent reloading on every run # if cfgfile has been modified we will still run. if (not defined $cfg or not defined $probes or $cfg->{__last} < $cfmod ){ $cfg = undef; my $parser = get_parser; $cfg = get_config $parser, $cfgfile; $cfg->{__parser} = $parser; $cfg->{__last} = $cfmod; $cfg->{__cfgfile} = $cfgfile; $probes = undef; $probes = load_probes $cfg; $cfg->{__probes} = $probes; return if $noinit; init_alerts $cfg if $cfg->{Alerts}; init_target_tree $cfg, $probes, $cfg->{Targets}, $cfg->{General}{datadir}; } else { do_log(``Config file unmodified, skipping reload'') unless $cgimode; } }
sub makepod ($){ my $parser = shift; my $e='='; my $a='@'; my $retval = <<POD;
${e}head1 NAME
smokeping_config - Reference for the SmokePing Config File
${e}head1 OVERVIEW
SmokePing takes its configuration from a single central configuration file. Its location must be hardcoded in the smokeping script and smokeping.cgi.
The contents of this manual is generated directly from the configuration file parser.
The Parser for the Configuration file is written using David Schweikers Config::Grammar module. Read all about it in the Config::Grammar manpage.
The Configuration file has a tree-like structure with section headings at various levels. It also contains variable assignments and tables.
Warning: this manual is rather long. See the smokeping_examples manpage for simple configuration examples.
${e}head1 REFERENCE
${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 '
or "
.
Whitespace can also be escaped with \\
. 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
$retval .= $parser->makepod; $retval .= <<POD;
${e}head1 COPYRIGHT
Copyright (c) 2001-2003 by Tobias Oetiker. All right reserved.
${e}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.
${e}head1 AUTHOR
Tobias Oetiker <tobi\@oetiker.ch>
${e}cut POD
} sub cgi ($) { $cgimode = 'yes'; umask 022; load_cfg shift; my $q=new CGI; 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') ); display_webpage $cfg,$q; } }
sub gen_page ($$$); sub gen_page ($$$) { my ($cfg, $tree, $open) = @_; my ($q, $name, $page);
$q = bless \$q, 'dummyCGI';
$name = @$open ? join('.', @$open) . ".html" : "index.html";
die "Can not open $cfg-{General}{pagedir}/$name for writing: $!" unless open PAGEFILE, ">$cfg->{General}{pagedir}/$name";
my $step = $probes->{$tree->{probe}}->step(); my $readversion = "?"; $VERSION =~ /(\d+)\.(\d{3})(\d{3})/ and $readversion = sprintf("%d.%d.%d",$1,$2,$3); $page = fill_template ($cfg->{Presentation}{template}, { menu => target_menu($cfg->{Targets}, [@$open], #copy this because it gets changed "", ".html"), title => $tree->{title}, remark => ($tree->{remark} || ''), overview => get_overview( $cfg,$q,$tree,$open ), body => get_detail( $cfg,$q,$tree,$open ), target_ip => ($tree->{host} || ''), owner => $cfg->{General}{owner}, contact => $cfg->{General}{contact}, author => '<A HREF="http://tobi.oetiker.ch/">Tobi Oetiker</A> and Niko Tyni', smokeping => '<A HREF="http://people.ee.ethz.ch/~oetiker/webtools/smokeping/counter.cgi/'.$VERSION.'">SmokePing-'.$readversion.'</A>', step => $step, rrdlogo => '<A HREF="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/"><img border="0" src="'.$cfg->{General}{imgurl}.'/rrdtool.png"></a>', smokelogo => '<A HREF="http://people.ee.ethz.ch/~oetiker/webtools/smokeping/counter.cgi/'.$VERSION.'"><img border="0" src="'.$cfg->{General}{imgurl}.'/smokeping.png"></a>', });
print PAGEFILE $page; close PAGEFILE;
foreach my $key (keys %$tree) { my $value = $tree->{$key}; next unless ref($value) eq 'HASH'; gen_page($cfg, $value, [ @$open, $key ]); } }
sub makestaticpages ($$) { my $cfg = shift; my $dir = shift;
# If directory is given, override current values (pagedir and and # imgurl) so that all generated data is in $dir. If $dir is undef, # use values from config file. if ($dir) { mkdir $dir, 0755 unless -d $dir; $cfg->{General}{pagedir} = $dir; $cfg->{General}{imgurl} = '.'; } die "ERROR: No pagedir defined for static pages\n" unless $cfg->{General}{pagedir}; # Logos. gen_imgs($cfg);
# Iterate over all targets. my $tree = $cfg->{Targets}; gen_page($cfg, $tree, []); }
sub pages ($) { my ($config) = @_; umask 022; load_cfg($config); makestaticpages($cfg, undef); }
sub pod2man { my $string = shift; my $pid = open(P, ``-|''); if ($pid) { pod2usage(-verbose => 2, -input => \*P); exit 0; } else { print $string; exit 0; } }
sub maybe_require { | |
# like eval ``require $class'', but tries to | |
# fake missing classes by adding them to %INC. | |
# This rocks when we're building the documentation | |
# so we don't need to have the external modules | |
# installed. |
my $class = shift;
# don't do the kludge unless we're building documentation unless (exists $opt{makepod} or exists $opt{man}) { eval "require $class"; die("require $class failed: $@") if $@; return; }
my %faked;
my $file = $class; $file =~ s,::,/,g; $file .= ".pm";
eval "require $class";
while ($@ =~ /Can't locate (\S+)\.pm/) { my $missing = $1; die("Can't fake missing class $missing, giving up. This shouldn't happen.") if $faked{$missing}++; $INC{"$missing.pm"} = "foobar"; $missing =~ s,/,::,;
delete $INC{"$file"}; # so we can redo the require() eval "require $class"; last unless $@; } die("require $class failed: $@") if $@; my $libpath = find_libdir; $INC{$file} = "$libpath/$file"; }
sub probedoc { my $class = shift; my $do_man = shift; maybe_require($class); if ($do_man) { pod2man($class->pod); } else { print $class->pod; } exit 0; }
sub verify_cfg { my $cfgfile = shift; get_config(get_parser, $cfgfile); print ``Configuration file '$cfgfile' syntax OK.\n''; }
sub make_kid { my $sleep_count = 0; my $pid; do { $pid = fork; unless (defined $pid) { do_log("Fatal: cannot fork: $!"); die "bailing out" if $sleep_count++ > 6; sleep 10; } } until defined $pid; srand(); return $pid; }
sub start_probes { my $pids = shift; my $pid; my $myprobe; for my $p (keys %$probes) { if ($probes->{$p}->target_count == 0) { do_log(``No targets defined for probe $p, skipping.''); next; } $pid = make_kid(); $myprobe = $p; $pids->{$pid} = $p; last unless $pid; do_log(``Child process $pid started for probe $p.''); } return $pid; }
sub main (;$) {
$cgimode = 0;
umask 022;
my $defaultcfg = shift;
$opt{filter}=[];
GetOptions(\%opt, 'version', 'email', 'man:s','help','logfile=s','static-pages:s', 'debug-daemon',
'nosleep', 'makepod:s','debug','restart', 'filter=s', 'nodaemon|nodemon',
'config=s', 'check', 'gen-examples', 'reload') or pod2usage(2);
if($opt{version})
{ print ``$VERSION\n''; exit(0)
};
if(exists $opt{man}) {
if ($opt{man}) {
if ($opt{man} eq 'smokeping_config') {
pod2man(makepod(get_parser));
} else {
probedoc($opt{man}, 'do_man');
}
} else {
pod2usage(-verbose => 2);
}
exit 0;
}
if($opt{help})
{ pod2usage(-verbose => 1); exit 0 };
if(exists $opt{makepod}) {
if ($opt{makepod} and $opt{makepod} ne 'smokeping_config') {
probedoc($opt{makepod});
} else {
print makepod(get_parser);
}
exit 0;
}
if (exists $opt{'gen-examples'}) {
Smokeping::Examples::make($opt{check});
exit 0;
}
initialize_debuglog if $opt{debug} or $opt{'debug-daemon'};
my $cfgfile = $opt{config} || $defaultcfg;
if(defined $opt{'check'}) { verify_cfg($cfgfile); exit 0; }
if($opt{reload})
{
load_cfg $cfgfile, 'noinit'; # we need just the piddir
kill_smoke $cfg->{General}{piddir}.``/smokeping.pid'', SIGHUP;
print ``HUP signal sent to the running SmokePing process, exiting.\n'';
exit 0;
};
load_cfg $cfgfile;
if(defined $opt{'static-pages'}) { makestaticpages $cfg, $opt{'static-pages'}; exit 0 };
if($opt{email})
{ enable_dynamic $cfg, $cfg->{Targets},``'',``''; exit 0 };
if($opt{restart})
{ kill_smoke $cfg->{General}{piddir}.``/smokeping.pid'', SIGINT;};
if($opt{logfile})
{ initialize_filelog($opt{logfile})
};
if (not keys %$probes) {
do_log(``No probes defined, exiting.'');
exit 1;
}
unless ($opt{debug} or $opt{nodaemon}) {
if (defined $cfg->{General}{syslogfacility}) {
initialize_syslog($cfg->{General}{syslogfacility},
$cfg->{General}{syslogpriority});
}
daemonize_me $cfg->{General}{piddir}.``/smokeping.pid'';
}
do_log ``Smokeping version $VERSION successfully launched.'';
RESTART:
my $myprobe;
my $multiprocessmode;
my $forkprobes = $cfg->{General}{concurrentprobes} || 'yes';
if ($forkprobes eq ``yes'' and keys %$probes > 1 and not $opt{debug}) {
$multiprocessmode = 1;
my %probepids;
my $pid;
do_log(``Entering multiprocess mode.'');
$pid = start_probes(\%probepids);
$myprobe = $probepids{$pid};
goto KID unless $pid; # child skips rest of loop
# parent
do_log(``All probe processes started successfully.'');
my $exiting = 0;
my $reloading = 0;
for my $sig (qw(INT TERM)) {
$SIG{$sig} = sub {
do_log(``Got $sig signal, terminating child processes.'');
$exiting = 1;
kill $sig, $_ for keys %probepids;
my $now = time;
while(keys %probepids) { # SIGCHLD handler below removes the keys
if (time - $now > 2) {
do_log(``Fatal: can't terminate all child processes, giving up.'');
exit 1;
}
sleep 1;
}
do_log(``All child processes successfully terminated, exiting.'');
exit 0;
}
};
$SIG{CHLD} = sub {
while ((my $dead = waitpid(-1, WNOHANG)) > 0) {
my $p = $probepids{$dead};
$p = 'unknown' unless defined $p;
do_log(``Child process $dead (probe $p) exited unexpectedly with status $?.'')
unless $exiting or $reloading;
delete $probepids{$dead};
}
};
my $gothup = 0;
$SIG{HUP} = sub {
do_debuglog(``Got HUP signal.'');
$gothup = 1;
};
while (1) { # just wait for the signals
sleep;
next unless $gothup;
$reloading = 1;
$gothup = 0;
my $oldprobes = $probes;
$reloading = 0, next unless reload_cfg($cfgfile);
do_debuglog(``Restarting probe processes '' . join(``,'', keys %probepids) . ``.'');
kill SIGHUP, $_ for (keys %probepids);
my $i=0;
while (keys %probepids) {
sleep 1;
if ($i % 10 == 0) {
do_log(``Waiting for child processes to terminate.'');
}
$i++;
my %termsent;
for (keys %probepids) {
my $step = $oldprobes->{$probepids{$_}}->step;
if ($i > $step) {
do_log(``Child process $_ took over its step value to terminate, killing it with SIGTERM'');
if (kill SIGTERM, $_ == 0 and exists $probepids{$_}) {
do_log(``Fatal: Child process $_ has disappeared? This shouldn't happen. Giving up.'');
exit 1;
} else {
$termsent{$_} = time;
}
}
for (keys %termsent) {
if (exists $probepids{$_}) {
if (time() - $termsent{$_} > 2) {
do_log(``Fatal: Child process $_ took over 2 seconds to exit on TERM signal. Giving up.'');
exit 1;
}
} else {
delete $termsent{$_};
}
}
}
}
$reloading = 0;
do_log(``Child processes terminated, restarting with new configuration.'');
$SIG{CHLD} = 'DEFAULT'; # restore
goto RESTART;
}
do_log(``Exiting abnormally - this should not happen.'');
exit 1; # not reached
} else {
$multiprocessmode = 0;
if ($forkprobes ne ``yes'') {
do_log(``Not entering multiprocess mode because the 'concurrentprobes' variable is not set.'');
for my $p (keys %$probes) {
for my $what (qw(offset step)) {
do_log(``Warning: probe-specific parameter '$what' ignored for probe $p in single-process mode.'' )
if defined $cfg->{Probes}{$p}{$what};
}
}
} elsif ($opt{debug}) {
do_debuglog(``Not entering multiprocess mode with '--debug'. Use '--debug-daemon' for that.'')
} elsif (keys %$probes == 1) {
do_log(``Not entering multiprocess mode for just a single probe.'');
$myprobe = (keys %$probes)[0]; # this way we won't ignore a probe-specific step parameter
}
}
KID:
my $offset;
my $step;
my $gothup = 0;
my $changeprocessnames = $cfg->{General}{changeprocessnames} ne ``no'';
$SIG{HUP} = sub {
do_log(``Got HUP signal, '' . ($multiprocessmode ? ``exiting'' : ``restarting'') . `` gracefully.'');
$gothup = 1;
};
for my $sig (qw(INT TERM)) {
$SIG{$sig} = sub {
do_log(``got $sig signal, terminating.'');
exit 1;
}
}
if (defined $myprobe) {
$offset = $probes->{$myprobe}->offset || 'random';
$step = $probes->{$myprobe}->step;
$0 .= `` [$myprobe]'' if $changeprocessnames;
} else {
$offset = $cfg->{General}{offset} || 'random';
$step = $cfg->{Database}{step};
}
if ($offset eq 'random'){
$offset = int(rand($step));
} else {
$offset =~ s/%$//;
$offset = $offset / 100 * $step;
}
for (keys %$probes) {
next if defined $myprobe and $_ ne $myprobe;
# fill this in for report_probes()
below
$probes->{$_}->offset_in_seconds($offset); # this is just for humans
if ($opt{debug} or $opt{'debug-daemon'}) {
$probes->{$_}->debug(1)
if $probes->{$_}->can('debug');
}
}
report_probes($probes, $myprobe);
while (1) { unless ($opt{nosleep} or $opt{debug}) { my $sleeptime = $step - (time-$offset) % $step; if (defined $myprobe) { $probes->{$myprobe}->do_debug("Sleeping $sleeptime seconds."); } else { do_debuglog("Sleeping $sleeptime seconds."); } sleep $sleeptime; last if checkhup($multiprocessmode, $gothup) && reload_cfg($cfgfile); } my $now = time; run_probes $probes, $myprobe; # $myprobe is undef if running without 'concurrentprobes' update_rrds $cfg, $probes, $cfg->{Targets}, $cfg->{General}{datadir}, $myprobe; exit 0 if $opt{debug}; my $runtime = time - $now; if ($runtime > $step) { my $warn = "WARNING: smokeping took $runtime seconds to complete 1 round of polling. ". "It should complete polling in $step seconds. ". "You may have unresponsive devices in your setup.\n"; if (defined $myprobe) { $probes->{$myprobe}->do_log($warn); } else { do_log($warn); } } last if checkhup($multiprocessmode, $gothup) && reload_cfg($cfgfile); } $0 =~ s/ \[$myprobe\]$// if $changeprocessnames; goto RESTART; }
sub checkhup ($$) { my $multiprocessmode = shift; my $gothup = shift; if ($gothup) { if ($multiprocessmode) { do_log(``Exiting due to HUP signal.''); exit 0; } else { do_log(``Restarting due to HUP signal.''); return 1; } } return 0; }
sub reload_cfg ($) {
my $cfgfile = shift;
my ($oldcfg, $oldprobes) = ($cfg, $probes);
do_log(``Reloading configuration.'');
eval { load_cfg($cfgfile)
};
if ($@) {
do_log(``Reloading configuration from $cfgfile failed: $@'');
($cfg, $probes) = ($oldcfg, $oldprobes);
return 0;
}
return 1;
}
sub gen_imgs ($){
my $cfg = shift; my $modulemodtime; for (@INC) { ( -f "$_/Smokeping.pm" ) or next; $modulemodtime = (stat _)[9]; last; } if (not -r $cfg->{General}{imgcache}."/rrdtool.png" or (defined $modulemodtime and $modulemodtime > (stat _)[9])){ open W, ">".$cfg->{General}{imgcache}."/rrdtool.png" or do { warn "WARNING: creating $cfg->{General}{imgcache}/rrdtool.png: $!\n"; return 0 }; print W unpack ('u', <<'UUENC'); MB5!.1PT*&@H -24A$4@ 'D P" 8 UK=7M "7!(67, YG M .9P&/B8)Q .]DE$051XG.V<"71.9QK''X14FMJ"JHHEDUJJC)-2M11I MAEK'4OM6,[744E7&F58QMJ/6*:JTJ&,=Y*B=:C%$::GE9"2DUE!#$;%5$T*8 M^WO-_=Q\WWOS+8FV^/[GY'S)?=_[O,_[[,][[Y<<8N#2I4N54E-3.]Z^?;O0 MG3MWQ(^''X8>4Z]>O;HR(B(B)D=24E(Y0[D';MRXD<?X_*UY\R.;D)Z>+LG) MR7+TZ-&F 88'MTY+2\MC*%G\2GYT@"[/G#DC\?'Q'0-NW;H5S 4T[U?RHP-# MKV*$:[EX\6+^G/X<_&CB[MV[CM]S_H9\^/$KP:_DQP !=@/)EU/D0G**QX3" M2A:0P#RVY.3(B61)OW/7=MR*X"?S2.@S^6S'/>$M,#"7A(46]&@]9]PQ^+SZ M\TTIF/\)G^Y_D+AF\'7F_,\NU\-+%Y+< 7J?M=7*VBU'9/J"/5XQ4+G\T]*I M>26)JE7&9:SGD'5R/27-8UKYG@J4AG7"I5OK/TK1D"=]XBTX*(]$O/",M&M: M4:I7>=9V7ONWOY!CIRYEN/9>[UK2NM'S'O/[:^';_:=EZ.2M+M?7?=Y!BA4) MUMZ3K>'ZP _GY>_C-\O(J3'*&[("+#9Z_4%IW_\+.7@DR2<:&-7V[T])W^$; MI/^(C9)RXU:6>'I8\4!R,IZV8,5_LH46RA[\X:8L*P@/Z&5$$QV=1UWY7BN9 M7%DAO+#CQPX+5AYPZ\WD<"LMY[!LXD+R+[)AZU%O675!PK&+,N7S72[7LQIU M?N^PKY1L,*A'#:E=M:3C;P3WQM]6N0@*#SS]TS4I]6Q^6UH8S,)_MG3\#8VA MD_\M7W]SPF7NG@-GW>;(,8,BI6'=<&44<Y;NEQ5?_> RAVLM7ZN@C,H<UWER M[*'SDC/G/1^H^U))"2D8E&&<5+!CSX^R/_XGN7@Y56[?OF/DQ">E?'@1B7RY MM$=%&WQNVW52CIV\I&1%P1E2($C5$;6KA4K0$[G=TO $7BO9&0@+99TZ<]5E M+"75NS"8,V<.J?=R&:V2;]Y,]Y@.$6%(WU?D\K4;LO6[DR[CBU?'*8,8^\DW MMC0VQAQ3/R LM%D&)2]>%2>S#2/2%I*&X4R>_9UT^/,+TJ=S5;4GE[VDW9;I M\_?(\B\/R:W;KH=17*?P?*=;=6E>OYS[#;M!EG,R5>E934D/BH8$::]G!@JE M[*+5HUV$]CKYV=<0/?RC;?+1W%V9=@HH<=[R6'E[Q)<N2F2,3F/)VGBM@DT0 M"4=_O%VFS=OM$Y]6>.W)T>L.R?;=]Q1QXO0555'K!%8V+,0EQ#GCPJ5?'-Z$ MT [\<$'.)5W7SJT1$>HMJXH'PN;EJS<R7$> )\]<\9K>@A4'M+4!D2QW0"Y# M'I<S7-\=>\9H];Z7=__ZLN,:G8>N6X#7Z[^DN3@,:Y;_0Q%I\$J8U_R:\%K) M>($GL/,B*Q"V+F\Z P'4KE;2[3P=BC_]E(N20;*11\UZ8,#HK]0!BQ7=6E>1 MJ)KW^OU2)?(K7LGSSJ#__F148_4["J2SL")ZW4%IUZ2BX@.'T*4BUNK7M9KR M;.H;#HZLF&)$CC_5*J,-_9X@VULHBH51[]:3R!JELX4>1<C'_VAH>YKC#KER MZN\C^IA5O8XV190YSIXP;EV!UB3R.<?OND,@%!?S_Q2T8>LQ+2_-HLJJ3_B@ M:',&!1H&XBNR7'A9@:4MG]G&MA7R%JU>*Z\*J*P >E0,)]W1Y94VSJ4L!R_ M%BVDW[?IF0G']8<Z>+D)NU.KA.,7I<KSQ3SBU1E>NT=;(_0@>%UKA'?04G@* MLPKNVJJR=IRV*2L]+/E=E^,QQA+%[<_&=; KM(+RWF]S G+KQ6FF"W*N#M9( M8A>Q2!>^PFLEUWRQA/*P5D:OJ</R+Q,\II4O.%#1ZM?U)6U?2>^X*_:_WK+H MEA?.V+WM00/SY-)>M[9V=FT>[1"P&H055D-.N:%_<8-S>%_A<TYN;.0BG=7% M'CKG4CBX9<+PK,:6W&;%\@V>&XT55,%V1ZN>])[."K,[W4N^<K]@2[JD3PUA MH0749\7GBFK'+UJ*/KONHF+9(O;,NH'/2L;S["I>FGEO818?SMBQ]T?;C3MC MQJ*]TF7@2FG9:YGJ9W6AGC33Q,:@K/AZQW%5;'&80N5-0:0SZGV6](2!.P,# MCJIUK_W1%69@KY&63,1I"BS2&M''5V2INK93S,:8XUX]5@3AI0IIK15%>=)F M 7I,CED)\SH0HB<.J>_2BN@*17I9GESQ< 1Z]/R=6KC6#IQ^<6C!*=>2-?$N MXQS%FL_&JU4NKGZ<,6[F3O7HE"=X>RP*-]&]?83/[1/(DI(YP]8=>-!J^/) MP<YH5GZ5D.GID"> SYECFFA?)*A=U;.#EEX=7U0MG3-6;SJL/<'"^][YRTL9 MKHT='.5202,O3LBV[$QTH4UJH6[)"K*D9*RK262X=FS9NH->T^/A@N[M$JK3 M+3M=#Q$\ 2$6;_K7E):V>:U3BTJ9/E&STOIX1$-;8[0"Y<P<T]AE/Z2Y^9.: M:XW%"N[C['O8VW7<KN4.MGTRITPZ"W*V0I[HV+4&]*AF*$0PG-M:4<0I3%)! M]GNCFB3^F/%X$%C#OQUO5I1ZMH"$&FU252,\NJND$>C<"<UETS?'5;@TWQ*A M?^6ID/55).;^XYVZTJ'9"[+YVT253V_=OE>DY376>=%07OU7PC)]]8BH,FML M4W7L&;/[E,0=OI^'"^;+:QA ,<-(RFL[CM!G\FOWGMD><QP]>O3#]/3T]V[> MO*G>U?7CT4!:6IH</'A0=N[<N=[_MN9C +^2'P/XE?P8(%,ESY@Q0X8,&?+ M%I\W;YX,&# @V^F2BUJV;"E5JU:5UU]_7?W]6V/+EBW2I4L7]4U#9YP^?5J- MQ<;&/I"U,U7RV;-GY<0)[UN7"Q<N2-^^?942G0&]GCU[RMJU:Z58L6(2'JYO MP3(#W]\:.7*DK0%.G3I5 @,#9>'"A1(6%B;3IT_WBOZ$"1,4C]F)RY<O2T)" M@K:XA=<*%2I(<+#^"51FV+Y]NUL#\>I1(TRN7+E2*;%Z]>K2KET[Q\MN++9C MQPZ^12=7KER1LF7+2E14E!I#H?OW[Y>4E!0Y=>J4U*Y=6VK6K"E[]MQ_01YO M6[UZM73OWEWFS)FCA 'S* D<.7)$5JU:)>?.G9-<N7+)]>O7I5>O7EH^$1;S M"A8LJ#PG)"0DTWWA2='1T>J3>_GLTZ>/VB>\M&_?7N;.G:O6*UJTJ"Q;MDSQ M@^*00X<.'21W[MRR=>M6M8\Z=>K(XL6+U?H]>O3(L#[CPX</%[H9Z"$'9TR> M/%DB(R/EY,F3LGOW;K5&JU:MU!CW+5FR1.DB*"A(C??NW3M39_$X)[-Q%( G MXH%XQZ1)D]08RAHX<* 4*5)$NG;MJA2 @$)#0V7#A@W*ZRI5JB2M6[=6@D'9 M;!SKV[AQHX/^BA4K9.S8L>IKM#$Q,3)X\& U=NW:->59* ZA 8P%Z]>A08,& M*@HU;=I4^-[UH$&#U'64X!PN$1K[.GSXL**-LJ!=IDP9M2X\C1DS1ADP0$'P M'!$1H80_<^9,1\1"@1@+2F)_&/?0H4,SK+=@P0*I6[>NH@U=<W^LP_X ]\V> M/5LV;=JD^$4FID- &QK-FC53Z^-4&'UF4<!C3\:2"2ML"D&P"1AYZZVWE%! M\^;-E:57K%A161C &KD/IKBO7+ERF>9(O)^YI4J5DFG3IBD!L!$,IV'#ADJQ M6#D*,PW)"GCZ[+//E!*('G@*1HD ,9J)$R>J^TU %T%VZ]9-\<UW>A%R4E*2 MXAL0K1 X_+_YYIM*H'GSYE4T,6)S_P #1GGPQ3SD9BH/$"%0#H;,_C R': S M?_Y\]5FO7CV)BXN3:M6J*7F6+EU:14/2UKAQXY17-V[<V%:F'BOYV+%CBC@; M!811%F%1/ ?KZM^_OPH;6+WI/7CO\N7+E;<@[/CX>!DQ8H3M.I4KWWL(0"@R M-\M:*(8-[=V[5RF8\.6L8#:+@C&V8<.&*0\CXG _$2A?OGPNX1%C93[WL4>, MTS0FLQXQ#100<<CY)J]$)BO@V^3+#*$8HPF,U[H_NP,HG,&<QP]R $0;PC.1 M,S4U5>VI;=NVMO($'BN9_,*_)S!A;@YKW;QYLQ0N7%@50C"-@ID/R.%8(!Z M]2+\S$*+Z3U6(/Q]^_;)IY]^JFC@=<6+NS[-00' S./,PXM&CQZM_L:3G>D3 MUO%2C %%DFY,13CSA->___[[TJ)%"T?1U[%CQPQSV3_&C_>C!%-&WB*GS;MI M&"[1H$:-&NIOC,%NK@F/E8QUDX_P2HHJ\Q.!(B1R&L6&:?&,X6U8,=Z1/__] MUX4(I=#S%!@489M0;!H/'MBI4Z<, JQ2I8JR>J(*1L!<!(#@^62N<R%&SN:' M HM( YA'T><,JP(!81TCL484YL GX9,"#3Z(@*2.[ !&NW[]^@PI@$A$JK&# M5THF'-)>L%'"V?CQX]480L&B\"1R#*&%0L(<8RY&P'WD/,9T'FL'# =C@C8\ M #X)IR8/@'I@RI0I*JR;11LADQQ)GXKW]>O73WFX"?Y/"DH(" APT"9R\+N9 M<DQ@-%3$A':,G%1$&F%O)LSJG#X=@Z-H<N=IG@(#PD!9P^0596-L=";L7P>? M'E PUZJD^O7K*Z^R"H]#" H&BH=1HT8Y"@,4_>JKKZK\;;8%[H#"6)-"Q03" MHQZ8-6N6]AZKQYG ^*A$K;QC$+1#>+(YGW7(^Z0:.]K F3XA'^5OV[;-14;9 M 2(BYP]KUJQQI"MX)V4L7;HT0QME?4#ATRNYSLP3+M@@E6:! @542,2*.W?N M+.?/GU=A?M&B18[^E=#B3;C&0/!"6B(V1ZXC#5 IVT'G/6:Q8P75/'PW:M1( MY6*3]@<??. 5;6=DMX(!<D.1;=JT49&-"C\Q,5'UZ9GUR=GVJ!'!T'80CLEM M9O$#R%N,0Y^0XES8> **'L*@F5/9E)G_LPKV3NB'?WI]>/=%2>S1/ AZD""= ML!9U#OSJPK35D_W/DQ]1:)\G6__ODQ^/!J@=*"SYMXN)>+%YJN17]L,/=$A; MR,%37%Q<8H[HZ.A@(Q=];;AV#?*=7\D//] A!:11>2<:CEO/\3*OT?Y4,I3M F^W<Q_/C=@-J*'Z,-Y/EC^O\ 2S'2(P:7,"@ 245.1*Y"8(*O UUENC close W; }
if (not -r $cfg->{General}{imgcache}."/smokeping.png" or (defined $modulemodtime and $modulemodtime > (stat _)[9])){ open W, ">".$cfg->{General}{imgcache}."/smokeping.png" or do { warn "WARNING: creating $cfg->{General}{imgcache}/smokeping.png: $!\n"; return 0}; print W unpack ('u', <<'UUENC'); MB5!.1PT*&@H -24A$4@ '@ B" ( ;$XH4 "7!(67, [# M .PP'';ZAD 0T$E$051HWNU:2XQDUUG^S[FONO?6^]'555U=W5V>GA[W M>#RT;0T$$@,!(1:.!$B(#;!B$6416!*06("4!6$5@8B04((,64 DE,064BP4 M$A3%P=8(VSUN3_?TLUY=5;>>]]9]G@>+,UU3Z>F9\0-F9)E_U75UZM1_OO/_ MW__]_VWTTDLOP2? $$(8X_DGC#'.^6-S0'[2"#P.0PC%8K%X/!Z+Q1!"G'// M\QS'\7W_L?GPB0!:EN54*E6I5$JE4BP6FTZGK5:KU6I%440I?4P^/&D0'H=A MC%.I5*U6>_[YY[/9[.GIZ9MOOCD>CX?#X?\#_;]LDB2ET^E*I9),)B5)VMO; MDR3I<7(T_NA;?(Q,(,LY?YP0"_MD 8T0>E(__<D"^@G:Q1Q]869]B'"8W^?" MKW/.A=[Z0/O/UL_V?U*A>C]0#_+D J E25(4199EH? YYY120@@AA#%V_[[S M6\\OP!AKFB;+,N<\BJ(P#,^Y)4F2IFFB*!%"@B!X)'4BA&;NB=\5OD51=+]O M']3.G>7<<2X$2CXSP?O"$T+((X"6)"EQ9H9A2)+$&*.44DH=Q^GW^[9MS^LA M15%,TS1-4X#E^[YMVZ(+4%4UF\UF,AG#,!ACCN,,!H/1:"2^CA#2=3V;S:;3 MZ5@LQAB;3J=B011%#SJ8JJJ)1"*93)JF&8O%Q/'",/0\S[9MV[:GT^F'EFN: MIL7C<5W717@10AS'<5WW0M0D23)-,YE,)A()3=-$;@F@7=>=3":V;8=A>#'0 MBJ+D\_E2J;2TM)3/YSGGCN,(UQECDB0%0>#[_NPD""'#,!87%RN52CJ=]GV_ MV^TVF\U.IX,Q+I5*U6JU6JT6BT5"2*/1.#P\9(P-AT, ,$US:6FI6JTN+R]G M,AE*:;O=/CP\/#HZLBSKPKQ))!(+"PO%8K%8+.;S^60RJ6D:8\QUW=%HU.ET M.IU.M]NU+"L(@@^*LCA[N5PN% J&8411U&PV14=S/]"Q6"R7RPE/=%T7L!!" M$$*JJF*,;=L6_CB.,\M1>79%V6QV;6WMRI4K5ZY<88P='!PXCC,:C02-"HC% MO<WR2]?U<KG\P@LOK*VM3:?3G9T=004 L+2TM+FY^?SSS^?S>4KIX>$AQMCW M_2 (**6Y7*Y2J3SWW'////.,:9J,L5:K)<NRZ[K3Z70ZG9Y#6?1U:VMKZ^OK MM5HMF\W."!IC' 1!J]7:W]^_<^>.JJKM=ON#]M;Q>+Q2J5R[=FUS<],PC(.# M ]=U+<NZ?V4L%BN52JNKJY<N7<ID,I9E-9O-\7@<!(&F:=ELME H5*O57"X7 MB\7J]?ID,A%^RK/$R>?SM5KMQHT;V6SVYLV;^_O[>WM[(@!5504 S_.B*)IG M,8RQ:9KE<KE<+G/.%44)PS"*(M=U5555% 5CC#%6%*56JTTF$\_S""'C\5@4 M ,&SBJ( 0+5:M2RK7J^WVVW7=>?)VC",<KE\^?+EK:VMC8T-15':[;:(EUD; M<O7JU6*Q:)HFQI@0TFZW+TSY^Q.%<ZYI6BZ7$Y&QOKYN6=9X/!;I?^["9%DN M% IK:VO7KU]?7U\74;^WM]?M=@DAJJJ62B7#,%975\OELJJJC#%"B(B;NT"K MJII,)JO5:JE4&@P&_7Z_V^V>GI[ZOB]*!,;X?ITOGH@Q&$(HE\LE$@E)DJ(H M&@Z'>WM[A)!GGWVV4JEHFG;Y\N7Q>&Q9ENNZGN?U>KWM[6W?]Z]?OY[)9 1D M\7A<5=5Y.2%2K5JM7KMV[>K5JP#P[KOO[NSLM%HM 70VFUU=7;UV[=K2TM+6 MUE88AH[C.(XS' X1P,-KJW!;D-+*RDJE4G%==WM[>W=WMUZO#X=#QMA\8"63 MR=F5BRP\.3EIM5JNZR*$7-=EC"42B965E:VM+820\"0(@BB*Y%EL"C0QQJ*8 M*HHB(E0LN+#^SIP0?XQ&H]%H)!@J#,-^OQ^&82*1R.5RAF$DD\E4*J6J*J5T M,ID()SCG@G"%EE 413#/S'1=S^?S*RLKERY=4A1E?W]_>WM[9V>GV6RZKBM) M4B:3<1Q'EF73-'.YW/KZ>JO5ZG8[CFW[$941YW"! A,GY9RKJBK"N5:KJ:JZ MO;V]M[=7K]?[_3ZE='9 L3*52I5*I8V-C4PF<WQ\['F>YWEA&,X&L&$8"LI6 M%&5C8^/T]+3;[0Z'0T*(/%OA.$Z]7B^52MELME@L+B\OB_HF]B*$/%Q[$4)$ M$K3;[<%@P!@3*/=Z/<=Q#,,0D\DP#*?3J2 '@7*OUUM;6S--$^X3H4*<9#(9 M46\]SVLVF_5Z70 A_/$\3Y*D7"ZWLK*23J>+Q>)BL7@GD<)*/\Z&-M4PL'/2 M5MQ*,IE,)I.&82PM+:VNKI9*)<NR]O?WCX^/.YU.$ 3GG%$4)9%(+"XN%HM% MP0&ZKINFJ>OZ+!R%:!&X)9/)6"P6B\4T37,<YR[0ON_W>KW=W=TP#*O5JBS+ MI5))4912J30<#@5A.8[C>=Z#I*4@(]NV/<_CG NN%,5-R( @"&S;GDPF@O@0 M0@)TS_,>I,E$'3=-,Y5*"4QG23.KR5$4B8>3R812:NAZ,I4V-/6+RV]\.K[_ MG>Y3?U??!(!YS$S3+)5*I5*)$&(81JU66U]?US1M,!@,A\-^O^\XSOV>*(JB MZ[K0HP @A)TLR[%8;.:_)$FNZYZ<G$11)$F2$*Q"D,@SF$2R#(?#HZ.C;#8K MU'2Y7&:,]?O]3J<CHG4\'E^(M>A*HBB:02 ZG=G,5VC><YDA%CRD+Q U4Y9E M..M-1-^$YGY7;")VEB3,Y=B*VGLQL0\(?J-T\$IWI<?T^>A4534>-V.:9AB& MKNN"T^8PO=@303B2) FB"() >#+O?! $ ME&HR$<$Y''&+NGH\,PM"QK,IF< MGI[JNBX46#J=OGKU*L;X^/CXUJU; JQS\FMVX NKY;F/YS!]9"M(*0V"0.2$ MHB@B$V59(E&( %PA+BJJEI,UQ09(R"$1+[;"<UVE"B9]NUA>A!ILLH #@# MS@# <YW!8#B93L>C$8F"3J?3:K4N7;J4S:1SF50NEQ^-QHX] 83@KGN(GUVS MD*>2)(DTFK4:(K8((6$8CD:CF?^BXX-SG2%CS/=]W_<GD\EH- J"()E,KJZN M"NE**>WU>MUN]YS\>I_V(;["&!.$T^_W!8_G<KE\/G_:6W [%B61!%0QTX5" M8;&0RRPN8TD>CD:3D=5R\)_4/[LBM6Y-\RY5DHA'7":2SJ48 #A$ZG0MJW6R M>]34M)@DR:ETNI#/+2R6:E=?Z _'WM2Y'?+(FP+"E",)@0PDBB+1OCJ.D\UF MHRCR/$\0XSR;/VB\(Y][*M:)CDLTM:(-%?I/2.-Y^?5_:N+EWG X/#DY65U= M+1:+RY6E^MKES>D/BJG_FA+YN_XO?"IU\J+YFG8$*O]9\MF_J%MJO=%6W,[O M+=SDP&^D>__0V C ^*STPQM[W]/=R_#25XV;?__IM[[Z&6G2J*C_W'WZH&%D MLP?ERLHUF5[9^7*M^RK-A#MR^A]/5GXU<UC5[2,O^8W&E2@BD\FDW6XW&HU, M)B,*G:[K8E QPW V(#H'^EV@95D6PXT@",3T1PP6!(O)LNS[OF59HF?YZ..; M]VFB([4LZ^3D9&=GQS#T<KET?4N)M_^ZY+=H4EME/RI[)V #* C>_C8__E%[ M^4]/3B>1;?UBK2XI, [4;S8O^8!6T4G>.F'N"7SW\^;KWS(-Q!6>3L.:^?J? MG:0/ZIFEO7<V?_"7:OVFJF).V,\95NVIMHII6O=SMO]R\TI$R&0R;K5:[[WW M7B:322:3A4(AE\L)L< Y%Q@*N(0T$*WY/: QQLED4HQXQ'R'4AJ/QX6Z7%A8 M"()@=W?WX.!@,!B<:Y8$Y0. &(E<&)7B^8->[\]8[,(%C+'1:%2OUS5-0P"; MFU=6JXN\L,"/ .&HI ZB3WU!TG3TUC>X.\"!M;C[]5;G1</U(B9)$244,T MB'$,&O"P#WNOC7[E2T?U]D;KFQJ+3(4\+1_^6W?CUW?^20UN,AUCU;!7/W?< M#RN#[Z?Y "(@% NWILZTW6[KNJXHRN;F9JE4JM5JG'/1&<;C<2$B$$*]7J_3 MZ; S P"9<RY)DF"&Q<5%H0HQQH9A+"PL% J%;K?;:#3NW+ES<'#0[7;G1S:$ MD.%P>/OV;='L-1J-<_1-*>WW^^^]]YYMVX/!H-/I"%DRNX,@"-KM]JU;MU*I M5+/9[/?[][?.ON^?GIX"0!B&@X&ULO$SEP-?Q8 8LW_I*][6YT>C<>06-F_^ M,2BP1(ZB82T"D! '!!CN,B%P"@BDD'8N__8[A=]_??_UD+YQ0[D%".(X&/?[ MA85MT '[K+[Y!R>7O_#..V^[I[G/XZ\;:C3KRBB[J\K$;*]8+&8RF5JMELOE M**6B*8O%8N/Q>#P>BXGFO:$20HA2*GC=<1Q*J2S+DB112@>#@6C\+<OJ]7J] M7F\V(A'FNFZSV8RBZ/;MVY32T6@T& SFYYR.XS2;S3 ,=W9VPC \MX/XRN'A MX60R$>S4[7:GT^G]<3V=3IO-IN>ZPWYOK]&/AZW+$D2*_I-3=?#*OXR'?6_D MK6 ]SCT#^1+SYUL4A( 00@D%#J# CQOPSM&WVQVK)V%0 3@@A(COJ-0&#B## MOQ\K5N-5Y@V/1FHSD5[7>F?>< 0HBJ)>KQ>&X60R*10*J51*"%#19XK67^!F MV_;\A%T6%RX0&8U&9_KI+M91%/F^+R9JYR;WHE()[&9I+L9&\PLZG<Y,[HC= M9@PC9&8419U.1SP)P]#W_0L9QO?]3K=KVV/4'G]FH0\Z*-Q[]XWOO^TM^]ZT MPHZ-)1\ 0H8I1^K9&SH. ( ((7=["@R[^T?ONL0-2%3P11N#$0\)V!$6"Z:' M/_F/T=,J(FDTRJ5MX/>:'7[F]G X%.,]T1D*F<\8$P,U8><&Z_+LS&+X(#3Y M[%V#&&;/QD;G#D\I%9O" ^R1"T2/ ^_/**539^K:9!J?@@E X7/PJC5Z-J+L M=XNW,.: X-A/#",M(8>,WX,& -C9Y=F./1@-"4<T>Z^BA%RZXZ9?R'0YA=]) MOQ&YDUX8^ZWB05;S@0%"9R_,SC84;SE\WQ^/Q^*E#YS5F',MS'F@9S8K37.I MA^")OC_^*4/ *.., P+"<3%F__E3/^ ,$ ;@ A>[:P2CC%P"0$@D! @$/P M@ '8O?#$" !($ ,4R^UUO^M?QQ-A;D).^/GOIOP# -%2^2=84\1,J*&?U\ MK#P(J O>@J.?MB<-[0.,@XS8R_4K>TY*H.P1^6N'S_SGL&Q*4<0E*XQ- K4? MQA@@0'P2*5-?&05:R"0$' $X1'8"Q?5EFR@:I@T__J7;/__V,#\.-"^2WQGD M_FI_*^08'@7 ^X3KX_R?2ACVI\GO=%^\GK PX@T_WO#BAD0XP"C2_G#G10#@ M #Z58YC^S<FS7T,< (F*9C)P/ZU\]0KO34$$#&L89I1 LK1WYX\DU+"8S>Q M[R6O)ZR4$@('C\F$HT=.MQ]N'TN@S^0A:!+SJ/SF>(%QI&!FRA'C@BK (<K= MZT < #PJSW]$ "&7 B(!@(1XQ/&*;G_YZ1\S@L9$>[FUL6I,?K-X(*YSSTV[ M5$G(H=C\$P3T7<(5P $WI8@!XAS- R&AV75<\!$ $'!\MCR&Z5MV_OO=RB\O M--)R\,5+;P$ YP <]B>I;YU>BF'"/P+*\/$#FH.$>,.+[XXRA".;*!AQRA&_ MCTKY0S^>>RBRX"N'6V].%FZD.BDY!( )47:<[&O]98<H*J;\D6S]4/L?<4+# 1JW)7%&T 245.1*Y"8(*D UUENC close W; } }
Smokeping.pm - SmokePing Perl Module
Almost all SmokePing functionality sits in this Module. The programs smokeping and smokeping.cgi are merely figure heads allowing to hardcode some pathnames.
If you feel like documenting what is happening within this library you are most welcome todo so.
Copyright (c) 2001 by Tobias Oetiker. All right reserved.
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.
Tobias Oetiker <tobi@oetiker.ch>
Niko Tyni <ntyni@iki.fi>