2015-10-05 23:46:25 +02:00
|
|
|
# pylint: skip-file
|
|
|
|
|
2014-05-18 21:01:21 +02:00
|
|
|
#
|
|
|
|
# The Qubes OS Project, http://www.qubes-os.org
|
|
|
|
#
|
|
|
|
# Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
|
|
|
|
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
|
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# This library is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
|
|
# License as published by the Free Software Foundation; either
|
|
|
|
# version 2.1 of the License, or (at your option) any later version.
|
2014-05-18 21:01:21 +02:00
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# This library is distributed in the hope that it will be useful,
|
2014-05-18 21:01:21 +02:00
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2017-10-12 00:11:50 +02:00
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
# Lesser General Public License for more details.
|
2014-05-18 21:01:21 +02:00
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
|
|
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
|
2014-05-18 21:01:21 +02:00
|
|
|
#
|
2015-10-01 21:55:47 +02:00
|
|
|
|
|
|
|
import logging
|
|
|
|
import os
|
2010-08-30 11:40:19 +02:00
|
|
|
import string
|
|
|
|
import time
|
|
|
|
|
2017-02-23 00:15:38 +01:00
|
|
|
import functools
|
2015-10-01 21:55:47 +02:00
|
|
|
import xen.lowlevel.xc
|
|
|
|
import xen.lowlevel.xs
|
|
|
|
|
2015-10-01 22:14:35 +02:00
|
|
|
import qubes.qmemman.algo
|
2015-10-01 21:55:47 +02:00
|
|
|
|
2015-03-17 16:45:00 +01:00
|
|
|
|
2012-07-05 01:23:43 +02:00
|
|
|
no_progress_msg="VM refused to give back requested memory"
|
|
|
|
slow_memset_react_msg="VM didn't give back all requested memory"
|
|
|
|
|
2010-08-30 11:40:19 +02:00
|
|
|
class DomainState:
|
|
|
|
def __init__(self, id):
|
2016-01-11 21:50:57 +01:00
|
|
|
self.memory_current = 0 # the current memory size
|
2016-01-06 00:40:10 +01:00
|
|
|
self.memory_actual = None # the current memory allocation (what VM
|
2018-01-06 15:12:48 +01:00
|
|
|
# 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.no_progress = False # no react to memset
|
|
|
|
self.slow_memset_react = False # slow react to memset (after few
|
|
|
|
# tries still above target)
|
2010-08-30 11:40:19 +02:00
|
|
|
|
2016-01-14 03:15:38 +01:00
|
|
|
def __repr__(self):
|
|
|
|
return self.__dict__.__repr__()
|
|
|
|
|
2015-03-17 16:45:00 +01:00
|
|
|
class SystemState(object):
|
2010-08-30 11:40:19 +02:00
|
|
|
def __init__(self):
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log = logging.getLogger('qmemman.systemstate')
|
|
|
|
self.log.debug('SystemState()')
|
|
|
|
|
2010-08-30 11:40:19 +02:00
|
|
|
self.domdict = {}
|
|
|
|
self.xc = xen.lowlevel.xc.xc()
|
2010-09-03 16:19:48 +02:00
|
|
|
self.xs = xen.lowlevel.xs.xs()
|
2010-08-30 11:40:19 +02:00
|
|
|
self.BALOON_DELAY = 0.1
|
2010-09-16 15:57:11 +02:00
|
|
|
self.XEN_FREE_MEM_LEFT = 50*1024*1024
|
qmemman: handle requests for small pieces correctly
There seems to be a problem with xm mem-set, when executed for a value
very close to the current value - the request is ignored; apparently, the
domU kernel imposes some granularity on the request size.
So, if qmemman is asked for, say 470MB, and there is 469MB free, it will try
to milk 1MB from all domains - and this will fail. REQ_SAFETY_NET_FACTOR
does not help in this scenario.
The logs show
req= 1110016 avail= 2503727104.0 donors [('11', 194375270.40000001),...
borrow 90484.1597129 from 11 - so, beg for 90K from a domain
borrow 132239.288652 from 10
borrow 537099.316089 from 0
borrow 148004.024941 from 7
borrow 139834.21573 from 9
borrow 117855.794876 from 8
and then we fail when a domain does not provide this lousy 90KB.
The solution is to ask for actual_need+XEN_FREE_MEM_LEFT, but return if we already
have actual_need+XEN_FREE_MEM_MIN (the latter is 25MB smaller).
2011-04-05 10:52:53 +02:00
|
|
|
self.XEN_FREE_MEM_MIN = 25*1024*1024
|
2018-01-06 15:12:48 +01:00
|
|
|
# Overhead of per-page Xen structures, taken from OpenStack
|
|
|
|
# nova/virt/xenapi/driver.py
|
2015-10-09 19:39:40 +02:00
|
|
|
# see https://wiki.openstack.org/wiki/XenServer/Overhead
|
2018-01-06 15:12:48 +01:00
|
|
|
# we divide total and free physical memory by this to get
|
|
|
|
# "assignable" memory
|
2015-10-09 19:39:40 +02:00
|
|
|
self.MEM_OVERHEAD_FACTOR = 1.0 / 1.00781
|
2016-04-22 15:23:39 +02:00
|
|
|
try:
|
|
|
|
self.ALL_PHYS_MEM = int(self.xc.physinfo()['total_memory']*1024 * self.MEM_OVERHEAD_FACTOR)
|
|
|
|
except xen.lowlevel.xc.Error:
|
|
|
|
self.ALL_PHYS_MEM = 0
|
2010-08-30 11:40:19 +02:00
|
|
|
|
|
|
|
def add_domain(self, id):
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.debug('add_domain(id={!r})'.format(id))
|
2010-08-30 11:40:19 +02:00
|
|
|
self.domdict[id] = DomainState(id)
|
2016-01-06 00:40:10 +01:00
|
|
|
# TODO: move to DomainState.__init__
|
|
|
|
target_str = self.xs.read('', '/local/domain/' + id + '/memory/target')
|
|
|
|
if target_str:
|
|
|
|
self.domdict[id].last_target = int(target_str) * 1024
|
2010-08-30 11:40:19 +02:00
|
|
|
|
|
|
|
def del_domain(self, id):
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.debug('del_domain(id={!r})'.format(id))
|
2010-08-30 11:40:19 +02:00
|
|
|
self.domdict.pop(id)
|
|
|
|
|
|
|
|
def get_free_xen_memory(self):
|
2016-01-06 00:40:10 +01:00
|
|
|
xen_free = int(self.xc.physinfo()['free_memory']*1024 *
|
|
|
|
self.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)
|
2017-02-23 00:15:38 +01:00
|
|
|
assigned_but_unused = functools.reduce(
|
2016-01-06 00:40:10 +01:00
|
|
|
lambda acc, dom: acc + max(0, dom.last_target-dom.memory_current),
|
|
|
|
self.domdict.values(),
|
|
|
|
0
|
|
|
|
)
|
2016-01-14 03:15:38 +01:00
|
|
|
# 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:
|
|
|
|
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:
|
|
|
|
self.log.error("Xen free = {!r} too small for satisfy assignments! "
|
|
|
|
"assigned_but_unused={!r}, domdict={!r}".format(
|
|
|
|
xen_free, assigned_but_unused, self.domdict))
|
2016-01-06 00:40:10 +01:00
|
|
|
return xen_free - assigned_but_unused
|
2010-08-30 11:40:19 +02:00
|
|
|
|
2018-01-06 15:12:48 +01:00
|
|
|
# refresh information on memory assigned to all domains
|
2010-08-30 11:40:19 +02:00
|
|
|
def refresh_memactual(self):
|
|
|
|
for domain in self.xc.domain_getinfo():
|
|
|
|
id = str(domain['domid'])
|
2017-02-23 00:15:38 +01:00
|
|
|
if id in self.domdict:
|
2016-01-06 00:40:10 +01:00
|
|
|
# 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
|
|
|
|
)
|
2011-06-07 16:19:52 +02:00
|
|
|
self.domdict[id].memory_maximum = self.xs.read('', '/local/domain/%s/memory/static-max' % str(id))
|
2013-10-22 01:59:31 +02:00
|
|
|
if self.domdict[id].memory_maximum:
|
|
|
|
self.domdict[id].memory_maximum = int(self.domdict[id].memory_maximum)*1024
|
|
|
|
else:
|
2011-07-22 11:33:11 +02:00
|
|
|
self.domdict[id].memory_maximum = self.ALL_PHYS_MEM
|
2018-01-06 15:12:48 +01:00
|
|
|
# the previous line used to be
|
|
|
|
# self.domdict[id].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
|
2010-08-30 11:40:19 +02:00
|
|
|
|
2012-07-05 01:23:43 +02:00
|
|
|
def clear_outdated_error_markers(self):
|
|
|
|
# Clear outdated errors
|
|
|
|
for i in self.domdict.keys():
|
2018-01-18 16:47:55 +01:00
|
|
|
if self.domdict[i].mem_used is None:
|
|
|
|
continue
|
2018-01-11 02:15:15 +01:00
|
|
|
# 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]):
|
2012-07-05 01:23:43 +02:00
|
|
|
self.domdict[i].slow_memset_react = False
|
|
|
|
self.domdict[i].no_progress = False
|
|
|
|
|
2018-01-06 15:12:48 +01:00
|
|
|
# the below works (and is fast), but then 'xm list' shows unchanged
|
|
|
|
# memory value
|
2010-09-03 16:19:48 +02:00
|
|
|
def mem_set(self, id, val):
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.info('mem-set domain {} to {}'.format(id, val))
|
2010-09-09 10:36:13 +02:00
|
|
|
self.domdict[id].last_target = val
|
2018-01-06 15:12:48 +01:00
|
|
|
# can happen in the middle of domain shutdown
|
|
|
|
# apparently xc.lowlevel throws exceptions too
|
2010-09-07 13:10:48 +02:00
|
|
|
try:
|
2012-07-05 01:17:45 +02:00
|
|
|
self.xc.domain_setmaxmem(int(id), int(val/1024) + 1024) # LIBXL_MAXMEM_CONSTANT=1024
|
2018-01-11 03:35:46 +01:00
|
|
|
self.xc.domain_set_target_mem(int(id), int(val / 1024))
|
2010-09-07 13:10:48 +02:00
|
|
|
except:
|
|
|
|
pass
|
2018-01-11 03:35:46 +01:00
|
|
|
# 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',
|
|
|
|
str(int(val/1024 - 16 * 1024)))
|
2012-07-05 01:17:45 +02:00
|
|
|
|
2018-01-06 15:12:48 +01:00
|
|
|
# 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
|
2011-07-22 13:40:21 +02:00
|
|
|
def inhibit_balloon_up(self):
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.debug('inhibit_balloon_up()')
|
2011-07-22 13:40:21 +02:00
|
|
|
for i in self.domdict.keys():
|
|
|
|
dom = self.domdict[i]
|
|
|
|
if dom.memory_actual is not None and dom.memory_actual + 200*1024 < dom.last_target:
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.info(
|
|
|
|
'Preventing balloon up to {}'.format(dom.last_target))
|
2011-07-22 13:40:21 +02:00
|
|
|
self.mem_set(i, dom.memory_actual)
|
|
|
|
|
2018-01-06 15:12:48 +01:00
|
|
|
# perform memory ballooning, across all domains, to add "memsize" to Xen
|
|
|
|
# free memory
|
2010-08-30 11:40:19 +02:00
|
|
|
def do_balloon(self, memsize):
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.info('do_balloon(memsize={!r})'.format(memsize))
|
2015-10-09 13:47:27 +02:00
|
|
|
CHECK_PERIOD_S = 3
|
|
|
|
CHECK_MB_S = 100
|
|
|
|
|
2010-08-30 11:40:19 +02:00
|
|
|
niter = 0
|
|
|
|
prev_memory_actual = None
|
2015-03-17 16:45:00 +01:00
|
|
|
|
2010-08-30 11:40:19 +02:00
|
|
|
for i in self.domdict.keys():
|
|
|
|
self.domdict[i].no_progress = False
|
2015-03-17 16:45:00 +01:00
|
|
|
|
2015-10-09 19:29:15 +02:00
|
|
|
#: number of loop iterations for CHECK_PERIOD_S seconds
|
2015-10-09 13:47:27 +02:00
|
|
|
check_period = max(1, int((CHECK_PERIOD_S + 0.0) / self.BALOON_DELAY))
|
2015-10-09 19:29:15 +02:00
|
|
|
#: number of free memory bytes expected to get during CHECK_PERIOD_S
|
|
|
|
#: seconds
|
2015-10-09 13:47:27 +02:00
|
|
|
check_delta = CHECK_PERIOD_S * CHECK_MB_S * 1024 * 1024
|
2015-10-09 19:29:15 +02:00
|
|
|
#: helper array for holding free memory size, CHECK_PERIOD_S seconds
|
|
|
|
#: ago, at every loop iteration
|
2015-10-09 13:47:27 +02:00
|
|
|
xenfree_ring = [0] * check_period
|
|
|
|
|
2010-08-30 11:40:19 +02:00
|
|
|
while True:
|
2015-10-09 13:47:27 +02:00
|
|
|
self.log.debug('niter={:2d}'.format(niter))
|
2011-07-22 13:40:21 +02:00
|
|
|
self.refresh_memactual()
|
2010-08-30 11:40:19 +02:00
|
|
|
xenfree = self.get_free_xen_memory()
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.info('xenfree={!r}'.format(xenfree))
|
qmemman: handle requests for small pieces correctly
There seems to be a problem with xm mem-set, when executed for a value
very close to the current value - the request is ignored; apparently, the
domU kernel imposes some granularity on the request size.
So, if qmemman is asked for, say 470MB, and there is 469MB free, it will try
to milk 1MB from all domains - and this will fail. REQ_SAFETY_NET_FACTOR
does not help in this scenario.
The logs show
req= 1110016 avail= 2503727104.0 donors [('11', 194375270.40000001),...
borrow 90484.1597129 from 11 - so, beg for 90K from a domain
borrow 132239.288652 from 10
borrow 537099.316089 from 0
borrow 148004.024941 from 7
borrow 139834.21573 from 9
borrow 117855.794876 from 8
and then we fail when a domain does not provide this lousy 90KB.
The solution is to ask for actual_need+XEN_FREE_MEM_LEFT, but return if we already
have actual_need+XEN_FREE_MEM_MIN (the latter is 25MB smaller).
2011-04-05 10:52:53 +02:00
|
|
|
if xenfree >= memsize + self.XEN_FREE_MEM_MIN:
|
2011-07-22 13:40:21 +02:00
|
|
|
self.inhibit_balloon_up()
|
2010-08-30 11:40:19 +02:00
|
|
|
return True
|
2015-10-09 19:29:15 +02:00
|
|
|
# fail the request if over past CHECK_PERIOD_S seconds,
|
|
|
|
# we got less than CHECK_MB_S MB/s on average
|
2015-10-09 13:47:27 +02:00
|
|
|
ring_slot = niter % check_period
|
|
|
|
if niter >= check_period and xenfree < xenfree_ring[ring_slot] + check_delta:
|
|
|
|
return False
|
|
|
|
xenfree_ring[ring_slot] = xenfree
|
2010-08-30 11:40:19 +02:00
|
|
|
if prev_memory_actual is not None:
|
|
|
|
for i in prev_memory_actual.keys():
|
|
|
|
if prev_memory_actual[i] == self.domdict[i].memory_actual:
|
2018-01-06 15:12:48 +01:00
|
|
|
# domain not responding to memset requests, remove it
|
|
|
|
# from donors
|
2010-08-30 11:40:19 +02:00
|
|
|
self.domdict[i].no_progress = True
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.info('domain {} stuck at {}'.format(i, self.domdict[i].memory_actual))
|
2015-10-01 22:14:35 +02:00
|
|
|
memset_reqs = qubes.qmemman.algo.balloon(memsize + self.XEN_FREE_MEM_LEFT - xenfree, self.domdict)
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.info('memset_reqs={!r}'.format(memset_reqs))
|
2015-10-09 13:47:27 +02:00
|
|
|
if len(memset_reqs) == 0:
|
2010-08-30 11:40:19 +02:00
|
|
|
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
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.debug('sleeping for {} s'.format(self.BALOON_DELAY))
|
2010-08-30 11:40:19 +02:00
|
|
|
time.sleep(self.BALOON_DELAY)
|
|
|
|
niter = niter + 1
|
2012-07-05 01:29:16 +02:00
|
|
|
|
2011-05-04 17:10:01 +02:00
|
|
|
def refresh_meminfo(self, domid, untrusted_meminfo_key):
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.debug(
|
|
|
|
'refresh_meminfo(domid={}, untrusted_meminfo_key={!r})'.format(
|
|
|
|
domid, untrusted_meminfo_key))
|
|
|
|
|
2015-10-01 22:14:35 +02:00
|
|
|
qubes.qmemman.algo.refresh_meminfo_for_domain(
|
2015-03-17 16:45:00 +01:00
|
|
|
self.domdict[domid], untrusted_meminfo_key)
|
2010-09-09 12:36:18 +02:00
|
|
|
self.do_balance()
|
2010-08-30 11:40:19 +02:00
|
|
|
|
2018-01-06 15:12:48 +01:00
|
|
|
# is the computed balance request big enough ?
|
|
|
|
# so that we do not trash with small adjustments
|
2010-09-16 16:00:07 +02:00
|
|
|
def is_balance_req_significant(self, memset_reqs, xenfree):
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.debug(
|
|
|
|
'is_balance_req_significant(memset_reqs={}, xenfree={})'.format(
|
|
|
|
memset_reqs, xenfree))
|
|
|
|
|
2010-09-01 10:39:39 +02:00
|
|
|
total_memory_transfer = 0
|
|
|
|
MIN_TOTAL_MEMORY_TRANSFER = 150*1024*1024
|
2010-09-10 11:35:30 +02:00
|
|
|
MIN_MEM_CHANGE_WHEN_UNDER_PREF = 15*1024*1024
|
2015-03-17 16:45:00 +01:00
|
|
|
|
2012-06-26 00:26:53 +02:00
|
|
|
# If xenfree to low, return immediately
|
|
|
|
if self.XEN_FREE_MEM_LEFT - xenfree > MIN_MEM_CHANGE_WHEN_UNDER_PREF:
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.debug('xenfree is too low, returning')
|
2012-06-26 00:26:53 +02:00
|
|
|
return True
|
2015-03-17 16:45:00 +01:00
|
|
|
|
2010-09-01 10:39:39 +02:00
|
|
|
for rq in memset_reqs:
|
|
|
|
dom, mem = rq
|
2010-09-10 11:35:30 +02:00
|
|
|
last_target = self.domdict[dom].last_target
|
|
|
|
memory_change = mem - last_target
|
2010-09-01 10:39:39 +02:00
|
|
|
total_memory_transfer += abs(memory_change)
|
2015-10-01 22:14:35 +02:00
|
|
|
pref = qubes.qmemman.algo.prefmem(self.domdict[dom])
|
2015-03-17 16:45:00 +01:00
|
|
|
|
2018-01-06 15:12:48 +01:00
|
|
|
if 0 < last_target < pref and \
|
|
|
|
memory_change > MIN_MEM_CHANGE_WHEN_UNDER_PREF:
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.info(
|
|
|
|
'dom {} is below pref, allowing balance'.format(dom))
|
2010-09-10 11:35:30 +02:00
|
|
|
return True
|
2015-03-17 16:45:00 +01:00
|
|
|
|
|
|
|
ret = total_memory_transfer + abs(xenfree - self.XEN_FREE_MEM_LEFT) > MIN_TOTAL_MEMORY_TRANSFER
|
|
|
|
self.log.debug('is_balance_req_significant return {}'.format(ret))
|
|
|
|
return ret
|
|
|
|
|
2010-09-01 10:39:39 +02:00
|
|
|
|
2010-09-09 10:29:35 +02:00
|
|
|
def print_stats(self, xenfree, memset_reqs):
|
|
|
|
for i in self.domdict.keys():
|
2016-09-07 03:43:46 +02:00
|
|
|
if self.domdict[i].mem_used is not None:
|
2018-01-07 17:09:06 +01:00
|
|
|
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 ''))
|
2015-03-17 16:45:00 +01:00
|
|
|
|
|
|
|
self.log.info('stat: xenfree={} memset_reqs={}'.format(xenfree, memset_reqs))
|
|
|
|
|
2012-07-05 01:29:16 +02:00
|
|
|
|
2010-08-30 11:40:19 +02:00
|
|
|
def do_balance(self):
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.debug('do_balance()')
|
2010-09-07 16:00:14 +02:00
|
|
|
if os.path.isfile('/var/run/qubes/do-not-membalance'):
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.debug('do-not-membalance file preset, returning')
|
2010-08-30 11:40:19 +02:00
|
|
|
return
|
2015-03-17 16:45:00 +01:00
|
|
|
|
2010-08-30 11:40:19 +02:00
|
|
|
self.refresh_memactual()
|
2012-07-05 01:23:43 +02:00
|
|
|
self.clear_outdated_error_markers()
|
2010-08-30 11:40:19 +02:00
|
|
|
xenfree = self.get_free_xen_memory()
|
2015-10-01 22:14:35 +02:00
|
|
|
memset_reqs = qubes.qmemman.algo.balance(xenfree - self.XEN_FREE_MEM_LEFT, self.domdict)
|
2010-09-16 16:00:07 +02:00
|
|
|
if not self.is_balance_req_significant(memset_reqs, xenfree):
|
2010-09-01 10:39:39 +02:00
|
|
|
return
|
2012-07-05 01:29:16 +02:00
|
|
|
|
2010-09-09 10:29:35 +02:00
|
|
|
self.print_stats(xenfree, memset_reqs)
|
2010-09-09 10:36:13 +02:00
|
|
|
|
2012-07-05 01:23:43 +02:00
|
|
|
prev_memactual = {}
|
|
|
|
for i in self.domdict.keys():
|
|
|
|
prev_memactual[i] = self.domdict[i].memory_actual
|
2010-09-09 10:36:13 +02:00
|
|
|
for rq in memset_reqs:
|
|
|
|
dom, mem = rq
|
2012-07-05 01:23:43 +02:00
|
|
|
# Force to always have at least 0.9*self.XEN_FREE_MEM_LEFT (some
|
|
|
|
# margin for rounding errors). Before giving memory to
|
2018-01-06 15:12:48 +01:00
|
|
|
# domain, ensure that others have gave it back.
|
2012-07-05 01:23:43 +02:00
|
|
|
# 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:
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.debug('do_balance dom={!r} sleeping ntries={}'.format(
|
|
|
|
dom, ntries))
|
2012-07-05 01:23:43 +02:00
|
|
|
time.sleep(self.BALOON_DELAY)
|
2016-01-06 00:40:10 +01:00
|
|
|
self.refresh_memactual()
|
2012-07-05 01:23:43 +02:00
|
|
|
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:
|
2018-01-06 15:12:48 +01:00
|
|
|
# All donors have been processed
|
|
|
|
break
|
2012-07-05 01:23:43 +02:00
|
|
|
# allow some small margin
|
|
|
|
if self.domdict[dom2].memory_actual > self.domdict[dom2].last_target + self.XEN_FREE_MEM_LEFT/4:
|
2018-01-06 15:12:48 +01:00
|
|
|
# VM didn't react to memory request at all,
|
|
|
|
# remove from donors
|
2012-07-05 01:23:43 +02:00
|
|
|
if prev_memactual[dom2] == self.domdict[dom2].memory_actual:
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.warning(
|
|
|
|
'dom {!r} didnt react to memory request'
|
|
|
|
' (holds {}, requested balloon down to {})'
|
|
|
|
.format(dom2,
|
|
|
|
self.domdict[dom2].memory_actual,
|
|
|
|
mem2))
|
2012-07-05 01:23:43 +02:00
|
|
|
self.domdict[dom2].no_progress = True
|
|
|
|
else:
|
2015-03-17 16:45:00 +01:00
|
|
|
self.log.warning('dom {!r} still hold more'
|
|
|
|
' memory than have assigned ({} > {})'
|
|
|
|
.format(dom2,
|
|
|
|
self.domdict[dom2].memory_actual,
|
|
|
|
mem2))
|
2012-07-05 01:23:43 +02:00
|
|
|
self.domdict[dom2].slow_memset_react = True
|
|
|
|
self.mem_set(dom, self.get_free_xen_memory() + self.domdict[dom].memory_actual - self.XEN_FREE_MEM_LEFT)
|
|
|
|
return
|
|
|
|
|
2010-08-30 11:40:19 +02:00
|
|
|
self.mem_set(dom, mem)
|
|
|
|
|
|
|
|
# for i in self.domdict.keys():
|
2016-09-07 03:43:46 +02:00
|
|
|
# print 'domain ', i, ' meminfo=', self.domdict[i].mem_used, 'actual mem', self.domdict[i].memory_actual
|
2010-08-30 11:40:19 +02:00
|
|
|
# print 'domain ', i, 'actual mem', self.domdict[i].memory_actual
|
|
|
|
# print 'xen free mem', self.get_free_xen_memory()
|