qmemman: add comments, make some identifiers more verbose
This commit is contained in:
parent
18e207cbc5
commit
6067be29df
@ -7,11 +7,11 @@ import os
|
||||
|
||||
class DomainState:
|
||||
def __init__(self, id):
|
||||
self.meminfo = None
|
||||
self.memory_actual = None
|
||||
self.mem_used = None
|
||||
self.id = id
|
||||
self.last_target = 0
|
||||
self.meminfo = None #dictionary of memory info read from client
|
||||
self.memory_actual = None #the current 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
|
||||
|
||||
class SystemState:
|
||||
def __init__(self):
|
||||
@ -36,6 +36,7 @@ class SystemState:
|
||||
# ret = host_metrics_record["memory_free"]
|
||||
# return long(ret)
|
||||
|
||||
#refresh information on memory assigned to all domains
|
||||
def refresh_memactual(self):
|
||||
for domain in self.xc.domain_getinfo():
|
||||
id = str(domain['domid'])
|
||||
@ -67,6 +68,7 @@ class SystemState:
|
||||
except XenAPI.Failure:
|
||||
pass
|
||||
|
||||
#perform memory ballooning, across all domains, to add "memsize" to Xen free memory
|
||||
def do_balloon(self, memsize):
|
||||
MAX_TRIES = 20
|
||||
niter = 0
|
||||
@ -82,6 +84,7 @@ class SystemState:
|
||||
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
|
||||
print 'domain', i, 'stuck at', self.domdict[i].memory_actual
|
||||
memset_reqs = qmemman_algo.balloon(memsize + self.XEN_FREE_MEM_LEFT - xenfree, self.domdict)
|
||||
@ -100,6 +103,8 @@ class SystemState:
|
||||
qmemman_algo.refresh_meminfo_for_domain(self.domdict[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):
|
||||
total_memory_transfer = 0
|
||||
MIN_TOTAL_MEMORY_TRANSFER = 150*1024*1024
|
||||
|
@ -14,7 +14,7 @@ def parse_meminfo(untrusted_meminfo):
|
||||
|
||||
return untrusted_dict
|
||||
|
||||
def is_meminfo_suspicious(dom, untrusted_meminfo):
|
||||
def is_meminfo_suspicious(domain, untrusted_meminfo):
|
||||
ret = False
|
||||
|
||||
#check whether the required keys exist and are not negative
|
||||
@ -37,51 +37,54 @@ def is_meminfo_suspicious(dom, untrusted_meminfo):
|
||||
#it can be achieved with legal values, too, and it will not allow to
|
||||
#starve existing domains, by design
|
||||
if ret:
|
||||
print 'suspicious meminfo for domain', dom.id, 'mem actual', dom.memory_actual, untrusted_meminfo
|
||||
print 'suspicious meminfo for domain', domain.id, 'mem actual', domain.memory_actual, untrusted_meminfo
|
||||
return ret
|
||||
|
||||
def refresh_meminfo_for_domain(dom, untrusted_xenstore_key):
|
||||
#called when a domain updates its 'meminfo' xenstore key
|
||||
def refresh_meminfo_for_domain(domain, untrusted_xenstore_key):
|
||||
untrusted_meminfo = parse_meminfo(untrusted_xenstore_key)
|
||||
if untrusted_meminfo is None:
|
||||
dom.meminfo = None
|
||||
domain.meminfo = None
|
||||
return
|
||||
#sanitize start
|
||||
if is_meminfo_suspicious(dom, untrusted_meminfo):
|
||||
if is_meminfo_suspicious(domain, untrusted_meminfo):
|
||||
#sanitize end
|
||||
dom.meminfo = None
|
||||
dom.mem_used = None
|
||||
domain.meminfo = None
|
||||
domain.mem_used = None
|
||||
else:
|
||||
#sanitized, can assign
|
||||
dom.meminfo = untrusted_meminfo
|
||||
dom.mem_used = dom.meminfo['MemTotal'] - dom.meminfo['MemFree'] - dom.meminfo['Cached'] - dom.meminfo['Buffers'] + dom.meminfo['SwapTotal'] - dom.meminfo['SwapFree']
|
||||
domain.meminfo = untrusted_meminfo
|
||||
domain.mem_used = domain.meminfo['MemTotal'] - domain.meminfo['MemFree'] - domain.meminfo['Cached'] - domain.meminfo['Buffers'] + domain.meminfo['SwapTotal'] - domain.meminfo['SwapFree']
|
||||
|
||||
def prefmem(dom):
|
||||
def prefmem(domain):
|
||||
CACHE_FACTOR = 1.3
|
||||
#dom0 is special, as it must have large cache, for vbds. Thus, give it a special boost
|
||||
if dom.id == '0':
|
||||
return dom.mem_used*CACHE_FACTOR + 350*1024*1024
|
||||
return dom.mem_used*CACHE_FACTOR
|
||||
if domain.id == '0':
|
||||
return domain.mem_used*CACHE_FACTOR + 350*1024*1024
|
||||
return domain.mem_used*CACHE_FACTOR
|
||||
|
||||
def memneeded(dom):
|
||||
def memory_needed(domain):
|
||||
#do not change
|
||||
#in balance(), "distribute totalsum proportionally to mempref" relies on this exact formula
|
||||
ret = prefmem(dom) - dom.memory_actual
|
||||
#in balance(), "distribute total_available_memory proportionally to mempref" relies on this exact formula
|
||||
ret = prefmem(domain) - domain.memory_actual
|
||||
return ret
|
||||
|
||||
|
||||
def balloon(memsize, domdict):
|
||||
#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):
|
||||
REQ_SAFETY_NET_FACTOR = 1.05
|
||||
donors = list()
|
||||
request = list()
|
||||
available = 0
|
||||
for i in domdict.keys():
|
||||
if domdict[i].meminfo is None:
|
||||
for i in domain_dictionary.keys():
|
||||
if domain_dictionary[i].meminfo is None:
|
||||
continue
|
||||
if domdict[i].no_progress:
|
||||
if domain_dictionary[i].no_progress:
|
||||
continue
|
||||
need = memneeded(domdict[i])
|
||||
need = memory_needed(domain_dictionary[i])
|
||||
if need < 0:
|
||||
print 'balloon: dom' , i, 'has actual memory', domdict[i].memory_actual
|
||||
print 'balloon: dom' , i, 'has actual memory', domain_dictionary[i].memory_actual
|
||||
donors.append((i,-need))
|
||||
available-=need
|
||||
print 'req=', memsize, 'avail=', available, 'donors', donors
|
||||
@ -92,78 +95,92 @@ def balloon(memsize, domdict):
|
||||
id, mem = donors_iter
|
||||
memborrowed = mem*scale*REQ_SAFETY_NET_FACTOR
|
||||
print 'borrow' , memborrowed, 'from', id
|
||||
memtarget = int(domdict[id].memory_actual - memborrowed)
|
||||
memtarget = int(domain_dictionary[id].memory_actual - memborrowed)
|
||||
request.append((id, memtarget))
|
||||
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 "totalsum" of memory between domains, proportionally to prefmem
|
||||
def balance_when_enough_memory(domdict, xenfree, total_mem_pref, totalsum):
|
||||
#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):
|
||||
donors_rq = list()
|
||||
acceptors_rq = list()
|
||||
for i in domdict.keys():
|
||||
if domdict[i].meminfo is None:
|
||||
for i in domain_dictionary.keys():
|
||||
if domain_dictionary[i].meminfo is None:
|
||||
continue
|
||||
#distribute totalsum proportionally to mempref
|
||||
scale = 1.0*prefmem(domdict[i])/total_mem_pref
|
||||
target_nonint = prefmem(domdict[i]) + scale*totalsum
|
||||
#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
|
||||
target = int(0.999*target_nonint)
|
||||
if (target < domdict[i].memory_actual):
|
||||
if (target < domain_dictionary[i].memory_actual):
|
||||
donors_rq.append((i, target))
|
||||
else:
|
||||
acceptors_rq.append((i, target))
|
||||
# print 'balance(enough): xenfree=', xenfree, 'requests:', donors_rq + acceptors_rq
|
||||
# print 'balance(enough): xen_free_memory=', xen_free_memory, 'requests:', donors_rq + acceptors_rq
|
||||
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(domdict, xenfree, total_mem_pref_acceptors, donors, acceptors):
|
||||
def balance_when_low_on_memory(domain_dictionary, xen_free_memory, total_mem_pref_acceptors, donors, acceptors):
|
||||
donors_rq = list()
|
||||
acceptors_rq = list()
|
||||
squeezed_mem = xenfree
|
||||
squeezed_mem = xen_free_memory
|
||||
for i in donors:
|
||||
avail = -memneeded(domdict[i])
|
||||
avail = -memory_needed(domain_dictionary[i])
|
||||
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(domdict[i])))
|
||||
donors_rq.append((i, prefmem(domain_dictionary[i])))
|
||||
#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(domdict[i])/total_mem_pref_acceptors
|
||||
target_nonint = domdict[i].memory_actual + scale*squeezed_mem
|
||||
scale = 1.0*prefmem(domain_dictionary[i])/total_mem_pref_acceptors
|
||||
target_nonint = domain_dictionary[i].memory_actual + scale*squeezed_mem
|
||||
acceptors_rq.append((i, int(target_nonint)))
|
||||
# print 'balance(low): xenfree=', xenfree, 'requests:', donors_rq + acceptors_rq
|
||||
# print 'balance(low): xen_free_memory=', xen_free_memory, 'requests:', donors_rq + acceptors_rq
|
||||
return donors_rq + acceptors_rq
|
||||
|
||||
def balance(xenfree, domdict):
|
||||
total_memneeded = 0
|
||||
|
||||
|
||||
#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):
|
||||
|
||||
#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
|
||||
total_mem_pref = 0
|
||||
|
||||
#sum of memory preferences of all domains that require more memory
|
||||
total_mem_pref_acceptors = 0
|
||||
|
||||
donors = list()
|
||||
acceptors = list()
|
||||
donors = list() # domains that can yield memory
|
||||
acceptors = list() # domains that require more memory
|
||||
#pass 1: compute the above "total" values
|
||||
for i in domdict.keys():
|
||||
if domdict[i].meminfo is None:
|
||||
for i in domain_dictionary.keys():
|
||||
if domain_dictionary[i].meminfo is None:
|
||||
continue
|
||||
need = memneeded(domdict[i])
|
||||
# print 'domain' , i, 'act/pref', domdict[i].memory_actual, prefmem(domdict[i]), 'need=', need
|
||||
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:
|
||||
donors.append(i)
|
||||
else:
|
||||
acceptors.append(i)
|
||||
total_mem_pref_acceptors += prefmem(domdict[i])
|
||||
total_memneeded += need
|
||||
total_mem_pref += prefmem(domdict[i])
|
||||
total_mem_pref_acceptors += prefmem(domain_dictionary[i])
|
||||
total_memory_needed += need
|
||||
total_mem_pref += prefmem(domain_dictionary[i])
|
||||
|
||||
totalsum = xenfree - total_memneeded
|
||||
if totalsum > 0:
|
||||
return balance_when_enough_memory(domdict, xenfree, total_mem_pref, totalsum)
|
||||
total_available_memory = xen_free_memory - total_memory_needed
|
||||
if total_available_memory > 0:
|
||||
return balance_when_enough_memory(domain_dictionary, xen_free_memory, total_mem_pref, total_available_memory)
|
||||
else:
|
||||
return balance_when_low_on_memory(domdict, xenfree, total_mem_pref_acceptors, donors, acceptors)
|
||||
return balance_when_low_on_memory(domain_dictionary, xen_free_memory, total_mem_pref_acceptors, donors, acceptors)
|
||||
|
@ -17,7 +17,7 @@ def only_in_first_list(l1, l2):
|
||||
ret.append(i)
|
||||
return ret
|
||||
|
||||
def get_req_node(domain_id):
|
||||
def get_domain_meminfo_key(domain_id):
|
||||
return '/local/domain/'+domain_id+'/memory/meminfo'
|
||||
|
||||
|
||||
@ -29,27 +29,29 @@ class WatchType:
|
||||
class XS_Watcher:
|
||||
def __init__(self):
|
||||
self.handle = xen.lowlevel.xs.xs()
|
||||
self.handle.watch('/vm', WatchType(XS_Watcher.dom_list_change, None))
|
||||
self.handle.watch('/vm', WatchType(XS_Watcher.domain_list_changed, None))
|
||||
self.watch_token_dict = {}
|
||||
|
||||
def dom_list_change(self, param):
|
||||
def domain_list_changed(self, param):
|
||||
curr = self.handle.ls('', '/local/domain')
|
||||
if curr == None:
|
||||
return
|
||||
global_lock.acquire()
|
||||
for i in only_in_first_list(curr, self.watch_token_dict.keys()):
|
||||
watch = WatchType(XS_Watcher.request, i)
|
||||
#new domain has been created
|
||||
watch = WatchType(XS_Watcher.meminfo_changed, i)
|
||||
self.watch_token_dict[i] = watch
|
||||
self.handle.watch(get_req_node(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):
|
||||
self.handle.unwatch(get_req_node(i), self.watch_token_dict[i])
|
||||
#domain destroyed
|
||||
self.handle.unwatch(get_domain_meminfo_key(i), self.watch_token_dict[i])
|
||||
self.watch_token_dict.pop(i)
|
||||
system_state.del_domain(i)
|
||||
global_lock.release()
|
||||
|
||||
def request(self, domain_id):
|
||||
untrusted_meminfo_key = self.handle.read('', get_req_node(domain_id))
|
||||
def meminfo_changed(self, domain_id):
|
||||
untrusted_meminfo_key = self.handle.read('', get_domain_meminfo_key(domain_id))
|
||||
if untrusted_meminfo_key == None or untrusted_meminfo_key == '':
|
||||
return
|
||||
global_lock.acquire()
|
||||
|
Loading…
Reference in New Issue
Block a user