pySMART API documentation

# pySMART

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>

In the above cases if a new DeviceList is empty or a specific Device reports an "UNKNOWN INTERFACE", you are likely running without administrative privileges. On POSIX systems, you can request smartctl is run as a superuser by setting the sudo attribute of the global SMARTCTL object to True. Note this may cause you to be prompted for a password.

    #!bash
    >>> from pySMART import DeviceList
    >>> from pySMART import Device
    >>> sda = Device('/dev/sda')
    >>> sda
    <UNKNOWN INTERFACE device on /dev/sda mod:None sn:None>
    >>> devlist = DeviceList()
    >>> devlist
    <DeviceList contents:
    >
    >>> from pySMART import SMARTCTL
    >>> SMARTCTL.sudo = True
    >>> sda = Device('/dev/sda')
    >>> sda
    [sudo] password for user:
    <SAT device on /dev/sda mod:ST10000DM0004-1ZC101 sn:ZA20VNPT>
    >>> devlist = DeviceList()
    >>> devlist
    <DeviceList contents:
    <NVME device on /dev/nvme0 mod:Sabrent Rocket 4.0 1TB sn:03850709185D88300410>
    <NVME device on /dev/nvme1 mod:Samsung SSD 970 EVO Plus 2TB sn:S59CNM0RB05028D>
    <NVME device on /dev/nvme2 mod:Samsung SSD 970 EVO Plus 2TB sn:S59CNM0RB05113H>
    <SAT device on /dev/sda mod:ST10000DM0004-1ZC101 sn:ZA20VNPT>
    <SAT device on /dev/sdb mod:ST10000DM0004-1ZC101 sn:ZA22W366>
    <SAT device on /dev/sdc mod:ST10000DM0004-1ZC101 sn:ZA22SPLG>
    <SAT device on /dev/sdd mod:ST10000DM0004-1ZC101 sn:ZA2215HL>
    >

In general, it is recommended to run the base script with enough privileges to execute smartctl, but this is not possible in all cases, so this workaround is provided as a convenience. However, note that using sudo inside other non-terminal projects may cause dev-bugs/issues.

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 portability, I thought a simple wrapper for smartctl would save time in the development of future automated test tools.

View Source

      1# Copyright (C) 2014 Marc Herndon
      2#
      3# This program is free software; you can redistribute it and/or
      4# modify it under the terms of the GNU General Public License,
      5# version 2, as published by the Free Software Foundation.
      6#
      7# This program is distributed in the hope that it will be useful,
      8# but WITHOUT ANY WARRANTY; without even the implied warranty of
      9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     10# GNU General Public License for more details.
     11#
     12# You should have received a copy of the GNU General Public License
     13# along with this program; if not, write to the Free Software
     14# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
     15# MA  02110-1301, USA.
     16#
     17################################################################
     18"""
     19Copyright (C) 2014 Marc Herndon
     20
     21pySMART is a simple Python wrapper for the `smartctl` component of
     22`smartmontools`. It works under Linux and Windows, as long as smartctl is on
     23the system path. Running with administrative (root) privilege is strongly
     24recommended, as smartctl cannot accurately detect all device types or parse
     25all SMART information without full permissions.
     26
     27With only a device's name (ie: /dev/sda, pd0), the API will create a
     28`Device` object, populated with all relevant information about
     29that device. The documented API can then be used to query this object for
     30information, initiate device self-tests, and perform other functions.
     31
     32Usage
     33-----
     34The most common way to use pySMART is to create a logical representation of the
     35physical storage device that you would like to work with, as shown:
     36
     37    #!bash
     38    >>> from pySMART import Device
     39    >>> sda = Device('/dev/sda')
     40    >>> sda
     41    <SATA device on /dev/sda mod:WDC WD5000AAKS-60Z1A0 sn:WD-WCAWFxxxxxxx>
     42
     43`Device` class members can be accessed directly, and a number of helper methods
     44are provided to retrieve information in bulk.  Some examples are shown below:
     45
     46    #!bash
     47    >>> sda.assessment  # Query the SMART self-assessment
     48    'PASS'
     49    >>> sda.attributes[9]  # Query a single SMART attribute
     50    <SMART Attribute 'Power_On_Hours' 068/000 raw:23644>
     51    >>> sda.all_attributes()  # Print the entire SMART attribute table
     52    ID# ATTRIBUTE_NAME          CUR WST THR TYPE     UPDATED WHEN_FAIL    RAW
     53      1 Raw_Read_Error_Rate     200 200 051 Pre-fail Always  -           0
     54      3 Spin_Up_Time            141 140 021 Pre-fail Always  -           3908
     55      4 Start_Stop_Count        098 098 000 Old_age  Always  -           2690
     56      5 Reallocated_Sector_Ct   200 200 140 Pre-fail Always  -           0
     57        ... # Edited for brevity
     58    199 UDMA_CRC_Error_Count    200 200 000 Old_age  Always  -           0
     59    200 Multi_Zone_Error_Rate   200 200 000 Old_age  Offline -           0
     60    >>> sda.tests[0]  # Query the most recent self-test result
     61    <SMART Self-test [Short offline|Completed without error] hrs:23734 lba:->
     62    >>> sda.all_selftests()  # Print the entire self-test log
     63    ID Test_Description Status                        Left Hours  1st_Error@lba
     64     1 Short offline    Completed without error       00%  23734  -
     65     2 Short offline    Completed without error       00%  23734  -
     66       ... # Edited for brevity
     67     7 Short offline    Completed without error       00%  23726  -
     68     8 Short offline    Completed without error       00%  1      -
     69
     70Alternatively, the package provides a `DeviceList` class. When instantiated,
     71this will auto-detect all local storage devices and create a list containing
     72one `Device` object for each detected storage device.
     73
     74    #!bash
     75    >>> from pySMART import DeviceList
     76    >>> devlist = DeviceList()
     77    >>> devlist
     78    <DeviceList contents:
     79    <SAT device on /dev/sdb mod:WDC WD20EADS-00R6B0 sn:WD-WCAVYxxxxxxx>
     80    <SAT device on /dev/sdc mod:WDC WD20EADS-00S2B0 sn:WD-WCAVYxxxxxxx>
     81    <CSMI device on /dev/csmi0,0 mod:WDC WD5000AAKS-60Z1A0 sn:WD-WCAWFxxxxxxx>
     82    >
     83    >>> devlist.devices[0].attributes[5]  # Access Device data as above
     84    <SMART Attribute 'Reallocated_Sector_Ct' 173/140 raw:214>
     85
     86In the above cases if a new DeviceList is empty or a specific Device reports an
     87"UNKNOWN INTERFACE", you are likely running without administrative privileges.
     88On POSIX systems, you can request smartctl is run as a superuser by setting the
     89sudo attribute of the global SMARTCTL object to True. Note this may cause you
     90to be prompted for a password.
     91
     92    #!bash
     93    >>> from pySMART import DeviceList
     94    >>> from pySMART import Device
     95    >>> sda = Device('/dev/sda')
     96    >>> sda
     97    <UNKNOWN INTERFACE device on /dev/sda mod:None sn:None>
     98    >>> devlist = DeviceList()
     99    >>> devlist
    100    <DeviceList contents:
    101    >
    102    >>> from pySMART import SMARTCTL
    103    >>> SMARTCTL.sudo = True
    104    >>> sda = Device('/dev/sda')
    105    >>> sda
    106    [sudo] password for user:
    107    <SAT device on /dev/sda mod:ST10000DM0004-1ZC101 sn:ZA20VNPT>
    108    >>> devlist = DeviceList()
    109    >>> devlist
    110    <DeviceList contents:
    111    <NVME device on /dev/nvme0 mod:Sabrent Rocket 4.0 1TB sn:03850709185D88300410>
    112    <NVME device on /dev/nvme1 mod:Samsung SSD 970 EVO Plus 2TB sn:S59CNM0RB05028D>
    113    <NVME device on /dev/nvme2 mod:Samsung SSD 970 EVO Plus 2TB sn:S59CNM0RB05113H>
    114    <SAT device on /dev/sda mod:ST10000DM0004-1ZC101 sn:ZA20VNPT>
    115    <SAT device on /dev/sdb mod:ST10000DM0004-1ZC101 sn:ZA22W366>
    116    <SAT device on /dev/sdc mod:ST10000DM0004-1ZC101 sn:ZA22SPLG>
    117    <SAT device on /dev/sdd mod:ST10000DM0004-1ZC101 sn:ZA2215HL>
    118    >
    119
    120In general, it is recommended to run the base script with enough privileges to
    121execute smartctl, but this is not possible in all cases, so this workaround is
    122provided as a convenience. However, note that using sudo inside other
    123non-terminal projects may cause dev-bugs/issues.
    124
    125
    126Using the pySMART wrapper, Python applications be be rapidly developed to take
    127advantage of the powerful features of smartmontools.
    128
    129Acknowledgements
    130----------------
    131I would like to thank the entire team behind smartmontools for creating and
    132maintaining such a fantastic product.
    133
    134In particular I want to thank Christian Franke, who maintains the Windows port
    135of the software.  For several years I have written Windows batch files that
    136rely on smartctl.exe to automate evaluation and testing of large pools of
    137storage devices under Windows.  Without his work, my job would have been
    138significantly more miserable. :)
    139
    140Having recently migrated my development from Batch to Python for Linux
    141portability, I thought a simple wrapper for smartctl would save time in the
    142development of future automated test tools.
    143"""
    144# autopep8: off
    145from .testentry import TestEntry
    146from .interface.ata.attribute import Attribute
    147from . import utils
    148utils.configure_trace_logging()
    149from .smartctl import SMARTCTL
    150from .device_list import DeviceList
    151from .device import Device, smart_health_assement
    152from .version import __version__,__version_tuple__
    153# autopep8: on
    154
    155
    156__all__ = [
    157    '__version__', '__version_tuple__',
    158    'TestEntry', 'Attribute', 'utils', 'SMARTCTL', 'DeviceList', 'Device',
    159    'smart_health_assement'
    160]

__version__ = '1.3.1.dev8'
__version_tuple__ = (1, 3, 1, 'dev8')
class TestEntry: View Source

     27class TestEntry(object):
     28    """
     29    Contains all of the information associated with a single SMART Self-test
     30    log entry. This data is intended to exactly mirror that obtained through
     31    smartctl.
     32    """
     33
     34    def __init__(self, format, num: Optional[int], test_type, status, hours, lba,
     35                 remain=None,
     36                 segment=None,
     37                 sense=None,
     38                 asc=None,
     39                 ascq=None,
     40                 nsid=None,
     41                 sct=None,
     42                 code=None):
     43
     44        self._format = format
     45        """
     46        **(str):** Indicates whether this entry was taken from an 'ata' or
     47        'scsi' self-test log. Used to display the content properly.
     48        """
     49        self.num: Optional[int] = num
     50        """
     51        **(int):** Entry's position in the log from 1 (most recent) to 21
     52        (least recent).  ATA logs save the last 21 entries while SCSI logs
     53        only save the last 20.
     54        """
     55        self.type = test_type
     56        """
     57        **(str):** Type of test run.  Generally short, long (extended), or
     58        conveyance, plus offline (background) or captive (foreground).
     59        """
     60        self.status = status
     61        """
     62        **(str):** Self-test's status message, for example 'Completed without
     63        error' or 'Completed: read failure'.
     64        """
     65        self.hours = hours
     66        """
     67        **(str):** The device's power-on hours at the time the self-test
     68        was initiated.
     69        """
     70        self.LBA = lba
     71        """
     72        **(str):** Indicates the first LBA at which an error was encountered
     73        during this self-test. Presented as a decimal value for ATA/SATA
     74        devices and in hexadecimal notation for SAS/SCSI devices.
     75        """
     76        self.remain = remain
     77        """
     78        **(str):** Percentage value indicating how much of the self-test is
     79        left to perform. '00%' indicates a complete test, while any other
     80        value could indicate a test in progress or one that failed prior to
     81        completion. Only reported by ATA devices.
     82        """
     83        self.segment = segment
     84        """
     85        **(str):** A manufacturer-specific self-test segment number reported
     86        by SCSI devices on self-test failure. Set to '-' otherwise.
     87        """
     88        self.sense = sense
     89        """
     90        **(str):** SCSI sense key reported on self-test failure. Set to '-'
     91        otherwise.
     92        """
     93        self.ASC = asc
     94        """
     95        **(str):** SCSI 'Additonal Sense Code' reported on self-test failure.
     96        Set to '-' otherwise.
     97        """
     98        self.ASCQ = ascq
     99        """
    100        **(str):** SCSI 'Additonal Sense Code Quaifier' reported on self-test
    101        failure. Set to '-' otherwise.
    102        """
    103        self.nsid = nsid
    104        """
    105        **(str):** NVMe 'Name Space Identifier' reported on self-test failure.
    106        Set to '-' if no namespace is defined.
    107        """
    108        self.sct = sct
    109        """
    110        **(str):** NVMe 'Status Code Type' reported on self-test failure.
    111        Set to '-' if undefined.
    112        """
    113        self.code = code
    114        """
    115        **(str):** NVMe 'Status Code' reported on self-test failure.
    116        Set to '-' if undefined.
    117        """
    118
    119    def __getstate__(self):
    120        return {
    121            'num': self.num,
    122            'type': self.type,
    123            'status': self.status,
    124            'hours': self.hours,
    125            'lba': self.LBA,
    126            'remain': self.remain,
    127            'segment': self.segment,
    128            'sense': self.sense,
    129            'asc': self.ASC,
    130            'ascq': self.ASCQ,
    131            'nsid': self.nsid,
    132            'sct': self.sct,
    133            'code': self.code
    134        }
    135
    136    def __repr__(self):
    137        """Define a basic representation of the class object."""
    138        return "<SMART Self-test [%s|%s] hrs:%s LBA:%s>" % (
    139            self.type, self.status, self.hours, self.LBA)
    140
    141    def __str__(self):
    142        """
    143        Define a formatted string representation of the object's content.
    144        Looks nearly identical to the output of smartctl, without overflowing
    145        80-character lines.
    146        """
    147        if self._format == 'ata':
    148            return "{0:>2} {1:17}{2:30}{3:5}{4:7}{5:17}".format(
    149                self.num, self.type, self.status, self.remain, self.hours,
    150                self.LBA)
    151        elif self._format == 'scsi':
    152            # 'Segment' could not be fit on the 80-char line. It's of limited
    153            # utility anyway due to it's manufacturer-proprietary nature...
    154            return ("{0:>2} {1:17}{2:23}{3:7}{4:14}[{5:4}{6:5}{7:4}]".format(
    155                self.num,
    156                self.type,
    157                self.status,
    158                self.hours,
    159                self.LBA,
    160                self.sense,
    161                self.ASC,
    162                self.ASCQ
    163            ))
    164        elif self._format == 'nvme':
    165            ## NVME FORMAT ##
    166            # Example smartctl output
    167            # Self-test Log (NVMe Log 0x06)
    168            # Self-test status: Extended self-test in progress (28% completed)
    169            # Num  Test_Description  Status                       Power_on_Hours  Failing_LBA  NSID Seg SCT Code
    170            #  0   Extended          Completed without error                3441            -     -   -   -    -
    171            return ("{0:^4} {1:<18}{2:<29}{3:>14}{4:>13}{5:>6}{6:>4}{7:>4}{8:>5}".format(
    172                self.num,
    173                self.type,
    174                self.status,
    175                self.hours,
    176                self.LBA if self.LBA is not None else '-',
    177                self.nsid if self.LBA is not None else '-',
    178                self.segment if self.segment is not None else '-',
    179                self.sct if self.LBA is not None else '-',
    180                self.code if self.LBA is not None else '-'
    181            ))
    182        else:
    183            return "Unknown test format: %s" % self._format

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.

TestEntry( format, num: Optional[int], test_type, status, hours, lba, remain=None, segment=None, sense=None, asc=None, ascq=None, nsid=None, sct=None, code=None) View Source

     34    def __init__(self, format, num: Optional[int], test_type, status, hours, lba,
     35                 remain=None,
     36                 segment=None,
     37                 sense=None,
     38                 asc=None,
     39                 ascq=None,
     40                 nsid=None,
     41                 sct=None,
     42                 code=None):
     43
     44        self._format = format
     45        """
     46        **(str):** Indicates whether this entry was taken from an 'ata' or
     47        'scsi' self-test log. Used to display the content properly.
     48        """
     49        self.num: Optional[int] = num
     50        """
     51        **(int):** Entry's position in the log from 1 (most recent) to 21
     52        (least recent).  ATA logs save the last 21 entries while SCSI logs
     53        only save the last 20.
     54        """
     55        self.type = test_type
     56        """
     57        **(str):** Type of test run.  Generally short, long (extended), or
     58        conveyance, plus offline (background) or captive (foreground).
     59        """
     60        self.status = status
     61        """
     62        **(str):** Self-test's status message, for example 'Completed without
     63        error' or 'Completed: read failure'.
     64        """
     65        self.hours = hours
     66        """
     67        **(str):** The device's power-on hours at the time the self-test
     68        was initiated.
     69        """
     70        self.LBA = lba
     71        """
     72        **(str):** Indicates the first LBA at which an error was encountered
     73        during this self-test. Presented as a decimal value for ATA/SATA
     74        devices and in hexadecimal notation for SAS/SCSI devices.
     75        """
     76        self.remain = remain
     77        """
     78        **(str):** Percentage value indicating how much of the self-test is
     79        left to perform. '00%' indicates a complete test, while any other
     80        value could indicate a test in progress or one that failed prior to
     81        completion. Only reported by ATA devices.
     82        """
     83        self.segment = segment
     84        """
     85        **(str):** A manufacturer-specific self-test segment number reported
     86        by SCSI devices on self-test failure. Set to '-' otherwise.
     87        """
     88        self.sense = sense
     89        """
     90        **(str):** SCSI sense key reported on self-test failure. Set to '-'
     91        otherwise.
     92        """
     93        self.ASC = asc
     94        """
     95        **(str):** SCSI 'Additonal Sense Code' reported on self-test failure.
     96        Set to '-' otherwise.
     97        """
     98        self.ASCQ = ascq
     99        """
    100        **(str):** SCSI 'Additonal Sense Code Quaifier' reported on self-test
    101        failure. Set to '-' otherwise.
    102        """
    103        self.nsid = nsid
    104        """
    105        **(str):** NVMe 'Name Space Identifier' reported on self-test failure.
    106        Set to '-' if no namespace is defined.
    107        """
    108        self.sct = sct
    109        """
    110        **(str):** NVMe 'Status Code Type' reported on self-test failure.
    111        Set to '-' if undefined.
    112        """
    113        self.code = code
    114        """
    115        **(str):** NVMe 'Status Code' reported on self-test failure.
    116        Set to '-' if undefined.
    117        """

num: Optional[int]

(int): 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.

type

(str): Type of test run. Generally short, long (extended), or conveyance, plus offline (background) or captive (foreground).

status

(str): Self-test's status message, for example 'Completed without error' or 'Completed: read failure'.

hours

(str): The device's power-on hours at the time the self-test was initiated.

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.

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.

segment

(str): A manufacturer-specific self-test segment number reported by SCSI devices on self-test failure. Set to '-' otherwise.

sense

(str): SCSI sense key reported on self-test failure. Set to '-' otherwise.

ASC

(str): SCSI 'Additonal Sense Code' reported on self-test failure. Set to '-' otherwise.

ASCQ

(str): SCSI 'Additonal Sense Code Quaifier' reported on self-test failure. Set to '-' otherwise.

nsid

(str): NVMe 'Name Space Identifier' reported on self-test failure. Set to '-' if no namespace is defined.

sct

(str): NVMe 'Status Code Type' reported on self-test failure. Set to '-' if undefined.

code

(str): NVMe 'Status Code' reported on self-test failure. Set to '-' if undefined.

class Attribute: View Source

     28class Attribute(object):
     29    """
     30    Contains all of the information associated with a single SMART attribute
     31    in a `Device`'s SMART table. This data is intended to exactly mirror that
     32    obtained through smartctl.
     33    """
     34
     35    def __init__(self, num: int, name, flags: int, value, worst, thresh, attr_type, updated, when_failed, raw):
     36        self.num: int = num
     37        """**(int):** Attribute's ID as a decimal value (1-255)."""
     38        self.name: str = name
     39        """
     40        **(str):** Attribute's name, as reported by smartmontools' drive.db.
     41        """
     42        self.flags: int = flags
     43        """**(int):** Attribute flags as a bit value (ie: 0x0032)."""
     44        self._value: str = value
     45        """**(str):** Attribute's current normalized value."""
     46        self._worst: str = worst
     47        """**(str):** Worst recorded normalized value for this attribute."""
     48        self._thresh: str = thresh
     49        """**(str):** Attribute's failure threshold."""
     50        self.type: str = attr_type
     51        """**(str):** Attribute's type, generally 'pre-fail' or 'old-age'."""
     52        self.updated: str = updated
     53        """
     54        **(str):** When is this attribute updated? Generally 'Always' or
     55        'Offline'
     56        """
     57        self.when_failed: str = when_failed
     58        """
     59        **(str):** When did this attribute cross below
     60        `pySMART.attribute.Attribute.thresh`? Reads '-' when not failed.
     61        Generally either 'FAILING_NOW' or 'In_the_Past' otherwise.
     62        """
     63        self.raw = raw
     64        """**(str):** Attribute's current raw (non-normalized) value."""
     65
     66    @property
     67    def value_str(self) -> str:
     68        """Gets the attribute value
     69
     70        Returns:
     71            str: The attribute value in string format
     72        """
     73        return self._value
     74
     75    @property
     76    def value_int(self) -> int:
     77        """Gets the attribute value
     78
     79        Returns:
     80            int: The attribute value in integer format.
     81        """
     82        return int(self._value)
     83
     84    @property
     85    def value(self) -> str:
     86        """Gets the attribue value
     87
     88        Returns:
     89            str: The attribute value in string format
     90        """
     91        return self.value_str
     92
     93    @property
     94    def worst(self) -> int:
     95        """Gets the worst value
     96
     97        Returns:
     98            int: The attribute worst field in integer format
     99        """
    100        return int(self._worst)
    101
    102    @property
    103    def thresh(self) -> Optional[int]:
    104        """Gets the threshold value
    105
    106        Returns:
    107            int: The attribute threshold field in integer format
    108        """
    109        return None if self._thresh == '---' else int(self._thresh)
    110
    111    @property
    112    def raw_int(self) -> Optional[int]:
    113        """Gets the raw value converted to int
    114        NOTE: Some values may not be correctly converted!
    115
    116        Returns:
    117            int: The attribute raw-value field in integer format.
    118            None: In case the raw string failed to be parsed
    119        """
    120        try:
    121            return int(re.search(r'\d+', self.raw).group())
    122        except:
    123            return None
    124
    125    def __repr__(self):
    126        """Define a basic representation of the class object."""
    127        return "<SMART Attribute %r %s/%s raw:%s>" % (
    128            self.name, self.value, self.thresh, self.raw)
    129
    130    def __str__(self):
    131        """
    132        Define a formatted string representation of the object's content.
    133        In the interest of not overflowing 80-character lines this does not
    134        print the value of `pySMART.attribute.Attribute.flags_hex`.
    135        """
    136        return "{0:>3} {1:23}{2:>4}{3:>4}{4:>4} {5:9}{6:8}{7:12}{8}".format(
    137            self.num,
    138            self.name,
    139            self.value,
    140            self.worst,
    141            self.thresh,
    142            self.type,
    143            self.updated,
    144            self.when_failed,
    145            self.raw
    146        )
    147
    148    def __getstate__(self):
    149        return {
    150            'name': self.name,
    151            'num': self.num,
    152            'flags': self.flags,
    153            'raw': self.raw,
    154            'value': self.value,
    155            'worst': self.worst,
    156            'thresh': self.thresh,
    157            'type': self.type,
    158            'updated': self.updated,
    159            'when_failed': self.when_failed,
    160            # Raw values
    161            '_value': self._value,
    162            '_worst': self._worst,
    163            '_thresh': self._thresh,
    164            'raw_int': self.raw_int,
    165
    166        }

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.

Attribute( num: int, name, flags: int, value, worst, thresh, attr_type, updated, when_failed, raw) View Source

    35    def __init__(self, num: int, name, flags: int, value, worst, thresh, attr_type, updated, when_failed, raw):
    36        self.num: int = num
    37        """**(int):** Attribute's ID as a decimal value (1-255)."""
    38        self.name: str = name
    39        """
    40        **(str):** Attribute's name, as reported by smartmontools' drive.db.
    41        """
    42        self.flags: int = flags
    43        """**(int):** Attribute flags as a bit value (ie: 0x0032)."""
    44        self._value: str = value
    45        """**(str):** Attribute's current normalized value."""
    46        self._worst: str = worst
    47        """**(str):** Worst recorded normalized value for this attribute."""
    48        self._thresh: str = thresh
    49        """**(str):** Attribute's failure threshold."""
    50        self.type: str = attr_type
    51        """**(str):** Attribute's type, generally 'pre-fail' or 'old-age'."""
    52        self.updated: str = updated
    53        """
    54        **(str):** When is this attribute updated? Generally 'Always' or
    55        'Offline'
    56        """
    57        self.when_failed: str = when_failed
    58        """
    59        **(str):** When did this attribute cross below
    60        `pySMART.attribute.Attribute.thresh`? Reads '-' when not failed.
    61        Generally either 'FAILING_NOW' or 'In_the_Past' otherwise.
    62        """
    63        self.raw = raw
    64        """**(str):** Attribute's current raw (non-normalized) value."""

num: int

(int): Attribute's ID as a decimal value (1-255).

name: str

(str): Attribute's name, as reported by smartmontools' drive.db.

flags: int

(int): Attribute flags as a bit value (ie: 0x0032).

type: str

(str): Attribute's type, generally 'pre-fail' or 'old-age'.

updated: str

(str): When is this attribute updated? Generally 'Always' or 'Offline'

when_failed: str

(str): When did this attribute cross below `pySMART.attribute.Attribute.thresh`? Reads '-' when not failed. Generally either 'FAILING_NOW' or 'In_the_Past' otherwise.

raw

(str): Attribute's current raw (non-normalized) value.

value_str: str View Source

    66    @property
    67    def value_str(self) -> str:
    68        """Gets the attribute value
    69
    70        Returns:
    71            str: The attribute value in string format
    72        """
    73        return self._value

Gets the attribute value

Returns: str: The attribute value in string format

value_int: int View Source

    75    @property
    76    def value_int(self) -> int:
    77        """Gets the attribute value
    78
    79        Returns:
    80            int: The attribute value in integer format.
    81        """
    82        return int(self._value)

Gets the attribute value

Returns: int: The attribute value in integer format.

value: str View Source

    84    @property
    85    def value(self) -> str:
    86        """Gets the attribue value
    87
    88        Returns:
    89            str: The attribute value in string format
    90        """
    91        return self.value_str

Gets the attribue value

Returns: str: The attribute value in string format

worst: int View Source

     93    @property
     94    def worst(self) -> int:
     95        """Gets the worst value
     96
     97        Returns:
     98            int: The attribute worst field in integer format
     99        """
    100        return int(self._worst)

Gets the worst value

Returns: int: The attribute worst field in integer format

thresh: Optional[int] View Source

    102    @property
    103    def thresh(self) -> Optional[int]:
    104        """Gets the threshold value
    105
    106        Returns:
    107            int: The attribute threshold field in integer format
    108        """
    109        return None if self._thresh == '---' else int(self._thresh)

Gets the threshold value

Returns: int: The attribute threshold field in integer format

raw_int: Optional[int] View Source

    111    @property
    112    def raw_int(self) -> Optional[int]:
    113        """Gets the raw value converted to int
    114        NOTE: Some values may not be correctly converted!
    115
    116        Returns:
    117            int: The attribute raw-value field in integer format.
    118            None: In case the raw string failed to be parsed
    119        """
    120        try:
    121            return int(re.search(r'\d+', self.raw).group())
    122        except:
    123            return None

Gets the raw value converted to int NOTE: Some values may not be correctly converted!

Returns: int: The attribute raw-value field in integer format. None: In case the raw string failed to be parsed

SMARTCTL = <pySMART.smartctl.Smartctl object>
class DeviceList: View Source

     37class DeviceList(object):
     38    """
     39    Represents a list of all the storage devices connected to this computer.
     40    """
     41
     42    def __init__(self, init: bool = True, smartctl=SMARTCTL, catch_errors: bool = False):
     43        """Instantiates and optionally initializes the `DeviceList`.
     44
     45        Args:
     46            init (bool, optional): By default, `pySMART.device_list.DeviceList.devices`
     47                is populated with `Device` objects during instantiation. Setting init
     48                to False will skip initialization and create an empty
     49                `pySMART.device_list.DeviceList` object instead. Defaults to True.
     50            smartctl ([type], optional): This stablish the smartctl wrapper.
     51                Defaults the global `SMARTCTL` object and should be only
     52                overwritten on tests.
     53            catch_errors (bool, optional): If True, individual device-parsing errors will be caught
     54        """
     55
     56        self.devices: List[Device] = []
     57        """
     58        **(list of `Device`):** Contains all storage devices detected during
     59        instantiation, as `Device` objects.
     60        """
     61        self.smartctl: Smartctl = smartctl
     62        """The smartctl wrapper
     63        """
     64        if init:
     65            self.initialize(catch_errors)
     66
     67    def __repr__(self):
     68        """Define a basic representation of the class object."""
     69        rep = "<DeviceList contents:\n"
     70        for device in self.devices:
     71            rep += str(device) + '\n'
     72        return rep + '>'
     73        # return "<DeviceList contents:%r>" % (self.devices)
     74
     75    def _cleanup(self):
     76        """
     77        Removes duplicate ATA devices that correspond to an existing CSMI
     78        device. Also removes any device with no capacity value, as this
     79        indicates removable storage, ie: CD/DVD-ROM, ZIP, etc.
     80        """
     81        # We can't operate directly on the list while we're iterating
     82        # over it, so we collect indeces to delete and remove them later
     83        to_delete = []
     84        # Enumerate the list to get tuples containing indeces and values
     85        for index, device in enumerate(self.devices):
     86            if device.interface == 'csmi':
     87                for otherindex, otherdevice in enumerate(self.devices):
     88                    if (otherdevice.interface == 'ata' or
     89                            otherdevice.interface == 'sata'):
     90                        if device.serial == otherdevice.serial:
     91                            to_delete.append(otherindex)
     92                            device._sd_name = otherdevice.name
     93            if device.capacity is None and index not in to_delete:
     94                to_delete.append(index)
     95        # Recreate the self.devices list without the marked indeces
     96        self.devices[:] = [v for i, v in enumerate(self.devices)
     97                           if i not in to_delete]
     98
     99    def initialize(self, catch_errors: bool = False):
    100        """
    101        Scans system busses for attached devices and add them to the
    102        `DeviceList` as `Device` objects.
    103        If device list is already populated, it will be cleared first.
    104
    105        Args:
    106            catch_errors (bool, optional): If True, individual device-parsing errors will be caught
    107        """
    108
    109        # Clear the list if it's already populated
    110        if len(self.devices):
    111            self.devices = []
    112
    113        # Scan for devices
    114        for line in self.smartctl.scan():
    115            if not ('failed:' in line or line == ''):
    116                groups = re.compile(
    117                    r'^(\S+)\s+-d\s+(\S+)').match(line).groups()
    118                name = groups[0]
    119                interface = groups[1]
    120
    121                try:
    122                    # Add the device to the list
    123                    self.devices.append(
    124                        Device(name, interface=interface, smartctl=self.smartctl))
    125
    126                except Exception as e:
    127                    if catch_errors:
    128                        # Print the exception
    129                        import logging
    130
    131                        logging.exception(f"Error parsing device {name}")
    132
    133                    else:
    134                        # Reraise the exception
    135                        raise e
    136
    137        # Remove duplicates and unwanted devices (optical, etc.) from the list
    138        self._cleanup()
    139        # Sort the list alphabetically by device name
    140        self.devices.sort(key=lambda device: device.name)
    141
    142    def __getitem__(self, index: int) -> Device:
    143        """Returns an element from self.devices
    144
    145        Args:
    146            index (int): An index of self.devices
    147
    148        Returns:
    149            Device: Returns a Device that is located on the asked index
    150        """
    151        return self.devices[index]

Represents a list of all the storage devices connected to this computer.

DeviceList( init: bool = True, smartctl=<pySMART.smartctl.Smartctl object>, catch_errors: bool = False) View Source

    42    def __init__(self, init: bool = True, smartctl=SMARTCTL, catch_errors: bool = False):
    43        """Instantiates and optionally initializes the `DeviceList`.
    44
    45        Args:
    46            init (bool, optional): By default, `pySMART.device_list.DeviceList.devices`
    47                is populated with `Device` objects during instantiation. Setting init
    48                to False will skip initialization and create an empty
    49                `pySMART.device_list.DeviceList` object instead. Defaults to True.
    50            smartctl ([type], optional): This stablish the smartctl wrapper.
    51                Defaults the global `SMARTCTL` object and should be only
    52                overwritten on tests.
    53            catch_errors (bool, optional): If True, individual device-parsing errors will be caught
    54        """
    55
    56        self.devices: List[Device] = []
    57        """
    58        **(list of `Device`):** Contains all storage devices detected during
    59        instantiation, as `Device` objects.
    60        """
    61        self.smartctl: Smartctl = smartctl
    62        """The smartctl wrapper
    63        """
    64        if init:
    65            self.initialize(catch_errors)

Instantiates and optionally initializes the `DeviceList`.

Args: init (bool, optional): By default, `DeviceList.devices` is populated with `Device` objects during instantiation. Setting init to False will skip initialization and create an empty `DeviceList` object instead. Defaults to True. smartctl ([type], optional): This stablish the smartctl wrapper. Defaults the global `SMARTCTL` object and should be only overwritten on tests. catch_errors (bool, optional): If True, individual device-parsing errors will be caught

devices: List[Device]

(list of `Device`): Contains all storage devices detected during instantiation, as `Device` objects.

smartctl: pySMART.smartctl.Smartctl

The smartctl wrapper

def initialize(self, catch_errors: bool = False): View Source

     99    def initialize(self, catch_errors: bool = False):
    100        """
    101        Scans system busses for attached devices and add them to the
    102        `DeviceList` as `Device` objects.
    103        If device list is already populated, it will be cleared first.
    104
    105        Args:
    106            catch_errors (bool, optional): If True, individual device-parsing errors will be caught
    107        """
    108
    109        # Clear the list if it's already populated
    110        if len(self.devices):
    111            self.devices = []
    112
    113        # Scan for devices
    114        for line in self.smartctl.scan():
    115            if not ('failed:' in line or line == ''):
    116                groups = re.compile(
    117                    r'^(\S+)\s+-d\s+(\S+)').match(line).groups()
    118                name = groups[0]
    119                interface = groups[1]
    120
    121                try:
    122                    # Add the device to the list
    123                    self.devices.append(
    124                        Device(name, interface=interface, smartctl=self.smartctl))
    125
    126                except Exception as e:
    127                    if catch_errors:
    128                        # Print the exception
    129                        import logging
    130
    131                        logging.exception(f"Error parsing device {name}")
    132
    133                    else:
    134                        # Reraise the exception
    135                        raise e
    136
    137        # Remove duplicates and unwanted devices (optical, etc.) from the list
    138        self._cleanup()
    139        # Sort the list alphabetically by device name
    140        self.devices.sort(key=lambda device: device.name)

Scans system busses for attached devices and add them to the `DeviceList` as `Device` objects. If device list is already populated, it will be cleared first.

Args: catch_errors (bool, optional): If True, individual device-parsing errors will be caught

class Device: View Source

      81class Device(object):
      82    """
      83    Represents any device attached to an internal storage interface, such as a
      84    hard drive or DVD-ROM, and detected by smartmontools. Includes eSATA
      85    (considered SATA) but excludes other external devices (USB, Firewire).
      86    """
      87
      88    def __init__(self, name: str, interface: Optional[str] = None, abridged: bool = False, smart_options: Union[str, List[str], None] = None, smartctl: Smartctl = SMARTCTL):
      89        """Instantiates and initializes the `pySMART.device.Device`."""
      90        if not (
      91                interface is None or
      92                smartctl_isvalid_type(interface.lower())
      93        ):
      94            raise ValueError(
      95                'Unknown interface: {0} specified for {1}'.format(interface, name))
      96        self.abridged = abridged or interface == 'UNKNOWN INTERFACE'
      97        if smart_options is not None:
      98            if isinstance(smart_options,  str):
      99                smart_options = smart_options.split(' ')
     100            smartctl.add_options(smart_options)
     101        self.smartctl = smartctl
     102        """
     103        """
     104        self.name: str = name.replace('/dev/', '').replace('nvd', 'nvme')
     105        """
     106        **(str):** Device's hardware ID, without the '/dev/' prefix.
     107        (ie: sda (Linux), pd0 (Windows))
     108        """
     109        self.family: Optional[str] = None
     110        """**(str):** Device's family (if any)."""
     111        self.model: Optional[str] = None
     112        """**(str):** Device's model number (if any)."""
     113        self.serial: Optional[str] = None
     114        """**(str):** Device's serial number (if any)."""
     115        self._vendor: Optional[str] = None
     116        """**(str):** Device's vendor (if any)."""
     117        self._interface: Optional[str] = None if interface == 'UNKNOWN INTERFACE' else interface
     118        """
     119        **(str):** Device's interface type. Must be one of:
     120            * **ATA** - Advanced Technology Attachment
     121            * **SATA** - Serial ATA
     122            * **SCSI** - Small Computer Systems Interface
     123            * **SAS** - Serial Attached SCSI
     124            * **SAT** - SCSI-to-ATA Translation (SATA device plugged into a
     125            SAS port)
     126            * **CSMI** - Common Storage Management Interface (Intel ICH /
     127            Matrix RAID)
     128        Generally this should not be specified to allow auto-detection to
     129        occur. Otherwise, this value overrides the auto-detected type and could
     130        produce unexpected or no data.
     131        """
     132        self._capacity: Optional[int] = None
     133        """**(str):** Device's user capacity as reported directly by smartctl (RAW)."""
     134        self._capacity_human: Optional[str] = None
     135        """**(str):** Device's user capacity (human readable) as reported directly by smartctl (RAW)."""
     136        self.firmware: Optional[str] = None
     137        """**(str):** Device's firmware version."""
     138        self.smart_capable: bool = 'nvme' in self.name
     139        """
     140        **(bool):** True if the device has SMART Support Available.
     141        False otherwise. This is useful for VMs amongst other things.
     142        """
     143        self.smart_enabled: bool = 'nvme' in self.name
     144        """
     145        **(bool):** True if the device supports SMART (or SCSI equivalent) and
     146        has the feature set enabled. False otherwise.
     147        """
     148        self.assessment: Optional[str] = None
     149        """
     150        **(str):** SMART health self-assessment as reported by the device.
     151        """
     152        self.messages: List[str] = []
     153        """
     154        **(list of str):** Contains any SMART warnings or other error messages
     155        reported by the device (ie: ascq codes).
     156        """
     157        self.is_ssd: bool = True if 'nvme' in self.name else False
     158        """
     159        **(bool):** True if this device is a Solid State Drive.
     160        False otherwise.
     161        """
     162        self.rotation_rate: Optional[int] = None
     163        """
     164        **(int):** The Roatation Rate of the Drive if it is not a SSD.
     165        The Metric is RPM.
     166        """
     167        self.test_capabilities = {
     168            'offline': False,  # SMART execute Offline immediate (ATA only)
     169            'short': 'nvme' not in self.name,  # SMART short Self-test
     170            'long': 'nvme' not in self.name,  # SMART long Self-test
     171            'conveyance': False,  # SMART Conveyance Self-Test (ATA only)
     172            'selective': False,  # SMART Selective Self-Test (ATA only)
     173        }
     174        # Note have not included 'offline' test for scsi as it runs in the foregorund
     175        # mode. While this may be beneficial to us in someways it is against the
     176        # general layout and pattern that the other tests issued using pySMART are
     177        # followed hence not doing it currently
     178        """
     179        **(dict): ** This dictionary contains key == 'Test Name' and
     180        value == 'True/False' of self-tests that this device is capable of.
     181        """
     182        # Note: The above are just default values and can/will be changed
     183        # upon update() when the attributes and type of the disk is actually
     184        # determined.
     185        self.test_polling_time = {
     186            'short': 10,
     187            'long': 1000,
     188            'conveyance': 20,
     189        }
     190        """
     191        **(dict): ** This dictionary contains key == 'Test Name' and
     192        value == int of approximate times to run each test type that this
     193        device is capable of.
     194        """
     195        # Note: The above are just default values and can/will be changed
     196        # upon update() when the attributes and type of the disk is actually
     197        # determined.
     198        self.tests: List[TestEntry] = []
     199        """
     200        **(list of `TestEntry`):** Contains the complete SMART self-test log
     201        for this device, as provided by smartctl.
     202        """
     203        self._test_running = False
     204        """
     205        **(bool):** True if a self-test is currently being run.
     206        False otherwise.
     207        """
     208        self._test_ECD = None
     209        """
     210        **(str):** Estimated completion time of the running SMART selftest.
     211        Not provided by SAS/SCSI devices.
     212        """
     213        self._test_progress = None
     214        """
     215        **(int):** Estimate progress percantage of the running SMART selftest.
     216        """
     217        self.diagnostics: Diagnostics = Diagnostics()
     218        """
     219        **Diagnostics** Contains parsed and processed diagnostic information
     220        extracted from the SMART information. Currently only populated for
     221        SAS and SCSI devices, since ATA/SATA SMART attributes are manufacturer
     222        proprietary.
     223        """
     224        self._temperature: Optional[int] = None
     225        """
     226        **(int or None): Since SCSI disks do not report attributes like ATA ones
     227        we need to grep/regex the shit outta the normal "smartctl -a" output.
     228        In case the device have more than one temperature sensor the first value
     229        will be stored here too.
     230        Note: Temperatures are always in Celsius (if possible).
     231        """
     232        self.temperatures: Dict[int, int] = {}
     233        """
     234        **(dict of int): NVMe disks usually report multiple temperatures, which
     235        will be stored here if available. Keys are sensor numbers as reported in
     236        output data.
     237        Note: Temperatures are always in Celsius (if possible).
     238        """
     239        self.logical_sector_size: Optional[int] = None
     240        """
     241        **(int):** The logical sector size of the device (or LBA).
     242        """
     243        self.physical_sector_size: Optional[int] = None
     244        """
     245        **(int):** The physical sector size of the device.
     246        """
     247        self.if_attributes: Union[None, NvmeAttributes, AtaAttributes] = None
     248        """
     249        **(NvmeAttributes):** This object may vary for each device interface attributes.
     250        It will store all data obtained from smartctl
     251        """
     252
     253        if self.name is None:
     254            warnings.warn(
     255                "\nDevice '{0}' does not exist! This object should be destroyed.".format(
     256                    name)
     257            )
     258            return
     259        # If no interface type was provided, scan for the device
     260        # Lets do this only for the non-abridged case
     261        # (we can work with no interface for abridged case)
     262        elif self._interface is None and not self.abridged:
     263            logger.trace(
     264                "Determining interface of disk: {0}".format(self.name))
     265            raw, returncode = self.smartctl.generic_call(
     266                ['-d', 'test', self.dev_reference])
     267
     268            if len(raw) > 0:
     269                # I do not like this parsing logic but it works for now!
     270                # just for reference _stdout.split('\n') gets us
     271                # something like
     272                # [
     273                #     ...copyright string...,
     274                #     '',
     275                #     "/dev/ada2: Device of type 'atacam' [ATA] detected",
     276                #     "/dev/ada2: Device of type 'atacam' [ATA] opened",
     277                #     ''
     278                # ]
     279                # The above example should be enough for anyone to understand the line below
     280                try:
     281                    for line in reversed(raw):
     282                        if "opened" in line:
     283                            self._interface = line.split("'")[1]
     284
     285                            if self._interface == "nvme":  # if nvme set SMART to true
     286                                self.smart_capable = True
     287                                self.smart_enabled = True
     288
     289                            break
     290                except:
     291                    # for whatever reason we could not get the interface type
     292                    # we should mark this as an `abbridged` case and move on
     293                    self._interface = None
     294                    self.abbridged = True
     295                # TODO: Uncomment the classify call if we ever find out that we need it
     296                # Disambiguate the generic interface to a specific type
     297                # self._classify()
     298            else:
     299                warnings.warn(
     300                    "\nDevice '{0}' does not exist! This object should be destroyed.".format(
     301                        name)
     302                )
     303                return
     304        # If a valid device was detected, populate its information
     305        # OR if in unabridged mode, then do it even without interface info
     306        if self._interface is not None or self.abridged:
     307            self.update()
     308
     309    @property
     310    def attributes(self) -> List[Optional[Attribute]]:
     311        """Returns the SMART attributes of the device.
     312        Note: This is only filled with ATA/SATA attributes. SCSI/SAS/NVMe devices will have empty lists!!
     313        @deprecated: Use `if_attributes` instead.
     314
     315        Returns:
     316            list of `Attribute`: The SMART attributes of the device.
     317        """
     318        if self.if_attributes is None or not isinstance(self.if_attributes, AtaAttributes):
     319            return [None] * 256
     320        else:
     321            return self.if_attributes.legacyAttributes
     322
     323    @property
     324    def dev_interface(self) -> Optional[str]:
     325        """Returns the internal interface type of the device.
     326           It may not be the same as the interface type as used by smartctl.
     327
     328        Returns:
     329            str: The interface type of the device. (example: ata, scsi, nvme)
     330                 None if the interface type could not be determined.
     331        """
     332        # Try to get the fine-tuned interface type
     333        fineType = self._classify()
     334
     335        # If return still contains a megaraid, just asume it's type
     336        if 'megaraid' in fineType:
     337            # If any attributes is not None and has at least non None value, then it is a sat+megaraid device
     338            if isinstance(self.if_attributes, AtaAttributes):
     339                return 'ata'
     340            else:
     341                return 'sas'
     342
     343        return fineType
     344
     345    @property
     346    def temperature(self) -> Optional[int]:
     347        """Returns the temperature of the device.
     348
     349        Returns:
     350            int: The temperature of the device in Celsius.
     351                 None if the temperature could not be determined.
     352        """
     353        if self.if_attributes is None:
     354            return self._temperature
     355        else:
     356            return self.if_attributes.temperature or self._temperature
     357
     358    @property
     359    def smartctl_interface(self) -> Optional[str]:
     360        """Returns the interface type of the device as it is used in smartctl.
     361
     362        Returns:
     363            str: The interface type of the device. (example: ata, scsi, nvme)
     364                 None if the interface type could not be determined.
     365        """
     366        return self._interface
     367
     368    @property
     369    def interface(self) -> Optional[str]:
     370        """Returns the interface type of the device as it is used in smartctl.
     371
     372        Returns:
     373            str: The interface type of the device. (example: ata, scsi, nvme)
     374                 None if the interface type could not be determined.
     375        """
     376        return self.smartctl_interface
     377
     378    @property
     379    def dev_reference(self) -> str:
     380        """The reference to the device as provided by smartctl.
     381           - On unix-like systems, this is the path to the device. (example /dev/<name>)
     382           - On MacOS, this is the name of the device. (example <name>)
     383           - On Windows, this is the drive letter of the device. (example <drive letter>)
     384
     385        Returns:
     386            str: The reference to the device as provided by smartctl.
     387        """
     388
     389        # detect if we are on MacOS
     390        if 'IOService' in self.name:
     391            return self.name
     392
     393        # otherwise asume we are on unix-like systems
     394        return os.path.join('/dev/', self.name)
     395
     396    @property
     397    def vendor(self) -> Optional[str]:
     398        """Returns the vendor of the device.
     399
     400        Returns:
     401            str: The vendor of the device.
     402        """
     403        if self._vendor:
     404            return self._vendor
     405
     406        # If family is present, try to stract from family. Skip anything but letters.
     407        elif self.family:
     408            filter = re.search(r'^[a-zA-Z]+', self.family.strip())
     409            if filter:
     410                return filter.group(0)
     411
     412        # If model is present, try to stract from model. Skip anything but letters.
     413        elif self.model:
     414            filter = re.search(r'^[a-zA-Z]+', self.model.strip())
     415            if filter:
     416                return filter.group(0)
     417
     418        # If all else fails, return None
     419        return None
     420
     421    @property
     422    def capacity(self) -> Optional[str]:
     423        """Returns the capacity in the raw smartctl format.
     424        This may be deprecated in the future and its only retained for compatibility.
     425
     426        Returns:
     427            str: The capacity in the raw smartctl format
     428        """
     429        return self._capacity_human
     430
     431    @property
     432    def diags(self) -> Dict[str, str]:
     433        """Gets the old/deprecated version of SCSI/SAS diags atribute.
     434        """
     435        return self.diagnostics.get_classic_format()
     436
     437    @property
     438    def size_raw(self) -> Optional[str]:
     439        """Returns the capacity in the raw smartctl format.
     440
     441        Returns:
     442            str: The capacity in the raw smartctl format
     443        """
     444        return self._capacity_human
     445
     446    @property
     447    def size(self) -> int:
     448        """Returns the capacity in bytes
     449
     450        Returns:
     451            int: The capacity in bytes
     452        """
     453        import humanfriendly
     454
     455        if self._capacity is not None:
     456            return self._capacity
     457        elif self._capacity_human is not None:
     458            return humanfriendly.parse_size(self._capacity_human)
     459        else:
     460            return 0
     461
     462    @property
     463    def sector_size(self) -> int:
     464        """Returns the sector size of the device.
     465
     466        Returns:
     467            int: The sector size of the device in Bytes. If undefined, we'll assume 512B
     468        """
     469        if self.logical_sector_size is not None:
     470            return self.logical_sector_size
     471        elif self.physical_sector_size is not None:
     472            return self.physical_sector_size
     473        else:
     474            return 512
     475
     476    def __repr__(self):
     477        """Define a basic representation of the class object."""
     478        return "<{0} device on /dev/{1} mod:{2} sn:{3}>".format(
     479            self._interface.upper() if self._interface else 'UNKNOWN INTERFACE',
     480            self.name,
     481            self.model,
     482            self.serial
     483        )
     484
     485    def __getstate__(self, all_info=True) -> Dict:
     486        """
     487        Allows us to send a pySMART Device object over a serializable
     488        medium which uses json (or the likes of json) payloads
     489        """
     490
     491        state_dict = {
     492            'attributes': [attr.__getstate__() if attr else None for attr in self.attributes],
     493            'capacity': self._capacity_human,
     494            'diagnostics': self.diagnostics.__getstate__(all_info),
     495            'firmware': self.firmware,
     496            'if_attributes': self.if_attributes.__getstate__() if self.if_attributes else None,
     497            'interface': self._interface if self._interface else 'UNKNOWN INTERFACE',
     498            'is_ssd': self.is_ssd,
     499            'messages': self.messages,
     500            'model': self.model,
     501            'name': self.name,
     502            'path': self.dev_reference,
     503            'rotation_rate': self.rotation_rate,
     504            'serial': self.serial,
     505            'smart_capable': self.smart_capable,
     506            'smart_enabled': self.smart_enabled,
     507            'smart_status': self.assessment,
     508            'temperature': self.temperature,
     509            'test_capabilities': self.test_capabilities.copy(),
     510            'tests': [t.__getstate__() for t in self.tests] if self.tests else [],
     511        }
     512        return state_dict
     513
     514    def __setstate__(self, state):
     515        state['assessment'] = state['smart_status']
     516        del state['smart_status']
     517        self.__dict__.update(state)
     518
     519    def smart_toggle(self, action: str) -> Tuple[bool, List[str]]:
     520        """
     521        A basic function to enable/disable SMART on device.
     522
     523        # Args:
     524        * **action (str):** Can be either 'on'(for enabling) or 'off'(for disabling).
     525
     526        # Returns"
     527        * **(bool):** Return True (if action succeded) else False
     528        * **(List[str]):** None if option succeded else contains the error message.
     529        """
     530        # Lets make the action verb all lower case
     531        if self._interface == 'nvme':
     532            return False, ['NVME devices do not currently support toggling SMART enabled']
     533        action_lower = action.lower()
     534        if action_lower not in ['on', 'off']:
     535            return False, ['Unsupported action {0}'.format(action)]
     536        # Now lets check if the device's smart enabled status is already that of what
     537        # the supplied action is intending it to be. If so then just return successfully
     538        if self.smart_enabled:
     539            if action_lower == 'on':
     540                return True, []
     541        else:
     542            if action_lower == 'off':
     543                return True, []
     544        if self._interface is not None:
     545            raw, returncode = self.smartctl.generic_call(
     546                ['-s', action_lower, '-d', self._interface, self.dev_reference])
     547        else:
     548            raw, returncode = self.smartctl.generic_call(
     549                ['-s', action_lower, self.dev_reference])
     550
     551        if returncode != 0:
     552            return False, raw
     553        # if everything worked out so far lets perform an update() and check the result
     554        self.update()
     555        if action_lower == 'off' and self.smart_enabled:
     556            return False, ['Failed to turn SMART off.']
     557        if action_lower == 'on' and not self.smart_enabled:
     558            return False, ['Failed to turn SMART on.']
     559        return True, []
     560
     561    def all_attributes(self, print_fn=print):
     562        """
     563        Prints the entire SMART attribute table, in a format similar to
     564        the output of smartctl.
     565        allows usage of custom print function via parameter print_fn by default uses print
     566        """
     567        header_printed = False
     568        for attr in self.attributes:
     569            if attr is not None:
     570                if not header_printed:
     571                    print_fn("{0:>3} {1:24}{2:4}{3:4}{4:4}{5:9}{6:8}{7:12}{8}"
     572                             .format('ID#', 'ATTRIBUTE_NAME', 'CUR', 'WST', 'THR', 'TYPE', 'UPDATED', 'WHEN_FAIL',
     573                                     'RAW'))
     574                    header_printed = True
     575                print_fn(attr)
     576        if not header_printed:
     577            print_fn('This device does not support SMART attributes.')
     578
     579    def all_selftests(self):
     580        """
     581        Prints the entire SMART self-test log, in a format similar to
     582        the output of smartctl.
     583        """
     584        if self.tests:
     585            all_tests = []
     586            if smartctl_type(self._interface) == 'scsi':
     587                header = "{0:3}{1:17}{2:23}{3:7}{4:14}{5:15}".format(
     588                    'ID',
     589                    'Test Description',
     590                    'Status',
     591                    'Hours',
     592                    '1st_Error@LBA',
     593                    '[SK  ASC  ASCQ]'
     594                )
     595            else:
     596                header = ("{0:3}{1:17}{2:30}{3:5}{4:7}{5:17}".format(
     597                    'ID',
     598                    'Test_Description',
     599                    'Status',
     600                    'Left',
     601                    'Hours',
     602                    '1st_Error@LBA'))
     603            all_tests.append(header)
     604            for test in self.tests:
     605                all_tests.append(str(test))
     606
     607            return all_tests
     608        else:
     609            no_tests = 'No self-tests have been logged for this device.'
     610            return no_tests
     611
     612    def _classify(self) -> str:
     613        """
     614        Disambiguates generic device types ATA and SCSI into more specific
     615        ATA, SATA, SAS, SAT and SCSI.
     616        """
     617
     618        fine_interface = self._interface or ''
     619        # SCSI devices might be SCSI, SAS or SAT
     620        # ATA device might be ATA or SATA
     621        if fine_interface in ['scsi', 'ata'] or 'megaraid' in fine_interface:
     622            if 'megaraid' in fine_interface:
     623                if not 'sat+' in fine_interface:
     624                    test = 'sat'+fine_interface
     625                else:
     626                    test = fine_interface
     627            else:
     628                test = 'sat' if fine_interface == 'scsi' else 'sata'
     629            # Look for a SATA PHY to detect SAT and SATA
     630            raw, returncode = self.smartctl.try_generic_call([
     631                '-d',
     632                smartctl_type(test),
     633                '-l',
     634                'sataphy',
     635                self.dev_reference])
     636
     637            if returncode == 0 and 'GP Log 0x11' in raw[3]:
     638                fine_interface = test
     639        # If device type is still SCSI (not changed to SAT above), then
     640        # check for a SAS PHY
     641        if fine_interface in ['scsi'] or 'megaraid' in fine_interface:
     642            raw, returncode = self.smartctl.try_generic_call([
     643                '-d',
     644                smartctl_type(fine_interface),
     645                '-l',
     646                'sasphy',
     647                self.dev_reference])
     648            if returncode == 0 and 'SAS SSP' in raw[4]:
     649                fine_interface = 'sas'
     650            # Some older SAS devices do not support the SAS PHY log command.
     651            # For these, see if smartmontools reports a transport protocol.
     652            else:
     653                raw = self.smartctl.all(self.dev_reference, fine_interface)
     654
     655                for line in raw:
     656                    if 'Transport protocol' in line and 'SAS' in line:
     657                        fine_interface = 'sas'
     658
     659        return fine_interface
     660
     661    def _guess_smart_type(self, line):
     662        """
     663        This function is not used in the generic wrapper, however the header
     664        is defined so that it can be monkey-patched by another application.
     665        """
     666        pass
     667
     668    def _make_smart_warnings(self):
     669        """
     670        Parses an ATA/SATA SMART table for attributes with the 'when_failed'
     671        value set. Generates an warning message for any such attributes and
     672        updates the self-assessment value if necessary.
     673        """
     674        if smartctl_type(self._interface) == 'scsi':
     675            return
     676        for attr in self.attributes:
     677            if attr is not None:
     678                if attr.when_failed == 'In_the_past':
     679                    warn_str = "{0} failed in the past with value {1}. [Threshold: {2}]".format(
     680                        attr.name, attr.worst, attr.thresh)
     681                    self.messages.append(warn_str)
     682                    if not self.assessment == 'FAIL':
     683                        self.assessment = 'WARN'
     684                elif attr.when_failed == 'FAILING_NOW':
     685                    warn_str = "{0} is failing now with value {1}. [Threshold: {2}]".format(
     686                        attr.name, attr.value, attr.thresh)
     687                    self.assessment = 'FAIL'
     688                    self.messages.append(warn_str)
     689                elif not attr.when_failed == '-':
     690                    warn_str = "{0} says it failed '{1}'. [V={2},W={3},T={4}]".format(
     691                        attr.name, attr.when_failed, attr.value, attr.worst, attr.thresh)
     692                    self.messages.append(warn_str)
     693                    if not self.assessment == 'FAIL':
     694                        self.assessment = 'WARN'
     695
     696    def get_selftest_result(self, output=None):
     697        """
     698        Refreshes a device's `pySMART.device.Device.tests` attribute to obtain
     699        the latest test results. If a new test result is obtained, its content
     700        is returned.
     701
     702        # Args:
     703        * **output (str, optional):** If set to 'str', the string
     704        representation of the most recent test result will be returned, instead
     705        of a `Test_Entry` object.
     706
     707        # Returns:
     708        * **(int):** Return status code. One of the following:
     709            * 0 - Success. Object (or optionally, string rep) is attached.
     710            * 1 - Self-test in progress. Must wait for it to finish.
     711            * 2 - No new test results.
     712            * 3 - The Self-test was Aborted by host
     713        * **(`Test_Entry` or str):** Most recent `Test_Entry` object (or
     714        optionally it's string representation) if new data exists.  Status
     715        message string on failure.
     716        * **(int):** Estimate progress percantage of the running SMART selftest, if known.
     717        Otherwise 'None'.
     718        """
     719        # SCSI self-test logs hold 20 entries while ATA logs hold 21
     720        if smartctl_type(self._interface) == 'scsi':
     721            maxlog = 20
     722        else:
     723            maxlog = 21
     724        # If we looked only at the most recent test result we could be fooled
     725        # by two short tests run close together (within the same hour)
     726        # appearing identical. Comparing the length of the log adds some
     727        # confidence until it maxes, as above. Comparing the least-recent test
     728        # result greatly diminishes the chances that two sets of two tests each
     729        # were run within an hour of themselves, but with 16-17 other tests run
     730        # in between them.
     731        if self.tests:
     732            _first_entry = self.tests[0]
     733            _len = len(self.tests)
     734            _last_entry = self.tests[_len - 1]
     735        else:
     736            _len = 0
     737        self.update()
     738        # Since I have changed the update() parsing to DTRT to pickup currently
     739        # running selftests we can now purely rely on that for self._test_running
     740        # Thus check for that variable first and return if it is True with appropos message.
     741        if self._test_running is True:
     742            return 1, 'Self-test in progress. Please wait.', self._test_progress
     743        # Check whether the list got longer (ie: new entry)
     744        # If so return the newest test result
     745        # If not, because it's max size already, check for new entries
     746        if (
     747                (len(self.tests) != _len) or
     748                (
     749                    len == maxlog and
     750                    (
     751                        _first_entry.type != self.tests[0].type or
     752                        _first_entry.hours != self.tests[0].hours or
     753                        _last_entry.type != self.tests[len(self.tests) - 1].type or
     754                        _last_entry.hours != self.tests[len(
     755                            self.tests) - 1].hours
     756                    )
     757                )
     758        ):
     759            return (
     760                0 if 'Aborted' not in self.tests[0].status else 3,
     761                str(self.tests[0]) if output == 'str' else self.tests[0],
     762                None
     763            )
     764        else:
     765            return 2, 'No new self-test results found.', None
     766
     767    def abort_selftest(self):
     768        """
     769        Aborts non-captive SMART Self Tests.   Note that this command
     770        will  abort the Offline Immediate Test routine only if your disk
     771        has the "Abort Offline collection upon new command"  capability.
     772
     773        # Args: Nothing (just aborts directly)
     774
     775        # Returns:
     776        * **(int):** The returncode of calling `smartctl -X device_path`
     777        """
     778        return self.smartctl.test_stop(smartctl_type(self._interface), self.dev_reference)
     779
     780    def run_selftest(self, test_type, ETA_type='date'):
     781        """
     782        Instructs a device to begin a SMART self-test. All tests are run in
     783        'offline' / 'background' mode, allowing normal use of the device while
     784        it is being tested.
     785
     786        # Args:
     787        * **test_type (str):** The type of test to run. Accepts the following
     788        (not case sensitive):
     789            * **short** - Brief electo-mechanical functionality check.
     790            Generally takes 2 minutes or less.
     791            * **long** - Thorough electro-mechanical functionality check,
     792            including complete recording media scan. Generally takes several
     793            hours.
     794            * **conveyance** - Brief test used to identify damage incurred in
     795            shipping. Generally takes 5 minutes or less. **This test is not
     796            supported by SAS or SCSI devices.**
     797            * **offline** - Runs SMART Immediate Offline Test. The effects of
     798            this test are visible only in that it updates the SMART Attribute
     799            values, and if errors are found they will appear in the SMART error
     800            log, visible with the '-l error' option to smartctl. **This test is
     801            not supported by SAS or SCSI devices in pySMART use cli smartctl for
     802            running 'offline' selftest (runs in foreground) on scsi devices.**
     803            * **ETA_type** - Format to return the estimated completion time/date
     804            in. Default is 'date'. One could otherwise specidy 'seconds'.
     805            Again only for ATA devices.
     806
     807        # Returns:
     808        * **(int):** Return status code.  One of the following:
     809            * 0 - Self-test initiated successfully
     810            * 1 - Previous self-test running. Must wait for it to finish.
     811            * 2 - Unknown or unsupported (by the device) test type requested.
     812            * 3 - Unspecified smartctl error. Self-test not initiated.
     813        * **(str):** Return status message.
     814        * **(str)/(float):** Estimated self-test completion time if a test is started.
     815        The optional argument of 'ETA_type' (see above) controls the return type.
     816        if 'ETA_type' == 'date' then a date string is returned else seconds(float)
     817        is returned.
     818        Note: The self-test completion time can only be obtained for ata devices.
     819        Otherwise 'None'.
     820        """
     821        # Lets call get_selftest_result() here since it does an update() and
     822        # checks for an existing selftest is running or not, this way the user
     823        # can issue a test from the cli and this can still pick that up
     824        # Also note that we do not need to obtain the results from this as the
     825        # data is already stored in the Device class object's variables
     826        self.get_selftest_result()
     827        if self._test_running:
     828            return 1, 'Self-test in progress. Please wait.', self._test_ECD
     829        test_type = test_type.lower()
     830        interface = smartctl_type(self._interface)
     831        try:
     832            if not self.test_capabilities[test_type]:
     833                return (
     834                    2,
     835                    "Device {0} does not support the '{1}' test ".format(
     836                        self.name, test_type),
     837                    None
     838                )
     839        except KeyError:
     840            return 2, "Unknown test type '{0}' requested.".format(test_type), None
     841
     842        raw, rc = self.smartctl.test_start(
     843            interface, test_type, self.dev_reference)
     844        _success = False
     845        _running = False
     846        for line in raw:
     847            if 'has begun' in line:
     848                _success = True
     849                self._test_running = True
     850            if 'aborting current test' in line:
     851                _running = True
     852                try:
     853                    self._test_progress = 100 - \
     854                        int(line.split('(')[-1].split('%')[0])
     855                except ValueError:
     856                    pass
     857
     858            if _success and 'complete after' in line:
     859                self._test_ECD = line[25:].rstrip()
     860                if ETA_type == 'seconds':
     861                    self._test_ECD = mktime(
     862                        strptime(self._test_ECD, '%a %b %d %H:%M:%S %Y')) - time()
     863                self._test_progress = 0
     864        if _success:
     865            return 0, 'Self-test started successfully', self._test_ECD
     866        else:
     867            if _running:
     868                return 1, 'Self-test already in progress. Please wait.', self._test_ECD
     869            else:
     870                return 3, 'Unspecified Error. Self-test not started.', None
     871
     872    def run_selftest_and_wait(self, test_type, output=None, polling=5, progress_handler=None):
     873        """
     874        This is essentially a wrapper around run_selftest() such that we
     875        call self.run_selftest() and wait on the running selftest till
     876        it finished before returning.
     877        The above holds true for all pySMART supported tests with the
     878        exception of the 'offline' test (ATA only) as it immediately
     879        returns, since the entire test only affects the smart error log
     880        (if any errors found) and updates the SMART attributes. Other
     881        than that it is not visibile anywhere else, so we start it and
     882        simply return.
     883        # Args:
     884        * **test_type (str):** The type of test to run. Accepts the following
     885        (not case sensitive):
     886            * **short** - Brief electo-mechanical functionality check.
     887            Generally takes 2 minutes or less.
     888            * **long** - Thorough electro-mechanical functionality check,
     889            including complete recording media scan. Generally takes several
     890            hours.
     891            * **conveyance** - Brief test used to identify damage incurred in
     892            shipping. Generally takes 5 minutes or less. **This test is not
     893            supported by SAS or SCSI devices.**
     894            * **offline** - Runs SMART Immediate Offline Test. The effects of
     895            this test are visible only in that it updates the SMART Attribute
     896            values, and if errors are found they will appear in the SMART error
     897            log, visible with the '-l error' option to smartctl. **This test is
     898            not supported by SAS or SCSI devices in pySMART use cli smartctl for
     899            running 'offline' selftest (runs in foreground) on scsi devices.**
     900        * **output (str, optional):** If set to 'str', the string
     901            representation of the most recent test result will be returned,
     902            instead of a `Test_Entry` object.
     903        * **polling (int, default=5):** The time duration to sleep for between
     904            checking for test_results and progress.
     905        * **progress_handler (function, optional):** This if provided is called
     906            with self._test_progress as the supplied argument everytime a poll to
     907            check the status of the selftest is done.
     908        # Returns:
     909        * **(int):** Return status code.  One of the following:
     910            * 0 - Self-test executed and finished successfully
     911            * 1 - Previous self-test running. Must wait for it to finish.
     912            * 2 - Unknown or illegal test type requested.
     913            * 3 - The Self-test was Aborted by host
     914            * 4 - Unspecified smartctl error. Self-test not initiated.
     915        * **(`Test_Entry` or str):** Most recent `Test_Entry` object (or
     916        optionally it's string representation) if new data exists.  Status
     917        message string on failure.
     918        """
     919        test_initiation_result = self.run_selftest(test_type)
     920        if test_initiation_result[0] != 0:
     921            return test_initiation_result[:2]
     922        if test_type == 'offline':
     923            self._test_running = False
     924        # if not then the test initiated correctly and we can start the polling.
     925        # For now default 'polling' value is 5 seconds if not specified by the user
     926
     927        # Do an initial check, for good measure.
     928        # In the probably impossible case that self._test_running is instantly False...
     929        selftest_results = self.get_selftest_result(output=output)
     930        while self._test_running:
     931            if selftest_results[0] != 1:
     932                # the selftest is run and finished lets return with the results
     933                break
     934            # Otherwise see if we are provided with the progress_handler to update progress
     935            if progress_handler is not None:
     936                progress_handler(
     937                    selftest_results[2] if selftest_results[2] is not None else 50)
     938            # Now sleep 'polling' seconds before checking the progress again
     939            sleep(polling)
     940
     941            # Check after the sleep to ensure we return the right result, and not an old one.
     942            selftest_results = self.get_selftest_result(output=output)
     943
     944        # Now if (selftes_results[0] == 2) i.e No new selftest (because the same
     945        # selftest was run twice within the last hour) but we know for a fact that
     946        # we just ran a new selftest then just return the latest entry in self.tests
     947        if selftest_results[0] == 2:
     948            selftest_return_value = 0 if 'Aborted' not in self.tests[0].status else 3
     949            return selftest_return_value, str(self.tests[0]) if output == 'str' else self.tests[0]
     950        return selftest_results[:2]
     951
     952    def update(self):
     953        """
     954        Queries for device information using smartctl and updates all
     955        class members, including the SMART attribute table and self-test log.
     956        Can be called at any time to refresh the `pySMART.device.Device`
     957        object's data content.
     958        """
     959        # set temperature back to None so that if update() is called more than once
     960        # any logic that relies on self.temperature to be None to rescan it works.it
     961        self._temperature = None
     962        # same for temperatures
     963        self.temperatures = {}
     964        if self.abridged:
     965            interface = None
     966            raw = self.smartctl.info(self.dev_reference)
     967
     968        else:
     969            interface = smartctl_type(self._interface)
     970            raw = self.smartctl.all(
     971                self.dev_reference, interface)
     972
     973        parse_self_tests = False
     974        parse_running_test = False
     975        parse_ascq = False
     976        polling_minute_type = None
     977        message = ''
     978        self.tests = []
     979        self._test_running = False
     980        self._test_progress = None
     981        # Lets skip the first couple of non-useful lines
     982        _stdout = raw[4:]
     983
     984        #######################################
     985        #           Encoding fixing           #
     986        #######################################
     987        # In some scenarios, smartctl returns some lines with a different/strange encoding
     988        # This is a workaround to fix that
     989        for i, line in enumerate(_stdout):
     990            # character ' ' (U+202F) should be removed
     991            _stdout[i] = line.replace('\u202f', '')
     992
     993        #######################################
     994        #   Dedicated interface attributes    #
     995        #######################################
     996        if AtaAttributes.has_compatible_data(iter(_stdout)):
     997            self.if_attributes = AtaAttributes(iter(_stdout))
     998
     999        elif self.dev_interface == 'nvme':
    1000            self.if_attributes = NvmeAttributes(iter(_stdout))
    1001
    1002            # Get Tests
    1003            for test in self.if_attributes.tests:
    1004                self.tests.append(TestEntry('nvme', test.num, test.description, test.status, test.powerOnHours,
    1005                                  test.failingLBA, nsid=test.nsid, segment=test.seg, sct=test.sct, code=test.code, remain=100-test.progress))
    1006
    1007            # Set running test
    1008            if any(test.status == 'Running' for test in self.if_attributes.tests):
    1009                self._test_running = True
    1010                self._test_progress = self.if_attributes.tests[0].progress
    1011            else:
    1012                self._test_running = False
    1013                self._test_progress = None
    1014
    1015        else:
    1016            self.if_attributes = None
    1017
    1018        #######################################
    1019        #    Global / generic  attributes     #
    1020        #######################################
    1021        stdout_iter = iter(_stdout)
    1022        for line in stdout_iter:
    1023            if line.strip() == '':  # Blank line stops sub-captures
    1024                if parse_self_tests is True:
    1025                    parse_self_tests = False
    1026                if parse_ascq:
    1027                    parse_ascq = False
    1028                    self.messages.append(message)
    1029            if parse_ascq:
    1030                message += ' ' + line.lstrip().rstrip()
    1031            if parse_self_tests:
    1032                # Detect Test Format
    1033
    1034                ## SCSI/SAS FORMAT ##
    1035                # Example smartctl output
    1036                # SMART Self-test log
    1037                # Num  Test              Status                 segment  LifeTime  LBA_first_err [SK ASC ASQ]
    1038                #      Description                              number   (hours)
    1039                # # 1  Background short  Completed                   -   33124                 - [-   -    -]
    1040                format_scsi = re.compile(
    1041                    r'^[#\s]*(\d+)\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s+\[([^\s]+)\s+([^\s]+)\s+([^\s]+)\]$').match(line)
    1042
    1043                ## ATA FORMAT ##
    1044                # Example smartctl output:
    1045                # SMART Self-test log structure revision number 1
    1046                # Num  Test_Description    Status                  Remaining  LifeTime(hours)  LBA_of_first_error
    1047                # # 1  Extended offline    Completed without error       00%     46660         -
    1048                format_ata = re.compile(
    1049                    r'^[#\s]*(\d+)\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{1,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])$').match(line)
    1050
    1051                if format_scsi is not None:
    1052                    format = 'scsi'
    1053                    parsed = format_scsi.groups()
    1054                    num = int(parsed[0])
    1055                    test_type = parsed[1]
    1056                    status = parsed[2]
    1057                    segment = parsed[3]
    1058                    hours = parsed[4]
    1059                    lba = parsed[5]
    1060                    sense = parsed[6]
    1061                    asc = parsed[7]
    1062                    ascq = parsed[8]
    1063                    self.tests.append(TestEntry(
    1064                        format,
    1065                        num,
    1066                        test_type,
    1067                        status,
    1068                        hours,
    1069                        lba,
    1070                        segment=segment,
    1071                        sense=sense,
    1072                        asc=asc,
    1073                        ascq=ascq
    1074                    ))
    1075                elif format_ata is not None:
    1076                    ## ATA FORMAT ##
    1077                    format = 'ata'
    1078                    parsed = format_ata.groups()
    1079                    num = parsed[0]
    1080                    test_type = parsed[1]
    1081                    status = parsed[2]
    1082                    remain = parsed[3]
    1083                    hours = parsed[4]
    1084                    lba = parsed[5]
    1085
    1086                    try:
    1087                        num = int(num)
    1088                    except:
    1089                        num = None
    1090
    1091                    self.tests.append(
    1092                        TestEntry(format, num, test_type, status,
    1093                                  hours, lba, remain=remain)
    1094                    )
    1095                else:
    1096                    pass
    1097
    1098            # Basic device information parsing
    1099            if any_in(line, 'Device Model', 'Product', 'Model Number'):
    1100                self.model = line.split(':')[1].lstrip().rstrip()
    1101                self._guess_smart_type(line.lower())
    1102                continue
    1103
    1104            if 'Model Family' in line:
    1105                self.family = line.split(':')[1].strip()
    1106                self._guess_smart_type(line.lower())
    1107                continue
    1108
    1109            if 'LU WWN' in line:
    1110                self._guess_smart_type(line.lower())
    1111                continue
    1112
    1113            if any_in(line, 'Serial Number', 'Serial number'):
    1114                try:
    1115                    self.serial = line.split(':')[1].split()[0].rstrip()
    1116                except IndexError:
    1117                    # Serial reported empty
    1118                    self.serial = ""
    1119                continue
    1120
    1121            vendor = re.compile(r'^Vendor:\s+(\w+)').match(line)
    1122            if vendor is not None:
    1123                self._vendor = vendor.groups()[0]
    1124
    1125            if any_in(line, 'Firmware Version', 'Revision'):
    1126                self.firmware = line.split(':')[1].strip()
    1127
    1128            if any_in(line, 'User Capacity', 'Total NVM Capacity', 'Namespace 1 Size/Capacity'):
    1129                # TODO: support for multiple NVMe namespaces
    1130                m = re.match(
    1131                    r'.*:\s+([\d,. \u2019\u00a0]+)\s\D*\[?([^\]]+)?\]?', line.strip())
    1132
    1133                if m is not None:
    1134                    tmp = m.groups()
    1135                    if tmp[0] == ' ':
    1136                        # This capacity is set to 0, skip it
    1137                        continue
    1138
    1139                    self._capacity = int(
    1140                        tmp[0].strip().replace(',', '').replace('.', '').replace(' ', '').replace('\u2019', '').replace('\u00a0', ''))
    1141
    1142                    if len(tmp) == 2 and tmp[1] is not None:
    1143                        self._capacity_human = tmp[1].strip().replace(',', '.')
    1144
    1145            if 'SMART support' in line:
    1146                # self.smart_capable = 'Available' in line
    1147                # self.smart_enabled = 'Enabled' in line
    1148                # Since this line repeats twice the above method is flawed
    1149                # Lets try the following instead, it is a bit redundant but
    1150                # more robust.
    1151                if any_in(line, 'Unavailable', 'device lacks SMART capability'):
    1152                    self.smart_capable = False
    1153                    self.smart_enabled = False
    1154                elif 'Enabled' in line:
    1155                    self.smart_enabled = True
    1156                elif 'Disabled' in line:
    1157                    self.smart_enabled = False
    1158                elif any_in(line, 'Available', 'device has SMART capability'):
    1159                    self.smart_capable = True
    1160                continue
    1161
    1162            if 'does not support SMART' in line:
    1163                self.smart_capable = False
    1164                self.smart_enabled = False
    1165                continue
    1166
    1167            if 'Rotation Rate' in line:
    1168                if 'Solid State Device' in line:
    1169                    self.is_ssd = True
    1170                elif 'rpm' in line:
    1171                    self.is_ssd = False
    1172                    try:
    1173                        self.rotation_rate = int(
    1174                            line.split(':')[1].lstrip().rstrip()[:-4])
    1175                    except ValueError:
    1176                        # Cannot parse the RPM? Assigning None instead
    1177                        self.rotation_rate = None
    1178                continue
    1179
    1180            if 'SMART overall-health self-assessment' in line:  # ATA devices
    1181                if line.split(':')[1].strip() == 'PASSED':
    1182                    self.assessment = 'PASS'
    1183                else:
    1184                    self.assessment = 'FAIL'
    1185                continue
    1186
    1187            if 'SMART Health Status' in line:  # SCSI devices
    1188                if line.split(':')[1].strip() == 'OK':
    1189                    self.assessment = 'PASS'
    1190                else:
    1191                    self.assessment = 'FAIL'
    1192                    parse_ascq = True  # Set flag to capture status message
    1193                    message = line.split(':')[1].lstrip().rstrip()
    1194                continue
    1195
    1196            # Parse SMART test capabilities (ATA only)
    1197            # Note: SCSI does not list this but and allows for only 'offline', 'short' and 'long'
    1198            if 'SMART execute Offline immediate' in line:
    1199                self.test_capabilities['offline'] = 'No' not in line
    1200                continue
    1201
    1202            if 'Conveyance Self-test supported' in line:
    1203                self.test_capabilities['conveyance'] = 'No' not in line
    1204                continue
    1205
    1206            if 'Selective Self-test supported' in line:
    1207                self.test_capabilities['selective'] = 'No' not in line
    1208                continue
    1209
    1210            if 'Self-test supported' in line:
    1211                self.test_capabilities['short'] = 'No' not in line
    1212                self.test_capabilities['long'] = 'No' not in line
    1213                continue
    1214
    1215            # Parse SMART test capabilities (NVMe only)
    1216            if 'Optional Admin Commands' in line:
    1217                if 'Self_Test' in line:
    1218                    self.test_capabilities['short'] = True
    1219                    self.test_capabilities['long'] = True
    1220
    1221            if 'Short self-test routine' in line:
    1222                polling_minute_type = 'short'
    1223                continue
    1224            if 'Extended self-test routine' in line:
    1225                polling_minute_type = 'long'
    1226                continue
    1227            if 'Conveyance self-test routine' in line:
    1228                polling_minute_type = 'conveyance'
    1229                continue
    1230            if 'recommended polling time:' in line:
    1231                self.test_polling_time[polling_minute_type] = float(
    1232                    re.sub("[^0-9]", "", line)
    1233                )
    1234                continue
    1235
    1236            # For some reason smartctl does not show a currently running test
    1237            # for 'ATA' in the Test log so I just have to catch it this way i guess!
    1238            # For 'scsi' I still do it since it is the only place I get % remaining in scsi
    1239            if 'Self-test execution status' in line:
    1240                if 'progress' in line:
    1241                    self._test_running = True
    1242                    # for ATA the "%" remaining is on the next line
    1243                    # thus set the parse_running_test flag and move on
    1244                    parse_running_test = True
    1245                elif '%' in line:
    1246                    # for scsi the progress is on the same line
    1247                    # so we can just parse it and move on
    1248                    self._test_running = True
    1249                    try:
    1250                        self._test_progress = 100 - \
    1251                            int(line.split('%')[0][-3:].strip())
    1252                    except ValueError:
    1253                        pass
    1254                continue
    1255            if parse_running_test is True:
    1256                try:
    1257                    self._test_progress = 100 - \
    1258                        int(line.split('%')[0][-3:].strip())
    1259                except ValueError:
    1260                    pass
    1261                parse_running_test = False
    1262
    1263            if "Self-test log" in line:
    1264                parse_self_tests = True  # Set flag to capture test entries
    1265                continue
    1266
    1267            #######################################
    1268            #              SCSI only              #
    1269            #######################################
    1270            #
    1271            # Everything from here on is parsing SCSI information that takes
    1272            # the place of similar ATA SMART information
    1273            if 'used endurance' in line:
    1274                pct = int(line.split(':')[1].strip()[:-1])
    1275                self.diagnostics.Life_Left = 100 - pct
    1276                continue
    1277
    1278            if 'Specified cycle count' in line:
    1279                self.diagnostics.Start_Stop_Spec = int(
    1280                    line.split(':')[1].strip())
    1281                continue
    1282
    1283            if 'Accumulated start-stop cycles' in line:
    1284                self.diagnostics.Start_Stop_Cycles = int(
    1285                    line.split(':')[1].strip())
    1286                if self.diagnostics.Start_Stop_Spec and self.diagnostics.Start_Stop_Spec != 0:
    1287                    self.diagnostics.Start_Stop_Pct_Left = int(round(
    1288                        100 - (self.diagnostics.Start_Stop_Cycles /
    1289                               self.diagnostics.Start_Stop_Spec), 0))
    1290                continue
    1291
    1292            if 'Specified load-unload count' in line:
    1293                self.diagnostics.Load_Cycle_Spec = int(
    1294                    line.split(':')[1].strip())
    1295                continue
    1296
    1297            if 'Accumulated load-unload cycles' in line:
    1298                self.diagnostics.Load_Cycle_Count = int(
    1299                    line.split(':')[1].strip())
    1300                if self.diagnostics.Load_Cycle_Spec and self.diagnostics.Load_Cycle_Spec != 0:
    1301                    self.diagnostics.Load_Cycle_Pct_Left = int(round(
    1302                        100 - (self.diagnostics.Load_Cycle_Count /
    1303                               self.diagnostics.Load_Cycle_Spec), 0))
    1304                continue
    1305
    1306            if 'Elements in grown defect list' in line:
    1307                self.diagnostics.Reallocated_Sector_Ct = int(
    1308                    line.split(':')[1].strip())
    1309                continue
    1310
    1311            if 'read:' in line:
    1312                line_ = ' '.join(line.split()).split(' ')
    1313                if line_[1] == '0' and line_[2] == '0' and line_[3] == '0' and line_[4] == '0':
    1314                    self.diagnostics.Corrected_Reads = 0
    1315                elif line_[4] == '0':
    1316                    self.diagnostics.Corrected_Reads = int(
    1317                        line_[1]) + int(line_[2]) + int(line_[3])
    1318                else:
    1319                    self.diagnostics.Corrected_Reads = int(line_[4])
    1320                self.diagnostics._Reads_GB = float(line_[6].replace(',', '.'))
    1321                self.diagnostics._Uncorrected_Reads = int(line_[7])
    1322                continue
    1323
    1324            if 'write:' in line:
    1325                line_ = ' '.join(line.split()).split(' ')
    1326                if (line_[1] == '0' and line_[2] == '0' and
    1327                        line_[3] == '0' and line_[4] == '0'):
    1328                    self.diagnostics.Corrected_Writes = 0
    1329                elif line_[4] == '0':
    1330                    self.diagnostics.Corrected_Writes = int(
    1331                        line_[1]) + int(line_[2]) + int(line_[3])
    1332                else:
    1333                    self.diagnostics.Corrected_Writes = int(line_[4])
    1334                self.diagnostics._Writes_GB = float(line_[6].replace(',', '.'))
    1335                self.diagnostics._Uncorrected_Writes = int(line_[7])
    1336                continue
    1337
    1338            if 'verify:' in line:
    1339                line_ = ' '.join(line.split()).split(' ')
    1340                if (line_[1] == '0' and line_[2] == '0' and
    1341                        line_[3] == '0' and line_[4] == '0'):
    1342                    self.diagnostics.Corrected_Verifies = 0
    1343                elif line_[4] == '0':
    1344                    self.diagnostics.Corrected_Verifies = int(
    1345                        line_[1]) + int(line_[2]) + int(line_[3])
    1346                else:
    1347                    self.diagnostics.Corrected_Verifies = int(line_[4])
    1348                self.diagnostics._Verifies_GB = float(
    1349                    line_[6].replace(',', '.'))
    1350                self.diagnostics._Uncorrected_Verifies = int(line_[7])
    1351                continue
    1352
    1353            if 'non-medium error count' in line:
    1354                self.diagnostics.Non_Medium_Errors = int(
    1355                    line.split(':')[1].strip())
    1356                continue
    1357
    1358            if 'Accumulated power on time' in line:
    1359                self.diagnostics.Power_On_Hours = int(
    1360                    line.split(':')[1].split(' ')[1])
    1361                continue
    1362
    1363            if 'Current Drive Temperature' in line or ('Temperature:' in
    1364                                                       line and interface == 'nvme'):
    1365                try:
    1366                    self._temperature = int(
    1367                        line.split(':')[-1].strip().split()[0])
    1368
    1369                    if 'fahrenheit' in line.lower():
    1370                        self._temperature = int(
    1371                            (self.temperature - 32) * 5 / 9)
    1372
    1373                except ValueError:
    1374                    pass
    1375
    1376                continue
    1377
    1378            if 'Temperature Sensor ' in line:
    1379                try:
    1380                    match = re.search(
    1381                        r'Temperature\sSensor\s([0-9]+):\s+(-?[0-9]+)', line)
    1382                    if match:
    1383                        (tempsensor_number_s, tempsensor_value_s) = match.group(1, 2)
    1384                        tempsensor_number = int(tempsensor_number_s)
    1385                        tempsensor_value = int(tempsensor_value_s)
    1386
    1387                        if 'fahrenheit' in line.lower():
    1388                            tempsensor_value = int(
    1389                                (tempsensor_value - 32) * 5 / 9)
    1390
    1391                        self.temperatures[tempsensor_number] = tempsensor_value
    1392                        if self.temperature is None or tempsensor_number == 0:
    1393                            self._temperature = tempsensor_value
    1394                except ValueError:
    1395                    pass
    1396
    1397                continue
    1398
    1399            #######################################
    1400            #            Common values            #
    1401            #######################################
    1402
    1403            # Sector sizes
    1404            if 'Sector Sizes' in line:  # ATA
    1405                m = re.match(
    1406                    r'.* (\d+) bytes logical,\s*(\d+) bytes physical', line)
    1407                if m:
    1408                    self.logical_sector_size = int(m.group(1))
    1409                    self.physical_sector_size = int(m.group(2))
    1410                    # set diagnostics block size to physical sector size
    1411                    self.diagnostics._block_size = self.physical_sector_size
    1412                continue
    1413            if 'Logical block size:' in line:  # SCSI 1/2
    1414                self.logical_sector_size = int(
    1415                    line.split(':')[1].strip().split(' ')[0])
    1416                # set diagnostics block size to logical sector size
    1417                self.diagnostics._block_size = self.logical_sector_size
    1418                continue
    1419            if 'Physical block size:' in line:  # SCSI 2/2
    1420                self.physical_sector_size = int(
    1421                    line.split(':')[1].strip().split(' ')[0])
    1422                continue
    1423            if 'Namespace 1 Formatted LBA Size' in line:  # NVMe
    1424                # Note: we will assume that there is only one namespace
    1425                self.logical_sector_size = int(
    1426                    line.split(':')[1].strip().split(' ')[0])
    1427                continue
    1428
    1429        if not self.abridged:
    1430            if not interface == 'scsi':
    1431                # Parse the SMART table for below-threshold attributes and create
    1432                # corresponding warnings for non-SCSI disks
    1433                self._make_smart_warnings()
    1434            else:
    1435                # If not obtained Power_On_Hours above, make a direct attempt to extract power on
    1436                # hours from the background scan results log.
    1437                if self.smart_enabled and self.diagnostics.Power_On_Hours is None:
    1438                    raw, returncode = self.smartctl.generic_call(
    1439                        [
    1440                            '-d',
    1441                            'scsi',
    1442                            '-l',
    1443                            'background',
    1444                            self.dev_reference
    1445                        ])
    1446
    1447                    for line in raw:
    1448                        if 'power on time' in line:
    1449                            self.diagnostics.Power_On_Hours = int(
    1450                                line.split(':')[1].split(' ')[1])
    1451
    1452        # Now that we have finished the update routine, if we did not find a runnning selftest
    1453        # nuke the self._test_ECD and self._test_progress
    1454        if self._test_running is False:
    1455            self._test_ECD = None
    1456            self._test_progress = None

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).

Device( name: str, interface: Optional[str] = None, abridged: bool = False, smart_options: Union[str, List[str], NoneType] = None, smartctl: pySMART.smartctl.Smartctl = <pySMART.smartctl.Smartctl object>) View Source

     88    def __init__(self, name: str, interface: Optional[str] = None, abridged: bool = False, smart_options: Union[str, List[str], None] = None, smartctl: Smartctl = SMARTCTL):
     89        """Instantiates and initializes the `pySMART.device.Device`."""
     90        if not (
     91                interface is None or
     92                smartctl_isvalid_type(interface.lower())
     93        ):
     94            raise ValueError(
     95                'Unknown interface: {0} specified for {1}'.format(interface, name))
     96        self.abridged = abridged or interface == 'UNKNOWN INTERFACE'
     97        if smart_options is not None:
     98            if isinstance(smart_options,  str):
     99                smart_options = smart_options.split(' ')
    100            smartctl.add_options(smart_options)
    101        self.smartctl = smartctl
    102        """
    103        """
    104        self.name: str = name.replace('/dev/', '').replace('nvd', 'nvme')
    105        """
    106        **(str):** Device's hardware ID, without the '/dev/' prefix.
    107        (ie: sda (Linux), pd0 (Windows))
    108        """
    109        self.family: Optional[str] = None
    110        """**(str):** Device's family (if any)."""
    111        self.model: Optional[str] = None
    112        """**(str):** Device's model number (if any)."""
    113        self.serial: Optional[str] = None
    114        """**(str):** Device's serial number (if any)."""
    115        self._vendor: Optional[str] = None
    116        """**(str):** Device's vendor (if any)."""
    117        self._interface: Optional[str] = None if interface == 'UNKNOWN INTERFACE' else interface
    118        """
    119        **(str):** Device's interface type. Must be one of:
    120            * **ATA** - Advanced Technology Attachment
    121            * **SATA** - Serial ATA
    122            * **SCSI** - Small Computer Systems Interface
    123            * **SAS** - Serial Attached SCSI
    124            * **SAT** - SCSI-to-ATA Translation (SATA device plugged into a
    125            SAS port)
    126            * **CSMI** - Common Storage Management Interface (Intel ICH /
    127            Matrix RAID)
    128        Generally this should not be specified to allow auto-detection to
    129        occur. Otherwise, this value overrides the auto-detected type and could
    130        produce unexpected or no data.
    131        """
    132        self._capacity: Optional[int] = None
    133        """**(str):** Device's user capacity as reported directly by smartctl (RAW)."""
    134        self._capacity_human: Optional[str] = None
    135        """**(str):** Device's user capacity (human readable) as reported directly by smartctl (RAW)."""
    136        self.firmware: Optional[str] = None
    137        """**(str):** Device's firmware version."""
    138        self.smart_capable: bool = 'nvme' in self.name
    139        """
    140        **(bool):** True if the device has SMART Support Available.
    141        False otherwise. This is useful for VMs amongst other things.
    142        """
    143        self.smart_enabled: bool = 'nvme' in self.name
    144        """
    145        **(bool):** True if the device supports SMART (or SCSI equivalent) and
    146        has the feature set enabled. False otherwise.
    147        """
    148        self.assessment: Optional[str] = None
    149        """
    150        **(str):** SMART health self-assessment as reported by the device.
    151        """
    152        self.messages: List[str] = []
    153        """
    154        **(list of str):** Contains any SMART warnings or other error messages
    155        reported by the device (ie: ascq codes).
    156        """
    157        self.is_ssd: bool = True if 'nvme' in self.name else False
    158        """
    159        **(bool):** True if this device is a Solid State Drive.
    160        False otherwise.
    161        """
    162        self.rotation_rate: Optional[int] = None
    163        """
    164        **(int):** The Roatation Rate of the Drive if it is not a SSD.
    165        The Metric is RPM.
    166        """
    167        self.test_capabilities = {
    168            'offline': False,  # SMART execute Offline immediate (ATA only)
    169            'short': 'nvme' not in self.name,  # SMART short Self-test
    170            'long': 'nvme' not in self.name,  # SMART long Self-test
    171            'conveyance': False,  # SMART Conveyance Self-Test (ATA only)
    172            'selective': False,  # SMART Selective Self-Test (ATA only)
    173        }
    174        # Note have not included 'offline' test for scsi as it runs in the foregorund
    175        # mode. While this may be beneficial to us in someways it is against the
    176        # general layout and pattern that the other tests issued using pySMART are
    177        # followed hence not doing it currently
    178        """
    179        **(dict): ** This dictionary contains key == 'Test Name' and
    180        value == 'True/False' of self-tests that this device is capable of.
    181        """
    182        # Note: The above are just default values and can/will be changed
    183        # upon update() when the attributes and type of the disk is actually
    184        # determined.
    185        self.test_polling_time = {
    186            'short': 10,
    187            'long': 1000,
    188            'conveyance': 20,
    189        }
    190        """
    191        **(dict): ** This dictionary contains key == 'Test Name' and
    192        value == int of approximate times to run each test type that this
    193        device is capable of.
    194        """
    195        # Note: The above are just default values and can/will be changed
    196        # upon update() when the attributes and type of the disk is actually
    197        # determined.
    198        self.tests: List[TestEntry] = []
    199        """
    200        **(list of `TestEntry`):** Contains the complete SMART self-test log
    201        for this device, as provided by smartctl.
    202        """
    203        self._test_running = False
    204        """
    205        **(bool):** True if a self-test is currently being run.
    206        False otherwise.
    207        """
    208        self._test_ECD = None
    209        """
    210        **(str):** Estimated completion time of the running SMART selftest.
    211        Not provided by SAS/SCSI devices.
    212        """
    213        self._test_progress = None
    214        """
    215        **(int):** Estimate progress percantage of the running SMART selftest.
    216        """
    217        self.diagnostics: Diagnostics = Diagnostics()
    218        """
    219        **Diagnostics** Contains parsed and processed diagnostic information
    220        extracted from the SMART information. Currently only populated for
    221        SAS and SCSI devices, since ATA/SATA SMART attributes are manufacturer
    222        proprietary.
    223        """
    224        self._temperature: Optional[int] = None
    225        """
    226        **(int or None): Since SCSI disks do not report attributes like ATA ones
    227        we need to grep/regex the shit outta the normal "smartctl -a" output.
    228        In case the device have more than one temperature sensor the first value
    229        will be stored here too.
    230        Note: Temperatures are always in Celsius (if possible).
    231        """
    232        self.temperatures: Dict[int, int] = {}
    233        """
    234        **(dict of int): NVMe disks usually report multiple temperatures, which
    235        will be stored here if available. Keys are sensor numbers as reported in
    236        output data.
    237        Note: Temperatures are always in Celsius (if possible).
    238        """
    239        self.logical_sector_size: Optional[int] = None
    240        """
    241        **(int):** The logical sector size of the device (or LBA).
    242        """
    243        self.physical_sector_size: Optional[int] = None
    244        """
    245        **(int):** The physical sector size of the device.
    246        """
    247        self.if_attributes: Union[None, NvmeAttributes, AtaAttributes] = None
    248        """
    249        **(NvmeAttributes):** This object may vary for each device interface attributes.
    250        It will store all data obtained from smartctl
    251        """
    252
    253        if self.name is None:
    254            warnings.warn(
    255                "\nDevice '{0}' does not exist! This object should be destroyed.".format(
    256                    name)
    257            )
    258            return
    259        # If no interface type was provided, scan for the device
    260        # Lets do this only for the non-abridged case
    261        # (we can work with no interface for abridged case)
    262        elif self._interface is None and not self.abridged:
    263            logger.trace(
    264                "Determining interface of disk: {0}".format(self.name))
    265            raw, returncode = self.smartctl.generic_call(
    266                ['-d', 'test', self.dev_reference])
    267
    268            if len(raw) > 0:
    269                # I do not like this parsing logic but it works for now!
    270                # just for reference _stdout.split('\n') gets us
    271                # something like
    272                # [
    273                #     ...copyright string...,
    274                #     '',
    275                #     "/dev/ada2: Device of type 'atacam' [ATA] detected",
    276                #     "/dev/ada2: Device of type 'atacam' [ATA] opened",
    277                #     ''
    278                # ]
    279                # The above example should be enough for anyone to understand the line below
    280                try:
    281                    for line in reversed(raw):
    282                        if "opened" in line:
    283                            self._interface = line.split("'")[1]
    284
    285                            if self._interface == "nvme":  # if nvme set SMART to true
    286                                self.smart_capable = True
    287                                self.smart_enabled = True
    288
    289                            break
    290                except:
    291                    # for whatever reason we could not get the interface type
    292                    # we should mark this as an `abbridged` case and move on
    293                    self._interface = None
    294                    self.abbridged = True
    295                # TODO: Uncomment the classify call if we ever find out that we need it
    296                # Disambiguate the generic interface to a specific type
    297                # self._classify()
    298            else:
    299                warnings.warn(
    300                    "\nDevice '{0}' does not exist! This object should be destroyed.".format(
    301                        name)
    302                )
    303                return
    304        # If a valid device was detected, populate its information
    305        # OR if in unabridged mode, then do it even without interface info
    306        if self._interface is not None or self.abridged:
    307            self.update()

Instantiates and initializes the `Device`.

abridged
smartctl
name: str

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

family: Optional[str]

(str): Device's family (if any).

model: Optional[str]

(str): Device's model number (if any).

serial: Optional[str]

(str): Device's serial number (if any).

firmware: Optional[str]

(str): Device's firmware version.

smart_capable: bool

(bool): True if the device has SMART Support Available. False otherwise. This is useful for VMs amongst other things.

smart_enabled: bool

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

assessment: Optional[str]

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

messages: List[str]

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

is_ssd: bool

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

rotation_rate: Optional[int]

(int): The Roatation Rate of the Drive if it is not a SSD. The Metric is RPM.

test_capabilities

*(dict): * This dictionary contains key == 'Test Name' and value == 'True/False' of self-tests that this device is capable of.

test_polling_time

*(dict): * This dictionary contains key == 'Test Name' and value == int of approximate times to run each test type that this device is capable of.

tests: List[TestEntry]

(list of `TestEntry`): Contains the complete SMART self-test log for this device, as provided by smartctl.

diagnostics: pySMART.diagnostics.Diagnostics

Diagnostics 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.

temperatures: Dict[int, int]

**(dict of int): NVMe disks usually report multiple temperatures, which will be stored here if available. Keys are sensor numbers as reported in output data. Note: Temperatures are always in Celsius (if possible).

logical_sector_size: Optional[int]

(int): The logical sector size of the device (or LBA).

physical_sector_size: Optional[int]

(int): The physical sector size of the device.

if_attributes: Union[NoneType, pySMART.interface.nvme.NvmeAttributes, pySMART.interface.ata.AtaAttributes]

(NvmeAttributes): This object may vary for each device interface attributes. It will store all data obtained from smartctl

attributes: List[Optional[Attribute]] View Source

    309    @property
    310    def attributes(self) -> List[Optional[Attribute]]:
    311        """Returns the SMART attributes of the device.
    312        Note: This is only filled with ATA/SATA attributes. SCSI/SAS/NVMe devices will have empty lists!!
    313        @deprecated: Use `if_attributes` instead.
    314
    315        Returns:
    316            list of `Attribute`: The SMART attributes of the device.
    317        """
    318        if self.if_attributes is None or not isinstance(self.if_attributes, AtaAttributes):
    319            return [None] * 256
    320        else:
    321            return self.if_attributes.legacyAttributes

Returns the SMART attributes of the device. Note: This is only filled with ATA/SATA attributes. SCSI/SAS/NVMe devices will have empty lists!! @deprecated: Use `if_attributes` instead.

Returns: list of `Attribute`: The SMART attributes of the device.

dev_interface: Optional[str] View Source

    323    @property
    324    def dev_interface(self) -> Optional[str]:
    325        """Returns the internal interface type of the device.
    326           It may not be the same as the interface type as used by smartctl.
    327
    328        Returns:
    329            str: The interface type of the device. (example: ata, scsi, nvme)
    330                 None if the interface type could not be determined.
    331        """
    332        # Try to get the fine-tuned interface type
    333        fineType = self._classify()
    334
    335        # If return still contains a megaraid, just asume it's type
    336        if 'megaraid' in fineType:
    337            # If any attributes is not None and has at least non None value, then it is a sat+megaraid device
    338            if isinstance(self.if_attributes, AtaAttributes):
    339                return 'ata'
    340            else:
    341                return 'sas'
    342
    343        return fineType

Returns the internal interface type of the device. It may not be the same as the interface type as used by smartctl.

Returns: str: The interface type of the device. (example: ata, scsi, nvme) None if the interface type could not be determined.

temperature: Optional[int] View Source

    345    @property
    346    def temperature(self) -> Optional[int]:
    347        """Returns the temperature of the device.
    348
    349        Returns:
    350            int: The temperature of the device in Celsius.
    351                 None if the temperature could not be determined.
    352        """
    353        if self.if_attributes is None:
    354            return self._temperature
    355        else:
    356            return self.if_attributes.temperature or self._temperature

Returns the temperature of the device.

Returns: int: The temperature of the device in Celsius. None if the temperature could not be determined.

smartctl_interface: Optional[str] View Source

    358    @property
    359    def smartctl_interface(self) -> Optional[str]:
    360        """Returns the interface type of the device as it is used in smartctl.
    361
    362        Returns:
    363            str: The interface type of the device. (example: ata, scsi, nvme)
    364                 None if the interface type could not be determined.
    365        """
    366        return self._interface

Returns the interface type of the device as it is used in smartctl.

Returns: str: The interface type of the device. (example: ata, scsi, nvme) None if the interface type could not be determined.

interface: Optional[str] View Source

    368    @property
    369    def interface(self) -> Optional[str]:
    370        """Returns the interface type of the device as it is used in smartctl.
    371
    372        Returns:
    373            str: The interface type of the device. (example: ata, scsi, nvme)
    374                 None if the interface type could not be determined.
    375        """
    376        return self.smartctl_interface

Returns the interface type of the device as it is used in smartctl.

Returns: str: The interface type of the device. (example: ata, scsi, nvme) None if the interface type could not be determined.

dev_reference: str View Source

    378    @property
    379    def dev_reference(self) -> str:
    380        """The reference to the device as provided by smartctl.
    381           - On unix-like systems, this is the path to the device. (example /dev/<name>)
    382           - On MacOS, this is the name of the device. (example <name>)
    383           - On Windows, this is the drive letter of the device. (example <drive letter>)
    384
    385        Returns:
    386            str: The reference to the device as provided by smartctl.
    387        """
    388
    389        # detect if we are on MacOS
    390        if 'IOService' in self.name:
    391            return self.name
    392
    393        # otherwise asume we are on unix-like systems
    394        return os.path.join('/dev/', self.name)

The reference to the device as provided by smartctl.

- On unix-like systems, this is the path to the device. (example /dev/)
- On MacOS, this is the name of the device. (example )
- On Windows, this is the drive letter of the device. (example )

Returns: str: The reference to the device as provided by smartctl.

vendor: Optional[str] View Source

    396    @property
    397    def vendor(self) -> Optional[str]:
    398        """Returns the vendor of the device.
    399
    400        Returns:
    401            str: The vendor of the device.
    402        """
    403        if self._vendor:
    404            return self._vendor
    405
    406        # If family is present, try to stract from family. Skip anything but letters.
    407        elif self.family:
    408            filter = re.search(r'^[a-zA-Z]+', self.family.strip())
    409            if filter:
    410                return filter.group(0)
    411
    412        # If model is present, try to stract from model. Skip anything but letters.
    413        elif self.model:
    414            filter = re.search(r'^[a-zA-Z]+', self.model.strip())
    415            if filter:
    416                return filter.group(0)
    417
    418        # If all else fails, return None
    419        return None

Returns the vendor of the device.

Returns: str: The vendor of the device.

capacity: Optional[str] View Source

    421    @property
    422    def capacity(self) -> Optional[str]:
    423        """Returns the capacity in the raw smartctl format.
    424        This may be deprecated in the future and its only retained for compatibility.
    425
    426        Returns:
    427            str: The capacity in the raw smartctl format
    428        """
    429        return self._capacity_human

Returns the capacity in the raw smartctl format. This may be deprecated in the future and its only retained for compatibility.

Returns: str: The capacity in the raw smartctl format

diags: Dict[str, str] View Source

    431    @property
    432    def diags(self) -> Dict[str, str]:
    433        """Gets the old/deprecated version of SCSI/SAS diags atribute.
    434        """
    435        return self.diagnostics.get_classic_format()

Gets the old/deprecated version of SCSI/SAS diags atribute.

size_raw: Optional[str] View Source

    437    @property
    438    def size_raw(self) -> Optional[str]:
    439        """Returns the capacity in the raw smartctl format.
    440
    441        Returns:
    442            str: The capacity in the raw smartctl format
    443        """
    444        return self._capacity_human

Returns the capacity in the raw smartctl format.

Returns: str: The capacity in the raw smartctl format

size: int View Source

    446    @property
    447    def size(self) -> int:
    448        """Returns the capacity in bytes
    449
    450        Returns:
    451            int: The capacity in bytes
    452        """
    453        import humanfriendly
    454
    455        if self._capacity is not None:
    456            return self._capacity
    457        elif self._capacity_human is not None:
    458            return humanfriendly.parse_size(self._capacity_human)
    459        else:
    460            return 0

Returns the capacity in bytes

Returns: int: The capacity in bytes

sector_size: int View Source

    462    @property
    463    def sector_size(self) -> int:
    464        """Returns the sector size of the device.
    465
    466        Returns:
    467            int: The sector size of the device in Bytes. If undefined, we'll assume 512B
    468        """
    469        if self.logical_sector_size is not None:
    470            return self.logical_sector_size
    471        elif self.physical_sector_size is not None:
    472            return self.physical_sector_size
    473        else:
    474            return 512

Returns the sector size of the device.

Returns: int: The sector size of the device in Bytes. If undefined, we'll assume 512B

def smart_toggle(self, action: str) -> Tuple[bool, List[str]]: View Source

    519    def smart_toggle(self, action: str) -> Tuple[bool, List[str]]:
    520        """
    521        A basic function to enable/disable SMART on device.
    522
    523        # Args:
    524        * **action (str):** Can be either 'on'(for enabling) or 'off'(for disabling).
    525
    526        # Returns"
    527        * **(bool):** Return True (if action succeded) else False
    528        * **(List[str]):** None if option succeded else contains the error message.
    529        """
    530        # Lets make the action verb all lower case
    531        if self._interface == 'nvme':
    532            return False, ['NVME devices do not currently support toggling SMART enabled']
    533        action_lower = action.lower()
    534        if action_lower not in ['on', 'off']:
    535            return False, ['Unsupported action {0}'.format(action)]
    536        # Now lets check if the device's smart enabled status is already that of what
    537        # the supplied action is intending it to be. If so then just return successfully
    538        if self.smart_enabled:
    539            if action_lower == 'on':
    540                return True, []
    541        else:
    542            if action_lower == 'off':
    543                return True, []
    544        if self._interface is not None:
    545            raw, returncode = self.smartctl.generic_call(
    546                ['-s', action_lower, '-d', self._interface, self.dev_reference])
    547        else:
    548            raw, returncode = self.smartctl.generic_call(
    549                ['-s', action_lower, self.dev_reference])
    550
    551        if returncode != 0:
    552            return False, raw
    553        # if everything worked out so far lets perform an update() and check the result
    554        self.update()
    555        if action_lower == 'off' and self.smart_enabled:
    556            return False, ['Failed to turn SMART off.']
    557        if action_lower == 'on' and not self.smart_enabled:
    558            return False, ['Failed to turn SMART on.']
    559        return True, []

A basic function to enable/disable SMART on device.

# Args:

- action (str): Can be either 'on'(for enabling) or 'off'(for disabling).

# Returns"

- (bool): Return True (if action succeded) else False
- (List[str]): None if option succeded else contains the error message.

def all_attributes(self, print_fn=<built-in function print>): View Source

    561    def all_attributes(self, print_fn=print):
    562        """
    563        Prints the entire SMART attribute table, in a format similar to
    564        the output of smartctl.
    565        allows usage of custom print function via parameter print_fn by default uses print
    566        """
    567        header_printed = False
    568        for attr in self.attributes:
    569            if attr is not None:
    570                if not header_printed:
    571                    print_fn("{0:>3} {1:24}{2:4}{3:4}{4:4}{5:9}{6:8}{7:12}{8}"
    572                             .format('ID#', 'ATTRIBUTE_NAME', 'CUR', 'WST', 'THR', 'TYPE', 'UPDATED', 'WHEN_FAIL',
    573                                     'RAW'))
    574                    header_printed = True
    575                print_fn(attr)
    576        if not header_printed:
    577            print_fn('This device does not support SMART attributes.')

Prints the entire SMART attribute table, in a format similar to the output of smartctl. allows usage of custom print function via parameter print_fn by default uses print

def all_selftests(self): View Source

    579    def all_selftests(self):
    580        """
    581        Prints the entire SMART self-test log, in a format similar to
    582        the output of smartctl.
    583        """
    584        if self.tests:
    585            all_tests = []
    586            if smartctl_type(self._interface) == 'scsi':
    587                header = "{0:3}{1:17}{2:23}{3:7}{4:14}{5:15}".format(
    588                    'ID',
    589                    'Test Description',
    590                    'Status',
    591                    'Hours',
    592                    '1st_Error@LBA',
    593                    '[SK  ASC  ASCQ]'
    594                )
    595            else:
    596                header = ("{0:3}{1:17}{2:30}{3:5}{4:7}{5:17}".format(
    597                    'ID',
    598                    'Test_Description',
    599                    'Status',
    600                    'Left',
    601                    'Hours',
    602                    '1st_Error@LBA'))
    603            all_tests.append(header)
    604            for test in self.tests:
    605                all_tests.append(str(test))
    606
    607            return all_tests
    608        else:
    609            no_tests = 'No self-tests have been logged for this device.'
    610            return no_tests

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

def get_selftest_result(self, output=None): View Source

    696    def get_selftest_result(self, output=None):
    697        """
    698        Refreshes a device's `pySMART.device.Device.tests` attribute to obtain
    699        the latest test results. If a new test result is obtained, its content
    700        is returned.
    701
    702        # Args:
    703        * **output (str, optional):** If set to 'str', the string
    704        representation of the most recent test result will be returned, instead
    705        of a `Test_Entry` object.
    706
    707        # Returns:
    708        * **(int):** Return status code. One of the following:
    709            * 0 - Success. Object (or optionally, string rep) is attached.
    710            * 1 - Self-test in progress. Must wait for it to finish.
    711            * 2 - No new test results.
    712            * 3 - The Self-test was Aborted by host
    713        * **(`Test_Entry` or str):** Most recent `Test_Entry` object (or
    714        optionally it's string representation) if new data exists.  Status
    715        message string on failure.
    716        * **(int):** Estimate progress percantage of the running SMART selftest, if known.
    717        Otherwise 'None'.
    718        """
    719        # SCSI self-test logs hold 20 entries while ATA logs hold 21
    720        if smartctl_type(self._interface) == 'scsi':
    721            maxlog = 20
    722        else:
    723            maxlog = 21
    724        # If we looked only at the most recent test result we could be fooled
    725        # by two short tests run close together (within the same hour)
    726        # appearing identical. Comparing the length of the log adds some
    727        # confidence until it maxes, as above. Comparing the least-recent test
    728        # result greatly diminishes the chances that two sets of two tests each
    729        # were run within an hour of themselves, but with 16-17 other tests run
    730        # in between them.
    731        if self.tests:
    732            _first_entry = self.tests[0]
    733            _len = len(self.tests)
    734            _last_entry = self.tests[_len - 1]
    735        else:
    736            _len = 0
    737        self.update()
    738        # Since I have changed the update() parsing to DTRT to pickup currently
    739        # running selftests we can now purely rely on that for self._test_running
    740        # Thus check for that variable first and return if it is True with appropos message.
    741        if self._test_running is True:
    742            return 1, 'Self-test in progress. Please wait.', self._test_progress
    743        # Check whether the list got longer (ie: new entry)
    744        # If so return the newest test result
    745        # If not, because it's max size already, check for new entries
    746        if (
    747                (len(self.tests) != _len) or
    748                (
    749                    len == maxlog and
    750                    (
    751                        _first_entry.type != self.tests[0].type or
    752                        _first_entry.hours != self.tests[0].hours or
    753                        _last_entry.type != self.tests[len(self.tests) - 1].type or
    754                        _last_entry.hours != self.tests[len(
    755                            self.tests) - 1].hours
    756                    )
    757                )
    758        ):
    759            return (
    760                0 if 'Aborted' not in self.tests[0].status else 3,
    761                str(self.tests[0]) if output == 'str' else self.tests[0],
    762                None
    763            )
    764        else:
    765            return 2, 'No new self-test results found.', None

Refreshes a device's `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.
  - 3 - The Self-test was Aborted by host
- (`Test_Entry` or str): Most recent `Test_Entry` object (or optionally it's string representation) if new data exists. Status message string on failure.
- (int): Estimate progress percantage of the running SMART selftest, if known. Otherwise 'None'.

def abort_selftest(self): View Source

    767    def abort_selftest(self):
    768        """
    769        Aborts non-captive SMART Self Tests.   Note that this command
    770        will  abort the Offline Immediate Test routine only if your disk
    771        has the "Abort Offline collection upon new command"  capability.
    772
    773        # Args: Nothing (just aborts directly)
    774
    775        # Returns:
    776        * **(int):** The returncode of calling `smartctl -X device_path`
    777        """
    778        return self.smartctl.test_stop(smartctl_type(self._interface), self.dev_reference)

Aborts non-captive SMART Self Tests. Note that this command will abort the Offline Immediate Test routine only if your disk has the "Abort Offline collection upon new command" capability.

# Args: Nothing (just aborts directly)

# Returns:

- (int): The returncode of calling `smartctl -X device_path`

def run_selftest(self, test_type, ETA_type='date'): View Source

    780    def run_selftest(self, test_type, ETA_type='date'):
    781        """
    782        Instructs a device to begin a SMART self-test. All tests are run in
    783        'offline' / 'background' mode, allowing normal use of the device while
    784        it is being tested.
    785
    786        # Args:
    787        * **test_type (str):** The type of test to run. Accepts the following
    788        (not case sensitive):
    789            * **short** - Brief electo-mechanical functionality check.
    790            Generally takes 2 minutes or less.
    791            * **long** - Thorough electro-mechanical functionality check,
    792            including complete recording media scan. Generally takes several
    793            hours.
    794            * **conveyance** - Brief test used to identify damage incurred in
    795            shipping. Generally takes 5 minutes or less. **This test is not
    796            supported by SAS or SCSI devices.**
    797            * **offline** - Runs SMART Immediate Offline Test. The effects of
    798            this test are visible only in that it updates the SMART Attribute
    799            values, and if errors are found they will appear in the SMART error
    800            log, visible with the '-l error' option to smartctl. **This test is
    801            not supported by SAS or SCSI devices in pySMART use cli smartctl for
    802            running 'offline' selftest (runs in foreground) on scsi devices.**
    803            * **ETA_type** - Format to return the estimated completion time/date
    804            in. Default is 'date'. One could otherwise specidy 'seconds'.
    805            Again only for ATA devices.
    806
    807        # Returns:
    808        * **(int):** Return status code.  One of the following:
    809            * 0 - Self-test initiated successfully
    810            * 1 - Previous self-test running. Must wait for it to finish.
    811            * 2 - Unknown or unsupported (by the device) test type requested.
    812            * 3 - Unspecified smartctl error. Self-test not initiated.
    813        * **(str):** Return status message.
    814        * **(str)/(float):** Estimated self-test completion time if a test is started.
    815        The optional argument of 'ETA_type' (see above) controls the return type.
    816        if 'ETA_type' == 'date' then a date string is returned else seconds(float)
    817        is returned.
    818        Note: The self-test completion time can only be obtained for ata devices.
    819        Otherwise 'None'.
    820        """
    821        # Lets call get_selftest_result() here since it does an update() and
    822        # checks for an existing selftest is running or not, this way the user
    823        # can issue a test from the cli and this can still pick that up
    824        # Also note that we do not need to obtain the results from this as the
    825        # data is already stored in the Device class object's variables
    826        self.get_selftest_result()
    827        if self._test_running:
    828            return 1, 'Self-test in progress. Please wait.', self._test_ECD
    829        test_type = test_type.lower()
    830        interface = smartctl_type(self._interface)
    831        try:
    832            if not self.test_capabilities[test_type]:
    833                return (
    834                    2,
    835                    "Device {0} does not support the '{1}' test ".format(
    836                        self.name, test_type),
    837                    None
    838                )
    839        except KeyError:
    840            return 2, "Unknown test type '{0}' requested.".format(test_type), None
    841
    842        raw, rc = self.smartctl.test_start(
    843            interface, test_type, self.dev_reference)
    844        _success = False
    845        _running = False
    846        for line in raw:
    847            if 'has begun' in line:
    848                _success = True
    849                self._test_running = True
    850            if 'aborting current test' in line:
    851                _running = True
    852                try:
    853                    self._test_progress = 100 - \
    854                        int(line.split('(')[-1].split('%')[0])
    855                except ValueError:
    856                    pass
    857
    858            if _success and 'complete after' in line:
    859                self._test_ECD = line[25:].rstrip()
    860                if ETA_type == 'seconds':
    861                    self._test_ECD = mktime(
    862                        strptime(self._test_ECD, '%a %b %d %H:%M:%S %Y')) - time()
    863                self._test_progress = 0
    864        if _success:
    865            return 0, 'Self-test started successfully', self._test_ECD
    866        else:
    867            if _running:
    868                return 1, 'Self-test already in progress. Please wait.', self._test_ECD
    869            else:
    870                return 3, 'Unspecified Error. Self-test not started.', None

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.
  - offline - Runs SMART Immediate Offline Test. The effects of this test are visible only in that it updates the SMART Attribute values, and if errors are found they will appear in the SMART error log, visible with the '-l error' option to smartctl. This test is not supported by SAS or SCSI devices in pySMART use cli smartctl for running 'offline' selftest (runs in foreground) on scsi devices.
  - ETA_type - Format to return the estimated completion time/date in. Default is 'date'. One could otherwise specidy 'seconds'. Again only for ATA 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 unsupported (by the device) test type requested.
  - 3 - Unspecified smartctl error. Self-test not initiated.
- (str): Return status message.
- (str)/(float): Estimated self-test completion time if a test is started. The optional argument of 'ETA_type' (see above) controls the return type. if 'ETA_type' == 'date' then a date string is returned else seconds(float) is returned. Note: The self-test completion time can only be obtained for ata devices. Otherwise 'None'.

def run_selftest_and_wait(self, test_type, output=None, polling=5, progress_handler=None): View Source

    872    def run_selftest_and_wait(self, test_type, output=None, polling=5, progress_handler=None):
    873        """
    874        This is essentially a wrapper around run_selftest() such that we
    875        call self.run_selftest() and wait on the running selftest till
    876        it finished before returning.
    877        The above holds true for all pySMART supported tests with the
    878        exception of the 'offline' test (ATA only) as it immediately
    879        returns, since the entire test only affects the smart error log
    880        (if any errors found) and updates the SMART attributes. Other
    881        than that it is not visibile anywhere else, so we start it and
    882        simply return.
    883        # Args:
    884        * **test_type (str):** The type of test to run. Accepts the following
    885        (not case sensitive):
    886            * **short** - Brief electo-mechanical functionality check.
    887            Generally takes 2 minutes or less.
    888            * **long** - Thorough electro-mechanical functionality check,
    889            including complete recording media scan. Generally takes several
    890            hours.
    891            * **conveyance** - Brief test used to identify damage incurred in
    892            shipping. Generally takes 5 minutes or less. **This test is not
    893            supported by SAS or SCSI devices.**
    894            * **offline** - Runs SMART Immediate Offline Test. The effects of
    895            this test are visible only in that it updates the SMART Attribute
    896            values, and if errors are found they will appear in the SMART error
    897            log, visible with the '-l error' option to smartctl. **This test is
    898            not supported by SAS or SCSI devices in pySMART use cli smartctl for
    899            running 'offline' selftest (runs in foreground) on scsi devices.**
    900        * **output (str, optional):** If set to 'str', the string
    901            representation of the most recent test result will be returned,
    902            instead of a `Test_Entry` object.
    903        * **polling (int, default=5):** The time duration to sleep for between
    904            checking for test_results and progress.
    905        * **progress_handler (function, optional):** This if provided is called
    906            with self._test_progress as the supplied argument everytime a poll to
    907            check the status of the selftest is done.
    908        # Returns:
    909        * **(int):** Return status code.  One of the following:
    910            * 0 - Self-test executed and finished successfully
    911            * 1 - Previous self-test running. Must wait for it to finish.
    912            * 2 - Unknown or illegal test type requested.
    913            * 3 - The Self-test was Aborted by host
    914            * 4 - Unspecified smartctl error. Self-test not initiated.
    915        * **(`Test_Entry` or str):** Most recent `Test_Entry` object (or
    916        optionally it's string representation) if new data exists.  Status
    917        message string on failure.
    918        """
    919        test_initiation_result = self.run_selftest(test_type)
    920        if test_initiation_result[0] != 0:
    921            return test_initiation_result[:2]
    922        if test_type == 'offline':
    923            self._test_running = False
    924        # if not then the test initiated correctly and we can start the polling.
    925        # For now default 'polling' value is 5 seconds if not specified by the user
    926
    927        # Do an initial check, for good measure.
    928        # In the probably impossible case that self._test_running is instantly False...
    929        selftest_results = self.get_selftest_result(output=output)
    930        while self._test_running:
    931            if selftest_results[0] != 1:
    932                # the selftest is run and finished lets return with the results
    933                break
    934            # Otherwise see if we are provided with the progress_handler to update progress
    935            if progress_handler is not None:
    936                progress_handler(
    937                    selftest_results[2] if selftest_results[2] is not None else 50)
    938            # Now sleep 'polling' seconds before checking the progress again
    939            sleep(polling)
    940
    941            # Check after the sleep to ensure we return the right result, and not an old one.
    942            selftest_results = self.get_selftest_result(output=output)
    943
    944        # Now if (selftes_results[0] == 2) i.e No new selftest (because the same
    945        # selftest was run twice within the last hour) but we know for a fact that
    946        # we just ran a new selftest then just return the latest entry in self.tests
    947        if selftest_results[0] == 2:
    948            selftest_return_value = 0 if 'Aborted' not in self.tests[0].status else 3
    949            return selftest_return_value, str(self.tests[0]) if output == 'str' else self.tests[0]
    950        return selftest_results[:2]

This is essentially a wrapper around run_selftest() such that we call self.run_selftest() and wait on the running selftest till it finished before returning. The above holds true for all pySMART supported tests with the exception of the 'offline' test (ATA only) as it immediately returns, since the entire test only affects the smart error log (if any errors found) and updates the SMART attributes. Other than that it is not visibile anywhere else, so we start it and simply return.

# 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.
  - offline - Runs SMART Immediate Offline Test. The effects of this test are visible only in that it updates the SMART Attribute values, and if errors are found they will appear in the SMART error log, visible with the '-l error' option to smartctl. This test is not supported by SAS or SCSI devices in pySMART use cli smartctl for running 'offline' selftest (runs in foreground) on scsi devices.
- 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.
- polling (int, default=5): The time duration to sleep for between checking for test_results and progress.

- progress_handler (function, optional): This if provided is called with self._test_progress as the supplied argument everytime a poll to check the status of the selftest is done.

  # Returns:

- (int): Return status code. One of the following:

  - 0 - Self-test executed and finished successfully
  - 1 - Previous self-test running. Must wait for it to finish.
  - 2 - Unknown or illegal test type requested.
  - 3 - The Self-test was Aborted by host
  - 4 - Unspecified smartctl error. Self-test not initiated.
- (`Test_Entry` or str): Most recent `Test_Entry` object (or optionally it's string representation) if new data exists. Status message string on failure.

def update(self): View Source

     952    def update(self):
     953        """
     954        Queries for device information using smartctl and updates all
     955        class members, including the SMART attribute table and self-test log.
     956        Can be called at any time to refresh the `pySMART.device.Device`
     957        object's data content.
     958        """
     959        # set temperature back to None so that if update() is called more than once
     960        # any logic that relies on self.temperature to be None to rescan it works.it
     961        self._temperature = None
     962        # same for temperatures
     963        self.temperatures = {}
     964        if self.abridged:
     965            interface = None
     966            raw = self.smartctl.info(self.dev_reference)
     967
     968        else:
     969            interface = smartctl_type(self._interface)
     970            raw = self.smartctl.all(
     971                self.dev_reference, interface)
     972
     973        parse_self_tests = False
     974        parse_running_test = False
     975        parse_ascq = False
     976        polling_minute_type = None
     977        message = ''
     978        self.tests = []
     979        self._test_running = False
     980        self._test_progress = None
     981        # Lets skip the first couple of non-useful lines
     982        _stdout = raw[4:]
     983
     984        #######################################
     985        #           Encoding fixing           #
     986        #######################################
     987        # In some scenarios, smartctl returns some lines with a different/strange encoding
     988        # This is a workaround to fix that
     989        for i, line in enumerate(_stdout):
     990            # character ' ' (U+202F) should be removed
     991            _stdout[i] = line.replace('\u202f', '')
     992
     993        #######################################
     994        #   Dedicated interface attributes    #
     995        #######################################
     996        if AtaAttributes.has_compatible_data(iter(_stdout)):
     997            self.if_attributes = AtaAttributes(iter(_stdout))
     998
     999        elif self.dev_interface == 'nvme':
    1000            self.if_attributes = NvmeAttributes(iter(_stdout))
    1001
    1002            # Get Tests
    1003            for test in self.if_attributes.tests:
    1004                self.tests.append(TestEntry('nvme', test.num, test.description, test.status, test.powerOnHours,
    1005                                  test.failingLBA, nsid=test.nsid, segment=test.seg, sct=test.sct, code=test.code, remain=100-test.progress))
    1006
    1007            # Set running test
    1008            if any(test.status == 'Running' for test in self.if_attributes.tests):
    1009                self._test_running = True
    1010                self._test_progress = self.if_attributes.tests[0].progress
    1011            else:
    1012                self._test_running = False
    1013                self._test_progress = None
    1014
    1015        else:
    1016            self.if_attributes = None
    1017
    1018        #######################################
    1019        #    Global / generic  attributes     #
    1020        #######################################
    1021        stdout_iter = iter(_stdout)
    1022        for line in stdout_iter:
    1023            if line.strip() == '':  # Blank line stops sub-captures
    1024                if parse_self_tests is True:
    1025                    parse_self_tests = False
    1026                if parse_ascq:
    1027                    parse_ascq = False
    1028                    self.messages.append(message)
    1029            if parse_ascq:
    1030                message += ' ' + line.lstrip().rstrip()
    1031            if parse_self_tests:
    1032                # Detect Test Format
    1033
    1034                ## SCSI/SAS FORMAT ##
    1035                # Example smartctl output
    1036                # SMART Self-test log
    1037                # Num  Test              Status                 segment  LifeTime  LBA_first_err [SK ASC ASQ]
    1038                #      Description                              number   (hours)
    1039                # # 1  Background short  Completed                   -   33124                 - [-   -    -]
    1040                format_scsi = re.compile(
    1041                    r'^[#\s]*(\d+)\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s+\[([^\s]+)\s+([^\s]+)\s+([^\s]+)\]$').match(line)
    1042
    1043                ## ATA FORMAT ##
    1044                # Example smartctl output:
    1045                # SMART Self-test log structure revision number 1
    1046                # Num  Test_Description    Status                  Remaining  LifeTime(hours)  LBA_of_first_error
    1047                # # 1  Extended offline    Completed without error       00%     46660         -
    1048                format_ata = re.compile(
    1049                    r'^[#\s]*(\d+)\s{2,}(.*[^\s])\s{2,}(.*[^\s])\s{1,}(.*[^\s])\s{2,}(.*[^\s])\s{2,}(.*[^\s])$').match(line)
    1050
    1051                if format_scsi is not None:
    1052                    format = 'scsi'
    1053                    parsed = format_scsi.groups()
    1054                    num = int(parsed[0])
    1055                    test_type = parsed[1]
    1056                    status = parsed[2]
    1057                    segment = parsed[3]
    1058                    hours = parsed[4]
    1059                    lba = parsed[5]
    1060                    sense = parsed[6]
    1061                    asc = parsed[7]
    1062                    ascq = parsed[8]
    1063                    self.tests.append(TestEntry(
    1064                        format,
    1065                        num,
    1066                        test_type,
    1067                        status,
    1068                        hours,
    1069                        lba,
    1070                        segment=segment,
    1071                        sense=sense,
    1072                        asc=asc,
    1073                        ascq=ascq
    1074                    ))
    1075                elif format_ata is not None:
    1076                    ## ATA FORMAT ##
    1077                    format = 'ata'
    1078                    parsed = format_ata.groups()
    1079                    num = parsed[0]
    1080                    test_type = parsed[1]
    1081                    status = parsed[2]
    1082                    remain = parsed[3]
    1083                    hours = parsed[4]
    1084                    lba = parsed[5]
    1085
    1086                    try:
    1087                        num = int(num)
    1088                    except:
    1089                        num = None
    1090
    1091                    self.tests.append(
    1092                        TestEntry(format, num, test_type, status,
    1093                                  hours, lba, remain=remain)
    1094                    )
    1095                else:
    1096                    pass
    1097
    1098            # Basic device information parsing
    1099            if any_in(line, 'Device Model', 'Product', 'Model Number'):
    1100                self.model = line.split(':')[1].lstrip().rstrip()
    1101                self._guess_smart_type(line.lower())
    1102                continue
    1103
    1104            if 'Model Family' in line:
    1105                self.family = line.split(':')[1].strip()
    1106                self._guess_smart_type(line.lower())
    1107                continue
    1108
    1109            if 'LU WWN' in line:
    1110                self._guess_smart_type(line.lower())
    1111                continue
    1112
    1113            if any_in(line, 'Serial Number', 'Serial number'):
    1114                try:
    1115                    self.serial = line.split(':')[1].split()[0].rstrip()
    1116                except IndexError:
    1117                    # Serial reported empty
    1118                    self.serial = ""
    1119                continue
    1120
    1121            vendor = re.compile(r'^Vendor:\s+(\w+)').match(line)
    1122            if vendor is not None:
    1123                self._vendor = vendor.groups()[0]
    1124
    1125            if any_in(line, 'Firmware Version', 'Revision'):
    1126                self.firmware = line.split(':')[1].strip()
    1127
    1128            if any_in(line, 'User Capacity', 'Total NVM Capacity', 'Namespace 1 Size/Capacity'):
    1129                # TODO: support for multiple NVMe namespaces
    1130                m = re.match(
    1131                    r'.*:\s+([\d,. \u2019\u00a0]+)\s\D*\[?([^\]]+)?\]?', line.strip())
    1132
    1133                if m is not None:
    1134                    tmp = m.groups()
    1135                    if tmp[0] == ' ':
    1136                        # This capacity is set to 0, skip it
    1137                        continue
    1138
    1139                    self._capacity = int(
    1140                        tmp[0].strip().replace(',', '').replace('.', '').replace(' ', '').replace('\u2019', '').replace('\u00a0', ''))
    1141
    1142                    if len(tmp) == 2 and tmp[1] is not None:
    1143                        self._capacity_human = tmp[1].strip().replace(',', '.')
    1144
    1145            if 'SMART support' in line:
    1146                # self.smart_capable = 'Available' in line
    1147                # self.smart_enabled = 'Enabled' in line
    1148                # Since this line repeats twice the above method is flawed
    1149                # Lets try the following instead, it is a bit redundant but
    1150                # more robust.
    1151                if any_in(line, 'Unavailable', 'device lacks SMART capability'):
    1152                    self.smart_capable = False
    1153                    self.smart_enabled = False
    1154                elif 'Enabled' in line:
    1155                    self.smart_enabled = True
    1156                elif 'Disabled' in line:
    1157                    self.smart_enabled = False
    1158                elif any_in(line, 'Available', 'device has SMART capability'):
    1159                    self.smart_capable = True
    1160                continue
    1161
    1162            if 'does not support SMART' in line:
    1163                self.smart_capable = False
    1164                self.smart_enabled = False
    1165                continue
    1166
    1167            if 'Rotation Rate' in line:
    1168                if 'Solid State Device' in line:
    1169                    self.is_ssd = True
    1170                elif 'rpm' in line:
    1171                    self.is_ssd = False
    1172                    try:
    1173                        self.rotation_rate = int(
    1174                            line.split(':')[1].lstrip().rstrip()[:-4])
    1175                    except ValueError:
    1176                        # Cannot parse the RPM? Assigning None instead
    1177                        self.rotation_rate = None
    1178                continue
    1179
    1180            if 'SMART overall-health self-assessment' in line:  # ATA devices
    1181                if line.split(':')[1].strip() == 'PASSED':
    1182                    self.assessment = 'PASS'
    1183                else:
    1184                    self.assessment = 'FAIL'
    1185                continue
    1186
    1187            if 'SMART Health Status' in line:  # SCSI devices
    1188                if line.split(':')[1].strip() == 'OK':
    1189                    self.assessment = 'PASS'
    1190                else:
    1191                    self.assessment = 'FAIL'
    1192                    parse_ascq = True  # Set flag to capture status message
    1193                    message = line.split(':')[1].lstrip().rstrip()
    1194                continue
    1195
    1196            # Parse SMART test capabilities (ATA only)
    1197            # Note: SCSI does not list this but and allows for only 'offline', 'short' and 'long'
    1198            if 'SMART execute Offline immediate' in line:
    1199                self.test_capabilities['offline'] = 'No' not in line
    1200                continue
    1201
    1202            if 'Conveyance Self-test supported' in line:
    1203                self.test_capabilities['conveyance'] = 'No' not in line
    1204                continue
    1205
    1206            if 'Selective Self-test supported' in line:
    1207                self.test_capabilities['selective'] = 'No' not in line
    1208                continue
    1209
    1210            if 'Self-test supported' in line:
    1211                self.test_capabilities['short'] = 'No' not in line
    1212                self.test_capabilities['long'] = 'No' not in line
    1213                continue
    1214
    1215            # Parse SMART test capabilities (NVMe only)
    1216            if 'Optional Admin Commands' in line:
    1217                if 'Self_Test' in line:
    1218                    self.test_capabilities['short'] = True
    1219                    self.test_capabilities['long'] = True
    1220
    1221            if 'Short self-test routine' in line:
    1222                polling_minute_type = 'short'
    1223                continue
    1224            if 'Extended self-test routine' in line:
    1225                polling_minute_type = 'long'
    1226                continue
    1227            if 'Conveyance self-test routine' in line:
    1228                polling_minute_type = 'conveyance'
    1229                continue
    1230            if 'recommended polling time:' in line:
    1231                self.test_polling_time[polling_minute_type] = float(
    1232                    re.sub("[^0-9]", "", line)
    1233                )
    1234                continue
    1235
    1236            # For some reason smartctl does not show a currently running test
    1237            # for 'ATA' in the Test log so I just have to catch it this way i guess!
    1238            # For 'scsi' I still do it since it is the only place I get % remaining in scsi
    1239            if 'Self-test execution status' in line:
    1240                if 'progress' in line:
    1241                    self._test_running = True
    1242                    # for ATA the "%" remaining is on the next line
    1243                    # thus set the parse_running_test flag and move on
    1244                    parse_running_test = True
    1245                elif '%' in line:
    1246                    # for scsi the progress is on the same line
    1247                    # so we can just parse it and move on
    1248                    self._test_running = True
    1249                    try:
    1250                        self._test_progress = 100 - \
    1251                            int(line.split('%')[0][-3:].strip())
    1252                    except ValueError:
    1253                        pass
    1254                continue
    1255            if parse_running_test is True:
    1256                try:
    1257                    self._test_progress = 100 - \
    1258                        int(line.split('%')[0][-3:].strip())
    1259                except ValueError:
    1260                    pass
    1261                parse_running_test = False
    1262
    1263            if "Self-test log" in line:
    1264                parse_self_tests = True  # Set flag to capture test entries
    1265                continue
    1266
    1267            #######################################
    1268            #              SCSI only              #
    1269            #######################################
    1270            #
    1271            # Everything from here on is parsing SCSI information that takes
    1272            # the place of similar ATA SMART information
    1273            if 'used endurance' in line:
    1274                pct = int(line.split(':')[1].strip()[:-1])
    1275                self.diagnostics.Life_Left = 100 - pct
    1276                continue
    1277
    1278            if 'Specified cycle count' in line:
    1279                self.diagnostics.Start_Stop_Spec = int(
    1280                    line.split(':')[1].strip())
    1281                continue
    1282
    1283            if 'Accumulated start-stop cycles' in line:
    1284                self.diagnostics.Start_Stop_Cycles = int(
    1285                    line.split(':')[1].strip())
    1286                if self.diagnostics.Start_Stop_Spec and self.diagnostics.Start_Stop_Spec != 0:
    1287                    self.diagnostics.Start_Stop_Pct_Left = int(round(
    1288                        100 - (self.diagnostics.Start_Stop_Cycles /
    1289                               self.diagnostics.Start_Stop_Spec), 0))
    1290                continue
    1291
    1292            if 'Specified load-unload count' in line:
    1293                self.diagnostics.Load_Cycle_Spec = int(
    1294                    line.split(':')[1].strip())
    1295                continue
    1296
    1297            if 'Accumulated load-unload cycles' in line:
    1298                self.diagnostics.Load_Cycle_Count = int(
    1299                    line.split(':')[1].strip())
    1300                if self.diagnostics.Load_Cycle_Spec and self.diagnostics.Load_Cycle_Spec != 0:
    1301                    self.diagnostics.Load_Cycle_Pct_Left = int(round(
    1302                        100 - (self.diagnostics.Load_Cycle_Count /
    1303                               self.diagnostics.Load_Cycle_Spec), 0))
    1304                continue
    1305
    1306            if 'Elements in grown defect list' in line:
    1307                self.diagnostics.Reallocated_Sector_Ct = int(
    1308                    line.split(':')[1].strip())
    1309                continue
    1310
    1311            if 'read:' in line:
    1312                line_ = ' '.join(line.split()).split(' ')
    1313                if line_[1] == '0' and line_[2] == '0' and line_[3] == '0' and line_[4] == '0':
    1314                    self.diagnostics.Corrected_Reads = 0
    1315                elif line_[4] == '0':
    1316                    self.diagnostics.Corrected_Reads = int(
    1317                        line_[1]) + int(line_[2]) + int(line_[3])
    1318                else:
    1319                    self.diagnostics.Corrected_Reads = int(line_[4])
    1320                self.diagnostics._Reads_GB = float(line_[6].replace(',', '.'))
    1321                self.diagnostics._Uncorrected_Reads = int(line_[7])
    1322                continue
    1323
    1324            if 'write:' in line:
    1325                line_ = ' '.join(line.split()).split(' ')
    1326                if (line_[1] == '0' and line_[2] == '0' and
    1327                        line_[3] == '0' and line_[4] == '0'):
    1328                    self.diagnostics.Corrected_Writes = 0
    1329                elif line_[4] == '0':
    1330                    self.diagnostics.Corrected_Writes = int(
    1331                        line_[1]) + int(line_[2]) + int(line_[3])
    1332                else:
    1333                    self.diagnostics.Corrected_Writes = int(line_[4])
    1334                self.diagnostics._Writes_GB = float(line_[6].replace(',', '.'))
    1335                self.diagnostics._Uncorrected_Writes = int(line_[7])
    1336                continue
    1337
    1338            if 'verify:' in line:
    1339                line_ = ' '.join(line.split()).split(' ')
    1340                if (line_[1] == '0' and line_[2] == '0' and
    1341                        line_[3] == '0' and line_[4] == '0'):
    1342                    self.diagnostics.Corrected_Verifies = 0
    1343                elif line_[4] == '0':
    1344                    self.diagnostics.Corrected_Verifies = int(
    1345                        line_[1]) + int(line_[2]) + int(line_[3])
    1346                else:
    1347                    self.diagnostics.Corrected_Verifies = int(line_[4])
    1348                self.diagnostics._Verifies_GB = float(
    1349                    line_[6].replace(',', '.'))
    1350                self.diagnostics._Uncorrected_Verifies = int(line_[7])
    1351                continue
    1352
    1353            if 'non-medium error count' in line:
    1354                self.diagnostics.Non_Medium_Errors = int(
    1355                    line.split(':')[1].strip())
    1356                continue
    1357
    1358            if 'Accumulated power on time' in line:
    1359                self.diagnostics.Power_On_Hours = int(
    1360                    line.split(':')[1].split(' ')[1])
    1361                continue
    1362
    1363            if 'Current Drive Temperature' in line or ('Temperature:' in
    1364                                                       line and interface == 'nvme'):
    1365                try:
    1366                    self._temperature = int(
    1367                        line.split(':')[-1].strip().split()[0])
    1368
    1369                    if 'fahrenheit' in line.lower():
    1370                        self._temperature = int(
    1371                            (self.temperature - 32) * 5 / 9)
    1372
    1373                except ValueError:
    1374                    pass
    1375
    1376                continue
    1377
    1378            if 'Temperature Sensor ' in line:
    1379                try:
    1380                    match = re.search(
    1381                        r'Temperature\sSensor\s([0-9]+):\s+(-?[0-9]+)', line)
    1382                    if match:
    1383                        (tempsensor_number_s, tempsensor_value_s) = match.group(1, 2)
    1384                        tempsensor_number = int(tempsensor_number_s)
    1385                        tempsensor_value = int(tempsensor_value_s)
    1386
    1387                        if 'fahrenheit' in line.lower():
    1388                            tempsensor_value = int(
    1389                                (tempsensor_value - 32) * 5 / 9)
    1390
    1391                        self.temperatures[tempsensor_number] = tempsensor_value
    1392                        if self.temperature is None or tempsensor_number == 0:
    1393                            self._temperature = tempsensor_value
    1394                except ValueError:
    1395                    pass
    1396
    1397                continue
    1398
    1399            #######################################
    1400            #            Common values            #
    1401            #######################################
    1402
    1403            # Sector sizes
    1404            if 'Sector Sizes' in line:  # ATA
    1405                m = re.match(
    1406                    r'.* (\d+) bytes logical,\s*(\d+) bytes physical', line)
    1407                if m:
    1408                    self.logical_sector_size = int(m.group(1))
    1409                    self.physical_sector_size = int(m.group(2))
    1410                    # set diagnostics block size to physical sector size
    1411                    self.diagnostics._block_size = self.physical_sector_size
    1412                continue
    1413            if 'Logical block size:' in line:  # SCSI 1/2
    1414                self.logical_sector_size = int(
    1415                    line.split(':')[1].strip().split(' ')[0])
    1416                # set diagnostics block size to logical sector size
    1417                self.diagnostics._block_size = self.logical_sector_size
    1418                continue
    1419            if 'Physical block size:' in line:  # SCSI 2/2
    1420                self.physical_sector_size = int(
    1421                    line.split(':')[1].strip().split(' ')[0])
    1422                continue
    1423            if 'Namespace 1 Formatted LBA Size' in line:  # NVMe
    1424                # Note: we will assume that there is only one namespace
    1425                self.logical_sector_size = int(
    1426                    line.split(':')[1].strip().split(' ')[0])
    1427                continue
    1428
    1429        if not self.abridged:
    1430            if not interface == 'scsi':
    1431                # Parse the SMART table for below-threshold attributes and create
    1432                # corresponding warnings for non-SCSI disks
    1433                self._make_smart_warnings()
    1434            else:
    1435                # If not obtained Power_On_Hours above, make a direct attempt to extract power on
    1436                # hours from the background scan results log.
    1437                if self.smart_enabled and self.diagnostics.Power_On_Hours is None:
    1438                    raw, returncode = self.smartctl.generic_call(
    1439                        [
    1440                            '-d',
    1441                            'scsi',
    1442                            '-l',
    1443                            'background',
    1444                            self.dev_reference
    1445                        ])
    1446
    1447                    for line in raw:
    1448                        if 'power on time' in line:
    1449                            self.diagnostics.Power_On_Hours = int(
    1450                                line.split(':')[1].split(' ')[1])
    1451
    1452        # Now that we have finished the update routine, if we did not find a runnning selftest
    1453        # nuke the self._test_ECD and self._test_progress
    1454        if self._test_running is False:
    1455            self._test_ECD = None
    1456            self._test_progress = None

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 smart_health_assement( disk_name: str, interface: Optional[str] = None, smartctl: pySMART.smartctl.Smartctl = <pySMART.smartctl.Smartctl object>) -> Optional[str]: View Source

    49def smart_health_assement(disk_name: str, interface: Optional[str] = None, smartctl: Smartctl = SMARTCTL) -> Optional[str]:
    50    """
    51    This function gets the SMART Health Status of the disk (IF the disk
    52    is SMART capable and smart is enabled on it else returns None).
    53    This function is to be used only in abridged mode and not otherwise,
    54    since in non-abridged mode update gets this information anyways.
    55
    56    Args:
    57        disk_name (str): name of the disk
    58        interface (str, optional): interface type of the disk (e.g. 'sata', 'scsi', 'nvme',... Defaults to None.)
    59
    60    Returns:
    61        str: SMART Health Status of the disk. Returns None if the disk is not SMART capable or smart is not enabled on it.
    62             Possible values are 'PASS', 'FAIL' or None.
    63    """
    64    assessment = None
    65    raw = smartctl.health(os.path.join(
    66        '/dev/', disk_name.replace('nvd', 'nvme')), interface)
    67    line = raw[4]  # We only need this line
    68    if 'SMART overall-health self-assessment' in line:  # ATA devices
    69        if line.split(':')[1].strip() == 'PASSED':
    70            assessment = 'PASS'
    71        else:
    72            assessment = 'FAIL'
    73    if 'SMART Health Status' in line:  # SCSI devices
    74        if line.split(':')[1].strip() == 'OK':
    75            assessment = 'PASS'
    76        else:
    77            assessment = 'FAIL'
    78    return assessment

This function gets the SMART Health Status of the disk (IF the disk is SMART capable and smart is enabled on it else returns None). This function is to be used only in abridged mode and not otherwise, since in non-abridged mode update gets this information anyways.

Args: disk_name (str): name of the disk interface (str, optional): interface type of the disk (e.g. 'sata', 'scsi', 'nvme',... Defaults to None.)

Returns: str: SMART Health Status of the disk. Returns None if the disk is not SMART capable or smart is not enabled on it. Possible values are 'PASS', 'FAIL' or None.