Removed pySMART version hack, now using pySMART.smartx from pip

This commit is contained in:
Tim Nibert 2018-08-22 20:36:36 +10:00
parent c15a970937
commit 4259f78eda
8 changed files with 7 additions and 1663 deletions

View File

@ -1,113 +0,0 @@
# Copyright (C) 2014 Marc Herndon
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
# version 2, as published by the Free Software Foundation.
#
# 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.
#
################################################################
"""
Copyright (C) 2014 Marc Herndon
pySMART is a simple Python wrapper for the `smartctl` component of
`smartmontools`. It works under Linux and Windows, as long as smartctl is on
the system path. Running with administrative (root) privilege is strongly
recommended, as smartctl cannot accurately detect all device types or parse
all SMART information without full permissions.
With only a device's name (ie: /dev/sda, pd0), the API will create a
`Device` object, populated with all relevant information about
that device. The documented API can then be used to query this object for
information, initiate device self-tests, and perform other functions.
Usage
-----
The most common way to use pySMART is to create a logical representation of the
physical storage device that you would like to work with, as shown:
#!bash
>>> from pySMART import Device
>>> sda = Device('/dev/sda')
>>> sda
<SATA device on /dev/sda mod:WDC WD5000AAKS-60Z1A0 sn:WD-WCAWFxxxxxxx>
`Device` class members can be accessed directly, and a number of helper methods
are provided to retrieve information in bulk. Some examples are shown below:
#!bash
>>> sda.assessment # Query the SMART self-assessment
'PASS'
>>> sda.attributes[9] # Query a single SMART attribute
<SMART Attribute 'Power_On_Hours' 068/000 raw:23644>
>>> sda.all_attributes() # Print the entire SMART attribute table
ID# ATTRIBUTE_NAME CUR WST THR TYPE UPDATED WHEN_FAIL RAW
1 Raw_Read_Error_Rate 200 200 051 Pre-fail Always - 0
3 Spin_Up_Time 141 140 021 Pre-fail Always - 3908
4 Start_Stop_Count 098 098 000 Old_age Always - 2690
5 Reallocated_Sector_Ct 200 200 140 Pre-fail Always - 0
... # Edited for brevity
199 UDMA_CRC_Error_Count 200 200 000 Old_age Always - 0
200 Multi_Zone_Error_Rate 200 200 000 Old_age Offline - 0
>>> sda.tests[0] # Query the most recent self-test result
<SMART Self-test [Short offline|Completed without error] hrs:23734 LBA:->
>>> sda.all_selftests() # Print the entire self-test log
ID Test_Description Status Left Hours 1st_Error@LBA
1 Short offline Completed without error 00% 23734 -
2 Short offline Completed without error 00% 23734 -
... # Edited for brevity
7 Short offline Completed without error 00% 23726 -
8 Short offline Completed without error 00% 1 -
Alternatively, the package provides a `DeviceList` class. When instantiated,
this will auto-detect all local storage devices and create a list containing
one `Device` object for each detected storage device.
#!bash
>>> from pySMART import DeviceList
>>> devlist = DeviceList()
>>> devlist
<DeviceList contents:
<SAT device on /dev/sdb mod:WDC WD20EADS-00R6B0 sn:WD-WCAVYxxxxxxx>
<SAT device on /dev/sdc mod:WDC WD20EADS-00S2B0 sn:WD-WCAVYxxxxxxx>
<CSMI device on /dev/csmi0,0 mod:WDC WD5000AAKS-60Z1A0 sn:WD-WCAWFxxxxxxx>
>
>>> devlist.devices[0].attributes[5] # Access Device data as above
<SMART Attribute 'Reallocated_Sector_Ct' 173/140 raw:214>
Using the pySMART wrapper, Python applications be be rapidly developed to take
advantage of the powerful features of smartmontools.
Acknowledgements
----------------
I would like to thank the entire team behind smartmontools for creating and
maintaining such a fantastic product.
In particular I want to thank Christian Franke, who maintains the Windows port
of the software. For several years I have written Windows batch files that
rely on smartctl.exe to automate evaluation and testing of large pools of
storage devices under Windows. Without his work, my job would have been
significantly more miserable. :)
Having recently migrated my development from Batch to Python for Linux
portabiity, I thought a simple wrapper for smartctl would save time in the
development of future automated test tools.
"""
from . import utils
utils.configure_trace_logging()
from .attribute import Attribute
from .device import Device, smart_health_assement
from .device_list import DeviceList
from .test_entry import Test_Entry
__version__ = '0.3'

View File

@ -1,99 +0,0 @@
# Copyright (C) 2014 Marc Herndon
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
# version 2, as published by the Free Software Foundation.
#
# 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.
#
################################################################
"""
This module contains the definition of the `Attribute` class, used to represent
individual SMART attributes associated with a `Device`.
"""
class Attribute(object):
"""
Contains all of the information associated with a single SMART attribute
in a `Device`'s SMART table. This data is intended to exactly mirror that
obtained through smartctl.
"""
def __init__(self, num, name, flags, value, worst, thresh, attr_type, updated, when_failed, raw):
self.num = num
"""**(str):** Attribute's ID as a decimal value (1-255)."""
self.name = name
"""
**(str):** Attribute's name, as reported by smartmontools' drive.db.
"""
self.flags = flags
"""**(str):** Attribute flags as a hexadecimal value (ie: 0x0032)."""
self.value = value
"""**(str):** Attribute's current normalized value."""
self.worst = worst
"""**(str):** Worst recorded normalized value for this attribute."""
self.thresh = thresh
"""**(str):** Attribute's failure threshold."""
self.type = attr_type
"""**(str):** Attribute's type, generally 'pre-fail' or 'old-age'."""
self.updated = updated
"""
**(str):** When is this attribute updated? Generally 'Always' or
'Offline'
"""
self.when_failed = when_failed
"""
**(str):** When did this attribute cross below
`pySMART.attribute.Attribute.thresh`? Reads '-' when not failed.
Generally either 'FAILING_NOW' or 'In_the_Past' otherwise.
"""
self.raw = raw
"""**(str):** Attribute's current raw (non-normalized) value."""
def __repr__(self):
"""Define a basic representation of the class object."""
return "<SMART Attribute %r %s/%s raw:%s>" % (
self.name, self.value, self.thresh, self.raw)
def __str__(self):
"""
Define a formatted string representation of the object's content.
In the interest of not overflowing 80-character lines this does not
print the value of `pySMART.attribute.Attribute.flags_hex`.
"""
return "{0:>3} {1:24}{2:4}{3:4}{4:4}{5:9}{6:8}{7:12}{8}".format(
self.num,
self.name,
self.value,
self.worst,
self.thresh,
self.type,
self.updated,
self.when_failed,
self.raw
)
def __getstate__(self):
return {
'num': self.num,
'flags': self.flags,
'raw': self.raw,
'value': self.value,
'worst': self.worst,
'threshold': self.thresh,
'type': self.type,
'updated': self.updated,
'when_failed': self.when_failed,
}
__all__ = ['Attribute']

File diff suppressed because it is too large Load Diff

View File

@ -1,112 +0,0 @@
# Copyright (C) 2014 Marc Herndon
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
# version 2, as published by the Free Software Foundation.
#
# 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.
#
################################################################
"""
This module contains the definition of the `DeviceList` class, used to
represent all physical storage devices connected to the system.
Once initialized, the sole member `devices` will contain a list of `Device`
objects.
This class has no public methods. All interaction should be through the
`Device` class API.
"""
# Python built-ins
from subprocess import Popen, PIPE
# pySMART module imports
from .device import Device
from .utils import SMARTCTL_PATH
class DeviceList(object):
"""
Represents a list of all the storage devices connected to this computer.
"""
def __init__(self, init=True):
"""
Instantiates and optionally initializes the `DeviceList`.
###Args:
* **init (bool):** By default, `pySMART.device_list.DeviceList.devices`
is populated with `Device` objects during instantiation. Setting init
to False will skip initialization and create an empty
`pySMART.device_list.DeviceList` object instead.
"""
self.devices = []
"""
**(list of `Device`):** Contains all storage devices detected during
instantiation, as `Device` objects.
"""
if init:
self._initialize()
def __repr__(self):
"""Define a basic representation of the class object."""
rep = "<DeviceList contents:\n"
for device in self.devices:
rep += str(device) + '\n'
return rep + '>'
# return "<DeviceList contents:%r>" % (self.devices)
def _cleanup(self):
"""
Removes duplicate ATA devices that correspond to an existing CSMI
device. Also removes any device with no capacity value, as this
indicates removable storage, ie: CD/DVD-ROM, ZIP, etc.
"""
# We can't operate directly on the list while we're iterating
# over it, so we collect indeces to delete and remove them later
to_delete = []
# Enumerate the list to get tuples containing indeces and values
for index, device in enumerate(self.devices):
if device.interface == 'csmi':
for otherindex, otherdevice in enumerate(self.devices):
if (otherdevice.interface == 'ata' or
otherdevice.interface == 'sata'):
if device.serial == otherdevice.serial:
to_delete.append(otherindex)
device._sd_name = otherdevice.name
if device.capacity is None and index not in to_delete:
to_delete.append(index)
# Recreate the self.devices list without the marked indeces
self.devices[:] = [v for i, v in enumerate(self.devices)
if i not in to_delete]
def _initialize(self):
"""
Scans system busses for attached devices and add them to the
`DeviceList` as `Device` objects.
"""
cmd = Popen([SMARTCTL_PATH, '--scan-open'], stdout=PIPE, stderr=PIPE)
_stdout, _stderr = [i.decode('utf8') for i in cmd.communicate()]
for line in _stdout.split('\n'):
if not ('failed:' in line or line == ''):
name = line.split(' ')[0].replace('/dev/', '')
# CSMI devices are explicitly of the 'csmi' type and do not
# require further disambiguation
if name[0:4] == 'csmi':
self.devices.append(Device(name, interface='csmi'))
# Other device types will be disambiguated by Device.__init__
else:
self.devices.append(Device(name))
# Remove duplicates and unwanted devices (optical, etc.) from the list
self._cleanup()
# Sort the list alphabetically by device name
self.devices.sort(key=lambda device: device.name)
__all__ = ['DeviceList']

View File

@ -1,139 +0,0 @@
# Copyright (C) 2014 Marc Herndon
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
# version 2, as published by the Free Software Foundation.
#
# 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.
#
################################################################
"""
This module contains the definition of the `Test_Entry` class, used to
represent individual entries in a `Device`'s SMART Self-test Log.
"""
class Test_Entry(object):
"""
Contains all of the information associated with a single SMART Self-test
log entry. This data is intended to exactly mirror that obtained through
smartctl.
"""
def __init__(self, format, num, test_type, status, hours, LBA, remain=None, segment=None, sense=None, ASC=None, ASCQ=None):
self._format = format
"""
**(str):** Indicates whether this entry was taken from an 'ata' or
'scsi' self-test log. Used to display the content properly.
"""
self.num = num
"""
**(str):** Entry's position in the log from 1 (most recent) to 21
(least recent). ATA logs save the last 21 entries while SCSI logs
only save the last 20.
"""
self.type = test_type
"""
**(str):** Type of test run. Generally short, long (extended), or
conveyance, plus offline (background) or captive (foreground).
"""
self.status = status
"""
**(str):** Self-test's status message, for example 'Completed without
error' or 'Completed: read failure'.
"""
self.hours = hours
"""
**(str):** The device's power-on hours at the time the self-test
was initiated.
"""
self.LBA = LBA
"""
**(str):** Indicates the first LBA at which an error was encountered
during this self-test. Presented as a decimal value for ATA/SATA
devices and in hexadecimal notation for SAS/SCSI devices.
"""
self.remain = remain
"""
**(str):** Percentage value indicating how much of the self-test is
left to perform. '00%' indicates a complete test, while any other
value could indicate a test in progress or one that failed prior to
completion. Only reported by ATA devices.
"""
self.segment = segment
"""
**(str):** A manufacturer-specific self-test segment number reported
by SCSI devices on self-test failure. Set to '-' otherwise.
"""
self.sense = sense
"""
**(str):** SCSI sense key reported on self-test failure. Set to '-'
otherwise.
"""
self.ASC = ASC
"""
**(str):** SCSI 'Additonal Sense Code' reported on self-test failure.
Set to '-' otherwise.
"""
self.ASCQ = ASCQ
"""
**(str):** SCSI 'Additonal Sense Code Quaifier' reported on self-test
failure. Set to '-' otherwise.
"""
def __getstate__(self):
try:
num = int(self.num)
except:
num = None
return {
'num': num,
'type': self.type,
'status': self.status,
'hours': self.hours,
'lba': self.LBA,
'remain': self.remain,
'segment': self.segment,
'sense': self.sense,
'asc': self.ASC,
'ascq': self.ASCQ
}
def __repr__(self):
"""Define a basic representation of the class object."""
return "<SMART Self-test [%s|%s] hrs:%s LBA:%s>" % (
self.type, self.status, self.hours, self.LBA)
def __str__(self):
"""
Define a formatted string representation of the object's content.
Looks nearly identical to the output of smartctl, without overflowing
80-character lines.
"""
if self._format == 'ata':
return "{0:>2} {1:17}{2:30}{3:5}{4:7}{5:17}".format(
self.num, self.type, self.status, self.remain, self.hours,
self.LBA)
else:
# 'Segment' could not be fit on the 80-char line. It's of limited
# utility anyway due to it's manufacturer-proprietary nature...
return ("{0:>2} {1:17}{2:23}{3:7}{4:14}[{5:4}{6:5}{7:4}]".format(
self.num,
self.type,
self.status,
self.hours,
self.LBA,
self.sense,
self.ASC,
self.ASCQ
))
__all__ = ['Test_Entry']

View File

@ -1,95 +0,0 @@
# Copyright (C) 2014 Marc Herndon
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
# version 2, as published by the Free Software Foundation.
#
# 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.
#
################################################################
"""
This module contains generic utilities and configuration information for use
by the other submodules of the `pySMART` package.
"""
import logging
import logging.handlers
import os
import io
import traceback
from shutil import which
_srcfile = __file__
TRACE = logging.DEBUG - 5
class TraceLogger(logging.Logger):
def __init__(self, name):
logging.Logger.__init__(self, name)
logging.addLevelName(TRACE, 'TRACE')
return
def trace(self, msg, *args, **kwargs):
self.log(TRACE, msg, *args, **kwargs)
def findCaller(self, stack_info=False):
"""
Overload built-in findCaller method
to omit not only logging/__init__.py but also the current file
"""
f = logging.currentframe()
# On some versions of IronPython, currentframe() returns None if
# IronPython isn't run with -X:Frames.
if f is not None:
f = f.f_back
rv = "(unknown file)", 0, "(unknown function)", None
while hasattr(f, "f_code"):
co = f.f_code
filename = os.path.normcase(co.co_filename)
if filename in (logging._srcfile, _srcfile):
f = f.f_back
continue
sinfo = None
if stack_info:
sio = io.StringIO()
sio.write('Stack (most recent call last):\n')
traceback.print_stack(f, file=sio)
sinfo = sio.getvalue()
if sinfo[-1] == '\n':
sinfo = sinfo[:-1]
sio.close()
rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
break
return rv
def configure_trace_logging():
if getattr(logging.handlers.logging.getLoggerClass(), 'trace', None) is None:
logging.setLoggerClass(TraceLogger)
smartctl_type = {
'ata': 'ata',
'csmi': 'ata',
'nvme': 'nvme',
'sas': 'scsi',
'sat': 'sat',
'sata': 'ata',
'scsi': 'scsi',
'atacam': 'atacam'
}
SMARTCTL_PATH = which('smartctl')
"""
**(dict of str):** Contains actual interface types (ie: sas, csmi) as keys and
the corresponding smartctl interface type (ie: scsi, ata) as values.
"""
__all__ = ['smartctl_type', 'SMARTCTL_PATH']

View File

@ -24,14 +24,10 @@ Must execute as root
"usermod -a -G disk USERNAME" is not sufficient unfortunately
SmartCTL (/usr/sbin/smartctl) must be in system path for python2.
PySMART is a python2 library.
There are a few forks that are potentially python 3 compatible
- https://github.com/ilyinon/py.SMART/ https://pypi.org/project/py.SMART/
- https://github.com/freenas/py-SMART/ - BSD port
The python2 PySMART library expects smartctl to be in PATH, but adding a bash alias does not help with this.
The BSD port uses shutil.which() to get smartctl, but this does not exist on python2.
Regular PySMART is a python2 library.
We are using the pySMART.smartx updated library to support both python 2 and 3.
If we only have disk group access:
If we only have disk group access (no root):
$ smartctl -i /dev/sda
smartctl 6.6 2016-05-31 r4324 [x86_64-linux-4.15.0-30-generic] (local build)
Copyright (C) 2002-16, Bruce Allen, Christian Franke, www.smartmontools.org
@ -44,20 +40,15 @@ This is not very hopeful: https://medium.com/opsops/why-smartctl-could-not-be-ru
So, here is what we are going to do:
Check for admin access. If no admin access, disable SMART plugin.
Check for python version. If 3.x use patched library, if 2.7 use library from pypi
If smartmontools is not installed, we will catch the error upstream in plugin initialization.
If smartmontools is not installed, we should catch the error upstream in plugin initialization.
"""
from glances.plugins.glances_plugin import GlancesPlugin
from glances.logger import logger
from glances.main import disable
import sys, os
if sys.version_info >= (3,0):
# https://github.com/freenas/py-SMART/ commit 881d09e85f7171f927d528b6205ce2015c7631bb
from BpySMART import DeviceList
else:
from pySMART import DeviceList
import os
from pySMART import DeviceList
DEVKEY = "DeviceName"

View File

@ -1,2 +1,2 @@
psutil==5.4.3
pysmart==0.3
pySMART.smartx