Skip to content

Commit 1032e83

Browse files
committed
Simplified balloon API
Trying to use a dictionary and lists to allow multiple domains to be set on one one call. Doesn't help the preload use case thought because each one is called individually at domain-pre-paused.
1 parent 0baf2c3 commit 1032e83

File tree

4 files changed

+81
-27
lines changed

4 files changed

+81
-27
lines changed

qubes/qmemman/client.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ def _send(self, data: str) -> bool:
3838
def request_mem(self, amount: int | float) -> bool:
3939
return self._send("{}\n".format(int(amount)))
4040

41-
def set_mem(self, domid: int | str, amount: int | float) -> bool:
42-
return self._send("{} {}\n".format(domid, int(amount)))
41+
def set_mem(self, dom_memset: dict[int | str, int | float]) -> bool:
42+
dom_memset_str = " ".join(
43+
"{}:{}".format(key, value) for key, value in dom_memset.items()
44+
)
45+
return self._send("{}\n".format(dom_memset_str))
4346

4447
def close(self) -> None:
4548
assert isinstance(self.sock, socket.socket)

qubes/qmemman/systemstate.py

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
CHECK_MB_S = 100
3838
MIN_TOTAL_MEMORY_TRANSFER = 150 * 1024 * 1024
3939
MIN_MEM_CHANGE_WHEN_UNDER_PREF = 15 * 1024 * 1024
40+
#: number of loop iterations for CHECK_PERIOD_S seconds
41+
CHECK_PERIOD = max(1, int((CHECK_PERIOD_S + 0.0) / BALLOON_DELAY))
42+
#: number of free memory bytes expected to get during CHECK_PERIOD_S
43+
#: seconds
44+
CHECK_DELTA = CHECK_PERIOD_S * CHECK_MB_S * 1024 * 1024
4045

4146

4247
class SystemState:
@@ -116,11 +121,13 @@ def get_free_xen_mem(self) -> int:
116121
)
117122
return xen_free - assigned_but_unused
118123

119-
# Refresh information on memory assigned to all domains
120-
def refresh_mem_actual(self) -> None:
124+
# Refresh information on memory assigned to all or specific domains
125+
def refresh_mem_actual(self, domid_list: Optional[list] = None) -> None:
121126
for domain in self.xc.domain_getinfo():
122127
domid = str(domain["domid"])
123128
if domid in self.dom_dict:
129+
if domid_list and domid not in domid_list:
130+
continue
124131
dom = self.dom_dict[domid]
125132
# Real memory usage
126133
dom.mem_current = domain["mem_kb"] * 1024
@@ -220,17 +227,12 @@ def do_balloon(self, mem_size) -> bool:
220227
for _, dom in self.dom_dict.items():
221228
dom.no_progress = False
222229

223-
#: number of loop iterations for CHECK_PERIOD_S seconds
224-
check_period = max(1, int((CHECK_PERIOD_S + 0.0) / BALLOON_DELAY))
225-
#: number of free memory bytes expected to get during CHECK_PERIOD_S
226-
#: seconds
227-
check_delta = CHECK_PERIOD_S * CHECK_MB_S * 1024 * 1024
228230
#: helper array for holding free memory size, CHECK_PERIOD_S seconds
229231
#: ago, at every loop iteration
230-
xenfree_ring = [0] * check_period
232+
xenfree_ring = [0] * CHECK_PERIOD
231233

232234
while True:
233-
self.log.debug("niter={:2d}".format(niter))
235+
self.log.debug("niter={:d}".format(niter))
234236
self.refresh_mem_actual()
235237
xenfree = self.get_free_xen_mem()
236238
self.log.info("xenfree={!r}".format(xenfree))
@@ -239,10 +241,10 @@ def do_balloon(self, mem_size) -> bool:
239241
return True
240242
# fail the request if over past CHECK_PERIOD_S seconds,
241243
# we got less than CHECK_MB_S MB/s on average
242-
ring_slot = niter % check_period
244+
ring_slot = niter % CHECK_PERIOD
243245
if (
244-
niter >= check_period
245-
and xenfree < xenfree_ring[ring_slot] + check_delta
246+
niter >= CHECK_PERIOD
247+
and xenfree < xenfree_ring[ring_slot] + CHECK_DELTA
246248
):
247249
return False
248250
xenfree_ring[ring_slot] = xenfree
@@ -269,6 +271,56 @@ def do_balloon(self, mem_size) -> bool:
269271
time.sleep(BALLOON_DELAY)
270272
niter = niter + 1
271273

274+
def do_balloon_dom(self, dom_memset: dict) -> bool:
275+
self.log.info("do_balloon_dom(dom_memset={!r})".format(dom_memset))
276+
niter = 0
277+
if not dom_memset:
278+
return False
279+
280+
domid_list = list(dom_memset.keys())
281+
dom_dict = {
282+
domid: state
283+
for domid, state in self.dom_dict.items()
284+
if domid in domid_list
285+
}
286+
287+
for _, dom in dom_dict.items():
288+
dom.no_progress = False
289+
290+
memset_reqs = {}
291+
for domid, memset in dom_memset.items():
292+
if memset == 0:
293+
mem_pref = qubes.qmemman.algo.pref_mem(dom_dict[domid])
294+
memset_reqs[domid] = mem_pref
295+
self.log.debug(
296+
"mem for dom '%s' is 0, using its pref '%s'",
297+
domid,
298+
mem_pref,
299+
)
300+
else:
301+
memset_reqs[domid] = memset
302+
303+
succeeded = []
304+
while True:
305+
self.log.debug("niter={:d}".format(niter))
306+
self.refresh_mem_actual(domid_list)
307+
for domid, dom in dom_dict.items():
308+
assert isinstance(dom.mem_actual, int)
309+
if (
310+
domid not in succeeded
311+
and dom.mem_actual / memset_reqs[domid] < 1.1
312+
):
313+
succeeded.append(domid)
314+
if all(dom in succeeded for dom in domid_list):
315+
return True
316+
if niter >= CHECK_PERIOD:
317+
return False
318+
for domid, memset in memset_reqs.items():
319+
self.mem_set(domid, memset)
320+
self.log.debug("sleeping for {} s".format(BALLOON_DELAY))
321+
time.sleep(BALLOON_DELAY)
322+
niter += 1
323+
272324
def refresh_meminfo(self, domid, untrusted_meminfo_key) -> None:
273325
self.log.debug(
274326
"refresh_meminfo(domid={}, untrusted_meminfo_key={!r})".format(

qubes/tools/qmemmand.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,15 @@ def handle(self):
222222
memory = int(data_args[0])
223223
if system_state.do_balloon(memory):
224224
resp = "OK"
225-
elif len(data_args) == 2:
225+
elif ":" in data_args[0]:
226226
resp = "FAIL"
227-
domid, memory = str(data_args[0]), int(data_args[1])
228-
# TODO: ben: create the function to set memory to a domain.
229-
if system_state.mem_set(domid, memory):
227+
dom_memset = {
228+
str(key): int(value)
229+
for key, value in (
230+
pair.split(":") for pair in data_args
231+
)
232+
}
233+
if system_state.do_balloon_dom(dom_memset):
230234
resp = "OK"
231235
resp = str(resp + "\n").encode("ascii")
232236

qubes/vm/qubesvm.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2076,27 +2076,22 @@ def request_mem(self, mem_required=None):
20762076

20772077
return qmemman_client
20782078

2079-
def set_mem(self, wanted_mem: int | None = None):
2079+
def set_mem(self):
20802080
"""
2081-
Balloon up to the specified value.
2082-
2083-
:param int mem: Maximum value that domain can balloon to.
2081+
Balloon to mem_pref.
20842082
"""
20852083
if not qmemman_present or self.maxmem == 0:
20862084
return None
20872085

2088-
if wanted_mem is None:
2089-
wanted_mem = self.get_pref_mem()
2090-
20912086
qmemman_client = qubes.qmemman.client.QMemmanClient()
20922087
try:
2093-
result = qmemman_client.set_mem(self.xid, wanted_mem)
2088+
result = qmemman_client.set_mem({self.xid: 0})
20942089
except IOError as e:
20952090
raise IOError("Failed to connect to qmemman: {!s}".format(e))
20962091

20972092
if not result:
20982093
qmemman_client.close()
2099-
raise qubes.exc.QubesMemoryError(self)
2094+
self.log.warning("Failed to set memory")
21002095

21012096
return qmemman_client
21022097

0 commit comments

Comments
 (0)