Skip to content

Commit f2fb5e4

Browse files
committed
Add thermald adaptive engine startup test (New)
1 parent d2ddde9 commit f2fb5e4

File tree

3 files changed

+256
-0
lines changed

3 files changed

+256
-0
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
#!/usr/bin/env python3
2+
import subprocess
3+
import signal
4+
import os
5+
import logging
6+
7+
# Set up logging
8+
logging.basicConfig(
9+
level=logging.INFO,
10+
format='%(asctime)s - %(levelname)s - %(message)s',
11+
datefmt='%Y-%m-%d %H:%M:%S'
12+
)
13+
14+
# --- Configuration ---
15+
THERMALD_CMD = ["thermald", "--no-daemon", "--loglevel=debug", "--adaptive"]
16+
17+
# Patterns to search for (as bytes).
18+
FAILURE_PATTERN = b"Also unable to evaluate any conditions"
19+
SUCCESS_PATTERN = b"Start main loop"
20+
21+
# The grep command will search for either pattern, stop after the first match (-m 1),
22+
# and flush its output immediately (--line-buffered).
23+
GREP_CMD = [
24+
"grep",
25+
"--line-buffered",
26+
"-m", "1",
27+
"-e", FAILURE_PATTERN.decode('utf-8'),
28+
"-e", SUCCESS_PATTERN.decode('utf-8'),
29+
]
30+
31+
TIMEOUT_SECONDS = 15
32+
33+
34+
def stop_thermald_service():
35+
"""
36+
Stop the thermald systemd service if it's running.
37+
Returns True if service was running and stopped, False if it wasn't running.
38+
"""
39+
logging.info("--- Checking thermald systemd service ---")
40+
41+
try:
42+
# Check if thermald service is active
43+
result = subprocess.run(
44+
["systemctl", "is-active", "thermald"],
45+
capture_output=True,
46+
text=True
47+
)
48+
49+
if result.returncode == 0 and result.stdout.strip() == "active":
50+
logging.info("Thermald service is running. Stopping it...")
51+
stop_result = subprocess.run(
52+
["systemctl", "stop", "thermald"],
53+
capture_output=True,
54+
text=True
55+
)
56+
57+
if stop_result.returncode == 0:
58+
logging.info("Thermald service stopped successfully.")
59+
return True
60+
else:
61+
logging.error(f"Failed to stop thermald service: {stop_result.stderr}")
62+
return False
63+
else:
64+
logging.info("Thermald service is not running.")
65+
return False
66+
67+
except Exception as e:
68+
logging.error(f"Error checking/stopping thermald service: {e}")
69+
return False
70+
71+
72+
def start_thermald_service():
73+
"""
74+
Start the thermald systemd service.
75+
"""
76+
logging.info("--- Restarting thermald systemd service ---")
77+
78+
try:
79+
result = subprocess.run(
80+
["systemctl", "start", "thermald"],
81+
capture_output=True,
82+
text=True
83+
)
84+
85+
if result.returncode == 0:
86+
logging.info("Thermald service restarted successfully.")
87+
else:
88+
logging.error(f"Failed to restart thermald service: {result.stderr}")
89+
90+
except Exception as e:
91+
logging.error(f"Error restarting thermald service: {e}")
92+
93+
94+
def run_thermald_grep_test():
95+
"""
96+
Test thermald's adaptive engine startup by monitoring its output.
97+
98+
This test launches thermald with adaptive engine and uses grep to detect
99+
which pattern appears first:
100+
- SUCCESS: "Start main loop" - adaptive engine started correctly
101+
- FAILURE: "Also unable to evaluate any conditions" - adaptive engine failed,
102+
thermald falls back to highest power profile
103+
104+
The test captures the first occurrence of either pattern to determine if
105+
the adaptive engine initialization was successful.
106+
"""
107+
logging.info("=== THERMALD ADAPTIVE ENGINE TEST ===")
108+
logging.info("This test verifies that thermald's adaptive engine starts correctly.")
109+
logging.info("Monitoring thermald output for adaptive engine initialization patterns...")
110+
logging.info(f"Success pattern: '{SUCCESS_PATTERN.decode('utf-8')}'")
111+
logging.info(f"Failure pattern: '{FAILURE_PATTERN.decode('utf-8')}'")
112+
logging.info(f"Command: {' '.join(THERMALD_CMD)}")
113+
logging.info(f"Timeout: {TIMEOUT_SECONDS} seconds")
114+
logging.info("=" * 70)
115+
116+
if os.geteuid() != 0:
117+
logging.error("\nERROR: This script requires sudo privileges.")
118+
logging.error("Please run it with: sudo python3 test_thermald.py")
119+
raise SystemExit(1)
120+
121+
# Stop thermald service if it's running
122+
service_was_running = stop_thermald_service()
123+
124+
thermald_proc = None
125+
grep_proc = None
126+
result_message = ""
127+
exit_code = 1 # Default to failure
128+
129+
try:
130+
# 1. Start the thermald process.
131+
# - Redirect stderr to stdout so grep sees all output.
132+
# - preexec_fn=os.setsid creates a new process group for robust cleanup.
133+
thermald_proc = subprocess.Popen(
134+
THERMALD_CMD,
135+
stdout=subprocess.PIPE,
136+
stderr=subprocess.STDOUT,
137+
preexec_fn=os.setsid
138+
)
139+
logging.info(f"Started thermald with adaptive engine (PGID: {thermald_proc.pid})")
140+
141+
# 2. Start the grep process, piping thermald's output to its input.
142+
grep_proc = subprocess.Popen(
143+
GREP_CMD,
144+
stdin=thermald_proc.stdout,
145+
stdout=subprocess.PIPE
146+
)
147+
logging.info(f"Started pattern monitoring (grep PID: {grep_proc.pid})")
148+
logging.info("Waiting for adaptive engine initialization patterns...")
149+
150+
# 3. Allow thermald's stdout pipe to be closed. This is VERY important.
151+
# It prevents thermald from hanging if grep exits before thermald does.
152+
thermald_proc.stdout.close()
153+
154+
# 4. Wait for grep to finish or for the timeout to expire.
155+
# communicate() reads the output and waits for the process.
156+
try:
157+
grep_output, _ = grep_proc.communicate(timeout=TIMEOUT_SECONDS)
158+
159+
# 5. Analyze grep's output to determine the result.
160+
if FAILURE_PATTERN in grep_output:
161+
result_message = ("FAILURE: Thermald adaptive engine failed to initialize.\n"
162+
"Found failure pattern first - thermald fell back to highest power profile.")
163+
exit_code = 1
164+
elif SUCCESS_PATTERN in grep_output:
165+
result_message = ("SUCCESS: Thermald adaptive engine started correctly.\n"
166+
"Found success pattern - adaptive engine is active.")
167+
exit_code = 0
168+
else:
169+
# This case means thermald exited before grep found anything.
170+
result_message = ("FAILURE: Thermald process exited unexpectedly.\n"
171+
"No adaptive engine patterns detected in output.")
172+
exit_code = 1
173+
174+
except subprocess.TimeoutExpired:
175+
result_message = (f"FAILURE: Test timed out after {TIMEOUT_SECONDS} seconds.\n"
176+
"Thermald may be stuck or taking too long to initialize adaptive engine.")
177+
exit_code = 1
178+
179+
except FileNotFoundError as e:
180+
logging.error(f"\nERROR: Command not found: '{e.filename}'. Is it installed?")
181+
raise SystemExit(1)
182+
except Exception as e:
183+
result_message = (f"FAILURE: An unexpected error occurred during the test: {e}\n"
184+
"This may indicate system configuration issues.")
185+
exit_code = 1
186+
finally:
187+
# --- Graceful Cleanup ---
188+
logging.info("--- Cleaning up test processes ---")
189+
# First, ensure the grep process is gone
190+
if grep_proc and grep_proc.poll() is None:
191+
logging.info(f"Terminating leftover grep process {grep_proc.pid}...")
192+
grep_proc.terminate()
193+
grep_proc.wait(timeout=2)
194+
if grep_proc.poll() is None:
195+
grep_proc.kill()
196+
197+
# Then, clean up the main thermald process and its children
198+
if thermald_proc and thermald_proc.poll() is None:
199+
logging.info(f"Sending SIGTERM to thermald process group {thermald_proc.pid}...")
200+
try:
201+
os.killpg(thermald_proc.pid, signal.SIGTERM)
202+
thermald_proc.wait(timeout=5)
203+
logging.info("Thermald process group terminated gracefully.")
204+
except ProcessLookupError:
205+
logging.info("Thermald process group already gone.")
206+
except subprocess.TimeoutExpired:
207+
logging.warning("Thermald did not respond to SIGTERM. Sending SIGKILL...")
208+
os.killpg(thermald_proc.pid, signal.SIGKILL)
209+
logging.info("Thermald process group killed.")
210+
else:
211+
logging.info("Thermald process already terminated.")
212+
213+
# Restart thermald service if it was originally running
214+
if service_was_running:
215+
start_thermald_service()
216+
217+
logging.info("=" * 70)
218+
logging.info("=== THERMALD ADAPTIVE ENGINE TEST RESULT ===")
219+
logging.info(result_message)
220+
if exit_code == 0:
221+
logging.info("PASSED: Thermald adaptive engine is working correctly.")
222+
else:
223+
logging.info("FAILED: Thermald adaptive engine did not start properly.")
224+
logging.info("=" * 70)
225+
return exit_code
226+
227+
228+
def main():
229+
test_result = run_thermald_grep_test()
230+
raise SystemExit(test_result)
231+
232+
233+
if __name__ == "__main__":
234+
main()

contrib/pc-sanity/units/pc-sanity/pc-sanity-thermal.pxu

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,24 @@ requires:
286286
package.name == 'systemd'
287287
depends: miscellanea/thermald
288288
command: check-thermald-unknown-cond.sh
289+
290+
plugin: shell
291+
category_id: com.canonical.plainbox::miscellanea
292+
id: miscellanea/check-thermald-adaptive-engine
293+
user: root
294+
_summary: Test thermald's adaptive engine startup by monitoring its output.
295+
_description:
296+
This test launches thermald with adaptive engine and uses grep to detect
297+
which pattern appears first:
298+
- SUCCESS: "Start main loop" - adaptive engine started correctly
299+
- FAILURE: "Also unable to evaluate any conditions" - adaptive engine failed,
300+
thermald falls back to highest power profile
301+
The test captures the first occurrence of either pattern to determine if
302+
the adaptive engine initialization was successful.
303+
estimated_duration: 0.5
304+
requires:
305+
cpuinfo.type == 'GenuineIntel'
306+
package.name == 'systemd'
307+
depends: miscellanea/thermald
308+
command: thermald_adaptive_engine_test.py
309+

contrib/pc-sanity/units/pc-sanity/pc-sanity.pxu

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ include:
3535
com.canonical.certification::miscellanea/thermald
3636
com.canonical.certification::miscellanea/thermal-policy-set_.*
3737
com.canonical.certification::miscellanea/check-thermald-unknown-cond
38+
com.canonical.certification::miscellanea/check-thermald-adaptive-engine
3839
com.canonical.certification::miscellanea/dump_libsmbios_tokens
3940
com.canonical.certification::miscellanea/dump_libsmbios_tokens_attachment
4041
com.canonical.certification::miscellanea/check-i2c-hid-existence

0 commit comments

Comments
 (0)