-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcheck_smart
332 lines (295 loc) · 12.2 KB
/
check_smart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
#!/usr/bin/perl -w
# Check SMART status of ATA/SCSI disks, returning any usable metrics as perfdata.
# For usage information, run ./check_smart -h
#
# This script was created under contract for the US Government and is therefore Public Domain
#
# Changes and Modifications
# =========================
# Feb 3, 2009: Kurt Yoder - initial version of script
# Mar 3, 2010: Giles Westwood - add support for hardware raid interfaces
use strict;
use Getopt::Long;
use File::Basename qw(basename);
my $basename = basename($0);
my $revision = '$Revision: 1.1 $';
use lib '/usr/local/libexec/nagios/';
use utils qw(%ERRORS &print_revision &support &usage);
$ENV{'PATH'}='/bin:/usr/bin:/sbin:/usr/sbin';
$ENV{'BASH_ENV'}='';
$ENV{'ENV'}='';
use vars qw($opt_d $opt_debug $opt_h $opt_i $opt_v $opt_m);
Getopt::Long::Configure('bundling');
GetOptions(
"debug" => \$opt_debug,
"d=s" => \$opt_d, "device=s" => \$opt_d,
"h" => \$opt_h, "help" => \$opt_h,
"i=s" => \$opt_i, "interface=s" => \$opt_i,
"m=s" => \$opt_m, "mode=s" => \$opt_m,
"v" => \$opt_v, "version" => \$opt_v,
);
if ($opt_v) {
print_revision($basename,$revision);
exit $ERRORS{'OK'};
}
if ($opt_h) {
print_help();
exit $ERRORS{'OK'};
}
my ($device, $interface, $mode) = qw//;
if ($opt_d) {
unless($opt_i){
print "must specify an interface for $opt_d using -i/--interface!\n\n";
print_help();
exit $ERRORS{'UNKNOWN'};
}
if (!$opt_i =~/3ware|cciss|areca/){
if ( -b $opt_d){
$device = $opt_d;
}
else{
print "$opt_d is not a valid block device!\n\n";
print_help();
exit $ERRORS{'UNKNOWN'};
}
}
else{
$device = $opt_d;
}
if($opt_i =~
/ata|scsi|3ware|cciss|areca|sat|marvell|usbsunplus|hpt|usbcypress|usbsunplus|auto/){
$interface = $opt_i;
}
else{
print "invalid interface $opt_i for $opt_d!\n\n";
print_help();
exit $ERRORS{'UNKNOWN'};
}
}
else{
print "must specify a device!\n\n";
print_help();
exit $ERRORS{'UNKNOWN'};
}
if ($opt_m){
if ($opt_m =~ /scsi/){
$mode = 'scsi';
}
if ($opt_m =~ /ata/){
$mode = 'ata';
}
}
else{
$mode = 'unused';
}
my $smart_command = '/usr/local/bin/sudo /usr/local/sbin/smartctl';
my @error_messages = qw//;
my $exit_status = 'OK';
warn "###########################################################\n" if $opt_debug;
warn "(debug) CHECK 1: getting overall SMART health status\n" if $opt_debug;
warn "###########################################################\n\n\n" if $opt_debug;
my $full_command = "$smart_command -d $interface -H $device";
warn "(debug) executing:\n$full_command\n\n" if $opt_debug;
my @output = `$full_command`;
warn "(debug) output:\n@output\n\n" if $opt_debug;
# parse ata output, looking for "health status: passed"
my $found_status = 0;
my $line_str = 'SMART overall-health self-assessment test result: '; # ATA SMART line
my $ok_str = 'PASSED'; # ATA SMART OK string
if ($interface eq 'scsi' || $mode eq 'scsi'){
$line_str = 'SMART Health Status: '; # SCSI SMART line
$ok_str = 'OK'; #SCSI SMART OK string
}
foreach my $line (@output){
if($line =~ /$line_str(.+)/){
$found_status = 1;
warn "(debug) parsing line:\n$line\n\n" if $opt_debug;
if ($1 eq $ok_str) {
warn "(debug) found string '$ok_str'; status OK\n\n" if $opt_debug;
}
else {
warn "(debug) no '$ok_str' status; failing\n\n" if $opt_debug;
push(@error_messages, "Health status: $1");
escalate_status('CRITICAL');
}
}
}
unless ($found_status) {
push(@error_messages, 'No health status line found');
escalate_status('UNKNOWN');
}
warn "###########################################################\n" if $opt_debug;
warn "(debug) CHECK 2: getting silent SMART health check\n" if $opt_debug;
warn "###########################################################\n\n\n" if $opt_debug;
$full_command = "$smart_command -d $interface -q silent -A $device";
warn "(debug) executing:\n$full_command\n\n" if $opt_debug;
system($full_command);
my $return_code = $?;
warn "(debug) exit code:\n$return_code\n\n" if $opt_debug;
if ($return_code & 0x01) {
push(@error_messages, 'Commandline parse failure');
escalate_status('UNKNOWN');
}
if ($return_code & 0x02) {
push(@error_messages, 'Device could not be opened');
escalate_status('UNKNOWN');
}
if ($return_code & 0x04) {
push(@error_messages, 'Checksum failure');
escalate_status('WARNING');
}
if ($return_code & 0x08) {
push(@error_messages, 'Disk is failing');
escalate_status('CRITICAL');
}
if ($return_code & 0x10) {
push(@error_messages, 'Disk is in prefail');
escalate_status('WARNING');
}
if ($return_code & 0x20) {
push(@error_messages, 'Disk may be close to failure');
escalate_status('WARNING');
}
if ($return_code & 0x40) {
push(@error_messages, 'Error log contains errors');
escalate_status('WARNING');
}
if ($return_code & 0x80) {
push(@error_messages, 'Self-test log contains errors');
escalate_status('WARNING');
}
if ($return_code && !$exit_status) {
push(@error_messages, 'Unknown return code');
escalate_status('CRITICAL');
}
if ($return_code) {
warn "(debug) non-zero exit code, generating error condition\n\n" if $opt_debug;
}
else {
warn "(debug) zero exit code, status OK\n\n" if $opt_debug;
}
warn "###########################################################\n" if $opt_debug;
warn "(debug) CHECK 3: getting detailed statistics\n" if $opt_debug;
warn "(debug) information contains a few more potential trouble spots\n" if $opt_debug;
warn "(debug) plus, we can also use the information for perfdata/graphing\n" if $opt_debug;
warn "###########################################################\n\n\n" if $opt_debug;
$full_command = "$smart_command -d $interface -A $device";
warn "(debug) executing:\n$full_command\n\n" if $opt_debug;
@output = `$full_command`;
warn "(debug) output:\n@output\n\n" if $opt_debug;
my @perfdata = qw//;
# separate metric-gathering and output analysis for ATA vs SCSI SMART output
if ($interface eq 'ata' || $mode eq 'ata'){
foreach my $line(@output){
# get lines that look like this:
# 9 Power_On_Minutes 0x0032 241 241 000 Old_age Always - 113h+12m
next unless $line =~ /^\s*\d+\s(\S+)\s+(?:\S+\s+){6}(\S+)\s+(\d+)/;
my ($attribute_name, $when_failed, $raw_value) = ($1, $2, $3);
if ($when_failed ne '-'){
push(@error_messages, "Attribute $attribute_name failed at $when_failed");
escalate_status('WARNING');
warn "(debug) parsed SMART attribute $attribute_name with error condition:\n$when_failed\n\n" if $opt_debug;
}
# some attributes produce questionable data; no need to graph them
if (grep {$_ eq $attribute_name} ('Unknown_Attribute', 'Power_On_Minutes') ){
next;
}
push (@perfdata, "$attribute_name=$raw_value");
# do some manual checks
if ( ($attribute_name eq 'Current_Pending_Sector') && $raw_value ) {
push(@error_messages, "Sectors pending re-allocation");
escalate_status('WARNING');
warn "(debug) Current_Pending_Sector is non-zero ($raw_value)\n\n" if $opt_debug;
}
}
}
else{
my ($current_temperature, $max_temperature, $current_start_stop, $max_start_stop) = qw//;
foreach my $line(@output){
if ($line =~ /Current Drive Temperature:\s+(\d+)/){
$current_temperature = $1;
}
elsif ($line =~ /Drive Trip Temperature:\s+(\d+)/){
$max_temperature = $1;
}
elsif ($line =~ /Current start stop count:\s+(\d+)/){
$current_start_stop = $1;
}
elsif ($line =~ /Recommended maximum start stop count:\s+(\d+)/){
$max_start_stop = $1;
}
elsif ($line =~ /Elements in grown defect list:\s+(\d+)/){
push (@perfdata, "defect_list=$1");
}
elsif ($line =~ /Blocks sent to initiator =\s+(\d+)/){
push (@perfdata, "sent_blocks=$1");
}
}
if($current_temperature){
if($max_temperature){
push (@perfdata, "temperature=$current_temperature;;$max_temperature");
if($current_temperature > $max_temperature){
warn "(debug) Disk temperature is greater than max ($current_temperature > $max_temperature)\n\n" if $opt_debug;
push(@error_messages, 'Disk temperature is higher than maximum');
escalate_status('CRITICAL');
}
}
else{
push (@perfdata, "temperature=$current_temperature");
}
}
if($current_start_stop){
if($max_start_stop){
push (@perfdata, "start_stop=$current_start_stop;$max_start_stop");
if($current_start_stop > $max_start_stop){
warn "(debug) Disk start_stop is greater than max ($current_start_stop > $max_start_stop)\n\n" if $opt_debug;
push(@error_messages, 'Disk start_stop is higher than maximum');
escalate_status('WARNING');
}
}
else{
push (@perfdata, "start_stop=$current_start_stop");
}
}
}
warn "(debug) gathered perfdata:\n@perfdata\n\n" if $opt_debug;
my $perf_string = join(' ', @perfdata);
warn "###########################################################\n" if $opt_debug;
warn "(debug) FINAL STATUS: $exit_status\n" if $opt_debug;
warn "###########################################################\n\n\n" if $opt_debug;
warn "(debug) final status/output:\n" if $opt_debug;
my $status_string = '';
if($exit_status ne 'OK'){
$status_string = "$exit_status: ".join(', ', @error_messages);
}
else {
$status_string = "OK: no SMART errors detected";
}
print "$status_string|$perf_string\n";
exit $ERRORS{$exit_status};
sub print_help {
print_revision($basename,$revision);
print "Usage: $basename (--device=<SMART device> --interface=(ata|scsi)|-h|-v) [--debug]\n";
print " --debug: show debugging information\n";
print " -d/--device: a device to be SMART monitored, eg /dev/sda /dev/twa0\n";
print " -i/--interface: ata, scsi, sat,marvell,3ware,N,areca,N,\n";
print " usbcypress, usbjmicron, usbsunplus, cciss,N, hpt,L/M (or hpt,L/M/N), and test\n";
print " -m/--mode: Set mode to scsi,ata when drives are behind\n";
print " hardware raid interface \n";
print " -h/--help: this help\n";
print " -v/--version: Version number\n";
support();
}
# escalate an exit status IFF it's more severe than the previous exit status
sub escalate_status {
my $requested_status = shift;
# no test for 'CRITICAL'; automatically escalates upwards
if ($requested_status eq 'WARNING') {
return if $exit_status eq 'CRITICAL';
}
if ($requested_status eq 'UNKNOWN') {
return if $exit_status eq 'WARNING';
return if $exit_status eq 'CRITICAL';
}
$exit_status = $requested_status;
}