Skip to content

Commit f36c16a

Browse files
committed
refactor(test/balloon): move logic to get guest avail mem to framework
Move the logic to get the MemAvailable from /proc/meminfo inside the guest to a new guest_stats module in the test framework. This provides a new class MeminfoGuest that can be used to retrieve this information (and more!). Signed-off-by: Riccardo Mancini <[email protected]>
1 parent 2a8a966 commit f36c16a

File tree

3 files changed

+84
-23
lines changed

3 files changed

+84
-23
lines changed

tests/framework/guest_stats.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Classes for querying guest stats inside microVMs.
5+
"""
6+
7+
8+
class ByteUnit:
9+
"""Represents a byte unit that can be converted to other units."""
10+
11+
value_bytes: int
12+
13+
def __init__(self, value_bytes: int):
14+
self.value_bytes = value_bytes
15+
16+
@classmethod
17+
def from_kib(cls, value_kib: int):
18+
"""Creates a ByteUnit from a value in KiB."""
19+
if value_kib < 0:
20+
raise ValueError("value_kib must be non-negative")
21+
return ByteUnit(value_kib * 1024)
22+
23+
def bytes(self) -> float:
24+
"""Returns the value in B."""
25+
return self.value_bytes
26+
27+
def kib(self) -> float:
28+
"""Returns the value in KiB."""
29+
return self.value_bytes / 1024
30+
31+
def mib(self) -> float:
32+
"""Returns the value in MiB."""
33+
return self.value_bytes / (1 << 20)
34+
35+
def gib(self) -> float:
36+
"""Returns the value in GiB."""
37+
return self.value_bytes / (1 << 30)
38+
39+
40+
class Meminfo:
41+
"""Represents the contents of /proc/meminfo inside the guest"""
42+
43+
mem_total: ByteUnit
44+
mem_free: ByteUnit
45+
mem_available: ByteUnit
46+
buffers: ByteUnit
47+
cached: ByteUnit
48+
49+
def __init__(self):
50+
self.mem_total = ByteUnit(0)
51+
self.mem_free = ByteUnit(0)
52+
self.mem_available = ByteUnit(0)
53+
self.buffers = ByteUnit(0)
54+
self.cached = ByteUnit(0)
55+
56+
57+
class MeminfoGuest:
58+
"""Queries /proc/meminfo inside the guest"""
59+
60+
def __init__(self, vm):
61+
self.vm = vm
62+
63+
def get(self) -> Meminfo:
64+
"""Returns the contents of /proc/meminfo inside the guest"""
65+
meminfo = Meminfo()
66+
for line in self.vm.ssh.check_output("cat /proc/meminfo").stdout.splitlines():
67+
parts = line.split()
68+
if parts[0] == "MemTotal:":
69+
meminfo.mem_total = ByteUnit.from_kib(int(parts[1]))
70+
elif parts[0] == "MemFree:":
71+
meminfo.mem_free = ByteUnit.from_kib(int(parts[1]))
72+
elif parts[0] == "MemAvailable:":
73+
meminfo.mem_available = ByteUnit.from_kib(int(parts[1]))
74+
elif parts[0] == "Buffers:":
75+
meminfo.buffers = ByteUnit.from_kib(int(parts[1]))
76+
elif parts[0] == "Cached:":
77+
meminfo.cached = ByteUnit.from_kib(int(parts[1]))
78+
79+
return meminfo

tests/framework/utils.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -254,25 +254,6 @@ def search_output_from_cmd(cmd: str, find_regex: typing.Pattern) -> typing.Match
254254
)
255255

256256

257-
def get_free_mem_ssh(ssh_connection):
258-
"""
259-
Get how much free memory in kB a guest sees, over ssh.
260-
261-
:param ssh_connection: connection to the guest
262-
:return: available mem column output of 'free'
263-
"""
264-
_, stdout, stderr = ssh_connection.run("cat /proc/meminfo | grep MemAvailable")
265-
assert stderr == ""
266-
267-
# Split "MemAvailable: 123456 kB" and validate it
268-
meminfo_data = stdout.split()
269-
if len(meminfo_data) == 3:
270-
# Return the middle element in the array
271-
return int(meminfo_data[1])
272-
273-
raise Exception("Available memory not found in `/proc/meminfo")
274-
275-
276257
def _format_output_message(proc, stdout, stderr):
277258
output_message = f"\n[{proc.pid}] Command:\n{proc.args}"
278259
# Append stdout/stderr to the output message

tests/integration_tests/functional/test_balloon.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pytest
1010
import requests
1111

12+
from framework.guest_stats import MeminfoGuest
1213
from framework.utils import get_resident_memory
1314

1415
STATS_POLLING_INTERVAL_S = 1
@@ -142,18 +143,18 @@ def test_inflate_reduces_free(uvm_plain_any):
142143

143144
# Start the microvm
144145
test_microvm.start()
145-
firecracker_pid = test_microvm.firecracker_pid
146+
meminfo = MeminfoGuest(test_microvm)
146147

147148
# Get the free memory before ballooning.
148-
available_mem_deflated = get_free_mem_ssh(test_microvm.ssh)
149+
available_mem_deflated = meminfo.get().mem_free.kib()
149150

150151
# Inflate 64 MB == 16384 page balloon.
151152
test_microvm.api.balloon.patch(amount_mib=64)
152153
# This call will internally wait for rss to become stable.
153-
_ = get_stable_rss_mem(test_microvm.ps)
154+
_ = get_stable_rss_mem(test_microvm)
154155

155156
# Get the free memory after ballooning.
156-
available_mem_inflated = get_free_mem_ssh(test_microvm.ssh)
157+
available_mem_inflated = meminfo.get().mem_free.kib()
157158

158159
# Assert that ballooning reclaimed about 64 MB of memory.
159160
assert available_mem_inflated <= available_mem_deflated - 85 * 64000 / 100

0 commit comments

Comments
 (0)