Prime95 test fully functional

This commit is contained in:
2Shirt 2019-11-14 19:13:21 -07:00
parent 1a91f72d8c
commit 45086c90bb
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C
2 changed files with 140 additions and 52 deletions

View file

@ -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.")

View file

@ -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':