diff --git a/scripts/wk/cfg/__init__.py b/scripts/wk/cfg/__init__.py index 9538f1c8..fc63cf2d 100644 --- a/scripts/wk/cfg/__init__.py +++ b/scripts/wk/cfg/__init__.py @@ -1,5 +1,6 @@ """WizardKit: cfg module init""" +from wk.cfg import hw from wk.cfg import log from wk.cfg import main from wk.cfg import net diff --git a/scripts/wk/cfg/hw.py b/scripts/wk/cfg/hw.py new file mode 100644 index 00000000..972f6ce0 --- /dev/null +++ b/scripts/wk/cfg/hw.py @@ -0,0 +1,34 @@ +"""WizardKit: Config - Hardware""" +# pylint: disable=bad-whitespace,line-too-long +# vim: sts=2 sw=2 ts=2 + + +ATTRIBUTES = { + # NVMe + 'critical_warning': {'Critical': True, 'Ignore': False, 'Warning': None, 'Error': 1, 'Maximum': None, }, + 'media_errors': {'Critical': False, 'Ignore': False, 'Warning': None, 'Error': 1, 'Maximum': None, }, + 'power_on_hours': {'Critical': False, 'Ignore': True, 'Warning': 17532, 'Error': 26298, 'Maximum': None, }, + 'unsafe_shutdowns': {'Critical': False, 'Ignore': True, 'Warning': 1, 'Error': None, 'Maximum': None, }, + # SMART + 5: {'Hex': '05', 'Critical': True, 'Ignore': False, 'Warning': None, 'Error': 1, 'Maximum': None, }, + 9: {'Hex': '09', 'Critical': False, 'Ignore': True, 'Warning': 17532, 'Error': 26298, 'Maximum': None, }, + 10: {'Hex': '10', 'Critical': False, 'Ignore': False, 'Warning': 1, 'Error': 10, 'Maximum': 10000, }, + 184: {'Hex': 'B8', 'Critical': False, 'Ignore': False, 'Warning': 1, 'Error': 10, 'Maximum': 10000, }, + 187: {'Hex': 'BB', 'Critical': False, 'Ignore': False, 'Warning': 1, 'Error': 10, 'Maximum': 10000, }, + 188: {'Hex': 'BC', 'Critical': False, 'Ignore': False, 'Warning': 1, 'Error': 10, 'Maximum': 10000, }, + 196: {'Hex': 'C4', 'Critical': False, 'Ignore': False, 'Warning': 1, 'Error': 10, 'Maximum': 10000, }, + 197: {'Hex': 'C5', 'Critical': True, 'Ignore': False, 'Warning': None, 'Error': 1, 'Maximum': None, }, + 198: {'Hex': 'C6', 'Critical': True, 'Ignore': False, 'Warning': None, 'Error': 1, 'Maximum': None, }, + 199: {'Hex': 'C7', 'Critical': False, 'Ignore': True, 'Warning': None, 'Error': 1, 'Maximum': None, }, + 201: {'Hex': 'C9', 'Critical': False, 'Ignore': False, 'Warning': None, 'Error': 1, 'Maximum': 10000, }, + } +ATTRIBUTE_COLORS = ( + # NOTE: Ordered by ascending importance + ('Warning', 'YELLOW'), + ('Error', 'RED'), + ('Maximum', 'PURPLE'), + ) + + +if __name__ == '__main__': + print("This file is not meant to be called directly.") diff --git a/scripts/wk/hw/obj.py b/scripts/wk/hw/obj.py index 844896dc..ab30d258 100644 --- a/scripts/wk/hw/obj.py +++ b/scripts/wk/hw/obj.py @@ -1,4 +1,4 @@ -"""WizardKit: Hardware objects (mostly).""" +"""WizardKit: Hardware objects (mostly)""" # vim: sts=2 sw=2 ts=2 import logging @@ -9,6 +9,7 @@ import re from collections import OrderedDict +from wk.cfg.hw import ATTRIBUTES, ATTRIBUTE_COLORS from wk.exe import get_json_from_command, run_program from wk.std import bytes_to_string, color_string, string_to_bytes @@ -93,8 +94,8 @@ class CpuRam(): f'{count}x {desc}' for desc, count in sorted(details.items()) ] - def generate_cpu_report(self): - """Generate CPU report with data from all tests.""" + def generate_report(self): + """Generate CPU & RAM report, returns list.""" report = [] report.append(color_string('Device', 'BLUE')) report.append(f' {self.description}') @@ -116,7 +117,7 @@ class Disk(): self.attributes = {} self.description = 'Unknown' self.details = {} - self.nvme_smart_notes = {} + self.notes = {} self.path = pathlib.Path(path).resolve() self.smartctl = {} self.tests = OrderedDict() @@ -137,6 +138,77 @@ class Disk(): ] run_program(cmd, check=False) + def generate_attribute_report(self): + """Generate attribute report, returns list.""" + report = [] + for attr, value in sorted(self.attributes.items()): + note = '' + value_color = 'GREEN' + + # Skip attributes not in our list + if attr not in ATTRIBUTES: + continue + + # ID / Name + label = f'{attr:>3}' + if isinstance(attr, int): + # Assuming SMART, include hex ID and name + label += f' / {str(hex(attr))[2:].upper():0>2}: {value["name"]}' + label = f' {label.replace("_", " "):38}' + + # Value color + for threshold, color in ATTRIBUTE_COLORS: + if value['raw'] >= ATTRIBUTES.get(threshold, float('inf')): + value_color = color + if threshold == 'Maximum': + note = '(invalid?)' + + # 199/C7 warning + if str(attr) == '199' and value['raw'] > 0: + note = '(bad cable?)' + + # Build colored string and append to report + line = color_string( + [label, value['raw_str'], note], + [None, value_color, 'YELLOW'], + ) + report.append(line) + + # Done + return report + + + def generate_report(self): + """Generate Disk report, returns list.""" + report = [] + report.append(color_string(f'Device {self.path.name}', 'BLUE')) + report.append(f' {self.description}') + + # Attributes + if self.attributes: + report.append(color_string('Attributes', 'BLUE')) + report.extend(self.generate_attribute_report()) + else: + report.append( + color_string(' No NVMe or SMART data available', 'YELLOW')) + + # Notes + if self.notes: + report.append(color_string('Notes', 'BLUE')) + for note in sorted(self.notes.keys()): + report.append(f' {note}') + + # 4K alignment check + if not self.is_4k_aligned(): + report.append(color_string('Warning', 'YELLOW')) + report.append(' One or more partitions are not 4K aligned') + + # Tests + for test in self.tests.values(): + report.extend(test.report) + + return report + def get_details(self): """Get disk details using OS specific methods. @@ -187,6 +259,11 @@ class Disk(): # Done return labels + def is_4k_aligned(self): + """Check that all disk partitions are aligned, returns bool.""" + #TODO: Make real + return True + def update_smart_details(self): """Update SMART details via smartctl.""" self.attributes = {}