diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index e1dc4a42..fa0e7209 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -39,6 +39,11 @@ TESTS = { 'Results': {}, 'Status': {}, }, + 'iobenchmark': { + 'Enabled': False, + 'Results': {}, + 'Status': {}, + }, } def get_smart_details(dev): @@ -66,15 +71,17 @@ def menu_diags(*args): """Main HW-Diagnostic menu.""" diag_modes = [ {'Name': 'All tests', - 'Tests': ['Prime95', 'NVMe/SMART', 'badblocks']}, + 'Tests': ['Prime95', 'NVMe/SMART', 'badblocks', 'iobenchmark']}, {'Name': 'Prime95', 'Tests': ['Prime95']}, - {'Name': 'NVMe/SMART & badblocks', - 'Tests': ['NVMe/SMART', 'badblocks']}, + {'Name': 'All drive tests', + 'Tests': ['NVMe/SMART', 'badblocks', 'iobenchmark']}, {'Name': 'NVMe/SMART', 'Tests': ['NVMe/SMART']}, {'Name': 'badblocks', 'Tests': ['badblocks']}, + {'Name': 'I/O Benchmark', + 'Tests': ['iobenchmark']}, {'Name': 'Quick drive test', 'Tests': ['Quick', 'NVMe/SMART']}, ] @@ -197,6 +204,75 @@ def run_badblocks(): run_program('tmux kill-pane -a'.split(), check=False) pass +def run_iobenchmark(): + """Run a read-only test for all detected disks.""" + aborted = False + clear_screen() + print_log('\nStart I/O Benchmark test(s)\n') + progress_file = '{}/iobenchmark_progress.out'.format(global_vars['LogDir']) + update_progress() + + # Set Window layout and start test + run_program('tmux split-window -dhl 15 watch -c -n1 -t cat {}'.format( + TESTS['Progress Out']).split()) + + # Show disk details + for name, dev in sorted(TESTS['iobenchmark']['Devices'].items()): + show_disk_details(dev) + print_standard(' ') + update_progress() + + # Run + print_standard('Running benchmark test(s):') + for name, dev in sorted(TESTS['iobenchmark']['Devices'].items()): + cur_status = TESTS['iobenchmark']['Status'][name] + nvme_smart_status = TESTS['NVMe/SMART']['Status'].get(name, None) + bb_status = TESTS['badblocks']['Status'].get(name, None) + if cur_status == 'Denied': + # Skip denied disks + continue + if nvme_smart_status == 'NS': + TESTS['iobenchmark']['Status'][name] = 'Skipped' + elif bb_status in ['NS', 'Skipped']: + TESTS['iobenchmark']['Status'][name] = 'Skipped' + else: + # (SMART tests not run or CS/OVERRIDE) + # AND (BADBLOCKS tests not run or CS) + TESTS['iobenchmark']['Status'][name] = 'Working' + update_progress() + print_standard(' /dev/{:11} '.format(name+'...'), end='', flush=True) + run_program('tmux split-window -dl 5 {} {} {}'.format( + 'hw-diags-iobenchmark', + '/dev/{}'.format(name), + progress_file).split()) + wait_for_process('dd') + print_standard('Done', timestamp=False) + + # Check results + with open(progress_file, 'r') as f: + text = f.read() + io_stats = text.replace('\r', '\n').split('\n') + try: + io_stats = [re.sub(r'.*\s+(\d+) MB/s\s*$', r'\1', stat) + for stat in io_stats if 'MB/s' in stat] + io_stats = [int(x) for x in io_stats] + TESTS['iobenchmark']['Results'][name] = 'Read speed: {:0.0f} MB/s (Min: {}, Max: {})'.format( + sum(io_stats) / len(io_stats), + min(io_stats), + max(io_stats)) + TESTS['iobenchmark']['Status'][name] = 'CS' + except: + TESTS['iobenchmark']['Status'][name] = 'NS' + + # Move temp file + shutil.move(progress_file, '{}/iobenchmark-{}.log'.format( + global_vars['LogDir'], name)) + update_progress() + + # Done + run_program('tmux kill-pane -a'.split(), check=False) + pass + def run_mprime(): """Run Prime95 for MPRIME_LIMIT minutes while showing the temps.""" aborted = False @@ -389,12 +465,12 @@ def run_tests(tests): print_log('Starting Hardware Diagnostics') print_log('\nRunning tests: {}'.format(', '.join(tests))) # Enable selected tests - for t in ['Prime95', 'NVMe/SMART', 'badblocks']: + for t in ['Prime95', 'NVMe/SMART', 'badblocks', 'iobenchmark']: TESTS[t]['Enabled'] = t in tests TESTS['NVMe/SMART']['Quick'] = 'Quick' in tests # Initialize - if TESTS['NVMe/SMART']['Enabled'] or TESTS['badblocks']['Enabled']: + if TESTS['NVMe/SMART']['Enabled'] or TESTS['badblocks']['Enabled'] or TESTS['iobenchmark']['Enabled']: scan_disks() update_progress() @@ -410,6 +486,8 @@ def run_tests(tests): run_nvme_smart() if TESTS['badblocks']['Enabled']: run_badblocks() + if TESTS['iobenchmark']['Enabled']: + run_iobenchmark() # Show results show_results() @@ -437,6 +515,7 @@ def scan_disks(): devs[d['name']] = {'lsblk': d} TESTS['NVMe/SMART']['Status'][d['name']] = 'Pending' TESTS['badblocks']['Status'][d['name']] = 'Pending' + TESTS['iobenchmark']['Status'][d['name']] = 'Pending' else: # Skip WizardKit devices wk_label = '{}_LINUX'.format(KIT_NAME_SHORT) @@ -444,6 +523,7 @@ def scan_disks(): devs[d['name']] = {'lsblk': d} TESTS['NVMe/SMART']['Status'][d['name']] = 'Pending' TESTS['badblocks']['Status'][d['name']] = 'Pending' + TESTS['iobenchmark']['Status'][d['name']] = 'Pending' for dev, data in devs.items(): # Get SMART attributes @@ -483,7 +563,7 @@ def scan_disks(): data['SMART Support'] = False # Ask for manual overrides if necessary - if not data['Quick Health OK'] and TESTS['badblocks']['Enabled']: + if not data['Quick Health OK'] and (TESTS['badblocks']['Enabled'] or TESTS['iobenchmark']['Enabled']): show_disk_details(data) print_warning("WARNING: Health can't be confirmed for: {}".format( '/dev/{}'.format(dev))) @@ -494,10 +574,12 @@ def scan_disks(): else: TESTS['NVMe/SMART']['Status'][dev_name] = 'NS' TESTS['badblocks']['Status'][dev_name] = 'Denied' + TESTS['iobenchmark']['Status'][dev_name] = 'Denied' print_standard(' ') # In case there's more than one "OVERRIDE" disk TESTS['NVMe/SMART']['Devices'] = devs TESTS['badblocks']['Devices'] = devs + TESTS['iobenchmark']['Devices'] = devs def show_disk_details(dev): """Display disk details.""" @@ -616,8 +698,8 @@ def show_results(): print(' {}'.format(line.strip())) print_standard(' ') - # NVMe/SMART / badblocks - if TESTS['NVMe/SMART']['Enabled'] or TESTS['badblocks']['Enabled']: + # NVMe/SMART / badblocks / iobenchmark + if TESTS['NVMe/SMART']['Enabled'] or TESTS['badblocks']['Enabled'] or TESTS['iobenchmark']['Enabled']: print_success('Disks:') for name, dev in sorted(TESTS['NVMe/SMART']['Devices'].items()): show_disk_details(dev) @@ -635,6 +717,12 @@ def show_results(): print_standard(' {}'.format(line)) else: print_error(' {}'.format(line)) + io_status = TESTS['iobenchmark']['Status'].get(name, None) + if (TESTS['iobenchmark']['Enabled'] + and io_status not in ['Denied', 'OVERRIDE', 'Skipped']): + print_info('Benchmark:') + result = TESTS['iobenchmark']['Results'].get(name, '') + print_standard(' {}'.format(result)) print_standard(' ') # Done @@ -676,6 +764,16 @@ def update_progress(): s_color = get_status_color(status), status = status, **COLORS)) + if TESTS['iobenchmark']['Enabled']: + output.append(' ') + output.append('{BLUE}I/O Benchmark{CLEAR}'.format(**COLORS)) + for dev, status in sorted(TESTS['iobenchmark']['Status'].items()): + output.append('{dev}{s_color}{status:>{pad}}{CLEAR}'.format( + dev = dev, + pad = 15-len(dev), + s_color = get_status_color(status), + status = status, + **COLORS)) # Add line-endings output = ['{}\n'.format(line) for line in output] diff --git a/.bin/Scripts/hw-diags-iobenchmark b/.bin/Scripts/hw-diags-iobenchmark new file mode 100644 index 00000000..7d1f69d8 --- /dev/null +++ b/.bin/Scripts/hw-diags-iobenchmark @@ -0,0 +1,18 @@ +#!/bin/bash +# +## Wizard Kit: HW Diagnostics - Benchmarks + +function usage { + echo "Usage: ${0} device log-file" + echo " e.g. ${0} /dev/sda /tmp/tmp.XXXXXXX/benchmarks.log" +} + +# Bail early +if [ ! -b "${1}" ]; then + usage + exit 1 +fi + +# Run Benchmarks +echo 3 | sudo tee -a /proc/sys/vm/drop_caches >/dev/null 2>&1 +sudo dd bs=4M count=4096 if="${1}" of=/dev/null status=progress 2>&1 | tee -a "${2}"