Skip to content

Commit c6efd4e

Browse files
committed
qemu_guest_agent: Add new api support 'guest-get-load'
Add a new guest agent command 'guest-get-load' to get cpu load average info of Guest. Signed-off-by: Dehan Meng <[email protected]>
1 parent 870c1ea commit c6efd4e

File tree

2 files changed

+175
-8
lines changed

2 files changed

+175
-8
lines changed

qemu/tests/cfg/qemu_guest_agent.cfg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,13 @@
666666
remove_image_image1 = yes
667667
cmd_run_debugview = 'start WIN_UTILS:\Debugviewconsole.exe -d C:\debug.dbglog'
668668
cmd_check_string_VSS = 'type C:\debug.dbglog | findstr /i "QEMU Guest Agent VSS Provider\[[0-9]*\]"'
669+
- check_get_load:
670+
gagent_check_type = get_load
671+
cmd_get_load = "cat /proc/loadavg |awk '{print $1,$2,$3}'"
672+
cmd_install_stressng = "dnf -y install stress-ng"
673+
cmd_run_stress = "stress-ng --cpu 8 --cpu-load 80 --timeout 30 --quiet &"
674+
Windows:
675+
cmd_run_stress = powershell "1..12 | ForEach-Object { Start-Job -ScriptBlock { while ($true) { [math]::sqrt(999999) } } }"
669676
variants:
670677
- virtio_serial:
671678
gagent_serial_type = virtio

qemu/tests/qemu_guest_agent.py

Lines changed: 168 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,7 +1214,7 @@ def gagent_check_fstrim(self, test, params, env):
12141214
Execute "guest-fstrim" command to guest agent
12151215
:param test: kvm test object
12161216
:param params: Dictionary with the test parameters
1217-
:param env: Dictionary with test environment.
1217+
:param env: Dictionary with the test environment.
12181218
12191219
"""
12201220

@@ -2072,6 +2072,166 @@ def parse_ipv6_route(route_output):
20722072
if session:
20732073
session.close()
20742074

2075+
@error_context.context_aware
2076+
def gagent_check_get_load(self, test, params, env):
2077+
"""
2078+
Test guest-get-load command functionality.
2079+
2080+
Steps:
2081+
1) Get initial load values and verify qga/guest match
2082+
2) Start stress test and verify load increases
2083+
3) Stop stress test and verify load decreases
2084+
2085+
:param test: kvm test object
2086+
:param params: Dictionary with test parameters
2087+
:param env: Dictionary with test environment
2088+
"""
2089+
2090+
def _get_load_stats(session, get_guest=True):
2091+
"""
2092+
Get load statistics from either guest OS or QGA.
2093+
Returns tuple of (1min, 5min, 15min) load values.
2094+
"""
2095+
if get_guest:
2096+
try:
2097+
loads = session.cmd_output(params["cmd_get_load"]).strip().split()
2098+
return tuple(round(float(x), 2) for x in loads[:3])
2099+
except (IndexError, ValueError) as e:
2100+
test.error(f"Failed to get guest load stats: {e}")
2101+
else:
2102+
try:
2103+
loads = self.gagent.get_load()
2104+
load_keys = ("load1m", "load5m", "load15m")
2105+
return tuple(round(float(loads[k]), 2) for k in load_keys)
2106+
except (KeyError, ValueError) as e:
2107+
test.error(f"Failed to get QGA load stats: {e}")
2108+
2109+
def _verify_load_values(qga_vals, guest_vals, check_type="match"):
2110+
"""
2111+
Compare load values between QGA and guest OS.
2112+
Also verifies if values changed as expected.
2113+
"""
2114+
errors = []
2115+
periods = ["1-minute", "5-minute", "15-minute"]
2116+
2117+
for period, qga, guest in zip(periods, qga_vals, guest_vals):
2118+
if abs(qga - guest) > 0.5:
2119+
errors.append(
2120+
f"{period} load mismatch: guest={guest:.2f}, qga={qga:.2f}"
2121+
)
2122+
2123+
# Only check load1m for increase/decrease
2124+
if check_type != "match" and prev_values:
2125+
qga_1m = qga_vals[0]
2126+
guest_1m = guest_vals[0]
2127+
prev_qga_1m = prev_values["qga"][0]
2128+
prev_guest_1m = prev_values["guest"][0]
2129+
2130+
if check_type == "increase":
2131+
if qga_1m <= prev_qga_1m or guest_1m <= prev_guest_1m:
2132+
errors.append(
2133+
"1-minute load did not increase as expected:\n"
2134+
f"QGA: {prev_qga_1m:.2f} -> {qga_1m:.2f}\n"
2135+
f"Guest: {prev_guest_1m:.2f} -> {guest_1m:.2f}"
2136+
)
2137+
elif check_type == "decrease":
2138+
if qga_1m >= prev_qga_1m or guest_1m >= prev_guest_1m:
2139+
errors.append(
2140+
"1-minute load did not decrease as expected:\n"
2141+
f"QGA: {prev_qga_1m:.2f} -> {qga_1m:.2f}\n"
2142+
f"Guest: {prev_guest_1m:.2f} -> {guest_1m:.2f}"
2143+
)
2144+
2145+
return errors
2146+
2147+
def _log_load_values(guest_vals, qga_vals, phase):
2148+
"""Log load values in a consistent format"""
2149+
LOG_JOB.info(
2150+
"%s load averages:\nGuest OS: %s\nQGA: %s",
2151+
phase,
2152+
[f"{x:.2f}" for x in guest_vals],
2153+
[f"{x:.2f}" for x in qga_vals],
2154+
)
2155+
2156+
session = self._get_session(params, self.vm)
2157+
self._open_session_list.append(session)
2158+
prev_values = None
2159+
2160+
if params.get("os_type") == "windows":
2161+
error_context.context("Get load info for Windows", LOG_JOB.info)
2162+
try:
2163+
# Get initial load values
2164+
load_info = self.gagent.get_load()
2165+
# Check if all required fields exist
2166+
for key in ["load1m", "load5m", "load15m"]:
2167+
if key not in load_info:
2168+
test.fail(f"Missing {key} in guest-get-load return value")
2169+
initial_load = load_info["load1m"]
2170+
LOG_JOB.info("Initial load info from guest-agent: %s", load_info)
2171+
2172+
# Start CPU stress test
2173+
error_context.context("Start CPU stress test", LOG_JOB.info)
2174+
session.cmd(params["cmd_run_stress"])
2175+
time.sleep(10)
2176+
2177+
# Get load values after stress
2178+
load_info = self.gagent.get_load()
2179+
stress_load = load_info["load1m"]
2180+
LOG_JOB.info("Load info after stress: %s", load_info)
2181+
2182+
# Verify load value changed
2183+
if stress_load <= initial_load:
2184+
test.fail(
2185+
f"Load value did not increase after CPU stress:"
2186+
f" before={initial_load}, after={stress_load}"
2187+
)
2188+
LOG_JOB.info(
2189+
"Load value increased as expected: before=%s, after=%s",
2190+
initial_load,
2191+
stress_load,
2192+
)
2193+
except guest_agent.VAgentCmdError as e:
2194+
test.fail(f"guest-get-load command failed: {e}")
2195+
else:
2196+
# Initial load check
2197+
error_context.context("Check initial load average info", LOG_JOB.info)
2198+
guest_vals = _get_load_stats(session)
2199+
qga_vals = _get_load_stats(session, False)
2200+
prev_values = {"guest": guest_vals, "qga": qga_vals}
2201+
2202+
_log_load_values(guest_vals, qga_vals, "Initial")
2203+
2204+
if errors := _verify_load_values(qga_vals, guest_vals):
2205+
test.fail("Initial load check failed:\n" + "\n".join(errors))
2206+
2207+
# Stress test
2208+
error_context.context("Starting CPU stress test", LOG_JOB.info)
2209+
s, o = session.cmd_status_output(params["cmd_install_stressng"])
2210+
if s != 0:
2211+
test.error(f"Failed to install stress-ng: {o}")
2212+
session.cmd(params["cmd_run_stress"])
2213+
time.sleep(25)
2214+
2215+
guest_vals = _get_load_stats(session)
2216+
qga_vals = _get_load_stats(session, False)
2217+
2218+
_log_load_values(guest_vals, qga_vals, "Under stress")
2219+
2220+
if errors := _verify_load_values(qga_vals, guest_vals, "increase"):
2221+
test.fail("Stress test load check failed:\n" + "\n".join(errors))
2222+
2223+
prev_values = {"guest": guest_vals, "qga": qga_vals}
2224+
2225+
# sleep (60) wait for the stress-ng terminated.
2226+
time.sleep(60)
2227+
guest_vals = _get_load_stats(session)
2228+
qga_vals = _get_load_stats(session, False)
2229+
2230+
_log_load_values(guest_vals, qga_vals, "After stress")
2231+
2232+
if errors := _verify_load_values(qga_vals, guest_vals, "decrease"):
2233+
test.fail("Post-stress load check failed:\n" + "\n".join(errors))
2234+
20752235
@error_context.context_aware
20762236
def gagent_check_reboot_shutdown(self, test, params, env):
20772237
"""
@@ -2273,7 +2433,7 @@ def gagent_check_file_write(self, test, params, env):
22732433
22742434
:param test: kvm test object
22752435
:param params: Dictionary with the test parameters
2276-
:param env: Dictionary with test environment.
2436+
:param env: Dictionary with the test environment.
22772437
"""
22782438
error_context.context(
22792439
"Change guest-file related cmd to white list and get guest file name."
@@ -2894,7 +3054,7 @@ def gagent_check_fsfreeze(self, test, params, env):
28943054
28953055
:param test: kvm test object
28963056
:param params: Dictionary with the test parameters
2897-
:param env: Dictionary with test environmen.
3057+
:param env: Dictionary with the test environmen.
28983058
"""
28993059
session = self._get_session(params, self.vm)
29003060
self._open_session_list.append(session)
@@ -2926,7 +3086,7 @@ def gagent_check_fsfreeze_list(self, test, params, env):
29263086
29273087
:param test: kvm test object
29283088
:param params: Dictionary with the test parameters
2929-
:param env: Dictionary with test environmen.
3089+
:param env: Dictionary with the test environmen.
29303090
"""
29313091
session = self._get_session(params, self.vm)
29323092
self._open_session_list.append(session)
@@ -3019,7 +3179,7 @@ def gagent_check_thaw_unfrozen(self, test, params, env):
30193179
30203180
:param test: kvm test object
30213181
:param params: Dictionary with the test parameters
3022-
:param env: Dictionary with test environment.
3182+
:param env: Dictionary with the test environment
30233183
"""
30243184
error_context.context("Verify if FS is thawed", LOG_JOB.info)
30253185
expect_status = self.gagent.FSFREEZE_STATUS_THAWED
@@ -3041,7 +3201,7 @@ def gagent_check_freeze_frozen(self, test, params, env):
30413201
30423202
:param test: kvm test object
30433203
:param params: Dictionary with the test parameters
3044-
:param env: Dictionary with test environment.
3204+
:param env: Dictionary with the test environment
30453205
"""
30463206
# Since Qemu9.1, the report message changes.
30473207
qga_ver = self.gagent.guest_info()["version"].split(".")
@@ -3498,7 +3658,7 @@ def gagent_check_vss_status(self, test, params, env):
34983658
34993659
:param test: kvm test object
35003660
:param params: Dictionary with the test parameters
3501-
:param env: Dictionary with test environment
3661+
:param env: Dictionary with test environment.
35023662
"""
35033663

35043664
def check_vss_info(cmd_type, key, expect_value):
@@ -4867,7 +5027,7 @@ def gagent_check_resource_leak(self, test, params, env):
48675027
48685028
:param test: kvm test object
48695029
:param params: Dictionary with the test parameters
4870-
:param env: Dictionary with test environment.
5030+
:param env: Dictionary with the test environment.
48715031
"""
48725032

48735033
def execute_qga_cmds_loop():

0 commit comments

Comments
 (0)