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