Skip to content

Commit 498b42a

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 0874a20 commit 498b42a

File tree

2 files changed

+166
-0
lines changed

2 files changed

+166
-0
lines changed

qemu/tests/cfg/qemu_guest_agent.cfg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,13 @@
655655
remove_image_image1 = yes
656656
cmd_run_debugview = 'start WIN_UTILS:\Debugviewconsole.exe -d C:\debug.dbglog'
657657
cmd_check_string_VSS = 'type C:\debug.dbglog | findstr /i "QEMU Guest Agent VSS Provider\[[0-9]*\]"'
658+
- check_get_load:
659+
gagent_check_type = get_load
660+
cmd_get_load = "cat /proc/loadavg |awk '{print $1,$2,$3}'"
661+
cmd_install_stressng = "dnf -y install stress-ng"
662+
cmd_run_stress = "stress-ng --cpu 8 --cpu-load 80 --timeout 30 --quiet &"
663+
Windows:
664+
cmd_run_stress = powershell.exe "1..4 | ForEach-Object { Start-Job -ScriptBlock { while ($true) { [math]::sqrt(999999) } } }"
658665
variants:
659666
- virtio_serial:
660667
gagent_serial_type = virtio

qemu/tests/qemu_guest_agent.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1850,6 +1850,165 @@ def ip_addr_check(session, mac_addr, ret_list, if_index, if_name):
18501850
if session_serial:
18511851
session_serial.close()
18521852

1853+
@error_context.context_aware
1854+
def gagent_check_get_load(self, test, params, env):
1855+
"""
1856+
Test guest-get-load command functionality.
1857+
1858+
Steps:
1859+
1) Get initial load values and verify qga/guest match
1860+
2) Start stress test and verify load increases
1861+
3) Stop stress test and verify load decreases
1862+
1863+
:param test: kvm test object
1864+
:param params: Dictionary with test parameters
1865+
:param env: Dictionary with test environment
1866+
"""
1867+
1868+
def _get_load_stats(session, get_guest=True):
1869+
"""
1870+
Get load statistics from either guest OS or QGA.
1871+
Returns tuple of (1min, 5min, 15min) load values.
1872+
"""
1873+
if get_guest:
1874+
try:
1875+
loads = session.cmd_output(params["cmd_get_load"]).strip().split()
1876+
return tuple(round(float(x), 2) for x in loads[:3])
1877+
except (IndexError, ValueError) as e:
1878+
test.error(f"Failed to get guest load stats: {e}")
1879+
else:
1880+
try:
1881+
loads = self.gagent.get_load()
1882+
load_keys = ("load1m", "load5m", "load15m")
1883+
return tuple(round(float(loads[k]), 2) for k in load_keys)
1884+
except (KeyError, ValueError) as e:
1885+
test.error(f"Failed to get QGA load stats: {e}")
1886+
1887+
def _verify_load_values(qga_vals, guest_vals, check_type="match"):
1888+
"""
1889+
Compare load values between QGA and guest OS.
1890+
Also verifies if values changed as expected.
1891+
"""
1892+
errors = []
1893+
periods = ["1-minute", "5-minute", "15-minute"]
1894+
1895+
for period, qga, guest in zip(periods, qga_vals, guest_vals):
1896+
if abs(qga - guest) > 0.5:
1897+
errors.append(
1898+
f"{period} load mismatch: guest={guest:.2f}, qga={qga:.2f}"
1899+
)
1900+
1901+
# Only check load1m for increase/decrease
1902+
if check_type != "match" and prev_values:
1903+
qga_1m = qga_vals[0]
1904+
guest_1m = guest_vals[0]
1905+
prev_qga_1m = prev_values["qga"][0]
1906+
prev_guest_1m = prev_values["guest"][0]
1907+
1908+
if check_type == "increase":
1909+
if qga_1m <= prev_qga_1m or guest_1m <= prev_guest_1m:
1910+
errors.append(
1911+
"1-minute load did not increase as expected:\n"
1912+
f"QGA: {prev_qga_1m:.2f} -> {qga_1m:.2f}\n"
1913+
f"Guest: {prev_guest_1m:.2f} -> {guest_1m:.2f}"
1914+
)
1915+
elif check_type == "decrease":
1916+
if qga_1m >= prev_qga_1m or guest_1m >= prev_guest_1m:
1917+
errors.append(
1918+
"1-minute load did not decrease as expected:\n"
1919+
f"QGA: {prev_qga_1m:.2f} -> {qga_1m:.2f}\n"
1920+
f"Guest: {prev_guest_1m:.2f} -> {guest_1m:.2f}"
1921+
)
1922+
1923+
return errors
1924+
1925+
def _log_load_values(guest_vals, qga_vals, phase):
1926+
"""Log load values in a consistent format"""
1927+
LOG_JOB.info(
1928+
"%s load averages:\nGuest OS: %s\nQGA: %s",
1929+
phase,
1930+
[f"{x:.2f}" for x in guest_vals],
1931+
[f"{x:.2f}" for x in qga_vals],
1932+
)
1933+
1934+
session = self._get_session(params, self.vm)
1935+
self._open_session_list.append(session)
1936+
prev_values = None
1937+
1938+
if params.get("os_type") == "windows":
1939+
error_context.context("Get load info via guest-agent for Windows", LOG_JOB.info)
1940+
try:
1941+
# Get initial load values
1942+
load_info = self.gagent.get_load()
1943+
# Check if all required fields exist
1944+
for key in ["load1m", "load5m", "load15m"]:
1945+
if key not in load_info:
1946+
test.fail(f"Missing {key} in guest-get-load return value")
1947+
initial_load = load_info["load1m"]
1948+
LOG_JOB.info("Initial load info from guest-agent: %s", load_info)
1949+
1950+
# Start CPU stress test
1951+
error_context.context("Start CPU stress test", LOG_JOB.info)
1952+
session.cmd(params["cmd_run_stress"])
1953+
time.sleep(10)
1954+
1955+
# Get load values after stress
1956+
load_info = self.gagent.get_load()
1957+
stress_load = load_info["load1m"]
1958+
LOG_JOB.info("Load info after stress: %s", load_info)
1959+
1960+
# Verify load value changed
1961+
if stress_load <= initial_load:
1962+
test.fail(
1963+
f"Load value did not increase after CPU stress:"
1964+
f" before={initial_load}, after={stress_load}"
1965+
)
1966+
LOG_JOB.info(
1967+
"Load value increased as expected:"
1968+
" before=%s, after=%s", initial_load, stress_load
1969+
)
1970+
except guest_agent.VAgentCmdError as e:
1971+
test.fail(f"guest-get-load command failed: {e}")
1972+
else:
1973+
# Initial load check
1974+
error_context.context("Check initial load average info", LOG_JOB.info)
1975+
guest_vals = _get_load_stats(session)
1976+
qga_vals = _get_load_stats(session, False)
1977+
prev_values = {"guest": guest_vals, "qga": qga_vals}
1978+
1979+
_log_load_values(guest_vals, qga_vals, "Initial")
1980+
1981+
if errors := _verify_load_values(qga_vals, guest_vals):
1982+
test.fail("Initial load check failed:\n" + "\n".join(errors))
1983+
1984+
# Stress test
1985+
error_context.context("Starting CPU stress test", LOG_JOB.info)
1986+
s, o = session.cmd_status_output(params["cmd_install_stressng"])
1987+
if s != 0:
1988+
test.error(f"Failed to install stress-ng: {o}")
1989+
session.cmd(params["cmd_run_stress"])
1990+
time.sleep(25)
1991+
1992+
guest_vals = _get_load_stats(session)
1993+
qga_vals = _get_load_stats(session, False)
1994+
1995+
_log_load_values(guest_vals, qga_vals, "Under stress")
1996+
1997+
if errors := _verify_load_values(qga_vals, guest_vals, "increase"):
1998+
test.fail("Stress test load check failed:\n" + "\n".join(errors))
1999+
2000+
prev_values = {"guest": guest_vals, "qga": qga_vals}
2001+
2002+
# sleep (60) wait for the stress-ng terminated.
2003+
time.sleep(60)
2004+
guest_vals = _get_load_stats(session)
2005+
qga_vals = _get_load_stats(session, False)
2006+
2007+
_log_load_values(guest_vals, qga_vals, "After stress")
2008+
2009+
if errors := _verify_load_values(qga_vals, guest_vals, "decrease"):
2010+
test.fail("Post-stress load check failed:\n" + "\n".join(errors))
2011+
18532012
@error_context.context_aware
18542013
def gagent_check_reboot_shutdown(self, test, params, env):
18552014
"""

0 commit comments

Comments
 (0)