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