Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions qemu/tests/cfg/qemu_guest_agent.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,13 @@
remove_image_image1 = yes
cmd_run_debugview = 'start WIN_UTILS:\Debugviewconsole.exe -d C:\debug.dbglog'
cmd_check_string_VSS = 'type C:\debug.dbglog | findstr /i "QEMU Guest Agent VSS Provider\[[0-9]*\]"'
- check_get_load:
gagent_check_type = get_load
cmd_get_load = "cat /proc/loadavg |awk '{print $1,$2,$3}'"
cmd_install_stressng = "dnf -y install stress-ng"
cmd_run_stress = "stress-ng --cpu 8 --cpu-load 80 --timeout 30 --quiet &"
Windows:
cmd_run_stress = powershell "1..12 | ForEach-Object { Start-Job -ScriptBlock { while ($true) { [math]::sqrt(999999) } } }"
variants:
- virtio_serial:
gagent_serial_type = virtio
Expand Down
181 changes: 172 additions & 9 deletions qemu/tests/qemu_guest_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -1214,7 +1214,7 @@ def gagent_check_fstrim(self, test, params, env):
Execute "guest-fstrim" command to guest agent
:param test: kvm test object
:param params: Dictionary with the test parameters
:param env: Dictionary with test environment.
:param env: Dictionary with the test environment.

"""

Expand Down Expand Up @@ -2072,6 +2072,167 @@ def parse_ipv6_route(route_output):
if session:
session.close()

@error_context.context_aware
def gagent_check_get_load(self, test, params, env):
"""
Test guest-get-load command functionality.

Steps:
1) Get initial load values and verify qga/guest match
2) Start stress test and verify load increases
3) Stop stress test and verify load decreases

:param test: kvm test object
:param params: Dictionary with test parameters
:param env: Dictionary with test environment
"""

def _get_load_stats(session, get_guest=True):
"""
Get load statistics from either guest OS or QGA.
Returns tuple of (1min, 5min, 15min) load values.
"""
if get_guest:
try:
loads = session.cmd_output(params["cmd_get_load"]).strip().split()
return tuple(round(float(x), 2) for x in loads[:3])
except (IndexError, ValueError) as e:
test.error(f"Failed to get guest load stats: {e}")
else:
try:
loads = self.gagent.get_load()
load_keys = ("load1m", "load5m", "load15m")
return tuple(round(float(loads[k]), 2) for k in load_keys)
except (KeyError, ValueError) as e:
test.error(f"Failed to get QGA load stats: {e}")

def _verify_load_values(qga_vals, guest_vals, check_type="match",
prev_values=None):
"""
Compare load values between QGA and guest OS.
Also verifies if values changed as expected.
"""
errors = []
periods = ["1-minute", "5-minute", "15-minute"]

for period, qga, guest in zip(periods, qga_vals, guest_vals):
if abs(qga - guest) > 0.5:
errors.append(
f"{period} load mismatch: guest={guest:.2f}, qga={qga:.2f}"
)

# Only check load1m for increase/decrease
if check_type != "match" and prev_values:
qga_1m = qga_vals[0]
guest_1m = guest_vals[0]
prev_qga_1m = prev_values["qga"][0]
prev_guest_1m = prev_values["guest"][0]

if check_type == "increase":
if qga_1m <= prev_qga_1m or guest_1m <= prev_guest_1m:
errors.append(
"1-minute load did not increase as expected:\n"
f"QGA: {prev_qga_1m:.2f} -> {qga_1m:.2f}\n"
f"Guest: {prev_guest_1m:.2f} -> {guest_1m:.2f}"
)
elif check_type == "decrease":
if qga_1m >= prev_qga_1m or guest_1m >= prev_guest_1m:
errors.append(
"1-minute load did not decrease as expected:\n"
f"QGA: {prev_qga_1m:.2f} -> {qga_1m:.2f}\n"
f"Guest: {prev_guest_1m:.2f} -> {guest_1m:.2f}"
)

return errors

def _log_load_values(guest_vals, qga_vals, phase):
"""Log load values in a consistent format"""
LOG_JOB.info(
"%s load averages:\nGuest OS: %s\nQGA: %s",
phase,
[f"{x:.2f}" for x in guest_vals],
[f"{x:.2f}" for x in qga_vals],
)

session = self._get_session(params, self.vm)
self._open_session_list.append(session)
prev_values = None

if params.get("os_type") == "windows":
error_context.context("Get load info for Windows", LOG_JOB.info)
try:
# Get initial load values
load_info = self.gagent.get_load()
# Check if all required fields exist
for key in ["load1m", "load5m", "load15m"]:
if key not in load_info:
test.fail(f"Missing {key} in guest-get-load return value")
initial_load = load_info["load1m"]
LOG_JOB.info("Initial load info from guest-agent: %s", load_info)

# Start CPU stress test
error_context.context("Start CPU stress test", LOG_JOB.info)
session.cmd(params["cmd_run_stress"])
time.sleep(10)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @6-dehan
I saw you added some time.sleep to wait for the process to start or terminate, I'm worried it will introduce some failures in the future, is it possible to check the process status directly instead of time.sleep?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @leidwang
I tried to detect the running status, but it is not enough to just have the running status. In the end, we still need to run the stress command for a while to let the stress value rise to meet our expectations. And it is just a simple stress test command. time.sleep is enough to deal with it. There is no need to make it too complicated.


# Get load values after stress
load_info = self.gagent.get_load()
stress_load = load_info["load1m"]
LOG_JOB.info("Load info after stress: %s", load_info)

# Verify load value changed
if stress_load <= initial_load:
test.fail(
f"Load value did not increase after CPU stress:"
f" before={initial_load}, after={stress_load}"
)
LOG_JOB.info(
"Load value increased as expected:" " before=%s, after=%s",
initial_load,
stress_load,
)
except guest_agent.VAgentCmdError as e:
test.fail(f"guest-get-load command failed: {e}")
else:
# Initial load check
error_context.context("Check initial load average info", LOG_JOB.info)
guest_vals = _get_load_stats(session)
qga_vals = _get_load_stats(session, False)
prev_values = {"guest": guest_vals, "qga": qga_vals}

_log_load_values(guest_vals, qga_vals, "Initial")

if errors := _verify_load_values(qga_vals, guest_vals):
test.fail("Initial load check failed:\n" + "\n".join(errors))

# Stress test
error_context.context("Starting CPU stress test", LOG_JOB.info)
s, o = session.cmd_status_output(params["cmd_install_stressng"])
if s != 0:
test.error(f"Failed to install stress-ng: {o}")
session.cmd(params["cmd_run_stress"])
time.sleep(25)

guest_vals = _get_load_stats(session)
qga_vals = _get_load_stats(session, False)

_log_load_values(guest_vals, qga_vals, "Under stress")

if errors := _verify_load_values(qga_vals, guest_vals, "increase"):
test.fail("Stress test load check failed:\n" + "\n".join(errors))

prev_values = {"guest": guest_vals, "qga": qga_vals}

# sleep (60) wait for the stress-ng terminated.
time.sleep(60)
guest_vals = _get_load_stats(session)
qga_vals = _get_load_stats(session, False)

_log_load_values(guest_vals, qga_vals, "After stress")

if errors := _verify_load_values(qga_vals, guest_vals, "decrease"):
test.fail("Post-stress load check failed:\n" + "\n".join(errors))

@error_context.context_aware
def gagent_check_reboot_shutdown(self, test, params, env):
"""
Expand Down Expand Up @@ -2273,7 +2434,7 @@ def gagent_check_file_write(self, test, params, env):

:param test: kvm test object
:param params: Dictionary with the test parameters
:param env: Dictionary with test environment.
:param env: Dictionary with the test environment.
"""
error_context.context(
"Change guest-file related cmd to white list and get guest file name."
Expand Down Expand Up @@ -2894,7 +3055,7 @@ def gagent_check_fsfreeze(self, test, params, env):

:param test: kvm test object
:param params: Dictionary with the test parameters
:param env: Dictionary with test environmen.
:param env: Dictionary with the test environmen.
"""
session = self._get_session(params, self.vm)
self._open_session_list.append(session)
Expand Down Expand Up @@ -2926,7 +3087,7 @@ def gagent_check_fsfreeze_list(self, test, params, env):

:param test: kvm test object
:param params: Dictionary with the test parameters
:param env: Dictionary with test environmen.
:param env: Dictionary with the test environmen.
"""
session = self._get_session(params, self.vm)
self._open_session_list.append(session)
Expand Down Expand Up @@ -3019,7 +3180,7 @@ def gagent_check_thaw_unfrozen(self, test, params, env):

:param test: kvm test object
:param params: Dictionary with the test parameters
:param env: Dictionary with test environment.
:param env: Dictionary with the test environment
"""
error_context.context("Verify if FS is thawed", LOG_JOB.info)
expect_status = self.gagent.FSFREEZE_STATUS_THAWED
Expand All @@ -3041,7 +3202,7 @@ def gagent_check_freeze_frozen(self, test, params, env):

:param test: kvm test object
:param params: Dictionary with the test parameters
:param env: Dictionary with test environment.
:param env: Dictionary with the test environment
"""
# Since Qemu9.1, the report message changes.
qga_ver = self.gagent.guest_info()["version"].split(".")
Expand Down Expand Up @@ -3461,7 +3622,9 @@ def gagent_check_frozen_io(self, test, params, env):
)
session = self._get_session(self.params, None)
self._open_session_list.append(session)
iozone_cmd = utils_misc.set_winutils_letter(session, params["iozone_cmd"])
iozone_cmd = utils_misc.set_winutils_letter(
session, params["iozone_cmd"]
)
session.cmd(iozone_cmd, timeout=360)
error_context.context("Freeze the FS.", LOG_JOB.info)
try:
Expand Down Expand Up @@ -3498,7 +3661,7 @@ def gagent_check_vss_status(self, test, params, env):

:param test: kvm test object
:param params: Dictionary with the test parameters
:param env: Dictionary with test environment
:param env: Dictionary with test environment.
"""

def check_vss_info(cmd_type, key, expect_value):
Expand Down Expand Up @@ -4867,7 +5030,7 @@ def gagent_check_resource_leak(self, test, params, env):

:param test: kvm test object
:param params: Dictionary with the test parameters
:param env: Dictionary with test environment.
:param env: Dictionary with the test environment.
"""

def execute_qga_cmds_loop():
Expand Down