]> jfr.im git - munin-plugins.git/blame - hddtemp_smartctl
support for a 2nd unbound plugin
[munin-plugins.git] / hddtemp_smartctl
CommitLineData
0d8dd5f2
JR
1#!/usr/bin/perl -w
2# -*- perl -*-
3
4use strict;
5use warnings;
6
7=head1 NAME
8
9hddtemp_smartctl - Plugin to monitor harddrive temperatures through
10SMART
11
12=head1 CONFIGURATION
13
14This plugin needs to run as root or some other user that has access to
15the harddrive devices.
16
17The following environment variables are used
18
19 smartctl - path to smartctl executable
20 drives - List drives to monitor. E.g. "env.drives hda hdc".
21 type_$dev - device type for one drive, e.g. "env.type_sda 3ware,0"
22 or more typically "env.type_sda ata" if sda is a SATA disk.
23 args_$dev - additional arguments to smartctl for one drive,
24 e.g. "env.args_hda -v 194,10xCelsius". Use this to make
25 the plugin use the --all or -a option if your disk will
26 not return its temperature when only the -A option is
27 used.
28 dev_$dev - monitoring device for one drive, e.g. twe0
29
30If the "smartctl" environment variable is not set the plugin will
31search your $PATH, /usr/bin, /usr/sbin, /usr/local/bin and
32/usr/local/sbin for a file called "smartctl", and use that.
33
34If the "drives" environment variable is not set, the plugin will
35attempt to search for drives to probe.
36
37=head1 MAGIC MARKERS
38
39 #%# family=auto
40 #%# capabilities=autoconf
41
42=head1 AUTHOR
43
44Copyright (c) 2005, Lutz Peter Christoph
45All rights reserved.
46
472016-08-27, Gabriele Pohl (contact@dipohl.de)
48Fix for github issue #690
49
50=head1 LICENSE
51
52Redistribution and use in source and binary forms, with or without
53modification, are permitted provided that the following conditions
54are met:
55
56 * Redistributions of source code must retain the above copyright
57 notice, this list of conditions and the following disclaimer.
58
59 * Redistributions in binary form must reproduce the above copyright
60 notice, this list of conditions and the following disclaimer in
61 the documentation and/or other materials provided with the
62 distribution.
63
64 * The name and aliases of Lutz Peter Christoph ("Lupe Christoph",
65 "Lutz Christoph") may not be used to endorse or promote products
66 derived from this software without specific prior written
67 permission.
68
69THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
70"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
71LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
72A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
73OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
74SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
75LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
76DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
77THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
78(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
79OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
80
81=head1 NOTES
82
83Note for users of RAID controllers (smartmontools currently only
84supports 3ware): you can specify the drives attached to your RAID
85controller(s) as raiddev_num (e.g. sda_0). Then you must specify the
86type like this: type_sda_0 3ware,0.
87
88Recent versions of the kernel driver use a separate major device
89number for monitoring purposes, like /dev/twe<n> or /dev/twa<n>. This
90can be put in the e.g. dev_sda environment variable, to allow the user
91to keep sda as the name of the disk.
92
93=cut
94
95use File::Spec::Functions qw(splitdir);
96use lib $ENV{'MUNIN_LIBDIR'};
97use Munin::Plugin;
98
99my $DEBUG = $ENV{'MUNIN_DEBUG'} || 0;
100
101my $smartctl;
102
103if (exists $ENV{smartctl}) {
104 $smartctl = $ENV{smartctl};
105 if (defined $ARGV[0] and $ARGV[0] eq 'autoconf') {
106 # The real "autoconf" section follows later. But here we need to check for requirements, too.
107 if (! -e $smartctl) {
108 print "no (Predefined smartctl ($smartctl) does not exist)\n";
109 exit 0;
110 } elsif (! -x $smartctl) {
111 print "no (Predefined smartctl ($smartctl) is not executable)\n";
112 exit 0;
113 }
114 } else {
115 # immediate failure is allowed outside of "autoconf"
116 die "$smartctl does not exist\n" unless (-e $smartctl);
117 die "$smartctl is not executable\n" unless (-x $smartctl);
118 }
119} else {
120 # Not defined in %ENV? Check obvious places
121 my @dirs = split(':', $ENV{PATH});
122 push (@dirs, qw(/usr/bin /usr/sbin /usr/local/bin /usr/local/sbin) );
123
124 until ($smartctl or @dirs == 0) {
125 my $dir = shift @dirs;
126 my $path = $dir.'/smartctl';
127 $smartctl = $path if -x $path;
128 }
129
130 unless ($smartctl) {
131 if (defined $ARGV[0] and $ARGV[0] eq 'autoconf') {
132 print "no ('smartctl' executable not found)\n";
133 exit 0;
134 } else {
135 die "'smartctl' executable not found\n";
136 }
137 }
138}
139
140my @drives;
141
142# Try to get a default set of drives
143if ($^O eq 'linux') {
144 # On Linux, we know how to enumerate ide drives.
145 my @drivesIDE;
146 if (-d '/proc/ide') {
147 opendir(IDE, '/proc/ide');
148 @drivesIDE = grep /hd[a-z]/, readdir IDE;
149 closedir(IDE);
150 }
151
152 # Look for SCSI / SATA drives in /sys
153 my @drivesSCSI;
154 if (-d '/sys/block/') {
155 opendir(SCSI, '/sys/block/');
156 @drivesSCSI = grep /sd[a-z]/, readdir SCSI;
157 closedir(SCSI);
158 }
159
160 # Look for NVMe drives in /sys
161 my @drivesNVME;
162 if (-d '/sys/block/') {
163 opendir(NVME, '/sys/block/');
164 @drivesNVME = grep /nvme[0-9]+n[0-9]+/, readdir NVME;
165 closedir(NVME);
166 }
167
168 # Get list of all drives we found
169 @drives=(@drivesIDE,@drivesSCSI,@drivesNVME);
170
171} elsif ($^O eq 'freebsd') {
172 opendir(DEV, '/dev');
173 @drives = grep /^(ada?|da)[0-9]+$/, readdir DEV;
174 closedir(DEV);
175} elsif ($^O eq 'solaris') {
176 @drives = map { s@.*/@@ ; $_ } glob '/dev/rdsk/c*t*d*s2';
177}
178
179@drives = split ' ', $ENV{drives} if exists $ENV{drives};
180
181# Sort list of drives
182@drives = sort @drives;
183
184warn "[DEBUG] Drives: ",join(', ',@drives),"\n" if $DEBUG;
185
186if (defined $ARGV[0]) {
187 if ($ARGV[0] eq 'autoconf') {
188 if (@drives) {
189 my $cmd = command_for_drive_device($drives[0],
190 device_for_drive($drives[0]));
191 if (`$cmd` =~ /Temperature/) {
192 print "yes\n";
193 } else {
194 print "no (first drive not supported, configure the plugin)\n";
195 }
196 exit 0;
197 } else {
198 print "no (no drives known)\n";
199 exit 0;
200 }
201 } elsif ($ARGV[0] eq 'config') {
202 print "graph_title HDD temperature\n";
203 print "graph_vlabel Degrees Celsius\n";
204 print "graph_category sensors\n";
205 print "graph_info This graph shows the temperature in degrees Celsius of the hard drives in the machine.\n";
206 foreach (@drives) {
207 my @dirs = splitdir($_);
208 print clean_fieldname($_) . ".label " . $dirs[-1] . "\n";
209 print clean_fieldname($_) . ".max 100\n";
210 print clean_fieldname($_) . ".warning 57\n";
211 print clean_fieldname($_) . ".critical 60\n";
212 }
213 exit 0;
214 }
215}
216
217foreach my $drive (@drives) {
218 warn "[DEBUG] Processing $drive\n" if $DEBUG;
219 my $fulldev = device_for_drive($drive);
220
221 my $cmd = command_for_drive_device($drive, $fulldev);
222 warn "[DEBUG] Command for $drive is % $cmd %\n" if $DEBUG;
223
224 my $output = `$cmd`;
225 my $cmd_exit = $?;
226
227 # Strip header
228 $output =~ s/.*?\n\n//s;
229 # Strip trailer
230 $output =~ s/Please specify device type with the -d option.\n//s;
231 $output =~ s/Use smartctl -h to get a usage summary//s;
232 $output =~ s/\n+$//s;
233
234 if ($cmd_exit != 0) {
235 print "$drive.value U\n";
236 if ($cmd_exit == -1) {
237 warn "[ERROR] Command $cmd on drive $drive failed to execute: $!";
238 } else {
239 my $smartctl_exit = $cmd_exit >> 8;
240 print "$drive.extinfo Command '$cmd' on drive $drive failed with exit($smartctl_exit)\n";
241
242 warn "[ERROR] Command $cmd on drive $drive failed with exit($smartctl_exit): $output";
243 }
244 next;
245 }
246 if ($output =~ /Current Drive Temperature:\s*(\d+)/) {
247 print "$drive.value $1\n";
248 } elsif ($output =~ /^(194 Temperature_(Celsius|Internal).*)/m) {
249 my @F = split /\s+/, $1;
250 print "$drive.value $F[9]\n";
251 } elsif ($output =~ /^(231 Temperature_Celsius.*)/m) {
252 my @F = split ' ', $1;
253 print "$drive.value $F[9]\n";
254 } elsif ($output =~ /^(190 (Airflow_Temperature_Cel|Temperature_Case).*)/m) {
255 my @F = split ' ', $1;
256 print "$drive.value $F[9]\n";
257 } elsif ($output =~ /Temperature:\s*(\d+) Celsius/) {
258 print "$drive.value $1\n";
259 } else {
260 print "$drive.value U\n";
261 print "$drive.extinfo Temperature not detected in smartctl output\n";
262 }
263}
264
265
266sub device_for_drive {
267 my ($drive) = @_;
268
269 my $dev = $drive =~ /(.*)(?:_\d+)$/ ? $1 : $drive;
270
271 my $fulldev = '/dev/';
272 $fulldev .= 'rdsk/' if $^O eq 'solaris';
273 $fulldev .= exists $ENV{'dev_'.$drive} ? $ENV{'dev_'.$drive} : $dev;
274
275 return $fulldev;
276}
277
278
279sub command_for_drive_device {
280 my ($drive, $fulldev) = @_;
281
282 my $cmd = $smartctl.' -A ';
283 $cmd .= $ENV{'args_'.$drive}.' ' if exists $ENV{'args_'.$drive};
284 $cmd .= '-d '.$ENV{'type_'.$drive}.' ' if exists $ENV{'type_'.$drive};
285 $cmd .= $fulldev;
286
287}