From 37e21f28a0efb09caefabf8ab25b1dcfcdc0f5e3 Mon Sep 17 00:00:00 2001 From: Ben Grande Date: Fri, 4 Jul 2025 12:50:33 +0200 Subject: [PATCH 1/6] Fix pylint complaints - Enable pylint; - Rename keyword 'id' or variable 'i' to 'domid', which is always xid but better to use a VMM agnostic name; - Move constants to module scope; - Pylint trips on dictionary iteration of .keys() with 'consider-using-dict-items' and 'unnecessary-dict-index'. Fixing that helped find some useless assignments which were already objects of the dictionary; - Assign current domain dictionary domid looping to an object for easier reading and modification; and - Separate 'do_balance()' to avoid 'too-many-nested-blocks'. --- qubes/qmemman/algo.py | 144 ++++++++--------- qubes/qmemman/client.py | 12 +- qubes/qmemman/domainstate.py | 7 +- qubes/qmemman/systemstate.py | 304 +++++++++++++++++------------------ 4 files changed, 220 insertions(+), 247 deletions(-) diff --git a/qubes/qmemman/algo.py b/qubes/qmemman/algo.py index 685a30eb0..1c627d9c9 100644 --- a/qubes/qmemman/algo.py +++ b/qubes/qmemman/algo.py @@ -1,5 +1,3 @@ -# pylint: skip-file - # # The Qubes OS Project, http://www.qubes-os.org # @@ -21,13 +19,13 @@ # import logging -import string # This are only defaults - can be overridden by QMemmanServer with values from # config file CACHE_FACTOR = 1.3 MIN_PREFMEM = 200 * 1024 * 1024 DOM0_MEM_BOOST = 350 * 1024 * 1024 +REQ_SAFETY_NET_FACTOR = 1.05 log = logging.getLogger("qmemman.daemon.algo") @@ -53,7 +51,7 @@ def refresh_meminfo_for_domain(domain, untrusted_xenstore_key): def prefmem(domain): # dom0 is special, as it must have large cache, for vbds. Thus, give it # a special boost - if domain.id == "0": + if domain.domid == "0": return int( min( domain.mem_used * CACHE_FACTOR + DOM0_MEM_BOOST, @@ -85,23 +83,22 @@ def balloon(memsize, domain_dictionary): memsize, domain_dictionary ) ) - REQ_SAFETY_NET_FACTOR = 1.05 - donors = list() - request = list() + donors = [] + request = [] available = 0 - for i in domain_dictionary.keys(): - if domain_dictionary[i].mem_used is None: + for domid, dom in domain_dictionary.items(): + if dom.mem_used is None: continue - if domain_dictionary[i].no_progress: + if dom.no_progress: continue - need = memory_needed(domain_dictionary[i]) + need = memory_needed(dom) if need < 0: log.info( "balloon: dom {} has actual memory {}".format( - i, domain_dictionary[i].memory_actual + domid, dom.memory_actual ) ) - donors.append((i, -need)) + donors.append((domid, -need)) available -= need log.info("req={} avail={} donors={!r}".format(memsize, available, donors)) @@ -139,26 +136,24 @@ def balance_when_enough_memory( # memory not assigned because of static max left_memory = 0 acceptors_count = 0 - for i in domain_dictionary.keys(): - if domain_dictionary[i].mem_used is None: + for domid, dom in domain_dictionary.items(): + if dom.mem_used is None: continue - if domain_dictionary[i].no_progress: + if dom.no_progress: continue # distribute total_available_memory proportionally to mempref - scale = 1.0 * prefmem(domain_dictionary[i]) / total_mem_pref - target_nonint = ( - prefmem(domain_dictionary[i]) + scale * total_available_memory - ) + scale = 1.0 * prefmem(dom) / total_mem_pref + target_nonint = prefmem(dom) + scale * total_available_memory # prevent rounding errors target = int(0.999 * target_nonint) # do not try to give more memory than static max - if target > domain_dictionary[i].memory_maximum: - left_memory += target - domain_dictionary[i].memory_maximum - target = domain_dictionary[i].memory_maximum + if target > dom.memory_maximum: + left_memory += target - dom.memory_maximum + target = dom.memory_maximum else: # count domains which can accept more memory acceptors_count += 1 - target_memory[i] = target + target_memory[domid] = target # distribute left memory across all acceptors while left_memory > 0 and acceptors_count > 0: log.info( @@ -169,34 +164,32 @@ def balance_when_enough_memory( new_left_memory = 0 new_acceptors_count = acceptors_count - for i in target_memory.keys(): - target = target_memory[i] - if target < domain_dictionary[i].memory_maximum: + for domid, target in target_memory.items(): + dom = domain_dictionary[domid] + if target < dom.memory_maximum: memory_bonus = int(0.999 * (left_memory / acceptors_count)) - if target + memory_bonus >= domain_dictionary[i].memory_maximum: + if target + memory_bonus >= dom.memory_maximum: new_left_memory += ( - target - + memory_bonus - - domain_dictionary[i].memory_maximum + target + memory_bonus - dom.memory_maximum ) - target = domain_dictionary[i].memory_maximum + target = dom.memory_maximum new_acceptors_count -= 1 else: target += memory_bonus - target_memory[i] = target + target_memory[domid] = target left_memory = new_left_memory acceptors_count = new_acceptors_count # split target_memory dictionary to donors and acceptors # this is needed to first get memory from donors and only then give it # to acceptors - donors_rq = list() - acceptors_rq = list() - for i in target_memory.keys(): - target = target_memory[i] - if target < domain_dictionary[i].memory_actual: - donors_rq.append((i, target)) + donors_rq = [] + acceptors_rq = [] + for domid, target in target_memory.items(): + dom = domain_dictionary[domid] + if target < dom.memory_actual: + donors_rq.append((domid, target)) else: - acceptors_rq.append((i, target)) + acceptors_rq.append((domid, target)) # print 'balance(enough): xen_free_memory=', xen_free_memory, \ # 'requests:', donors_rq + acceptors_rq @@ -218,30 +211,28 @@ def balance_when_low_on_memory( xen_free_memory, total_mem_pref_acceptors, donors, acceptors ) ) - donors_rq = list() - acceptors_rq = list() + donors_rq = [] + acceptors_rq = [] squeezed_mem = xen_free_memory - for i in donors: - avail = -memory_needed(domain_dictionary[i]) + for domid in donors: + dom = domain_dictionary[domid] + avail = -memory_needed(dom) if avail < 10 * 1024 * 1024: # probably we have already tried making it exactly at prefmem, # give up continue squeezed_mem -= avail - donors_rq.append((i, prefmem(domain_dictionary[i]))) + donors_rq.append((domid, prefmem(dom))) # the below can happen if initially xen free memory is below 50M if squeezed_mem < 0: return donors_rq - for i in acceptors: - scale = 1.0 * prefmem(domain_dictionary[i]) / total_mem_pref_acceptors - target_nonint = ( - domain_dictionary[i].memory_actual + scale * squeezed_mem - ) + for domid in acceptors: + dom = domain_dictionary[domid] + scale = 1.0 * prefmem(dom) / total_mem_pref_acceptors + target_nonint = dom.memory_actual + scale * squeezed_mem # do not try to give more memory than static max - target = min( - int(0.999 * target_nonint), domain_dictionary[i].memory_maximum - ) - acceptors_rq.append((i, target)) + target = min(int(0.999 * target_nonint), dom.memory_maximum) + acceptors_rq.append((domid, target)) # print 'balance(low): xen_free_memory=', xen_free_memory, 'requests:', # donors_rq + acceptors_rq return donors_rq + acceptors_rq @@ -270,29 +261,25 @@ def memory_info(xen_free_memory, domain_dictionary): # sum of memory preferences of all domains that require more memory total_mem_pref_acceptors = 0 - donors = list() # domains that can yield memory - acceptors = list() # domains that require more memory + donors = [] + acceptors = [] # pass 1: compute the above "total" values - for i in domain_dictionary.keys(): - if domain_dictionary[i].mem_used is None: + for domid, dom in domain_dictionary.items(): + if dom.mem_used is None: continue - if domain_dictionary[i].no_progress: + if dom.no_progress: continue - need = memory_needed(domain_dictionary[i]) - # print 'domain' , i, 'act/pref', \ - # domain_dictionary[i].memory_actual, prefmem(domain_dictionary[i]), \ + need = memory_needed(dom) + # print 'domain' , domid, 'act/pref', \ + # dom.memory_actual, prefmem(dom), \ # 'need=', need - if ( - need < 0 - or domain_dictionary[i].memory_actual - >= domain_dictionary[i].memory_maximum - ): - donors.append(i) + if need < 0 or dom.memory_actual >= dom.memory_maximum: + donors.append(domid) else: - acceptors.append(i) - total_mem_pref_acceptors += prefmem(domain_dictionary[i]) + acceptors.append(domid) + total_mem_pref_acceptors += prefmem(dom) total_memory_needed += need - total_mem_pref += prefmem(domain_dictionary[i]) + total_mem_pref += prefmem(dom) total_available_memory = xen_free_memory - total_memory_needed @@ -326,11 +313,10 @@ def balance(xen_free_memory, domain_dictionary): memory_dictionary["total_mem_pref"], memory_dictionary["total_available_memory"], ) - else: - return balance_when_low_on_memory( - memory_dictionary["domain_dictionary"], - memory_dictionary["xen_free_memory"], - memory_dictionary["total_mem_pref_acceptors"], - memory_dictionary["donors"], - memory_dictionary["acceptors"], - ) + return balance_when_low_on_memory( + memory_dictionary["domain_dictionary"], + memory_dictionary["xen_free_memory"], + memory_dictionary["total_mem_pref_acceptors"], + memory_dictionary["donors"], + memory_dictionary["acceptors"], + ) diff --git a/qubes/qmemman/client.py b/qubes/qmemman/client.py index 3b6bb088f..ca1d84547 100644 --- a/qubes/qmemman/client.py +++ b/qubes/qmemman/client.py @@ -1,5 +1,3 @@ -# pylint: skip-file - # # The Qubes OS Project, http://www.qubes-os.org # @@ -24,20 +22,18 @@ class QMemmanClient: + def __init__(self): + self.sock = None + def request_memory(self, amount): self.sock = socket.socket(socket.AF_UNIX) - flags = fcntl.fcntl(self.sock.fileno(), fcntl.F_GETFD) flags |= fcntl.FD_CLOEXEC fcntl.fcntl(self.sock.fileno(), fcntl.F_SETFD, flags) - self.sock.connect("/var/run/qubes/qmemman.sock") self.sock.send(str(int(amount)).encode("ascii") + b"\n") received = self.sock.recv(1024).strip() - if received == b"OK": - return True - else: - return False + return bool(received == b"OK") def close(self): self.sock.close() diff --git a/qubes/qmemman/domainstate.py b/qubes/qmemman/domainstate.py index a476e5d7c..9e3be5f45 100644 --- a/qubes/qmemman/domainstate.py +++ b/qubes/qmemman/domainstate.py @@ -1,4 +1,3 @@ -# pylint: skip-file # # The Qubes OS Project, https://www.qubes-os.org/ # @@ -20,14 +19,14 @@ # License along with this library; if not, see . -class DomainState: - def __init__(self, id): +class DomainState: # pylint: disable=too-few-public-methods + def __init__(self, domid): self.memory_current = 0 # the current memory size self.memory_actual = None # the current memory allocation (what VM # is using or can use at any time) self.memory_maximum = None # the maximum memory size self.mem_used = None # used memory, computed based on meminfo - self.id = id # domain id + self.domid = domid # domain id self.last_target = 0 # the last memset target self.use_hotplug = False # use memory hotplug for mem-set self.no_progress = False # no react to memset diff --git a/qubes/qmemman/systemstate.py b/qubes/qmemman/systemstate.py index a12011183..04eeccae6 100644 --- a/qubes/qmemman/systemstate.py +++ b/qubes/qmemman/systemstate.py @@ -1,4 +1,3 @@ -# pylint: skip-file # # The Qubes OS Project, https://www.qubes-os.org/ # @@ -18,23 +17,34 @@ # # You should have received a copy of the GNU General Public # License along with this library; if not, see . + import functools import logging import os import time - -import xen.lowlevel +import xen.lowlevel # pylint: disable=import-error from pathlib import Path import qubes.qmemman from qubes.qmemman.domainstate import DomainState +BALOON_DELAY = 0.1 +XEN_FREE_MEM_LEFT = 50 * 1024 * 1024 +XEN_FREE_MEM_MIN = 25 * 1024 * 1024 +# Overhead of per-page Xen structures, taken from OpenStack +# nova/virt/xenapi/driver.py +# see https://wiki.openstack.org/wiki/XenServer/Overhead +MEM_OVERHEAD_FACTOR = 1.0 / 1.00781 +CHECK_PERIOD_S = 3 +CHECK_MB_S = 100 +MIN_TOTAL_MEMORY_TRANSFER = 150 * 1024 * 1024 +MIN_MEM_CHANGE_WHEN_UNDER_PREF = 15 * 1024 * 1024 no_progress_msg = "VM refused to give back requested memory" slow_memset_react_msg = "VM didn't give back all requested memory" -class SystemState(object): +class SystemState: def __init__(self): self.log = logging.getLogger("qmemman.systemstate") self.log.debug("SystemState()") @@ -42,49 +52,43 @@ def __init__(self): self.domdict = {} self.xc = None self.xs = None + self.all_phys_mem = 0 def init(self): self.xc = xen.lowlevel.xc.xc() self.xs = xen.lowlevel.xs.xs() - self.BALOON_DELAY = 0.1 - self.XEN_FREE_MEM_LEFT = 50 * 1024 * 1024 - self.XEN_FREE_MEM_MIN = 25 * 1024 * 1024 - # Overhead of per-page Xen structures, taken from OpenStack - # nova/virt/xenapi/driver.py - # see https://wiki.openstack.org/wiki/XenServer/Overhead # we divide total and free physical memory by this to get # "assignable" memory - self.MEM_OVERHEAD_FACTOR = 1.0 / 1.00781 try: - self.ALL_PHYS_MEM = int( - self.xc.physinfo()["total_memory"] - * 1024 - * self.MEM_OVERHEAD_FACTOR + self.all_phys_mem = int( + self.xc.physinfo()["total_memory"] * 1024 * MEM_OVERHEAD_FACTOR ) except xen.lowlevel.xc.Error: - self.ALL_PHYS_MEM = 0 + pass - def add_domain(self, id): - self.log.debug("add_domain(id={!r})".format(id)) - self.domdict[id] = DomainState(id) + def add_domain(self, domid): + self.log.debug("add_domain(domid={!r})".format(domid)) + self.domdict[domid] = DomainState(domid) # TODO: move to DomainState.__init__ - target_str = self.xs.read("", "/local/domain/" + id + "/memory/target") + target_str = self.xs.read( + "", "/local/domain/" + domid + "/memory/target" + ) if target_str: - self.domdict[id].last_target = int(target_str) * 1024 + self.domdict[domid].last_target = int(target_str) * 1024 - def del_domain(self, id): - self.log.debug("del_domain(id={!r})".format(id)) - self.domdict.pop(id) + def del_domain(self, domid): + self.log.debug("del_domain(domid={!r})".format(domid)) + self.domdict.pop(domid) def get_free_xen_memory(self): xen_free = int( - self.xc.physinfo()["free_memory"] * 1024 * self.MEM_OVERHEAD_FACTOR + self.xc.physinfo()["free_memory"] * 1024 * MEM_OVERHEAD_FACTOR ) # now check for domains which have assigned more memory than really # used - do not count it as "free", because domain is free to use it # at any time # assumption: self.refresh_memactual was called before - # (so domdict[id].memory_actual is up-to-date) + # (so dom.memory_actual is up-to-date) assigned_but_unused = functools.reduce( lambda acc, dom: acc + max(0, dom.last_target - dom.memory_current), self.domdict.values(), @@ -93,14 +97,14 @@ def get_free_xen_memory(self): # If, at any time, Xen have less memory than XEN_FREE_MEM_MIN, # it is a failure of qmemman. Collect as much data as possible to # debug it - if xen_free < self.XEN_FREE_MEM_MIN: + if xen_free < XEN_FREE_MEM_MIN: self.log.error( "Xen free = {!r} below acceptable value! " "assigned_but_unused={!r}, domdict={!r}".format( xen_free, assigned_but_unused, self.domdict ) ) - elif xen_free < assigned_but_unused + self.XEN_FREE_MEM_MIN: + elif xen_free < assigned_but_unused + XEN_FREE_MEM_MIN: self.log.error( "Xen free = {!r} too small to satisfy assignments! " "assigned_but_unused={!r}, domdict={!r}".format( @@ -112,32 +116,32 @@ def get_free_xen_memory(self): # refresh information on memory assigned to all domains def refresh_memactual(self): for domain in self.xc.domain_getinfo(): - id = str(domain["domid"]) - if id in self.domdict: + domid = str(domain["domid"]) + if domid in self.domdict: + dom = self.domdict[domid] # real memory usage - self.domdict[id].memory_current = domain["mem_kb"] * 1024 + dom.memory_current = domain["mem_kb"] * 1024 # what VM is using or can use - self.domdict[id].memory_actual = max( - self.domdict[id].memory_current, - self.domdict[id].last_target, + dom.memory_actual = max( + dom.memory_current, + dom.last_target, ) hotplug_max = self.xs.read( - "", "/local/domain/%s/memory/hotplug-max" % str(id) + "", "/local/domain/%s/memory/hotplug-max" % str(domid) ) static_max = self.xs.read( - "", "/local/domain/%s/memory/static-max" % str(id) + "", "/local/domain/%s/memory/static-max" % str(domid) ) if hotplug_max: - self.domdict[id].memory_maximum = int(hotplug_max) * 1024 - self.domdict[id].use_hotplug = True + dom.memory_maximum = int(hotplug_max) * 1024 + dom.use_hotplug = True elif static_max: - self.domdict[id].memory_maximum = int(static_max) * 1024 - self.domdict[id].use_hotplug = False + dom.memory_maximum = int(static_max) * 1024 + dom.use_hotplug = False else: - self.domdict[id].memory_maximum = self.ALL_PHYS_MEM + dom.memory_maximum = self.all_phys_mem # the previous line used to be - # self.domdict[id].memory_maximum = domain[ - # 'maxmem_kb']*1024 + # dom.memory_maximum = domain['maxmem_kb']*1024 # but domain['maxmem_kb'] changes in self.mem_set as well, # and this results in the memory never increasing # in fact, the only possible case of nonexisting @@ -146,49 +150,47 @@ def refresh_memactual(self): def clear_outdated_error_markers(self): # Clear outdated errors - for i in self.domdict.keys(): - if self.domdict[i].mem_used is None: + for dom in self.domdict.values(): + if dom.mem_used is None: continue # clear markers excluding VM from memory balance, if: # - VM have responded to previous request (with some safety margin) # - VM request more memory than it has assigned # The second condition avoids starving a VM, even when there is # some free memory available - if self.domdict[i].memory_actual <= self.domdict[ - i - ].last_target + self.XEN_FREE_MEM_LEFT / 2 or self.domdict[ - i - ].memory_actual < qubes.qmemman.algo.prefmem( - self.domdict[i] + if ( + dom.memory_actual <= dom.last_target + XEN_FREE_MEM_LEFT / 2 + or dom.memory_actual < qubes.qmemman.algo.prefmem(dom) ): - self.domdict[i].slow_memset_react = False - self.domdict[i].no_progress = False + dom.slow_memset_react = False + dom.no_progress = False # the below works (and is fast), but then 'xm list' shows unchanged # memory value - def mem_set(self, id, val): - self.log.info("mem-set domain {} to {}".format(id, val)) - self.domdict[id].last_target = val + def mem_set(self, domid, val): + self.log.info("mem-set domain {} to {}".format(domid, val)) + dom = self.domdict[domid] + dom.last_target = val # can happen in the middle of domain shutdown # apparently xc.lowlevel throws exceptions too try: self.xc.domain_setmaxmem( - int(id), int(val / 1024) + 1024 + int(domid), int(val / 1024) + 1024 ) # LIBXL_MAXMEM_CONSTANT=1024 - self.xc.domain_set_target_mem(int(id), int(val / 1024)) - except: + self.xc.domain_set_target_mem(int(domid), int(val / 1024)) + except Exception: pass # VM sees about 16MB memory less, so adjust for it here - qmemman # handle Xen view of memory self.xs.write( "", - "/local/domain/" + id + "/memory/target", + "/local/domain/" + domid + "/memory/target", str(int(val / 1024 - 16 * 1024)), ) - if self.domdict[id].use_hotplug: + if dom.use_hotplug: self.xs.write( "", - "/local/domain/" + id + "/memory/static-max", + "/local/domain/" + domid + "/memory/static-max", str(int(val / 1024)), ) @@ -196,8 +198,7 @@ def mem_set(self, id, val): # make sure that past mem_set will not decrease Xen free mem def inhibit_balloon_up(self): self.log.debug("inhibit_balloon_up()") - for i in self.domdict.keys(): - dom = self.domdict[i] + for domid, dom in self.domdict.items(): if ( dom.memory_actual is not None and dom.memory_actual + 200 * 1024 < dom.last_target @@ -205,23 +206,20 @@ def inhibit_balloon_up(self): self.log.info( "Preventing balloon up to {}".format(dom.last_target) ) - self.mem_set(i, dom.memory_actual) + self.mem_set(domid, dom.memory_actual) # perform memory ballooning, across all domains, to add "memsize" to Xen # free memory def do_balloon(self, memsize): self.log.info("do_balloon(memsize={!r})".format(memsize)) - CHECK_PERIOD_S = 3 - CHECK_MB_S = 100 - niter = 0 prev_memory_actual = None - for i in self.domdict.keys(): - self.domdict[i].no_progress = False + for dom in self.domdict.values(): + dom.no_progress = False #: number of loop iterations for CHECK_PERIOD_S seconds - check_period = max(1, int((CHECK_PERIOD_S + 0.0) / self.BALOON_DELAY)) + check_period = max(1, int((CHECK_PERIOD_S + 0.0) / BALOON_DELAY)) #: number of free memory bytes expected to get during CHECK_PERIOD_S #: seconds check_delta = CHECK_PERIOD_S * CHECK_MB_S * 1024 * 1024 @@ -234,7 +232,7 @@ def do_balloon(self, memsize): self.refresh_memactual() xenfree = self.get_free_xen_memory() self.log.info("xenfree={!r}".format(xenfree)) - if xenfree >= memsize + self.XEN_FREE_MEM_MIN: + if xenfree >= memsize + XEN_FREE_MEM_MIN: self.inhibit_balloon_up() return True # fail the request if over past CHECK_PERIOD_S seconds, @@ -247,29 +245,30 @@ def do_balloon(self, memsize): return False xenfree_ring[ring_slot] = xenfree if prev_memory_actual is not None: - for i in prev_memory_actual.keys(): - if prev_memory_actual[i] == self.domdict[i].memory_actual: + for domid, prev_mem in prev_memory_actual.items(): + dom = self.domdict[domid] + if prev_mem == dom.memory_actual: # domain not responding to memset requests, remove it # from donors - self.domdict[i].no_progress = True + dom.no_progress = True self.log.info( "domain {} stuck at {}".format( - i, self.domdict[i].memory_actual + domid, dom.memory_actual ) ) memset_reqs = qubes.qmemman.algo.balloon( - memsize + self.XEN_FREE_MEM_LEFT - xenfree, self.domdict + memsize + XEN_FREE_MEM_LEFT - xenfree, self.domdict ) self.log.info("memset_reqs={!r}".format(memset_reqs)) if len(memset_reqs) == 0: return False prev_memory_actual = {} - for i in memset_reqs: - dom, mem = i + for req in memset_reqs: + dom, mem = req self.mem_set(dom, mem) prev_memory_actual[dom] = self.domdict[dom].memory_actual - self.log.debug("sleeping for {} s".format(self.BALOON_DELAY)) - time.sleep(self.BALOON_DELAY) + self.log.debug("sleeping for {} s".format(BALOON_DELAY)) + time.sleep(BALOON_DELAY) niter = niter + 1 def refresh_meminfo(self, domid, untrusted_meminfo_key): @@ -294,16 +293,14 @@ def is_balance_req_significant(self, memset_reqs, xenfree): ) total_memory_transfer = 0 - MIN_TOTAL_MEMORY_TRANSFER = 150 * 1024 * 1024 - MIN_MEM_CHANGE_WHEN_UNDER_PREF = 15 * 1024 * 1024 # If xenfree to low, return immediately - if self.XEN_FREE_MEM_LEFT - xenfree > MIN_MEM_CHANGE_WHEN_UNDER_PREF: + if XEN_FREE_MEM_LEFT - xenfree > MIN_MEM_CHANGE_WHEN_UNDER_PREF: self.log.debug("xenfree is too low, returning") return True - for rq in memset_reqs: - dom, mem = rq + for req in memset_reqs: + dom, mem = req last_target = self.domdict[dom].last_target memory_change = mem - last_target total_memory_transfer += abs(memory_change) @@ -319,28 +316,24 @@ def is_balance_req_significant(self, memset_reqs, xenfree): return True ret = ( - total_memory_transfer + abs(xenfree - self.XEN_FREE_MEM_LEFT) + total_memory_transfer + abs(xenfree - XEN_FREE_MEM_LEFT) > MIN_TOTAL_MEMORY_TRANSFER ) self.log.debug("is_balance_req_significant return {}".format(ret)) return ret def print_stats(self, xenfree, memset_reqs): - for i in self.domdict.keys(): - if self.domdict[i].mem_used is not None: + for domid, dom in self.domdict.items(): + if dom.mem_used is not None: self.log.info( "stat: dom {!r} act={} pref={} last_target={}" "{}{}".format( - i, - self.domdict[i].memory_actual, - qubes.qmemman.algo.prefmem(self.domdict[i]), - self.domdict[i].last_target, - " no_progress" if self.domdict[i].no_progress else "", - ( - " slow_memset_react" - if self.domdict[i].slow_memset_react - else "" - ), + domid, + dom.memory_actual, + qubes.qmemman.algo.prefmem(dom), + dom.last_target, + " no_progress" if dom.no_progress else "", + (" slow_memset_react" if dom.slow_memset_react else ""), ) ) @@ -348,6 +341,38 @@ def print_stats(self, xenfree, memset_reqs): "stat: xenfree={} memset_reqs={}".format(xenfree, memset_reqs) ) + def debug_stuck_balance(self, domid, memset_reqs, prev_memactual): + for rq2 in memset_reqs: + domid2, mem2 = rq2 + if domid2 == domid: + # All donors have been processed + break + dom2 = self.domdict[domid2] + # allow some small margin + if dom2.memory_actual > dom2.last_target + XEN_FREE_MEM_LEFT / 4: + # VM didn't react to memory request at all, + # remove from donors + if prev_memactual[domid2] == dom2.memory_actual: + self.log.warning( + "dom {!r} did not react to memory request" + " (holds {}, requested balloon down to {})".format( + domid2, + dom2.memory_actual, + mem2, + ) + ) + dom2.no_progress = True + else: + self.log.warning( + "dom {!r} still holds more" + " memory than assigned ({} > {})".format( + domid2, + dom2.memory_actual, + mem2, + ) + ) + dom2.slow_memset_react = True + def do_balance(self): self.log.debug("do_balance()") if os.path.isfile("/var/run/qubes/do-not-membalance"): @@ -358,7 +383,7 @@ def do_balance(self): self.clear_outdated_error_markers() xenfree = self.get_free_xen_memory() memset_reqs = qubes.qmemman.algo.balance( - xenfree - self.XEN_FREE_MEM_LEFT, self.domdict + xenfree - XEN_FREE_MEM_LEFT, self.domdict ) if not self.is_balance_req_significant(memset_reqs, xenfree): return @@ -366,78 +391,45 @@ def do_balance(self): self.print_stats(xenfree, memset_reqs) prev_memactual = {} - for i in self.domdict.keys(): - prev_memactual[i] = self.domdict[i].memory_actual - for rq in memset_reqs: - dom, mem = rq - # Force to always have at least 0.9*self.XEN_FREE_MEM_LEFT (some + for domid, dom in self.domdict.items(): + prev_memactual[domid] = dom.memory_actual + for req in memset_reqs: + domid, mem = req + dom = self.domdict[domid] + # Force to always have at least 0.9*XEN_FREE_MEM_LEFT (some # margin for rounding errors). Before giving memory to # domain, ensure that others have gave it back. # If not - wait a little. ntries = 5 while ( - self.get_free_xen_memory() - - (mem - self.domdict[dom].memory_actual) - < 0.9 * self.XEN_FREE_MEM_LEFT + self.get_free_xen_memory() - (mem - dom.memory_actual) + < 0.9 * XEN_FREE_MEM_LEFT ): self.log.debug( - "do_balance dom={!r} sleeping ntries={}".format(dom, ntries) + "do_balance dom={!r} sleeping ntries={}".format( + domid, ntries + ) ) - time.sleep(self.BALOON_DELAY) + time.sleep(BALOON_DELAY) self.refresh_memactual() ntries -= 1 if ntries <= 0: # Waiting haven't helped; Find which domain get stuck and # abort balance (after distributing what we have) - for rq2 in memset_reqs: - dom2, mem2 = rq2 - if dom2 == dom: - # All donors have been processed - break - # allow some small margin - if ( - self.domdict[dom2].memory_actual - > self.domdict[dom2].last_target - + self.XEN_FREE_MEM_LEFT / 4 - ): - # VM didn't react to memory request at all, - # remove from donors - if ( - prev_memactual[dom2] - == self.domdict[dom2].memory_actual - ): - self.log.warning( - "dom {!r} did not react to memory request" - " (holds {}, requested balloon down to {})".format( - dom2, - self.domdict[dom2].memory_actual, - mem2, - ) - ) - self.domdict[dom2].no_progress = True - else: - self.log.warning( - "dom {!r} still holds more" - " memory than assigned ({} > {})".format( - dom2, - self.domdict[dom2].memory_actual, - mem2, - ) - ) - self.domdict[dom2].slow_memset_react = True + self.debug_stuck_balance(domid, memset_reqs, prev_memactual) self.mem_set( - dom, + domid, self.get_free_xen_memory() - + self.domdict[dom].memory_actual - - self.XEN_FREE_MEM_LEFT, + + dom.memory_actual + - XEN_FREE_MEM_LEFT, ) return - self.mem_set(dom, mem) + self.mem_set(domid, mem) xenfree = self.get_free_xen_memory() memory_dictionary = qubes.qmemman.algo.memory_info( - xenfree - self.XEN_FREE_MEM_LEFT, self.domdict + xenfree - XEN_FREE_MEM_LEFT, self.domdict ) avail_mem_file = qubes.config.qmemman_avail_mem_file avail_mem_file_tmp = Path(avail_mem_file).with_suffix(".tmp") @@ -446,8 +438,8 @@ def do_balance(self): os.chmod(avail_mem_file_tmp, 0o644) os.replace(avail_mem_file_tmp, avail_mem_file) - -# for i in self.domdict.keys(): -# print 'domain ', i, ' meminfo=', self.domdict[i].mem_used, 'actual mem', self.domdict[i].memory_actual -# print 'domain ', i, 'actual mem', self.domdict[i].memory_actual -# print 'xen free mem', self.get_free_xen_memory() + # pylint: disable=line-too-long + # for i in self.domdict.keys(): + # print 'domain ', i, ' meminfo=', dom.mem_used, 'actual mem', dom.memory_actual + # print 'domain ', i, 'actual mem', dom.memory_actual + # print 'xen free mem', self.get_free_xen_memory() From 4b3ac670c92dcfb5425e669a16b57d962d1b1ad6 Mon Sep 17 00:00:00 2001 From: Ben Grande Date: Fri, 4 Jul 2025 16:12:15 +0200 Subject: [PATCH 2/6] Remove commented code --- qubes/qmemman/algo.py | 8 -------- qubes/qmemman/systemstate.py | 13 +++---------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/qubes/qmemman/algo.py b/qubes/qmemman/algo.py index 1c627d9c9..3f5f39a68 100644 --- a/qubes/qmemman/algo.py +++ b/qubes/qmemman/algo.py @@ -190,9 +190,6 @@ def balance_when_enough_memory( donors_rq.append((domid, target)) else: acceptors_rq.append((domid, target)) - - # print 'balance(enough): xen_free_memory=', xen_free_memory, \ - # 'requests:', donors_rq + acceptors_rq return donors_rq + acceptors_rq @@ -233,8 +230,6 @@ def balance_when_low_on_memory( # do not try to give more memory than static max target = min(int(0.999 * target_nonint), dom.memory_maximum) acceptors_rq.append((domid, target)) - # print 'balance(low): xen_free_memory=', xen_free_memory, 'requests:', - # donors_rq + acceptors_rq return donors_rq + acceptors_rq @@ -270,9 +265,6 @@ def memory_info(xen_free_memory, domain_dictionary): if dom.no_progress: continue need = memory_needed(dom) - # print 'domain' , domid, 'act/pref', \ - # dom.memory_actual, prefmem(dom), \ - # 'need=', need if need < 0 or dom.memory_actual >= dom.memory_maximum: donors.append(domid) else: diff --git a/qubes/qmemman/systemstate.py b/qubes/qmemman/systemstate.py index 04eeccae6..5f6c37eca 100644 --- a/qubes/qmemman/systemstate.py +++ b/qubes/qmemman/systemstate.py @@ -143,10 +143,9 @@ def refresh_memactual(self): # the previous line used to be # dom.memory_maximum = domain['maxmem_kb']*1024 # but domain['maxmem_kb'] changes in self.mem_set as well, - # and this results in the memory never increasing - # in fact, the only possible case of nonexisting - # memory/static-max is dom0 - # see #307 + # and this results in the memory never increasing in fact, + # the only possible case of nonexisting memory/static-max + # is dom0, see #307 def clear_outdated_error_markers(self): # Clear outdated errors @@ -437,9 +436,3 @@ def do_balance(self): file.write(str(memory_dictionary["total_available_memory"])) os.chmod(avail_mem_file_tmp, 0o644) os.replace(avail_mem_file_tmp, avail_mem_file) - - # pylint: disable=line-too-long - # for i in self.domdict.keys(): - # print 'domain ', i, ' meminfo=', dom.mem_used, 'actual mem', dom.memory_actual - # print 'domain ', i, 'actual mem', dom.memory_actual - # print 'xen free mem', self.get_free_xen_memory() From b8fa5b805b784f14fdcf21a1025b95f7aa956df3 Mon Sep 17 00:00:00 2001 From: Ben Grande Date: Fri, 4 Jul 2025 20:27:44 +0200 Subject: [PATCH 3/6] Minor cleanup --- qubes/qmemman/algo.py | 120 ++++++++++++++------------------- qubes/qmemman/systemstate.py | 125 +++++++++++++++++------------------ 2 files changed, 110 insertions(+), 135 deletions(-) diff --git a/qubes/qmemman/algo.py b/qubes/qmemman/algo.py index 3f5f39a68..70d4dab92 100644 --- a/qubes/qmemman/algo.py +++ b/qubes/qmemman/algo.py @@ -30,53 +30,40 @@ log = logging.getLogger("qmemman.daemon.algo") -# untrusted meminfo size is taken from xenstore key, thus its size is limited -# so splits do not require excessive memory def sanitize_and_parse_meminfo(untrusted_meminfo): + # Untrusted meminfo size is read from xenstore, thus its size is limited + # and splits do not require excessive memory. if not untrusted_meminfo: return None - - # new syntax - just one int - if untrusted_meminfo.isdigit(): - return int(untrusted_meminfo) * 1024 - - return None + if not untrusted_meminfo.isdigit(): + return None + return int(untrusted_meminfo) * 1024 -# called when a domain updates its 'meminfo' xenstore key +# Called when a domain updates its 'meminfo' xenstore key. def refresh_meminfo_for_domain(domain, untrusted_xenstore_key): domain.mem_used = sanitize_and_parse_meminfo(untrusted_xenstore_key) def prefmem(domain): - # dom0 is special, as it must have large cache, for vbds. Thus, give it - # a special boost + # As dom0 must have large cache for vbds, give it a special boost. + mem_used = domain.mem_used * CACHE_FACTOR if domain.domid == "0": - return int( - min( - domain.mem_used * CACHE_FACTOR + DOM0_MEM_BOOST, - domain.memory_maximum, - ) - ) - return int( - max( - min(domain.mem_used * CACHE_FACTOR, domain.memory_maximum), - MIN_PREFMEM, - ) - ) + mem_used += DOM0_MEM_BOOST + return int(min(mem_used, domain.memory_maximum)) + return int(max(min(mem_used, domain.memory_maximum), MIN_PREFMEM)) def memory_needed(domain): - # do not change - # in balance(), "distribute total_available_memory proportionally to - # mempref" relies on this exact formula + # Do not change. In balance(), "distribute total_available_memory + # proportionally to mempref" relies on this exact formula. ret = prefmem(domain) - domain.memory_actual return ret -# prepare list of (domain, memory_target) pairs that need to be passed -# to "xm memset" equivalent in order to obtain "memsize" of memory -# return empty list when the request cannot be satisfied +# Prepare list of (domain, memory_target) pairs that need to be passed to "xm +# memset" equivalent in order to obtain "memsize". +# Returns empty list when the request cannot be satisfied. def balloon(memsize, domain_dictionary): log.debug( "balloon(memsize={!r}, domain_dictionary={!r})".format( @@ -87,9 +74,7 @@ def balloon(memsize, domain_dictionary): request = [] available = 0 for domid, dom in domain_dictionary.items(): - if dom.mem_used is None: - continue - if dom.no_progress: + if dom.mem_used is None or dom.no_progress: continue need = memory_needed(dom) if need < 0: @@ -120,8 +105,8 @@ def balloon(memsize, domain_dictionary): # get stuck. The surplus will return to the VM during "balance" call. -# redistribute positive "total_available_memory" of memory between domains, -# proportionally to prefmem +# Redistribute positive "total_available_memory" of memory between domains, +# proportionally to prefmem. def balance_when_enough_memory( domain_dictionary, xen_free_memory, total_mem_pref, total_available_memory ): @@ -133,28 +118,26 @@ def balance_when_enough_memory( ) target_memory = {} - # memory not assigned because of static max + # Memory not assigned because of static max. left_memory = 0 acceptors_count = 0 for domid, dom in domain_dictionary.items(): - if dom.mem_used is None: + if dom.mem_used is None or dom.no_progress: continue - if dom.no_progress: - continue - # distribute total_available_memory proportionally to mempref + # Distribute total_available_memory proportionally to mempref. scale = 1.0 * prefmem(dom) / total_mem_pref target_nonint = prefmem(dom) + scale * total_available_memory - # prevent rounding errors + # Prevent rounding errors. target = int(0.999 * target_nonint) - # do not try to give more memory than static max + # Do not try to give more memory than static max. if target > dom.memory_maximum: left_memory += target - dom.memory_maximum target = dom.memory_maximum else: - # count domains which can accept more memory + # Count domains which can accept more memory. acceptors_count += 1 target_memory[domid] = target - # distribute left memory across all acceptors + # Distribute left memory across all acceptors. while left_memory > 0 and acceptors_count > 0: log.info( "left_memory={} acceptors_count={}".format( @@ -179,9 +162,8 @@ def balance_when_enough_memory( target_memory[domid] = target left_memory = new_left_memory acceptors_count = new_acceptors_count - # split target_memory dictionary to donors and acceptors - # this is needed to first get memory from donors and only then give it - # to acceptors + # Split target_memory dictionary to donors and acceptors. This is needed to + # first get memory from donors and only then give it to acceptors. donors_rq = [] acceptors_rq = [] for domid, target in target_memory.items(): @@ -193,8 +175,8 @@ def balance_when_enough_memory( return donors_rq + acceptors_rq -# when not enough mem to make everyone be above prefmem, make donors be at -# prefmem, and redistribute anything left between acceptors +# When not enough mem to make everyone be above prefmem, make donors be at +# prefmem, and redistribute anything left between acceptors. def balance_when_low_on_memory( domain_dictionary, xen_free_memory, @@ -215,27 +197,27 @@ def balance_when_low_on_memory( dom = domain_dictionary[domid] avail = -memory_needed(dom) if avail < 10 * 1024 * 1024: - # probably we have already tried making it exactly at prefmem, - # give up + # Probably we have already tried making it exactly at prefmem, give + # up. continue squeezed_mem -= avail donors_rq.append((domid, prefmem(dom))) - # the below can happen if initially xen free memory is below 50M + # The below condition can happen if initially xen free memory is below 50M. if squeezed_mem < 0: return donors_rq for domid in acceptors: dom = domain_dictionary[domid] scale = 1.0 * prefmem(dom) / total_mem_pref_acceptors target_nonint = dom.memory_actual + scale * squeezed_mem - # do not try to give more memory than static max + # Do not try to give more memory than static max. target = min(int(0.999 * target_nonint), dom.memory_maximum) acceptors_rq.append((domid, target)) return donors_rq + acceptors_rq -# get memory information -# called before and after domain balances -# return a dictionary of various memory data points +# Get memory information. +# Called before and after domain balances. +# Return a dictionary of various memory data points. def memory_info(xen_free_memory, domain_dictionary): log.debug( "memory_info(xen_free_memory={!r}, domain_dictionary={!r})".format( @@ -243,26 +225,24 @@ def memory_info(xen_free_memory, domain_dictionary): ) ) - # sum of all memory requirements - in other words, the difference between - # memory required to be added to domains (acceptors) to make them be - # at their preferred memory, and memory that can be taken from domains - # (donors) that can provide memory. So, it can be negative when plenty - # of memory. + # Sum of all memory requirements - in other words, the difference between + # memory required to be added to domains (acceptors) to make them be at + # their preferred memory, and memory that can be taken from domains + # (donors) that can provide memory. So, it can be negative when plenty of + # memory. total_memory_needed = 0 - # sum of memory preferences of all domains + # Sum of memory preferences of all domains. total_mem_pref = 0 - # sum of memory preferences of all domains that require more memory + # Sum of memory preferences of all domains that require more memory. total_mem_pref_acceptors = 0 donors = [] acceptors = [] - # pass 1: compute the above "total" values + # Pass 1: compute the above "total" values. for domid, dom in domain_dictionary.items(): - if dom.mem_used is None: - continue - if dom.no_progress: + if dom.mem_used is None or dom.no_progress: continue need = memory_needed(dom) if need < 0 or dom.memory_actual >= dom.memory_maximum: @@ -286,10 +266,10 @@ def memory_info(xen_free_memory, domain_dictionary): return mem_dictionary -# redistribute memory across domains -# called when one of domains update its 'meminfo' xenstore key -# return the list of (domain, memory_target) pairs to be passed to -# "xm memset" equivalent +# Redistribute memory across domains. +# Called when one of domains update its 'meminfo' xenstore key. +# Return the list of (domain, memory_target) pairs to be passed to "xm memset" +# equivalent def balance(xen_free_memory, domain_dictionary): log.debug( "balance(xen_free_memory={!r}, domain_dictionary={!r})".format( diff --git a/qubes/qmemman/systemstate.py b/qubes/qmemman/systemstate.py index 5f6c37eca..21074f115 100644 --- a/qubes/qmemman/systemstate.py +++ b/qubes/qmemman/systemstate.py @@ -40,9 +40,6 @@ MIN_TOTAL_MEMORY_TRANSFER = 150 * 1024 * 1024 MIN_MEM_CHANGE_WHEN_UNDER_PREF = 15 * 1024 * 1024 -no_progress_msg = "VM refused to give back requested memory" -slow_memset_react_msg = "VM didn't give back all requested memory" - class SystemState: def __init__(self): @@ -57,8 +54,8 @@ def __init__(self): def init(self): self.xc = xen.lowlevel.xc.xc() self.xs = xen.lowlevel.xs.xs() - # we divide total and free physical memory by this to get - # "assignable" memory + # We divide total and free physical memory by this to get "assignable" + # memory try: self.all_phys_mem = int( self.xc.physinfo()["total_memory"] * 1024 * MEM_OVERHEAD_FACTOR @@ -66,13 +63,14 @@ def init(self): except xen.lowlevel.xc.Error: pass + def get_xs_path(self, domid, key): + return "/local/domain/" + str(domid) + "/memory/" + key + def add_domain(self, domid): self.log.debug("add_domain(domid={!r})".format(domid)) self.domdict[domid] = DomainState(domid) # TODO: move to DomainState.__init__ - target_str = self.xs.read( - "", "/local/domain/" + domid + "/memory/target" - ) + target_str = self.xs.read("", self.get_xs_path(domid, "target")) if target_str: self.domdict[domid].last_target = int(target_str) * 1024 @@ -84,19 +82,17 @@ def get_free_xen_memory(self): xen_free = int( self.xc.physinfo()["free_memory"] * 1024 * MEM_OVERHEAD_FACTOR ) - # now check for domains which have assigned more memory than really - # used - do not count it as "free", because domain is free to use it - # at any time - # assumption: self.refresh_memactual was called before - # (so dom.memory_actual is up-to-date) + # Check for domains which have assigned more memory than really used - + # do not count it as "free", because domain is free to use it at any + # time. Assumption: self.refresh_memactual was called before (so + # dom.memory_actual is up-to-date) assigned_but_unused = functools.reduce( lambda acc, dom: acc + max(0, dom.last_target - dom.memory_current), self.domdict.values(), 0, ) - # If, at any time, Xen have less memory than XEN_FREE_MEM_MIN, - # it is a failure of qmemman. Collect as much data as possible to - # debug it + # If, at any time, Xen have less memory than XEN_FREE_MEM_MIN, it is a + # failure of qmemman. Collect as much data as possible to debug it if xen_free < XEN_FREE_MEM_MIN: self.log.error( "Xen free = {!r} below acceptable value! " @@ -113,24 +109,24 @@ def get_free_xen_memory(self): ) return xen_free - assigned_but_unused - # refresh information on memory assigned to all domains + # Refresh information on memory assigned to all domains def refresh_memactual(self): for domain in self.xc.domain_getinfo(): domid = str(domain["domid"]) if domid in self.domdict: dom = self.domdict[domid] - # real memory usage + # Real memory usage dom.memory_current = domain["mem_kb"] * 1024 - # what VM is using or can use + # What VM is using or can use dom.memory_actual = max( dom.memory_current, dom.last_target, ) hotplug_max = self.xs.read( - "", "/local/domain/%s/memory/hotplug-max" % str(domid) + "", self.get_xs_path(domid, "hotplug-max") ) static_max = self.xs.read( - "", "/local/domain/%s/memory/static-max" % str(domid) + "", self.get_xs_path(domid, "static-max") ) if hotplug_max: dom.memory_maximum = int(hotplug_max) * 1024 @@ -152,11 +148,11 @@ def clear_outdated_error_markers(self): for dom in self.domdict.values(): if dom.mem_used is None: continue - # clear markers excluding VM from memory balance, if: + # Clear markers excluding VM from memory balance, if: # - VM have responded to previous request (with some safety margin) # - VM request more memory than it has assigned # The second condition avoids starving a VM, even when there is - # some free memory available + # some free memory available. if ( dom.memory_actual <= dom.last_target + XEN_FREE_MEM_LEFT / 2 or dom.memory_actual < qubes.qmemman.algo.prefmem(dom) @@ -164,14 +160,14 @@ def clear_outdated_error_markers(self): dom.slow_memset_react = False dom.no_progress = False - # the below works (and is fast), but then 'xm list' shows unchanged - # memory value + # The below works (and is fast), but then 'xm list' shows unchanged memory + # value. def mem_set(self, domid, val): self.log.info("mem-set domain {} to {}".format(domid, val)) dom = self.domdict[domid] dom.last_target = val - # can happen in the middle of domain shutdown - # apparently xc.lowlevel throws exceptions too + # Can happen in the middle of domain shutdown apparently xc.lowlevel + # throws exceptions too. try: self.xc.domain_setmaxmem( int(domid), int(val / 1024) + 1024 @@ -181,20 +177,21 @@ def mem_set(self, domid, val): pass # VM sees about 16MB memory less, so adjust for it here - qmemman # handle Xen view of memory + # handle Xen view of memory. self.xs.write( "", - "/local/domain/" + domid + "/memory/target", + self.get_xs_path(domid, "target"), str(int(val / 1024 - 16 * 1024)), ) if dom.use_hotplug: self.xs.write( "", - "/local/domain/" + domid + "/memory/static-max", + self.get_xs_path(domid, "static-max"), str(int(val / 1024)), ) - # this is called at the end of ballooning, when we have Xen free mem already - # make sure that past mem_set will not decrease Xen free mem + # This is called at the end of ballooning, when we have Xen free mem + # already, make sure that past mem_set will not decrease Xen free mem. def inhibit_balloon_up(self): self.log.debug("inhibit_balloon_up()") for domid, dom in self.domdict.items(): @@ -207,8 +204,8 @@ def inhibit_balloon_up(self): ) self.mem_set(domid, dom.memory_actual) - # perform memory ballooning, across all domains, to add "memsize" to Xen - # free memory + # Perform memory ballooning, across all domains, to add "memsize" to Xen + # free memory def do_balloon(self, memsize): self.log.info("do_balloon(memsize={!r})".format(memsize)) niter = 0 @@ -282,8 +279,8 @@ def refresh_meminfo(self, domid, untrusted_meminfo_key): ) self.do_balance() - # is the computed balance request big enough ? - # so that we do not trash with small adjustments + # Is the computed balance request big enough so that we do not trash with + # small adjustments. def is_balance_req_significant(self, memset_reqs, xenfree): self.log.debug( "is_balance_req_significant(memset_reqs={}, xenfree={})".format( @@ -293,7 +290,7 @@ def is_balance_req_significant(self, memset_reqs, xenfree): total_memory_transfer = 0 - # If xenfree to low, return immediately + # If xenfree to low, return immediately. if XEN_FREE_MEM_LEFT - xenfree > MIN_MEM_CHANGE_WHEN_UNDER_PREF: self.log.debug("xenfree is too low, returning") return True @@ -340,37 +337,36 @@ def print_stats(self, xenfree, memset_reqs): "stat: xenfree={} memset_reqs={}".format(xenfree, memset_reqs) ) - def debug_stuck_balance(self, domid, memset_reqs, prev_memactual): - for rq2 in memset_reqs: - domid2, mem2 = rq2 - if domid2 == domid: - # All donors have been processed + def debug_stuck_balance(self, stuck_domid, memset_reqs, prev_memactual): + for req in memset_reqs: + domid, mem = req + if domid == stuck_domid: + # All donors have been processed. break - dom2 = self.domdict[domid2] - # allow some small margin - if dom2.memory_actual > dom2.last_target + XEN_FREE_MEM_LEFT / 4: - # VM didn't react to memory request at all, - # remove from donors - if prev_memactual[domid2] == dom2.memory_actual: + dom = self.domdict[domid] + # Allow some small margin. + if dom.memory_actual > dom.last_target + XEN_FREE_MEM_LEFT / 4: + # VM didn't react to memory request at all, remove from donors. + if prev_memactual[domid] == dom.memory_actual: self.log.warning( - "dom {!r} did not react to memory request" - " (holds {}, requested balloon down to {})".format( - domid2, - dom2.memory_actual, - mem2, + "dom {!r} did not react to memory request (holds {}, " + "requested balloon down to {})".format( + domid, + dom.memory_actual, + mem, ) ) - dom2.no_progress = True + dom.no_progress = True else: self.log.warning( - "dom {!r} still holds more" - " memory than assigned ({} > {})".format( - domid2, - dom2.memory_actual, - mem2, + "dom {!r} still holds more memory than assigned ({} > " + "{})".format( + domid, + dom.memory_actual, + mem, ) ) - dom2.slow_memset_react = True + dom.slow_memset_react = True def do_balance(self): self.log.debug("do_balance()") @@ -395,10 +391,9 @@ def do_balance(self): for req in memset_reqs: domid, mem = req dom = self.domdict[domid] - # Force to always have at least 0.9*XEN_FREE_MEM_LEFT (some - # margin for rounding errors). Before giving memory to - # domain, ensure that others have gave it back. - # If not - wait a little. + # Force to always have at least 0.9*XEN_FREE_MEM_LEFT (some margin + # for rounding errors). Before giving memory to domain, ensure that + # others have gave it back. If not, wait a little. ntries = 5 while ( self.get_free_xen_memory() - (mem - dom.memory_actual) @@ -413,8 +408,8 @@ def do_balance(self): self.refresh_memactual() ntries -= 1 if ntries <= 0: - # Waiting haven't helped; Find which domain get stuck and - # abort balance (after distributing what we have) + # Waiting hasn't helped. Find which domain got stuck and + # abort balance (after distributing what we have). self.debug_stuck_balance(domid, memset_reqs, prev_memactual) self.mem_set( domid, From 8d8821492969c6528e5da5c10acd233882e80c9a Mon Sep 17 00:00:00 2001 From: Ben Grande Date: Thu, 10 Jul 2025 17:13:32 +0200 Subject: [PATCH 4/6] Standardize object names --- qubes/qmemman/algo.py | 250 +++++++++++++++++---------------- qubes/qmemman/client.py | 2 +- qubes/qmemman/domainstate.py | 6 +- qubes/qmemman/systemstate.py | 160 +++++++++++----------- qubes/tests/qmemman.py | 258 +++++++++++++++++------------------ qubes/tools/qmemmand.py | 174 +++++++++++------------ qubes/vm/qubesvm.py | 14 +- 7 files changed, 422 insertions(+), 442 deletions(-) diff --git a/qubes/qmemman/algo.py b/qubes/qmemman/algo.py index 70d4dab92..c2adad56d 100644 --- a/qubes/qmemman/algo.py +++ b/qubes/qmemman/algo.py @@ -20,11 +20,14 @@ import logging -# This are only defaults - can be overridden by QMemmanServer with values from -# config file +# These defaults can be overridden by QMemmanServer with values from config +# file. CACHE_FACTOR = 1.3 MIN_PREFMEM = 200 * 1024 * 1024 DOM0_MEM_BOOST = 350 * 1024 * 1024 +# REQ_SAFETY_NET_FACTOR is a bit greater that 1. So that if the domain +# yields a bit less than requested, due to e.g. rounding errors, we will not +# get stuck. The surplus will return to the VM during "balance" call. REQ_SAFETY_NET_FACTOR = 1.05 log = logging.getLogger("qmemman.daemon.algo") @@ -40,177 +43,168 @@ def sanitize_and_parse_meminfo(untrusted_meminfo): return int(untrusted_meminfo) * 1024 -# Called when a domain updates its 'meminfo' xenstore key. -def refresh_meminfo_for_domain(domain, untrusted_xenstore_key): - domain.mem_used = sanitize_and_parse_meminfo(untrusted_xenstore_key) +def refresh_meminfo_for_domain(dom, untrusted_xenstore_key): + """ + Called when a domain updates its 'meminfo' xenstore key. + """ + dom.mem_used = sanitize_and_parse_meminfo(untrusted_xenstore_key) -def prefmem(domain): +def pref_mem(dom): # As dom0 must have large cache for vbds, give it a special boost. - mem_used = domain.mem_used * CACHE_FACTOR - if domain.domid == "0": + mem_used = dom.mem_used * CACHE_FACTOR + if dom.domid == "0": mem_used += DOM0_MEM_BOOST - return int(min(mem_used, domain.memory_maximum)) - return int(max(min(mem_used, domain.memory_maximum), MIN_PREFMEM)) + return int(min(mem_used, dom.mem_max)) + return int(max(min(mem_used, dom.mem_max), MIN_PREFMEM)) -def memory_needed(domain): - # Do not change. In balance(), "distribute total_available_memory - # proportionally to mempref" relies on this exact formula. - ret = prefmem(domain) - domain.memory_actual +def needed_mem(dom): + # Do not change. In balance(), "distribute total_available_mem + # proportionally to pref_mem" relies on this exact formula. + ret = pref_mem(dom) - dom.mem_actual return ret -# Prepare list of (domain, memory_target) pairs that need to be passed to "xm -# memset" equivalent in order to obtain "memsize". +# Prepare list of (dom, mem_target) pairs that need to be passed to "xm +# memset" equivalent in order to obtain "mem_size". # Returns empty list when the request cannot be satisfied. -def balloon(memsize, domain_dictionary): +def balloon(mem_size, dom_dict): log.debug( - "balloon(memsize={!r}, domain_dictionary={!r})".format( - memsize, domain_dictionary - ) + "balloon(mem_size={!r}, dom_dict={!r})".format(mem_size, dom_dict) ) donors = [] request = [] available = 0 - for domid, dom in domain_dictionary.items(): + for domid, dom in dom_dict.items(): if dom.mem_used is None or dom.no_progress: continue - need = memory_needed(dom) + need = needed_mem(dom) if need < 0: log.info( "balloon: dom {} has actual memory {}".format( - domid, dom.memory_actual + domid, dom.mem_actual ) ) donors.append((domid, -need)) available -= need - log.info("req={} avail={} donors={!r}".format(memsize, available, donors)) + log.info("req={} avail={} donors={!r}".format(mem_size, available, donors)) - if available < memsize: + if available < mem_size: return [] - scale = 1.0 * memsize / available + scale = 1.0 * mem_size / available for donors_iter in donors: - dom_id, mem = donors_iter - memborrowed = mem * scale * REQ_SAFETY_NET_FACTOR - log.info("borrow {} from {}".format(memborrowed, dom_id)) - memtarget = int(domain_dictionary[dom_id].memory_actual - memborrowed) - request.append((dom_id, memtarget)) + domid, mem = donors_iter + mem_borrowed = mem * scale * REQ_SAFETY_NET_FACTOR + log.info("borrow {} from {}".format(mem_borrowed, domid)) + mem_target = int(dom_dict[domid].mem_actual - mem_borrowed) + request.append((domid, mem_target)) return request -# REQ_SAFETY_NET_FACTOR is a bit greater that 1. So that if the domain -# yields a bit less than requested, due to e.g. rounding errors, we will not -# get stuck. The surplus will return to the VM during "balance" call. - - -# Redistribute positive "total_available_memory" of memory between domains, -# proportionally to prefmem. -def balance_when_enough_memory( - domain_dictionary, xen_free_memory, total_mem_pref, total_available_memory +# Redistribute positive "total_available_mem" of memory between domains, +# proportionally to pref_mem. +def balance_when_enough_mem( + dom_dict, xen_free_mem, total_mem_pref, total_available_mem ): log.info( - "balance_when_enough_memory(xen_free_memory={!r}, " - "total_mem_pref={!r}, total_available_memory={!r})".format( - xen_free_memory, total_mem_pref, total_available_memory + "balance_when_enough_mem(xen_free_mem={!r}, " + "total_mem_pref={!r}, total_available_mem={!r})".format( + xen_free_mem, total_mem_pref, total_available_mem ) ) - target_memory = {} + target_mem = {} # Memory not assigned because of static max. - left_memory = 0 + mem_left = 0 acceptors_count = 0 - for domid, dom in domain_dictionary.items(): + for domid, dom in dom_dict.items(): if dom.mem_used is None or dom.no_progress: continue - # Distribute total_available_memory proportionally to mempref. - scale = 1.0 * prefmem(dom) / total_mem_pref - target_nonint = prefmem(dom) + scale * total_available_memory + # Distribute total_available_mem proportionally to pref_mem. + scale = 1.0 * pref_mem(dom) / total_mem_pref + target_nonint = pref_mem(dom) + scale * total_available_mem # Prevent rounding errors. target = int(0.999 * target_nonint) # Do not try to give more memory than static max. - if target > dom.memory_maximum: - left_memory += target - dom.memory_maximum - target = dom.memory_maximum + if target > dom.mem_max: + mem_left += target - dom.mem_max + target = dom.mem_max else: # Count domains which can accept more memory. acceptors_count += 1 - target_memory[domid] = target + target_mem[domid] = target # Distribute left memory across all acceptors. - while left_memory > 0 and acceptors_count > 0: + while mem_left > 0 and acceptors_count > 0: log.info( - "left_memory={} acceptors_count={}".format( - left_memory, acceptors_count - ) + "mem_left={} acceptors_count={}".format(mem_left, acceptors_count) ) - new_left_memory = 0 + new_mem_left = 0 new_acceptors_count = acceptors_count - for domid, target in target_memory.items(): - dom = domain_dictionary[domid] - if target < dom.memory_maximum: - memory_bonus = int(0.999 * (left_memory / acceptors_count)) - if target + memory_bonus >= dom.memory_maximum: - new_left_memory += ( - target + memory_bonus - dom.memory_maximum - ) - target = dom.memory_maximum + for domid, target in target_mem.items(): + dom = dom_dict[domid] + if target < dom.mem_max: + mem_bonus = int(0.999 * (mem_left / acceptors_count)) + if target + mem_bonus >= dom.mem_max: + new_mem_left += target + mem_bonus - dom.mem_max + target = dom.mem_max new_acceptors_count -= 1 else: - target += memory_bonus - target_memory[domid] = target - left_memory = new_left_memory + target += mem_bonus + target_mem[domid] = target + mem_left = new_mem_left acceptors_count = new_acceptors_count - # Split target_memory dictionary to donors and acceptors. This is needed to + # Split target_mem dictionary to donors and acceptors. This is needed to # first get memory from donors and only then give it to acceptors. donors_rq = [] acceptors_rq = [] - for domid, target in target_memory.items(): - dom = domain_dictionary[domid] - if target < dom.memory_actual: + for domid, target in target_mem.items(): + dom = dom_dict[domid] + if target < dom.mem_actual: donors_rq.append((domid, target)) else: acceptors_rq.append((domid, target)) return donors_rq + acceptors_rq -# When not enough mem to make everyone be above prefmem, make donors be at -# prefmem, and redistribute anything left between acceptors. -def balance_when_low_on_memory( - domain_dictionary, - xen_free_memory, +# When not enough mem to make everyone be above pref_mem, make donors be at +# pref_mem, and redistribute anything left between acceptors. +def balance_when_low_on_mem( + dom_dict, + xen_free_mem, total_mem_pref_acceptors, donors, acceptors, ): log.info( - "balance_when_low_on_memory(xen_free_memory={!r}, " + "balance_when_low_on_mem(xen_free_mem={!r}, " "total_mem_pref_acceptors={!r}, donors={!r}, acceptors={!r})".format( - xen_free_memory, total_mem_pref_acceptors, donors, acceptors + xen_free_mem, total_mem_pref_acceptors, donors, acceptors ) ) donors_rq = [] acceptors_rq = [] - squeezed_mem = xen_free_memory + squeezed_mem = xen_free_mem for domid in donors: - dom = domain_dictionary[domid] - avail = -memory_needed(dom) + dom = dom_dict[domid] + avail = -needed_mem(dom) if avail < 10 * 1024 * 1024: - # Probably we have already tried making it exactly at prefmem, give + # Probably we have already tried making it exactly at pref_mem, give # up. continue squeezed_mem -= avail - donors_rq.append((domid, prefmem(dom))) + donors_rq.append((domid, pref_mem(dom))) # The below condition can happen if initially xen free memory is below 50M. if squeezed_mem < 0: return donors_rq for domid in acceptors: - dom = domain_dictionary[domid] - scale = 1.0 * prefmem(dom) / total_mem_pref_acceptors - target_nonint = dom.memory_actual + scale * squeezed_mem + dom = dom_dict[domid] + scale = 1.0 * pref_mem(dom) / total_mem_pref_acceptors + target_nonint = dom.mem_actual + scale * squeezed_mem # Do not try to give more memory than static max. - target = min(int(0.999 * target_nonint), dom.memory_maximum) + target = min(int(0.999 * target_nonint), dom.mem_max) acceptors_rq.append((domid, target)) return donors_rq + acceptors_rq @@ -218,10 +212,10 @@ def balance_when_low_on_memory( # Get memory information. # Called before and after domain balances. # Return a dictionary of various memory data points. -def memory_info(xen_free_memory, domain_dictionary): +def mem_info(xen_free_mem, dom_dict): log.debug( - "memory_info(xen_free_memory={!r}, domain_dictionary={!r})".format( - xen_free_memory, domain_dictionary + "mem_info(xen_free_mem={!r}, dom_dict={!r})".format( + xen_free_mem, dom_dict ) ) @@ -230,7 +224,7 @@ def memory_info(xen_free_memory, domain_dictionary): # their preferred memory, and memory that can be taken from domains # (donors) that can provide memory. So, it can be negative when plenty of # memory. - total_memory_needed = 0 + total_needed_mem = 0 # Sum of memory preferences of all domains. total_mem_pref = 0 @@ -241,54 +235,54 @@ def memory_info(xen_free_memory, domain_dictionary): donors = [] acceptors = [] # Pass 1: compute the above "total" values. - for domid, dom in domain_dictionary.items(): + for domid, dom in dom_dict.items(): if dom.mem_used is None or dom.no_progress: continue - need = memory_needed(dom) - if need < 0 or dom.memory_actual >= dom.memory_maximum: + need = needed_mem(dom) + if need < 0 or dom.mem_actual >= dom.mem_max: donors.append(domid) else: acceptors.append(domid) - total_mem_pref_acceptors += prefmem(dom) - total_memory_needed += need - total_mem_pref += prefmem(dom) + total_mem_pref_acceptors += pref_mem(dom) + total_needed_mem += need + total_mem_pref += pref_mem(dom) - total_available_memory = xen_free_memory - total_memory_needed + total_available_mem = xen_free_mem - total_needed_mem - mem_dictionary = {} - mem_dictionary["domain_dictionary"] = domain_dictionary - mem_dictionary["total_available_memory"] = total_available_memory - mem_dictionary["xen_free_memory"] = xen_free_memory - mem_dictionary["total_mem_pref"] = total_mem_pref - mem_dictionary["total_mem_pref_acceptors"] = total_mem_pref_acceptors - mem_dictionary["donors"] = donors - mem_dictionary["acceptors"] = acceptors - return mem_dictionary + mem_dict = {} + mem_dict["dom_dict"] = dom_dict + mem_dict["total_available_mem"] = total_available_mem + mem_dict["xen_free_mem"] = xen_free_mem + mem_dict["total_mem_pref"] = total_mem_pref + mem_dict["total_mem_pref_acceptors"] = total_mem_pref_acceptors + mem_dict["donors"] = donors + mem_dict["acceptors"] = acceptors + return mem_dict # Redistribute memory across domains. # Called when one of domains update its 'meminfo' xenstore key. -# Return the list of (domain, memory_target) pairs to be passed to "xm memset" +# Return the list of (domain, mem_target) pairs to be passed to "xm memset" # equivalent -def balance(xen_free_memory, domain_dictionary): +def balance(xen_free_mem, dom_dict): log.debug( - "balance(xen_free_memory={!r}, domain_dictionary={!r})".format( - xen_free_memory, domain_dictionary + "balance(xen_free_mem={!r}, dom_dict={!r})".format( + xen_free_mem, dom_dict ) ) - memory_dictionary = memory_info(xen_free_memory, domain_dictionary) - - if memory_dictionary["total_available_memory"] > 0: - return balance_when_enough_memory( - memory_dictionary["domain_dictionary"], - memory_dictionary["xen_free_memory"], - memory_dictionary["total_mem_pref"], - memory_dictionary["total_available_memory"], + mem_dict = mem_info(xen_free_mem, dom_dict) + + if mem_dict["total_available_mem"] > 0: + return balance_when_enough_mem( + mem_dict["dom_dict"], + mem_dict["xen_free_mem"], + mem_dict["total_mem_pref"], + mem_dict["total_available_mem"], ) - return balance_when_low_on_memory( - memory_dictionary["domain_dictionary"], - memory_dictionary["xen_free_memory"], - memory_dictionary["total_mem_pref_acceptors"], - memory_dictionary["donors"], - memory_dictionary["acceptors"], + return balance_when_low_on_mem( + mem_dict["dom_dict"], + mem_dict["xen_free_mem"], + mem_dict["total_mem_pref_acceptors"], + mem_dict["donors"], + mem_dict["acceptors"], ) diff --git a/qubes/qmemman/client.py b/qubes/qmemman/client.py index ca1d84547..f42f2493f 100644 --- a/qubes/qmemman/client.py +++ b/qubes/qmemman/client.py @@ -25,7 +25,7 @@ class QMemmanClient: def __init__(self): self.sock = None - def request_memory(self, amount): + def request_mem(self, amount): self.sock = socket.socket(socket.AF_UNIX) flags = fcntl.fcntl(self.sock.fileno(), fcntl.F_GETFD) flags |= fcntl.FD_CLOEXEC diff --git a/qubes/qmemman/domainstate.py b/qubes/qmemman/domainstate.py index 9e3be5f45..9081d401c 100644 --- a/qubes/qmemman/domainstate.py +++ b/qubes/qmemman/domainstate.py @@ -21,10 +21,10 @@ class DomainState: # pylint: disable=too-few-public-methods def __init__(self, domid): - self.memory_current = 0 # the current memory size - self.memory_actual = None # the current memory allocation (what VM + self.mem_current = 0 # the current memory size + self.mem_actual = None # the current memory allocation (what VM # is using or can use at any time) - self.memory_maximum = None # the maximum memory size + self.mem_max = None # the maximum memory size self.mem_used = None # used memory, computed based on meminfo self.domid = domid # domain id self.last_target = 0 # the last memset target diff --git a/qubes/qmemman/systemstate.py b/qubes/qmemman/systemstate.py index 21074f115..4cfae76b7 100644 --- a/qubes/qmemman/systemstate.py +++ b/qubes/qmemman/systemstate.py @@ -46,7 +46,7 @@ def __init__(self): self.log = logging.getLogger("qmemman.systemstate") self.log.debug("SystemState()") - self.domdict = {} + self.dom_dict = {} self.xc = None self.xs = None self.all_phys_mem = 0 @@ -68,27 +68,27 @@ def get_xs_path(self, domid, key): def add_domain(self, domid): self.log.debug("add_domain(domid={!r})".format(domid)) - self.domdict[domid] = DomainState(domid) + self.dom_dict[domid] = DomainState(domid) # TODO: move to DomainState.__init__ target_str = self.xs.read("", self.get_xs_path(domid, "target")) if target_str: - self.domdict[domid].last_target = int(target_str) * 1024 + self.dom_dict[domid].last_target = int(target_str) * 1024 def del_domain(self, domid): self.log.debug("del_domain(domid={!r})".format(domid)) - self.domdict.pop(domid) + self.dom_dict.pop(domid) - def get_free_xen_memory(self): + def get_free_xen_mem(self): xen_free = int( self.xc.physinfo()["free_memory"] * 1024 * MEM_OVERHEAD_FACTOR ) # Check for domains which have assigned more memory than really used - # do not count it as "free", because domain is free to use it at any - # time. Assumption: self.refresh_memactual was called before (so - # dom.memory_actual is up-to-date) + # time. Assumption: self.refresh_mem_actual was called before (so + # dom.mem_actual is up-to-date) assigned_but_unused = functools.reduce( - lambda acc, dom: acc + max(0, dom.last_target - dom.memory_current), - self.domdict.values(), + lambda acc, dom: acc + max(0, dom.last_target - dom.mem_current), + self.dom_dict.values(), 0, ) # If, at any time, Xen have less memory than XEN_FREE_MEM_MIN, it is a @@ -96,30 +96,30 @@ def get_free_xen_memory(self): if xen_free < XEN_FREE_MEM_MIN: self.log.error( "Xen free = {!r} below acceptable value! " - "assigned_but_unused={!r}, domdict={!r}".format( - xen_free, assigned_but_unused, self.domdict + "assigned_but_unused={!r}, dom_dict={!r}".format( + xen_free, assigned_but_unused, self.dom_dict ) ) elif xen_free < assigned_but_unused + XEN_FREE_MEM_MIN: self.log.error( "Xen free = {!r} too small to satisfy assignments! " - "assigned_but_unused={!r}, domdict={!r}".format( - xen_free, assigned_but_unused, self.domdict + "assigned_but_unused={!r}, dom_dict={!r}".format( + xen_free, assigned_but_unused, self.dom_dict ) ) return xen_free - assigned_but_unused # Refresh information on memory assigned to all domains - def refresh_memactual(self): + def refresh_mem_actual(self): for domain in self.xc.domain_getinfo(): domid = str(domain["domid"]) - if domid in self.domdict: - dom = self.domdict[domid] + if domid in self.dom_dict: + dom = self.dom_dict[domid] # Real memory usage - dom.memory_current = domain["mem_kb"] * 1024 + dom.mem_current = domain["mem_kb"] * 1024 # What VM is using or can use - dom.memory_actual = max( - dom.memory_current, + dom.mem_actual = max( + dom.mem_current, dom.last_target, ) hotplug_max = self.xs.read( @@ -129,15 +129,15 @@ def refresh_memactual(self): "", self.get_xs_path(domid, "static-max") ) if hotplug_max: - dom.memory_maximum = int(hotplug_max) * 1024 + dom.mem_max = int(hotplug_max) * 1024 dom.use_hotplug = True elif static_max: - dom.memory_maximum = int(static_max) * 1024 + dom.mem_max = int(static_max) * 1024 dom.use_hotplug = False else: - dom.memory_maximum = self.all_phys_mem + dom.mem_max = self.all_phys_mem # the previous line used to be - # dom.memory_maximum = domain['maxmem_kb']*1024 + # dom.mem_max = domain['maxmem_kb']*1024 # but domain['maxmem_kb'] changes in self.mem_set as well, # and this results in the memory never increasing in fact, # the only possible case of nonexisting memory/static-max @@ -145,7 +145,7 @@ def refresh_memactual(self): def clear_outdated_error_markers(self): # Clear outdated errors - for dom in self.domdict.values(): + for dom in self.dom_dict.values(): if dom.mem_used is None: continue # Clear markers excluding VM from memory balance, if: @@ -154,8 +154,8 @@ def clear_outdated_error_markers(self): # The second condition avoids starving a VM, even when there is # some free memory available. if ( - dom.memory_actual <= dom.last_target + XEN_FREE_MEM_LEFT / 2 - or dom.memory_actual < qubes.qmemman.algo.prefmem(dom) + dom.mem_actual <= dom.last_target + XEN_FREE_MEM_LEFT / 2 + or dom.mem_actual < qubes.qmemman.algo.pref_mem(dom) ): dom.slow_memset_react = False dom.no_progress = False @@ -164,7 +164,7 @@ def clear_outdated_error_markers(self): # value. def mem_set(self, domid, val): self.log.info("mem-set domain {} to {}".format(domid, val)) - dom = self.domdict[domid] + dom = self.dom_dict[domid] dom.last_target = val # Can happen in the middle of domain shutdown apparently xc.lowlevel # throws exceptions too. @@ -194,24 +194,24 @@ def mem_set(self, domid, val): # already, make sure that past mem_set will not decrease Xen free mem. def inhibit_balloon_up(self): self.log.debug("inhibit_balloon_up()") - for domid, dom in self.domdict.items(): + for domid, dom in self.dom_dict.items(): if ( - dom.memory_actual is not None - and dom.memory_actual + 200 * 1024 < dom.last_target + dom.mem_actual is not None + and dom.mem_actual + 200 * 1024 < dom.last_target ): self.log.info( "Preventing balloon up to {}".format(dom.last_target) ) - self.mem_set(domid, dom.memory_actual) + self.mem_set(domid, dom.mem_actual) - # Perform memory ballooning, across all domains, to add "memsize" to Xen + # Perform memory ballooning, across all domains, to add "mem_size" to Xen # free memory - def do_balloon(self, memsize): - self.log.info("do_balloon(memsize={!r})".format(memsize)) + def do_balloon(self, mem_size): + self.log.info("do_balloon(mem_size={!r})".format(mem_size)) niter = 0 - prev_memory_actual = None + prev_mem_actual = None - for dom in self.domdict.values(): + for dom in self.dom_dict.values(): dom.no_progress = False #: number of loop iterations for CHECK_PERIOD_S seconds @@ -225,10 +225,10 @@ def do_balloon(self, memsize): while True: self.log.debug("niter={:2d}".format(niter)) - self.refresh_memactual() - xenfree = self.get_free_xen_memory() + self.refresh_mem_actual() + xenfree = self.get_free_xen_mem() self.log.info("xenfree={!r}".format(xenfree)) - if xenfree >= memsize + XEN_FREE_MEM_MIN: + if xenfree >= mem_size + XEN_FREE_MEM_MIN: self.inhibit_balloon_up() return True # fail the request if over past CHECK_PERIOD_S seconds, @@ -240,29 +240,29 @@ def do_balloon(self, memsize): ): return False xenfree_ring[ring_slot] = xenfree - if prev_memory_actual is not None: - for domid, prev_mem in prev_memory_actual.items(): - dom = self.domdict[domid] + if prev_mem_actual is not None: + for domid, prev_mem in prev_mem_actual.items(): + dom = self.dom_dict[domid] if prev_mem == dom.memory_actual: # domain not responding to memset requests, remove it # from donors dom.no_progress = True self.log.info( "domain {} stuck at {}".format( - domid, dom.memory_actual + domid, dom.mem_actual ) ) memset_reqs = qubes.qmemman.algo.balloon( - memsize + XEN_FREE_MEM_LEFT - xenfree, self.domdict + mem_size + XEN_FREE_MEM_LEFT - xenfree, self.dom_dict ) self.log.info("memset_reqs={!r}".format(memset_reqs)) if len(memset_reqs) == 0: return False - prev_memory_actual = {} + prev_mem_actual = {} for req in memset_reqs: dom, mem = req self.mem_set(dom, mem) - prev_memory_actual[dom] = self.domdict[dom].memory_actual + prev_mem_actual[dom] = self.dom_dict[dom].mem_actual self.log.debug("sleeping for {} s".format(BALOON_DELAY)) time.sleep(BALOON_DELAY) niter = niter + 1 @@ -275,7 +275,7 @@ def refresh_meminfo(self, domid, untrusted_meminfo_key): ) qubes.qmemman.algo.refresh_meminfo_for_domain( - self.domdict[domid], untrusted_meminfo_key + self.dom_dict[domid], untrusted_meminfo_key ) self.do_balance() @@ -288,7 +288,7 @@ def is_balance_req_significant(self, memset_reqs, xenfree): ) ) - total_memory_transfer = 0 + total_mem_transfer = 0 # If xenfree to low, return immediately. if XEN_FREE_MEM_LEFT - xenfree > MIN_MEM_CHANGE_WHEN_UNDER_PREF: @@ -297,14 +297,14 @@ def is_balance_req_significant(self, memset_reqs, xenfree): for req in memset_reqs: dom, mem = req - last_target = self.domdict[dom].last_target - memory_change = mem - last_target - total_memory_transfer += abs(memory_change) - pref = qubes.qmemman.algo.prefmem(self.domdict[dom]) + last_target = self.dom_dict[dom].last_target + mem_change = mem - last_target + total_mem_transfer += abs(mem_change) + pref = qubes.qmemman.algo.pref_mem(self.dom_dict[dom]) if ( 0 < last_target < pref - and memory_change > MIN_MEM_CHANGE_WHEN_UNDER_PREF + and mem_change > MIN_MEM_CHANGE_WHEN_UNDER_PREF ): self.log.info( "dom {} is below pref, allowing balance".format(dom) @@ -312,21 +312,21 @@ def is_balance_req_significant(self, memset_reqs, xenfree): return True ret = ( - total_memory_transfer + abs(xenfree - XEN_FREE_MEM_LEFT) + total_mem_transfer + abs(xenfree - XEN_FREE_MEM_LEFT) > MIN_TOTAL_MEMORY_TRANSFER ) self.log.debug("is_balance_req_significant return {}".format(ret)) return ret def print_stats(self, xenfree, memset_reqs): - for domid, dom in self.domdict.items(): + for domid, dom in self.dom_dict.items(): if dom.mem_used is not None: self.log.info( "stat: dom {!r} act={} pref={} last_target={}" "{}{}".format( domid, - dom.memory_actual, - qubes.qmemman.algo.prefmem(dom), + dom.mem_actual, + qubes.qmemman.algo.pref_mem(dom), dom.last_target, " no_progress" if dom.no_progress else "", (" slow_memset_react" if dom.slow_memset_react else ""), @@ -337,22 +337,22 @@ def print_stats(self, xenfree, memset_reqs): "stat: xenfree={} memset_reqs={}".format(xenfree, memset_reqs) ) - def debug_stuck_balance(self, stuck_domid, memset_reqs, prev_memactual): + def debug_stuck_balance(self, stuck_domid, memset_reqs, prev_mem_actual): for req in memset_reqs: domid, mem = req if domid == stuck_domid: # All donors have been processed. break - dom = self.domdict[domid] + dom = self.dom_dict[domid] # Allow some small margin. - if dom.memory_actual > dom.last_target + XEN_FREE_MEM_LEFT / 4: + if dom.mem_actual > dom.last_target + XEN_FREE_MEM_LEFT / 4: # VM didn't react to memory request at all, remove from donors. - if prev_memactual[domid] == dom.memory_actual: + if prev_mem_actual[domid] == dom.mem_actual: self.log.warning( "dom {!r} did not react to memory request (holds {}, " "requested balloon down to {})".format( domid, - dom.memory_actual, + dom.mem_actual, mem, ) ) @@ -362,7 +362,7 @@ def debug_stuck_balance(self, stuck_domid, memset_reqs, prev_memactual): "dom {!r} still holds more memory than assigned ({} > " "{})".format( domid, - dom.memory_actual, + dom.mem_actual, mem, ) ) @@ -374,29 +374,29 @@ def do_balance(self): self.log.debug("do-not-membalance file present, returning") return - self.refresh_memactual() + self.refresh_mem_actual() self.clear_outdated_error_markers() - xenfree = self.get_free_xen_memory() + xenfree = self.get_free_xen_mem() memset_reqs = qubes.qmemman.algo.balance( - xenfree - XEN_FREE_MEM_LEFT, self.domdict + xenfree - XEN_FREE_MEM_LEFT, self.dom_dict ) if not self.is_balance_req_significant(memset_reqs, xenfree): return self.print_stats(xenfree, memset_reqs) - prev_memactual = {} - for domid, dom in self.domdict.items(): - prev_memactual[domid] = dom.memory_actual + prev_mem_actual = {} + for domid, dom in self.dom_dict.items(): + prev_mem_actual[domid] = dom.mem_actual for req in memset_reqs: domid, mem = req - dom = self.domdict[domid] + dom = self.dom_dict[domid] # Force to always have at least 0.9*XEN_FREE_MEM_LEFT (some margin # for rounding errors). Before giving memory to domain, ensure that # others have gave it back. If not, wait a little. ntries = 5 while ( - self.get_free_xen_memory() - (mem - dom.memory_actual) + self.get_free_xen_mem() - (mem - dom.mem_actual) < 0.9 * XEN_FREE_MEM_LEFT ): self.log.debug( @@ -405,29 +405,31 @@ def do_balance(self): ) ) time.sleep(BALOON_DELAY) - self.refresh_memactual() + self.refresh_mem_actual() ntries -= 1 if ntries <= 0: # Waiting hasn't helped. Find which domain got stuck and # abort balance (after distributing what we have). - self.debug_stuck_balance(domid, memset_reqs, prev_memactual) + self.debug_stuck_balance( + domid, memset_reqs, prev_mem_actual + ) self.mem_set( domid, - self.get_free_xen_memory() - + dom.memory_actual + self.get_free_xen_mem() + + dom.mem_actual - XEN_FREE_MEM_LEFT, ) return self.mem_set(domid, mem) - xenfree = self.get_free_xen_memory() - memory_dictionary = qubes.qmemman.algo.memory_info( - xenfree - XEN_FREE_MEM_LEFT, self.domdict + xenfree = self.get_free_xen_mem() + mem_dict = qubes.qmemman.algo.mem_info( + xenfree - XEN_FREE_MEM_LEFT, self.dom_dict ) avail_mem_file = qubes.config.qmemman_avail_mem_file avail_mem_file_tmp = Path(avail_mem_file).with_suffix(".tmp") with open(avail_mem_file_tmp, "w", encoding="ascii") as file: - file.write(str(memory_dictionary["total_available_memory"])) + file.write(str(mem_dict["total_available_mem"])) os.chmod(avail_mem_file_tmp, 0o644) os.replace(avail_mem_file_tmp, avail_mem_file) diff --git a/qubes/tests/qmemman.py b/qubes/tests/qmemman.py index 2517880ff..b520227c5 100644 --- a/qubes/tests/qmemman.py +++ b/qubes/tests/qmemman.py @@ -23,28 +23,26 @@ import qubes.tests - -def MB(val): - return int(val * 1024 * 1024) +MB = 1024 * 1024 def construct_dominfo( - id, + domid, mem_used=None, - memory_maximum=None, - memory_actual=None, - memory_current=0, + mem_max=None, + mem_actual=None, + mem_current=0, last_target=0, use_hotplug=False, ): - d = qubes.qmemman.domainstate.DomainState(id) - d.mem_used = mem_used - d.memory_maximum = memory_maximum - d.memory_actual = memory_actual - d.memory_current = memory_current - d.last_target = last_target - d.use_hotplug = use_hotplug - return d + dom = qubes.qmemman.domainstate.DomainState(domid) + dom.mem_used = mem_used + dom.mem_max = mem_max + dom.mem_actual = mem_actual + dom.mem_current = mem_current + dom.last_target = last_target + dom.use_hotplug = use_hotplug + return dom class TC_00_Qmemman_algo(qubes.tests.QubesTestCase): @@ -66,141 +64,141 @@ def test_000_meminfo(self): qubes.qmemman.algo.sanitize_and_parse_meminfo(b"4096\n1024") ) - def test_010_prefmem_dom0(self): - d = qubes.qmemman.domainstate.DomainState("0") - d.mem_used = MB(1024) - d.memory_maximum = MB(4096) - self.assertEqual(qubes.qmemman.algo.prefmem(d), MB(1681.2)) - d.mem_used = MB(5000) - self.assertEqual(qubes.qmemman.algo.prefmem(d), MB(4096)) + def test_010_pref_mem_dom0(self): + dom = qubes.qmemman.domainstate.DomainState("0") + dom.mem_used = int(1024 * MB) + dom.mem_max = int(4096 * MB) + self.assertEqual(qubes.qmemman.algo.pref_mem(dom), int(1681.2 * MB)) + dom.mem_used = int(5000 * MB) + self.assertEqual(qubes.qmemman.algo.pref_mem(dom), int(4096 * MB)) - def test_011_prefmem_domU(self): - d = qubes.qmemman.domainstate.DomainState("10") - d.mem_used = MB(1024) - d.memory_maximum = MB(4096) - self.assertEqual(qubes.qmemman.algo.prefmem(d), MB(1331.2)) - d.mem_used = MB(5000) - self.assertEqual(qubes.qmemman.algo.prefmem(d), MB(4096)) + def test_011_pref_mem_domU(self): # pylint: disable=invalid-name + dom = qubes.qmemman.domainstate.DomainState("10") + dom.mem_used = int(1024 * MB) + dom.mem_max = int(4096 * MB) + self.assertEqual(qubes.qmemman.algo.pref_mem(dom), int(1331.2 * MB)) + dom.mem_used = int(5000 * MB) + self.assertEqual(qubes.qmemman.algo.pref_mem(dom), int(4096 * MB)) - def test_020_memory_needed(self): - d = qubes.qmemman.domainstate.DomainState("10") - d.mem_used = MB(1024) - d.memory_maximum = MB(4096) - d.memory_actual = MB(1024) - self.assertEqual(qubes.qmemman.algo.memory_needed(d), MB(307.2)) + def test_020_needed_mem(self): + dom = qubes.qmemman.domainstate.DomainState("10") + dom.mem_used = int(1024 * MB) + dom.mem_max = int(4096 * MB) + dom.mem_actual = int(1024 * MB) + self.assertEqual(qubes.qmemman.algo.needed_mem(dom), int(307.2 * MB)) - d.memory_actual = MB(2024) - self.assertEqual(qubes.qmemman.algo.memory_needed(d), MB(-692.800001)) + dom.mem_actual = int(2024 * MB) + self.assertEqual( + qubes.qmemman.algo.needed_mem(dom), int(-692.800001 * MB) + ) def test_100_balloon(self): domains = { "0": construct_dominfo( "0", - mem_used=MB(1024), - memory_maximum=MB(4096), - memory_actual=MB(1736), - memory_current=MB(1736), + mem_used=int(1024 * MB), + mem_max=int(4096 * MB), + mem_actual=int(1736 * MB), + mem_current=int(1736 * MB), ), "1": construct_dominfo( "1", - mem_used=MB(1024), - memory_maximum=MB(4096), - memory_actual=MB(1536), - memory_current=MB(1536), + mem_used=int(1024 * MB), + mem_max=int(4096 * MB), + mem_actual=int(1536 * MB), + mem_current=int(1536 * MB), ), - # at prefmem + # at pref_mem "2": construct_dominfo( "2", - mem_used=MB(4096), - memory_maximum=MB(4096), - memory_actual=MB(4096), - memory_current=MB(4096), + mem_used=int(4096 * MB), + mem_max=int(4096 * MB), + mem_actual=int(4096 * MB), + mem_current=int(4096 * MB), ), # no meminfo at all "3": construct_dominfo( "3", mem_used=None, - memory_maximum=MB(4096), - memory_actual=MB(4096), - memory_current=MB(4096), + mem_max=int(4096 * MB), + mem_actual=int(4096 * MB), + mem_current=int(4096 * MB), ), } - result = qubes.qmemman.algo.balloon(MB(400), domains) + result = qubes.qmemman.algo.balloon(int(400 * MB), domains) expected = [] self.assertEqual(result, expected) - domains["1"].mem_used = MB(512) + domains["1"].mem_used = int(512 * MB) - result = qubes.qmemman.algo.balloon(MB(400), domains) - released = sum(domains[l[0]].memory_current - l[1] for l in result) + result = qubes.qmemman.algo.balloon(int(400 * MB), domains) + released = sum(domains[l[0]].mem_current - l[1] for l in result) expected = [("0", 1794242737), ("1", 1196296014)] - self.assertGreater(released, MB(400)) + self.assertGreater(released, int(400 * MB)) # should be within about 5% margin - self.assertLess(released - MB(400), MB(21)) + self.assertLess(released - int(400 * MB), int(21 * MB)) self.assertEqual(result, expected) - def test_200_balance_whan_enough_memory(self): + def test_200_balance_when_enough_mem(self): domains = { "0": construct_dominfo( "0", - mem_used=MB(1024), - memory_maximum=MB(4096), - memory_actual=MB(1736), - memory_current=MB(1736), + mem_used=int(1024 * MB), + mem_max=int(4096 * MB), + mem_actual=int(1736 * MB), + mem_current=int(1736 * MB), ), "1": construct_dominfo( "1", - mem_used=MB(1024), - memory_maximum=MB(4096), - memory_actual=MB(1536), - memory_current=MB(1536), + mem_used=int(1024 * MB), + mem_max=int(4096 * MB), + mem_actual=int(1536 * MB), + mem_current=int(1536 * MB), ), # at maxmem "2": construct_dominfo( "2", - mem_used=MB(4096), - memory_maximum=MB(4096), - memory_actual=MB(4096), - memory_current=MB(4096), + mem_used=int(4096 * MB), + mem_max=int(4096 * MB), + mem_actual=int(4096 * MB), + mem_current=int(4096 * MB), ), # no meminfo at all "3": construct_dominfo( "3", mem_used=None, - memory_maximum=MB(4096), - memory_actual=MB(4096), - memory_current=MB(4096), + mem_max=int(4096 * MB), + mem_actual=int(4096 * MB), + mem_current=int(4096 * MB), ), - # at prefmem, but can get more + # at pref_mem, but can get more "4": construct_dominfo( "4", - mem_used=MB(1536), - memory_maximum=MB(4096), - memory_actual=MB(1536), - memory_current=MB(1536), + mem_used=int(1536 * MB), + mem_max=int(4096 * MB), + mem_actual=int(1536 * MB), + mem_current=int(1536 * MB), ), # low maxmem "5": construct_dominfo( "5", - mem_used=MB(512), - memory_maximum=MB(1024), - memory_actual=MB(768), - memory_current=MB(768), + mem_used=int(512 * MB), + mem_max=int(1024 * MB), + mem_actual=int(768 * MB), + mem_current=int(768 * MB), ), } - total_prefmem = sum( - qubes.qmemman.algo.prefmem(d) - for d in domains.values() - if d.mem_used is not None - ) - # xen_free_memory is ignored, use dummy 0 there - result = qubes.qmemman.algo.balance_when_enough_memory( - domains, 0, total_prefmem, MB(4096) + total_pref_mem = sum( + qubes.qmemman.algo.pref_mem(dom) + for dom in domains.values() + if dom.mem_used is not None ) - total_allocated = sum( - l[1] - domains[l[0]].memory_actual for l in result + # xen_free_mem is ignored, use dummy 0 there + result = qubes.qmemman.algo.balance_when_enough_mem( + domains, 0, total_pref_mem, int(4096 * MB) ) + total_allocated = sum(l[1] - domains[l[0]].mem_actual for l in result) # FIXME: the current algo is broken here, thus +5% - self.assertLess(total_allocated, MB(4096) * 1.05) + self.assertLess(total_allocated, int(4096 * MB) * 1.05) # should be no repeats self.assertEqual( len(result), @@ -209,11 +207,11 @@ def test_200_balance_whan_enough_memory(self): ) # no meminfo -> no adjustment self.assertNotIn(("3", unittest.mock.ANY), result) - # prefmem==maxmem==current, shouldn't adjust + # pref_mem==maxmem==current, shouldn't adjust request = [x for x in result if x[0] == "2"][0] - self.assertEqual(request[1], domains["2"].memory_actual) + self.assertEqual(request[1], domains["2"].mem_actual) - # bigger prefmem -> bigger target + # bigger pref_mem -> bigger target request1 = [x for x in result if x[0] == "1"][0] # mem_used 1GB request2 = [x for x in result if x[0] == "4"][0] # mem_used 1.5GB self.assertGreater(request2[1], request1[1]) @@ -231,59 +229,59 @@ def test_200_balance_whan_enough_memory(self): ], ) - def test_250_balance_low_on_memory(self): + def test_250_balance_when_low_on_mem(self): domains = { - # below prefmem + # below pref_mem "0": construct_dominfo( "0", - mem_used=MB(1024), - memory_maximum=MB(4096), - memory_actual=MB(768), - memory_current=MB(768), + mem_used=int(1024 * MB), + mem_max=int(4096 * MB), + mem_actual=int(768 * MB), + mem_current=int(768 * MB), ), "1": construct_dominfo( "1", - mem_used=MB(1024), - memory_maximum=MB(4096), - memory_actual=MB(1536), - memory_current=MB(1536), + mem_used=int(1024 * MB), + mem_max=int(4096 * MB), + mem_actual=int(1536 * MB), + mem_current=int(1536 * MB), ), # at maxmem "2": construct_dominfo( "2", - mem_used=MB(4096), - memory_maximum=MB(4096), - memory_actual=MB(4096), - memory_current=MB(4096), + mem_used=int(4096 * MB), + mem_max=int(4096 * MB), + mem_actual=int(4096 * MB), + mem_current=int(4096 * MB), ), # no meminfo at all "3": construct_dominfo( "3", mem_used=None, - memory_maximum=MB(4096), - memory_actual=MB(4096), - memory_current=MB(4096), + mem_max=int(4096 * MB), + mem_actual=int(4096 * MB), + mem_current=int(4096 * MB), ), - # at prefmem, but can get more + # at pref_mem, but can get more "4": construct_dominfo( "4", - mem_used=MB(1536), - memory_maximum=MB(4096), - memory_actual=MB(1536), - memory_current=MB(1536), + mem_used=int(1536 * MB), + mem_max=int(4096 * MB), + mem_actual=int(1536 * MB), + mem_current=int(1536 * MB), ), # low maxmem "5": construct_dominfo( "5", - mem_used=MB(512), - memory_maximum=MB(1024), - memory_actual=MB(768), - memory_current=MB(768), + mem_used=int(512 * MB), + mem_max=int(1024 * MB), + mem_actual=int(768 * MB), + mem_current=int(768 * MB), ), } - # call "balance" instead of "balance_low_on_memory" directly, + # call "balance" instead of "balance_when_low_on_mem" directly, # to collect donors/acceptors list - result = qubes.qmemman.algo.balance(MB(50), domains) + result = qubes.qmemman.algo.balance(int(50 * MB), domains) # should be no repeats self.assertEqual( len(result), @@ -293,18 +291,18 @@ def test_250_balance_low_on_memory(self): # no meminfo -> no adjustment self.assertNotIn(("3", unittest.mock.ANY), result) for domid, target in result: - if domains[domid].mem_used > domains[domid].memory_actual: - # no domain should get less, if already below prefmem + if domains[domid].mem_used > domains[domid].mem_actual: + # no domain should get less, if already below pref_mem self.assertGreaterEqual( target, - domains[domid].memory_actual, + domains[domid].mem_actual, "Request for {} reduces in {!r}".format(domid, result), ) else: # otherwise it _should_ get reduced self.assertLess( target, - domains[domid].memory_actual, + domains[domid].mem_actual, "Request for {} increases in {!r}".format(domid, result), ) diff --git a/qubes/tools/qmemmand.py b/qubes/tools/qmemmand.py index 0e6b4392f..0a7e505b5 100644 --- a/qubes/tools/qmemmand.py +++ b/qubes/tools/qmemmand.py @@ -38,30 +38,25 @@ import qubes.utils SOCK_PATH = "/var/run/qubes/qmemman.sock" - +GLOBAL_LOCK = threading.Lock() system_state = qubes.qmemman.systemstate.SystemState() -global_lock = threading.Lock() -# If XSWatcher will -# handle meminfo event before @introduceDomain, it will use -# incomplete domain list for that and may redistribute memory -# allocated to some VM, but not yet used (see #1389). -# To fix that, system_state should be updated (refresh domain -# list) before processing other changes, every time some process requested -# memory for a new VM, before releasing the lock. Then XS_Watcher will check -# this flag before processing other event. -force_refresh_domain_list = False +# If XSWatcher handles meminfo event before @introduceDomain, it will use +# incomplete domain list for that and may redistribute memory allocated to some +# VM, but not yet used (see #1389). To fix that, system_state should be updated +# (refresh domain list) before processing other changes, every time some +# process requested memory for a new VM, before releasing the lock. Then +# XS_Watcher will check this flag before processing other event. +FORCE_REFRESH_DOMAIN_LIST = False -def only_in_first_list(list1, list2): - ret = [] - for i in list1: - if i not in list2: - ret.append(i) - return ret +def unique_per_list(list1, list2): + first = [item for item in list1 if item not in list2] + second = [item for item in list2 if item not in list1] + return first, second -def get_domain_meminfo_key(domain_id): - return "/local/domain/" + domain_id + "/memory/meminfo" +def get_domain_meminfo_key(domid): + return "/local/domain/" + domid + "/memory/meminfo" @dataclass @@ -91,7 +86,7 @@ def domain_list_changed(self, refresh_only=False): :param refresh_only If True, only refresh domain list, do not redistribute memory. In this mode, caller must already hold - global_lock. + GLOBAL_LOCK. """ self.log.debug( "domain_list_changed(only_refresh={!r})".format(refresh_only) @@ -99,43 +94,40 @@ def domain_list_changed(self, refresh_only=False): got_lock = False if not refresh_only: - self.log.debug("acquiring global_lock") + self.log.debug("acquiring GLOBAL_LOCK") # pylint: disable=consider-using-with - global_lock.acquire() + GLOBAL_LOCK.acquire() got_lock = True - self.log.debug("global_lock acquired") + self.log.debug("GLOBAL_LOCK acquired") try: curr = self.handle.ls("", "/local/domain") if curr is None: return - # check if domain is really there, it may happen that some empty + # Check if domain is really there, it may happen that some empty # directories are left in xenstore - curr = list( - filter( - lambda x: self.handle.read( - "", "/local/domain/{}/domid".format(x) - ) - is not None, - curr, - ) - ) + curr = [ + domid + for domid in curr + if self.handle.read("", "/local/domain/{}/domid".format(domid)) + is not None + ] self.log.debug("curr={!r}".format(curr)) - for i in only_in_first_list(curr, self.watch_token_dict.keys()): - # new domain has been created - watch = WatchType(XSWatcher.meminfo_changed, i) - self.watch_token_dict[i] = watch - self.handle.watch(get_domain_meminfo_key(i), watch) - system_state.add_domain(i) - - for i in only_in_first_list(self.watch_token_dict.keys(), curr): - # domain destroyed + created, destroyed = unique_per_list( + curr, self.watch_token_dict.keys() + ) + for domid in created: + watch = WatchType(XSWatcher.meminfo_changed, domid) + self.watch_token_dict[domid] = watch + self.handle.watch(get_domain_meminfo_key(domid), watch) + system_state.add_domain(domid) + for domid in destroyed: self.handle.unwatch( - get_domain_meminfo_key(i), self.watch_token_dict[i] + get_domain_meminfo_key(domid), self.watch_token_dict[domid] ) - self.watch_token_dict.pop(i) - system_state.del_domain(i) + self.watch_token_dict.pop(domid) + system_state.del_domain(domid) if not refresh_only: try: @@ -146,33 +138,33 @@ def domain_list_changed(self, refresh_only=False): self.log.exception("Updating domain list failed") finally: if got_lock: - global_lock.release() - self.log.debug("global_lock released") + GLOBAL_LOCK.release() + self.log.debug("GLOBAL_LOCK released") - def meminfo_changed(self, domain_id): - self.log.debug("meminfo_changed(domain_id={!r})".format(domain_id)) + def meminfo_changed(self, domid): + self.log.debug("meminfo_changed(domid={!r})".format(domid)) untrusted_meminfo_key = self.handle.read( - "", get_domain_meminfo_key(domain_id) + "", get_domain_meminfo_key(domid) ) if untrusted_meminfo_key is None or untrusted_meminfo_key == b"": return - self.log.debug("acquiring global_lock") - with global_lock: - self.log.debug("global_lock acquired") + self.log.debug("acquiring GLOBAL_LOCK") + with GLOBAL_LOCK: + self.log.debug("GLOBAL_LOCK acquired") try: - global force_refresh_domain_list - if force_refresh_domain_list: + global FORCE_REFRESH_DOMAIN_LIST + if FORCE_REFRESH_DOMAIN_LIST: self.domain_list_changed(refresh_only=True) - force_refresh_domain_list = False - if domain_id not in self.watch_token_dict: - # domain just destroyed + FORCE_REFRESH_DOMAIN_LIST = False + if domid not in self.watch_token_dict: + # Domain was just destroyed. return - system_state.refresh_meminfo(domain_id, untrusted_meminfo_key) + system_state.refresh_meminfo(domid, untrusted_meminfo_key) except: # pylint: disable=bare-except - self.log.exception("Updating meminfo for %s failed", domain_id) - self.log.debug("global_lock released") + self.log.exception("Updating meminfo for %s failed", domid) + self.log.debug("GLOBAL_LOCK released") def watch_loop(self): self.log.debug("watch_loop()") @@ -187,9 +179,8 @@ class QMemmanReqHandler(socketserver.BaseRequestHandler): """ The RequestHandler class for our server. - It is instantiated once per connection to the server, and must - override the handle() method to implement communication to the - client. + It is instantiated once per connection to the server, and must override the + handle() method to implement communication to the client. """ def handle(self): @@ -204,8 +195,8 @@ def handle(self): if len(self.data) == 0: self.log.info("client disconnected, resuming membalance") if got_lock: - global force_refresh_domain_list - force_refresh_domain_list = True + global FORCE_REFRESH_DOMAIN_LIST + FORCE_REFRESH_DOMAIN_LIST = True return # XXX something is wrong here: return without release? @@ -213,10 +204,10 @@ def handle(self): self.log.warning("Second request over qmemman.sock?") return - self.log.debug("acquiring global_lock") + self.log.debug("acquiring GLOBAL_LOCK") # pylint: disable=consider-using-with - global_lock.acquire() - self.log.debug("global_lock acquired") + GLOBAL_LOCK.acquire() + self.log.debug("GLOBAL_LOCK acquired") got_lock = True if self.data.isdigit() and system_state.do_balloon( @@ -233,33 +224,30 @@ def handle(self): ) finally: if got_lock: - global_lock.release() - self.log.debug("global_lock released") - - -parser = qubes.tools.QubesArgumentParser(want_app=False) - -parser.add_argument( - "--config", - "-c", - metavar="FILE", - action="store", - default="/etc/qubes/qmemman.conf", - help="qmemman config file", -) - -parser.add_argument( - "--foreground", - action="store_true", - default=False, - help="do not close stdio", -) + GLOBAL_LOCK.release() + self.log.debug("GLOBAL_LOCK released") def main(): + parser = qubes.tools.QubesArgumentParser(want_app=False) + parser.add_argument( + "--config", + "-c", + metavar="FILE", + action="store", + default="/etc/qubes/qmemman.conf", + help="qmemman config file", + ) + parser.add_argument( + "--foreground", + "-f", + action="store_true", + default=False, + help="do not close stdio", + ) args = parser.parse_args() - # setup logging + # Setup logging. ha_syslog = logging.handlers.SysLogHandler("/dev/log") ha_syslog.setFormatter( logging.Formatter("%(name)s[%(process)d]: %(message)s") @@ -313,13 +301,13 @@ def main(): log.debug("instantiating server") os.umask(0) - # Initialize the connection to Xen and to XenStore + # Initialize the connection to Xen and to XenStore. system_state.init() server = socketserver.UnixStreamServer(SOCK_PATH, QMemmanReqHandler) os.umask(0o077) - # notify systemd + # Notify systemd. nofity_socket = os.getenv("NOTIFY_SOCKET") if nofity_socket: log.debug("notifying systemd") diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index 4d8a71628..6dc5be0ee 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -1432,7 +1432,7 @@ async def start( ) qmemman_client = await asyncio.get_event_loop().run_in_executor( - None, self.request_memory, mem_required + None, self.request_mem, mem_required ) await self.storage.start() @@ -2032,7 +2032,7 @@ def use_memory_hotplug(self): return bool(feature) return False - def request_memory(self, mem_required=None): + def request_mem(self, mem_required=None): if not qmemman_present: return None @@ -2065,9 +2065,7 @@ def request_memory(self, mem_required=None): # 2 pages per 1MB of RAM, see # libxl__get_required_paging_memory() mem_required_with_overhead += maxmem * 8192 - got_memory = qmemman_client.request_memory( - mem_required_with_overhead - ) + got_memory = qmemman_client.request_mem(mem_required_with_overhead) except IOError as e: raise IOError("Failed to connect to qmemman: {!s}".format(e)) @@ -2863,7 +2861,7 @@ def _update_libvirt_domain(self): # workshop -- those are to be reworked later # - def get_prefmem(self): + def get_pref_mem(self): # TODO: qmemman is still xen specific untrusted_meminfo_key = self.app.vmm.xs.read( "", "/local/domain/{}/memory/meminfo".format(self.xid) @@ -2879,9 +2877,9 @@ def get_prefmem(self): if domain.mem_used is None: # apparently invalid xenstore content return 0 - domain.memory_maximum = self.get_mem_static_max() * 1024 + domain.mem_max = self.get_mem_static_max() * 1024 - return qubes.qmemman.algo.prefmem(domain) / 1024 + return qubes.qmemman.algo.pref_mem(domain) / 1024 def _clean_volume_config(config): From 173b41568fb08a4498c59a3a3854212a08b56ec6 Mon Sep 17 00:00:00 2001 From: Ben Grande Date: Thu, 10 Jul 2025 19:28:32 +0200 Subject: [PATCH 5/6] Annotate types --- qubes/qmemman/algo.py | 15 +++--- qubes/qmemman/client.py | 10 ++-- qubes/qmemman/domainstate.py | 35 ++++++++------ qubes/qmemman/systemstate.py | 91 ++++++++++++++++++------------------ 4 files changed, 82 insertions(+), 69 deletions(-) diff --git a/qubes/qmemman/algo.py b/qubes/qmemman/algo.py index c2adad56d..4a86a7e72 100644 --- a/qubes/qmemman/algo.py +++ b/qubes/qmemman/algo.py @@ -19,6 +19,7 @@ # import logging +from typing import Optional # These defaults can be overridden by QMemmanServer with values from config # file. @@ -33,7 +34,7 @@ log = logging.getLogger("qmemman.daemon.algo") -def sanitize_and_parse_meminfo(untrusted_meminfo): +def sanitize_and_parse_meminfo(untrusted_meminfo) -> Optional[int]: # Untrusted meminfo size is read from xenstore, thus its size is limited # and splits do not require excessive memory. if not untrusted_meminfo: @@ -43,14 +44,14 @@ def sanitize_and_parse_meminfo(untrusted_meminfo): return int(untrusted_meminfo) * 1024 -def refresh_meminfo_for_domain(dom, untrusted_xenstore_key): +def refresh_meminfo_for_domain(dom, untrusted_xenstore_key) -> None: """ Called when a domain updates its 'meminfo' xenstore key. """ dom.mem_used = sanitize_and_parse_meminfo(untrusted_xenstore_key) -def pref_mem(dom): +def pref_mem(dom) -> int: # As dom0 must have large cache for vbds, give it a special boost. mem_used = dom.mem_used * CACHE_FACTOR if dom.domid == "0": @@ -59,7 +60,7 @@ def pref_mem(dom): return int(max(min(mem_used, dom.mem_max), MIN_PREFMEM)) -def needed_mem(dom): +def needed_mem(dom) -> int: # Do not change. In balance(), "distribute total_available_mem # proportionally to pref_mem" relies on this exact formula. ret = pref_mem(dom) - dom.mem_actual @@ -69,7 +70,7 @@ def needed_mem(dom): # Prepare list of (dom, mem_target) pairs that need to be passed to "xm # memset" equivalent in order to obtain "mem_size". # Returns empty list when the request cannot be satisfied. -def balloon(mem_size, dom_dict): +def balloon(mem_size, dom_dict) -> list: log.debug( "balloon(mem_size={!r}, dom_dict={!r})".format(mem_size, dom_dict) ) @@ -212,7 +213,7 @@ def balance_when_low_on_mem( # Get memory information. # Called before and after domain balances. # Return a dictionary of various memory data points. -def mem_info(xen_free_mem, dom_dict): +def mem_info(xen_free_mem, dom_dict) -> dict: log.debug( "mem_info(xen_free_mem={!r}, dom_dict={!r})".format( xen_free_mem, dom_dict @@ -264,7 +265,7 @@ def mem_info(xen_free_mem, dom_dict): # Called when one of domains update its 'meminfo' xenstore key. # Return the list of (domain, mem_target) pairs to be passed to "xm memset" # equivalent -def balance(xen_free_mem, dom_dict): +def balance(xen_free_mem, dom_dict) -> dict: log.debug( "balance(xen_free_mem={!r}, dom_dict={!r})".format( xen_free_mem, dom_dict diff --git a/qubes/qmemman/client.py b/qubes/qmemman/client.py index f42f2493f..8e0e28bac 100644 --- a/qubes/qmemman/client.py +++ b/qubes/qmemman/client.py @@ -19,13 +19,14 @@ import socket import fcntl +from typing import Optional class QMemmanClient: - def __init__(self): - self.sock = None + def __init__(self) -> None: + self.sock: Optional[socket.socket] = None - def request_mem(self, amount): + def request_mem(self, amount) -> bool: self.sock = socket.socket(socket.AF_UNIX) flags = fcntl.fcntl(self.sock.fileno(), fcntl.F_GETFD) flags |= fcntl.FD_CLOEXEC @@ -35,5 +36,6 @@ def request_mem(self, amount): received = self.sock.recv(1024).strip() return bool(received == b"OK") - def close(self): + def close(self) -> None: + assert isinstance(self.sock, socket.socket) self.sock.close() diff --git a/qubes/qmemman/domainstate.py b/qubes/qmemman/domainstate.py index 9081d401c..38a82b895 100644 --- a/qubes/qmemman/domainstate.py +++ b/qubes/qmemman/domainstate.py @@ -18,20 +18,29 @@ # You should have received a copy of the GNU General Public # License along with this library; if not, see . +from typing import Optional + class DomainState: # pylint: disable=too-few-public-methods - def __init__(self, domid): - self.mem_current = 0 # the current memory size - self.mem_actual = None # the current memory allocation (what VM - # is using or can use at any time) - self.mem_max = None # the maximum memory size - self.mem_used = None # used memory, computed based on meminfo - self.domid = domid # domain id - self.last_target = 0 # the last memset target - self.use_hotplug = False # use memory hotplug for mem-set - self.no_progress = False # no react to memset - self.slow_memset_react = False # slow react to memset (after few - # tries still above target) + def __init__(self, domid) -> None: + # Current memory size. + self.mem_current: int = 0 + # Current memory allocation (what VM is using or can use at any time). + self.mem_actual: Optional[int] = None + # Maximum memory size. + self.mem_max: Optional[int] = None + # Used memory, computed based on meminfo. + self.mem_used: Optional[int] = None + # Domain ID. + self.domid: str = domid + # Last memset target. + self.last_target: int = 0 + # Use memory hotplug for mem-set. + self.use_hotplug: bool = False + # No reaction to memset. + self.no_progress: bool = False + # Slow react to memset (after few tries still above target). + self.slow_memset_react: bool = False - def __repr__(self): + def __repr__(self) -> str: return self.__dict__.__repr__() diff --git a/qubes/qmemman/systemstate.py b/qubes/qmemman/systemstate.py index 4cfae76b7..a2e832eef 100644 --- a/qubes/qmemman/systemstate.py +++ b/qubes/qmemman/systemstate.py @@ -24,6 +24,7 @@ import time import xen.lowlevel # pylint: disable=import-error from pathlib import Path +from typing import Optional import qubes.qmemman from qubes.qmemman.domainstate import DomainState @@ -42,16 +43,16 @@ class SystemState: - def __init__(self): + def __init__(self) -> None: self.log = logging.getLogger("qmemman.systemstate") self.log.debug("SystemState()") - self.dom_dict = {} - self.xc = None - self.xs = None - self.all_phys_mem = 0 + self.dom_dict: dict[str, DomainState] = {} + self.xc: xen.lowlevel.xc.xc = None + self.xs: xen.lowlevel.xs.xs = None + self.all_phys_mem: int = 0 - def init(self): + def init(self) -> None: self.xc = xen.lowlevel.xc.xc() self.xs = xen.lowlevel.xs.xs() # We divide total and free physical memory by this to get "assignable" @@ -63,10 +64,10 @@ def init(self): except xen.lowlevel.xc.Error: pass - def get_xs_path(self, domid, key): + def get_xs_path(self, domid, key) -> str: return "/local/domain/" + str(domid) + "/memory/" + key - def add_domain(self, domid): + def add_domain(self, domid) -> None: self.log.debug("add_domain(domid={!r})".format(domid)) self.dom_dict[domid] = DomainState(domid) # TODO: move to DomainState.__init__ @@ -74,11 +75,11 @@ def add_domain(self, domid): if target_str: self.dom_dict[domid].last_target = int(target_str) * 1024 - def del_domain(self, domid): + def del_domain(self, domid) -> None: self.log.debug("del_domain(domid={!r})".format(domid)) self.dom_dict.pop(domid) - def get_free_xen_mem(self): + def get_free_xen_mem(self) -> int: xen_free = int( self.xc.physinfo()["free_memory"] * 1024 * MEM_OVERHEAD_FACTOR ) @@ -110,7 +111,7 @@ def get_free_xen_mem(self): return xen_free - assigned_but_unused # Refresh information on memory assigned to all domains - def refresh_mem_actual(self): + def refresh_mem_actual(self) -> None: for domain in self.xc.domain_getinfo(): domid = str(domain["domid"]) if domid in self.dom_dict: @@ -143,8 +144,8 @@ def refresh_mem_actual(self): # the only possible case of nonexisting memory/static-max # is dom0, see #307 - def clear_outdated_error_markers(self): - # Clear outdated errors + def clear_outdated_error_markers(self) -> None: + # Clear outdated errors. for dom in self.dom_dict.values(): if dom.mem_used is None: continue @@ -153,6 +154,7 @@ def clear_outdated_error_markers(self): # - VM request more memory than it has assigned # The second condition avoids starving a VM, even when there is # some free memory available. + assert isinstance(dom.mem_actual, int) if ( dom.mem_actual <= dom.last_target + XEN_FREE_MEM_LEFT / 2 or dom.mem_actual < qubes.qmemman.algo.pref_mem(dom) @@ -162,7 +164,7 @@ def clear_outdated_error_markers(self): # The below works (and is fast), but then 'xm list' shows unchanged memory # value. - def mem_set(self, domid, val): + def mem_set(self, domid, val) -> None: self.log.info("mem-set domain {} to {}".format(domid, val)) dom = self.dom_dict[domid] dom.last_target = val @@ -192,7 +194,7 @@ def mem_set(self, domid, val): # This is called at the end of ballooning, when we have Xen free mem # already, make sure that past mem_set will not decrease Xen free mem. - def inhibit_balloon_up(self): + def inhibit_balloon_up(self) -> None: self.log.debug("inhibit_balloon_up()") for domid, dom in self.dom_dict.items(): if ( @@ -206,10 +208,10 @@ def inhibit_balloon_up(self): # Perform memory ballooning, across all domains, to add "mem_size" to Xen # free memory - def do_balloon(self, mem_size): + def do_balloon(self, mem_size) -> bool: self.log.info("do_balloon(mem_size={!r})".format(mem_size)) niter = 0 - prev_mem_actual = None + prev_mem_actual: dict[str, Optional[int]] = {} for dom in self.dom_dict.values(): dom.no_progress = False @@ -240,18 +242,15 @@ def do_balloon(self, mem_size): ): return False xenfree_ring[ring_slot] = xenfree - if prev_mem_actual is not None: - for domid, prev_mem in prev_mem_actual.items(): - dom = self.dom_dict[domid] - if prev_mem == dom.memory_actual: - # domain not responding to memset requests, remove it - # from donors - dom.no_progress = True - self.log.info( - "domain {} stuck at {}".format( - domid, dom.mem_actual - ) - ) + for domid, prev_mem in prev_mem_actual.items(): + dom = self.dom_dict[domid] + if prev_mem == dom.mem_actual: + # domain not responding to memset requests, remove it + # from donors + dom.no_progress = True + self.log.info( + "domain {} stuck at {}".format(domid, dom.mem_actual) + ) memset_reqs = qubes.qmemman.algo.balloon( mem_size + XEN_FREE_MEM_LEFT - xenfree, self.dom_dict ) @@ -259,15 +258,14 @@ def do_balloon(self, mem_size): if len(memset_reqs) == 0: return False prev_mem_actual = {} - for req in memset_reqs: - dom, mem = req - self.mem_set(dom, mem) - prev_mem_actual[dom] = self.dom_dict[dom].mem_actual + for domid, memset in memset_reqs: + self.mem_set(domid, memset) + prev_mem_actual[domid] = self.dom_dict[domid].mem_actual self.log.debug("sleeping for {} s".format(BALOON_DELAY)) time.sleep(BALOON_DELAY) niter = niter + 1 - def refresh_meminfo(self, domid, untrusted_meminfo_key): + def refresh_meminfo(self, domid, untrusted_meminfo_key) -> None: self.log.debug( "refresh_meminfo(domid={}, untrusted_meminfo_key={!r})".format( domid, untrusted_meminfo_key @@ -281,7 +279,7 @@ def refresh_meminfo(self, domid, untrusted_meminfo_key): # Is the computed balance request big enough so that we do not trash with # small adjustments. - def is_balance_req_significant(self, memset_reqs, xenfree): + def is_balance_req_significant(self, memset_reqs, xenfree) -> bool: self.log.debug( "is_balance_req_significant(memset_reqs={}, xenfree={})".format( memset_reqs, xenfree @@ -295,19 +293,18 @@ def is_balance_req_significant(self, memset_reqs, xenfree): self.log.debug("xenfree is too low, returning") return True - for req in memset_reqs: - dom, mem = req - last_target = self.dom_dict[dom].last_target - mem_change = mem - last_target + for domid, memset in memset_reqs: + last_target = self.dom_dict[domid].last_target + mem_change = memset - last_target total_mem_transfer += abs(mem_change) - pref = qubes.qmemman.algo.pref_mem(self.dom_dict[dom]) + pref = qubes.qmemman.algo.pref_mem(self.dom_dict[domid]) if ( 0 < last_target < pref and mem_change > MIN_MEM_CHANGE_WHEN_UNDER_PREF ): self.log.info( - "dom {} is below pref, allowing balance".format(dom) + "dom {} is below pref, allowing balance".format(domid) ) return True @@ -318,7 +315,7 @@ def is_balance_req_significant(self, memset_reqs, xenfree): self.log.debug("is_balance_req_significant return {}".format(ret)) return ret - def print_stats(self, xenfree, memset_reqs): + def print_stats(self, xenfree, memset_reqs) -> None: for domid, dom in self.dom_dict.items(): if dom.mem_used is not None: self.log.info( @@ -337,7 +334,9 @@ def print_stats(self, xenfree, memset_reqs): "stat: xenfree={} memset_reqs={}".format(xenfree, memset_reqs) ) - def debug_stuck_balance(self, stuck_domid, memset_reqs, prev_mem_actual): + def debug_stuck_balance( + self, stuck_domid, memset_reqs, prev_mem_actual + ) -> None: for req in memset_reqs: domid, mem = req if domid == stuck_domid: @@ -345,6 +344,7 @@ def debug_stuck_balance(self, stuck_domid, memset_reqs, prev_mem_actual): break dom = self.dom_dict[domid] # Allow some small margin. + assert isinstance(dom.mem_actual, int) if dom.mem_actual > dom.last_target + XEN_FREE_MEM_LEFT / 4: # VM didn't react to memory request at all, remove from donors. if prev_mem_actual[domid] == dom.mem_actual: @@ -368,7 +368,7 @@ def debug_stuck_balance(self, stuck_domid, memset_reqs, prev_mem_actual): ) dom.slow_memset_react = True - def do_balance(self): + def do_balance(self) -> None: self.log.debug("do_balance()") if os.path.isfile("/var/run/qubes/do-not-membalance"): self.log.debug("do-not-membalance file present, returning") @@ -385,7 +385,7 @@ def do_balance(self): self.print_stats(xenfree, memset_reqs) - prev_mem_actual = {} + prev_mem_actual: dict[str, Optional[int]] = {} for domid, dom in self.dom_dict.items(): prev_mem_actual[domid] = dom.mem_actual for req in memset_reqs: @@ -413,6 +413,7 @@ def do_balance(self): self.debug_stuck_balance( domid, memset_reqs, prev_mem_actual ) + assert isinstance(dom.mem_actual, int) self.mem_set( domid, self.get_free_xen_mem() From d03bc0be5243c1a632f1b40b79eb625a9a5071af Mon Sep 17 00:00:00 2001 From: Ben Grande Date: Wed, 16 Jul 2025 14:47:51 +0200 Subject: [PATCH 6/6] Balloon typo --- qubes/qmemman/systemstate.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qubes/qmemman/systemstate.py b/qubes/qmemman/systemstate.py index a2e832eef..d85e17e1f 100644 --- a/qubes/qmemman/systemstate.py +++ b/qubes/qmemman/systemstate.py @@ -29,7 +29,7 @@ import qubes.qmemman from qubes.qmemman.domainstate import DomainState -BALOON_DELAY = 0.1 +BALLOON_DELAY = 0.1 XEN_FREE_MEM_LEFT = 50 * 1024 * 1024 XEN_FREE_MEM_MIN = 25 * 1024 * 1024 # Overhead of per-page Xen structures, taken from OpenStack @@ -217,7 +217,7 @@ def do_balloon(self, mem_size) -> bool: dom.no_progress = False #: number of loop iterations for CHECK_PERIOD_S seconds - check_period = max(1, int((CHECK_PERIOD_S + 0.0) / BALOON_DELAY)) + check_period = max(1, int((CHECK_PERIOD_S + 0.0) / BALLOON_DELAY)) #: number of free memory bytes expected to get during CHECK_PERIOD_S #: seconds check_delta = CHECK_PERIOD_S * CHECK_MB_S * 1024 * 1024 @@ -261,8 +261,8 @@ def do_balloon(self, mem_size) -> bool: for domid, memset in memset_reqs: self.mem_set(domid, memset) prev_mem_actual[domid] = self.dom_dict[domid].mem_actual - self.log.debug("sleeping for {} s".format(BALOON_DELAY)) - time.sleep(BALOON_DELAY) + self.log.debug("sleeping for {} s".format(BALLOON_DELAY)) + time.sleep(BALLOON_DELAY) niter = niter + 1 def refresh_meminfo(self, domid, untrusted_meminfo_key) -> None: @@ -404,7 +404,7 @@ def do_balance(self) -> None: domid, ntries ) ) - time.sleep(BALOON_DELAY) + time.sleep(BALLOON_DELAY) self.refresh_mem_actual() ntries -= 1 if ntries <= 0: