1#!@@PYTHON@@ 2# 3# Wildcard-plugin to monitor S.M.A.R.T attribute values through smartctl, 4# which is part of smartmontools package: 5# http://smartmontools.sourceforge.net/ 6# 7# To monitor a S.M.A.R.T device, link smart_<device> to this file. 8# E.g. 9# ln -s /usr/share/munin/plugins/smart_ /etc/munin/plugins/smart_hda 10# ...will monitor /dev/hda. 11# 12# Needs following minimal configuration in plugin-conf.d/munin-node: 13# [smart_*] 14# user root 15# group disk 16# 17# Parameters 18# smartpath - Specify path to smartctl program (Default: /usr/sbin/smartctl) 19# smartargs - Override '-a' argument passed to smartctl with '-A -i'+smartargs 20# ignorestandby - Ignore the standby state of the drive and perform SMART query. 21# Default: False 22# ignoreexit - Bits in smartctl exit code to ignore, e.g. 64. Default: 0 23# 24# Parameters can be specified on a per-drive basis, eg: 25# [smart_hda] 26# user root 27# group disk 28# env.smartargs -H -c -l error -l selftest -l selective -d ata 29# env.smartpath /usr/local/sbin/smartctl 30# 31# [smart_twa0-1] 32# user root 33# group disk 34# env.smartargs -H -l error -d 3ware,1 35# env.ignorestandby True 36# 37# [smart_twa0-2] 38# user root 39# group disk 40# env.smartargs -H -l error -d 3ware,2 41# 42# Author: Nicolas Stransky <Nico@stransky.cx> 43# 44# v1.0 22/08/2004 - First draft 45# v1.2 28/08/2004 - Clean up the code, add a verbose option 46# v1.3 14/11/2004 - Compatibility with python<2.2. See comments in the code 47# v1.4 17/11/2004 - Deal with non zero exit codes of smartctl 48# - config now prints the critical thresholds, as reported by smartctl 49# v1.5 18/11/2004 - Plot smartctl_exit_code bitmask 50# v1.6 21/11/2004 - Add autoconf and suggest capabilities 51# - smartctl path can be passed through "smartpath" environment variable 52# - Additional smartctl args can be passed through "smartargs" environment variable 53# v1.7 29/11/2004 - Add suggest capabilities for NetBSD, OpenBSD, FreeBSD and SunOS. 54# - Allow to override completely the smartctl arguments with "smartargs" 55# v1.8 16/02/2005 - Exit status field now only triggers warnings, not criticals. 56# v1.9 07/07/2005 - Allow to query several drives on the same 3ware card. 57# - Correct a bug when '-i' was not listed in smartargs 58# - Don't fail if no value was obtained for hard drive model 59# v1.10 19/08/2005 - smartctl_exit_code is now a numerical value 60# v2.0 08/05/2009 - Correct bug in the interpretation of smartctl_exit_code 61# - New option to suppress SMART warnings in munin 62# - Temporary lack of output for previously existing drive now reports U 63# - The plugin now contains its own documentation for use with munindoc 64# - Removed python<2.2 compatibility comments 65# - Better autodetection of drives 66# - Don't spin up devices in a low-power mode. 67# v2.1 2012-02-14 - Add support for Darwin (Mac OS X). 68# - Print the last line of smartctl output to verbose 69# log to aid in understanding why smartctl exited with 70# a nonzero status. 71# v2.2 2014-08-22 - Add "ignoreexit" environment variable. 72# v2.3 2018-08-10 - Improve readability, reduce code duplication, avoid shell expansion. 73# v2.4 2018-12-19 - Ignore invalid threshold data 74# 75# Copyright (c) 2004-2009 Nicolas Stransky. 76# Copyright (c) 2018 Lars Kruse <devel@sumpfralle.de> 77# 78# Permission to use, copy, and modify this software with or without fee 79# is hereby granted, provided that this entire notice is included in 80# all source code copies of any software which is or includes a copy or 81# modification of this software. 82# 83# THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR 84# IMPLIED WARRANTY. IN PARTICULAR, NONE OF THE AUTHORS MAKES ANY 85# REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE 86# MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR 87# PURPOSE. 88# 89# 90# Magic markers 91# #%# capabilities=autoconf suggest 92# #%# family=auto 93 94import collections 95import os 96import sys 97import string 98import pickle 99import subprocess 100 101# Increase verbosity (True/False) -> use munin-run --pidebug 102verbose = True if os.getenv('MUNIN_DEBUG') == '1' else False 103 104# collect configuration from environment 105smartctl_bin = os.getenv('smartpath', '/usr/sbin/smartctl') 106smartctl_args = os.getenv('smartargs', '-a') 107smartctl_ignore_standby = bool(os.getenv('ignorestandby', False)) 108smartctl_ignore_exitcode_bitmask = int(os.getenv('ignoreexit', 0)) 109 110# You may edit the following 3 variables 111# Suppress SMART warnings (True/False) 112report_warnings = True 113# You may not modify anything below this line 114 115plugin_version = "2.4" 116 117# some disks report invalid threshold values 118INVALID_THRESHOLDS_BLACKLIST = {"---"} 119 120 121SmartCtlParseResult = collections.namedtuple("SmartCtlParseResult", 122 ("has_failed", "smart_data", "model", "is_empty")) 123 124 125def verboselog(s): 126 if verbose: 127 sys.stderr.write('{}: {}\n'.format(plugin_name, s)) 128 129 130def guess_full_path(hard_drive): 131 """ try to find the full path for a given hard disk name 132 133 None is returned if no device node was found. 134 """ 135 for dev_dir in ('/dev', '/dev/disk/by-id'): 136 full_path = os.path.join(dev_dir, hard_drive) 137 if os.path.exists(full_path): 138 return full_path 139 else: 140 return None 141 142 143def is_fatal_exitcode(exit_code): 144 # The exit code represents a bitmask. 145 # Bits 0/1/2 belong to fatal errors (see smartctl's man page). Check if one of these is set. 146 return (exit_code & 0b111) > 0 147 148 149def read_values(hard_drive): 150 smart_values = {} 151 try: 152 verboselog('Reading S.M.A.R.T values') 153 os.putenv('LC_ALL', 'C') 154 device = guess_full_path(hard_drive) 155 command_tokens = [smartctl_bin] + smartctl_args.split() 156 if not smartctl_ignore_standby: 157 command_tokens.extend(('-n', 'standby')) 158 command_tokens.extend(('-A', '-i', device)) 159 proc = subprocess.Popen(command_tokens, stdout=subprocess.PIPE) 160 stdout, stderr = proc.communicate() 161 in_table_data = False 162 last_output_line = None 163 model = "unknown" 164 for line in stdout.decode().splitlines(): 165 if not line: 166 # the table is finished 167 in_table_data = False 168 elif not in_table_data: 169 # process header data 170 if line.startswith('Device Model:') or line.startswith('Device:'): 171 value = line.split(':', 1)[1].strip() 172 # ignore the "Version" string 173 model = ' '.join(token for token in value.split() if token != 'Version') 174 elif line.startswith('ID# ATTRIBUTE_NAME'): 175 # Start reading the Attributes block 176 in_table_data = True 177 else: 178 # we can ignore other header lines 179 pass 180 else: 181 # this is a data table row 182 tokens = line.split() 183 key = tokens[1].replace('-', '_') 184 value = tokens[3] 185 threshold = None if tokens[5] in INVALID_THRESHOLDS_BLACKLIST else tokens[5] 186 smart_values[key] = {"value": value, "threshold": threshold} 187 last_output_line = line 188 real_exit_code = proc.returncode 189 if real_exit_code > 0: 190 # Allow to turn off warnings for some bits 191 num_exit_status = real_exit_code & ~smartctl_ignore_exitcode_bitmask 192 else: 193 num_exit_status = 0 194 if num_exit_status != 0: 195 if is_fatal_exitcode(num_exit_status): 196 verboselog('smartctl cannot access S.M.A.R.T values on drive {}. Command exited ' 197 'with code {}'.format(hard_drive, num_exit_status)) 198 verboselog(last_output_line) 199 else: 200 # the error is not fatal, but we should announce a warning 201 verboselog('smartctl exited with code {}. {} may be FAILING RIGHT NOW!' 202 .format(num_exit_status, hard_drive)) 203 except Exception as exc: 204 verboselog('Cannot access S.M.A.R.T values ({})! Check user rights or proper ' 205 'smartmontools installation/arguments.'.format(exc)) 206 sys.exit(1) 207 if not smart_values: 208 verboselog("Can't find any S.M.A.R.T values in smartctl output!") 209 is_empty = True 210 else: 211 is_empty = False 212 smart_values["smartctl_exit_status"] = {"value": str(num_exit_status), "threshold": "1"} 213 return SmartCtlParseResult(is_fatal_exitcode(real_exit_code), smart_values, model, is_empty) 214 215 216def get_state_filename(hard_drive): 217 statefiledir = os.environ['MUNIN_PLUGSTATE'] 218 return os.path.join(statefiledir, "smart-{}.state".format(hard_drive)) 219 220 221def open_state_file(hard_drive, mode): 222 return open(get_state_filename(hard_drive), mode) 223 224 225def update_state_file(hard_drive, model, smart_values): 226 data_storage = dict(smart_values) 227 data_storage["model"] = model 228 try: 229 verboselog('Saving statefile') 230 pickle.dump(data_storage, open_state_file(hard_drive, "wb")) 231 except Exception as exc: 232 verboselog('Error trying to save state file ({})! Check access rights'.format(exc)) 233 234 235def parse_state_file(hard_drive): 236 data = pickle.load(open_state_file(hard_drive, "rb")) 237 model = data.pop("model", "unknown") 238 return model, data 239 240 241def print_plugin_values(hard_drive, is_empty, smart_values): 242 if not is_empty: 243 verboselog('Printing S.M.A.R.T values') 244 for key, content in smart_values.items(): 245 print("{}.value {}".format(key, content["value"])) 246 else: 247 print_unknown_from_statefile(hard_drive) 248 249 250def print_config(hard_drive): 251 if os.path.exists(get_state_filename(hard_drive)): 252 try: 253 verboselog('Try to recall previous S.M.A.R.T attributes for {}' 254 .format(hard_drive)) 255 model, smart_values_state = parse_state_file(hard_drive) 256 except Exception as exc: 257 verboselog('Error opening existing state file ({})!'.format(exc)) 258 sys.exit(1) 259 else: 260 verboselog('No state file, reading S.M.A.R.T values for the first time') 261 parsed_data = read_values(hard_drive) 262 update_state_file(hard_drive, parsed_data.model, parsed_data.smart_data) 263 model = parsed_data.model 264 smart_values_state = parsed_data.smart_data 265 266 verboselog('Printing configuration') 267 print('graph_title S.M.A.R.T values for drive {}'.format(hard_drive)) 268 print('graph_vlabel Attribute S.M.A.R.T value') 269 print('graph_args --base 1000 --lower-limit 0') 270 print('graph_category disk') 271 print('graph_info This graph shows the value of all S.M.A.R.T attributes of drive {} ({}). ' 272 'smartctl_exit_status is the return value of smartctl. A non-zero return value ' 273 'indicates an error, a potential error, or a fault on the drive.' 274 .format(hard_drive, model)) 275 for key, content in smart_values_state.items(): 276 print('{}.label {}'.format(key, key)) 277 if report_warnings: 278 if content["threshold"] is None: 279 # we did not parse a valid threshold 280 pass 281 elif key == 'smartctl_exit_status': 282 level = 'warning' 283 print('{}.{} {}'.format(key, level, content["threshold"])) 284 else: 285 level = "critical" 286 print('{}.{} {}:'.format(key, level, content["threshold"])) 287 288 289def print_unknown_from_statefile(hard_drive): 290 if os.path.exists(get_state_filename(hard_drive)): 291 try: 292 verboselog('Failed to get S.M.A.R.T values from drive. Try to recall previous ' 293 'S.M.A.R.T attributes for {}'.format(hard_drive)) 294 model, smart_values_state = parse_state_file(hard_drive) 295 except Exception as exc: 296 verboselog('Error opening existing state file ({})!'.format(exc)) 297 sys.exit(1) 298 else: 299 verboselog('No state file, reading S.M.A.R.T values for the first time') 300 sys.exit(1) 301 302 verboselog('Printing unknown values for all attributes in state file') 303 for key in smart_values_state.keys(): 304 print('{}.value U'.format(key)) 305 306 307def get_hard_drive_name(plugin_name): 308 try: 309 name = plugin_name.split('_', 1)[1] 310 if os.uname()[0] == "SunOS": 311 # Special handling of hard_drive names starting with "rdsk" or "rmt". 312 # These are changed from "rdsk0" to "rdsk/0". 313 for prefix in ('rdsk', 'rmt'): 314 if name.startswith(prefix): 315 name = os.path.join(prefix, name[len(prefix):]) 316 if guess_full_path(name) is None: 317 # For 3ware cards, we have to set multiple plugins for the same hard drive name. 318 # Let's see if we find a '-' in the drive name. 319 if '-' in name: 320 name = name.split('-')[0] 321 # Chech that the drive exists in /dev 322 if guess_full_path(name) is None: 323 verboselog('/dev/(disk/by-id/)? {} not found!'.format(name)) 324 sys.exit(1) 325 return name 326 except Exception as exc: 327 verboselog("No S.M.A.R.T device name found in plugin's symlink ({})!".format(exc)) 328 sys.exit(1) 329 330 331def find_smart_drives(): 332 # Try to autodetect Linux, *BSD, SunOS drives. Don't try to autodetect drives on a 3Ware card. 333 drives = [] 334 if os.uname()[0] == "Linux": 335 if os.path.exists('/sys/block/'): 336 # Running 2.6 337 try: 338 for drive in os.listdir('/sys/block/'): 339 if drive[:2] in ['md', 'fd', 'lo', 'ra', 'dm']: 340 continue # Ignore MD, Floppy, loop , RAM and LVM devices. 341 try: 342 verboselog('Trying {} ...'.format(drive)) 343 parsed_data = read_values(drive) 344 if not parsed_data.has_failed and not parsed_data.is_empty: 345 drives.append(drive) 346 except Exception: 347 continue 348 except Exception: 349 verboselog('Failed to list devices in /sys/block') 350 else: 351 verboselog('Not running linux2.6, failing back to /proc/partitions') 352 try: 353 partitions = open('/proc/partitions', 'r') 354 lines = partitions.readlines() 355 for line in lines: 356 words = line.split() 357 if len(words) == 0 or words[0][0] not in string.digits: 358 continue 359 if words[0] in ['1', '9', '58', '254']: 360 # Ignore RAM, md, LVM and LVM2 devices 361 continue 362 if words[-1][-1] not in string.digits: 363 try: 364 verboselog('Trying '+words[-1]+'...') 365 parsed_data = read_values(words[-1]) 366 if not parsed_data.has_failed and not parsed_data.is_empty: 367 drives.append(words[-1]) 368 except Exception: 369 continue 370 verboselog('Found drives in /proc/partitions ! '+str(drives)) 371 except Exception as exc: 372 verboselog('Failed to list devices in /proc/partitions: {}'.format(exc)) 373 elif os.uname()[0] == "OpenBSD": 374 try: 375 sysctl_kerndisks = os.popen('sysctl hw.disknames') 376 kerndisks = sysctl_kerndisks.readline().strip() 377 for drive in kerndisks[kerndisks.rindex('=')+1:].split(','): 378 if drive[:2] in ['md', 'cd', 'fd']: 379 # Ignore Memory Disks, CD-ROM drives and Floppy 380 continue 381 try: 382 verboselog('Trying '+drive+'c...') 383 parsed_data = read_values(drive + 'c') 384 if not parsed_data.has_failed and not parsed_data.is_empty: 385 drives.append(drive+'c') 386 except Exception: 387 continue 388 except Exception as exc: 389 verboselog('Failed to list OpenBSD disks: {}'.format(exc)) 390 elif os.uname()[0] == "FreeBSD": 391 try: 392 sysctl_kerndisks = os.popen('sysctl kern.disks') 393 kerndisks = sysctl_kerndisks.readline().strip() 394 for drive in kerndisks.split()[1:]: 395 if drive[:2] in ['md', 'cd', 'fd']: 396 # Ignore Memory Disks, CD-ROM drives and Floppy 397 continue 398 try: 399 verboselog('Trying '+drive+'...') 400 parsed_data = read_values(drive) 401 if not parsed_data.has_failed and not parsed_data.is_empty: 402 drives.append(drive) 403 except Exception: 404 continue 405 except Exception as exc: 406 verboselog('Failed to list FreeBSD disks: {}'.format(exc)) 407 elif os.uname()[0] == "Darwin": 408 try: 409 from glob import glob 410 for drivepath in glob('/dev/disk[0-9]'): 411 try: 412 drive = os.path.basename(drivepath) 413 verboselog('Trying '+drive+'...') 414 parsed_data = read_values(drive) 415 if not parsed_data.has_failed and not parsed_data.is_empty: 416 drives.append(drive) 417 except Exception: 418 continue 419 except Exception as exc: 420 verboselog('Failed to list Darwin disks: {}'.format(exc)) 421 elif os.uname()[0] == "NetBSD": 422 try: 423 sysctl_kerndisks = os.popen('sysctl hw.disknames') 424 kerndisks = sysctl_kerndisks.readline().strip() 425 for drive in kerndisks.split()[2:]: 426 if drive[:2] in ['md', 'cd', 'fd']: 427 # Ignore Memory Disks, CD-ROM drives and Floppy 428 continue 429 try: 430 verboselog('Trying {} ...'.format(drive)) 431 parsed_data = read_values(drive + 'c') 432 if not parsed_data.has_failed and not parsed_data.is_empty: 433 drives.append(drive + 'c') 434 except Exception: 435 continue 436 except Exception as exc: 437 verboselog('Failed to list NetBSD disks: {}'.format(exc)) 438 elif os.uname()[0] == "SunOS": 439 try: 440 from glob import glob 441 for drivepath in glob('/dev/rdsk/*s2'): 442 try: 443 drive = os.path.basename(drivepath) 444 verboselog('Trying rdsk {} ...'.format(drive)) 445 parsed_data = read_values('rdsk' + drive) 446 if not parsed_data.has_failed and not parsed_data.is_empty: 447 drives.append('rdsk' + drive) 448 except Exception: 449 continue 450 for drivepath in glob('/dev/rmt/*'): 451 try: 452 drive = os.path.basename(drivepath) 453 verboselog('Trying rmt {} ...'.format(drive)) 454 parsed_data = read_values('rmt' + drive) 455 if not parsed_data.has_failed and not parsed_data.is_empty: 456 drives.append('rmt' + drive) 457 except Exception: 458 continue 459 except Exception: 460 verboselog('Failed to list SunOS disks') 461 return drives 462 463 464""" Main part """ 465 466plugin_name = list(os.path.split(sys.argv[0]))[1] 467verboselog("plugins' UID: {:d} / plugins' GID: {:d}".format(os.geteuid(), os.getegid())) 468 469# Parse arguments 470if len(sys.argv) > 1: 471 if sys.argv[1] == "config": 472 hard_drive = get_hard_drive_name(plugin_name) 473 print_config(hard_drive) 474 sys.exit(0) 475 elif sys.argv[1] == "autoconf": 476 if os.path.exists(smartctl_bin): 477 if not find_smart_drives(): 478 print('no (no drives accessible)') 479 else: 480 print('yes') 481 sys.exit(0) 482 else: 483 print('no (smartmontools not found)') 484 sys.exit(0) 485 elif sys.argv[1] == "suggest": 486 for drive in find_smart_drives(): 487 print(drive) 488 sys.exit(0) 489 elif sys.argv[1] == "version": 490 print('smart_ Munin plugin, version '+plugin_version) 491 sys.exit(0) 492 elif sys.argv[1] != "": 493 verboselog('unknown argument "'+sys.argv[1]+'"') 494 sys.exit(1) 495 496# No argument given, doing the real job: 497hard_drive = get_hard_drive_name(plugin_name) 498parsed_data = read_values(hard_drive) 499if not parsed_data.is_empty: 500 update_state_file(hard_drive, parsed_data.model, parsed_data.smart_data) 501print_plugin_values(hard_drive, parsed_data.is_empty, parsed_data.smart_data) 502sys.exit(0) 503 504 505# The following is the smart_ plugin documentation, intended to be used with munindoc 506""" 507=head1 NAME 508 509smart_ - Munin wildcard-plugin to monitor S.M.A.R.T. attribute values through smartctl 510 511=head1 APPLICABLE SYSTEMS 512 513Node with B<Python> interpreter and B<smartmontools> (http://smartmontools.sourceforge.net/) 514installed and in function. 515 516=head1 CONFIGURATION 517 518=head2 Create link in service directory 519 520To monitor a S.M.A.R.T device, create a link in the service directory 521of the munin-node named smart_<device>, which is pointing to this file. 522 523E.g. 524 525ln -s /usr/share/munin/plugins/smart_ /etc/munin/plugins/smart_hda 526 527...will monitor /dev/hda. 528 529=head2 Grant privileges in munin-node 530 531The plugin must be run under high privileged user B<root>, to get access to the raw device. 532 533So following minimal configuration in plugin-conf.d/munin-node is needed. 534 535=over 2 536 537 [smart_*] 538 user root 539 group disk 540 541=back 542 543=head2 Set Parameter if needed 544 545 smartpath - Specify path to smartctl program (Default: /usr/sbin/smartctl) 546 smartargs - Override '-a' argument passed to smartctl with '-A -i'+smartargs 547 ignorestandby - Ignore the standby state of the drive and perform SMART query. Default: False 548 ignoreexit - Bits in smartctl exit code to ignore, e.g. 64. Default: 0 549 550Parameters can be specified on a per-drive basis, eg: 551 552=over 2 553 554 [smart_hda] 555 user root 556 env.smartargs -H -c -l error -l selftest -l selective -d ata 557 env.smartpath /usr/local/sbin/smartctl 558 559=back 560 561In particular, for SATA drives, with older versions of smartctl: 562 563=over 2 564 565 [smart_sda] 566 user root 567 env.smartargs -d ata -a 568 569 [smart_twa0-1] 570 user root 571 env.smartargs -H -l error -d 3ware,1 572 env.ignorestandby True 573 574 [smart_twa0-2] 575 user root 576 env.smartargs -H -l error -d 3ware,2 577 578=back 579 580The C<ignoreexit> parameter can be useful to exclude some bits in smartctl exit 581code, which is a bit mask described in its main page, from consideration. For 582example, if the drive had any errors in the past, the exit code would always 583have its bit 6 ("The device error log contains records of errors.") set, even if 584the errors happened a long time ago and are not relevant any more. To avoid 585getting munin warnings about this you can use 586 587=over 2 588 589 [smart_sda] 590 env.ignoreexit 64 591 592=back 593 594 595=head1 INTERPRETATION 596 597If a device supports the B<Self-Monitoring, Analysis 598and Reporting Technology (S.M.A.R.T.)> it offers readable 599access to the attribute table. There you find the B<raw value>, 600a B<normalised value> and a B<threshold> (set by the vendor) 601for each attribute, that is supported by that device. 602 603The meaning and handling of the raw value is a secret of the 604vendors embedded S.M.A.R.T.-Software on the disk. The only 605relevant info from our external view is the B<normalised value> 606in comparison with the B<threshold>. If the attributes value is 607equal or below the threshold, it signals its failure and 608the B<health status> of the device will switch from B<passed> to B<failed>. 609 610This plugin fetches the B<normalised values of all SMART-Attributes> 611and draw a curve for each of them. 612It takes the vendors threshold as critical limit for the munin datafield. 613So you will see an alarm, if the value reaches the vendors threshold. 614 615Looking at the graph: It is a bad sign, if the curve starts 616to curl or to meander. The more horizontal it runs, 617the better. Of course it is normal, that the temperatures 618curve swings a bit. But the others should stay steady on 619their level if everything is ok. 620 621S.M.A.R.T. distinguishes between B<Pre-fail> and B<Old-age> 622Attributes. An old disk will have more curling curves 623because of degradation, especially for the B<Old-age> Attributes. 624You should then backup more often, run more selftests[1] and prepare 625the disks replacement. 626 627B<Act directly>, if a <Pre-Fail> Attribute goes below threshold. 628Immediately back-up your data and replace your hard disk drive. 629A failure may be imminent.. 630 631[1] Consult the smartmontools manpages to learn about 632offline tests and automated selftests with smartd. 633Only with both activated, the values of the SMART-Attributes 634reflect the all over state of the device. 635 636Tutorials and articles about S.M.A.R.T. and smartmontools: 637http://smartmontools.sourceforge.net/doc.html#tutorials 638 639=head1 MAGIC MARKERS 640 641 #%# family=auto 642 #%# capabilities=autoconf suggest 643 644=head1 CALL OPTIONS 645 646B<none> 647 648=over 2 649 650Fetches values if called without arguments: 651 652E.g.: munin-run smart_hda 653 654=back 655 656B<config> 657 658=over 2 659 660Prints plugins configuration. 661 662E.g.: munin-run smart_hda config 663 664=back 665 666B<autoconf> 667 668=over 2 669 670Tries to find smartctl and outputs value 'yes' for success, 'no' if not. 671 672It's used by B<munin-node-configure> to see whether autoconfiguration is possible. 673 674=back 675 676B<suggest> 677 678=over 2 679 680Outputs the list of device names, that it found plugged to the system. 681 682B<munin-node-configure> use this to build the service links for this wildcard-plugin. 683 684=back 685 686=head1 VERSION 687 688Version 2.2 689 690=head1 BUGS 691 692None known 693 694=head1 AUTHOR 695 696(C) 2004-2009 Nicolas Stransky <Nico@stransky.cx> 697 698(C) 2008 Gabriele Pohl <contact@dipohl.de> 699Reformated existent documentation to POD-Style, added section Interpretation to the documentation. 700 701=head1 LICENSE 702 703GPLv2 (http://www.gnu.org/licenses/gpl-2.0.txt) 704 705=cut 706 707 708""" 709