qmemman: add comments, make some identifiers more verbose

This commit is contained in:
Rafal Wojtczuk 2011-05-04 17:58:28 +02:00
parent 18e207cbc5
commit 6067be29df
3 changed files with 93 additions and 69 deletions

View File

@ -7,11 +7,11 @@ import os
class DomainState: class DomainState:
def __init__(self, id): def __init__(self, id):
self.meminfo = None self.meminfo = None #dictionary of memory info read from client
self.memory_actual = None self.memory_actual = None #the current memory size
self.mem_used = None self.mem_used = None #used memory, computed based on meminfo
self.id = id self.id = id #domain id
self.last_target = 0 self.last_target = 0 #the last memset target
class SystemState: class SystemState:
def __init__(self): def __init__(self):
@ -36,6 +36,7 @@ class SystemState:
# ret = host_metrics_record["memory_free"] # ret = host_metrics_record["memory_free"]
# return long(ret) # return long(ret)
#refresh information on memory assigned to all domains
def refresh_memactual(self): def refresh_memactual(self):
for domain in self.xc.domain_getinfo(): for domain in self.xc.domain_getinfo():
id = str(domain['domid']) id = str(domain['domid'])
@ -67,6 +68,7 @@ class SystemState:
except XenAPI.Failure: except XenAPI.Failure:
pass pass
#perform memory ballooning, across all domains, to add "memsize" to Xen free memory
def do_balloon(self, memsize): def do_balloon(self, memsize):
MAX_TRIES = 20 MAX_TRIES = 20
niter = 0 niter = 0
@ -82,6 +84,7 @@ class SystemState:
if prev_memory_actual is not None: if prev_memory_actual is not None:
for i in prev_memory_actual.keys(): for i in prev_memory_actual.keys():
if prev_memory_actual[i] == self.domdict[i].memory_actual: 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.domdict[i].no_progress = True
print 'domain', i, 'stuck at', self.domdict[i].memory_actual print 'domain', i, 'stuck at', self.domdict[i].memory_actual
memset_reqs = qmemman_algo.balloon(memsize + self.XEN_FREE_MEM_LEFT - xenfree, self.domdict) 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) qmemman_algo.refresh_meminfo_for_domain(self.domdict[domid], untrusted_meminfo_key)
self.do_balance() 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): def is_balance_req_significant(self, memset_reqs, xenfree):
total_memory_transfer = 0 total_memory_transfer = 0
MIN_TOTAL_MEMORY_TRANSFER = 150*1024*1024 MIN_TOTAL_MEMORY_TRANSFER = 150*1024*1024

View File

@ -14,7 +14,7 @@ def parse_meminfo(untrusted_meminfo):
return untrusted_dict return untrusted_dict
def is_meminfo_suspicious(dom, untrusted_meminfo): def is_meminfo_suspicious(domain, untrusted_meminfo):
ret = False ret = False
#check whether the required keys exist and are not negative #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 #it can be achieved with legal values, too, and it will not allow to
#starve existing domains, by design #starve existing domains, by design
if ret: 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 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) untrusted_meminfo = parse_meminfo(untrusted_xenstore_key)
if untrusted_meminfo is None: if untrusted_meminfo is None:
dom.meminfo = None domain.meminfo = None
return return
#sanitize start #sanitize start
if is_meminfo_suspicious(dom, untrusted_meminfo): if is_meminfo_suspicious(domain, untrusted_meminfo):
#sanitize end #sanitize end
dom.meminfo = None domain.meminfo = None
dom.mem_used = None domain.mem_used = None
else: else:
#sanitized, can assign #sanitized, can assign
dom.meminfo = untrusted_meminfo domain.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.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 CACHE_FACTOR = 1.3
#dom0 is special, as it must have large cache, for vbds. Thus, give it a special boost #dom0 is special, as it must have large cache, for vbds. Thus, give it a special boost
if dom.id == '0': if domain.id == '0':
return dom.mem_used*CACHE_FACTOR + 350*1024*1024 return domain.mem_used*CACHE_FACTOR + 350*1024*1024
return dom.mem_used*CACHE_FACTOR return domain.mem_used*CACHE_FACTOR
def memneeded(dom): def memory_needed(domain):
#do not change #do not change
#in balance(), "distribute totalsum proportionally to mempref" relies on this exact formula #in balance(), "distribute total_available_memory proportionally to mempref" relies on this exact formula
ret = prefmem(dom) - dom.memory_actual ret = prefmem(domain) - domain.memory_actual
return ret return ret
#prepare list of (domain, memory_target) pairs that need to be passed
def balloon(memsize, domdict): #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 REQ_SAFETY_NET_FACTOR = 1.05
donors = list() donors = list()
request = list() request = list()
available = 0 available = 0
for i in domdict.keys(): for i in domain_dictionary.keys():
if domdict[i].meminfo is None: if domain_dictionary[i].meminfo is None:
continue continue
if domdict[i].no_progress: if domain_dictionary[i].no_progress:
continue continue
need = memneeded(domdict[i]) need = memory_needed(domain_dictionary[i])
if need < 0: 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)) donors.append((i,-need))
available-=need available-=need
print 'req=', memsize, 'avail=', available, 'donors', donors print 'req=', memsize, 'avail=', available, 'donors', donors
@ -92,78 +95,92 @@ def balloon(memsize, domdict):
id, mem = donors_iter id, mem = donors_iter
memborrowed = mem*scale*REQ_SAFETY_NET_FACTOR memborrowed = mem*scale*REQ_SAFETY_NET_FACTOR
print 'borrow' , memborrowed, 'from', id print 'borrow' , memborrowed, 'from', id
memtarget = int(domdict[id].memory_actual - memborrowed) memtarget = int(domain_dictionary[id].memory_actual - memborrowed)
request.append((id, memtarget)) request.append((id, memtarget))
return request return request
# REQ_SAFETY_NET_FACTOR is a bit greater that 1. So that if the domain yields a bit less than requested, due # 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. # 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 #redistribute positive "total_available_memory" of memory between domains, proportionally to prefmem
def balance_when_enough_memory(domdict, xenfree, total_mem_pref, totalsum): def balance_when_enough_memory(domain_dictionary, xen_free_memory, total_mem_pref, total_available_memory):
donors_rq = list() donors_rq = list()
acceptors_rq = list() acceptors_rq = list()
for i in domdict.keys(): for i in domain_dictionary.keys():
if domdict[i].meminfo is None: if domain_dictionary[i].meminfo is None:
continue continue
#distribute totalsum proportionally to mempref #distribute total_available_memory proportionally to mempref
scale = 1.0*prefmem(domdict[i])/total_mem_pref scale = 1.0*prefmem(domain_dictionary[i])/total_mem_pref
target_nonint = prefmem(domdict[i]) + scale*totalsum target_nonint = prefmem(domain_dictionary[i]) + scale*total_available_memory
#prevent rounding errors #prevent rounding errors
target = int(0.999*target_nonint) target = int(0.999*target_nonint)
if (target < domdict[i].memory_actual): if (target < domain_dictionary[i].memory_actual):
donors_rq.append((i, target)) donors_rq.append((i, target))
else: else:
acceptors_rq.append((i, target)) 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 return donors_rq + acceptors_rq
#when not enough mem to make everyone be above prefmem, make donors be at prefmem, and #when not enough mem to make everyone be above prefmem, make donors be at prefmem, and
#redistribute anything left between acceptors #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() donors_rq = list()
acceptors_rq = list() acceptors_rq = list()
squeezed_mem = xenfree squeezed_mem = xen_free_memory
for i in donors: for i in donors:
avail = -memneeded(domdict[i]) avail = -memory_needed(domain_dictionary[i])
if avail < 10*1024*1024: if avail < 10*1024*1024:
#probably we have already tried making it exactly at prefmem, give up #probably we have already tried making it exactly at prefmem, give up
continue continue
squeezed_mem -= avail 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 #the below can happen if initially xen free memory is below 50M
if squeezed_mem < 0: if squeezed_mem < 0:
return donors_rq return donors_rq
for i in acceptors: for i in acceptors:
scale = 1.0*prefmem(domdict[i])/total_mem_pref_acceptors scale = 1.0*prefmem(domain_dictionary[i])/total_mem_pref_acceptors
target_nonint = domdict[i].memory_actual + scale*squeezed_mem target_nonint = domain_dictionary[i].memory_actual + scale*squeezed_mem
acceptors_rq.append((i, int(target_nonint))) 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 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 total_mem_pref = 0
#sum of memory preferences of all domains that require more memory
total_mem_pref_acceptors = 0 total_mem_pref_acceptors = 0
donors = list() donors = list() # domains that can yield memory
acceptors = list() acceptors = list() # domains that require more memory
#pass 1: compute the above "total" values #pass 1: compute the above "total" values
for i in domdict.keys(): for i in domain_dictionary.keys():
if domdict[i].meminfo is None: if domain_dictionary[i].meminfo is None:
continue continue
need = memneeded(domdict[i]) need = memory_needed(domain_dictionary[i])
# print 'domain' , i, 'act/pref', domdict[i].memory_actual, prefmem(domdict[i]), 'need=', need # print 'domain' , i, 'act/pref', domain_dictionary[i].memory_actual, prefmem(domain_dictionary[i]), 'need=', need
if need < 0: if need < 0:
donors.append(i) donors.append(i)
else: else:
acceptors.append(i) acceptors.append(i)
total_mem_pref_acceptors += prefmem(domdict[i]) total_mem_pref_acceptors += prefmem(domain_dictionary[i])
total_memneeded += need total_memory_needed += need
total_mem_pref += prefmem(domdict[i]) total_mem_pref += prefmem(domain_dictionary[i])
totalsum = xenfree - total_memneeded total_available_memory = xen_free_memory - total_memory_needed
if totalsum > 0: if total_available_memory > 0:
return balance_when_enough_memory(domdict, xenfree, total_mem_pref, totalsum) return balance_when_enough_memory(domain_dictionary, xen_free_memory, total_mem_pref, total_available_memory)
else: 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)

View File

@ -17,7 +17,7 @@ def only_in_first_list(l1, l2):
ret.append(i) ret.append(i)
return ret return ret
def get_req_node(domain_id): def get_domain_meminfo_key(domain_id):
return '/local/domain/'+domain_id+'/memory/meminfo' return '/local/domain/'+domain_id+'/memory/meminfo'
@ -29,27 +29,29 @@ class WatchType:
class XS_Watcher: class XS_Watcher:
def __init__(self): def __init__(self):
self.handle = xen.lowlevel.xs.xs() 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 = {} self.watch_token_dict = {}
def dom_list_change(self, param): def domain_list_changed(self, param):
curr = self.handle.ls('', '/local/domain') curr = self.handle.ls('', '/local/domain')
if curr == None: if curr == None:
return return
global_lock.acquire() global_lock.acquire()
for i in only_in_first_list(curr, self.watch_token_dict.keys()): 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.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) system_state.add_domain(i)
for i in only_in_first_list(self.watch_token_dict.keys(), curr): 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) self.watch_token_dict.pop(i)
system_state.del_domain(i) system_state.del_domain(i)
global_lock.release() global_lock.release()
def request(self, domain_id): def meminfo_changed(self, domain_id):
untrusted_meminfo_key = self.handle.read('', get_req_node(domain_id)) untrusted_meminfo_key = self.handle.read('', get_domain_meminfo_key(domain_id))
if untrusted_meminfo_key == None or untrusted_meminfo_key == '': if untrusted_meminfo_key == None or untrusted_meminfo_key == '':
return return
global_lock.acquire() global_lock.acquire()