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 platform
|
||||||
import plistlib
|
import plistlib
|
||||||
import re
|
import re
|
||||||
import signal
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
@ -270,7 +269,7 @@ def build_menu(cli_mode=False, quick_mode=False):
|
||||||
|
|
||||||
# Update default selections for quick mode if necessary
|
# Update default selections for quick mode if necessary
|
||||||
if quick_mode:
|
if quick_mode:
|
||||||
for name in menu.options.keys():
|
for name in menu.options:
|
||||||
# Only select quick option(s)
|
# Only select quick option(s)
|
||||||
menu.options[name]['Selected'] = name in MENU_OPTIONS_QUICK
|
menu.options[name]['Selected'] = name in MENU_OPTIONS_QUICK
|
||||||
|
|
||||||
|
|
@ -291,29 +290,79 @@ def build_menu(cli_mode=False, quick_mode=False):
|
||||||
return menu
|
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):
|
def cpu_mprime_test(state, test_objects):
|
||||||
# pylint: disable=too-many-statements
|
|
||||||
#TODO: Fix above?
|
|
||||||
"""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')
|
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
|
# Bail early
|
||||||
if test.disabled:
|
if test_obj.disabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Prep
|
# Prep
|
||||||
dev = test.dev
|
state.update_top_pane(test_obj.dev.description)
|
||||||
test.set_status('Working')
|
test_obj.set_status('Working')
|
||||||
state.update_top_pane(dev.description)
|
|
||||||
|
|
||||||
# Start sensors monitor
|
# Start sensors monitor
|
||||||
sensors = hw_sensors.Sensors()
|
sensors = hw_sensors.Sensors()
|
||||||
sensors_out = pathlib.Path(f'{state.log_dir}/sensors.out')
|
sensors.start_background_monitor(sensors_out)
|
||||||
sensors_thread = exe.start_thread(
|
|
||||||
sensors.monitor_to_file, args=(sensors_out,))
|
|
||||||
|
|
||||||
# Create monitor and worker panes
|
# Create monitor and worker panes
|
||||||
state.panes['Prime95'] = tmux.split_window(
|
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_info('Starting stress test')
|
||||||
std.print_warning('If running too hot, press CTRL+c to abort the test')
|
std.print_warning('If running too hot, press CTRL+c to abort the test')
|
||||||
set_apple_fan_speed('max')
|
set_apple_fan_speed('max')
|
||||||
proc_mprime = subprocess.Popen(
|
proc_mprime = start_mprime_thread(state.log_dir, prime_log)
|
||||||
['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),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Show countdown
|
# Show countdown
|
||||||
try:
|
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:
|
except KeyboardInterrupt:
|
||||||
test.set_status('Aborted')
|
test_obj.set_status('Aborted')
|
||||||
except hw_sensors.ThermalLimitReachedError:
|
except hw_sensors.ThermalLimitReachedError:
|
||||||
test.set_status('Failed')
|
test_obj.failed = True
|
||||||
test.failed = True
|
test_obj.set_status('Failed')
|
||||||
thermal_abort = True
|
|
||||||
|
|
||||||
# Stop Prime95
|
# Stop Prime95
|
||||||
proc_mprime.send_signal(signal.SIGINT)
|
proc_mprime.terminate()
|
||||||
std.sleep(1)
|
try:
|
||||||
proc_mprime.kill()
|
proc_mprime.wait(timeout=5)
|
||||||
save_thread.join()
|
except subprocess.TimeoutExpired:
|
||||||
tmux.kill_pane(state.panes.pop('Prime95', None))
|
proc_mprime.kill()
|
||||||
|
set_apple_fan_speed('auto')
|
||||||
|
|
||||||
# Get cooldown temp
|
# Get cooldown temp
|
||||||
set_apple_fan_speed('auto')
|
|
||||||
std.clear_screen()
|
std.clear_screen()
|
||||||
std.print_standard('Letting CPU cooldown...')
|
std.print_standard('Letting CPU cooldown...')
|
||||||
std.sleep(5)
|
std.sleep(5)
|
||||||
|
|
@ -378,18 +410,21 @@ def cpu_mprime_test(state, test_objects):
|
||||||
sensors.save_average_temps(temp_label='Cooldown', seconds=5)
|
sensors.save_average_temps(temp_label='Cooldown', seconds=5)
|
||||||
|
|
||||||
# Check results and build report
|
# Check results and build report
|
||||||
std.print_report(sensors.generate_report('Current', 'Idle', 'Max','Cooldown'))
|
test_obj.report.append(std.color_string('Prime95', 'BLUE'))
|
||||||
|
check_mprime_results(test_obj=test_obj, working_dir=state.log_dir)
|
||||||
# Stop sensors monitor
|
test_obj.report.append(std.color_string('Temps', 'BLUE'))
|
||||||
sensors_out.with_suffix('.stop').touch()
|
for line in sensors.generate_report(
|
||||||
sensors_thread.join()
|
'Idle', 'Max', 'Cooldown', only_cpu=True):
|
||||||
|
test_obj.report.append(f' {line}')
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
|
sensors.stop_background_monitor()
|
||||||
state.panes.pop('Current', None)
|
state.panes.pop('Current', None)
|
||||||
|
tmux.kill_pane(state.panes.pop('Prime95', None))
|
||||||
tmux.kill_pane(state.panes.pop('Temps', None))
|
tmux.kill_pane(state.panes.pop('Temps', None))
|
||||||
|
|
||||||
#TODO: p95
|
#TODO: p95
|
||||||
std.pause()
|
std.print_report(test_obj.report)
|
||||||
|
|
||||||
|
|
||||||
def disk_attribute_check(state, test_objects):
|
def disk_attribute_check(state, test_objects):
|
||||||
|
|
@ -706,5 +741,31 @@ def set_apple_fan_speed(speed):
|
||||||
exe.run_program(cmd, check=False)
|
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__':
|
if __name__ == '__main__':
|
||||||
print("This file is not meant to be called directly.")
|
print("This file is not meant to be called directly.")
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import re
|
||||||
from subprocess import CalledProcessError
|
from subprocess import CalledProcessError
|
||||||
|
|
||||||
from wk.cfg.hw import CPU_THERMAL_LIMIT, SMC_IDS, TEMP_COLORS
|
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
|
from wk.std import color_string, sleep
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -23,6 +23,7 @@ SMC_REGEX = re.compile(
|
||||||
r'\s+(?P<Value>.*?)'
|
r'\s+(?P<Value>.*?)'
|
||||||
r'\s*\(bytes (?P<Bytes>.*)\)$'
|
r'\s*\(bytes (?P<Bytes>.*)\)$'
|
||||||
)
|
)
|
||||||
|
SENSOR_SOURCE_WIDTH = 25 if platform.system() == 'Darwin' else 20
|
||||||
|
|
||||||
|
|
||||||
# Error Classes
|
# Error Classes
|
||||||
|
|
@ -34,7 +35,9 @@ class ThermalLimitReachedError(RuntimeError):
|
||||||
class Sensors():
|
class Sensors():
|
||||||
"""Class for holding sensor specific data."""
|
"""Class for holding sensor specific data."""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.background_thread = None
|
||||||
self.data = get_sensor_data()
|
self.data = get_sensor_data()
|
||||||
|
self.out_path = None
|
||||||
|
|
||||||
def clear_temps(self):
|
def clear_temps(self):
|
||||||
"""Clear saved temps but keep structure"""
|
"""Clear saved temps but keep structure"""
|
||||||
|
|
@ -55,7 +58,7 @@ class Sensors():
|
||||||
for adapter, sources in sorted(adapters.items()):
|
for adapter, sources in sorted(adapters.items()):
|
||||||
report.append(fix_sensor_name(adapter))
|
report.append(fix_sensor_name(adapter))
|
||||||
for source, source_data in sorted(sources.items()):
|
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:
|
for label in temp_labels:
|
||||||
if label != 'Current':
|
if label != 'Current':
|
||||||
line += f' {label.lower()}: '
|
line += f' {label.lower()}: '
|
||||||
|
|
@ -78,12 +81,16 @@ class Sensors():
|
||||||
# Done
|
# Done
|
||||||
return report
|
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."""
|
"""Write report to path every second until stopped."""
|
||||||
stop_path = pathlib.Path(out_path).resolve().with_suffix('.stop')
|
stop_path = pathlib.Path(out_path).resolve().with_suffix('.stop')
|
||||||
|
if not temp_labels:
|
||||||
|
temp_labels = ('Current', 'Max')
|
||||||
|
|
||||||
|
# Start loop
|
||||||
while True:
|
while True:
|
||||||
self.update_sensor_data()
|
self.update_sensor_data()
|
||||||
report = self.generate_report('Current', 'Max')
|
report = self.generate_report(*temp_labels)
|
||||||
with open(out_path, 'w') as _f:
|
with open(out_path, 'w') as _f:
|
||||||
_f.write('\n'.join(report))
|
_f.write('\n'.join(report))
|
||||||
|
|
||||||
|
|
@ -111,6 +118,26 @@ class Sensors():
|
||||||
temps = source_data['Temps']
|
temps = source_data['Temps']
|
||||||
source_data[temp_label] = sum(temps) / len(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):
|
def update_sensor_data(self, exit_on_thermal_limit=True):
|
||||||
"""Update sensor data via OS-specific means."""
|
"""Update sensor data via OS-specific means."""
|
||||||
if platform.system() == 'Darwin':
|
if platform.system() == 'Darwin':
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue