import torrus 1.0.9
[freeside.git] / torrus / perllib / Torrus / Monitor.pm
1 #  Copyright (C) 2002  Stanislav Sinyagin
2 #
3 #  This program is free software; you can redistribute it and/or modify
4 #  it under the terms of the GNU General Public License as published by
5 #  the Free Software Foundation; either version 2 of the License, or
6 #  (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software
15 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
16
17 # $Id: Monitor.pm,v 1.1 2010-12-27 00:03:37 ivan Exp $
18 # Stanislav Sinyagin <ssinyagin@yahoo.com>
19
20 package Torrus::Monitor;
21 @Torrus::Monitor::ISA = qw(Torrus::Scheduler::PeriodicTask);
22
23 use strict;
24
25 use Torrus::DB;
26 use Torrus::ConfigTree;
27 use Torrus::Scheduler;
28 use Torrus::DataAccess;
29 use Torrus::TimeStamp;
30 use Torrus::Log;
31
32
33 sub new
34 {
35     my $proto = shift;
36     my %options = @_;
37
38     if( not $options{'-Name'} )
39     {
40         $options{'-Name'} = "Monitor";
41     }
42
43     my $class = ref($proto) || $proto;
44     my $self  = $class->SUPER::new( %options );
45     bless $self, $class;
46
47
48     $self->{'tree_name'} = $options{'-TreeName'};
49     $self->{'sched_data'} = $options{'-SchedData'};
50     $self->{'delay'} = $options{'-Delay'} * 60;
51     
52     return $self;
53 }
54
55
56 sub addTarget
57 {
58     my $self = shift;
59     my $config_tree = shift;
60     my $token = shift;
61
62     if( not defined( $self->{'targets'} ) )
63     {
64         $self->{'targets'} = [];
65     }
66     push( @{$self->{'targets'}}, $token );
67 }
68
69
70
71
72 sub run
73 {
74     my $self = shift;
75     
76     my $config_tree =
77         new Torrus::ConfigTree( -TreeName => $self->{'tree_name'},
78                                 -Wait => 1 );
79     if( not defined( $config_tree ) )
80     {
81         return;
82     }
83
84     my $da = new Torrus::DataAccess;
85     
86     $self->{'db_alarms'} = new Torrus::DB('monitor_alarms',
87                                           -Subdir => $self->{'tree_name'},
88                                           -WriteAccess => 1);
89
90     foreach my $token ( @{$self->{'targets'}} )
91     {
92         &Torrus::DB::checkInterrupted();
93         
94         my $mlist = $self->{'sched_data'}{'mlist'}{$token};
95         
96         foreach my $mname ( @{$mlist} )
97         {
98             my $obj = { 'token' => $token, 'mname' => $mname };
99
100             $obj->{'da'} = $da;
101             
102             my $mtype = $config_tree->getParam($mname, 'monitor-type');
103             $obj->{'mtype'} = $mtype;
104             
105             my $method = 'check_' . $mtype;
106             my( $alarm, $timestamp ) = $self->$method( $config_tree, $obj );
107             $obj->{'alarm'} = $alarm;
108             $obj->{'timestamp'} = $timestamp;
109             
110             Debug("Monitor $mname returned ($alarm, $timestamp) ".
111                   "for token $token");
112             
113             $self->setAlarm( $config_tree, $obj );
114             undef $obj;
115         }
116     }
117
118     $self->cleanupExpired();
119     
120     undef $self->{'db_alarms'};
121 }
122
123
124 sub check_failures
125 {
126     my $self = shift;
127     my $config_tree = shift;
128     my $obj = shift;
129
130     my $token = $obj->{'token'};
131     my $file = $config_tree->getNodeParam( $token, 'data-file' );
132     my $dir = $config_tree->getNodeParam( $token, 'data-dir' );
133     my $ds = $config_tree->getNodeParam( $token, 'rrd-ds' );
134
135     my ($value, $timestamp) = $obj->{'da'}->read_RRD_DS( $dir.'/'.$file,
136                                                          'FAILURES', $ds );
137     return( $value > 0 ? 1:0, $timestamp );
138
139 }
140
141
142 sub check_expression
143 {
144     my $self = shift;
145     my $config_tree = shift;
146     my $obj = shift;
147
148     my $token = $obj->{'token'};
149     my $mname = $obj->{'mname'};
150
151     my ($value, $timestamp) = $obj->{'da'}->read( $config_tree, $token );
152     $value = 'UNKN' unless defined($value);
153     
154     my $expr = $value . ',' . $config_tree->getParam($mname,'rpn-expr');
155     $expr = $self->substitute_vars( $config_tree, $obj, $expr );
156
157     my $display_expr = $config_tree->getParam($mname,'display-rpn-expr');
158     if( defined( $display_expr ) )
159     {
160         $display_expr =
161             $self->substitute_vars( $config_tree, $obj,
162                                     $value . ',' . $display_expr );
163         my ($dv, $dt) = $obj->{'da'}->read_RPN( $config_tree, $token,
164                                                 $display_expr, $timestamp );
165         $obj->{'display_value'} = $dv;
166     }
167     else
168     {
169         $obj->{'display_value'} = $value;
170     }
171     
172     return $obj->{'da'}->read_RPN( $config_tree, $token, $expr, $timestamp );
173 }
174
175
176 sub substitute_vars
177 {
178     my $self = shift;
179     my $config_tree = shift;
180     my $obj = shift;
181     my $expr = shift;
182     
183     my $token = $obj->{'token'};
184     my $mname = $obj->{'mname'};
185
186     if( index( $expr, '#' ) >= 0 )
187     {
188         my $vars;
189         if( exists( $self->{'varscache'}{$token} ) )
190         {
191             $vars = $self->{'varscache'}{$token};
192         }
193         else
194         {
195             my $varstring =
196                 $config_tree->getNodeParam( $token, 'monitor-vars' );
197             foreach my $pair ( split( '\s*;\s*', $varstring ) )
198             {
199                 my( $var, $value ) = split( '\s*\=\s*', $pair );
200                 $vars->{$var} = $value;
201             }
202             $self->{'varscache'}{$token} = $vars;
203         }
204
205         my $ok = 1;
206         while( index( $expr, '#' ) >= 0 and $ok )
207         {
208             if( not $expr =~ /\#(\w+)/ )
209             {
210                 Error("Error in monitor expression: $expr for monitor $mname");
211                 $ok = 0;
212             }
213             else
214             {
215                 my $var = $1;
216                 my $val = $vars->{$var};
217                 if( not defined $val )
218                 {
219                     Error("Unknown variable $var in monitor $mname");
220                     $ok = 0;
221                 }
222                 else
223                 {
224                     $expr =~ s/\#$var/$val$1/g;
225                 }
226             }
227         }
228
229     }
230
231     return $expr;
232 }
233     
234
235
236 sub setAlarm
237 {
238     my $self = shift;
239     my $config_tree = shift;
240     my $obj = shift;
241
242     my $token = $obj->{'token'};
243     my $mname = $obj->{'mname'};
244     my $alarm = $obj->{'alarm'};
245     my $timestamp = $obj->{'timestamp'};
246
247     my $key = $mname . ':' . $config_tree->path($token);
248     
249     my $prev_values = $self->{'db_alarms'}->get( $key );
250     my ($t_set, $t_expires, $prev_status, $t_last_change);
251     if( defined($prev_values) )
252     {
253         Debug("Previous state found, Alarm: $alarm, ".
254               "Token: $token, Monitor: $mname");
255         ($t_set, $t_expires, $prev_status, $t_last_change) =
256             split(':', $prev_values);
257     }
258
259     my $event;
260
261     $t_last_change = time();
262     
263     if( $alarm )
264     {
265         if( not $prev_status )
266         {
267             $t_set = $timestamp;
268             $event = 'set';
269         }
270         else
271         {
272             $event = 'repeat';
273         }
274     }
275     else
276     {
277         if( $prev_status )
278         {
279             $t_expires = $t_last_change +
280                 $config_tree->getParam($mname, 'expires');
281             $event = 'clear';
282         }
283         else
284         {
285             if( defined($t_expires) and time() > $t_expires )
286             {
287                 $self->{'db_alarms'}->del( $key );
288                 $event = 'forget';
289             }
290         }
291     }
292
293     if( $event )
294     {
295         Debug("Event: $event, Monitor: $mname, Token: $token");
296         $obj->{'event'} = $event;
297         
298         my $action_token = $token;
299         
300         my $action_target =
301             $config_tree->getNodeParam($token, 'monitor-action-target');
302         if( defined( $action_target ) )
303         {
304             Debug('Action target redirected to ' . $action_target);
305             $action_token = $config_tree->getRelative($token, $action_target);
306             Debug('Redirected to token ' . $action_token);
307         }
308         $obj->{'action_token'} = $action_token;
309
310         foreach my $aname (split(',',
311                                  $config_tree->getParam($mname, 'action')))
312         {
313             &Torrus::DB::checkInterrupted();
314             
315             Debug("Running action: $aname");
316             my $method = 'run_event_' .
317                 $config_tree->getParam($aname, 'action-type');
318             $self->$method( $config_tree, $aname, $obj );
319         }
320
321         if( $event ne 'forget' )
322         {
323             $self->{'db_alarms'}->put( $key,
324                                        join(':', ($t_set,
325                                                   $t_expires,
326                                                   ($alarm ? 1:0),
327                                                   $t_last_change)) );
328         }
329     }
330 }
331
332
333 # If an alarm is no longer in ConfigTree, it is not cleaned by setAlarm.
334 # We clean them up explicitly after they expire
335
336 sub cleanupExpired
337 {
338     my $self = shift;
339
340     &Torrus::DB::checkInterrupted();
341     
342     my $cursor = $self->{'db_alarms'}->cursor(-Write => 1);
343     while( my ($key, $timers) = $self->{'db_alarms'}->next($cursor) )
344     {
345         my ($t_set, $t_expires, $prev_status, $t_last_change) =
346             split(':', $timers);
347         
348         if( $t_last_change and
349             time() > ( $t_last_change + $Torrus::Monitor::alarmTimeout ) and
350             ( (not $t_expires) or (time() > $t_expires) ) )
351         {            
352             my ($mname, $path) = split(':', $key);
353             
354             Info('Cleaned up an orphaned alarm: monitor=' . $mname .
355                  ', path=' . $path);
356             $self->{'db_alarms'}->c_del( $cursor );            
357         }
358     }
359     undef $cursor;
360     
361     &Torrus::DB::checkInterrupted();
362 }
363     
364
365
366     
367
368 sub run_event_tset
369 {
370     my $self = shift;
371     my $config_tree = shift;
372     my $aname = shift;
373     my $obj = shift;
374
375     my $token = $obj->{'action_token'};
376     my $event = $obj->{'event'};
377     
378     if( $event eq 'set' or $event eq 'forget' )
379     {
380         my $tset = 'S'.$config_tree->getParam($aname, 'tset-name');
381
382         if( $event eq 'set' )
383         {
384             $config_tree->tsetAddMember($tset, $token, 'monitor');
385         }
386         else
387         {
388             $config_tree->tsetDelMember($tset, $token);
389         }
390     }
391 }
392
393
394 sub run_event_exec
395 {
396     my $self = shift;
397     my $config_tree = shift;
398     my $aname = shift;
399     my $obj = shift;
400
401     my $token = $obj->{'action_token'};
402     my $event = $obj->{'event'};
403     my $mname = $obj->{'mname'};
404     my $timestamp = $obj->{'timestamp'};
405
406     my $launch_when = $config_tree->getParam($aname, 'launch-when');
407     if( not defined $launch_when )
408     {
409         $launch_when = 'set';
410     }
411
412     if( grep {$event eq $_} split(',', $launch_when) )
413     {
414         my $cmd = $config_tree->getParam($aname, 'command');
415         $cmd =~ s/\&gt\;/\>/;
416         $cmd =~ s/\&lt\;/\</;
417
418         $ENV{'TORRUS_BIN'}       = $Torrus::Global::pkgbindir;
419         $ENV{'TORRUS_UPTIME'}    = time() - $self->whenStarted();
420
421         $ENV{'TORRUS_TREE'}      = $config_tree->treeName();
422         $ENV{'TORRUS_TOKEN'}     = $token;
423         $ENV{'TORRUS_NODEPATH'}  = $config_tree->path( $token );
424
425         my $nick =
426             $config_tree->getNodeParam( $token, 'descriptive-nickname' );
427         if( not defined( $nick ) )
428         {
429             $nick = $ENV{'TORRUS_NODEPATH'};
430         }
431         $ENV{'TORRUS_NICKNAME'} = $nick;
432         
433         $ENV{'TORRUS_NCOMMENT'}  =
434             $config_tree->getNodeParam( $token, 'comment', 1 );
435         $ENV{'TORRUS_NPCOMMENT'} =
436             $config_tree->getNodeParam( $config_tree->getParent( $token ),
437                                         'comment', 1 );
438         $ENV{'TORRUS_EVENT'}     = $event;
439         $ENV{'TORRUS_MONITOR'}   = $mname;
440         $ENV{'TORRUS_MCOMMENT'}  = $config_tree->getParam($mname, 'comment');
441         $ENV{'TORRUS_TSTAMP'}    = $timestamp;
442
443         if( defined( $obj->{'display_value'} ) )
444         {
445             $ENV{'TORRUS_VALUE'} = $obj->{'display_value'};
446
447             my $format = $config_tree->getParam($mname, 'display-format');
448             if( not defined( $format ) )
449             {
450                 $format = '%.2f';
451             }
452
453             $ENV{'TORRUS_DISPLAY_VALUE'} =
454                 sprintf( $format, $obj->{'display_value'} );
455         }
456
457         my $severity = $config_tree->getParam($mname, 'severity');
458         if( defined( $severity ) )
459         {
460             $ENV{'TORRUS_SEVERITY'} = $severity;
461         }
462         
463         my $setenv_params =
464             $config_tree->getParam($aname, 'setenv-params');
465
466         if( defined( $setenv_params ) )
467         {
468             foreach my $param ( split( ',', $setenv_params ) )
469             {
470                 # We retrieve the param from the monitored token, not
471                 # from action-token
472                 my $value = $config_tree->getNodeParam( $obj->{'token'},
473                                                         $param );
474                 if( not defined $value )
475                 {
476                     Warn('Parameter ' . $param . ' referenced in action '.
477                          $aname . ', but not defined for ' .
478                          $config_tree->path($obj->{'token'}));
479                     $value = '';
480                 }
481                 $param =~ s/\W/_/g;
482                 my $envName = 'TORRUS_P_'.$param;
483                 Debug("Setting environment $envName to $value");
484                 $ENV{$envName} = $value;
485             }
486         }
487
488         my $setenv_dataexpr =
489             $config_tree->getParam($aname, 'setenv-dataexpr');
490
491         if( defined( $setenv_dataexpr ) )
492         {
493             # <param name="setenv_dataexpr" value="ENV1=expr1, ENV2=expr2"/>
494             # Integrity checks are done at compilation time.
495             foreach my $pair ( split( ',', $setenv_dataexpr ) )
496             {
497                 my ($env, $param) = split( '=', $pair );
498                 my $expr = $config_tree->getParam($aname, $param);
499                 my ($value, $timestamp) =
500                     $obj->{'da'}->read_RPN( $config_tree, $token, $expr );
501                 my $envName = 'TORRUS_'.$env;
502                 Debug("Setting environment $envName to $value");
503                 $ENV{$envName} = $value;
504             }
505         }
506
507         Debug("Going to run command: $cmd");
508         my $status = system($cmd);
509         if( $status != 0 )
510         {
511             Error("$cmd executed with error: $!");
512         }
513
514         # Clean up the environment
515         foreach my $envName ( keys %ENV )
516         {
517             if( $envName =~ /^TORRUS_/ )
518             {
519                 delete $ENV{$envName};
520             }
521         }
522     }
523 }
524
525
526
527 #######  Monitor scheduler  ########
528
529 package Torrus::MonitorScheduler;
530 @Torrus::MonitorScheduler::ISA = qw(Torrus::Scheduler);
531
532 use Torrus::ConfigTree;
533 use Torrus::Log;
534 use Torrus::Scheduler;
535 use Torrus::TimeStamp;
536
537 sub beforeRun
538 {
539     my $self = shift;
540
541     my $tree = $self->treeName();
542     my $config_tree = new Torrus::ConfigTree(-TreeName => $tree, -Wait => 1);
543     if( not defined( $config_tree ) )
544     {
545         return undef;
546     }
547
548     my $data = $self->data();
549
550     # Prepare the list of tokens, sorted by period and offset,
551     # from config tree or from cache.
552
553     my $need_new_tasks = 0;
554
555     Torrus::TimeStamp::init();
556     my $known_ts = Torrus::TimeStamp::get($tree . ':monitor_cache');
557     my $actual_ts = $config_tree->getTimestamp();
558     if( $actual_ts >= $known_ts )
559     {
560         if( $self->{'delay'} > 0 )
561         {
562             Info(sprintf('Delaying for %d seconds', $self->{'delay'}));
563             sleep( $self->{'delay'} );
564         }
565
566         Info("Rebuilding monitor cache");
567         Debug("Config TS: $actual_ts, Monitor TS: $known_ts");
568
569         undef $data->{'targets'};
570         $need_new_tasks = 1;
571
572         $data->{'db_tokens'} = new Torrus::DB( 'monitor_tokens',
573                                                -Subdir => $tree,
574                                                -WriteAccess => 1,
575                                                -Truncate    => 1 );
576         $self->cacheMonitors( $config_tree, $config_tree->token('/') );
577         # explicitly close, since we don't need it often, and sometimes
578         # open it in read-only mode
579         $data->{'db_tokens'}->closeNow();
580         undef $data->{'db_tokens'};
581
582         # Set the timestamp
583         &Torrus::TimeStamp::setNow($tree . ':monitor_cache');
584     }
585     Torrus::TimeStamp::release();
586
587     &Torrus::DB::checkInterrupted();
588
589     if( not $need_new_tasks and not defined $data->{'targets'} )
590     {
591         $need_new_tasks = 1;
592
593         $data->{'db_tokens'} = new Torrus::DB('monitor_tokens',
594                                               -Subdir => $tree);
595         my $cursor = $data->{'db_tokens'}->cursor();
596         while( my ($token, $schedule) = $data->{'db_tokens'}->next($cursor) )
597         {
598             my ($period, $offset, $mlist) = split(':', $schedule);
599             if( not exists( $data->{'targets'}{$period}{$offset} ) )
600             {
601                 $data->{'targets'}{$period}{$offset} = [];
602             }
603             push( @{$data->{'targets'}{$period}{$offset}}, $token );
604             $data->{'mlist'}{$token} = [];
605             push( @{$data->{'mlist'}{$token}}, split(',', $mlist) );
606         }
607         undef $cursor;
608         $data->{'db_tokens'}->closeNow();
609         undef $data->{'db_tokens'};
610     }
611
612     &Torrus::DB::checkInterrupted();
613
614     # Now fill in Scheduler's task list, if needed
615
616     if( $need_new_tasks )
617     {
618         Verbose("Initializing tasks");
619         my $init_start = time();
620         $self->flushTasks();
621
622         foreach my $period ( keys %{$data->{'targets'}} )
623         {
624             foreach my $offset ( keys %{$data->{'targets'}{$period}} )
625             {
626                 my $monitor = new Torrus::Monitor( -Period => $period,
627                                                    -Offset => $offset,
628                                                    -TreeName => $tree,
629                                                    -SchedData => $data );
630
631                 foreach my $token ( @{$data->{'targets'}{$period}{$offset}} )
632                 {
633                     &Torrus::DB::checkInterrupted();
634                     
635                     $monitor->addTarget( $config_tree, $token );
636                 }
637
638                 $self->addTask( $monitor );
639             }
640         }
641         Verbose(sprintf("Tasks initialization finished in %d seconds",
642                         time() - $init_start));
643     }
644
645     Verbose("Monitor initialized");
646
647     return 1;
648 }
649
650
651 sub cacheMonitors
652 {
653     my $self = shift;
654     my $config_tree = shift;
655     my $ptoken = shift;
656
657     my $data = $self->data();
658
659     foreach my $ctoken ( $config_tree->getChildren( $ptoken ) )
660     {
661         &Torrus::DB::checkInterrupted();
662
663         if( $config_tree->isSubtree( $ctoken ) )
664         {
665             $self->cacheMonitors( $config_tree, $ctoken );
666         }
667         elsif( $config_tree->isLeaf( $ctoken ) and
668                ( $config_tree->getNodeParam($ctoken, 'ds-type') ne
669                  'rrd-multigraph') )
670         {
671             my $mlist = $config_tree->getNodeParam( $ctoken, 'monitor' );
672             if( defined $mlist )
673             {
674                 my $period = sprintf('%d',
675                                      $config_tree->getNodeParam
676                                      ( $ctoken, 'monitor-period' ) );
677                 my $offset = sprintf('%d',
678                                      $config_tree->getNodeParam
679                                      ( $ctoken, 'monitor-timeoffset' ) );
680                 
681                 $data->{'db_tokens'}->put( $ctoken,
682                                            $period.':'.$offset.':'.$mlist );
683                 
684                 push( @{$data->{'targets'}{$period}{$offset}}, $ctoken );
685                 $data->{'mlist'}{$ctoken} = [];
686                 push( @{$data->{'mlist'}{$ctoken}}, split(',', $mlist) );
687             }
688         }
689     }
690 }
691
692
693 1;
694
695
696 # Local Variables:
697 # mode: perl
698 # indent-tabs-mode: nil
699 # perl-indent-level: 4
700 # End: