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