Top

Module pySMART.device

This module contains the definition of the Device class, used to represent a physical storage device connected to the system. Once initialized, class members contain all relevant information about the device, including its model, serial number, firmware version, and all SMART attribute data.

Methods are provided for initiating self tests and querying their results.

#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 `Device` class, used to represent a
physical storage device connected to the system.
Once initialized, class members contain all relevant information about the
device, including its model, serial number, firmware version, and all SMART
attribute data.

Methods are provided for initiating self tests and querying their results.
"""
# Python built-ins
import re  # Don't delete this 'un-used' import
from subprocess import Popen, PIPE
import warnings

# pySMART module imports
from .attribute import Attribute
from .test_entry import Test_Entry
from .utils import *

class Device(object):
    """
    Represents any device attached to an internal storage interface, such as a
    hard drive or DVD-ROM, and detected by smartmontools. Includes eSATA
    (considered SATA) but excludes other external devices (USB, Firewire).
    """
    def __init__(self, name, interface=None):
        """Instantiates and initializes the `pySMART.device.Device`."""
        assert interface is None or interface.lower() in [
            'ata', 'csmi', 'sas', 'sat', 'sata', 'scsi']
        self.name = name.replace('/dev/','')
        """
        **(str):** Device's hardware ID, without the '/dev/' prefix.
        (ie: sda (Linux), pd0 (Windows))
        """
        if self.name[:2].lower() == 'pd':
            self.name = pd_to_sd(self.name[2:])            
        self.model = None
        """**(str):** Device's model number."""
        self.serial = None
        """**(str):** Device's serial number."""
        self.interface = interface
        """
        **(str):** Device's interface type. Must be one of:
        
        * **ATA** - Advanced Technology Attachment
        * **SATA** - Serial ATA
        * **SCSI** - Small Computer Systems Interface
        * **SAS** - Serial Attached SCSI
        * **SAT** - SCSI-to-ATA Translation (SATA device plugged into a SAS
        port)
        * **CSMI** - Common Storage Management Interface (Intel ICH /
        Matrix RAID)

        Generally this should not be specified to allow auto-detection to occur.
        Otherwise, this value overrides the auto-detected type and could
        produce unexpected or no data.
        """
        self.capacity = None
        """**(str):** Device's user capacity."""
        self.firmware = None
        """**(str):** Device's firmware version."""
        self.supports_smart = False
        """
        **(bool):** True if the device supports SMART (or SCSI equivalent) and
        has the feature set enabled. False otherwise.
        """
        self.assessment = None
        """**(str):** SMART health self-assessment as reported by the device."""
        self.messages = []
        """
        **(list of str):** Contains any SMART warnings or other error messages
        reported by the device (ie: ASCQ codes).
        """
        self.is_ssd = None
        """
        **(bool):** True if this device is a Solid State Drive.
        False otherwise.
        """
        self.attributes = [None] * 256
        """
        **(list of `Attribute`):** Contains the complete SMART table information
        for this device, as provided by smartctl. Indexed by attribute #,
        values are set to 'None' for attributes not suported by this device.
        """
        self.tests = []
        """
        **(list of `Log_Entry`):** Contains the complete SMART self-test log
        for this device, as provided by smartctl. If no SMART self-tests have
        been recorded, contains a `None` type instead.
        """
        self._test_running = False
        """
        **(bool):** True if a self-test is currently being run. False otherwise.
        """
        self._test_ECD = None
        """
        **(str):** Estimated completion time of the running selftest.
        """
        self.diags = {}
        """
        **(dict of str):** Contains parsed and processed diagnostic information
        extracted from the SMART information. Currently only populated for
        SAS and SCSI devices, since ATA/SATA SMART attributes are manufacturer
        proprietary.
        """
        if self.name is None:
            warnings.warn("\nDevice '{0}' does not exist! "
                          "This object should be destroyed.".format(name))
            return
        # If no interface type was provided, scan for the device
        elif self.interface is None:
            _grep = 'find' if OS == 'Windows' else 'grep'
            cmd = Popen('smartctl --scan-open | {0} "{1}"'.format(
                 _grep, self.name), shell=True, stdout=PIPE, stderr=PIPE)
            _stdout, _stderr = cmd.communicate()
            if _stdout != '':
                self.interface = _stdout.split(' ')[2]
                # Disambiguate the generic interface to a specific type
                self._classify()
            else:
                warnings.warn("\nDevice '{0}' does not exist! "
                              "This object should be destroyed.".format(name))
                return
        # If a valid device was detected, populate its information
        if self.interface is not None:
            self.update()

    def __repr__(self):
        """Define a basic representation of the class object."""
        return "<%s device on /dev/%s mod:%s sn:%s>" % (
            self.interface.upper(), self.name, self.model, self.serial)

    def all_attributes(self):
        """
        Prints the entire SMART attribute table, in a format similar to
        the output of smartctl.
        """
        header_printed = False
        for attr in self.attributes:
            if attr is not None:
                if not header_printed:
                    print("{0:>3} {1:24}{2:4}{3:4}{4:4}{5:9}{6:8}{7:12}{8}".format(
                        'ID#', 'ATTRIBUTE_NAME', 'CUR', 'WST', 'THR', 'TYPE',
                        'UPDATED', 'WHEN_FAIL', 'RAW'))
                    header_printed = True
                print(attr)
        if not header_printed:
            print("This device does not support SMART attributes.")

    def all_selftests(self):
        """
        Prints the entire SMART self-test log, in a format similar to
        the output of smartctl.
        """
        if self.tests is not None:
            if smartctl_type[self.interface] == 'scsi':
                print("{0:3}{1:17}{2:23}{3:7}{4:14}{5:15}".format(
                    'ID', 'Test Description', 'Status', 'Hours',
                    '1st_Error@LBA', '[SK  ASC  ASCQ]'))
            else:
                print("{0:3}{1:17}{2:30}{3:5}{4:7}{5:17}".format(
                    'ID', 'Test_Description', 'Status', 'Left', 'Hours',
                    '1st_Error@LBA'))
            for test in self.tests:
                print(test)
        else:
            print("No self-tests have been logged for this device.")

    def _classify(self):
        """
        Disambiguates generic device types ATA and SCSI into more specific
        ATA, SATA, SAS, SAT and SCSI.
        """
        # SCSI devices might be SCSI, SAS or SAT 
        # ATA device might be ATA or SATA
        if self.interface in ['scsi', 'ata']:
            if self.interface == 'scsi':
                test = 'sat'
            else:
                test = 'sata'
            # Look for a SATA PHY to detect SAT and SATA
            cmd = Popen('smartctl -d {0} -l sataphy /dev/{1}'.format(
                smartctl_type[test], self.name), shell=True,
                        stdout=PIPE, stderr=PIPE)
            _stdout, _stderr = cmd.communicate()
            if 'GP Log 0x11' in _stdout.split('\n')[3]:
                self.interface = test
        # If device type is still SCSI (not changed to SAT above), then
        # check for a SAS PHY
        if self.interface == 'scsi':
            cmd = Popen('smartctl -d scsi -l sasphy /dev/{0}'.format(
                self.name), shell=True, stdout=PIPE, stderr=PIPE)
            _stdout, _stderr = cmd.communicate()
            if 'SAS SSP' in _stdout.split('\n')[4]:
                self.interface = 'sas'

    def get_selftest_result(self, output=None):
        """
        Refreshes a device's `pySMART.device.Device.tests` attribute to obtain
        the latest test results. If a new test result is obtained, its content
        is returned.

        ###Args:
        * **output (str, optional):** If set to 'str', the string
        representation of the most recent test result will be returned, instead
        of a `Test_Entry` object.

        ##Returns:
        * **(int):** Return status code. One of the following:
            * 0 - Success. Object (or optionally, string rep) is attached.
            * 1 - Self-test in progress. Must wait for it to finish.
            * 2 - No new test results.
        * **(`Test_Entry` or str):** Most recent `Test_Entry` object (or
        optionally it's string representation) if new data exists.  Status
        message string on failure.
        * **(str):** Estimated completion time of a test in-progess, if known.
        Otherwise 'None'.
        """
        # SCSI self-test logs hold 20 entries while ATA logs hold 21
        if smartctl_type[self.interface] == 'scsi':
            maxlog = 20
        else:
            maxlog = 21
        # If we looked only at the most recent test result we could be fooled
        # by two short tests run close together (within the same hour) appearing
        # identical. Comparing the length of the log adds some confidence until
        # it maxes, as above. Comparing the least-recent test result greatly
        # diminishes the chances that two sets of two tests each were run within
        # an hour of themselves, but with 16-17 other tests run in between them.
        if self.tests is not None:
            _first_entry = self.tests[0]
            _len = len(self.tests)
            _last_entry = self.tests[_len - 1]
        else:
            _len = 0
        self.update()
        # Check whether the list got longer (ie: new entry)
        if self.tests is not None and len(self.tests) != _len:
            # If so, return the newest test result
            self._test_running = False
            self._test_ECD = None
            if output == 'str':
                return (0, str(self.tests[0]), None)
            else:
                return (0, self.tests[0], None)
        elif _len == maxlog:
            # If not, because it's max size already, check for new entries
            if (_first_entry.type != self.tests[0].type or
                    _first_entry.hours != self.tests[0].hours or
                    _last_entry.type != self.tests[len(self.tests) - 1].type or
                    _last_entry.hours != self.tests[len(self.tests) - 1].hours):
                self._test_running = False
                self._test_ECD = None
                if output == 'str':
                    return (0, str(self.tests[0]), None)
                else:
                    return (0, self.tests[0], None)
            else:
                # If nothing new was found, see if we know of a running test.
                if self._test_running:
                        return (1, 'Self-test in progress. Please wait.',
                                self._test_ECD)
                else:
                    return (2, 'No new self-test results found.', None)
        else:
            # If log is still empty, or did not get longer, see whether we
            # know of a running test.
            if self._test_running:
                    return (1, 'Self-test in progress. Please wait.',
                            self._test_ECD)
            else:
                return (2, 'No new self-test results found.', None)

    def _guess_SMART_type(self, line):
        """
        This function is not used in the generic wrapper, however the header
        is defined so that it can be monkey-patched by another application.
        """
        pass

    def _make_SMART_warnings(self):
        """
        Parses an ATA/SATA SMART table for attributes with the 'when_failed'
        value set. Generates an warning message for any such attributes and
        updates the self-assessment value if necessary.
        """
        if smartctl_type[self.interface] == 'scsi':
            return
        for attr in self.attributes:
            if attr is not None:
                if attr.when_failed == 'In_the_past':
                    self.messages.append("".join(
                        [attr.name, " failed in the past with value ",
                         attr.worst, ". [Threshold: ", attr.thresh, ']']))
                    if not self.assessment == 'FAIL':
                        self.assessment= 'WARN'
                elif attr.when_failed == 'FAILING_NOW':
                    self.assessment == 'FAIL'
                    self.messages.append("".join(
                        [attr.name, " is failing now with value ",
                         attr.value, ". [Threshold: ", attr.thresh, ']']))
                elif not attr.when_failed == '-':
                    self.messages.append("".join(
                        [attr.name, " says it failed '", attr.when_failed,
                         "'. [V=", attr.value, ",W=", attr.worst, ",T=".
                         attr.thresh, ']']))
                    if not self.assessment == 'FAIL':
                        self.assessment = 'WARN'

    def run_selftest(self, test_type):
        """
        Instructs a device to begin a SMART self-test. All tests are run in
        'offline' / 'background' mode, allowing normal use of the device while
        it is being tested.

        ##Args:
        * **test_type (str):** The type of test to run. Accepts the following
        (not case sensitive):
            * **short** - Brief electo-mechanical functionality check.
            Generally takes 2 minutes or less.
            * **long** - Thorough electro-mechanical functionality check,
            including complete recording media scan. Generally takes several
            hours.
            * **conveyance** - Brief test used to identify damage incurred in
            shipping. Generally takes 5 minutes or less. **This test is not
            supported by SAS or SCSI devices.**

        ##Returns:
        * **(int):** Return status code.  One of the following:
            * 0 - Self-test initiated successfully
            * 1 - Previous self-test running. Must wait for it to finish.
            * 2 - Unknown or illegal test type requested.
            * 3 - Unspecified smartctl error. Self-test not initiated.
        * **(str):** Return status message.
        * **(str):** Estimated self-test completion time if a test is started.
        Otherwise 'None'.
        """
        if self._test_running:
            return (1, 'Self-test already in progress. Please wait.',
                    self._test_ECD)
        if test_type.lower() in ['short', 'long', 'conveyance']:
            if (test_type.lower() == 'conveyance' and
                    smartctl_type[self.interface] == 'scsi'):
                return (2, "Cannot perform 'conveyance' test on SAS/SCSI "
                        "devices.", None)
            cmd = Popen('smartctl -d {0} -t {1} /dev/{2}'.format(
                 smartctl_type[self.interface], test_type, self.name),
                        shell=True, stdout=PIPE, stderr=PIPE)
            _stdout, _stderr = cmd.communicate()
            _success = False
            _running = False
            for line in _stdout.split('\n'):
                if 'Testing has begun' in line:
                    _success = True
                    self._test_running = True
                if 'aborting current test' in line:
                    _running = True
                if _success and 'complete after' in line:
                    self._test_ECD = line[25:].rstrip()
            if _success:
                return (0, "Self-test started successfully", self._test_ECD)
            else:
                if _running:
                    return (1, 'Self-test already in progress. Please wait.',
                            self._test_ECD)
                else:
                    return (3, 'Unspecified Error. Self-test not started.',
                            None)
        else:
            return (2, "Unknown test type '{0}' requested.".format(test_type),
                    None)

    def update(self):
        """
        Queries for device information using smartctl and updates all
        class members, including the SMART attribute table and self-test log.
        Can be called at any time to refresh the `pySMART.device.Device`
        object's data content.
        """
        cmd = Popen('smartctl -d {0} -a /dev/{1}'.format(
            smartctl_type[self.interface], self.name), shell=True,
                    stdout=PIPE, stderr=PIPE)
        _stdout, _stderr = cmd.communicate()
        parse_self_tests = False
        parse_ascq = False
        self.tests = []
        for line in _stdout.split('\n'):
            if line.strip() == '':  # Blank line stops sub-captures
                parse_self_tests = False
                if parse_ascq:
                    parse_ascq = False
                    self.messages.append(message)
            if parse_ascq:
                message += ' ' + line.lstrip().rstrip()
            if parse_self_tests:
                num = line[1:3]
                if smartctl_type[self.interface] == 'scsi':
                    format = 'scsi'
                    test_type = line[5:23].rstrip()
                    status = line[23:46].rstrip()
                    segment = line[46:55].lstrip().rstrip()
                    hours = line[55:65].lstrip().rstrip()
                    LBA = line[65:78].lstrip().rstrip()
                    line_ = ' '.join(line.split('[')[1].split()).split(' ')
                    sense = line_[0]
                    ASC = line_[1]
                    ASCQ = line_[2][:-1]
                    self.tests.append(Test_Entry(
                        format, num, test_type, status, hours, LBA,
                        segment=segment, sense=sense, ASC=ASC, ASCQ=ASCQ))
                else:
                    format = 'ata'
                    test_type = line[5:25].rstrip()
                    status = line[25:54].rstrip()
                    remain = line[54:58].lstrip().rstrip()
                    hours = line[60:68].lstrip().rstrip()
                    LBA = line[77:].rstrip()
                    self.tests.append(Test_Entry(
                        format, num, test_type, status, hours, LBA,
                        remain=remain))
            # Basic device information parsing
            if 'Model Family' in line:
                self._guess_SMART_type(line.lower())
            if 'Device Model' in line or 'Product' in line:
                self.model = line.split(':')[1].lstrip().rstrip()
                self._guess_SMART_type(line.lower())
            if 'Serial Number' in line or 'Serial number' in line:
                self.serial = line.split(':')[1].split()[0].rstrip()
            if 'LU WWN' in line:
                self._guess_SMART_type(line.lower())
            if 'Firmware Version' in line or 'Revision' in line:
                self.firmware = line.split(':')[1].lstrip().rstrip()
            if 'User Capacity' in line:
                self.capacity = line.replace(']', '[').split('[')[1].lstrip().rstrip()
            if 'SMART support' in line:
                self.supports_smart = 'Enabled' in line
            if 'does not support SMART' in line:
                self.supports_smart = False
            if 'Rotation Rate' in line:
                if 'Solid State Device' in line:
                    self.is_ssd = True
                elif 'rpm' in line:
                    self.is_ssd = False
            if 'SMART overall-health self-assessment' in line:  # ATA devices
                if line.split(':')[1].strip() == 'PASSED':
                    self.assessment = 'PASS'
                else:
                    self.assessment = 'FAIL'
            if 'SMART Health Status' in line:  # SCSI devices
                if line.split(':')[1].strip() == 'OK':
                    self.assessment = 'PASS'
                else:
                    self.assessment = 'FAIL'
                    parse_ascq = True  # Set flag to capture status message
                    message = line.split(':')[1].lstrip().rstrip()
            # SMART Attribute table parsing
            if '0x0' in line and '_' in line:
                # Replace multiple space separators with a single space, then
                # tokenize the string on space delimiters
                line_ = ' '.join(line.split()).split(' ')
                if not '' in line_:
                    self.attributes[int(line_[0])] = Attribute(
                        line_[0], line_[1], line[2], line_[3], line_[4],
                        line_[5], line_[6], line_[7], line_[8], line_[9])
            if 'Description' in line and '(hours)' in line:
                parse_self_tests = True  # Set flag to capture test entries
            if 'No self-tests have been logged.' in line:
                self.tests = None
            # Everything from here on is parsing SCSI information that takes
            # the place of similar ATA SMART information
            if 'SS Media used endurance' in line:
                pct = int(line.split(':')[1].strip()[:-1])
                self.diags['Life_Left'] = str(100 - pct) + '%'
            if 'Specified cycle count' in line:
                self.diags['Start_Stop_Spec'] = line.split(':')[1].strip()
            if 'Accumulated start-stop cycles' in line:
                self.diags['Start_Stop_Cycles'] = line.split(':')[1].strip()
                self.diags['Start_Stop_Pct_Left'] = str(int(round(
                    100 - (int(self.diags['Start_Stop_Cycles']) /
                           int(self.diags['Start_Stop_Spec'])), 0)))
            if 'Specified load-unload count' in line:
                self.diags['Load_Cycle_Spec'] = line.split(':')[1].strip()
            if 'Accumulated load-unload cycles' in line:
                self.diags['Load_Cycle_Count'] = line.split(':')[1].strip()
                self.diags['Load_Cycle_Pct_Left'] = str(int(round(
                    100 - (int(self.diags['Load_Cycle_Count']) /
                           int(self.diags['Load_Cycle_Spec'])), 0)))
            if 'Elements in grown defect list' in line:
                self.diags['Reallocated_Sector_Ct'] = line.split(':')[1].strip()
            if 'read:' in line and smartctl_type[self.interface] == 'scsi':
                line_ = ' '.join(line.split()).split(' ')
                if (line_[1] == '0' and line_[2] == '0' and
                        line_[3] == '0' and line_[4] == '0'):
                    self.diags['Corrected_Reads'] = '0'
                elif line_[4] == '0':
                    self.diags['Corrected_Reads'] = str(
                        int(line_[1]) + int(line_[2]) + int(line_[3]))
                else:
                    self.diags['Corrected_Reads'] = line_[4]
                self.diags['Reads_GB'] = line_[6]
                self.diags['Uncorrected_Reads'] = line_[7]
            if 'write:' in line and smartctl_type[self.interface] == 'scsi':
                line_ = ' '.join(line.split()).split(' ')
                if (line_[1] == '0' and line_[2] == '0' and
                        line_[3] == '0' and line_[4] == '0'):
                    self.diags['Corrected_Writes'] = '0'
                elif line_[4] == '0':
                    self.diags['Corrected_Writes'] = str(
                        int(line_[1]) + int(line_[2]) + int(line_[3]))
                else:
                    self.diags['Corrected_Writes'] = line_[4]
                self.diags['Writes_GB'] = line_[6]
                self.diags['Uncorrected_Writes'] = line_[7]
            if 'verify:' in line and smartctl_type[self.interface] == 'scsi':
                line_ = ' '.join(line.split()).split(' ')
                if (line_[1] == '0' and line_[2] == '0' and
                        line_[3] == '0' and line_[4] == '0'):
                    self.diags['Corrected_Verifies'] = '0'
                elif line_[4] == '0':
                    self.diags['Corrected_Verifies'] = str(
                        int(line_[1]) + int(line_[2]) + int(line_[3]))
                else:
                    self.diags['Corrected_Verifies'] = line_[4]
                self.diags['Verifies_GB'] = line_[6]
                self.diags['Uncorrected_Verifies'] = line_[7]
            if 'non-medium error count' in line:
                self.diags['Non-Medium_Errors'] = line.split(':')[1].strip()
            if 'Accumulated power on time' in line:
                self.diags['Power_On_Hours'] = line.split(':')[1].split(' ')[1]
        if not smartctl_type[self.interface] == 'scsi':
            # Parse the SMART table for below-threshold attributes and create
            # corresponding warnings for non-SCSI disks
            self._make_SMART_warnings
        else:
            # For SCSI disks, any diagnostic attribute which was not captured
            # above gets set to '-' to indicate unsupported/unavailable.
            for diag in ['Corrected_Reads', 'Corrected_Writes',
                         'Corrected_Verifies', 'Uncorrected_Reads',
                         'Uncorrected_Writes', 'Uncorrected_Verifies',
                         'Start_Stop_Pct_Left', 'Reallocated_Sector_Ct',
                         'Start_Stop_Spec', 'Start_Stop_Cycles',
                         'Load_Cycle_Spec', 'Load_Cycle_Count',
                         'Power_On_Hours', 'Life_Left', 'Non-Medium_Errors',
                         'Reads_GB', 'Writes_GB', 'Verifies_GB']:
                if not diag in self.diags:
                    self.diags[diag] = '-'

__all__ = ['Device', 'all_attributes', 'get_selftest_result', 'run_selftest',
           'update']

Index

Classes

class Device

Represents any device attached to an internal storage interface, such as a hard drive or DVD-ROM, and detected by smartmontools. Includes eSATA (considered SATA) but excludes other external devices (USB, Firewire).

class Device(object):
    """
    Represents any device attached to an internal storage interface, such as a
    hard drive or DVD-ROM, and detected by smartmontools. Includes eSATA
    (considered SATA) but excludes other external devices (USB, Firewire).
    """
    def __init__(self, name, interface=None):
        """Instantiates and initializes the `pySMART.device.Device`."""
        assert interface is None or interface.lower() in [
            'ata', 'csmi', 'sas', 'sat', 'sata', 'scsi']
        self.name = name.replace('/dev/','')
        """
        **(str):** Device's hardware ID, without the '/dev/' prefix.
        (ie: sda (Linux), pd0 (Windows))
        """
        if self.name[:2].lower() == 'pd':
            self.name = pd_to_sd(self.name[2:])            
        self.model = None
        """**(str):** Device's model number."""
        self.serial = None
        """**(str):** Device's serial number."""
        self.interface = interface
        """
        **(str):** Device's interface type. Must be one of:
        
        * **ATA** - Advanced Technology Attachment
        * **SATA** - Serial ATA
        * **SCSI** - Small Computer Systems Interface
        * **SAS** - Serial Attached SCSI
        * **SAT** - SCSI-to-ATA Translation (SATA device plugged into a SAS
        port)
        * **CSMI** - Common Storage Management Interface (Intel ICH /
        Matrix RAID)

        Generally this should not be specified to allow auto-detection to occur.
        Otherwise, this value overrides the auto-detected type and could
        produce unexpected or no data.
        """
        self.capacity = None
        """**(str):** Device's user capacity."""
        self.firmware = None
        """**(str):** Device's firmware version."""
        self.supports_smart = False
        """
        **(bool):** True if the device supports SMART (or SCSI equivalent) and
        has the feature set enabled. False otherwise.
        """
        self.assessment = None
        """**(str):** SMART health self-assessment as reported by the device."""
        self.messages = []
        """
        **(list of str):** Contains any SMART warnings or other error messages
        reported by the device (ie: ASCQ codes).
        """
        self.is_ssd = None
        """
        **(bool):** True if this device is a Solid State Drive.
        False otherwise.
        """
        self.attributes = [None] * 256
        """
        **(list of `Attribute`):** Contains the complete SMART table information
        for this device, as provided by smartctl. Indexed by attribute #,
        values are set to 'None' for attributes not suported by this device.
        """
        self.tests = []
        """
        **(list of `Log_Entry`):** Contains the complete SMART self-test log
        for this device, as provided by smartctl. If no SMART self-tests have
        been recorded, contains a `None` type instead.
        """
        self._test_running = False
        """
        **(bool):** True if a self-test is currently being run. False otherwise.
        """
        self._test_ECD = None
        """
        **(str):** Estimated completion time of the running selftest.
        """
        self.diags = {}
        """
        **(dict of str):** Contains parsed and processed diagnostic information
        extracted from the SMART information. Currently only populated for
        SAS and SCSI devices, since ATA/SATA SMART attributes are manufacturer
        proprietary.
        """
        if self.name is None:
            warnings.warn("\nDevice '{0}' does not exist! "
                          "This object should be destroyed.".format(name))
            return
        # If no interface type was provided, scan for the device
        elif self.interface is None:
            _grep = 'find' if OS == 'Windows' else 'grep'
            cmd = Popen('smartctl --scan-open | {0} "{1}"'.format(
                 _grep, self.name), shell=True, stdout=PIPE, stderr=PIPE)
            _stdout, _stderr = cmd.communicate()
            if _stdout != '':
                self.interface = _stdout.split(' ')[2]
                # Disambiguate the generic interface to a specific type
                self._classify()
            else:
                warnings.warn("\nDevice '{0}' does not exist! "
                              "This object should be destroyed.".format(name))
                return
        # If a valid device was detected, populate its information
        if self.interface is not None:
            self.update()

    def __repr__(self):
        """Define a basic representation of the class object."""
        return "<%s device on /dev/%s mod:%s sn:%s>" % (
            self.interface.upper(), self.name, self.model, self.serial)

    def all_attributes(self):
        """
        Prints the entire SMART attribute table, in a format similar to
        the output of smartctl.
        """
        header_printed = False
        for attr in self.attributes:
            if attr is not None:
                if not header_printed:
                    print("{0:>3} {1:24}{2:4}{3:4}{4:4}{5:9}{6:8}{7:12}{8}".format(
                        'ID#', 'ATTRIBUTE_NAME', 'CUR', 'WST', 'THR', 'TYPE',
                        'UPDATED', 'WHEN_FAIL', 'RAW'))
                    header_printed = True
                print(attr)
        if not header_printed:
            print("This device does not support SMART attributes.")

    def all_selftests(self):
        """
        Prints the entire SMART self-test log, in a format similar to
        the output of smartctl.
        """
        if self.tests is not None:
            if smartctl_type[self.interface] == 'scsi':
                print("{0:3}{1:17}{2:23}{3:7}{4:14}{5:15}".format(
                    'ID', 'Test Description', 'Status', 'Hours',
                    '1st_Error@LBA', '[SK  ASC  ASCQ]'))
            else:
                print("{0:3}{1:17}{2:30}{3:5}{4:7}{5:17}".format(
                    'ID', 'Test_Description', 'Status', 'Left', 'Hours',
                    '1st_Error@LBA'))
            for test in self.tests:
                print(test)
        else:
            print("No self-tests have been logged for this device.")

    def _classify(self):
        """
        Disambiguates generic device types ATA and SCSI into more specific
        ATA, SATA, SAS, SAT and SCSI.
        """
        # SCSI devices might be SCSI, SAS or SAT 
        # ATA device might be ATA or SATA
        if self.interface in ['scsi', 'ata']:
            if self.interface == 'scsi':
                test = 'sat'
            else:
                test = 'sata'
            # Look for a SATA PHY to detect SAT and SATA
            cmd = Popen('smartctl -d {0} -l sataphy /dev/{1}'.format(
                smartctl_type[test], self.name), shell=True,
                        stdout=PIPE, stderr=PIPE)
            _stdout, _stderr = cmd.communicate()
            if 'GP Log 0x11' in _stdout.split('\n')[3]:
                self.interface = test
        # If device type is still SCSI (not changed to SAT above), then
        # check for a SAS PHY
        if self.interface == 'scsi':
            cmd = Popen('smartctl -d scsi -l sasphy /dev/{0}'.format(
                self.name), shell=True, stdout=PIPE, stderr=PIPE)
            _stdout, _stderr = cmd.communicate()
            if 'SAS SSP' in _stdout.split('\n')[4]:
                self.interface = 'sas'

    def get_selftest_result(self, output=None):
        """
        Refreshes a device's `pySMART.device.Device.tests` attribute to obtain
        the latest test results. If a new test result is obtained, its content
        is returned.

        ###Args:
        * **output (str, optional):** If set to 'str', the string
        representation of the most recent test result will be returned, instead
        of a `Test_Entry` object.

        ##Returns:
        * **(int):** Return status code. One of the following:
            * 0 - Success. Object (or optionally, string rep) is attached.
            * 1 - Self-test in progress. Must wait for it to finish.
            * 2 - No new test results.
        * **(`Test_Entry` or str):** Most recent `Test_Entry` object (or
        optionally it's string representation) if new data exists.  Status
        message string on failure.
        * **(str):** Estimated completion time of a test in-progess, if known.
        Otherwise 'None'.
        """
        # SCSI self-test logs hold 20 entries while ATA logs hold 21
        if smartctl_type[self.interface] == 'scsi':
            maxlog = 20
        else:
            maxlog = 21
        # If we looked only at the most recent test result we could be fooled
        # by two short tests run close together (within the same hour) appearing
        # identical. Comparing the length of the log adds some confidence until
        # it maxes, as above. Comparing the least-recent test result greatly
        # diminishes the chances that two sets of two tests each were run within
        # an hour of themselves, but with 16-17 other tests run in between them.
        if self.tests is not None:
            _first_entry = self.tests[0]
            _len = len(self.tests)
            _last_entry = self.tests[_len - 1]
        else:
            _len = 0
        self.update()
        # Check whether the list got longer (ie: new entry)
        if self.tests is not None and len(self.tests) != _len:
            # If so, return the newest test result
            self._test_running = False
            self._test_ECD = None
            if output == 'str':
                return (0, str(self.tests[0]), None)
            else:
                return (0, self.tests[0], None)
        elif _len == maxlog:
            # If not, because it's max size already, check for new entries
            if (_first_entry.type != self.tests[0].type or
                    _first_entry.hours != self.tests[0].hours or
                    _last_entry.type != self.tests[len(self.tests) - 1].type or
                    _last_entry.hours != self.tests[len(self.tests) - 1].hours):
                self._test_running = False
                self._test_ECD = None
                if output == 'str':
                    return (0, str(self.tests[0]), None)
                else:
                    return (0, self.tests[0], None)
            else:
                # If nothing new was found, see if we know of a running test.
                if self._test_running:
                        return (1, 'Self-test in progress. Please wait.',
                                self._test_ECD)
                else:
                    return (2, 'No new self-test results found.', None)
        else:
            # If log is still empty, or did not get longer, see whether we
            # know of a running test.
            if self._test_running:
                    return (1, 'Self-test in progress. Please wait.',
                            self._test_ECD)
            else:
                return (2, 'No new self-test results found.', None)

    def _guess_SMART_type(self, line):
        """
        This function is not used in the generic wrapper, however the header
        is defined so that it can be monkey-patched by another application.
        """
        pass

    def _make_SMART_warnings(self):
        """
        Parses an ATA/SATA SMART table for attributes with the 'when_failed'
        value set. Generates an warning message for any such attributes and
        updates the self-assessment value if necessary.
        """
        if smartctl_type[self.interface] == 'scsi':
            return
        for attr in self.attributes:
            if attr is not None:
                if attr.when_failed == 'In_the_past':
                    self.messages.append("".join(
                        [attr.name, " failed in the past with value ",
                         attr.worst, ". [Threshold: ", attr.thresh, ']']))
                    if not self.assessment == 'FAIL':
                        self.assessment= 'WARN'
                elif attr.when_failed == 'FAILING_NOW':
                    self.assessment == 'FAIL'
                    self.messages.append("".join(
                        [attr.name, " is failing now with value ",
                         attr.value, ". [Threshold: ", attr.thresh, ']']))
                elif not attr.when_failed == '-':
                    self.messages.append("".join(
                        [attr.name, " says it failed '", attr.when_failed,
                         "'. [V=", attr.value, ",W=", attr.worst, ",T=".
                         attr.thresh, ']']))
                    if not self.assessment == 'FAIL':
                        self.assessment = 'WARN'

    def run_selftest(self, test_type):
        """
        Instructs a device to begin a SMART self-test. All tests are run in
        'offline' / 'background' mode, allowing normal use of the device while
        it is being tested.

        ##Args:
        * **test_type (str):** The type of test to run. Accepts the following
        (not case sensitive):
            * **short** - Brief electo-mechanical functionality check.
            Generally takes 2 minutes or less.
            * **long** - Thorough electro-mechanical functionality check,
            including complete recording media scan. Generally takes several
            hours.
            * **conveyance** - Brief test used to identify damage incurred in
            shipping. Generally takes 5 minutes or less. **This test is not
            supported by SAS or SCSI devices.**

        ##Returns:
        * **(int):** Return status code.  One of the following:
            * 0 - Self-test initiated successfully
            * 1 - Previous self-test running. Must wait for it to finish.
            * 2 - Unknown or illegal test type requested.
            * 3 - Unspecified smartctl error. Self-test not initiated.
        * **(str):** Return status message.
        * **(str):** Estimated self-test completion time if a test is started.
        Otherwise 'None'.
        """
        if self._test_running:
            return (1, 'Self-test already in progress. Please wait.',
                    self._test_ECD)
        if test_type.lower() in ['short', 'long', 'conveyance']:
            if (test_type.lower() == 'conveyance' and
                    smartctl_type[self.interface] == 'scsi'):
                return (2, "Cannot perform 'conveyance' test on SAS/SCSI "
                        "devices.", None)
            cmd = Popen('smartctl -d {0} -t {1} /dev/{2}'.format(
                 smartctl_type[self.interface], test_type, self.name),
                        shell=True, stdout=PIPE, stderr=PIPE)
            _stdout, _stderr = cmd.communicate()
            _success = False
            _running = False
            for line in _stdout.split('\n'):
                if 'Testing has begun' in line:
                    _success = True
                    self._test_running = True
                if 'aborting current test' in line:
                    _running = True
                if _success and 'complete after' in line:
                    self._test_ECD = line[25:].rstrip()
            if _success:
                return (0, "Self-test started successfully", self._test_ECD)
            else:
                if _running:
                    return (1, 'Self-test already in progress. Please wait.',
                            self._test_ECD)
                else:
                    return (3, 'Unspecified Error. Self-test not started.',
                            None)
        else:
            return (2, "Unknown test type '{0}' requested.".format(test_type),
                    None)

    def update(self):
        """
        Queries for device information using smartctl and updates all
        class members, including the SMART attribute table and self-test log.
        Can be called at any time to refresh the `pySMART.device.Device`
        object's data content.
        """
        cmd = Popen('smartctl -d {0} -a /dev/{1}'.format(
            smartctl_type[self.interface], self.name), shell=True,
                    stdout=PIPE, stderr=PIPE)
        _stdout, _stderr = cmd.communicate()
        parse_self_tests = False
        parse_ascq = False
        self.tests = []
        for line in _stdout.split('\n'):
            if line.strip() == '':  # Blank line stops sub-captures
                parse_self_tests = False
                if parse_ascq:
                    parse_ascq = False
                    self.messages.append(message)
            if parse_ascq:
                message += ' ' + line.lstrip().rstrip()
            if parse_self_tests:
                num = line[1:3]
                if smartctl_type[self.interface] == 'scsi':
                    format = 'scsi'
                    test_type = line[5:23].rstrip()
                    status = line[23:46].rstrip()
                    segment = line[46:55].lstrip().rstrip()
                    hours = line[55:65].lstrip().rstrip()
                    LBA = line[65:78].lstrip().rstrip()
                    line_ = ' '.join(line.split('[')[1].split()).split(' ')
                    sense = line_[0]
                    ASC = line_[1]
                    ASCQ = line_[2][:-1]
                    self.tests.append(Test_Entry(
                        format, num, test_type, status, hours, LBA,
                        segment=segment, sense=sense, ASC=ASC, ASCQ=ASCQ))
                else:
                    format = 'ata'
                    test_type = line[5:25].rstrip()
                    status = line[25:54].rstrip()
                    remain = line[54:58].lstrip().rstrip()
                    hours = line[60:68].lstrip().rstrip()
                    LBA = line[77:].rstrip()
                    self.tests.append(Test_Entry(
                        format, num, test_type, status, hours, LBA,
                        remain=remain))
            # Basic device information parsing
            if 'Model Family' in line:
                self._guess_SMART_type(line.lower())
            if 'Device Model' in line or 'Product' in line:
                self.model = line.split(':')[1].lstrip().rstrip()
                self._guess_SMART_type(line.lower())
            if 'Serial Number' in line or 'Serial number' in line:
                self.serial = line.split(':')[1].split()[0].rstrip()
            if 'LU WWN' in line:
                self._guess_SMART_type(line.lower())
            if 'Firmware Version' in line or 'Revision' in line:
                self.firmware = line.split(':')[1].lstrip().rstrip()
            if 'User Capacity' in line:
                self.capacity = line.replace(']', '[').split('[')[1].lstrip().rstrip()
            if 'SMART support' in line:
                self.supports_smart = 'Enabled' in line
            if 'does not support SMART' in line:
                self.supports_smart = False
            if 'Rotation Rate' in line:
                if 'Solid State Device' in line:
                    self.is_ssd = True
                elif 'rpm' in line:
                    self.is_ssd = False
            if 'SMART overall-health self-assessment' in line:  # ATA devices
                if line.split(':')[1].strip() == 'PASSED':
                    self.assessment = 'PASS'
                else:
                    self.assessment = 'FAIL'
            if 'SMART Health Status' in line:  # SCSI devices
                if line.split(':')[1].strip() == 'OK':
                    self.assessment = 'PASS'
                else:
                    self.assessment = 'FAIL'
                    parse_ascq = True  # Set flag to capture status message
                    message = line.split(':')[1].lstrip().rstrip()
            # SMART Attribute table parsing
            if '0x0' in line and '_' in line:
                # Replace multiple space separators with a single space, then
                # tokenize the string on space delimiters
                line_ = ' '.join(line.split()).split(' ')
                if not '' in line_:
                    self.attributes[int(line_[0])] = Attribute(
                        line_[0], line_[1], line[2], line_[3], line_[4],
                        line_[5], line_[6], line_[7], line_[8], line_[9])
            if 'Description' in line and '(hours)' in line:
                parse_self_tests = True  # Set flag to capture test entries
            if 'No self-tests have been logged.' in line:
                self.tests = None
            # Everything from here on is parsing SCSI information that takes
            # the place of similar ATA SMART information
            if 'SS Media used endurance' in line:
                pct = int(line.split(':')[1].strip()[:-1])
                self.diags['Life_Left'] = str(100 - pct) + '%'
            if 'Specified cycle count' in line:
                self.diags['Start_Stop_Spec'] = line.split(':')[1].strip()
            if 'Accumulated start-stop cycles' in line:
                self.diags['Start_Stop_Cycles'] = line.split(':')[1].strip()
                self.diags['Start_Stop_Pct_Left'] = str(int(round(
                    100 - (int(self.diags['Start_Stop_Cycles']) /
                           int(self.diags['Start_Stop_Spec'])), 0)))
            if 'Specified load-unload count' in line:
                self.diags['Load_Cycle_Spec'] = line.split(':')[1].strip()
            if 'Accumulated load-unload cycles' in line:
                self.diags['Load_Cycle_Count'] = line.split(':')[1].strip()
                self.diags['Load_Cycle_Pct_Left'] = str(int(round(
                    100 - (int(self.diags['Load_Cycle_Count']) /
                           int(self.diags['Load_Cycle_Spec'])), 0)))
            if 'Elements in grown defect list' in line:
                self.diags['Reallocated_Sector_Ct'] = line.split(':')[1].strip()
            if 'read:' in line and smartctl_type[self.interface] == 'scsi':
                line_ = ' '.join(line.split()).split(' ')
                if (line_[1] == '0' and line_[2] == '0' and
                        line_[3] == '0' and line_[4] == '0'):
                    self.diags['Corrected_Reads'] = '0'
                elif line_[4] == '0':
                    self.diags['Corrected_Reads'] = str(
                        int(line_[1]) + int(line_[2]) + int(line_[3]))
                else:
                    self.diags['Corrected_Reads'] = line_[4]
                self.diags['Reads_GB'] = line_[6]
                self.diags['Uncorrected_Reads'] = line_[7]
            if 'write:' in line and smartctl_type[self.interface] == 'scsi':
                line_ = ' '.join(line.split()).split(' ')
                if (line_[1] == '0' and line_[2] == '0' and
                        line_[3] == '0' and line_[4] == '0'):
                    self.diags['Corrected_Writes'] = '0'
                elif line_[4] == '0':
                    self.diags['Corrected_Writes'] = str(
                        int(line_[1]) + int(line_[2]) + int(line_[3]))
                else:
                    self.diags['Corrected_Writes'] = line_[4]
                self.diags['Writes_GB'] = line_[6]
                self.diags['Uncorrected_Writes'] = line_[7]
            if 'verify:' in line and smartctl_type[self.interface] == 'scsi':
                line_ = ' '.join(line.split()).split(' ')
                if (line_[1] == '0' and line_[2] == '0' and
                        line_[3] == '0' and line_[4] == '0'):
                    self.diags['Corrected_Verifies'] = '0'
                elif line_[4] == '0':
                    self.diags['Corrected_Verifies'] = str(
                        int(line_[1]) + int(line_[2]) + int(line_[3]))
                else:
                    self.diags['Corrected_Verifies'] = line_[4]
                self.diags['Verifies_GB'] = line_[6]
                self.diags['Uncorrected_Verifies'] = line_[7]
            if 'non-medium error count' in line:
                self.diags['Non-Medium_Errors'] = line.split(':')[1].strip()
            if 'Accumulated power on time' in line:
                self.diags['Power_On_Hours'] = line.split(':')[1].split(' ')[1]
        if not smartctl_type[self.interface] == 'scsi':
            # Parse the SMART table for below-threshold attributes and create
            # corresponding warnings for non-SCSI disks
            self._make_SMART_warnings
        else:
            # For SCSI disks, any diagnostic attribute which was not captured
            # above gets set to '-' to indicate unsupported/unavailable.
            for diag in ['Corrected_Reads', 'Corrected_Writes',
                         'Corrected_Verifies', 'Uncorrected_Reads',
                         'Uncorrected_Writes', 'Uncorrected_Verifies',
                         'Start_Stop_Pct_Left', 'Reallocated_Sector_Ct',
                         'Start_Stop_Spec', 'Start_Stop_Cycles',
                         'Load_Cycle_Spec', 'Load_Cycle_Count',
                         'Power_On_Hours', 'Life_Left', 'Non-Medium_Errors',
                         'Reads_GB', 'Writes_GB', 'Verifies_GB']:
                if not diag in self.diags:
                    self.diags[diag] = '-'

Ancestors (in MRO)

Instance variables

var assessment

(str): SMART health self-assessment as reported by the device.

var attributes

(list of Attribute): Contains the complete SMART table information for this device, as provided by smartctl. Indexed by attribute #, values are set to 'None' for attributes not suported by this device.

var capacity

(str): Device's user capacity.

var diags

(dict of str): Contains parsed and processed diagnostic information extracted from the SMART information. Currently only populated for SAS and SCSI devices, since ATA/SATA SMART attributes are manufacturer proprietary.

var firmware

(str): Device's firmware version.

var interface

(str): Device's interface type. Must be one of:

  • ATA - Advanced Technology Attachment
  • SATA - Serial ATA
  • SCSI - Small Computer Systems Interface
  • SAS - Serial Attached SCSI
  • SAT - SCSI-to-ATA Translation (SATA device plugged into a SAS port)
  • CSMI - Common Storage Management Interface (Intel ICH / Matrix RAID)

Generally this should not be specified to allow auto-detection to occur. Otherwise, this value overrides the auto-detected type and could produce unexpected or no data.

var is_ssd

(bool): True if this device is a Solid State Drive. False otherwise.

var messages

(list of str): Contains any SMART warnings or other error messages reported by the device (ie: ASCQ codes).

var model

(str): Device's model number.

var name

(str): Device's hardware ID, without the '/dev/' prefix. (ie: sda (Linux), pd0 (Windows))

var serial

(str): Device's serial number.

var supports_smart

(bool): True if the device supports SMART (or SCSI equivalent) and has the feature set enabled. False otherwise.

var tests

(list of Log_Entry): Contains the complete SMART self-test log for this device, as provided by smartctl. If no SMART self-tests have been recorded, contains a None type instead.

Methods

def __init__(

self, name, interface=None)

Instantiates and initializes the Device.

def __init__(self, name, interface=None):
    """Instantiates and initializes the `pySMART.device.Device`."""
    assert interface is None or interface.lower() in [
        'ata', 'csmi', 'sas', 'sat', 'sata', 'scsi']
    self.name = name.replace('/dev/','')
    """
    **(str):** Device's hardware ID, without the '/dev/' prefix.
    (ie: sda (Linux), pd0 (Windows))
    """
    if self.name[:2].lower() == 'pd':
        self.name = pd_to_sd(self.name[2:])            
    self.model = None
    """**(str):** Device's model number."""
    self.serial = None
    """**(str):** Device's serial number."""
    self.interface = interface
    """
    **(str):** Device's interface type. Must be one of:
    
    * **ATA** - Advanced Technology Attachment
    * **SATA** - Serial ATA
    * **SCSI** - Small Computer Systems Interface
    * **SAS** - Serial Attached SCSI
    * **SAT** - SCSI-to-ATA Translation (SATA device plugged into a SAS
    port)
    * **CSMI** - Common Storage Management Interface (Intel ICH /
    Matrix RAID)
    Generally this should not be specified to allow auto-detection to occur.
    Otherwise, this value overrides the auto-detected type and could
    produce unexpected or no data.
    """
    self.capacity = None
    """**(str):** Device's user capacity."""
    self.firmware = None
    """**(str):** Device's firmware version."""
    self.supports_smart = False
    """
    **(bool):** True if the device supports SMART (or SCSI equivalent) and
    has the feature set enabled. False otherwise.
    """
    self.assessment = None
    """**(str):** SMART health self-assessment as reported by the device."""
    self.messages = []
    """
    **(list of str):** Contains any SMART warnings or other error messages
    reported by the device (ie: ASCQ codes).
    """
    self.is_ssd = None
    """
    **(bool):** True if this device is a Solid State Drive.
    False otherwise.
    """
    self.attributes = [None] * 256
    """
    **(list of `Attribute`):** Contains the complete SMART table information
    for this device, as provided by smartctl. Indexed by attribute #,
    values are set to 'None' for attributes not suported by this device.
    """
    self.tests = []
    """
    **(list of `Log_Entry`):** Contains the complete SMART self-test log
    for this device, as provided by smartctl. If no SMART self-tests have
    been recorded, contains a `None` type instead.
    """
    self._test_running = False
    """
    **(bool):** True if a self-test is currently being run. False otherwise.
    """
    self._test_ECD = None
    """
    **(str):** Estimated completion time of the running selftest.
    """
    self.diags = {}
    """
    **(dict of str):** Contains parsed and processed diagnostic information
    extracted from the SMART information. Currently only populated for
    SAS and SCSI devices, since ATA/SATA SMART attributes are manufacturer
    proprietary.
    """
    if self.name is None:
        warnings.warn("\nDevice '{0}' does not exist! "
                      "This object should be destroyed.".format(name))
        return
    # If no interface type was provided, scan for the device
    elif self.interface is None:
        _grep = 'find' if OS == 'Windows' else 'grep'
        cmd = Popen('smartctl --scan-open | {0} "{1}"'.format(
             _grep, self.name), shell=True, stdout=PIPE, stderr=PIPE)
        _stdout, _stderr = cmd.communicate()
        if _stdout != '':
            self.interface = _stdout.split(' ')[2]
            # Disambiguate the generic interface to a specific type
            self._classify()
        else:
            warnings.warn("\nDevice '{0}' does not exist! "
                          "This object should be destroyed.".format(name))
            return
    # If a valid device was detected, populate its information
    if self.interface is not None:
        self.update()

def all_attributes(

self)

Prints the entire SMART attribute table, in a format similar to the output of smartctl.

def all_attributes(self):
    """
    Prints the entire SMART attribute table, in a format similar to
    the output of smartctl.
    """
    header_printed = False
    for attr in self.attributes:
        if attr is not None:
            if not header_printed:
                print("{0:>3} {1:24}{2:4}{3:4}{4:4}{5:9}{6:8}{7:12}{8}".format(
                    'ID#', 'ATTRIBUTE_NAME', 'CUR', 'WST', 'THR', 'TYPE',
                    'UPDATED', 'WHEN_FAIL', 'RAW'))
                header_printed = True
            print(attr)
    if not header_printed:
        print("This device does not support SMART attributes.")

def all_selftests(

self)

Prints the entire SMART self-test log, in a format similar to the output of smartctl.

def all_selftests(self):
    """
    Prints the entire SMART self-test log, in a format similar to
    the output of smartctl.
    """
    if self.tests is not None:
        if smartctl_type[self.interface] == 'scsi':
            print("{0:3}{1:17}{2:23}{3:7}{4:14}{5:15}".format(
                'ID', 'Test Description', 'Status', 'Hours',
                '1st_Error@LBA', '[SK  ASC  ASCQ]'))
        else:
            print("{0:3}{1:17}{2:30}{3:5}{4:7}{5:17}".format(
                'ID', 'Test_Description', 'Status', 'Left', 'Hours',
                '1st_Error@LBA'))
        for test in self.tests:
            print(test)
    else:
        print("No self-tests have been logged for this device.")

def get_selftest_result(

self, output=None)

Refreshes a device's tests attribute to obtain the latest test results. If a new test result is obtained, its content is returned.

Args:

  • output (str, optional): If set to 'str', the string representation of the most recent test result will be returned, instead of a Test_Entry object.

Returns:

  • (int): Return status code. One of the following:
    • 0 - Success. Object (or optionally, string rep) is attached.
    • 1 - Self-test in progress. Must wait for it to finish.
    • 2 - No new test results.
  • (Test_Entry or str): Most recent Test_Entry object (or optionally it's string representation) if new data exists. Status message string on failure.
  • (str): Estimated completion time of a test in-progess, if known. Otherwise 'None'.
def get_selftest_result(self, output=None):
    """
    Refreshes a device's `pySMART.device.Device.tests` attribute to obtain
    the latest test results. If a new test result is obtained, its content
    is returned.
    ###Args:
    * **output (str, optional):** If set to 'str', the string
    representation of the most recent test result will be returned, instead
    of a `Test_Entry` object.
    ##Returns:
    * **(int):** Return status code. One of the following:
        * 0 - Success. Object (or optionally, string rep) is attached.
        * 1 - Self-test in progress. Must wait for it to finish.
        * 2 - No new test results.
    * **(`Test_Entry` or str):** Most recent `Test_Entry` object (or
    optionally it's string representation) if new data exists.  Status
    message string on failure.
    * **(str):** Estimated completion time of a test in-progess, if known.
    Otherwise 'None'.
    """
    # SCSI self-test logs hold 20 entries while ATA logs hold 21
    if smartctl_type[self.interface] == 'scsi':
        maxlog = 20
    else:
        maxlog = 21
    # If we looked only at the most recent test result we could be fooled
    # by two short tests run close together (within the same hour) appearing
    # identical. Comparing the length of the log adds some confidence until
    # it maxes, as above. Comparing the least-recent test result greatly
    # diminishes the chances that two sets of two tests each were run within
    # an hour of themselves, but with 16-17 other tests run in between them.
    if self.tests is not None:
        _first_entry = self.tests[0]
        _len = len(self.tests)
        _last_entry = self.tests[_len - 1]
    else:
        _len = 0
    self.update()
    # Check whether the list got longer (ie: new entry)
    if self.tests is not None and len(self.tests) != _len:
        # If so, return the newest test result
        self._test_running = False
        self._test_ECD = None
        if output == 'str':
            return (0, str(self.tests[0]), None)
        else:
            return (0, self.tests[0], None)
    elif _len == maxlog:
        # If not, because it's max size already, check for new entries
        if (_first_entry.type != self.tests[0].type or
                _first_entry.hours != self.tests[0].hours or
                _last_entry.type != self.tests[len(self.tests) - 1].type or
                _last_entry.hours != self.tests[len(self.tests) - 1].hours):
            self._test_running = False
            self._test_ECD = None
            if output == 'str':
                return (0, str(self.tests[0]), None)
            else:
                return (0, self.tests[0], None)
        else:
            # If nothing new was found, see if we know of a running test.
            if self._test_running:
                    return (1, 'Self-test in progress. Please wait.',
                            self._test_ECD)
            else:
                return (2, 'No new self-test results found.', None)
    else:
        # If log is still empty, or did not get longer, see whether we
        # know of a running test.
        if self._test_running:
                return (1, 'Self-test in progress. Please wait.',
                        self._test_ECD)
        else:
            return (2, 'No new self-test results found.', None)

def run_selftest(

self, test_type)

Instructs a device to begin a SMART self-test. All tests are run in 'offline' / 'background' mode, allowing normal use of the device while it is being tested.

Args:

  • test_type (str): The type of test to run. Accepts the following (not case sensitive):
    • short - Brief electo-mechanical functionality check. Generally takes 2 minutes or less.
    • long - Thorough electro-mechanical functionality check, including complete recording media scan. Generally takes several hours.
    • conveyance - Brief test used to identify damage incurred in shipping. Generally takes 5 minutes or less. This test is not supported by SAS or SCSI devices.

Returns:

  • (int): Return status code. One of the following:
    • 0 - Self-test initiated successfully
    • 1 - Previous self-test running. Must wait for it to finish.
    • 2 - Unknown or illegal test type requested.
    • 3 - Unspecified smartctl error. Self-test not initiated.
  • (str): Return status message.
  • (str): Estimated self-test completion time if a test is started. Otherwise 'None'.
def run_selftest(self, test_type):
    """
    Instructs a device to begin a SMART self-test. All tests are run in
    'offline' / 'background' mode, allowing normal use of the device while
    it is being tested.
    ##Args:
    * **test_type (str):** The type of test to run. Accepts the following
    (not case sensitive):
        * **short** - Brief electo-mechanical functionality check.
        Generally takes 2 minutes or less.
        * **long** - Thorough electro-mechanical functionality check,
        including complete recording media scan. Generally takes several
        hours.
        * **conveyance** - Brief test used to identify damage incurred in
        shipping. Generally takes 5 minutes or less. **This test is not
        supported by SAS or SCSI devices.**
    ##Returns:
    * **(int):** Return status code.  One of the following:
        * 0 - Self-test initiated successfully
        * 1 - Previous self-test running. Must wait for it to finish.
        * 2 - Unknown or illegal test type requested.
        * 3 - Unspecified smartctl error. Self-test not initiated.
    * **(str):** Return status message.
    * **(str):** Estimated self-test completion time if a test is started.
    Otherwise 'None'.
    """
    if self._test_running:
        return (1, 'Self-test already in progress. Please wait.',
                self._test_ECD)
    if test_type.lower() in ['short', 'long', 'conveyance']:
        if (test_type.lower() == 'conveyance' and
                smartctl_type[self.interface] == 'scsi'):
            return (2, "Cannot perform 'conveyance' test on SAS/SCSI "
                    "devices.", None)
        cmd = Popen('smartctl -d {0} -t {1} /dev/{2}'.format(
             smartctl_type[self.interface], test_type, self.name),
                    shell=True, stdout=PIPE, stderr=PIPE)
        _stdout, _stderr = cmd.communicate()
        _success = False
        _running = False
        for line in _stdout.split('\n'):
            if 'Testing has begun' in line:
                _success = True
                self._test_running = True
            if 'aborting current test' in line:
                _running = True
            if _success and 'complete after' in line:
                self._test_ECD = line[25:].rstrip()
        if _success:
            return (0, "Self-test started successfully", self._test_ECD)
        else:
            if _running:
                return (1, 'Self-test already in progress. Please wait.',
                        self._test_ECD)
            else:
                return (3, 'Unspecified Error. Self-test not started.',
                        None)
    else:
        return (2, "Unknown test type '{0}' requested.".format(test_type),
                None)

def update(

self)

Queries for device information using smartctl and updates all class members, including the SMART attribute table and self-test log. Can be called at any time to refresh the Device object's data content.

def update(self):
    """
    Queries for device information using smartctl and updates all
    class members, including the SMART attribute table and self-test log.
    Can be called at any time to refresh the `pySMART.device.Device`
    object's data content.
    """
    cmd = Popen('smartctl -d {0} -a /dev/{1}'.format(
        smartctl_type[self.interface], self.name), shell=True,
                stdout=PIPE, stderr=PIPE)
    _stdout, _stderr = cmd.communicate()
    parse_self_tests = False
    parse_ascq = False
    self.tests = []
    for line in _stdout.split('\n'):
        if line.strip() == '':  # Blank line stops sub-captures
            parse_self_tests = False
            if parse_ascq:
                parse_ascq = False
                self.messages.append(message)
        if parse_ascq:
            message += ' ' + line.lstrip().rstrip()
        if parse_self_tests:
            num = line[1:3]
            if smartctl_type[self.interface] == 'scsi':
                format = 'scsi'
                test_type = line[5:23].rstrip()
                status = line[23:46].rstrip()
                segment = line[46:55].lstrip().rstrip()
                hours = line[55:65].lstrip().rstrip()
                LBA = line[65:78].lstrip().rstrip()
                line_ = ' '.join(line.split('[')[1].split()).split(' ')
                sense = line_[0]
                ASC = line_[1]
                ASCQ = line_[2][:-1]
                self.tests.append(Test_Entry(
                    format, num, test_type, status, hours, LBA,
                    segment=segment, sense=sense, ASC=ASC, ASCQ=ASCQ))
            else:
                format = 'ata'
                test_type = line[5:25].rstrip()
                status = line[25:54].rstrip()
                remain = line[54:58].lstrip().rstrip()
                hours = line[60:68].lstrip().rstrip()
                LBA = line[77:].rstrip()
                self.tests.append(Test_Entry(
                    format, num, test_type, status, hours, LBA,
                    remain=remain))
        # Basic device information parsing
        if 'Model Family' in line:
            self._guess_SMART_type(line.lower())
        if 'Device Model' in line or 'Product' in line:
            self.model = line.split(':')[1].lstrip().rstrip()
            self._guess_SMART_type(line.lower())
        if 'Serial Number' in line or 'Serial number' in line:
            self.serial = line.split(':')[1].split()[0].rstrip()
        if 'LU WWN' in line:
            self._guess_SMART_type(line.lower())
        if 'Firmware Version' in line or 'Revision' in line:
            self.firmware = line.split(':')[1].lstrip().rstrip()
        if 'User Capacity' in line:
            self.capacity = line.replace(']', '[').split('[')[1].lstrip().rstrip()
        if 'SMART support' in line:
            self.supports_smart = 'Enabled' in line
        if 'does not support SMART' in line:
            self.supports_smart = False
        if 'Rotation Rate' in line:
            if 'Solid State Device' in line:
                self.is_ssd = True
            elif 'rpm' in line:
                self.is_ssd = False
        if 'SMART overall-health self-assessment' in line:  # ATA devices
            if line.split(':')[1].strip() == 'PASSED':
                self.assessment = 'PASS'
            else:
                self.assessment = 'FAIL'
        if 'SMART Health Status' in line:  # SCSI devices
            if line.split(':')[1].strip() == 'OK':
                self.assessment = 'PASS'
            else:
                self.assessment = 'FAIL'
                parse_ascq = True  # Set flag to capture status message
                message = line.split(':')[1].lstrip().rstrip()
        # SMART Attribute table parsing
        if '0x0' in line and '_' in line:
            # Replace multiple space separators with a single space, then
            # tokenize the string on space delimiters
            line_ = ' '.join(line.split()).split(' ')
            if not '' in line_:
                self.attributes[int(line_[0])] = Attribute(
                    line_[0], line_[1], line[2], line_[3], line_[4],
                    line_[5], line_[6], line_[7], line_[8], line_[9])
        if 'Description' in line and '(hours)' in line:
            parse_self_tests = True  # Set flag to capture test entries
        if 'No self-tests have been logged.' in line:
            self.tests = None
        # Everything from here on is parsing SCSI information that takes
        # the place of similar ATA SMART information
        if 'SS Media used endurance' in line:
            pct = int(line.split(':')[1].strip()[:-1])
            self.diags['Life_Left'] = str(100 - pct) + '%'
        if 'Specified cycle count' in line:
            self.diags['Start_Stop_Spec'] = line.split(':')[1].strip()
        if 'Accumulated start-stop cycles' in line:
            self.diags['Start_Stop_Cycles'] = line.split(':')[1].strip()
            self.diags['Start_Stop_Pct_Left'] = str(int(round(
                100 - (int(self.diags['Start_Stop_Cycles']) /
                       int(self.diags['Start_Stop_Spec'])), 0)))
        if 'Specified load-unload count' in line:
            self.diags['Load_Cycle_Spec'] = line.split(':')[1].strip()
        if 'Accumulated load-unload cycles' in line:
            self.diags['Load_Cycle_Count'] = line.split(':')[1].strip()
            self.diags['Load_Cycle_Pct_Left'] = str(int(round(
                100 - (int(self.diags['Load_Cycle_Count']) /
                       int(self.diags['Load_Cycle_Spec'])), 0)))
        if 'Elements in grown defect list' in line:
            self.diags['Reallocated_Sector_Ct'] = line.split(':')[1].strip()
        if 'read:' in line and smartctl_type[self.interface] == 'scsi':
            line_ = ' '.join(line.split()).split(' ')
            if (line_[1] == '0' and line_[2] == '0' and
                    line_[3] == '0' and line_[4] == '0'):
                self.diags['Corrected_Reads'] = '0'
            elif line_[4] == '0':
                self.diags['Corrected_Reads'] = str(
                    int(line_[1]) + int(line_[2]) + int(line_[3]))
            else:
                self.diags['Corrected_Reads'] = line_[4]
            self.diags['Reads_GB'] = line_[6]
            self.diags['Uncorrected_Reads'] = line_[7]
        if 'write:' in line and smartctl_type[self.interface] == 'scsi':
            line_ = ' '.join(line.split()).split(' ')
            if (line_[1] == '0' and line_[2] == '0' and
                    line_[3] == '0' and line_[4] == '0'):
                self.diags['Corrected_Writes'] = '0'
            elif line_[4] == '0':
                self.diags['Corrected_Writes'] = str(
                    int(line_[1]) + int(line_[2]) + int(line_[3]))
            else:
                self.diags['Corrected_Writes'] = line_[4]
            self.diags['Writes_GB'] = line_[6]
            self.diags['Uncorrected_Writes'] = line_[7]
        if 'verify:' in line and smartctl_type[self.interface] == 'scsi':
            line_ = ' '.join(line.split()).split(' ')
            if (line_[1] == '0' and line_[2] == '0' and
                    line_[3] == '0' and line_[4] == '0'):
                self.diags['Corrected_Verifies'] = '0'
            elif line_[4] == '0':
                self.diags['Corrected_Verifies'] = str(
                    int(line_[1]) + int(line_[2]) + int(line_[3]))
            else:
                self.diags['Corrected_Verifies'] = line_[4]
            self.diags['Verifies_GB'] = line_[6]
            self.diags['Uncorrected_Verifies'] = line_[7]
        if 'non-medium error count' in line:
            self.diags['Non-Medium_Errors'] = line.split(':')[1].strip()
        if 'Accumulated power on time' in line:
            self.diags['Power_On_Hours'] = line.split(':')[1].split(' ')[1]
    if not smartctl_type[self.interface] == 'scsi':
        # Parse the SMART table for below-threshold attributes and create
        # corresponding warnings for non-SCSI disks
        self._make_SMART_warnings
    else:
        # For SCSI disks, any diagnostic attribute which was not captured
        # above gets set to '-' to indicate unsupported/unavailable.
        for diag in ['Corrected_Reads', 'Corrected_Writes',
                     'Corrected_Verifies', 'Uncorrected_Reads',
                     'Uncorrected_Writes', 'Uncorrected_Verifies',
                     'Start_Stop_Pct_Left', 'Reallocated_Sector_Ct',
                     'Start_Stop_Spec', 'Start_Stop_Cycles',
                     'Load_Cycle_Spec', 'Load_Cycle_Count',
                     'Power_On_Hours', 'Life_Left', 'Non-Medium_Errors',
                     'Reads_GB', 'Writes_GB', 'Verifies_GB']:
            if not diag in self.diags:
                self.diags[diag] = '-'

Documentation generated by pdoc 0.2.3. pdoc is in the public domain with the UNLICENSE.