1# Cricket: a configuration, polling and data display wrapper for RRD files
2#
3#    Copyright (C) 1998 Javier Muniz and WebTV Networks, Inc.
4#
5#    This program is free software; you can redistribute it and/or modify
6#    it under the terms of the GNU General Public License as published by
7#    the Free Software Foundation; either version 2 of the License, or
8#    (at your option) any later version.
9#
10#    This program is distributed in the hope that it will be useful,
11#    but WITHOUT ANY WARRANTY; without even the implied warranty of
12#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#    GNU General Public License for more details.
14#
15#    You should have received a copy of the GNU General Public License
16#    along with this program; if not, write to the Free Software
17#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19package Monitor;
20
21use strict;
22use snmpUtils;
23use RRD::File;
24use Common::Log;
25use Common::Util;
26
27# Registered monitors
28$Common::global::gMonitorTable{'value'} = \&monValue;
29$Common::global::gMonitorTable{'hunt'} = \&monHunt;
30$Common::global::gMonitorTable{'relation'} = \&monRelation;
31$Common::global::gMonitorTable{'exact'} = \&monExact;
32# Support for aberrant behavior detection
33$Common::global::gMonitorTable{'failures'} = \&monFailures;
34$Common::global::gMonitorTable{'quotient'} = \&monQuotient;
35
36# NaN Alarm processing
37# Return value of nanErr = 1 is not an alarm or threshold pass
38# Return value of nanErr = 0 is an alarm or threshold failure
39my $nanErr = 1;
40if ($Common::global::gEnableNoValueAlarms) {
41    $nanErr = ($Common::global::gEnableNoValueAlarms) ? 0 : 1;
42}
43
44sub new {
45    my($package) = @_;
46    my $self = { };
47    bless $self, $package;
48    return $self;
49}
50
51sub monValue {
52    my($self,$target,$ds,$type,$args) = @_;
53    my($min,$max,$minOK,$maxOK);
54    my(@Thresholds) = split(/\s*:\s*/, $args);
55
56    my($value) = $self->rrdFetch(
57                                 $target->{'rrd-datafile'},
58                                 $self->getDSNum($target, $ds), 0
59                                 );
60
61    if (!defined($value)) {
62        Warn("Monitor: Couldn't fetch last $ds value from " .
63             $target->{'rrd-datafile'}.".");
64        return 1;
65    }
66
67    return ($nanErr, 'NaN') if isNaN($value);
68
69    $min = shift(@Thresholds);
70    $min = 'n' if (! defined($min));
71
72    if (lc($min) eq 'n') {
73        $minOK = 1;
74    } else {
75        $minOK = ($value > $min) ? 1 : 0;
76    }
77
78    $max = shift(@Thresholds);
79    $max = 'n' if (! defined($max));
80
81    if (lc($max) eq 'n') {
82        $maxOK = 1;
83    } else {
84        $maxOK = ($value < $max) ? 1 : 0;
85    }
86    Debug ("Value is $value; min is $min; max is $max");
87    return ($maxOK && $minOK,$value);
88}
89
90sub monHunt {
91    my($self,$target,$ds,$type,$args) = @_;
92
93    my($roll, $cmp_name, $cmp_ds) = split(/\s*:\s*/, $args);
94
95    if (!defined($roll)) {
96        Warn("Monitor: Missing rollover value in hunt threshold for " .
97             $target->{'auto-target-name'} . " datasource $ds.");
98        return 1;
99    }
100
101    # Fetch the current value from target's rrd file
102    my($value) = $self->rrdFetch($target->{'rrd-datafile'},
103                                 $self->getDSNum($target, $ds), 0);
104
105    if (!defined($value)) {
106        Warn("Monitor: Couldn't fetch last $ds value from " .
107             "$target->{'rrd-datafile'}.");
108        return 1;
109    }
110    if ($value == 0) {
111        return 1; # no rollover, test succeeded.
112    }
113
114    my($cmp_target);
115    if (defined($cmp_name)) {
116        $cmp_name = join('/',$target->{'auto-target-path'},$cmp_name)
117            if (!($cmp_name =~ /^\//));
118
119        $cmp_target = $Common::global::gCT->configHash(lc($cmp_name), 'target');
120        if (defined($cmp_target)) {
121            ConfigTree::Cache::addAutoVariables(lc($cmp_name), $cmp_target,
122                                                $Common::global::gConfigRoot);
123            ConfigTree::Cache::expandHash($cmp_target, $cmp_target, \&Warn);
124        } else {
125            Warn("Monitor: No such target: $cmp_name");
126            return 1;
127        }
128    } else {
129        $cmp_target = $target;
130    }
131
132    $cmp_ds = $ds unless ($cmp_ds);
133    my($cmp_value) = $self->rrdFetch($cmp_target->{'rrd-datafile'},
134                                     $self->getDSNum($cmp_target, $cmp_ds), 0);
135
136    if (!defined($cmp_value)) {
137        Warn("Monitor: Couldn't fetch current value from $cmp_name");
138        return 1;
139    }
140
141    return ($cmp_value >= $roll);
142}
143
144# if X and Y are data sources (Y is possibly X shifted by some temporal offset)
145# and Z is a threshold, then a relation monitor checks:
146# abs(Y - X)/Y > Z for Z a percentage (marked pct)
147# abs(Y - X) > Z for Z not a percentage
148# Default > can be replaced with <
149# return 1 if the check passes or error, 0 if fail
150sub monRelation {
151    my($self, $target, $ds, $type, $args) = @_;
152
153    my($thresh, $cmp_name, $cmp_ds, $cmp_time) = split(/\s*:\s*/, $args);
154    my($pct) = ($thresh =~ s/\s*pct\s*//i);
155    $cmp_time = 0 unless(defined($cmp_time));
156
157    if (!defined($thresh) || !defined($cmp_time)) {
158        Warn("Monitor: Improperly formatted relation monitor for " .
159             "$target->{'auto-target-name'} datasource $ds.");
160        return 1;
161    }
162
163    my($gtlt);
164    if (substr($thresh,0,1) eq '<' || substr($thresh,0,1) eq '>') {
165        $gtlt = substr($thresh,0,1);
166        $thresh = substr($thresh,1);
167    } else {
168        $gtlt = '>';
169    }
170
171    # Fetch the current value from target's rrd file
172    my($value) = $self->rrdFetch($target->{'rrd-datafile'},
173                                 $self->getDSNum($target,$ds), 0);
174
175    if (!defined($value)) {
176        Warn("Monitor: Couldn't fetch last $ds value from " .
177             "$target->{'rrd-datafile'}.");
178        return 1;
179    }
180    return ($nanErr, 'NaN') if isNaN($value);
181
182    my ($cmp_value);
183    # Is this a straight comparison value or should we fetch it as a DS
184    if ($cmp_ds =~ /^[-+]?([0-9]*\.[0-9]+|[0-9]+)$/) {
185        $cmp_value = $cmp_ds;
186    # Fetch the value it must be a DS
187    } else {
188        ($cmp_value) = $self -> FetchComparisonValue($target,$ds,$cmp_name,$cmp_ds,$cmp_time);
189        return ($nanErr,'NaN') if isNaN($cmp_value);
190    }
191
192    my($difference) = abs($cmp_value - $value);
193    $thresh = abs($thresh); # differences are always positive
194
195    if ($pct) {
196        # threshold is a percentage
197        if ($cmp_value == 0) {
198            #avoid division by 0
199            if ($difference == 0 && $gtlt eq '<') {
200                return (1,$value);
201            } else {
202                return (0,$value);
203            }
204        }
205        $difference = $difference / abs($cmp_value) * 100;
206    }
207    Debug("Values $value, $cmp_value; Difference is $difference; " .
208          "gtlt is $gtlt; thresh is $thresh.");
209# see documentation: threshold fails if expression is false
210    return (0,$value) if ((eval "$difference $gtlt $thresh") == 0);
211    return (1,$value);
212}
213
214sub monExact {
215    my($self,$target,$ds,$type,$args) = @_;
216    my(@args) = split(/\s*:\s*/, $args);
217
218    if (@args != 1) {
219        Warn("Monitor: monitor type \"exact\" requires 1 argument for " .
220             "$target->{'auto-target-name'} $ds.");
221        return 1;
222    }
223    my $exact = shift @args;
224
225    my($value) = $self->rrdFetch(
226                                 $target->{'rrd-datafile'},
227                                 $self->getDSNum($target, $ds), 0
228                                 );
229
230    if (!defined($value)) {
231        Warn("Monitor: Couldn't fetch last $ds value from " .
232             "$target->{'rrd-datafile'}.");
233        return 1;
234    }
235    return ($nanErr,'NaN') if isNaN($value);
236
237    return $exact eq $value ? 0 : 1;
238}
239
240# check the FAILURES array for failure recorded by the aberrant behavior
241# detection algorithm in RRD
242# Return values: 1 = success
243#                0 = failure
244# Returns 1 on default or unexpected error
245sub monFailures {
246    my($self, $target, $ds, $type, $args) = @_;
247
248    # only check value range if specified, as this requires fetching from the RRD
249    if ($args =~ /:/) {
250        # monValue returns 0 if out of range, 1 otherwise
251        my ($rc, $value) = $self->monValue($target,$ds,'value',$args);
252        Debug("value is $value, in range $rc");
253        # value is out of range, so ignore any failure alarm
254        if ($rc == 0) { return 1 };
255    }
256    my $datafile = $target->{'rrd-datafile'};
257    my $dsNum = $self->getDSNum($target, $ds);
258    return 1 if (!defined($dsNum));
259    return 1 if (!defined($datafile));
260    # open the file if not already open
261    if (!defined($self->{currentfile}) || $datafile ne $self->{currentfile}) {
262        my($rrd) = new RRD::File( -file => $datafile );
263        return 1 if (!$rrd->open() || !$rrd->loadHeader());
264        $self->{currentfile} = $datafile;
265        $self->{openrrd} = $rrd;
266    }
267    # check for a valid $dsNum
268    return 1 unless ($dsNum < $self->{openrrd}->ds_cnt());
269    # find the FAILURES RRA
270    my $rra;
271    my $rraNum;
272    for($rraNum = 0; $rraNum <= $#{$self->{openrrd}->{rra_def}}; $rraNum++) {
273        $rra = $self->{openrrd}->rra_def($rraNum);
274        last if ($rra -> {rraName} eq 'FAILURES');
275    }
276    return 1 if ($rra -> {rraName} ne 'FAILURES');
277
278    # retrieve the most recent value
279    my $ret;
280    $ret = $self->{openrrd}->getDSRowValue($rraNum,0,$dsNum);
281    if (!defined($ret)) {
282        Warn("Monitor: Couldn't fetch last $ds value from " .
283             "$target->{'rrd-datafile'} FAILURES RRA.");
284        return 1;
285    }
286    # FAILURES array stores a 1 for a failure (so should return 0)
287    return (1,'NaN') if isNaN($ret);
288    return !($ret);
289}
290
291# if X and Y are data sources (Y is possibly X shifted by some temporal offset)
292# and Z is a threshold, then a quotient monitor checks:
293# X/Y > Z for Z a percentage (marked pct)
294# abs(Y - X) > Z for Z not a percentage (deprecated)
295# Default > can be replaced with <
296# return 1 if the check passes or error, 0 if fail
297sub monQuotient {
298    my($self, $target, $ds, $type, $args) = @_;
299
300    my($thresh, $cmp_name, $cmp_ds, $cmp_time) = split(/\s*:\s*/, $args);
301    my($pct) = ($thresh =~ s/\s*pct\s*//i);
302    $cmp_time = 0 unless(defined($cmp_time));
303    Info("Monitor: Use of quotient monitors without percent threshold is " .
304         "deprecated for $target->{'auto-target-name'} datasource $ds.")
305        unless (defined($pct));
306
307    if (!defined($thresh) || !defined($cmp_time)) {
308        Warn("Monitor: Improperly formatted quotient monitor for " .
309             "$target->{'auto-target-name'} datasource $ds.");
310        return 1;
311    }
312
313    my($gtlt);
314    if (substr($thresh,0,1) eq '<' || substr($thresh,0,1) eq '>') {
315        $gtlt = substr($thresh,0,1);
316        $thresh = substr($thresh,1);
317    } else {
318        $gtlt = '>';
319    }
320    # Fetch the current value from target's rrd file
321    my($value) = $self->rrdFetch($target->{'rrd-datafile'},
322                                 $self->getDSNum($target,$ds), 0);
323
324    if (!defined($value)) {
325        Warn("Monitor: Couldn't fetch last $ds value from " .
326             "$target->{'rrd-datafile'}.");
327        return 1;
328    }
329    return ($nanErr,'NaN') if isNaN($value);
330
331    my ($cmp_value);
332    # Is this a straight comparison value or should we fetch it as a DS
333    if ($cmp_ds =~ /^[-+]?([0-9]*\.[0-9]+|[0-9]+)$/) {
334        $cmp_value = $cmp_ds;
335
336    # Fetch the value it must be a DS
337    } else {
338        ($cmp_value) = $self -> FetchComparisonValue($target,$ds,$cmp_name,$cmp_ds,$cmp_time);
339        return ($nanErr,'NaN') if isNaN($cmp_value);
340    }
341
342    my($difference) = abs($cmp_value - $value);
343    $thresh = abs($thresh); # differences are always positive
344
345    if ($pct) {
346        # threshold is a percentage
347        if ($cmp_value == 0) {
348            # avoid division by 0
349            if ($difference == 0 && $gtlt eq '>') {
350                return 1;
351            } else {
352                return 0;
353            }
354        }
355        $difference = abs($value/$cmp_value) * 100;
356    }
357    Debug("Difference is $difference; gtlt is $gtlt; thresh is $thresh.");
358    return (0,$value) if (eval "$difference $gtlt $thresh");
359    return (1,$value);
360}
361
362# shared code used by monRelation and monQuotient
363sub FetchComparisonValue {
364    my($self,$target,$ds,$cmp_name,$cmp_ds,$cmp_time) = @_;
365    my($cmp_target);
366    if (! $cmp_name) {
367        $cmp_target = $target;
368    } else {
369        $cmp_name = join('/',$target->{'auto-target-path'}, $cmp_name)
370            if (!($cmp_name =~ /^\//));
371
372        $cmp_target = $Common::global::gCT->configHash(lc($cmp_name), 'target');
373        if (defined($cmp_target)) {
374            ConfigTree::Cache::addAutoVariables(lc($cmp_name), $cmp_target,
375                                                $Common::global::gConfigRoot);
376            ConfigTree::Cache::expandHash($cmp_target, $cmp_target, \&Warn);
377        } else {
378            Warn("Monitor: No such target: $cmp_name");
379            return 'NaN';
380        }
381    }
382
383    $cmp_ds = $ds unless ($cmp_ds);
384
385    my($cmp_value) = $self->rrdFetch($cmp_target->{'rrd-datafile'},
386                                     $self->getDSNum($cmp_target,$cmp_ds),
387                                     $cmp_time);
388
389    if (!defined($cmp_value)) {
390        Warn("Monitor: Couldn't fetch value for $cmp_time ".
391             "seconds ago from $cmp_name.");
392        return 'NaN';
393    }
394
395    if (isNaN($cmp_value)) {
396        Info("Monitor: Data for $cmp_time seconds ago from ".
397             "$cmp_name is NaN");
398        return 'NaN';
399    }
400    return $cmp_value;
401}
402
403# fetches any data you might need from a rrd file
404# in a RRA with consolidation function AVERAGE
405# caching the open RRD::File object for later use
406# if possible.
407sub rrdFetch {
408    my($self,$datafile,$dsNum,$sec) = @_;
409    # check all of our arguments;
410    return if (!defined($dsNum));
411    return if (!defined($sec));
412    return if (!defined($datafile));
413    Debug("in rrdFetch: file is $datafile");
414    if (!defined($self->{currentfile}) || $datafile ne $self->{currentfile}) {
415        my($rrd) = new RRD::File( -file => $datafile );
416        return if (!$rrd->open() || !$rrd->loadHeader());
417        $self->{currentfile} = $datafile;
418        $self->{openrrd} = $rrd;
419    }
420    return unless ($dsNum < $self->{openrrd}->ds_cnt());
421
422    my($rra,$lastrecord,$rraNum);
423    for($rraNum = 0; $rraNum <= $#{$self->{openrrd}->{rra_def}}; $rraNum++) {
424        $rra = $self->{openrrd}->rra_def($rraNum);
425        # if the consolidation function is not AVERAGE, we can
426        # skip this RRA
427        Debug("in rrdFetch: skipping RRA");
428        next if ($rra->{rraName} ne "AVERAGE");
429        $lastrecord = $rra->{row_cnt} * $rra->{pdp_cnt} *
430            $self->{openrrd}->pdp_step();
431        last if ($lastrecord >= $sec);
432    }
433
434    if ($lastrecord < $sec) {
435        Warn("Monitor: $datafile does not have data going back " .
436             "$sec seconds.");
437        return;
438    }
439
440    if ($sec % ($lastrecord / $rra->{row_cnt})) {
441        Warn("Monitor: RRA required to find data $sec seconds ago " .
442             "is too granular.");
443        return;
444    }
445
446    my($rowNum) = $sec / ($self->{openrrd}->pdp_step() * $rra->{pdp_cnt});
447    Debug("in rrdFetch: rraNum is $rraNum rowNum is $rowNum dsNum is $dsNum");
448
449    my ($foo) = $self->{openrrd}->getDSRowValue($rraNum,$rowNum,$dsNum);
450    Debug("in rrdFetch: return is $foo");
451    return $self->{openrrd}->getDSRowValue($rraNum,$rowNum,$dsNum);
452}
453
454# Given a target reference and datasource name,
455# returns the datasource number or undef if no
456# datasource of that name can be found in target's
457# target-type dictionary
458sub getDSNum {
459    my($self, $target, $dsName) = @_;
460    my($ttRef) = $Common::global::gCT->configHash(
461                                 join('/',$target->{'auto-target-path'},
462                                      $target->{'auto-target-name'}),
463                                                  'targettype',
464                                                  lc($target->{'target-type'}),
465                                                  $target);
466    my($Counter) = 0;
467    my(%dsMap) = map { $_ => $Counter++ } split(/\s*,\s*/,$ttRef->{'ds'});
468
469    return $dsMap{$dsName};
470}
471
472# Subroutines to handle alarms
473
474sub dispatchAlarm {
475# order of arguments: $self, $target, $name, $ds, $type, $threshold,
476#                     $alarmType, $alarmArgs, $val
477    my ($args, $action) = @_;
478
479    my ($target, $ds, $val) = ($$args[1], $$args[3], $$args[8]);
480
481    my $alarmType       = $$args[6];
482
483    my %dispatch = (
484        EXEC => \&alarmExec,
485        FILE => \&alarmFile,
486        FUNC => \&alarmFunc,
487        MAIL => \&alarmMail,
488        SNMP => \&alarmSnmp,
489        META => \&alarmMeta,
490    );
491
492    my $alarm = $dispatch{$alarmType};
493
494    unless (defined($alarm)) {
495        Warn("Monitor: unknown alarm $alarmType for target: ".
496             $target->{'auto-target-path'} . $target->{'auto-target-name'} .
497             " for datasource $ds");
498        return;
499    }
500
501    my $return = $alarm->($args, $action);
502    return;
503};
504
505
506# Process alarm action
507sub Alarm {
508# order of arguments: $self, $target, $name, $ds, $type, $threshold,
509#                     $alarmType, $alarmArgs, $val
510    my $action = 'ADD';
511
512    my $return = \&dispatchAlarm(\@_, $action);
513    return;
514};
515
516# Action to clear an alarm
517sub Clear {
518# order of arguments: $self, $target, $name, $ds, $type, $threshold,
519#                     $alarmType, $alarmArgs, $val
520    my $action = 'CLEAR';
521
522    my $return = \&dispatchAlarm(\@_, $action);
523    return;
524};
525
526sub alarmExec {
527    my ($args, $action) = @_;
528    my $alarmArgs       = $$args[7];
529
530    if ($action eq 'ADD') {
531        system($alarmArgs->[0]);
532        Info("Monitor: Triggered event with system command '".
533             $alarmArgs->[0]."' .");
534    }
535
536    else {
537        system($alarmArgs->[1]);
538        Info("Monitor: Cleared event with shell command '".
539             $alarmArgs->[1]."' .");
540    }
541
542    return;
543};
544
545sub alarmFile {
546    my ($args, $action)  = @_;
547    my ($self, $target)    = ($$args[0], $$args[1]);
548    my ($name, $ds)        = ($$args[2], $$args[3]);
549    my ($type, $threshold) = ($$args[4], $$args[5]);
550    my ($alarmArgs, $val)  = ($$args[7], $$args[8]);
551
552    $self->LogToFile($alarmArgs, $action, $name, $ds, $val);
553    return;
554};
555
556sub alarmFunc {
557    my ($args, $action) = @_;
558    my $alarmArgs       = $$args[7];
559
560    if (defined $main::gMonFuncEnabled) {
561
562        if ($action eq 'ADD') {
563            eval($alarmArgs->[0]);
564            Info("Monitor: Triggered event with FUNC '".$alarmArgs->[0]."' .");
565        }
566
567        elsif ($action eq 'CLEAR') {
568            eval($alarmArgs->[1]);
569            Info("Monitor: Cleared event with FUNC '".$alarmArgs->[1]."' .");
570        }
571
572    }
573
574    else {
575        Warn("Monitor: Exec triggered, but executable alarms are not enabled.");
576    }
577
578    return;
579};
580
581sub alarmMail {
582    my ($args, $action)    = @_;
583    my ($self, $target)    = ($$args[0], $$args[1]);
584    my ($name, $ds)        = ($$args[2], $$args[3]);
585    my ($type, $threshold) = ($$args[4], $$args[5]);
586    my ($alarmArgs, $val)  = ($$args[7], $$args[8]);
587    $self->sendEmail( $action, $alarmArgs, $type, $threshold,
588                      $name, $ds, $val, $target->{'inst'} );
589    return;
590};
591
592sub alarmSnmp {
593    my ($args, $action)    = @_;
594    my ($self, $target)    = ($$args[0], $$args[1]);
595    my ($name, $ds)        = ($$args[2], $$args[3]);
596    my ($type, $threshold) = ($$args[4], $$args[5]);
597    my ($val)              = ($$args[8]);
598
599    my $Specific_Trap_Type = $action eq 'ADD' ? 4 : 5;
600    $self->sendMonitorTrap( $target, $Specific_Trap_Type, $type,
601                            $threshold, $name, $ds, $val );
602    return;
603};
604
605sub alarmMeta {
606    my ($args, $action)    = @_;
607
608    # No action is required.
609    # Alarm data is already stored in the Cricket meta files by HandleTarget.pm
610    # The meta data is stored in $CRICKET/cricket-data/ directories.
611    return;
612};
613
614# Attempt to send an alarm trap for a given target
615sub sendMonitorTrap {
616    my($self,$target,$spec,$type,$threshold,$name,$ds,$val) = @_;
617
618    my $to = $target -> {'trap-address'};
619    if (!defined($to)) {
620        Warn("Monitor: No trap address defined for $target, couldn't send trap.");
621        Info("Monitor: Threshold Failed: $threshold for target $target");
622        return;
623    }
624
625    my($OID_Prefix) = '.1.3.6.1.4.1.2595.1.3'; # OID for Cricket Violations
626
627    my(@VarBinds);
628    push(@VarBinds, "${OID_Prefix}.1", 'string', $type);
629    push(@VarBinds, "${OID_Prefix}.2", 'string', $threshold);
630    # name is the fully qualified target name
631    push(@VarBinds, "${OID_Prefix}.3", 'string', $name);
632    push(@VarBinds, "${OID_Prefix}.4", 'string', $ds);
633    my($logName) = "cricket";
634    if (!Common::Util::isWin32() && defined($ENV{'LOGNAME'})) {
635        $logName = $ENV{'LOGNAME'};
636    }
637    push(@VarBinds, "${OID_Prefix}.5", 'string', $logName);
638    # Common::HandleTarget overloads this tag
639    # for scalar targets, it could be "" or 0
640    # otherwise, it is set the instance number
641    if ($target->{'inst'}) {
642        push(@VarBinds, "${OID_Prefix}.6", 'string', $target->{'inst'});
643    }
644    if (defined($target->{'inst-name'})) {
645        push(@VarBinds, "${OID_Prefix}.7", 'string', $target->{'inst-name'});
646    }
647    # send the html contact-name
648    my $htmlRef = $Common::global::gCT -> configHash($name,'html');
649    if (defined($htmlRef -> {'contact-name'})) {
650        # parse the mailto tag
651        my $tag = $htmlRef -> {'contact-name'};
652        if ($tag =~ /mailto\:([A-Z,a-z,.,@]+)/) {
653            push(@VarBinds, "${OID_Prefix}.8", 'string', $1);
654        }
655    }
656    push(@VarBinds, "${OID_Prefix}.9", 'string', $val) unless (!defined($val));
657
658    Info("Monitor: Trap Sent to $to:\n ". join(' -- ',@VarBinds));
659    snmpUtils::trap2($to,$spec,@VarBinds);
660}
661
662sub LogToFile {
663    my ($self, $alarmArgs, $action, $targetName, $dataSourceName) = @_;
664
665    my $filePath = $alarmArgs->[0];
666    my @lines = ();
667    my $targetLine;
668
669    return unless ($action eq 'ADD' || $action eq 'CLEAR');
670
671    # try to open for read first and hunt for duplicate lines
672    my $bFound = 0;
673
674    if (open(INFILE, "$filePath")) {
675        $targetLine = $targetName . " " . $dataSourceName;
676        while (<INFILE>) {
677            chomp;
678            if ($_ eq $targetLine) {
679                $bFound = 1;
680                # nothing to add
681                last if ($action eq 'ADD');
682            } else {
683                push (@lines, $_) if ($action eq 'CLEAR');
684            }
685        }
686    }
687
688    unless (open(INFILE, "+>>$filePath")) {
689        Info("Monitor: Failed to open file $filePath: $!");
690        return;
691    }
692
693
694    # append the new line to the end of the file
695    if ($action eq 'ADD' && $bFound == 0) {
696        Info("Monitor: Appending line $targetLine to $filePath");
697        print INFILE $targetLine . "\n";
698        close(INFILE);
699        return;
700    }
701
702    # don't need INFILE anymore
703    close(INFILE);
704
705    if ($action eq 'ADD' && $bFound == 1) {
706        Info("Monitor: $targetName datasource $dataSourceName already in ".
707             "file $filePath");
708        return;
709    }
710
711    if ($action eq 'CLEAR' && $bFound == 0) {
712        Info("Monitor: $targetName datasource $dataSourceName already " .
713             "deleted from file $filePath");
714        return;
715    }
716    # need to print out @lines, which excludes the $targetLine
717    # overwrite old file
718    unless (open(OUTFILE, ">$filePath")) {
719        Info("Monitor: Failed to open file $filePath: $!");
720        return;
721    }
722    Info("Monitor: Deleting $targetLine from $filePath");
723    print OUTFILE join("\n", @lines);
724    print OUTFILE "\n" unless (scalar(@lines) == 0);
725    close(OUTFILE);
726}
727
728sub sendEmail {
729    my($self, $spec, $alarmArgs, $type, $threshold, $target, $ds, $val, $inst) = @_;
730
731    my $to = $alarmArgs -> [1];
732    if (!defined($to)) {
733        Warn("Monitor: No destination address defined for $target, " .
734             "couldn't send email.");
735        return;
736    }
737
738    my @Message;
739    push @Message, "type:\t\t$type";
740    push @Message, "threshold:\t$threshold";
741    push @Message, "target:\t$target";
742    push @Message, "ds:\t\t$ds";
743    if (defined($val))  {
744        push @Message, "val:\t\t$val";
745    }
746    # for scalar targets, inst is either "" or 0
747    if ($inst) {
748        push @Message, "inst:\t\t$inst";
749    }
750
751    my $mail_program = $alarmArgs -> [0];
752    if (!defined($mail_program)) {
753        Warn("Monitor: No email-program defined. Not sending email");
754        return;
755    } elsif (!open(MAIL, "|$mail_program -s 'Cricket $spec: $target' $to\n")) {
756        Warn("Monitor: Failed to open pipe to mail program: $!");
757        return;
758    }
759    Debug("|$mail_program -s 'Cricket $spec: $target' $to\n");
760    print (MAIL join ("\n", @Message));
761    Info("Monitor: Email sent to: $to\n" . join(' -- ', @Message));
762    close(MAIL);
763}
7641;
765
766# Local Variables:
767# mode: perl
768# indent-tabs-mode: nil
769# tab-width: 4
770# perl-indent-level: 4
771# End:
772