Replaced show_attributes() with generate_report()
* Returns list of colored strings * Optionally includes short-test results * Optionally excludes disk info
This commit is contained in:
parent
5b74879805
commit
81f05fa79f
1 changed files with 133 additions and 24 deletions
|
|
@ -112,6 +112,7 @@ class DiskObj():
|
||||||
self.nvme_attributes = {}
|
self.nvme_attributes = {}
|
||||||
self.path = disk_path
|
self.path = disk_path
|
||||||
self.smart_attributes = {}
|
self.smart_attributes = {}
|
||||||
|
self.smart_self_test = {}
|
||||||
self.smartctl = {}
|
self.smartctl = {}
|
||||||
self.tests = OrderedDict()
|
self.tests = OrderedDict()
|
||||||
self.get_details()
|
self.get_details()
|
||||||
|
|
@ -156,7 +157,9 @@ class DiskObj():
|
||||||
print_standard(' (Have you tried swapping the disk cable?)')
|
print_standard(' (Have you tried swapping the disk cable?)')
|
||||||
else:
|
else:
|
||||||
# Override?
|
# Override?
|
||||||
self.show_attributes()
|
for line in self.generate_report():
|
||||||
|
print(line)
|
||||||
|
print_log(strip_colors(line))
|
||||||
print_warning('{} error(s) detected.'.format(attr_type))
|
print_warning('{} error(s) detected.'.format(attr_type))
|
||||||
if override_disabled:
|
if override_disabled:
|
||||||
print_standard('Tests disabled for this device')
|
print_standard('Tests disabled for this device')
|
||||||
|
|
@ -168,6 +171,93 @@ class DiskObj():
|
||||||
self.tests['NVMe / SMART'].update_status('OVERRIDE')
|
self.tests['NVMe / SMART'].update_status('OVERRIDE')
|
||||||
self.tests['NVMe / SMART'].disabled = True
|
self.tests['NVMe / SMART'].disabled = True
|
||||||
|
|
||||||
|
def generate_report(self, brief=False, short_test=False):
|
||||||
|
"""Generate NVMe / SMART report, returns list."""
|
||||||
|
report = []
|
||||||
|
if not brief:
|
||||||
|
report.append('{BLUE}Device: {dev_path}{CLEAR}'.format(
|
||||||
|
dev_path=self.path, **COLORS))
|
||||||
|
report.append(' {size:>6} ({tran}) {model} {serial}'.format(
|
||||||
|
**self.lsblk))
|
||||||
|
|
||||||
|
# Warnings
|
||||||
|
if self.nvme_attributes:
|
||||||
|
attr_type = 'NVMe'
|
||||||
|
report.append(
|
||||||
|
' {YELLOW}NVMe disk support is still experimental{CLEAR}'.format(
|
||||||
|
**COLORS))
|
||||||
|
elif self.smart_attributes:
|
||||||
|
attr_type = 'SMART'
|
||||||
|
else:
|
||||||
|
# No attribute data available, return short report
|
||||||
|
report.append(
|
||||||
|
' {YELLOW}No NVMe or SMART data available{CLEAR}'.format(
|
||||||
|
**COLORS))
|
||||||
|
return report
|
||||||
|
if not self.smartctl.get('smart_status', {}).get('passed', True):
|
||||||
|
report.append(
|
||||||
|
' {RED}SMART overall self-assessment: Failed{CLEAR}'.format(
|
||||||
|
**COLORS))
|
||||||
|
|
||||||
|
# Attributes
|
||||||
|
report.append('{BLUE}{a} Attributes{YELLOW}{u:>23} {t}{CLEAR}'.format(
|
||||||
|
a=attr_type,
|
||||||
|
u='Updated:' if brief else '',
|
||||||
|
t=time.strftime('%Y-%m-%d %H:%M %Z') if brief else '',
|
||||||
|
**COLORS))
|
||||||
|
if self.nvme_attributes:
|
||||||
|
attr_type = 'NVMe'
|
||||||
|
items = self.nvme_attributes.items()
|
||||||
|
elif self.smart_attributes:
|
||||||
|
attr_type = 'SMART'
|
||||||
|
items = self.smart_attributes.items()
|
||||||
|
for k, v in items:
|
||||||
|
if k in ATTRIBUTES[attr_type]:
|
||||||
|
_note = ''
|
||||||
|
_color = COLORS['GREEN']
|
||||||
|
|
||||||
|
# Attribute ID & Name
|
||||||
|
if attr_type == 'NVMe':
|
||||||
|
_line = ' {:38}'.format(k.replace('_', ' ').title())
|
||||||
|
else:
|
||||||
|
_line = ' {i:>3} / {h}: {n:28}'.format(
|
||||||
|
i=k,
|
||||||
|
h=ATTRIBUTES[attr_type][k]['Hex'],
|
||||||
|
n=v['name'][:28])
|
||||||
|
|
||||||
|
# Set color
|
||||||
|
for _t, _c in [['Warning', 'YELLOW'], ['Error', 'RED']]:
|
||||||
|
if _t in ATTRIBUTES[attr_type][k]:
|
||||||
|
if v['raw'] >= ATTRIBUTES[attr_type][k][_t]:
|
||||||
|
_color = COLORS[_c]
|
||||||
|
|
||||||
|
# 199/C7 warning
|
||||||
|
if str(k) == '199':
|
||||||
|
_note = '(bad cable?)'
|
||||||
|
|
||||||
|
# Attribute value
|
||||||
|
_line += '{}{} {}{}'.format(
|
||||||
|
_color,
|
||||||
|
v['raw_str'],
|
||||||
|
_note,
|
||||||
|
COLORS['CLEAR'])
|
||||||
|
|
||||||
|
# Add line to report
|
||||||
|
report.append(_line)
|
||||||
|
|
||||||
|
# SMART short-test
|
||||||
|
if short_test:
|
||||||
|
report.append('{BLUE}SMART Short self-test{CLEAR}'.format(**COLORS))
|
||||||
|
if 'TimedOut' in self.tests['NVMe / SMART'].status:
|
||||||
|
report.append(' {YELLOW}UNKNOWN{CLEAR}: Timed out'.format(**COLORS))
|
||||||
|
else:
|
||||||
|
report.append(' {}'.format(
|
||||||
|
self.smart_self_test['status'].get(
|
||||||
|
'string', 'UNKNOWN').capitalize()))
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return report
|
||||||
|
|
||||||
def get_details(self):
|
def get_details(self):
|
||||||
"""Get data from lsblk."""
|
"""Get data from lsblk."""
|
||||||
cmd = ['lsblk', '--json', '--output-all', '--paths', self.path]
|
cmd = ['lsblk', '--json', '--output-all', '--paths', self.path]
|
||||||
|
|
@ -219,7 +309,7 @@ class DiskObj():
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# Ignoring invalid attribute
|
# Ignoring invalid attribute
|
||||||
continue
|
continue
|
||||||
_name = str(a.get('name', 'UNKNOWN'))
|
_name = str(a.get('name', 'UNKNOWN')).replace('_', ' ').title()
|
||||||
_raw = int(a.get('raw', {}).get('value', -1))
|
_raw = int(a.get('raw', {}).get('value', -1))
|
||||||
_raw_str = a.get('raw', {}).get('string', 'UNKNOWN')
|
_raw_str = a.get('raw', {}).get('string', 'UNKNOWN')
|
||||||
|
|
||||||
|
|
@ -232,6 +322,13 @@ class DiskObj():
|
||||||
self.smart_attributes[_id] = {
|
self.smart_attributes[_id] = {
|
||||||
'name': _name, 'raw': _raw, 'raw_str': _raw_str}
|
'name': _name, 'raw': _raw, 'raw_str': _raw_str}
|
||||||
|
|
||||||
|
# Self-test data
|
||||||
|
for k in ['polling_minutes', 'status']:
|
||||||
|
self.smart_self_test[k] = self.smartctl.get(
|
||||||
|
'ata_smart_data', {}).get(
|
||||||
|
'self_test', {}).get(
|
||||||
|
k, {})
|
||||||
|
|
||||||
def safety_check(self, silent=False):
|
def safety_check(self, silent=False):
|
||||||
"""Run safety checks and disable tests if necessary."""
|
"""Run safety checks and disable tests if necessary."""
|
||||||
if self.nvme_attributes or self.smart_attributes:
|
if self.nvme_attributes or self.smart_attributes:
|
||||||
|
|
@ -251,31 +348,15 @@ class DiskObj():
|
||||||
'Run tests on this device anyway?')
|
'Run tests on this device anyway?')
|
||||||
|
|
||||||
if not self.disk_ok:
|
if not self.disk_ok:
|
||||||
|
if 'NVMe / SMART' in self.tests:
|
||||||
|
# NOTE: This will not overwrite the existing status if set
|
||||||
|
self.tests['NVMe / SMART'].update_status('NS')
|
||||||
|
self.tests['NVMe / SMART'].disabled = True
|
||||||
for t in ['badblocks', 'I/O Benchmark']:
|
for t in ['badblocks', 'I/O Benchmark']:
|
||||||
if t in self.tests:
|
if t in self.tests:
|
||||||
self.tests[t].update_status('Denied')
|
self.tests[t].update_status('Denied')
|
||||||
self.tests[t].disabled = True
|
self.tests[t].disabled = True
|
||||||
|
|
||||||
def show_attributes(self):
|
|
||||||
"""Show NVMe/SMART attributes."""
|
|
||||||
print_info('Device: {}'.format(self.path))
|
|
||||||
print_standard(
|
|
||||||
' {size:>6} ({tran}) {model} {serial}'.format(**self.lsblk))
|
|
||||||
print_info('Attributes')
|
|
||||||
if self.nvme_attributes:
|
|
||||||
for k, v in self.nvme_attributes.items():
|
|
||||||
if k in ATTRIBUTES['NVMe']:
|
|
||||||
print('TODO: {} {}'.format(k, v))
|
|
||||||
elif self.smart_attributes:
|
|
||||||
for k, v in self.smart_attributes.items():
|
|
||||||
# TODO: If k == 199/C7 then append ' (bad cable?)' to line
|
|
||||||
if k in ATTRIBUTES['SMART']:
|
|
||||||
print('TODO: {} {}'.format(k, v))
|
|
||||||
if not self.smartctl.get('smart_status', {}).get('passed', True):
|
|
||||||
print_error('SMART overall self-assessment: Failed')
|
|
||||||
else:
|
|
||||||
print_warning(' No NVMe or SMART data available')
|
|
||||||
|
|
||||||
class State():
|
class State():
|
||||||
"""Object to track device objects and overall state."""
|
"""Object to track device objects and overall state."""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -402,7 +483,7 @@ def build_outer_panes(state):
|
||||||
def build_status_string(label, status, info_label=False):
|
def build_status_string(label, status, info_label=False):
|
||||||
"""Build status string with appropriate colors."""
|
"""Build status string with appropriate colors."""
|
||||||
status_color = COLORS['CLEAR']
|
status_color = COLORS['CLEAR']
|
||||||
if status in ['Denied', 'ERROR', 'NS', 'OVERRIDE']:
|
if status in ['Denied', 'ERROR', 'NS', 'OVERRIDE', 'TimedOut']:
|
||||||
status_color = COLORS['RED']
|
status_color = COLORS['RED']
|
||||||
elif status in ['Aborted', 'N/A', 'Skipped', 'Unknown', 'Working']:
|
elif status in ['Aborted', 'N/A', 'Skipped', 'Unknown', 'Working']:
|
||||||
status_color = COLORS['YELLOW']
|
status_color = COLORS['YELLOW']
|
||||||
|
|
@ -651,6 +732,9 @@ def run_audio_test():
|
||||||
|
|
||||||
def run_badblocks_test(state, test):
|
def run_badblocks_test(state, test):
|
||||||
"""TODO"""
|
"""TODO"""
|
||||||
|
# Bail early
|
||||||
|
if test.disabled:
|
||||||
|
return
|
||||||
tmux_update_pane(
|
tmux_update_pane(
|
||||||
state.panes['Top'], text='{}\n{}'.format(
|
state.panes['Top'], text='{}\n{}'.format(
|
||||||
TOP_PANE_TEXT, 'badblocks'))
|
TOP_PANE_TEXT, 'badblocks'))
|
||||||
|
|
@ -699,6 +783,15 @@ def run_hw_tests(state):
|
||||||
for disk in state.disks:
|
for disk in state.disks:
|
||||||
disk.safety_check(silent=state.quick_mode)
|
disk.safety_check(silent=state.quick_mode)
|
||||||
|
|
||||||
|
# TODO Remove
|
||||||
|
clear_screen()
|
||||||
|
print_info('Running tests:')
|
||||||
|
for k, v in state.tests.items():
|
||||||
|
if v['Enabled']:
|
||||||
|
print_standard(' {}'.format(k))
|
||||||
|
update_progress_pane(state)
|
||||||
|
pause()
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
## Because state.tests is an OrderedDict and the disks were added
|
## Because state.tests is an OrderedDict and the disks were added
|
||||||
## in order, the tests will be run in order.
|
## in order, the tests will be run in order.
|
||||||
|
|
@ -720,6 +813,9 @@ def run_hw_tests(state):
|
||||||
|
|
||||||
def run_io_benchmark(state, test):
|
def run_io_benchmark(state, test):
|
||||||
"""TODO"""
|
"""TODO"""
|
||||||
|
# Bail early
|
||||||
|
if test.disabled:
|
||||||
|
return
|
||||||
tmux_update_pane(
|
tmux_update_pane(
|
||||||
state.panes['Top'], text='{}\n{}'.format(
|
state.panes['Top'], text='{}\n{}'.format(
|
||||||
TOP_PANE_TEXT, 'I/O Benchmark'))
|
TOP_PANE_TEXT, 'I/O Benchmark'))
|
||||||
|
|
@ -739,6 +835,9 @@ def run_keyboard_test():
|
||||||
|
|
||||||
def run_mprime_test(state, test):
|
def run_mprime_test(state, test):
|
||||||
"""Test CPU with Prime95 and track temps."""
|
"""Test CPU with Prime95 and track temps."""
|
||||||
|
# Bail early
|
||||||
|
if test.disabled:
|
||||||
|
return
|
||||||
test.started = True
|
test.started = True
|
||||||
test.update_status()
|
test.update_status()
|
||||||
update_progress_pane(state)
|
update_progress_pane(state)
|
||||||
|
|
@ -924,6 +1023,7 @@ def run_network_test():
|
||||||
|
|
||||||
def run_nvme_smart_tests(state, test):
|
def run_nvme_smart_tests(state, test):
|
||||||
"""Run NVMe or SMART test for test.dev."""
|
"""Run NVMe or SMART test for test.dev."""
|
||||||
|
_include_short_test = False
|
||||||
tmux_update_pane(
|
tmux_update_pane(
|
||||||
state.panes['Top'],
|
state.panes['Top'],
|
||||||
text='{t}\nDisk Health: {size:>6} ({tran}) {model} {serial}'.format(
|
text='{t}\nDisk Health: {size:>6} ({tran}) {model} {serial}'.format(
|
||||||
|
|
@ -941,10 +1041,14 @@ def run_nvme_smart_tests(state, test):
|
||||||
# NOTE: Pass/Fail based on both attributes and SMART short self-test
|
# NOTE: Pass/Fail based on both attributes and SMART short self-test
|
||||||
if test.dev.disk_ok:
|
if test.dev.disk_ok:
|
||||||
# Run short test
|
# Run short test
|
||||||
pause('TODO: Run SMART short self-test')
|
# TODO
|
||||||
|
_include_short_test = True
|
||||||
|
_timeout = test.dev.smart_self_test['polling_minutes'].get('short', 5)
|
||||||
|
_timeout = int(_timeout) + 5
|
||||||
|
|
||||||
# Check result
|
# Check result
|
||||||
# TODO
|
# TODO
|
||||||
|
# if 'remaining_percent' in 'status' then we've started.
|
||||||
short_test_passed = True
|
short_test_passed = True
|
||||||
if short_test_passed:
|
if short_test_passed:
|
||||||
test.passed = True
|
test.passed = True
|
||||||
|
|
@ -960,10 +1064,15 @@ def run_nvme_smart_tests(state, test):
|
||||||
else:
|
else:
|
||||||
test.failed = True
|
test.failed = True
|
||||||
test.update_status('NS')
|
test.update_status('NS')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
test.failed = True
|
test.failed = True
|
||||||
test.update_status('NS')
|
test.update_status('NS')
|
||||||
|
|
||||||
|
# Save report
|
||||||
|
test.report = test.dev.generate_report(
|
||||||
|
short_test=_include_short_test)
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
update_progress_pane(state)
|
update_progress_pane(state)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue