You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
403 lines
14 KiB
403 lines
14 KiB
#!/usr/bin/perl -w
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Config::Simple '-strict';
|
|
use Getopt::Long;
|
|
use File::Which;
|
|
use File::Basename;
|
|
use Zabbix::Agent::Addons::Disks;
|
|
use Zabbix::Agent::Addons::UPS;
|
|
|
|
# Output file
|
|
my $output = undef;
|
|
# When a threshold can be automatically detected,
|
|
# you may want to be notified before it's reached, so you can
|
|
# set a margin which will be substracted from the real threshold
|
|
my $temp_margin = '20';
|
|
my $temp_hd_margin = '10';
|
|
my $pwr_margin = '200';
|
|
my $pwr_rel_margin = '20';
|
|
# This value will be substracted from the higher threshold to define the low one
|
|
# so you can have hysteresis to prevent flip-flop
|
|
my $temp_hyst = '10';
|
|
my $temp_hd_hyst = '5';
|
|
my $temp_ups_hyst = '5';
|
|
my $pwr_hyst = '200';
|
|
my $pwr_rel_hyst = '10';
|
|
|
|
# Default threshold if not detected
|
|
my $def_temp_thres_high = '50';
|
|
my $def_hd_temp_thres_high = '50';
|
|
my $def_ups_temp_thres_high = '40';
|
|
my $def_fan_thres_high = '1000';
|
|
my $def_fan_thres_low = '700';
|
|
my $def_pwr_thres_high = '1000';
|
|
my $def_pwr_rel_thres_high = '80';
|
|
|
|
GetOptions(
|
|
"output=s" => \$output,
|
|
"temp-margin=i" => \$temp_margin,
|
|
"temp-hd-margin=i" => \$temp_hd_margin,
|
|
"pwr-margin=i" => \$pwr_margin,
|
|
"pwr-rel-margin=i" => \$pwr_rel_margin,
|
|
"temp-hyst=i" => \$temp_hyst,
|
|
"temp-hd-hyst=i" => \$temp_hd_hyst,
|
|
"temp-ups-hyst=i" => \$temp_ups_hyst,
|
|
"pwr-hyst=i" => \$pwr_hyst,
|
|
"pwr-rel-hys=i" => \$pwr_rel_hyst
|
|
);
|
|
|
|
sub usage(){
|
|
print<<"_EOF";
|
|
Usage: $0 --output=/etc/zabbix/sensors.ini
|
|
_EOF
|
|
}
|
|
|
|
unless ($output){
|
|
usage();
|
|
exit 1;
|
|
}
|
|
|
|
# Path
|
|
my $ipmitool = which('ipmitool');
|
|
my $smartctl = which('smartctl');
|
|
my $lmsensor = which('sensors');
|
|
my $upsc = which('upsc');
|
|
|
|
my $cfg = new Config::Simple(syntax => 'ini');
|
|
|
|
my $sensors = {};
|
|
|
|
# Try to detect IPMI sensors
|
|
if ($ipmitool && -x $ipmitool){
|
|
# First check for temperature sensors
|
|
my @lines = qx($ipmitool sdr type Temperature);
|
|
if ($? == 0){
|
|
SENSOR: foreach my $l (@lines){
|
|
chomp $l;
|
|
# Looks like
|
|
# Inlet Temp | 04h | ok | 7.1 | 25 degrees C
|
|
if ($l !~ m/^(\w+[\s\w]+?\w+)\s*\|.*\|\s*([\w\.\s]+)\s*\|.*\|\s*([\-\w\.\s]+)$/){
|
|
next SENSOR;
|
|
}
|
|
my $name = $1;
|
|
my $sensor = {};
|
|
|
|
my @details = qx($ipmitool sdr get '$name');
|
|
if ($? != 0){
|
|
print "Couldn't get detail for sensor $name\n";
|
|
next SENSOR;
|
|
}
|
|
my $val = undef;
|
|
foreach my $d (@details){
|
|
chomp $d;
|
|
if ($d =~ m/^\s*Sensor\sReading\s*:\s*(\-?\w+)/){
|
|
$val = $1;
|
|
print "Sensor $name has value: $val\n";
|
|
if ($val !~ m/^\-?\d+$/){
|
|
print "Skipping sensor $name, couldn't parse its value: $val\n";
|
|
next SENSOR;
|
|
}
|
|
}
|
|
elsif ($d =~ m/^\s*Upper\scritical\s*:\s*(\-?\d+(\.\d+))/){
|
|
$sensor->{threshold_high} = $1-$temp_margin;
|
|
}
|
|
elsif ($d =~ m/^\s*Upper\snon\-critical\s*:\s*(\-?\d+(\.\d+))/){
|
|
$sensor->{threshold_low} = $1-$temp_margin;
|
|
}
|
|
}
|
|
# Another loop to check for Normal max if Upper critical wasn't found
|
|
if (!$sensor->{threshold_high}){
|
|
foreach my $d (@details){
|
|
chomp $d;
|
|
if ($d =~ m/^\s*Normal\sMaximum\s*:\s*(\-?\d+(\.\d+))/){
|
|
$sensor->{threshold_high} = $1-$temp_margin;
|
|
}
|
|
}
|
|
}
|
|
next SENSOR unless $val;
|
|
$sensor->{threshold_low} ||= ($sensor->{threshold_high}) ? $sensor->{threshold_high}-$temp_hyst : $def_temp_thres_high-$temp_hyst;
|
|
$sensor->{threshold_high} ||= $def_temp_thres_high;
|
|
$sensor->{threshold_high} =~ s/\.0+$//;
|
|
$sensor->{threshold_low} =~ s/\.0+$//;
|
|
$sensor->{description} = $name;
|
|
$sensor->{type} = 'temp';
|
|
$sensor->{unit} = '°C';
|
|
$sensor->{cmd} = "$ipmitool sdr get '$name' | grep 'Sensor Reading' | awk '{print \$4}' | head -1";
|
|
my $id = sensor_name($name);
|
|
$sensors->{$id} = $sensor;
|
|
print "Found a temperature sensor using IPMI: $name\n";
|
|
}
|
|
}
|
|
# Now check for Fan, nearly the same as Temp, but
|
|
# * We try to detect the unit
|
|
# * threshold handling is not the same
|
|
@lines = qx($ipmitool sdr type Fan);
|
|
if ($? == 0){
|
|
SENSOR: foreach my $l (@lines){
|
|
chomp $l;
|
|
if ($l !~ m/^(\w+[\s\w]+?\w+)\s*\|.*\|\s*([\w\.\s]+)\s*\|.*\|\s*([\-\w\.\s]+)$/){
|
|
next SENSOR;
|
|
}
|
|
my $name = $1;
|
|
my $value = $3;
|
|
my $sensor = {};
|
|
|
|
my @details = qx($ipmitool sdr get '$name');
|
|
if ($? != 0){
|
|
print "Couldn't get detail for sensor $name\n";
|
|
next SENSOR;
|
|
}
|
|
my $val = undef;
|
|
foreach my $d (@details){
|
|
chomp $d;
|
|
if ($d =~ m/^\s*Sensor\sReading\s*:\s*(\w+)/){
|
|
$val = $1;
|
|
if ($val !~ m/^\d+$/){
|
|
print "Skipping sensor $name, couldn't parse its value: $val\n";
|
|
next SENSOR;
|
|
}
|
|
}
|
|
elsif ($d =~ m/^\s*Lower\scritical\s*:\s*(\d+(\.\d+))/){
|
|
$sensor->{threshold_low} = $1-$temp_margin;
|
|
}
|
|
elsif ($d =~ m/^\s*Lower\snon\-critical\s*:\s*(\d+(\.\d+))/){
|
|
$sensor->{threshold_high} = $1-$temp_margin;
|
|
}
|
|
}
|
|
next SENSOR unless $val;
|
|
$sensor->{threshold_high} ||= $def_fan_thres_high;
|
|
$sensor->{threshold_low} ||= $def_fan_thres_high-$temp_hyst;
|
|
$sensor->{threshold_high} =~ s/\.0+$//;
|
|
$sensor->{threshold_low} =~ s/\.0+$//;
|
|
$sensor->{description} = $name;
|
|
$sensor->{type} = 'fan';
|
|
$sensor->{unit} = ($value =~ m/percent|%/ || $val < 100) ? '%' : 'rpm';
|
|
$sensor->{cmd} = "$ipmitool sdr get '$name' | grep 'Sensor Reading' | awk '{print \$4}' | head -1";
|
|
my $id = sensor_name($name);
|
|
$sensors->{$id} = $sensor;
|
|
print "Found a fan sensor using IPMI: $name\n";
|
|
}
|
|
}
|
|
# Now look for power information
|
|
@lines = qx($ipmitool sdr type 'Current');
|
|
if ($? == 0){
|
|
SENSOR: foreach my $l (@lines){
|
|
chomp $l;
|
|
if ($l !~ m/^(\w+[\s\w]+?\w+(\s%)?)\s*\|.*\|\s*([\w\.\s]+)\s*\|.*\|\s*([\-\w\.\s]+)$/){
|
|
print "Skiping line $l\n";
|
|
next SENSOR;
|
|
}
|
|
my $name = $1;
|
|
my $value = $4;
|
|
my $sensor = {};
|
|
if ($name =~ m/(Power)|(Pwr)|(Consumption)|(PS\d+\sCurr\sOut)/i || $value =~ m/W(att)?/i){
|
|
my @details = qx($ipmitool sdr get '$name');
|
|
if ($? != 0){
|
|
print "Couldn't get detail for sensor $name\n";
|
|
next SENSOR;
|
|
}
|
|
my $val = undef;
|
|
my $unit = ($name =~ m/%/) ? '%' : 'Watt';
|
|
foreach my $d (@details){
|
|
chomp $d;
|
|
if ($d =~ m/^\s*Sensor\sReading\s*:\s*(\w+)/){
|
|
$val = $1;
|
|
if ($val !~ m/^\d+$/){
|
|
print "Skipping sensor $name, couldn't parse its value: $val\n";
|
|
next SENSOR;
|
|
}
|
|
}
|
|
elsif ($d =~ m/^\s*Upper\scritical\s*:\s*(\d+(\.\d+)?)/){
|
|
$sensor->{threshold_high} = ($unit eq '%') ? $1-$pwr_rel_margin : $1-$pwr_margin;
|
|
}
|
|
elsif ($d =~ m/^\s*Upper\snon\-critical\s*:\s*(\d+(\.\d+)?)/){
|
|
$sensor->{threshold_low} = ($unit eq '%') ? $1-$pwr_rel_margin : $1-$pwr_margin;
|
|
}
|
|
}
|
|
next SENSOR unless $val;
|
|
$sensor->{threshold_high} ||= ($unit eq '%') ? $def_pwr_rel_thres_high : $def_pwr_thres_high;
|
|
$sensor->{threshold_low} ||= ($unit eq '%') ? $def_pwr_rel_thres_high-$pwr_rel_hyst : $def_pwr_thres_high-$pwr_hyst;
|
|
$sensor->{threshold_high} =~ s/\.0+$//;
|
|
$sensor->{threshold_low} =~ s/\.0+$//;
|
|
$sensor->{description} = $name;
|
|
$sensor->{type} = 'power';
|
|
$sensor->{unit} = ($name =~ m/%/) ? '%' : 'Watt';
|
|
$sensor->{cmd} = "$ipmitool sdr get '$name' | grep 'Sensor Reading' | awk '{print \$4}' | head -1";
|
|
my $id = sensor_name($name);
|
|
$sensors->{$id} = $sensor;
|
|
print "Found a power sensor using IPMI: $name\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Try to detect lm_sensors, using the sensors command
|
|
if ($lmsensor && -x $lmsensor){
|
|
my @lines = qx($lmsensor);
|
|
if ($? == 0){
|
|
SENSOR: foreach my $l (@lines){
|
|
chomp $l;
|
|
# Looks like
|
|
# temp1: +27.8°C (crit = +119.0°C)
|
|
# or
|
|
# Core 0: +36.0°C (high = +80.0°C, crit = +100.0°C)
|
|
if ($l !~ m/^(\w+[\s\w]+?):\s*\+?(\d+)(\.\d+)?°C\s*(.*)$/){
|
|
next SENSOR;
|
|
}
|
|
my $name = $1;
|
|
my $val = $2;
|
|
my $thr = $4;
|
|
my $sensor = {};
|
|
|
|
if ($val !~ m/^\-?\d+$/){
|
|
print "Skipping sensor $name, couldn't parse its value: $val\n";
|
|
next SENSOR;
|
|
}
|
|
|
|
if ($name =~ m/^Core\s+\d+/){
|
|
print "Skipping individual core sensor $name\n";
|
|
next SENSOR;
|
|
}
|
|
|
|
if ($thr =~ m/high\s+=\s+\+(\d+(\.\d+)?)/){
|
|
$sensor->{threshold_high} = $1;
|
|
}
|
|
elsif ($thr =~ m/^crit\s+=\s+\+(\d+(\.\d+)?)/){
|
|
$sensor->{threshold_high} = $1 - $temp_margin;
|
|
}
|
|
|
|
next SENSOR unless $val;
|
|
$sensor->{threshold_low} ||= ($sensor->{threshold_high}) ? $sensor->{threshold_high}-$temp_hyst : $def_temp_thres_high-$temp_hyst;
|
|
$sensor->{threshold_high} ||= $def_temp_thres_high;
|
|
$sensor->{threshold_high} =~ s/\.0+$//;
|
|
$sensor->{threshold_low} =~ s/\.0+$//;
|
|
$sensor->{description} = $name;
|
|
$sensor->{type} = 'temp';
|
|
$sensor->{unit} = '°C';
|
|
$sensor->{cmd} = "$lmsensor | grep '$name:' | cut -d+ -f2 | cut -d. -f1 | head -1";
|
|
my $id = sensor_name($name);
|
|
$sensors->{$id} = $sensor;
|
|
print "Found a temperature sensor using lm_sensors: $name\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
# Now, try to detect smart capable HDD
|
|
if ($smartctl && -x $smartctl){
|
|
foreach my $block (Zabbix::Agent::Addons::Disks::list_smart_hdd({ skip_remouvable => 1 })){
|
|
my @lines = qx($smartctl -A /dev/$block);
|
|
next if ($? != 0);
|
|
foreach my $l (@lines){
|
|
if ($l =~ /(Temperature_Celsius|Airflow_Temperature_Cel)/){
|
|
$sensors->{$block} = {
|
|
description => "$block temperature",
|
|
threshold_low => $def_hd_temp_thres_high-$temp_hd_hyst,
|
|
threshold_high => $def_hd_temp_thres_high,
|
|
type => 'temp',
|
|
unit => '°C',
|
|
cmd => "$smartctl -A /dev/$block | grep $1 | awk '{print \$10}'"
|
|
};
|
|
print "Found a temperature sensor using smartctl: $block\n";
|
|
last;
|
|
}
|
|
# Format found on some NVMe SSD
|
|
elsif ($l =~ /Temperature:\s+(\d+(\.\d+)?)\sCelsius/){
|
|
$sensors->{$block} = {
|
|
description => "$block temperature",
|
|
threshold_low => $def_hd_temp_thres_high-$temp_hd_hyst,
|
|
threshold_high => $def_hd_temp_thres_high,
|
|
type => 'temp',
|
|
unit => '°C',
|
|
cmd => "$smartctl -A /dev/$block | grep Temperature: | awk '{ print \$2 }'"
|
|
};
|
|
print "Found a temperature sensor using smartctl: $block\n";
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
# Some LSI based hardware RAID controller can report HDD temp
|
|
if (-e '/dev/megaraid_sas_ioctl_node'){
|
|
# Only check for the firsts 26 drives
|
|
foreach my $i (0..25){
|
|
my @res = qx($smartctl -d megaraid,$i -A /dev/sda);
|
|
next if ($? != 0);
|
|
foreach my $l (@res){
|
|
if ($l =~ m/Drive\sTrip\sTemperature:\s+(\d+)\s/){
|
|
$sensors->{'sda-' . $i} = {
|
|
description => "Temperature for disk No $i on sda",
|
|
type => 'temp',
|
|
threshold_high => $1-$temp_hd_margin,
|
|
threshold_low => $1-$temp_hd_margin-$temp_hd_hyst,
|
|
unit => '°C',
|
|
cmd => "$smartctl -A -d megaraid,$i /dev/sda | grep 'Current Drive Temperature' | awk '{print \$4}'"
|
|
};
|
|
print "Found a temperature sensor using smartctl (megaraid): sda-$i\n";
|
|
last;
|
|
}
|
|
elsif ($l =~ /(Temperature_Celsius|Airflow_Temperature_Cel)/){
|
|
$sensors->{'sda-' . $i} = {
|
|
description => "Temperature for disk No $i on sda",
|
|
threshold_low => $def_hd_temp_thres_high-$temp_hd_hyst,
|
|
threshold_high => $def_hd_temp_thres_high,
|
|
type => 'temp',
|
|
unit => '°C',
|
|
cmd => "$smartctl -A -d megaraid,$i /dev/sda | grep $1 | awk '{print \$10}'"
|
|
};
|
|
print "Found a temperature sensor using smartctl (megaraid): sda-$i\n";
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Now check UPS
|
|
if ($upsc && -x $upsc){
|
|
foreach my $ups (Zabbix::Agent::Addons::UPS::list_ups()){
|
|
my @lines = qx($upsc $ups);
|
|
next if ($? != 0);
|
|
foreach my $l (@lines){
|
|
if ($l =~ m/^ups\.temperature:\s+(\d+(\.\d+)?)/){
|
|
$sensors->{'ups_' . lc $ups . '_temp'} = {
|
|
description => "ups temperature for $ups",
|
|
type => 'temp',
|
|
threshold_high => $def_ups_temp_thres_high,
|
|
threshold_low => $def_ups_temp_thres_high-$temp_ups_hyst,
|
|
unit => '°C',
|
|
cmd => "$upsc $ups ups.temperature"
|
|
};
|
|
print "Found a temperature sensor for ups $ups\n";
|
|
last;
|
|
}
|
|
elsif ($l =~ m/^ups\.load:\s+(\d+(\.\d+)?)/){
|
|
$sensors->{'ups_' . lc $ups . '_load'} = {
|
|
description => "ups load for $ups",
|
|
type => 'power',
|
|
threshold_high => $def_pwr_rel_thres_high,
|
|
threshold_low => $def_pwr_rel_thres_high-$pwr_rel_hyst,
|
|
unit => '%',
|
|
cmd => "$upsc $ups ups.load"
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# TODO: add support for lm sensors, but its ouput is harder to parse
|
|
foreach my $s (sort keys %$sensors){
|
|
$cfg->set_block($s, $sensors->{$s});
|
|
}
|
|
|
|
$cfg->write($output);
|
|
|
|
# Take a sensor description and return a suitable string as sensor ID
|
|
sub sensor_name{
|
|
my $desc = shift;
|
|
my $id = lc $desc;
|
|
$id =~ s/\s/_/g;
|
|
$id =~ s/%/percent/g;
|
|
$id =~ s/_rpm$//;
|
|
return $id;
|
|
}
|
|
|