Browse Source

Added smart attribute testing code and more tests

master
Johann Schmitz 5 years ago
parent
commit
66d1bb79fc
  1. 102
      src/smartcheck/check.py
  2. 8
      src/smartcheck/disks.json
  3. 6
      src/smartcheck/main.py
  4. 1
      tests/__main__.py
  5. 50
      tests/check.py
  6. 25
      tests/parsing.py
  7. 106
      tests/samples/WDC-WD2000FYYZ-01UL1B1.txt
  8. 8
      tests/samples/disks-min-max.json
  9. 8
      tests/samples/disks-thresholds.json
  10. 25
      tests/samples/no-data-section.txt

102
src/smartcheck/check.py

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import json
import re
@ -24,9 +25,11 @@ TEST_RESULT_RE = re.compile(r"#\s*(\d+)\s+(.*?)\s{2,}(.*?)\s{2,}\s+([\d%]+)\s+(\
class SMARTCheck(object):
def __init__(self, file_or_stream):
def __init__(self, file_or_stream, db_path=None):
self.raw = file_or_stream.read()
self.parsed_sections = None
self.db_path = db_path
self._database = None
@property
def information(self):
@ -46,6 +49,22 @@ class SMARTCheck(object):
self.parsed_sections = self.parse()
return self.parsed_sections
@property
def database(self):
if self._database is None:
if self.db_path:
with open(self.db_path) as f:
self._database = json.load(f)
else:
self._database = []
return self._database
def get_attributes_from_database(self, device_model):
for dev in self.database:
if re.match(dev['model'], device_model, re.IGNORECASE):
return dev['attributes']
return None
def parse(self):
return {
'information': self.parse_information_section(self.raw),
@ -54,14 +73,15 @@ class SMARTCheck(object):
}
def parse_information_section(self, s):
if INFORMATION_SECTION_START not in s:
return {}
start = s.index(INFORMATION_SECTION_START)
end = s.index(DATA_SECTION_START)
if end < 0:
if DATA_SECTION_START not in s:
end = len(s)
if start < 0:
return {}
else:
end = s.index(DATA_SECTION_START)
information_text = s[start:end]
@ -73,17 +93,11 @@ class SMARTCheck(object):
return d
def parse_data_section(self, s):
start = s.index(DATA_SECTION_START)
end = len(s) #s.index(DATA_SECTION_START)
if end < 0:
end = len(s)
if start < 0:
if DATA_SECTION_START not in s:
return {}
data_text = s[start:end]
#print(data_text)
start = s.index(DATA_SECTION_START)
data_text = s[start:]
d = {}
for k, regex in DATA_RE:
@ -96,14 +110,14 @@ class SMARTCheck(object):
return d
def parse_tests_section(self, s):
start = s.index(TESTS_SECTION_START)
end = s.index('\n\n', start+1)
if end < 0:
end = len(s)
if TESTS_SECTION_START not in s:
return {
'test_results': []
}
if start < 0:
return {}
start = s.index(TESTS_SECTION_START)
end = re.search(r'(\r\n\r\n|\n\n|\r\r)', s[start+1:], re.MULTILINE)
end = start + end.end(0) if end else len(s)
tests_text = s[start:end]
@ -111,4 +125,48 @@ class SMARTCheck(object):
'test_results': TEST_RESULT_RE.findall(tests_text)
}
def check(self):
return len(self.check_attributes()) == 0 and self.check_tests()
def check_tests(self):
ok_test_results = [
'Completed without error',
'Interrupted (host reset)' # reboot during self test
]
return not any([x[2] not in ok_test_results for x in self.self_tests['test_results']])
def check_attributes(self):
device_model = self.information.get('device_model', '')
db_attrs = self.get_attributes_from_database(device_model)
threshold_from = re.compile('^(\d+):$')
threshold_to = re.compile('^:(\d+)$')
if not db_attrs:
return {}
failed_attributes = {}
for attrid, name, flag, value, worst, tresh, type, updated, when_failed, raw_value in self.smart_data['attributes']:
if attrid in db_attrs:
min_check = None
max_check = None
value_field, min_value, max_value = tuple(db_attrs[attrid])
if threshold_from.match(str(min_value)):
min_check = lambda i: i >= int(threshold_from.match(str(min_value)).group(1))
else:
min_check = lambda i: i >= int(min_value)
if threshold_to.match(str(max_value)):
max_check = lambda i: i <= int(threshold_to.match(str(max_value)).group(1))
else:
max_check = lambda i: i <= int(max_value)
check_value = int(value if value_field == "VALUE" else raw_value)
if not (min_check(check_value) and max_check(check_value)):
failed_attributes[(attrid, name)] = (value_field, check_value)
return failed_attributes

8
src/smartcheck/disks.json

@ -0,0 +1,8 @@
[
{
"model": "^WDC WD[234]000FYYZ-01UL1B[012]$",
"attributes": {
"10": ["RAW_VALUE", 0, 10]
}
}
]

6
src/smartcheck/main.py

@ -12,9 +12,9 @@ if __name__ == "__main__":
args = parser.parse_args()
check = SMARTCheck(open(args.file, 'r'))
check = SMARTCheck(open(args.file, 'r'), args.data_file)
import pprint
#pprint.pprint(check.information)
pprint.pprint(check.smart_data)
#pprint.pprint(check.self_tests)
#pprint.pprint(check.smart_data)
pprint.pprint(check.self_tests)

1
tests/__main__.py

@ -6,6 +6,7 @@ except ImportError:
import unittest
from parsing import *
from check import *
if __name__ == '__main__':
unittest.main()

50
tests/check.py

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
import unittest
import os
from smartcheck.check import SMARTCheck
samples_path = os.path.join(os.path.dirname(__file__), 'samples')
db_path = os.path.join(samples_path, '../../src/smartcheck/disks.json')
class CheckTest(unittest.TestCase):
def test_check_broken1(self):
check = SMARTCheck(open(os.path.join(samples_path, 'seagate-barracuda-broken1.txt')))
self.assertFalse(check.check())
self.assertFalse(check.check_tests())
def test_check_broken2(self):
check = SMARTCheck(open(os.path.join(samples_path, 'seagate-barracuda-broken2.txt')))
self.assertFalse(check.check())
self.assertFalse(check.check_tests())
def test_smart_attributes_not_found(self):
check = SMARTCheck(open(os.path.join(samples_path, 'seagate-barracuda-broken1.txt')), db_path)
self.assertFalse(check.check())
self.assertFalse(check.check_tests())
self.assertDictEqual(check.check_attributes(), {}) # Attributes not found in disks.json
def test_smart_attributes_nothing_wrong(self):
check = SMARTCheck(open(os.path.join(samples_path, 'WDC-WD2000FYYZ-01UL1B1.txt')), db_path)
self.assertTrue(check.check())
self.assertTrue(check.check_tests())
self.assertDictEqual(check.check_attributes(), {})
def test_smart_attributes_min_max(self):
check = SMARTCheck(open(os.path.join(samples_path, 'WDC-WD2000FYYZ-01UL1B1.txt')),
os.path.join(samples_path, 'disks-min-max.json'))
self.assertFalse(check.check())
self.assertTrue(check.check_tests())
self.assertDictEqual(check.check_attributes(), {
('9', 'Power_On_Hours'): ('RAW_VALUE', 15360)
})
def test_smart_attributes_thresholds(self):
check = SMARTCheck(open(os.path.join(samples_path, 'WDC-WD2000FYYZ-01UL1B1.txt')),
os.path.join(samples_path, 'disks-thresholds.json'))
self.assertFalse(check.check())
self.assertTrue(check.check_tests())
self.assertDictEqual(check.check_attributes(), {
('9', 'Power_On_Hours'): ('RAW_VALUE', 15360)
})

25
tests/parsing.py

@ -1,22 +1,23 @@
# -*- coding: utf-8 -*-
from StringIO import StringIO
import os
import unittest
from smartcheck.check import SMARTCheck
samples_path = os.path.join(os.path.dirname(__file__), 'samples')
class InformationBlockParsingTest(unittest.TestCase):
def test_parsing(self):
samples_path = os.path.dirname(__file__)
for filename, expected_data in [
('samples/seagate-barracuda-broken1.txt', {
('seagate-barracuda-broken1.txt', {
'ata_version': 'ATA8-ACS T13/1699-D revision 4',
'device_model': 'ST3000DM001-1CH166',
'model_family': 'Seagate Barracuda 7200.14 (AF)',
'sata_version': 'SATA 3.0, 6.0 Gb/s (current: 6.0 Gb/s)',
'serial': 'Z1F220RJ'
}),
('samples/seagate-barracuda-broken2.txt', {
('seagate-barracuda-broken2.txt', {
'ata_version': 'ATA8-ACS T13/1699-D revision 4',
'device_model': 'ST3000DM001-1CH166',
'model_family': 'Seagate Barracuda 7200.14 (AF)',
@ -28,12 +29,15 @@ class InformationBlockParsingTest(unittest.TestCase):
check = SMARTCheck(open(os.path.join(samples_path, filename)))
self.assertDictEqual(check.information, expected_data)
def test_information_section_missing(self):
check = SMARTCheck(StringIO(""))
self.assertDictEqual(check.information, {})
class SMARTDataParsingTest(unittest.TestCase):
def test_parsing(self):
samples_path = os.path.dirname(__file__)
for filename, overall_health, attributes in [
('samples/seagate-barracuda-broken1.txt', 'PASSED', [
('seagate-barracuda-broken1.txt', 'PASSED', [
('1', 'Raw_Read_Error_Rate', '0x000f', '103', '099', '006', 'Pre-fail', 'Always', '-', '5845168'),
('3', 'Spin_Up_Time', '0x0003', '095', '095', '000', 'Pre-fail', 'Always', '-', '0'),
('4', 'Start_Stop_Count', '0x0032', '100', '100', '020', 'Old_age', 'Always', '-', '7'),
@ -64,13 +68,20 @@ class SMARTDataParsingTest(unittest.TestCase):
self.assertEqual(check.smart_data['overall_health_status'], overall_health)
self.assertEqual(check.smart_data['attributes'], attributes)
def test_data_section_missing(self):
check = SMARTCheck(StringIO(""))
self.assertDictEqual(check.smart_data, {})
def test_data_section_missing2(self):
check = SMARTCheck(open(os.path.join(samples_path, 'no-data-section.txt')))
self.assertDictEqual(check.smart_data, {})
class SelfTestParsingTest(unittest.TestCase):
def test_parsing(self):
samples_path = os.path.dirname(__file__)
for filename, tests in [
('samples/seagate-barracuda-broken1.txt', [
('seagate-barracuda-broken1.txt', [
('1', 'Extended offline', 'Completed: read failure', '80%', '23113', '1737376544'),
('2', 'Extended offline', 'Completed: read failure', '80%', '23000', '1737376544'),
('3', 'Extended offline', 'Interrupted (host reset)', '80%', '22998', '-'),

106
tests/samples/WDC-WD2000FYYZ-01UL1B1.txt

@ -0,0 +1,106 @@
smartctl 6.3 2014-07-26 r3976 [x86_64-linux-3.18.9-hardened-cit-1] (local build)
Copyright (C) 2002-14, Bruce Allen, Christian Franke, www.smartmontools.org
=== START OF INFORMATION SECTION ===
Model Family: Western Digital RE4 (SATA 6Gb/s)
Device Model: WDC WD2000FYYZ-01UL1B1
Serial Number: WD-WCC1P1106144
LU WWN Device Id: 5 0014ee 2098d479c
Firmware Version: 01.01K02
User Capacity: 2,000,398,934,016 bytes [2.00 TB]
Sector Size: 512 bytes logical/physical
Rotation Rate: 7200 rpm
Device is: In smartctl database [for details use: -P show]
ATA Version is: ATA8-ACS (minor revision not indicated)
SATA Version is: SATA 3.0, 6.0 Gb/s (current: 3.0 Gb/s)
Local Time is: Wed Oct 21 13:17:14 2015 CEST
SMART support is: Available - device has SMART capability.
SMART support is: Enabled
=== START OF READ SMART DATA SECTION ===
SMART overall-health self-assessment test result: PASSED
General SMART Values:
Offline data collection status: (0x84) Offline data collection activity
was suspended by an interrupting command from host.
Auto Offline Data Collection: Enabled.
Self-test execution status: ( 0) The previous self-test routine completed
without error or no self-test has ever
been run.
Total time to complete Offline
data collection: (25500) seconds.
Offline data collection
capabilities: (0x7b) SMART execute Offline immediate.
Auto Offline data collection on/off support.
Suspend Offline collection upon new
command.
Offline surface scan supported.
Self-test supported.
Conveyance Self-test supported.
Selective Self-test supported.
SMART capabilities: (0x0003) Saves SMART data before entering
power-saving mode.
Supports SMART auto save timer.
Error logging capability: (0x01) Error logging supported.
General Purpose Logging supported.
Short self-test routine
recommended polling time: ( 2) minutes.
Extended self-test routine
recommended polling time: ( 278) minutes.
Conveyance self-test routine
recommended polling time: ( 5) minutes.
SCT capabilities: (0x70bd) SCT Status supported.
SCT Error Recovery Control supported.
SCT Feature Control supported.
SCT Data Table supported.
SMART Attributes Data Structure revision number: 16
Vendor Specific SMART Attributes with Thresholds:
ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
1 Raw_Read_Error_Rate 0x002f 200 200 051 Pre-fail Always - 0
3 Spin_Up_Time 0x0027 100 253 021 Pre-fail Always - 0
4 Start_Stop_Count 0x0032 100 100 000 Old_age Always - 2
5 Reallocated_Sector_Ct 0x0033 200 200 140 Pre-fail Always - 0
7 Seek_Error_Rate 0x002e 200 200 000 Old_age Always - 0
9 Power_On_Hours 0x0032 079 079 000 Old_age Always - 15360
10 Spin_Retry_Count 0x0032 100 253 000 Old_age Always - 0
11 Calibration_Retry_Count 0x0032 100 253 000 Old_age Always - 0
12 Power_Cycle_Count 0x0032 100 100 000 Old_age Always - 2
183 Runtime_Bad_Block 0x0032 100 100 000 Old_age Always - 0
192 Power-Off_Retract_Count 0x0032 200 200 000 Old_age Always - 0
193 Load_Cycle_Count 0x0032 200 200 000 Old_age Always - 3
194 Temperature_Celsius 0x0022 107 095 000 Old_age Always - 43
196 Reallocated_Event_Count 0x0032 200 200 000 Old_age Always - 0
197 Current_Pending_Sector 0x0032 200 200 000 Old_age Always - 0
198 Offline_Uncorrectable 0x0030 200 200 000 Old_age Offline - 0
199 UDMA_CRC_Error_Count 0x0032 200 200 000 Old_age Always - 0
200 Multi_Zone_Error_Rate 0x0008 200 200 000 Old_age Offline - 0
SMART Error Log Version: 1
No Errors Logged
SMART Self-test log structure revision number 1
Num Test_Description Status Remaining LifeTime(hours) LBA_of_first_error
# 1 Extended offline Completed without error 00% 15268 -
# 2 Extended offline Completed without error 00% 15222 -
# 3 Extended offline Completed without error 00% 3268 -
# 4 Extended offline Completed without error 00% 3251 -
# 5 Extended offline Completed without error 00% 3246 -
# 6 Extended offline Completed without error 00% 3217 -
# 7 Extended offline Completed without error 00% 533 -
# 8 Extended offline Completed without error 00% 221 -
# 9 Extended offline Completed without error 00% 4 -
SMART Selective self-test log data structure revision number 1
SPAN MIN_LBA MAX_LBA CURRENT_TEST_STATUS
1 0 0 Not_testing
2 0 0 Not_testing
3 0 0 Not_testing
4 0 0 Not_testing
5 0 0 Not_testing
Selective self-test flags (0x0):
After scanning selected spans, do NOT read-scan remainder of disk.
If Selective self-test is pending on power-up, resume after 0 minute delay.

8
tests/samples/disks-min-max.json

@ -0,0 +1,8 @@
[
{
"model": "^WDC WD[234]000FYYZ-01UL1B[012]$",
"attributes": {
"9": ["RAW_VALUE", 0, 10]
}
}
]

8
tests/samples/disks-thresholds.json

@ -0,0 +1,8 @@
[
{
"model": "^WDC WD[234]000FYYZ-01UL1B[012]$",
"attributes": {
"9": ["RAW_VALUE", "0:", ":10"]
}
}
]

25
tests/samples/no-data-section.txt

@ -0,0 +1,25 @@
smartctl 6.1 2013-03-16 r3800 [x86_64-linux-4.0.5-gentoo] (local build)
Copyright (C) 2002-13, Bruce Allen, Christian Franke, www.smartmontools.org
=== START OF INFORMATION SECTION ===
Model Family: Seagate Barracuda 7200.14 (AF)
Device Model: ST3000DM001-1CH166
Serial Number: Z1F220RJ
LU WWN Device Id: 5 000c50 04f692485
Firmware Version: CC24
User Capacity: 3,000,592,982,016 bytes [3.00 TB]
Sector Sizes: 512 bytes logical, 4096 bytes physical
Rotation Rate: 7200 rpm
Device is: In smartctl database [for details use: -P show]
ATA Version is: ATA8-ACS T13/1699-D revision 4
SATA Version is: SATA 3.0, 6.0 Gb/s (current: 6.0 Gb/s)
Local Time is: Sat Oct 10 14:03:18 2015 CEST
==> WARNING: A firmware update for this drive may be available,
see the following Seagate web pages:
http://knowledge.seagate.com/articles/en_US/FAQ/207931en
http://knowledge.seagate.com/articles/en_US/FAQ/223651en
SMART support is: Available - device has SMART capability.
SMART support is: Enabled
Loading…
Cancel
Save