#!/usr/bin/perl
# -*- perl  -*-
# Copyright (C) 2004-2008 Jimmy Olsen
#
# 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; version 2 dated June,
# 1991.
#
# 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: munin-limits.in 1526 2008-03-03 22:11:07Z jo $

use strict;

use Munin;
use POSIX qw(strftime);
use Getopt::Long;
use Time::HiRes;
use Text::Balanced qw (extract_multiple extract_delimited
		       extract_quotelike extract_bracketed);

my $DEBUG=0;
my $conffile = "/etc/munin/munin.conf";
my $do_usage = 0;
my @limit_hosts = ();
my @limit_services = ();
my @limit_contacts = ();
my $force_root = 0;
my %notes = ();
my $stdout = 0;
my $force = 0;
my %default_text = ( "default" => '${var:group} :: ${var:host} :: ${var:graph_title}${if:cfields \n\tCRITICALs:${loop<,>:cfields  ${var:label} is ${var:value} (outside range [${var:crange}])${if:extinfo : ${var:extinfo}}}.}${if:wfields \n\tWARNINGs:${loop<,>:wfields  ${var:label} is ${var:value} (outside range [${var:wrange}])${if:extinfo : ${var:extinfo}}}.}${if:ufields \n\tUNKNOWNs:${loop<,>:ufields  ${var:label} is ${var:value}${if:extinfo : ${var:extinfo}}}.}${if:fofields \n\tOKs:${loop<,>:fofields  ${var:label} is ${var:value}${if:extinfo : ${var:extinfo}}}.}\n',
    		     "nagios"  => '${var:host}\t${var:graph_title}\t${var:worstid}\t${strtrunc:350 ${if:cfields CRITICALs:${loop<,>:cfields  ${var:label} is ${var:value} (outside range [${var:crange}])${if:extinfo : ${var:extinfo}}}.}${if:wfields WARNINGs:${loop<,>:wfields  ${var:label} is ${var:value} (outside range [${var:wrange}])${if:extinfo : ${var:extinfo}}}.}${if:ufields UNKNOWNs:${loop<,>:ufields  ${var:label} is ${var:value}${if:extinfo : ${var:extinfo}}}.}${if:fofields OKs:${loop<,>:fofields  ${var:label} is ${var:value}${if:extinfo : ${var:extinfo}}}.}}',
    		     "old-nagios"  => '${var:host}\t${var:plugin}\t${var:worstid}\t${strtrunc:350 ${var:graph_title}:${if:cfields CRITICALs:${loop<,>:cfields  ${var:label} is ${var:value} (outside range [${var:crange}])${if:extinfo : ${var:extinfo}}}.}${if:wfields WARNINGs:${loop<,>:wfields  ${var:label} is ${var:value} (outside range [${var:wrange}])${if:extinfo : ${var:extinfo}}}.}${if:ufields UNKNOWNs:${loop<,>:ufields  ${var:label} is ${var:value}${if:extinfo : ${var:extinfo}}}.}${if:fofields OKs:${loop<,>:fofields  ${var:label} is ${var:value}${if:extinfo : ${var:extinfo}}}.}}'
		    );

my $log = new IO::Handle;

# Get options
$do_usage=1  unless 
GetOptions ( "force-root!"  => \$force_root,
	     "host=s"       => \@limit_hosts,
	     "service=s"    => \@limit_services,
	     "contact=s"    => \@limit_contacts,
	     "config=s"     => \$conffile,
	     "debug!"       => \$DEBUG,
	     "stdout!"      => \$stdout,
	     "force!"       => \$force,
	     "help"         => \$do_usage );

if ($do_usage)
{
    print "Usage: $0 [options]

Options:
    --[no]force-root    Force running, even as root. [--noforce-root]
    --help		View this message.
    --debug		View debug messages.
    --stdout		Log to stdout as well as the log file.
    --force		Send messages even if they shouldn't normally be sent.
    --service <service>	Limit notified services to <service>. Multiple 
    			--service options may be supplied.
    --host <host>	Limit notified hosts to <host>. Multiple --host 
    			options may be supplied.
    --contact <contact>	Limit notified contacts to <contact>. Multiple 
    			--contact options may be supplied.
    --config <file>	Use <file> as configuration file. 
    			[/etc/munin/munin.conf]

";
    exit 0;
}

if ($> == 0 and !$force_root)
{
    print "You are running this program as root, which is dumb and will
cause many problems later.  Please run it as user 'munin'.
If you really want to run it as root, use the --force-root option.
Aborting.\n\n";
    exit (1);
}

my $config = &munin_config ($conffile);
my $oldnotes = &munin_readconfig ($config->{'dbdir'}."/limits", 1, 1);
my $modified=0;

logger("Starting munin-limits, checking lock");
munin_runlock("$config->{rundir}/munin-limits.lock");
logger("Created lock: $config->{rundir}/munin-limits.lock");

my $update_time = Time::HiRes::time;

if (!defined $config->{'contact'}->{'nagios'}->{'command'} and
	defined $config->{'nsca'}) {
    $config->{'contact'}->{'old-nagios'}->{'command'} = "$config->{nsca} $config->{nsca_server} -c $config->{nsca_config} -to 60";
    $config->{'contact'}->{'old-nagios'}->{'always_send'} = "critical warning";
}
if (!defined $config->{'contact'}->{'nagios'}->{'always_send'}) {
    $config->{'contact'}->{'nagios'}->{'always_send'} = "critical warning";
}

my $defaultcontacts = munin_get ($config, "contacts", "");
if (!length $defaultcontacts) {
    my @tmpcontacts = ();
    foreach my $cont (@{munin_get_children ($config->{"contact"})}) {
	if (munin_get ($cont, "command")) {
	    push @tmpcontacts, munin_get_node_name ($cont);
	}
    }
    $defaultcontacts = join (' ', @tmpcontacts);
}
munin_set_var_loc ($config, ["contacts"], $defaultcontacts);
logger ("Debug: Set default \"contacts\" to \"$defaultcontacts\"") if $DEBUG;

# Make array of what needs to be checked
my %work_hash_tmp;
my $work_array = [];
foreach my $workfield (@{munin_find_field ($config, qr/^(critical|warning|crit|warn)/)}) {
    my $parent = munin_get_parent ($workfield);
    if (!defined $work_hash_tmp{$parent}) {
	$work_hash_tmp{$parent} = 1;
	push @$work_array, $parent;
    }
}

# Process array containing services we need to check
foreach my $workservice (@$work_array) {
    process_service ($workservice);
}

&munin_writeconfig ("$config->{dbdir}/limits", \%notes);

$update_time = sprintf ("%.2f",(Time::HiRes::time - $update_time));

munin_removelock("$config->{rundir}/munin-limits.lock");

logger("munin-limits finished ($update_time sec)");

exit 0;

sub process_service {
    my $hash       = shift || return undef;
    my $parentobj  = munin_get_parent ($hash);
    my $gparentobj = munin_get_parent (munin_get_parent ($hash));
    my $service    = munin_get_node_name ($hash);
    my $parent     = munin_get_node_name ($parentobj);
    my $gparent    = munin_get_node_name ($gparentobj);
    my $children   = munin_get_children ($hash);

    # Some fields that are nice to have in the plugin output
    $hash->{'fields'} = join (' ', map { munin_get_node_name ($_) } @$children);
    $hash->{'plugin'} = $service;
    $hash->{'graph_title'} = $hash->{'notify_alias'} if defined $hash->{'notify_alias'};
    $hash->{'host'} = munin_get ($parentobj, "notify_alias", $parent);
    $hash->{'group'} = munin_get ($gparentobj, "notify_alias", $gparent);
    $hash->{'worst'} = "ok";
    $hash->{'worstid'} = 0 unless defined $hash->{'worstid'};

    foreach my $field (@$children) {
	next if (!defined $field or ref ($field) ne "HASH");
	my $fname   = munin_get_node_name ($field);
	my $warn    = munin_get ($field, "warning", undef);
	my $crit    = munin_get ($field, "critical", undef);
	my $fpath   = munin_get_node_loc ($field);
	my $onfield = munin_get_node ($oldnotes, $fpath);

	# Skip fields without warning/critical definitions
	next if (!defined $warn and !defined $crit);

	logger ("processing field: ".join ('::', @$fpath)) if $DEBUG;
	next if (@limit_services and !grep (/^$service$/, @limit_services));

	($warn, $crit) = get_limits ($field);

	my $filename = munin_get_rrd_filename ($field);
	my $value = munin_fetch("$filename");
	# De-taint.
	if (!defined $value) {
	    $value = "unknown";
	} else {
	    $value = sprintf "%.2f",$value;
	}

	# Some fields that are nice to have in the plugin output
	$field->{'value'} = $value;
	$field->{'crange'} = (defined $crit->[0]?$crit->[0]:"").":".(defined $crit->[1]?$crit->[1]:"");
	$field->{'wrange'} = (defined $warn->[0]?$warn->[0]:"").":".(defined $warn->[1]?$warn->[1]:"");

	logger ("value: ". join ('::', @{munin_get_node_loc ($hash)}) .": $value (crit: $crit->[0]:$crit->[1]) (warn: $warn->[0]:$warn->[1])") if $DEBUG;
	if ($value eq "unknown") {
	    $crit->[0] ||= "";
	    $crit->[1] ||= "";
	    $hash->{'worst'} = "UNKNOWN" if $hash->{"worst"} eq "OK";
	    $hash->{'worstid'} = 3 if $hash->{"worstid"} == 0;
	    munin_set_var_loc (\%notes, [@$fpath, "state"], "unknown");
	    munin_set_var_loc (\%notes, [@$fpath, "unknown"], (defined $field->{"extinfo"} ? "unknown: " . $field->{"extinfo"} : "Value is unknown."));

	    if (!defined $onfield or !defined $onfield->{"state"} or $onfield->{"state"} ne "unknown") {
		$hash->{'state_changed'} = 1;
	    }
	} elsif ((defined ($crit->[0]) and $value < $crit->[0]) or
		(defined ($crit->[1]) and $value > $crit->[1])) {
	    $crit->[0] ||= "";
	    $crit->[1] ||= "";
	    $hash->{'worst'} = "CRITICAL";
	    $hash->{'worstid'} = 2;
	    munin_set_var_loc (\%notes, [@$fpath, "state"], "critical");
	    munin_set_var_loc (\%notes, [@$fpath, "critical"], 
		(defined $field->{"extinfo"}? 
		"$value (not in $crit->[0]:$crit->[1]): ".
		$field->{"extinfo"}:
		"Value is $value. Critical range ($crit->[0]:$crit->[1]) exceeded"));

	    if (!defined $onfield or !defined $onfield->{"state"} or $onfield->{"state"} ne "critical") {
		$hash->{'state_changed'} = 1;
	    }
	} elsif ((defined ($warn->[0]) and $value < $warn->[0]) or 
		(defined ($warn->[1]) and $value > $warn->[1])) {
	    $warn->[0] ||= "";
	    $warn->[1] ||= "";
	    $hash->{'worst'} = "WARNING" if $hash->{"worst"} ne "CRITICAL";
	    $hash->{'worstid'} = 1 if $hash->{"worstid"} != 2;
	    munin_set_var_loc (\%notes, [@$fpath, "state"], "warning");
	    munin_set_var_loc (\%notes, [@$fpath, "warning"], 
		(defined $field->{"extinfo"}?
		"$value (not in $warn->[0]:$warn->[1]): ".
		$field->{"extinfo"}:
		"Value is $value. Warning range ($warn->[0]:$warn->[1]) exceeded"));

	    if (!defined $onfield or !defined $onfield->{"state"} or $onfield->{"state"} ne "warning") {
		$hash->{'state_changed'} = 1;
	    }
	} elsif (defined $onfield and defined $onfield->{"state"} or $force) {
	    munin_set_var_loc (\%notes, [@$fpath, "state"], "ok");
	    munin_set_var_loc (\%notes, [@$fpath, "ok"], "OK");
	    $hash->{'state_changed'} = 1;
	}
    }
    generate_service_message ($hash);
}

sub get_limits {
    my $hash = shift || return undef;
    my @critical = (undef, undef);
    my @warning  = (undef, undef);
    my $crit = munin_get ($hash, "critical", undef);
    my $warn = munin_get ($hash, "warning", undef);
    my $name = munin_get_node_name ($hash);

    if (defined $crit and $crit =~ /^\s*([-+\d.]*):([-+\d.]*)\s*$/) {
	$critical[0] = $1 if length $1;
	$critical[1] = $2 if length $2;
	logger ("processing critical: $name -> $critical[0] : $critical[1]") if $DEBUG;
    } elsif (defined $crit and $crit =~ /^\s*([-+\d.]+)\s*$/) {
	$critical[1] = $1 if defined $1;
	logger ("processing critical: $name -> $critical[0] : $critical[1]") if $DEBUG;
    } elsif (defined $crit) {
	@critical = (0, 0);
	logger ("processing critical: $name -> $critical[0] : $critical[1]") if $DEBUG;
    }

    if (defined $warn and $warn =~ /^\s*([-+\d.]*):([-+\d.]*)\s*$/) {
	$warning[0] = $1 if length $1;
	$warning[1] = $2 if length $2;
	logger ("processing warning: $name -> $warning[0] : $warning[1]") if $DEBUG;
    } elsif (defined $warn and $warn =~ /^\s*([-+\d.]+)\s*$/) {
	$warning[1] = $1 if defined $1;
	logger ("processing warning: $name -> $warning[0] : $warning[1]") if $DEBUG;
    } elsif (defined $warn) {
	@warning = (0, 0);
	logger ("processing warning: $name -> $warning[0] : $warning[1]") if $DEBUG;
    }
    return (\@warning, \@critical);
}

sub generate_service_message {
    my $hash       = shift || return undef;
    my $critical   = undef;
    my $worst      = $hash->{"worst"};
    my %stats      = ('critical' => [], 'warning' => [], 'unknown' => [], 'foks' => [], 'ok' => []);
    my $contacts   = munin_get_children (munin_get_node ($config, ["contact"]));

    logger ("generating service message: ". join ('::', @{munin_get_node_loc ($hash)})) if $DEBUG;
    foreach my $field (@{munin_get_children ($hash)}) {
	if (defined $field->{"state"}) {
	    push @{$stats{$field->{"state"}}}, munin_get_node_name ($field);
	    if ($field->{"state"} eq "ok") {
		push @{$stats{"foks"}}, munin_get_node_name ($field);
	    }
	}
    }
    $hash->{'cfields'}  = join " ", @{$stats{'critical'}};
    $hash->{'wfields'}  = join " ", @{$stats{'warning'}};
    $hash->{'ufields'}  = join " ", @{$stats{'unknown'}};
    $hash->{'fofields'} = join " ", @{$stats{'foks'}};
    $hash->{'ofields'}  = join " ", @{$stats{'ok'}};
    $hash->{'numcfields'}  = scalar @{$stats{'critical'}};
    $hash->{'numwfields'}  = scalar @{$stats{'warning'}};
    $hash->{'numufields'}  = scalar @{$stats{'unknown'}};
    $hash->{'numfofields'} = scalar @{$stats{'foks'}};
    $hash->{'numofields'}  = scalar @{$stats{'ok'}};

    my $contactlist =  munin_get ($hash, "contacts", "");
    logger ("Contact list for ". join ('::', @{munin_get_node_loc ($hash)}) . ": $contactlist");
    foreach my $c (split (/\s+/, $contactlist)) {
	next if $c eq "none";
	my $contactobj = munin_get_node ($config, ["contact", $c]);
	next unless defined $contactobj;
	next unless defined munin_get ($contactobj, "command", undef);
	if (@limit_contacts and !grep (/^$c$/, @limit_contacts)) {
	    next;
	}
	my $obsess = 0;
	my $cas = munin_get ($contactobj, "always_send");
	if (defined $cas) {
	    $obsess = grep {scalar(@{$stats{$_}})} (split (/\s+/, lc $cas));
	}
	if (!$hash->{'state_changed'} and !$obsess) {
	    next; # No need to send notification
	}
	logger ("state has changed, notifying $c");
	my $precmd = munin_get ($contactobj, "command");
	my $pretxt = munin_get ($contactobj, "text", munin_get (munin_get_node ($config, ["contact", "default"]), "text", $default_text{$c} || $default_text{"default"}));
	my $txt = message_expand ($hash, $pretxt, "");
	my $cmd = message_expand ($hash, $precmd, "");
	$txt =~ s/\\n/\n/g;
	$txt =~ s/\\t/\t/g;

	# In some cases we want to reopen the command
	my $maxmess = munin_get ($contactobj, "max_messages", 0);
	my $curmess = munin_get ($contactobj, "num_messages", 0);
	my $curcmd  = munin_get ($contactobj, "pipe_command", undef);
	my $pipe    = munin_get ($contactobj, "pipe", undef);
	if ($maxmess and $curmess >= $maxmess ) {
	    close ($pipe);
	    $pipe = undef;
	    munin_set_var_loc ($contactobj, ["pipe"], undef);
	    logger ("Debug: Closing \"$c\" -> command (max number of messages reached).") if $DEBUG;
	} elsif ($curcmd and $curcmd ne $cmd) {
	    close ($pipe);
	    $pipe = undef;
	    munin_set_var_loc ($contactobj, ["pipe"], undef);
	    logger ("Debug: Closing \"$c\" -> command (command has changed).") if $DEBUG;
	}
    
	if (!defined $pipe) {
	    my @cmd = extract_multiple (
		    message_expand ($hash, $cmd),
		    [ sub { extract_delimited ($_[0], q{"'})},
		      qr/\S+/
		    ],
		    undef, 1);
	    @cmd = map { s/['"]$//; s/^['"]//; $_ } @cmd;
	    $contactobj->{"num_messages"} = 0;
	    if ($cmd[0] eq "|") {
		$cmd[0] = "|-";
	    } elsif ($cmd[0] !~ /^[|>]/) {
		unshift (@cmd, "|-");
	    }
	    logger ("Debug: opening \"$c\" for writing: \"" . join('" "',@cmd) . "\".") if $DEBUG;
	    if ($cmd[0] eq ">") {
		if (! open ($pipe, join (' ', @cmd))) {
		    logger ("Fatal: Could not open " . join (' ', @cmd[1 .. $#cmd]) . " for writing: $!");
		    exit 3;
		}
	    } else {
		my $pid = open ($pipe, "|-");
		if (!defined $pid) {
		    logger ("Fatal: Unable to  fork: $!");
		    exit 3;
		} if (!$pid) { # Child
		    # Fork of stdout-to-log filter
		    my $logstdout;
		    my $logstderr;
		    my $logpid = open ($logstdout, "|-");
		    if (!defined $logpid) {
			logger ("Fatal: Unable to  fork: $!");
			exit 3;
		    } 
		    if (!$logpid) { # Child
			while (<STDIN>) {
			    chomp;
			    logger ("Command \"$c\" stdout: $_");
			}
			exit 0;
		    }
		    close (STDOUT);
		    *STDOUT = \$logstdout;
		    $logpid = open ($logstderr, "|-");
		    if (!defined $logpid) {
			logger ("Fatal: Unable to  fork: $!");
			exit 3;
		    }
		    if (!$logpid) { # Child
			while (<STDIN>) {
			    chomp;
			    logger ("Command \"$c\" stderr: $_");
			}
			exit 0;
		    }
		    open (STDOUT, ">&", $logstdout);
		    open (STDERR, ">&", $logstderr);

		    exec (@cmd[1 .. $#cmd]) or logger ("Warning: Could not run command \"" . join(' ',@cmd[1 .. $#cmd]) . "\": $!");
		    exit 5;
		    # NOTREACHED
		}
	    }
	    logger ("baz?");
	    munin_set_var_loc ($contactobj, ["pipe_command"], $cmd);
	    munin_set_var_loc ($contactobj, ["pipe"], $pipe);
	} 
	logger ("sending message: \"$txt\"") if ($DEBUG);
	print $pipe $txt, "\n" if (defined $pipe);
	$contactobj->{"num_messages"} = 1 + munin_get ($contactobj, "num_messages", 0); # $num_messages++
    }
}


sub message_expand {
    my $hash   = shift;
    my $text   = shift;
    my @res    = ();

    
    while (length ($text)) {   
	if ($text =~ /^([^\$]+|)(?:\$(\{.*)|)$/) {
	    push @res, $1;
	    $text = $2;
	}   
	my @a = extract_bracketed ($text, '{}');
	if ($a[0] =~ /^\{var:(\S+)\}$/) {
	    $a[0] = munin_get ($hash, $1, "");
	} elsif ($a[0] =~ /^\{loop<([^>]+)>:\s*(\S+)\s(.+)\}$/) {
	    my $d = $1;
	    my $f = $2;
	    my $t = $3;
	    my $fields = munin_get ($hash, $f, "");
	    my @res  = ();
	    if ($fields) {
		foreach my $sub (split /\s+/, $fields) {
		    if (defined $hash->{$sub}) {
			push @res, message_expand ($hash->{$sub}, $t);
		    }
		}
	    } 
	    $a[0] = join ($d, @res);
	} elsif ($a[0] =~ /^\{loop:\s*(\S+)\s(.+)\}$/) {
	    my $f = $1;
	    my $t = $2;
	    my $fields = munin_get ($hash, $f, "");
	    my $res  = "";
	    if ($fields) {
		foreach my $sub (split /\s+/, $fields) {
		    if (defined $hash->{$sub}) {
			push @res, message_expand ($hash->{$sub}, $t);
		    }
		}
	    } 
	    $a[0] = $res;
	} elsif ($a[0] =~ /^\{strtrunc:\s*(\S+)\s(.+)\}$/) {
	    my $f = "%.".$1."s";
	    my $t = $2;
	    $a[0] = sprintf ($f, message_expand ($hash, $t));
	} elsif ($a[0] =~ /^\{if:\s*(\!)?(\S+)\s(.+)\}$/) {
	    my $n = $1;
	    my $f = $2;
	    my $t = $3;
	    my $res  = "";
	    my $field = munin_get ($hash, $f, 0);
	    my $check = ($field ne "0" and length ($field));
	    $check = (!length ($field) or $field eq "0") if $n;

	    if ($check) {
		$res .= message_expand ($hash, $t);
	    } 
	    $a[0] = $res;
	}
	push @res, $a[0];
	$text = $a[1];
    }

    return join ('', @res);
}

sub logger_open {
    # Called from the Munin module when we call munin_config
    my $dirname = shift;

    if (!$log->opened) {
	if (!open ($log, ">>$dirname/munin-limits.log")) {
	    print STDERR "Warning: Could not open log file \"$dirname/munin-limits.log\" for writing: $!";
	} else {
	    open (STDERR, ">&", $log);
	}
    }
}


sub logger {
    my ($comment) = @_;
    my $now = strftime "%b %d %H:%M:%S", localtime;

    print "$now - $comment\n" if $stdout;

    if ($log->opened) {
	print $log "$now - $comment\n";
    } else {
	if (!open ($log, ">>/var/log/munin/munin-limits.log")) {
	    print STDERR "Warning: Could not open log file \"/var/log/munin/munin-limits.log\" for writing: $!";
	} else {
	    open (STDERR, ">&", $log);
	}
    }
}

close $log;

=head1 NAME

munin-limits - A program to check for any off-limit values

=head1 SYNOPSIS

munin-limits [options]

=head1 OPTIONS

=over 5

=item B<< --service <service> >>

Limit services to those of E<lt>serviceE<gt>. Multiple --service options may be supplied. [unset]

=item B<< --host <host> >>

Limit hosts to those of E<lt>host<gt>. Multiple --host options may be supplied. [unset]

=item B<< --contact <contact> >>

Limit contacts to those of E<lt>contact<gt>. Multiple --contact options may be supplied. [unset]

=item B<< --config <file> >>

Use E<lt>fileE<gt> as configuration file. [/etc/munin/munin.conf]

=item B<< --[no]force >>

Force sending of messages even if you normally wouldn't. [--noforce]

=item B<< --[no]force-root >>

Force running as root (stupid and unnecessary). [--noforce-root]

=item B<< --help >>

View help message.

=item B<< --[no]debug >>

If set, view debug messages. [--nodebug]

=back

=head1 DESCRIPTION

Munin-limits is a part of the package Munin, which is used in combination
with Munin's node.  Munin is a group of programs to gather data from
Munin's nodes, graph them, create html-pages, and optionally warn Nagios
about any off-limit values.

Munin-limits checks if any values are above or below the set limits, and saves these notes to a file. This file
is later used by programs like munin-nagios (to warn nagios) and munin-html (to incorporate them in the web
display).

If a service has fields with "warning" or "critical"-options (e.g. "load.warning 10"), and the munin-server
configuration file contains the necessary configuration options, munin-limits will check its value.

=head1 FILES

	/etc/munin/munin.conf
	/var/lib/munin/*
	/var/run/munin/*

=head1 VERSION

This is munin-limits version 1.3.4

=head1 AUTHORS

Knut Haugen, Audun Ytterdal and Jimmy Olsen.

=head1 BUGS

munin-limits does, as of now, not check the syntax of the configuration file.

Please report other bugs in the bug tracker at L<http://munin.sf.net/>.

=head1 COPYRIGHT

Copyright (C) 2002-2006 Knut Haugen, Audun Ytterdal, and Jimmy Olsen / Linpro AS.

This is free software; see the source for copying conditions. There is
NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.

This program is released under the GNU General Public License

=head1 SEE ALSO

For information on configuration options, please refer to the man page for
F<munin.conf>.

=cut

# vim: syntax=perl ts=8
