Prime95 test fully functional
This commit is contained in:
parent
1a91f72d8c
commit
45086c90bb
2 changed files with 140 additions and 52 deletions
|
|
@ -8,7 +8,6 @@ import pathlib
|
|||
import platform
|
||||
import plistlib
|
||||
import re
|
||||
import signal
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
|
|
@ -270,7 +269,7 @@ def build_menu(cli_mode=False, quick_mode=False):
|
|||
|
||||
# Update default selections for quick mode if necessary
|
||||
if quick_mode:
|
||||
for name in menu.options.keys():
|
||||
for name in menu.options:
|
||||
# Only select quick option(s)
|
||||
menu.options[name]['Selected'] = name in MENU_OPTIONS_QUICK
|
||||
|
||||
|
|
@ -291,29 +290,79 @@ def build_menu(cli_mode=False, quick_mode=False):
|
|||
return menu
|
||||
|
||||
|
||||
def check_mprime_results(test_obj, working_dir):
|
||||
"""Check mprime log files to determine if test passed."""
|
||||
passing_lines = {}
|
||||
warning_lines = {}
|
||||
|
||||
def _read_file(log_name):
|
||||
"""Read file and split into lines, returns list."""
|
||||
lines = []
|
||||
try:
|
||||
with open(f'{working_dir}/{log_name}', 'r') as _f:
|
||||
lines = _f.readlines()
|
||||
except FileNotFoundError:
|
||||
# File may be missing on older systems
|
||||
lines = []
|
||||
|
||||
return lines
|
||||
|
||||
# results.txt (check if failed)
|
||||
for line in _read_file('results.txt'):
|
||||
line = line.strip()
|
||||
if re.search(r'(error|fail)', line, re.IGNORECASE):
|
||||
warning_lines[line] = None
|
||||
|
||||
# print.log (check if passed)
|
||||
for line in _read_file('prime.log'):
|
||||
line = line.strip()
|
||||
match = re.search(
|
||||
r'(completed.*(\d+) errors, (\d+) warnings)', line, re.IGNORECASE)
|
||||
if match:
|
||||
if int(match.group(2)) + int(match.group(3)) > 0:
|
||||
# Errors and/or warnings encountered
|
||||
warning_lines[match.group(1).capitalize()] = None
|
||||
else:
|
||||
# No errors/warnings
|
||||
passing_lines[match.group(1).capitalize()] = None
|
||||
|
||||
# Update status
|
||||
if warning_lines:
|
||||
test_obj.failed = True
|
||||
test_obj.set_status('Failed')
|
||||
elif passing_lines and 'Aborted' not in test_obj.status:
|
||||
test_obj.passed = True
|
||||
test_obj.set_status('Passed')
|
||||
else:
|
||||
test_obj.set_status('Unknown')
|
||||
|
||||
# Update report
|
||||
for line in passing_lines:
|
||||
test_obj.report.append(f' {line}')
|
||||
for line in warning_lines:
|
||||
test_obj.report.append(std.color_string(f' {line}', 'YELLOW'))
|
||||
if not (passing_lines or warning_lines):
|
||||
test_obj.report.append(std.color_string(' Unknown result', 'YELLOW'))
|
||||
|
||||
|
||||
def cpu_mprime_test(state, test_objects):
|
||||
# pylint: disable=too-many-statements
|
||||
#TODO: Fix above?
|
||||
"""CPU & cooling check using Prime95."""
|
||||
LOG.info('CPU Test (Prime95)')
|
||||
thermal_abort = False
|
||||
prime_log = pathlib.Path(f'{state.log_dir}/prime.log')
|
||||
test = test_objects[0]
|
||||
sensors_out = pathlib.Path(f'{state.log_dir}/sensors.out')
|
||||
test_obj = test_objects[0]
|
||||
|
||||
# Bail early
|
||||
if test.disabled:
|
||||
if test_obj.disabled:
|
||||
return
|
||||
|
||||
# Prep
|
||||
dev = test.dev
|
||||
test.set_status('Working')
|
||||
state.update_top_pane(dev.description)
|
||||
state.update_top_pane(test_obj.dev.description)
|
||||
test_obj.set_status('Working')
|
||||
|
||||
# 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,))
|
||||
sensors.start_background_monitor(sensors_out)
|
||||
|
||||
# Create monitor and worker panes
|
||||
state.panes['Prime95'] = tmux.split_window(
|
||||
|
|
@ -333,44 +382,27 @@ def cpu_mprime_test(state, test_objects):
|
|||
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')
|
||||
proc_mprime = subprocess.Popen(
|
||||
['mprime', '-t'],
|
||||
bufsize=1,
|
||||
cwd=state.log_dir,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
proc_grep = subprocess.Popen(
|
||||
'grep --ignore-case --invert-match --line-buffered stress.txt'.split(),
|
||||
bufsize=1,
|
||||
stdin=proc_mprime.stdout,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
proc_mprime.stdout.close()
|
||||
save_nsbr = exe.NonBlockingStreamReader(proc_grep.stdout)
|
||||
save_thread = exe.start_thread(
|
||||
save_nsbr.save_to_file,
|
||||
args=(proc_grep, prime_log),
|
||||
)
|
||||
proc_mprime = start_mprime_thread(state.log_dir, prime_log)
|
||||
|
||||
# Show countdown
|
||||
try:
|
||||
print_countdown(seconds=cfg.hw.CPU_TEST_MINUTES*60)
|
||||
#print_countdown(seconds=cfg.hw.CPU_TEST_MINUTES*60)
|
||||
print_countdown(seconds=7)
|
||||
except KeyboardInterrupt:
|
||||
test.set_status('Aborted')
|
||||
test_obj.set_status('Aborted')
|
||||
except hw_sensors.ThermalLimitReachedError:
|
||||
test.set_status('Failed')
|
||||
test.failed = True
|
||||
thermal_abort = True
|
||||
test_obj.failed = True
|
||||
test_obj.set_status('Failed')
|
||||
|
||||
# Stop Prime95
|
||||
proc_mprime.send_signal(signal.SIGINT)
|
||||
std.sleep(1)
|
||||
proc_mprime.kill()
|
||||
save_thread.join()
|
||||
tmux.kill_pane(state.panes.pop('Prime95', None))
|
||||
proc_mprime.terminate()
|
||||
try:
|
||||
proc_mprime.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc_mprime.kill()
|
||||
set_apple_fan_speed('auto')
|
||||
|
||||
# Get cooldown temp
|
||||
set_apple_fan_speed('auto')
|
||||
std.clear_screen()
|
||||
std.print_standard('Letting CPU cooldown...')
|
||||
std.sleep(5)
|
||||
|
|
@ -378,18 +410,21 @@ def cpu_mprime_test(state, test_objects):
|
|||
sensors.save_average_temps(temp_label='Cooldown', seconds=5)
|
||||
|
||||
# Check results and build report
|
||||
std.print_report(sensors.generate_report('Current', 'Idle', 'Max','Cooldown'))
|
||||
|
||||
# Stop sensors monitor
|
||||
sensors_out.with_suffix('.stop').touch()
|
||||
sensors_thread.join()
|
||||
test_obj.report.append(std.color_string('Prime95', 'BLUE'))
|
||||
check_mprime_results(test_obj=test_obj, working_dir=state.log_dir)
|
||||
test_obj.report.append(std.color_string('Temps', 'BLUE'))
|
||||
for line in sensors.generate_report(
|
||||
'Idle', 'Max', 'Cooldown', only_cpu=True):
|
||||
test_obj.report.append(f' {line}')
|
||||
|
||||
# Cleanup
|
||||
sensors.stop_background_monitor()
|
||||
state.panes.pop('Current', None)
|
||||
tmux.kill_pane(state.panes.pop('Prime95', None))
|
||||
tmux.kill_pane(state.panes.pop('Temps', None))
|
||||
|
||||
#TODO: p95
|
||||
std.pause()
|
||||
std.print_report(test_obj.report)
|
||||
|
||||
|
||||
def disk_attribute_check(state, test_objects):
|
||||
|
|
@ -706,5 +741,31 @@ def set_apple_fan_speed(speed):
|
|||
exe.run_program(cmd, check=False)
|
||||
|
||||
|
||||
def start_mprime_thread(working_dir, log_path):
|
||||
"""Start mprime and save filtered output to log, returns Popen object."""
|
||||
proc_mprime = subprocess.Popen(
|
||||
['mprime', '-t'],
|
||||
bufsize=1,
|
||||
cwd=working_dir,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
proc_grep = subprocess.Popen(
|
||||
'grep --ignore-case --invert-match --line-buffered stress.txt'.split(),
|
||||
bufsize=1,
|
||||
stdin=proc_mprime.stdout,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
proc_mprime.stdout.close()
|
||||
save_nsbr = exe.NonBlockingStreamReader(proc_grep.stdout)
|
||||
exe.start_thread(
|
||||
save_nsbr.save_to_file,
|
||||
args=(proc_grep, log_path),
|
||||
)
|
||||
|
||||
# Return objects
|
||||
return proc_mprime
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("This file is not meant to be called directly.")
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import re
|
|||
from subprocess import CalledProcessError
|
||||
|
||||
from wk.cfg.hw import CPU_THERMAL_LIMIT, SMC_IDS, TEMP_COLORS
|
||||
from wk.exe import run_program
|
||||
from wk.exe import run_program, start_thread
|
||||
from wk.std import color_string, sleep
|
||||
|
||||
|
||||
|
|
@ -23,6 +23,7 @@ SMC_REGEX = re.compile(
|
|||
r'\s+(?P<Value>.*?)'
|
||||
r'\s*\(bytes (?P<Bytes>.*)\)$'
|
||||
)
|
||||
SENSOR_SOURCE_WIDTH = 25 if platform.system() == 'Darwin' else 20
|
||||
|
||||
|
||||
# Error Classes
|
||||
|
|
@ -34,7 +35,9 @@ class ThermalLimitReachedError(RuntimeError):
|
|||
class Sensors():
|
||||
"""Class for holding sensor specific data."""
|
||||
def __init__(self):
|
||||
self.background_thread = None
|
||||
self.data = get_sensor_data()
|
||||
self.out_path = None
|
||||
|
||||
def clear_temps(self):
|
||||
"""Clear saved temps but keep structure"""
|
||||
|
|
@ -55,7 +58,7 @@ class Sensors():
|
|||
for adapter, sources in sorted(adapters.items()):
|
||||
report.append(fix_sensor_name(adapter))
|
||||
for source, source_data in sorted(sources.items()):
|
||||
line = f'{fix_sensor_name(source):25} '
|
||||
line = f'{fix_sensor_name(source):{SENSOR_SOURCE_WIDTH}} '
|
||||
for label in temp_labels:
|
||||
if label != 'Current':
|
||||
line += f' {label.lower()}: '
|
||||
|
|
@ -78,12 +81,16 @@ class Sensors():
|
|||
# Done
|
||||
return report
|
||||
|
||||
def monitor_to_file(self, out_path):
|
||||
def monitor_to_file(self, out_path, temp_labels=None):
|
||||
"""Write report to path every second until stopped."""
|
||||
stop_path = pathlib.Path(out_path).resolve().with_suffix('.stop')
|
||||
if not temp_labels:
|
||||
temp_labels = ('Current', 'Max')
|
||||
|
||||
# Start loop
|
||||
while True:
|
||||
self.update_sensor_data()
|
||||
report = self.generate_report('Current', 'Max')
|
||||
report = self.generate_report(*temp_labels)
|
||||
with open(out_path, 'w') as _f:
|
||||
_f.write('\n'.join(report))
|
||||
|
||||
|
|
@ -111,6 +118,26 @@ class Sensors():
|
|||
temps = source_data['Temps']
|
||||
source_data[temp_label] = sum(temps) / len(temps)
|
||||
|
||||
def start_background_monitor(self, out_path, temp_labels=None):
|
||||
"""Start background thread to save report to file."""
|
||||
if self.background_thread:
|
||||
raise RuntimeError('Background thread already running')
|
||||
|
||||
self.out_path = pathlib.Path(out_path)
|
||||
self.background_thread = start_thread(
|
||||
self.monitor_to_file,
|
||||
args=(out_path, temp_labels),
|
||||
)
|
||||
|
||||
def stop_background_monitor(self):
|
||||
"""Stop background thread."""
|
||||
self.out_path.with_suffix('.stop').touch()
|
||||
self.background_thread.join()
|
||||
|
||||
# Reset vars to None
|
||||
self.background_thread = None
|
||||
self.out_path = None
|
||||
|
||||
def update_sensor_data(self, exit_on_thermal_limit=True):
|
||||
"""Update sensor data via OS-specific means."""
|
||||
if platform.system() == 'Darwin':
|
||||
|
|
|
|||
Loading…
Reference in a new issue