diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1377554 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/sensors_ b/sensors_ new file mode 100755 index 0000000..f694d00 --- /dev/null +++ b/sensors_ @@ -0,0 +1,381 @@ +#!/usr/bin/perl -w +# -*- perl -*- +=head1 NAME + +sensors_ - Wildcard-plugin to monitor information from temperature, +voltage, and fan speed sensors. + +=head1 CONFIGURATION + +The possible wildcard values are the follwing: fan, temp, volt. So you +would create symlinks to this plugin called sensors_fan, sensors_temp, +and sensors_volt. + +The plugins needs the following components configured: + +=over + +=item i2c and lm_sensors modules installed and loaded. + +=item sensors program installed and in path. + +=back + +Note: Sensor names are read from the output of the sensors program. +Change them in /etc/sensors.conf if you don't like them. + + [sensors_*] + env.sensors - Override default sensors program path + env.ignore_temp - Temperature will not be plotted + env.ignore_fan - Fan will not be plotted + env.ignore_volt - Voltage will not be plotted + env.fan_warn_percent - Percentage over mininum for warning + env.volt_warn_percent - Percentage over mininum/under maximum for warning + +=head1 AUTHOR + +Unknown author + +=head1 LICENSE + +GPLv2 + +=head1 MAGIC MARKERS + + #%# family=auto + #%# capabilities=autoconf suggest + +=cut + +use strict; + +$ENV{'LANG'} = "C"; # Force parsable output from sensors. +$ENV{'LC_ALL'} = "C"; # Force parsable output from sensors. +my $SENSORS = $ENV{'sensors'} || 'sensors'; + +# Example outputs from sensors & matching regex parts: + +# Fan output example from sensors: +# -------------------------------- +# Case Fan: 1268 RPM (min = 3750 RPM, div = 8) ALARM +# CPU Fan: 0 RPM (min = 1171 RPM, div = 128) ALARM +# Aux Fan: 0 RPM (min = 753 RPM, div = 128) ALARM +# fan4: 3375 RPM (min = 774 RPM, div = 8) +# fan5: 0 RPM (min = 1054 RPM, div = 128) ALARM +# +# ^^^^ ^^^^ ^^^^ +# $1 $2 $3 +# +# -------------------------------- +# +# Temperature output example from sensors: +# * Note that the degree character is replaced by a space, as we are running +# with LC_ALL=C and LANG=C. +# --------------------------------------- +# Sys Temp: +41.0 C (high = -128.0 C, hyst = +24.0 C) ALARM sensor = thermistor +# CPU Temp: +40.5 C (high = +80.0 C, hyst = +75.0 C) sensor = thermistor +# AUX Temp: +39.0 C (high = +80.0 C, hyst = +75.0 C) sensor = thermistor +# +# ^^^^^^^^ ^^ ^^ ^^ +# $1 $2 $3 $4 +# +# --------------------------------------- +# +# Voltage output example from sensors: +# ------------------------------------ +# +# VCore: +1.09 V (min = +0.00 V, max = +1.74 V) +# in1: +12.14 V (min = +10.51 V, max = +2.38 V) ALARM +# +# ^^^ ^^^ ^^^ ^^ +# $1 $2 $3 $4 +# +# +# ------------------------------------ + + +my %config = ( + fan => { + regex => qr/ + ^ # String must start with: + ([^:\n]*) # Match any non-whitespace char, except ':' and new line + # (Match label as \$1) + \s* # Zero or more spaces followed by + : # : character + \s* # Zero or more spaces followed by + \+? # Zero or one '+' char. Note: This might not be needed + # as FAN speeds don't have + signs in front of them. + # This can be probably be removed as the + # sensors program never prints a + char in front of + # RPM values. Verified in lm-sensors-3-3.1.2 + (\d+) # Match one or more digits (Match current value as \$2) + \s # Exactly one space followed by + (?: # a optional group, which might contain the minimum value: + RPM.*? # RPM string, and then any chars (but no newlines) + (\d+) # Match one or more digits (Match minimum value as \$3) + \s # Exactly one space followed by + )? # end of optional group + RPM # RPM string + /xm, # Use extended regular expressions and treat string as multiple lines. + title => 'Fans', + vlabel => 'RPM', + print_threshold => \&fan_threshold, + graph_args => '--base 1000 -l 0' + }, + + temp => { + regex => qr/ + ^ # String must start with: + ([^:\n]*) # Match any non-whitespace char, except ':' and new line + # (Match label as \$1) + \s* # Zero or more spaces followed by + : # ':' character + \s* # Zero or more spaces followed by + \+? # Zero or one '+' char followed by + + (-? # Match zero or one '-' char + \d+ # Match one or more digits + # (Match current value as \$2) followed by + + (?:\.\d+)?)# Zero or one match of '.' char followed by one or more + # digits. '?:' means it is not a numbered capture group. + [°\s] # Match degree char or space char + C # Match 'C' char + + (?: # >>>>Match the following statement zero or one time. + # '?:' means it is not a numbered capture group. + \s+ # One or more space followed by + \( # '(' char + (?:high|limit|crit) # 'high' or 'limit' or 'crit' string. + # '?:' means it is not a numbered capture group. + \s*=\s* # Match zero or more spaces and then '=' char and then + # zero or more spaces, followed by + \+? # Zero or one '+' char + (\d+ # Match one or more digits + # (Match high value as \$3) followed by + + (?:\.\d+)?)# Zero or one match of '.' char followed by one or more + # digits. '?:' means it is not a numbered capture group. + [°\s] # Match degree char or space char + C,?\s* # 'C' followed by optional comma and zero or more spaces + (?: # >>>Match the following non-capture group zero or more times + (?:crit|hyst(?:eresis)?) # 'crit' or 'hyst' string followed by optional 'eresis' string. + # '?:' means it is not a numbered capture group. + \s*=\s* # Match zero or more spaces and then '=' char and then + # zero or more spaces, followed by + \+? # Zero or one '+' char + (\d+ # Match one or more digits + # (Match hyst value as \$4) followed by + (?:\.\d+)?)# Zero or one match of '.' char followed by one or more + # digits. '?:' means it is not a numbered capture group. + [°\s]C # Match degree char or space char, and then 'C' char + )? # >>>end of group + \) # ')' char + )? # >>>>end of group + /xm, # Use extended regular expressions and treat string as multiple lines. + title => 'Temperatures', + vlabel => 'degrees Celsius', + print_threshold => \&temp_threshold, + graph_args => '--base 1000' + }, + + volt => { + regex => qr/ + ^ # String must start with: + ([^:\n]*) # Match any non-whitespace char, except ':' and new line + # one space in front of them. + # (match the label as \$1) + + \s*:\s* # Zero or more spaces followed by ':' and + # zero or more spaces followed by + \+? # Zero or one '+' char followed by + (-? # Match zero or one '-' char + \d+ # Match one or more digits + # (match the current voltage as \$2) followed by + (?:\.\d+)?) # Zero or one match of '.' char followed by one or more digits. + # '?:' means it is not a numbered capture group. + \sV # one space, 'V' char + + (?: # >>>>Match the following statement. + # '?:' means it is not a numbered capture group. + \s+ # One or more space followed by + \( # '(' char + min # Match min string + \s*=\s* # Match zero or more spaces and then '=' char and + # then zero or more spaces, followed by + \+? # Zero or one '+' char + (-? # Match zero or one '-' char + \d+ # Match one or more digits + # (Match the minimum value as \$3) followed by + (?:\.\d+)?) # Zero or one match of '.' char followed by one or more + # digits. '?:' means it is not a numbered capture group. + \sV,\s* # One space char, 'V,' string followed by zero or + # more spaces + + max # Match 'max' string + \s*=\s* # Match zero or more spaces and then '=' char and + # then zero or more spaces, followed by + \+? # Zero or one '+' char + (-? # Match zero or one '-' char + \d+ # Match one or more digits + # (Match the max value as \$4) followed by + (?:\.\d+)?) # Zero or one match of '.' char followed by one or more digits. + # '?:' means passive group (does not match) + \sV # one space, 'V' char + \) # ')' char + )? # >>>>end of statement + /xm, # Use extended regular expressions and treat string as multiple lines. + + title => 'Voltages', + vlabel => 'Volt', + print_threshold => \&volt_threshold, + graph_args => '--base 1000 --logarithmic' + }, + + power => { + regex => qr/ + ^ # String must start with: + ([^:\n]*) # Match any non-whitespace char, except ':' and new line + # one space in front of them. + # (match the label as \$1) + + \s*:\s* # Zero or more spaces followed by ':' and + # zero or more spaces followed by + (\d+ # Match one or more digits + # (match the power as \$2) followed by + (?:\.\d+)?) # Zero or one match of '.' char followed by one or more digits. + # '?:' means it is not a numbered capture group. + \sW # one space, 'W' char + + (?: # >>>>Match the following statement. + # '?:' means it is not a numbered capture group. + \s+ # One or more space followed by + \( # '(' char + crit # Match crit string + \s*=\s* # Match zero or more spaces and then '=' char and + # then zero or more spaces, followed by + (\d+ # Match one or more digits + # (Match the minimum value as \$3) followed by + (?:\.\d+)?) # Zero or one match of '.' char followed by one or more + # digits. '?:' means it is not a numbered capture group. + \sW\s* # One space char, 'W' character followed by zero or + # more spaces + \) # ')' char + )? # >>>>end of statement + /xm, # Use extended regular expressions and treat string as multiple lines. + + title => 'Power', + vlabel => 'Watts', + print_threshold => \&power_threshold, + graph_args => '--base 1000 --logarithmic' + }, +); + +if ( defined $ARGV[0] and $ARGV[0] eq 'autoconf' ) { + # Now see if "sensors" can run + my $text = `$SENSORS 2>/dev/null`; + if ($?) { + if ($? == -1) { + print "no (program $SENSORS not found)\n"; + } else { + print "no (program $SENSORS died)\n"; + } + exit 0; + } + + unless ($text =~ /[° ]C/) { + print "no (no temperature readings)\n"; + exit 0; + } + + print "yes\n"; + exit 0; +} + +if (defined $ARGV[0] and $ARGV[0] eq 'suggest') { + my $text = `$SENSORS 2>/dev/null`; + foreach my $func (keys %config) { + print $func, "\n" if $text =~ $config{$func}->{regex}; + } + exit; +} + +$0 =~ /sensors_(.+)*$/; +my $func = $1; +exit 2 unless defined $func; + +if ( defined $ARGV[0] and $ARGV[0] eq 'config' ) { + print "graph_title $config{$func}->{title}\n"; + print "graph_vlabel $config{$func}->{vlabel}\n"; + print "graph_args $config{$func}->{graph_args}\n"; + print "graph_category sensors\n"; + my $text = `$SENSORS`; + my $sensor = 1; + while ($text =~ /$config{$func}->{regex}/g) { + my ($label, undef, $max, $min) = ($1, $2, $3, $4); + print "$func$sensor.label $label\n"; + $config{$func}->{print_threshold}->($func.$sensor, $3, $4); + print "$func$sensor.graph no\n" if exists $ENV{"ignore_$func$sensor"}; + $sensor++; + } + exit 0; +} + +my $text = `$SENSORS`; +my $sensor = 1; +while ($text =~ /$config{$func}->{regex}/g) { + print "$func$sensor.value $2\n"; + $sensor++; +} + +sub fan_threshold { + my $name = shift; + my $min = shift; + + my $warn_percent = exists $ENV{fan_warn_percent} ? $ENV{fan_warn_percent} : 5; + + return unless defined $min; + + printf "$name.warning %d:\n", $min * (100 + $warn_percent) / 100; + printf "$name.critical %d:\n", $min; +} + +sub temp_threshold { + my $name = shift; + my $max = shift; + my $min = shift; + + if (defined($min) and defined($max) and $min > $max) { + ($min, $max) = ($max, $min); + } + + printf "$name.warning $min\n" if $min; + printf "$name.critical $max\n" if $max; +} + +sub volt_threshold { + my $name = shift; + my $min = shift; + my $max = shift; + my $warn_percent = exists $ENV{volt_warn_percent} ? $ENV{volt_warn_percent} : 20; + + return unless defined ($min && $max); + + my $diff = $max - $min; + my $dist = $diff * $warn_percent / 100; + printf "$name.warning %.2f:%.2f\n", $min + $dist, $max - $dist; + printf "$name.critical $min:$max\n"; +} + +sub power_threshold { + my $name = shift; + my $crit = shift; + my $warn_percent = exists $ENV{watt_warn_percent} ? $ENV{watt_warn_percent} : 20; + + return unless defined ($crit); + + printf "$name.warning :%.2f\n", $crit * (100-$warn_percent)/100; + printf "$name.critical :$crit\n"; +} + +# vim:syntax=perl