Skip to content

Commit 8d86200

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 8d86200

4 files changed

Lines changed: 71 additions & 27 deletions

File tree

qubes/qmemman/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def request_mem(self, amount: int | float) -> bool:
3939
return self._send("{}\n".format(int(amount)))
4040

4141
def set_mem(self, domid: int | str, amount: int | float) -> bool:
42-
return self._send("{} {}\n".format(domid, int(amount)))
42+
return self._send("{}:{}\n".format(domid, int(amount)))
4343

4444
def close(self) -> None:
4545
assert isinstance(self.sock, socket.socket)

qubes/qmemman/systemstate.py

Lines changed: 63 additions & 13 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,11 @@ 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: list = None) -> None:
121126
for domain in self.xc.domain_getinfo():
122127
domid = str(domain["domid"])
123-
if domid in self.dom_dict:
128+
if domid in domid_list and domid in self.dom_dict:
124129
dom = self.dom_dict[domid]
125130
# Real memory usage
126131
dom.mem_current = domain["mem_kb"] * 1024
@@ -220,17 +225,12 @@ def do_balloon(self, mem_size) -> bool:
220225
for _, dom in self.dom_dict.items():
221226
dom.no_progress = False
222227

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
228228
#: helper array for holding free memory size, CHECK_PERIOD_S seconds
229229
#: ago, at every loop iteration
230-
xenfree_ring = [0] * check_period
230+
xenfree_ring = [0] * CHECK_PERIOD
231231

232232
while True:
233-
self.log.debug("niter={:2d}".format(niter))
233+
self.log.debug("niter={:d}".format(niter))
234234
self.refresh_mem_actual()
235235
xenfree = self.get_free_xen_mem()
236236
self.log.info("xenfree={!r}".format(xenfree))
@@ -239,10 +239,10 @@ def do_balloon(self, mem_size) -> bool:
239239
return True
240240
# fail the request if over past CHECK_PERIOD_S seconds,
241241
# we got less than CHECK_MB_S MB/s on average
242-
ring_slot = niter % check_period
242+
ring_slot = niter % CHECK_PERIOD
243243
if (
244-
niter >= check_period
245-
and xenfree < xenfree_ring[ring_slot] + check_delta
244+
niter >= CHECK_PERIOD
245+
and xenfree < xenfree_ring[ring_slot] + CHECK_DELTA
246246
):
247247
return False
248248
xenfree_ring[ring_slot] = xenfree
@@ -269,6 +269,56 @@ def do_balloon(self, mem_size) -> bool:
269269
time.sleep(BALLOON_DELAY)
270270
niter = niter + 1
271271

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

qubes/tools/qmemmand.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,10 @@ 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 = {str(key): int(value) for key, value in (pair.split(":") for pair in data_args)}
228+
if system_state.do_balloon_dom(dom_memset):
230229
resp = "OK"
231230
resp = str(resp + "\n").encode("ascii")
232231

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)