diff --git a/qubes/qmemman/algo.py b/qubes/qmemman/algo.py
index 685a30eb0..4a86a7e72 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,316 +19,271 @@
#
import logging
-import string
+from typing import Optional
-# 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")
-# 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):
+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:
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
-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) -> None:
+ """
+ Called when a domain updates its 'meminfo' xenstore key.
+ """
+ dom.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
- if domain.id == "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,
- )
- )
+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":
+ mem_used += DOM0_MEM_BOOST
+ 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) -> 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
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
-def balloon(memsize, domain_dictionary):
+# 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) -> list:
log.debug(
- "balloon(memsize={!r}, domain_dictionary={!r})".format(
- memsize, domain_dictionary
- )
+ "balloon(mem_size={!r}, dom_dict={!r})".format(mem_size, dom_dict)
)
- 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:
- continue
- if domain_dictionary[i].no_progress:
+ for domid, dom in dom_dict.items():
+ if dom.mem_used is None or dom.no_progress:
continue
- need = memory_needed(domain_dictionary[i])
+ need = needed_mem(dom)
if need < 0:
log.info(
"balloon: dom {} has actual memory {}".format(
- i, domain_dictionary[i].memory_actual
+ domid, dom.mem_actual
)
)
- donors.append((i, -need))
+ 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 = {}
- # memory not assigned because of static max
- left_memory = 0
+ target_mem = {}
+ # Memory not assigned because of static max.
+ mem_left = 0
acceptors_count = 0
- for i in domain_dictionary.keys():
- if domain_dictionary[i].mem_used is None:
+ for domid, dom in dom_dict.items():
+ if dom.mem_used is None or dom.no_progress:
continue
- if domain_dictionary[i].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
- )
- # prevent rounding errors
+ # 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 > domain_dictionary[i].memory_maximum:
- left_memory += target - domain_dictionary[i].memory_maximum
- target = domain_dictionary[i].memory_maximum
+ # Do not try to give more memory than static max.
+ if target > dom.mem_max:
+ mem_left += target - dom.mem_max
+ target = dom.mem_max
else:
- # count domains which can accept more memory
+ # Count domains which can accept more memory.
acceptors_count += 1
- target_memory[i] = target
- # distribute left memory across all acceptors
- while left_memory > 0 and acceptors_count > 0:
+ target_mem[domid] = target
+ # Distribute left memory across all acceptors.
+ 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 i in target_memory.keys():
- target = target_memory[i]
- if target < domain_dictionary[i].memory_maximum:
- memory_bonus = int(0.999 * (left_memory / acceptors_count))
- if target + memory_bonus >= domain_dictionary[i].memory_maximum:
- new_left_memory += (
- target
- + memory_bonus
- - domain_dictionary[i].memory_maximum
- )
- target = domain_dictionary[i].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[i] = 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 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))
+ # 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_mem.items():
+ dom = dom_dict[domid]
+ if target < dom.mem_actual:
+ donors_rq.append((domid, target))
else:
- acceptors_rq.append((i, target))
-
- # print 'balance(enough): xen_free_memory=', xen_free_memory, \
- # 'requests:', donors_rq + acceptors_rq
+ 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 = list()
- acceptors_rq = list()
- squeezed_mem = xen_free_memory
- for i in donors:
- avail = -memory_needed(domain_dictionary[i])
+ donors_rq = []
+ acceptors_rq = []
+ squeezed_mem = xen_free_mem
+ for domid in donors:
+ dom = dom_dict[domid]
+ avail = -needed_mem(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 pref_mem, give
+ # up.
continue
squeezed_mem -= avail
- donors_rq.append((i, prefmem(domain_dictionary[i])))
- # the below can happen if initially xen free memory is below 50M
+ 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 i in acceptors:
- scale = 1.0 * prefmem(domain_dictionary[i]) / total_mem_pref_acceptors
- target_nonint = (
- domain_dictionary[i].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))
- # print 'balance(low): xen_free_memory=', xen_free_memory, 'requests:',
- # donors_rq + acceptors_rq
+ for domid in acceptors:
+ 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.mem_max)
+ 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
-def memory_info(xen_free_memory, domain_dictionary):
+# 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) -> 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
)
)
- # 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 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_needed_mem = 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 = list() # domains that can yield memory
- acceptors = list() # domains that require more memory
- # pass 1: compute the above "total" values
- for i in domain_dictionary.keys():
- if domain_dictionary[i].mem_used is None:
+ donors = []
+ acceptors = []
+ # Pass 1: compute the above "total" values.
+ for domid, dom in dom_dict.items():
+ if dom.mem_used is None or dom.no_progress:
continue
- if domain_dictionary[i].no_progress:
- continue
- need = memory_needed(domain_dictionary[i])
- # print 'domain' , i, 'act/pref', \
- # domain_dictionary[i].memory_actual, prefmem(domain_dictionary[i]), \
- # 'need=', need
- if (
- need < 0
- or domain_dictionary[i].memory_actual
- >= domain_dictionary[i].memory_maximum
- ):
- donors.append(i)
+ need = needed_mem(dom)
+ if need < 0 or dom.mem_actual >= dom.mem_max:
+ donors.append(domid)
else:
- acceptors.append(i)
- total_mem_pref_acceptors += prefmem(domain_dictionary[i])
- total_memory_needed += need
- total_mem_pref += prefmem(domain_dictionary[i])
-
- total_available_memory = xen_free_memory - total_memory_needed
-
- 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
-
-
-# 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):
+ acceptors.append(domid)
+ total_mem_pref_acceptors += pref_mem(dom)
+ total_needed_mem += need
+ total_mem_pref += pref_mem(dom)
+
+ total_available_mem = xen_free_mem - total_needed_mem
+
+ 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, mem_target) pairs to be passed to "xm memset"
+# equivalent
+def balance(xen_free_mem, dom_dict) -> 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"],
- )
- 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"],
+ 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_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 3b6bb088f..8e0e28bac 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
#
@@ -21,23 +19,23 @@
import socket
import fcntl
+from typing import Optional
class QMemmanClient:
- def request_memory(self, amount):
- self.sock = socket.socket(socket.AF_UNIX)
+ def __init__(self) -> None:
+ self.sock: Optional[socket.socket] = None
+ 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
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):
+ 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 a476e5d7c..38a82b895 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/
#
@@ -19,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:
- def __init__(self, id):
- 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.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 __repr__(self):
+class DomainState: # pylint: disable=too-few-public-methods
+ 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) -> str:
return self.__dict__.__repr__()
diff --git a/qubes/qmemman/systemstate.py b/qubes/qmemman/systemstate.py
index a12011183..d85e17e1f 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,210 +17,207 @@
#
# 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
+from typing import Optional
import qubes.qmemman
from qubes.qmemman.domainstate import DomainState
-
-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):
- def __init__(self):
+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
+# 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
+
+
+class SystemState:
+ def __init__(self) -> None:
self.log = logging.getLogger("qmemman.systemstate")
self.log.debug("SystemState()")
- self.domdict = {}
- self.xc = None
- self.xs = None
+ 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()
- 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
+ # 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
- * 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 get_xs_path(self, domid, key) -> str:
+ return "/local/domain/" + str(domid) + "/memory/" + key
+
+ 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__
- target_str = self.xs.read("", "/local/domain/" + id + "/memory/target")
+ target_str = self.xs.read("", self.get_xs_path(domid, "target"))
if target_str:
- self.domdict[id].last_target = int(target_str) * 1024
+ self.dom_dict[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) -> None:
+ self.log.debug("del_domain(domid={!r})".format(domid))
+ self.dom_dict.pop(domid)
- def get_free_xen_memory(self):
+ def get_free_xen_mem(self) -> int:
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)
+ # 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_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 failure of qmemman. Collect as much data as possible to
- # debug it
- if xen_free < self.XEN_FREE_MEM_MIN:
+ # 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! "
- "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 + 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(
- 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):
+ # Refresh information on memory assigned to all domains
+ def refresh_mem_actual(self) -> None:
for domain in self.xc.domain_getinfo():
- id = str(domain["domid"])
- if id in self.domdict:
- # real memory usage
- self.domdict[id].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,
+ domid = str(domain["domid"])
+ if domid in self.dom_dict:
+ dom = self.dom_dict[domid]
+ # Real memory usage
+ dom.mem_current = domain["mem_kb"] * 1024
+ # What VM is using or can use
+ dom.mem_actual = max(
+ dom.mem_current,
+ dom.last_target,
)
hotplug_max = self.xs.read(
- "", "/local/domain/%s/memory/hotplug-max" % str(id)
+ "", self.get_xs_path(domid, "hotplug-max")
)
static_max = self.xs.read(
- "", "/local/domain/%s/memory/static-max" % str(id)
+ "", self.get_xs_path(domid, "static-max")
)
if hotplug_max:
- self.domdict[id].memory_maximum = int(hotplug_max) * 1024
- self.domdict[id].use_hotplug = True
+ dom.mem_max = 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.mem_max = int(static_max) * 1024
+ dom.use_hotplug = False
else:
- self.domdict[id].memory_maximum = self.ALL_PHYS_MEM
+ dom.mem_max = self.all_phys_mem
# the previous line used to be
- # self.domdict[id].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 is dom0
- # see #307
-
- def clear_outdated_error_markers(self):
- # Clear outdated errors
- for i in self.domdict.keys():
- if self.domdict[i].mem_used is None:
+ # 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) -> None:
+ # Clear outdated errors.
+ for dom in self.dom_dict.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
- 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]
+ # 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)
):
- self.domdict[i].slow_memset_react = False
- self.domdict[i].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
- # can happen in the middle of domain shutdown
- # apparently xc.lowlevel throws exceptions too
+ 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, domid, val) -> None:
+ self.log.info("mem-set domain {} to {}".format(domid, val))
+ dom = self.dom_dict[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
+ # handle Xen view of memory.
self.xs.write(
"",
- "/local/domain/" + id + "/memory/target",
+ self.get_xs_path(domid, "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",
+ 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
- def inhibit_balloon_up(self):
+ # 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) -> None:
self.log.debug("inhibit_balloon_up()")
- for i in self.domdict.keys():
- dom = self.domdict[i]
+ 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(i, 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
+ self.mem_set(domid, dom.mem_actual)
+ # Perform memory ballooning, across all domains, to add "mem_size" to Xen
+ # free memory
+ def do_balloon(self, mem_size) -> bool:
+ self.log.info("do_balloon(mem_size={!r})".format(mem_size))
niter = 0
- prev_memory_actual = None
+ prev_mem_actual: dict[str, Optional[int]] = {}
- for i in self.domdict.keys():
- self.domdict[i].no_progress = False
+ for dom in self.dom_dict.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) / 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
@@ -231,10 +227,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 + self.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,
@@ -246,33 +242,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:
- # domain not responding to memset requests, remove it
- # from donors
- self.domdict[i].no_progress = True
- self.log.info(
- "domain {} stuck at {}".format(
- i, self.domdict[i].memory_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(
- memsize + self.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 = {}
- for i in memset_reqs:
- dom, mem = i
- 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)
+ prev_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(BALLOON_DELAY))
+ time.sleep(BALLOON_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
@@ -280,67 +273,60 @@ 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()
- # 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):
+ # 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) -> bool:
self.log.debug(
"is_balance_req_significant(memset_reqs={}, xenfree={})".format(
memset_reqs, xenfree
)
)
- total_memory_transfer = 0
- MIN_TOTAL_MEMORY_TRANSFER = 150 * 1024 * 1024
- MIN_MEM_CHANGE_WHEN_UNDER_PREF = 15 * 1024 * 1024
+ total_mem_transfer = 0
- # If xenfree to low, return immediately
- if self.XEN_FREE_MEM_LEFT - xenfree > MIN_MEM_CHANGE_WHEN_UNDER_PREF:
+ # 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
- for rq in memset_reqs:
- dom, mem = rq
- 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])
+ 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[domid])
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)
+ "dom {} is below pref, allowing balance".format(domid)
)
return True
ret = (
- total_memory_transfer + abs(xenfree - self.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 i in self.domdict.keys():
- if self.domdict[i].mem_used is not None:
+ 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(
"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.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 ""),
)
)
@@ -348,106 +334,103 @@ def print_stats(self, xenfree, memset_reqs):
"stat: xenfree={} memset_reqs={}".format(xenfree, memset_reqs)
)
- def do_balance(self):
+ 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:
+ # All donors have been processed.
+ 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:
+ self.log.warning(
+ "dom {!r} did not react to memory request (holds {}, "
+ "requested balloon down to {})".format(
+ domid,
+ dom.mem_actual,
+ mem,
+ )
+ )
+ dom.no_progress = True
+ else:
+ self.log.warning(
+ "dom {!r} still holds more memory than assigned ({} > "
+ "{})".format(
+ domid,
+ dom.mem_actual,
+ mem,
+ )
+ )
+ dom.slow_memset_react = True
+
+ 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")
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 - self.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 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
- # margin for rounding errors). Before giving memory to
- # domain, ensure that others have gave it back.
- # If not - wait a little.
+ 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:
+ domid, mem = req
+ 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 - self.domdict[dom].memory_actual)
- < 0.9 * self.XEN_FREE_MEM_LEFT
+ self.get_free_xen_mem() - (mem - dom.mem_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)
- self.refresh_memactual()
+ time.sleep(BALLOON_DELAY)
+ self.refresh_mem_actual()
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
+ # 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_mem_actual
+ )
+ assert isinstance(dom.mem_actual, int)
self.mem_set(
- dom,
- self.get_free_xen_memory()
- + self.domdict[dom].memory_actual
- - self.XEN_FREE_MEM_LEFT,
+ domid,
+ self.get_free_xen_mem()
+ + dom.mem_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 = 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)
-
-
-# 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()
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):