]> jfr.im git - munin-plugins.git/blob - hddtemp_smartctl
support for a 2nd unbound plugin
[munin-plugins.git] / hddtemp_smartctl
1 #!/usr/bin/perl -w
2 # -*- perl -*-
3
4 use strict;
5 use warnings;
6
7 =head1 NAME
8
9 hddtemp_smartctl - Plugin to monitor harddrive temperatures through
10 SMART
11
12 =head1 CONFIGURATION
13
14 This plugin needs to run as root or some other user that has access to
15 the harddrive devices.
16
17 The 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
30 If the "smartctl" environment variable is not set the plugin will
31 search your $PATH, /usr/bin, /usr/sbin, /usr/local/bin and
32 /usr/local/sbin for a file called "smartctl", and use that.
33
34 If the "drives" environment variable is not set, the plugin will
35 attempt to search for drives to probe.
36
37 =head1 MAGIC MARKERS
38
39 #%# family=auto
40 #%# capabilities=autoconf
41
42 =head1 AUTHOR
43
44 Copyright (c) 2005, Lutz Peter Christoph
45 All rights reserved.
46
47 2016-08-27, Gabriele Pohl (contact@dipohl.de)
48 Fix for github issue #690
49
50 =head1 LICENSE
51
52 Redistribution and use in source and binary forms, with or without
53 modification, are permitted provided that the following conditions
54 are 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
69 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
70 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
71 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
72 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
73 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
74 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
75 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
76 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
77 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
78 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
79 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
80
81 =head1 NOTES
82
83 Note for users of RAID controllers (smartmontools currently only
84 supports 3ware): you can specify the drives attached to your RAID
85 controller(s) as raiddev_num (e.g. sda_0). Then you must specify the
86 type like this: type_sda_0 3ware,0.
87
88 Recent versions of the kernel driver use a separate major device
89 number for monitoring purposes, like /dev/twe<n> or /dev/twa<n>. This
90 can be put in the e.g. dev_sda environment variable, to allow the user
91 to keep sda as the name of the disk.
92
93 =cut
94
95 use File::Spec::Functions qw(splitdir);
96 use lib $ENV{'MUNIN_LIBDIR'};
97 use Munin::Plugin;
98
99 my $DEBUG = $ENV{'MUNIN_DEBUG'} || 0;
100
101 my $smartctl;
102
103 if (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
140 my @drives;
141
142 # Try to get a default set of drives
143 if ($^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
184 warn "[DEBUG] Drives: ",join(', ',@drives),"\n" if $DEBUG;
185
186 if (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
217 foreach 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
266 sub 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
279 sub 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 }