Prime95 workflow mostly done
This commit is contained in:
parent
0eadb784bb
commit
46a6dda0ff
4 changed files with 150 additions and 20 deletions
|
|
@ -15,6 +15,7 @@ ATTRIBUTE_COLORS = (
|
||||||
('Maximum', 'PURPLE'),
|
('Maximum', 'PURPLE'),
|
||||||
)
|
)
|
||||||
CPU_FAILURE_TEMP = 90
|
CPU_FAILURE_TEMP = 90
|
||||||
|
CPU_TEST_MINUTES = 7
|
||||||
CPU_THERMAL_LIMIT = 99
|
CPU_THERMAL_LIMIT = 99
|
||||||
KEY_NVME = 'nvme_smart_health_information_log'
|
KEY_NVME = 'nvme_smart_health_information_log'
|
||||||
KEY_SMART = 'ata_smart_attributes'
|
KEY_SMART = 'ata_smart_attributes'
|
||||||
|
|
@ -118,8 +119,8 @@ TMUX_LAYOUT = OrderedDict({
|
||||||
'Started': {'width': TMUX_SIDE_WIDTH, 'Check': True},
|
'Started': {'width': TMUX_SIDE_WIDTH, 'Check': True},
|
||||||
'Progress': {'width': TMUX_SIDE_WIDTH, 'Check': True},
|
'Progress': {'width': TMUX_SIDE_WIDTH, 'Check': True},
|
||||||
# Testing panes
|
# Testing panes
|
||||||
'Prime95': {'height': 11, 'Check': False},
|
|
||||||
'Temps': {'height': 1000, 'Check': False},
|
'Temps': {'height': 1000, 'Check': False},
|
||||||
|
'Prime95': {'height': 11, 'Check': False},
|
||||||
'SMART': {'height': 3, 'Check': True},
|
'SMART': {'height': 3, 'Check': True},
|
||||||
'badblocks': {'height': 5, 'Check': True},
|
'badblocks': {'height': 5, 'Check': True},
|
||||||
'I/O Benchmark': {'height': 1000, 'Check': False},
|
'I/O Benchmark': {'height': 1000, 'Check': False},
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ from docopt import docopt
|
||||||
|
|
||||||
from wk import cfg, exe, log, net, std, tmux
|
from wk import cfg, exe, log, net, std, tmux
|
||||||
from wk.hw import obj as hw_obj
|
from wk.hw import obj as hw_obj
|
||||||
|
from wk.hw import sensors as hw_sensors
|
||||||
|
|
||||||
|
|
||||||
# atexit functions
|
# atexit functions
|
||||||
|
|
@ -78,6 +79,7 @@ class State():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.cpu = None
|
self.cpu = None
|
||||||
self.disks = []
|
self.disks = []
|
||||||
|
self.layout = cfg.hw.TMUX_LAYOUT.copy()
|
||||||
self.log_dir = None
|
self.log_dir = None
|
||||||
self.panes = {}
|
self.panes = {}
|
||||||
self.tests = OrderedDict({
|
self.tests = OrderedDict({
|
||||||
|
|
@ -111,11 +113,13 @@ class State():
|
||||||
|
|
||||||
# Init tmux and start a background process to maintain layout
|
# Init tmux and start a background process to maintain layout
|
||||||
self.init_tmux()
|
self.init_tmux()
|
||||||
if hasattr(signal, 'SIGWINCH'):
|
#TODO: Fix SIGWINCH?
|
||||||
# Use signal handling
|
#if hasattr(signal, 'SIGWINCH'):
|
||||||
signal.signal(signal.SIGWINCH, self.fix_tmux_layout)
|
# # Use signal handling
|
||||||
else:
|
# signal.signal(signal.SIGWINCH, self.fix_tmux_layout)
|
||||||
exe.start_thread(self.fix_tmux_layout_loop)
|
#else:
|
||||||
|
# exe.start_thread(self.fix_tmux_layout_loop)
|
||||||
|
exe.start_thread(self.fix_tmux_layout_loop)
|
||||||
|
|
||||||
def fix_tmux_layout(self, forced=True, signum=None, frame=None):
|
def fix_tmux_layout(self, forced=True, signum=None, frame=None):
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
|
@ -125,7 +129,7 @@ class State():
|
||||||
signum and frame must be valid aguments.
|
signum and frame must be valid aguments.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
tmux.fix_layout(self.panes, cfg.hw.TMUX_LAYOUT, forced=forced)
|
tmux.fix_layout(self.panes, self.layout, forced=forced)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
# Assuming self.panes changed while running
|
# Assuming self.panes changed while running
|
||||||
pass
|
pass
|
||||||
|
|
@ -143,6 +147,8 @@ class State():
|
||||||
"""Initialize diagnostic pass."""
|
"""Initialize diagnostic pass."""
|
||||||
# Reset objects
|
# Reset objects
|
||||||
self.disks.clear()
|
self.disks.clear()
|
||||||
|
self.layout.clear()
|
||||||
|
self.layout.update(cfg.hw.TMUX_LAYOUT)
|
||||||
for test_data in self.tests.values():
|
for test_data in self.tests.values():
|
||||||
test_data['Objects'].clear()
|
test_data['Objects'].clear()
|
||||||
|
|
||||||
|
|
@ -287,6 +293,81 @@ def build_menu(cli_mode=False, quick_mode=False):
|
||||||
def cpu_mprime_test(state, test_objects):
|
def cpu_mprime_test(state, test_objects):
|
||||||
"""CPU & cooling check using Prime95."""
|
"""CPU & cooling check using Prime95."""
|
||||||
LOG.info('CPU Test (Prime95)')
|
LOG.info('CPU Test (Prime95)')
|
||||||
|
thermal_abort = False
|
||||||
|
prime_log = pathlib.Path(f'{state.log_dir}/prime.log')
|
||||||
|
test = test_objects[0]
|
||||||
|
|
||||||
|
# Bail early
|
||||||
|
if test.disabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Prep
|
||||||
|
dev = test.dev
|
||||||
|
test.set_status('Working')
|
||||||
|
state.update_top_pane(dev.description)
|
||||||
|
|
||||||
|
# Start sensors monitor
|
||||||
|
sensors = hw_sensors.Sensors()
|
||||||
|
sensors_out = pathlib.Path(f'{state.log_dir}/sensors.out')
|
||||||
|
sensors_thread = exe.start_thread(
|
||||||
|
sensors.monitor_to_file, args=(sensors_out,))
|
||||||
|
|
||||||
|
# Create monitor and worker panes
|
||||||
|
state.panes['Prime95'] = tmux.split_window(
|
||||||
|
lines=10, vertical=True, watch_file=prime_log)
|
||||||
|
state.panes['Temps'] = tmux.split_window(
|
||||||
|
behind=True, percent=80, vertical=True, watch_file=sensors_out)
|
||||||
|
tmux.resize_pane(height=3)
|
||||||
|
state.panes['Current'] = ''
|
||||||
|
state.layout['Current'] = {'height': 3, 'Check': True}
|
||||||
|
|
||||||
|
# Get idle temps
|
||||||
|
std.clear_screen()
|
||||||
|
std.print_standard('Saving idle temps...')
|
||||||
|
sensors.save_average_temps(temp_label='Idle', seconds=5)
|
||||||
|
|
||||||
|
# Stress CPU
|
||||||
|
std.print_info('Starting stress test')
|
||||||
|
std.print_warning('If running too hot, press CTRL+c to abort the test')
|
||||||
|
set_apple_fan_speed('max')
|
||||||
|
#RUN: mprime -t | grep -iv --line-buffered 'stress.txt' | tee -a "prime.log"
|
||||||
|
try:
|
||||||
|
print_countdown(seconds=cfg.hw.CPU_TEST_MINUTES*60)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
test.set_status('Aborted')
|
||||||
|
except hw_sensors.ThermalLimitReachedError:
|
||||||
|
test.set_status('Failed')
|
||||||
|
test.failed = True
|
||||||
|
thermal_abort = True
|
||||||
|
|
||||||
|
# Stop Prime95
|
||||||
|
#TODO kill p95
|
||||||
|
tmux.kill_pane(state.panes.pop('Prime95', None))
|
||||||
|
|
||||||
|
# Get cooldown temp
|
||||||
|
set_apple_fan_speed('auto')
|
||||||
|
std.clear_screen()
|
||||||
|
std.print_standard('Letting CPU cooldown...')
|
||||||
|
std.sleep(5)
|
||||||
|
std.print_standard('Saving cooldown temps...')
|
||||||
|
sensors.save_average_temps(temp_label='Cooldown', seconds=5)
|
||||||
|
|
||||||
|
# Check results and build report
|
||||||
|
#TODO
|
||||||
|
|
||||||
|
# Stop sensors monitor
|
||||||
|
sensors_out.with_suffix('.stop').touch()
|
||||||
|
sensors_thread.join()
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
state.panes.pop('Current', None)
|
||||||
|
tmux.kill_pane(state.panes.pop('Temps', None))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#TODO: p95
|
#TODO: p95
|
||||||
std.print_warning('TODO: p95')
|
std.print_warning('TODO: p95')
|
||||||
std.pause()
|
std.pause()
|
||||||
|
|
@ -503,6 +584,26 @@ def network_test():
|
||||||
std.pause('Press Enter to return to main menu...')
|
std.pause('Press Enter to return to main menu...')
|
||||||
|
|
||||||
|
|
||||||
|
def print_countdown(seconds):
|
||||||
|
"""Print countdown to screen."""
|
||||||
|
time_limit = seconds
|
||||||
|
for i in range(seconds):
|
||||||
|
sec_left = (seconds - i) % 60
|
||||||
|
min_left = int((seconds - i) / 60)
|
||||||
|
|
||||||
|
out_str = '\r'
|
||||||
|
if min_left:
|
||||||
|
out_str += f'{min_left} minute{"s" if min_left != 1 else ""}, '
|
||||||
|
out_str += f'{sec_left} second{"s" if sec_left != 1 else ""}'
|
||||||
|
out_str += ' remaining'
|
||||||
|
|
||||||
|
print(f'{out_str:<40}', end='', flush=True)
|
||||||
|
std.sleep(1)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
print('')
|
||||||
|
|
||||||
|
|
||||||
def run_diags(state, menu, quick_mode=False):
|
def run_diags(state, menu, quick_mode=False):
|
||||||
"""Run selected diagnostics."""
|
"""Run selected diagnostics."""
|
||||||
aborted = False
|
aborted = False
|
||||||
|
|
@ -569,5 +670,23 @@ def screensaver(name):
|
||||||
tmux.zoom_pane()
|
tmux.zoom_pane()
|
||||||
|
|
||||||
|
|
||||||
|
def set_apple_fan_speed(speed):
|
||||||
|
"""Set Apple fan speed."""
|
||||||
|
cmd = None
|
||||||
|
|
||||||
|
# Check
|
||||||
|
if speed not in ('auto', 'max'):
|
||||||
|
raise RuntimeError(f'Invalid speed {speed}')
|
||||||
|
|
||||||
|
# Set cmd
|
||||||
|
if platform.system() == 'Linux':
|
||||||
|
cmd = ['apple-fans', speed]
|
||||||
|
#TODO: Add method for use under macOS
|
||||||
|
|
||||||
|
# Run cmd
|
||||||
|
if cmd:
|
||||||
|
exe.run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("This file is not meant to be called directly.")
|
print("This file is not meant to be called directly.")
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import pathlib
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
@ -77,14 +78,21 @@ class Sensors():
|
||||||
# Done
|
# Done
|
||||||
return report
|
return report
|
||||||
|
|
||||||
def monitor_to_file(self, path):
|
def monitor_to_file(self, out_path):
|
||||||
"""Write report to path every second until stopped."""
|
"""Write report to path every second until stopped."""
|
||||||
|
stop_path = pathlib.Path(out_path).resolve().with_suffix('.stop')
|
||||||
while True:
|
while True:
|
||||||
self.update_sensor_data()
|
self.update_sensor_data()
|
||||||
report = self.generate_report('Current', 'Max')
|
report = self.generate_report('Current', 'Max')
|
||||||
with open(path, 'w') as _f:
|
with open(out_path, 'w') as _f:
|
||||||
_f.write('\n'.join(report))
|
_f.write('\n'.join(report))
|
||||||
sleep(1)
|
|
||||||
|
# Check if we should stop
|
||||||
|
if stop_path.exists():
|
||||||
|
break
|
||||||
|
|
||||||
|
# Sleep before next loop
|
||||||
|
sleep(0.5)
|
||||||
|
|
||||||
def save_average_temps(self, temp_label, seconds=10):
|
def save_average_temps(self, temp_label, seconds=10):
|
||||||
# pylint: disable=unused-variable
|
# pylint: disable=unused-variable
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ def fix_layout(panes, layout, forced=False):
|
||||||
if isinstance(pane_list, str):
|
if isinstance(pane_list, str):
|
||||||
pane_list = [pane_list]
|
pane_list = [pane_list]
|
||||||
for pane_id in pane_list:
|
for pane_id in pane_list:
|
||||||
|
if name == 'Current':
|
||||||
|
pane_id = None
|
||||||
try:
|
try:
|
||||||
resize_pane(pane_id, **data)
|
resize_pane(pane_id, **data)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
|
|
@ -98,13 +100,16 @@ def layout_needs_fixed(panes, layout):
|
||||||
if name not in panes:
|
if name not in panes:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check pane size
|
# Check pane size(s)
|
||||||
pane_id = panes[name]
|
pane_list = panes[name]
|
||||||
width, height = get_pane_size(pane_id)
|
if isinstance(pane_list, str):
|
||||||
if data.get('width', False) and data['width'] != width:
|
pane_list = [pane_list]
|
||||||
needs_fixed = True
|
for pane_id in pane_list:
|
||||||
if data.get('height', False) and data['height'] != height:
|
width, height = get_pane_size(pane_id)
|
||||||
needs_fixed = True
|
if data.get('width', False) and data['width'] != width:
|
||||||
|
needs_fixed = True
|
||||||
|
if data.get('height', False) and data['height'] != height:
|
||||||
|
needs_fixed = True
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
return needs_fixed
|
return needs_fixed
|
||||||
|
|
@ -193,9 +198,6 @@ def resize_pane(pane_id=None, width=None, height=None, **kwargs):
|
||||||
cmd = ['tmux', 'resize-pane']
|
cmd = ['tmux', 'resize-pane']
|
||||||
|
|
||||||
# Safety checks
|
# Safety checks
|
||||||
if not poll_pane(pane_id):
|
|
||||||
LOG.debug('tmux pane %s not found', pane_id)
|
|
||||||
raise RuntimeError(f'tmux pane {pane_id} not found')
|
|
||||||
if not (width or height):
|
if not (width or height):
|
||||||
LOG.error('Neither width nor height specified')
|
LOG.error('Neither width nor height specified')
|
||||||
raise RuntimeError('Neither width nor height specified')
|
raise RuntimeError('Neither width nor height specified')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue