Skip to content

Instantly share code, notes, and snippets.

@dxiri
Created June 27, 2019 16:22
Show Gist options
  • Save dxiri/db972be89682cd755c3bd43deea71a76 to your computer and use it in GitHub Desktop.
Save dxiri/db972be89682cd755c3bd43deea71a76 to your computer and use it in GitHub Desktop.
check_smartctl
#!/usr/bin/python
# -*- coding: iso8859-1 -*-
#
# $Id: version.py 133 2006-03-24 10:30:20Z fuller $
#
# check_smartmon
# Copyright (C) 2006 daemogorgon.net
# Copyright (C) 2010 Orcan Ogetbil (orcan at nbcs.rutgers.edu)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Package versioning
"""
import os.path
import subprocess
import sys
import time
from optparse import OptionParser
from operator import itemgetter, attrgetter
__author__ = "fuller <fuller@daemogorgon.net>"
__version__ = "$Revision$"
# path to smartctl
_smartctlPath = "/usr/sbin/smartctl"
# application wide verbosity (can be adjusted with -v [0-3])
_verbosity = 0
is_array = lambda var: isinstance(var, (list, tuple))
def parseCmdLine(args):
"""Commandline parsing."""
usage = "usage: %prog [options] device"
version = "%%prog %s" % (__version__)
parser = OptionParser(usage=usage, version=version)
parser.add_option("-d", "--devicetype", action="store", dest="devicetype", default="scsi", metavar="DEVICETYPE",
help="device type (scsi or megaraid,N or ...; defaults to scsi)")
parser.add_option("-v", "--verbosity", action="store",
dest="verbosity", type="int", default=0,
metavar="LEVEL", help="set verbosity level to LEVEL; defaults to 0 (quiet), \
possible values go up to 3")
parser.add_option("-w", "--warning-threshold", metavar="TEMP", action="store",
type="int", dest="warningThreshold", default=55,
help="set temperature warning threshold to given temperature (defaults to 55)")
parser.add_option("-c", "--critical-threshold", metavar="TEMP", action="store",
type="int", dest="criticalThreshold", default="60",
help="set temperature critical threshold to given temperature (defaults to 60)")
(options,devices) = parser.parse_args(sys.argv[1:])
if len(devices) ==0:
exitWithMessage(3,"UNKNOWN: Error, at least one device must be entered")
return (options,devices)
# end
def checkDevice(path):
"""Check if device exists and permissions are ok.
Returns:
- 0 ok
- 1 no such device
"""
vprint(3, "Check if %s does exist and can be read" % path)
if not os.access(path, os.F_OK):
return (1, "UNKNOWN: no such device found (%s)" % (path))
# We can't check the read permissions as unprivileged user - Orcan
#elif not os.access(path, os.R_OK):
# return (2, "UNKNOWN: no read permission given (%s)" % (path))
else:
return (0, "")
# fi
# end
def checkSmartMonTools(path):
"""Check if smartctl is available and can be executed.
Returns:
- 0 ok
- 1 no such file
- 2 cannot execute file
"""
vprint(3, "Check if %s does exist and can be read" % path)
if not os.access(path, os.F_OK):
return (1, "UNKNOWN: cannot find %s" % path)
elif not os.access(path, os.X_OK):
return (2, "UNKNOWN: cannot execute %s" % path)
else:
return (0, "")
# fi
# end
def callSmartMonTools(path, devicetype, device):
# get health status
cmd = "sudo %s -d %s %s -a" % (path, devicetype, device)
vprint(3, "Get device health status: %s" % cmd)
sp = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# See if smartctl exits cleanly
# This is a lot hacky since smartctl output is not consistent. It doesn't always
# close output streams stdout, stderr etc. I really don't like the following
# code - Orcan
i = 0
poll = False
while i < 5:
if sp.poll():
poll = True
break
i = i+1
vprint(3, "smartctl did not exit yet. Waiting...")
time.sleep(0.1)
if poll: # clean
(child_stdin, child_stdout, child_stderr) = (sp.stdin, sp.stdout, sp.stderr)
child_stdout = child_stdout.readlines()
child_stderr = child_stderr.readline()
vprint(3, "smartctl did exit cleanly")
else: # not clean. let's gather what we have
vprint(3, "smartctl did not exit cleanly")
(child_stdout, child_stderr) = sp.communicate()
if len(child_stderr):
return (3, "UNKNOWN: call exits unexpectedly (%s)" % child_stderr, "",
"")
StatusOutput = ""
faultline = ""
for line in child_stdout:
if line.find("INVALID ARGUMENT TO -d") > 0 or line.find("Unknown device type") > -1:
faultline = line
continue
if faultline != "":
return (3, faultline + line, "")
StatusOutput = StatusOutput + line
# done
return (0 ,"", StatusOutput)
# end
def parseOutput(Message):
"""Parse smartctl output
Returns (health status, temperature).
"""
# parse health status and temperature
healthStatus=""
temperature = None
lines = Message.split("\n")
for line in lines:
if line.find("INQUIRY failed") > -1:
exitWithMessage(1, "UNKNOWN: " + line)
if line.startswith("SMART Health Status:") or line.startswith("SMART overall-health self-assessment"):
healthStatus = line.split()[-1]
if line.startswith("Current Drive Temperature:"):
try:
temperature = int(line.split()[-2])
except:
temperature = -100
break
vprint(3, "Health status: %s" % healthStatus)
vprint(3, "Temperature: %s" %temperature)
return (healthStatus, temperature)
# end
def createReturnInfo(device, devicetype, healthStatus, temperature, warningThreshold,
criticalThreshold):
"""Create return information according to given thresholds."""
# this is absolutely critical!
if healthStatus != "PASSED" and healthStatus != "OK":
if healthStatus == "":
return (2, "CRITICAL: device %s of type %s did not pass a health status. \n" % (device, devicetype))
return (2, "CRITICAL: device %s of type %s passed health status: (%s) \n" % (device, devicetype, healthStatus))
elif temperature == None and devicetype != "ata":
return (2, "CRITICAL: device %s of type %s does not pass temperature information \n" % (device, devicetype))
elif temperature > criticalThreshold:
return (2, "CRITICAL: device %s of type %s temperature (%d) exceeds critical temperature threshold (%s) \n" % (device, devicetype, temperature, criticalThreshold))
elif temperature > warningThreshold:
return (1, "WARNING: device %s of type %s temperature (%d) exceeds warning temperature threshold (%s) \n" % (device, devicetype, temperature, warningThreshold))
else:
if temperature == None:
temperature = "N/A"
return (0, "OK: device %s of type %s is functional and stable (temperature: %s C) \n" % ( device , devicetype, str(temperature)))
# fi
# end
def exitWithMessage(value, message = ""):
"""Exit with given value."""
if message:
print message
sys.exit(value)
# end
def vprint(level, message):
"""Verbosity print.
Decide according to the given verbosity level if the message will be
printed to stdout.
"""
if level <= verbosity:
print message
# fi
# end
def allDeviceTypes(devicetype):
dt_array = devicetype.split(",")
if len(dt_array) == 1:
return dt_array
dt_base = dt_array[0]
dt_all = []
for ext in dt_array[1:]:
dt_all.append(dt_base+","+ext)
return dt_all
if __name__ == "__main__":
(options, devices) = parseCmdLine(sys.argv)
verbosity = options.verbosity
vprint(2, "Get device name(s)B")
overallvalue = 0
devicetypes = allDeviceTypes(options.devicetype)
full_message = []
for device in devices:
for devicetype in devicetypes:
vprint(1, "Device: %s" % device)
# check if we can access 'path'
vprint(2, "Check device")
(value, message) = checkDevice(device)
if value != 0:
exitWithMessage(3, message)
# fi
# check if we have smartctl available
(value, message) = checkSmartMonTools(_smartctlPath)
if value != 0:
exitWithMessage(3, message)
# fi
vprint(1, "Path to smartctl: %s" % _smartctlPath)
# call smartctl and parse output
vprint(2, "Call smartctl")
(value, message, Output) = callSmartMonTools(_smartctlPath, devicetype, device)
if value != 0:
exitWithMessage(3, message)
vprint(2, "Parse smartctl output")
(healthStatus, temperature) = parseOutput(Output)
vprint(2, "Generate return information")
(value, message) = createReturnInfo(device, devicetype, healthStatus, temperature,
options.warningThreshold, options.criticalThreshold)
if value > overallvalue:
overallvalue = value
full_message.append((value,message))
full_message = sorted(full_message, key=itemgetter(1))
message_string = ""
for value,message in full_message:
message_string += message + " "
# exit program
exitWithMessage(overallvalue, message_string[:-1])
# fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment