Skip to content

Commit 3f5fd5a

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

File tree

3 files changed

+269
-0
lines changed

3 files changed

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