Show failed SMART attributes during disk tests

This commit is contained in:
2Shirt 2022-10-23 13:18:43 -07:00
parent 5c6c123daa
commit 2706d1a9a5
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C
2 changed files with 51 additions and 41 deletions

View file

@ -594,6 +594,7 @@ def disk_self_test(state, test_objects, test_mode=False) -> None:
f'Disk self-test{"s" if len(test_objects) > 1 else ""}',
)
std.print_info(f'Starting self-test{"s" if len(test_objects) > 1 else ""}')
show_failed_attributes(state)
for test in reversed(test_objects):
if test.disabled:
# Skip
@ -692,19 +693,7 @@ def disk_surface_scan(state, test_objects, test_mode=False) -> None:
std.print_info(
f'Starting disk surface scan{"s" if len(test_objects) > 1 else ""}',
)
for disk in state.disks:
failed_attributes = [
line for line in hw_smart.generate_attribute_report(disk) if 'failed' in line
]
if failed_attributes:
size_str = std.bytes_to_string(disk.size, use_binary=False)
std.print_colored(
['[', disk.path.name, ' ', size_str, ']'],
[None, 'BLUE', None, 'CYAN', None],
sep='',
)
std.print_report(failed_attributes)
std.print_standard('')
show_failed_attributes(state)
# Run surface scans
for test in reversed([test for test in test_objects if not test.disabled]):
@ -907,6 +896,16 @@ def run_diags(state, menu, quick_mode=False, test_mode=False) -> None:
std.pause('Press Enter to return to main menu...')
def show_failed_attributes(state) -> None:
"""Show failed attributes for all disks."""
for dev in state.disks:
std.print_colored([dev.name, dev.description], ['CYAN', None])
std.print_report(
hw_smart.generate_attribute_report(dev, only_failed=True),
)
std.print_standard('')
def show_results(state) -> None:
"""Show test results by device."""
std.sleep(0.5)

View file

@ -112,20 +112,14 @@ def enable_smart(dev) -> None:
run_program(cmd, check=False)
def generate_attribute_report(dev) -> list[str]:
def generate_attribute_report(dev, only_failed=False) -> list[str]:
"""Generate attribute report, returns list."""
report = []
for attr, value in sorted(dev.attributes.items()):
note = ''
value_color = 'GREEN'
# Skip attributes not in our list
if attr not in dev.known_attributes:
continue
# Check for attribute note
note = dev.known_attributes[attr].get('Note', '')
# ID / Name
label = f'{attr:>3}'
if isinstance(attr, int):
@ -133,28 +127,13 @@ def generate_attribute_report(dev) -> list[str]:
label += f' / {str(hex(attr))[2:].upper():0>2}: {value["name"]}'
label = f' {label.replace("_", " "):38}'
# Value color
if dev.known_attributes[attr].get('PercentageLife', False):
# PercentageLife values
if 0 <= value['raw'] <= dev.known_attributes[attr]['Error']:
value_color = 'RED'
note = '(failed, % life remaining)'
elif value['raw'] < 0 or value['raw'] > 100:
value_color = 'PURPLE'
note = '(invalid?)'
else:
for threshold, color in ATTRIBUTE_COLORS:
threshold_val = dev.known_attributes[attr].get(threshold, None)
if threshold_val and value['raw'] >= threshold_val:
value_color = color
if threshold == 'Error':
note = '(failed)'
elif threshold == 'Maximum':
note = '(invalid?)'
# Color & Note
value_color, note = get_attribute_value_color_and_note(dev, attr, value)
# 199/C7 warning
if str(attr) == '199' and value['raw'] > 0:
note = '(bad cable?)'
# Skip non-failing attributes if requested
## NOTE: This is a naive test and will include 'invalid' attributes
if only_failed and not note:
continue
# Build colored string and append to report
line = color_string(
@ -167,6 +146,38 @@ def generate_attribute_report(dev) -> list[str]:
return report
def get_attribute_value_color_and_note(dev, attr, value) -> tuple[str, str]:
"""Get attribute color and note based on SMART data."""
value_color = 'GREEN'
note = dev.known_attributes[attr].get('Note', '')
# Value value_color
if dev.known_attributes[attr].get('PercentageLife', False):
# PercentageLife values
if 0 <= value['raw'] <= dev.known_attributes[attr]['Error']:
value_color = 'RED'
note = '(failed, % life remaining)'
elif value['raw'] < 0 or value['raw'] > 100:
value_color = 'PURPLE'
note = '(invalid?)'
else:
for threshold, color in ATTRIBUTE_COLORS:
threshold_val = dev.known_attributes[attr].get(threshold, None)
if threshold_val and value['raw'] >= threshold_val:
value_color = color
if threshold == 'Error':
note = '(failed)'
elif threshold == 'Maximum':
note = '(invalid?)'
# 199/C7 warning
if str(attr) == '199' and value['raw'] > 0:
note = '(bad cable?)'
# Done
return (value_color, note)
def get_attribute_value_string(dev, attr) -> str:
"""Get attribute value string and report if it has changed."""
current_value = dev.attributes.get(attr, {})