Skip to content

Commit 610c0d3

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

File tree

3 files changed

+285
-0
lines changed

3 files changed

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