summaryrefslogtreecommitdiff
path: root/torrus/perllib/Torrus/Monitor.pm
diff options
context:
space:
mode:
Diffstat (limited to 'torrus/perllib/Torrus/Monitor.pm')
-rw-r--r--torrus/perllib/Torrus/Monitor.pm700
1 files changed, 700 insertions, 0 deletions
diff --git a/torrus/perllib/Torrus/Monitor.pm b/torrus/perllib/Torrus/Monitor.pm
new file mode 100644
index 000000000..72e5c2433
--- /dev/null
+++ b/torrus/perllib/Torrus/Monitor.pm
@@ -0,0 +1,700 @@
+# Copyright (C) 2002 Stanislav Sinyagin
+#
+# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+
+# $Id: Monitor.pm,v 1.1 2010-12-27 00:03:37 ivan Exp $
+# Stanislav Sinyagin <ssinyagin@yahoo.com>
+
+package Torrus::Monitor;
+@Torrus::Monitor::ISA = qw(Torrus::Scheduler::PeriodicTask);
+
+use strict;
+
+use Torrus::DB;
+use Torrus::ConfigTree;
+use Torrus::Scheduler;
+use Torrus::DataAccess;
+use Torrus::TimeStamp;
+use Torrus::Log;
+
+
+sub new
+{
+ my $proto = shift;
+ my %options = @_;
+
+ if( not $options{'-Name'} )
+ {
+ $options{'-Name'} = "Monitor";
+ }
+
+ my $class = ref($proto) || $proto;
+ my $self = $class->SUPER::new( %options );
+ bless $self, $class;
+
+
+ $self->{'tree_name'} = $options{'-TreeName'};
+ $self->{'sched_data'} = $options{'-SchedData'};
+ $self->{'delay'} = $options{'-Delay'} * 60;
+
+ return $self;
+}
+
+
+sub addTarget
+{
+ my $self = shift;
+ my $config_tree = shift;
+ my $token = shift;
+
+ if( not defined( $self->{'targets'} ) )
+ {
+ $self->{'targets'} = [];
+ }
+ push( @{$self->{'targets'}}, $token );
+}
+
+
+
+
+sub run
+{
+ my $self = shift;
+
+ my $config_tree =
+ new Torrus::ConfigTree( -TreeName => $self->{'tree_name'},
+ -Wait => 1 );
+ if( not defined( $config_tree ) )
+ {
+ return;
+ }
+
+ my $da = new Torrus::DataAccess;
+
+ $self->{'db_alarms'} = new Torrus::DB('monitor_alarms',
+ -Subdir => $self->{'tree_name'},
+ -WriteAccess => 1);
+
+ foreach my $token ( @{$self->{'targets'}} )
+ {
+ &Torrus::DB::checkInterrupted();
+
+ my $mlist = $self->{'sched_data'}{'mlist'}{$token};
+
+ foreach my $mname ( @{$mlist} )
+ {
+ my $obj = { 'token' => $token, 'mname' => $mname };
+
+ $obj->{'da'} = $da;
+
+ my $mtype = $config_tree->getParam($mname, 'monitor-type');
+ $obj->{'mtype'} = $mtype;
+
+ my $method = 'check_' . $mtype;
+ my( $alarm, $timestamp ) = $self->$method( $config_tree, $obj );
+ $obj->{'alarm'} = $alarm;
+ $obj->{'timestamp'} = $timestamp;
+
+ Debug("Monitor $mname returned ($alarm, $timestamp) ".
+ "for token $token");
+
+ $self->setAlarm( $config_tree, $obj );
+ undef $obj;
+ }
+ }
+
+ $self->cleanupExpired();
+
+ undef $self->{'db_alarms'};
+}
+
+
+sub check_failures
+{
+ my $self = shift;
+ my $config_tree = shift;
+ my $obj = shift;
+
+ my $token = $obj->{'token'};
+ my $file = $config_tree->getNodeParam( $token, 'data-file' );
+ my $dir = $config_tree->getNodeParam( $token, 'data-dir' );
+ my $ds = $config_tree->getNodeParam( $token, 'rrd-ds' );
+
+ my ($value, $timestamp) = $obj->{'da'}->read_RRD_DS( $dir.'/'.$file,
+ 'FAILURES', $ds );
+ return( $value > 0 ? 1:0, $timestamp );
+
+}
+
+
+sub check_expression
+{
+ my $self = shift;
+ my $config_tree = shift;
+ my $obj = shift;
+
+ my $token = $obj->{'token'};
+ my $mname = $obj->{'mname'};
+
+ my ($value, $timestamp) = $obj->{'da'}->read( $config_tree, $token );
+ $value = 'UNKN' unless defined($value);
+
+ my $expr = $value . ',' . $config_tree->getParam($mname,'rpn-expr');
+ $expr = $self->substitute_vars( $config_tree, $obj, $expr );
+
+ my $display_expr = $config_tree->getParam($mname,'display-rpn-expr');
+ if( defined( $display_expr ) )
+ {
+ $display_expr =
+ $self->substitute_vars( $config_tree, $obj,
+ $value . ',' . $display_expr );
+ my ($dv, $dt) = $obj->{'da'}->read_RPN( $config_tree, $token,
+ $display_expr, $timestamp );
+ $obj->{'display_value'} = $dv;
+ }
+ else
+ {
+ $obj->{'display_value'} = $value;
+ }
+
+ return $obj->{'da'}->read_RPN( $config_tree, $token, $expr, $timestamp );
+}
+
+
+sub substitute_vars
+{
+ my $self = shift;
+ my $config_tree = shift;
+ my $obj = shift;
+ my $expr = shift;
+
+ my $token = $obj->{'token'};
+ my $mname = $obj->{'mname'};
+
+ if( index( $expr, '#' ) >= 0 )
+ {
+ my $vars;
+ if( exists( $self->{'varscache'}{$token} ) )
+ {
+ $vars = $self->{'varscache'}{$token};
+ }
+ else
+ {
+ my $varstring =
+ $config_tree->getNodeParam( $token, 'monitor-vars' );
+ foreach my $pair ( split( '\s*;\s*', $varstring ) )
+ {
+ my( $var, $value ) = split( '\s*\=\s*', $pair );
+ $vars->{$var} = $value;
+ }
+ $self->{'varscache'}{$token} = $vars;
+ }
+
+ my $ok = 1;
+ while( index( $expr, '#' ) >= 0 and $ok )
+ {
+ if( not $expr =~ /\#(\w+)/ )
+ {
+ Error("Error in monitor expression: $expr for monitor $mname");
+ $ok = 0;
+ }
+ else
+ {
+ my $var = $1;
+ my $val = $vars->{$var};
+ if( not defined $val )
+ {
+ Error("Unknown variable $var in monitor $mname");
+ $ok = 0;
+ }
+ else
+ {
+ $expr =~ s/\#$var/$val$1/g;
+ }
+ }
+ }
+
+ }
+
+ return $expr;
+}
+
+
+
+sub setAlarm
+{
+ my $self = shift;
+ my $config_tree = shift;
+ my $obj = shift;
+
+ my $token = $obj->{'token'};
+ my $mname = $obj->{'mname'};
+ my $alarm = $obj->{'alarm'};
+ my $timestamp = $obj->{'timestamp'};
+
+ my $key = $mname . ':' . $config_tree->path($token);
+
+ my $prev_values = $self->{'db_alarms'}->get( $key );
+ my ($t_set, $t_expires, $prev_status, $t_last_change);
+ if( defined($prev_values) )
+ {
+ Debug("Previous state found, Alarm: $alarm, ".
+ "Token: $token, Monitor: $mname");
+ ($t_set, $t_expires, $prev_status, $t_last_change) =
+ split(':', $prev_values);
+ }
+
+ my $event;
+
+ $t_last_change = time();
+
+ if( $alarm )
+ {
+ if( not $prev_status )
+ {
+ $t_set = $timestamp;
+ $event = 'set';
+ }
+ else
+ {
+ $event = 'repeat';
+ }
+ }
+ else
+ {
+ if( $prev_status )
+ {
+ $t_expires = $t_last_change +
+ $config_tree->getParam($mname, 'expires');
+ $event = 'clear';
+ }
+ else
+ {
+ if( defined($t_expires) and time() > $t_expires )
+ {
+ $self->{'db_alarms'}->del( $key );
+ $event = 'forget';
+ }
+ }
+ }
+
+ if( $event )
+ {
+ Debug("Event: $event, Monitor: $mname, Token: $token");
+ $obj->{'event'} = $event;
+
+ my $action_token = $token;
+
+ my $action_target =
+ $config_tree->getNodeParam($token, 'monitor-action-target');
+ if( defined( $action_target ) )
+ {
+ Debug('Action target redirected to ' . $action_target);
+ $action_token = $config_tree->getRelative($token, $action_target);
+ Debug('Redirected to token ' . $action_token);
+ }
+ $obj->{'action_token'} = $action_token;
+
+ foreach my $aname (split(',',
+ $config_tree->getParam($mname, 'action')))
+ {
+ &Torrus::DB::checkInterrupted();
+
+ Debug("Running action: $aname");
+ my $method = 'run_event_' .
+ $config_tree->getParam($aname, 'action-type');
+ $self->$method( $config_tree, $aname, $obj );
+ }
+
+ if( $event ne 'forget' )
+ {
+ $self->{'db_alarms'}->put( $key,
+ join(':', ($t_set,
+ $t_expires,
+ ($alarm ? 1:0),
+ $t_last_change)) );
+ }
+ }
+}
+
+
+# If an alarm is no longer in ConfigTree, it is not cleaned by setAlarm.
+# We clean them up explicitly after they expire
+
+sub cleanupExpired
+{
+ my $self = shift;
+
+ &Torrus::DB::checkInterrupted();
+
+ my $cursor = $self->{'db_alarms'}->cursor(-Write => 1);
+ while( my ($key, $timers) = $self->{'db_alarms'}->next($cursor) )
+ {
+ my ($t_set, $t_expires, $prev_status, $t_last_change) =
+ split(':', $timers);
+
+ if( $t_last_change and
+ time() > ( $t_last_change + $Torrus::Monitor::alarmTimeout ) and
+ ( (not $t_expires) or (time() > $t_expires) ) )
+ {
+ my ($mname, $path) = split(':', $key);
+
+ Info('Cleaned up an orphaned alarm: monitor=' . $mname .
+ ', path=' . $path);
+ $self->{'db_alarms'}->c_del( $cursor );
+ }
+ }
+ undef $cursor;
+
+ &Torrus::DB::checkInterrupted();
+}
+
+
+
+
+
+sub run_event_tset
+{
+ my $self = shift;
+ my $config_tree = shift;
+ my $aname = shift;
+ my $obj = shift;
+
+ my $token = $obj->{'action_token'};
+ my $event = $obj->{'event'};
+
+ if( $event eq 'set' or $event eq 'forget' )
+ {
+ my $tset = 'S'.$config_tree->getParam($aname, 'tset-name');
+
+ if( $event eq 'set' )
+ {
+ $config_tree->tsetAddMember($tset, $token, 'monitor');
+ }
+ else
+ {
+ $config_tree->tsetDelMember($tset, $token);
+ }
+ }
+}
+
+
+sub run_event_exec
+{
+ my $self = shift;
+ my $config_tree = shift;
+ my $aname = shift;
+ my $obj = shift;
+
+ my $token = $obj->{'action_token'};
+ my $event = $obj->{'event'};
+ my $mname = $obj->{'mname'};
+ my $timestamp = $obj->{'timestamp'};
+
+ my $launch_when = $config_tree->getParam($aname, 'launch-when');
+ if( not defined $launch_when )
+ {
+ $launch_when = 'set';
+ }
+
+ if( grep {$event eq $_} split(',', $launch_when) )
+ {
+ my $cmd = $config_tree->getParam($aname, 'command');
+ $cmd =~ s/\&gt\;/\>/;
+ $cmd =~ s/\&lt\;/\</;
+
+ $ENV{'TORRUS_BIN'} = $Torrus::Global::pkgbindir;
+ $ENV{'TORRUS_UPTIME'} = time() - $self->whenStarted();
+
+ $ENV{'TORRUS_TREE'} = $config_tree->treeName();
+ $ENV{'TORRUS_TOKEN'} = $token;
+ $ENV{'TORRUS_NODEPATH'} = $config_tree->path( $token );
+
+ my $nick =
+ $config_tree->getNodeParam( $token, 'descriptive-nickname' );
+ if( not defined( $nick ) )
+ {
+ $nick = $ENV{'TORRUS_NODEPATH'};
+ }
+ $ENV{'TORRUS_NICKNAME'} = $nick;
+
+ $ENV{'TORRUS_NCOMMENT'} =
+ $config_tree->getNodeParam( $token, 'comment', 1 );
+ $ENV{'TORRUS_NPCOMMENT'} =
+ $config_tree->getNodeParam( $config_tree->getParent( $token ),
+ 'comment', 1 );
+ $ENV{'TORRUS_EVENT'} = $event;
+ $ENV{'TORRUS_MONITOR'} = $mname;
+ $ENV{'TORRUS_MCOMMENT'} = $config_tree->getParam($mname, 'comment');
+ $ENV{'TORRUS_TSTAMP'} = $timestamp;
+
+ if( defined( $obj->{'display_value'} ) )
+ {
+ $ENV{'TORRUS_VALUE'} = $obj->{'display_value'};
+
+ my $format = $config_tree->getParam($mname, 'display-format');
+ if( not defined( $format ) )
+ {
+ $format = '%.2f';
+ }
+
+ $ENV{'TORRUS_DISPLAY_VALUE'} =
+ sprintf( $format, $obj->{'display_value'} );
+ }
+
+ my $severity = $config_tree->getParam($mname, 'severity');
+ if( defined( $severity ) )
+ {
+ $ENV{'TORRUS_SEVERITY'} = $severity;
+ }
+
+ my $setenv_params =
+ $config_tree->getParam($aname, 'setenv-params');
+
+ if( defined( $setenv_params ) )
+ {
+ foreach my $param ( split( ',', $setenv_params ) )
+ {
+ # We retrieve the param from the monitored token, not
+ # from action-token
+ my $value = $config_tree->getNodeParam( $obj->{'token'},
+ $param );
+ if( not defined $value )
+ {
+ Warn('Parameter ' . $param . ' referenced in action '.
+ $aname . ', but not defined for ' .
+ $config_tree->path($obj->{'token'}));
+ $value = '';
+ }
+ $param =~ s/\W/_/g;
+ my $envName = 'TORRUS_P_'.$param;
+ Debug("Setting environment $envName to $value");
+ $ENV{$envName} = $value;
+ }
+ }
+
+ my $setenv_dataexpr =
+ $config_tree->getParam($aname, 'setenv-dataexpr');
+
+ if( defined( $setenv_dataexpr ) )
+ {
+ # <param name="setenv_dataexpr" value="ENV1=expr1, ENV2=expr2"/>
+ # Integrity checks are done at compilation time.
+ foreach my $pair ( split( ',', $setenv_dataexpr ) )
+ {
+ my ($env, $param) = split( '=', $pair );
+ my $expr = $config_tree->getParam($aname, $param);
+ my ($value, $timestamp) =
+ $obj->{'da'}->read_RPN( $config_tree, $token, $expr );
+ my $envName = 'TORRUS_'.$env;
+ Debug("Setting environment $envName to $value");
+ $ENV{$envName} = $value;
+ }
+ }
+
+ Debug("Going to run command: $cmd");
+ my $status = system($cmd);
+ if( $status != 0 )
+ {
+ Error("$cmd executed with error: $!");
+ }
+
+ # Clean up the environment
+ foreach my $envName ( keys %ENV )
+ {
+ if( $envName =~ /^TORRUS_/ )
+ {
+ delete $ENV{$envName};
+ }
+ }
+ }
+}
+
+
+
+####### Monitor scheduler ########
+
+package Torrus::MonitorScheduler;
+@Torrus::MonitorScheduler::ISA = qw(Torrus::Scheduler);
+
+use Torrus::ConfigTree;
+use Torrus::Log;
+use Torrus::Scheduler;
+use Torrus::TimeStamp;
+
+sub beforeRun
+{
+ my $self = shift;
+
+ my $tree = $self->treeName();
+ my $config_tree = new Torrus::ConfigTree(-TreeName => $tree, -Wait => 1);
+ if( not defined( $config_tree ) )
+ {
+ return undef;
+ }
+
+ my $data = $self->data();
+
+ # Prepare the list of tokens, sorted by period and offset,
+ # from config tree or from cache.
+
+ my $need_new_tasks = 0;
+
+ Torrus::TimeStamp::init();
+ my $known_ts = Torrus::TimeStamp::get($tree . ':monitor_cache');
+ my $actual_ts = $config_tree->getTimestamp();
+ if( $actual_ts >= $known_ts )
+ {
+ if( $self->{'delay'} > 0 )
+ {
+ Info(sprintf('Delaying for %d seconds', $self->{'delay'}));
+ sleep( $self->{'delay'} );
+ }
+
+ Info("Rebuilding monitor cache");
+ Debug("Config TS: $actual_ts, Monitor TS: $known_ts");
+
+ undef $data->{'targets'};
+ $need_new_tasks = 1;
+
+ $data->{'db_tokens'} = new Torrus::DB( 'monitor_tokens',
+ -Subdir => $tree,
+ -WriteAccess => 1,
+ -Truncate => 1 );
+ $self->cacheMonitors( $config_tree, $config_tree->token('/') );
+ # explicitly close, since we don't need it often, and sometimes
+ # open it in read-only mode
+ $data->{'db_tokens'}->closeNow();
+ undef $data->{'db_tokens'};
+
+ # Set the timestamp
+ &Torrus::TimeStamp::setNow($tree . ':monitor_cache');
+ }
+ Torrus::TimeStamp::release();
+
+ &Torrus::DB::checkInterrupted();
+
+ if( not $need_new_tasks and not defined $data->{'targets'} )
+ {
+ $need_new_tasks = 1;
+
+ $data->{'db_tokens'} = new Torrus::DB('monitor_tokens',
+ -Subdir => $tree);
+ my $cursor = $data->{'db_tokens'}->cursor();
+ while( my ($token, $schedule) = $data->{'db_tokens'}->next($cursor) )
+ {
+ my ($period, $offset, $mlist) = split(':', $schedule);
+ if( not exists( $data->{'targets'}{$period}{$offset} ) )
+ {
+ $data->{'targets'}{$period}{$offset} = [];
+ }
+ push( @{$data->{'targets'}{$period}{$offset}}, $token );
+ $data->{'mlist'}{$token} = [];
+ push( @{$data->{'mlist'}{$token}}, split(',', $mlist) );
+ }
+ undef $cursor;
+ $data->{'db_tokens'}->closeNow();
+ undef $data->{'db_tokens'};
+ }
+
+ &Torrus::DB::checkInterrupted();
+
+ # Now fill in Scheduler's task list, if needed
+
+ if( $need_new_tasks )
+ {
+ Verbose("Initializing tasks");
+ my $init_start = time();
+ $self->flushTasks();
+
+ foreach my $period ( keys %{$data->{'targets'}} )
+ {
+ foreach my $offset ( keys %{$data->{'targets'}{$period}} )
+ {
+ my $monitor = new Torrus::Monitor( -Period => $period,
+ -Offset => $offset,
+ -TreeName => $tree,
+ -SchedData => $data );
+
+ foreach my $token ( @{$data->{'targets'}{$period}{$offset}} )
+ {
+ &Torrus::DB::checkInterrupted();
+
+ $monitor->addTarget( $config_tree, $token );
+ }
+
+ $self->addTask( $monitor );
+ }
+ }
+ Verbose(sprintf("Tasks initialization finished in %d seconds",
+ time() - $init_start));
+ }
+
+ Verbose("Monitor initialized");
+
+ return 1;
+}
+
+
+sub cacheMonitors
+{
+ my $self = shift;
+ my $config_tree = shift;
+ my $ptoken = shift;
+
+ my $data = $self->data();
+
+ foreach my $ctoken ( $config_tree->getChildren( $ptoken ) )
+ {
+ &Torrus::DB::checkInterrupted();
+
+ if( $config_tree->isSubtree( $ctoken ) )
+ {
+ $self->cacheMonitors( $config_tree, $ctoken );
+ }
+ elsif( $config_tree->isLeaf( $ctoken ) and
+ ( $config_tree->getNodeParam($ctoken, 'ds-type') ne
+ 'rrd-multigraph') )
+ {
+ my $mlist = $config_tree->getNodeParam( $ctoken, 'monitor' );
+ if( defined $mlist )
+ {
+ my $period = sprintf('%d',
+ $config_tree->getNodeParam
+ ( $ctoken, 'monitor-period' ) );
+ my $offset = sprintf('%d',
+ $config_tree->getNodeParam
+ ( $ctoken, 'monitor-timeoffset' ) );
+
+ $data->{'db_tokens'}->put( $ctoken,
+ $period.':'.$offset.':'.$mlist );
+
+ push( @{$data->{'targets'}{$period}{$offset}}, $ctoken );
+ $data->{'mlist'}{$ctoken} = [];
+ push( @{$data->{'mlist'}{$ctoken}}, split(',', $mlist) );
+ }
+ }
+ }
+}
+
+
+1;
+
+
+# Local Variables:
+# mode: perl
+# indent-tabs-mode: nil
+# perl-indent-level: 4
+# End: