ecb831f05a
With default conf, it'll print "Init SSL without certificate database" which we're not interested in in this context (and would break Zabbix item)
404 lines
14 KiB
Perl
Executable File
404 lines
14 KiB
Perl
Executable File
#!/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 2>/dev/null"
|
|
};
|
|
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 2>/dev/null"
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# 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/[^\w]/_/g;
|
|
$id =~ s/%/percent/g;
|
|
$id =~ s/_rpm$//;
|
|
return $id;
|
|
}
|