Merge branch 'qmemman' of git://qubes-os.org/rafal/core
This commit is contained in:
		
						commit
						9b8c018bc2
					
				| @ -35,13 +35,8 @@ start() | ||||
| 			(read a b c d ; xenstore-write device/qubes_used_mem $c) | ||||
| 		# we're still running in DispVM template | ||||
| 		echo "Waiting for save/restore..." | ||||
| 		# WARNING: Nergalism! | ||||
| 		# Apparently it has been determined that DomU kernel | ||||
| 		# dmesg's "using vcpu" after restore | ||||
| 		while ! dmesg -c | grep "using vcpu" ; do usleep 10 ; done | ||||
| 		# we're now after restore in a new instance of a DispVM | ||||
| 		# ... wait until qubes_restore.c (in Dom0) recreates VM-specific keys | ||||
| 		while ! xenstore-read qubes_vm_type 2>/dev/null ; do  | ||||
| 		while ! xenstore-read qubes_restore_complete 2>/dev/null ; do  | ||||
| 			usleep 10 | ||||
| 		done | ||||
| 		echo Back to life. | ||||
| @ -87,6 +82,10 @@ start() | ||||
|         fi | ||||
| 	fi | ||||
| 
 | ||||
| 	MEM_CHANGE_THRESHOLD_KB=30000 | ||||
| 	MEMINFO_DELAY_USEC=100000 | ||||
| 	/usr/lib/qubes/meminfo-writer $MEM_CHANGE_THRESHOLD_KB $MEMINFO_DELAY_USEC & | ||||
| 
 | ||||
| 	[ -x /rw/config/rc.local ] && /rw/config/rc.local | ||||
| 	success | ||||
| 	echo "" | ||||
|  | ||||
							
								
								
									
										7
									
								
								common/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								common/Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| CC=gcc | ||||
| CFLAGS=-Wall -g -O3 | ||||
| all:	meminfo-writer | ||||
| meminfo-writer: meminfo-writer.o | ||||
| 	$(CC) -g -o meminfo-writer meminfo-writer.o -lxenstore | ||||
| clean: | ||||
| 	rm -f meminfo-writer *.o *~ | ||||
							
								
								
									
										121
									
								
								common/meminfo-writer.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								common/meminfo-writer.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,121 @@ | ||||
| #include <fcntl.h> | ||||
| #include <unistd.h> | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <xs.h> | ||||
| #include <syslog.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| unsigned long prev_used_mem; | ||||
| int used_mem_change_threshold; | ||||
| int delay; | ||||
| 
 | ||||
| char *parse(char *buf) | ||||
| { | ||||
| 	char *ptr = buf; | ||||
| 	char name[256]; | ||||
| 	static char outbuf[4096]; | ||||
| 	int val; | ||||
| 	int len; | ||||
| 	int MemTotal = 0, MemFree = 0, Buffers = 0, Cached = 0, SwapTotal = | ||||
| 	    0, SwapFree = 0; | ||||
| 	unsigned long long key; | ||||
| 	long used_mem, used_mem_diff; | ||||
| 	int nitems = 0; | ||||
| 
 | ||||
| 	while (nitems != 6) { | ||||
| 		sscanf(ptr, "%s %d kB\n%n", name, &val, &len); | ||||
| 		key = *(unsigned long long *) ptr; | ||||
| 		if (key == *(unsigned long long *) "MemTotal:") { | ||||
| 			MemTotal = val; | ||||
| 			nitems++; | ||||
| 		} else if (key == *(unsigned long long *) "MemFree:") { | ||||
| 			MemFree = val; | ||||
| 			nitems++; | ||||
| 		} else if (key == *(unsigned long long *) "Buffers:") { | ||||
| 			Buffers = val; | ||||
| 			nitems++; | ||||
| 		} else if (key == *(unsigned long long *) "Cached:  ") { | ||||
| 			Cached = val; | ||||
| 			nitems++; | ||||
| 		} else if (key == *(unsigned long long *) "SwapTotal:") { | ||||
| 			SwapTotal = val; | ||||
| 			nitems++; | ||||
| 		} else if (key == *(unsigned long long *) "SwapFree:") { | ||||
| 			SwapFree = val; | ||||
| 			nitems++; | ||||
| 		} | ||||
| 
 | ||||
| 		ptr += len; | ||||
| 	} | ||||
| 
 | ||||
| 	used_mem = | ||||
| 	    MemTotal - Buffers - Cached - MemFree + SwapTotal - SwapFree; | ||||
| 	if (used_mem < 0) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	used_mem_diff = used_mem - prev_used_mem; | ||||
| 	if (used_mem_diff < 0) | ||||
| 		used_mem_diff = -used_mem_diff; | ||||
| 	if (used_mem_diff > used_mem_change_threshold | ||||
| 	    || (used_mem > prev_used_mem && used_mem * 13 / 10 > MemTotal | ||||
| 		&& used_mem_diff > used_mem_change_threshold/2)) { | ||||
| 		prev_used_mem = used_mem; | ||||
| 		sprintf(outbuf, | ||||
| 			"MemTotal: %d kB\nMemFree: %d kB\nBuffers: %d kB\nCached: %d kB\n" | ||||
| 			"SwapTotal: %d kB\nSwapFree: %d kB\n", MemTotal, | ||||
| 			MemFree, Buffers, Cached, SwapTotal, SwapFree); | ||||
| 		return outbuf; | ||||
| 	} | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| void usage() | ||||
| { | ||||
| 	fprintf(stderr, | ||||
| 		"usage: meminfo_writer threshold_in_kb delay_in_us\n"); | ||||
| 	exit(1); | ||||
| } | ||||
| 
 | ||||
| void send_to_qmemman(struct xs_handle *xs, char *data) | ||||
| { | ||||
| 	if (!xs_write(xs, XBT_NULL, "memory/meminfo", data, strlen(data))) { | ||||
| 		syslog(LOG_DAEMON | LOG_ERR, "error writing xenstore ?"); | ||||
| 		exit(1); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| int main(int argc, char **argv) | ||||
| { | ||||
| 	char buf[4096]; | ||||
| 	int n; | ||||
| 	char *meminfo_data; | ||||
| 	int fd; | ||||
| 	struct xs_handle *xs; | ||||
| 
 | ||||
| 	if (argc != 3) | ||||
| 		usage(); | ||||
| 	used_mem_change_threshold = atoi(argv[1]); | ||||
| 	delay = atoi(argv[2]); | ||||
| 	if (!used_mem_change_threshold || !delay) | ||||
| 		usage(); | ||||
| 
 | ||||
| 	fd = open("/proc/meminfo", O_RDONLY); | ||||
| 	if (fd < 0) { | ||||
| 		perror("open meminfo"); | ||||
| 		exit(1); | ||||
| 	} | ||||
| 	xs = xs_domain_open(); | ||||
| 	if (!xs) { | ||||
| 		perror("xs_domain_open"); | ||||
| 		exit(1); | ||||
| 	} | ||||
| 	for (;;) { | ||||
| 		n = pread(fd, buf, sizeof(buf), 0); | ||||
| 		buf[n] = 0; | ||||
| 		meminfo_data = parse(buf); | ||||
| 		if (meminfo_data) | ||||
| 			send_to_qmemman(xs, meminfo_data); | ||||
| 		usleep(delay); | ||||
| 	} | ||||
| } | ||||
| @ -56,6 +56,12 @@ start() | ||||
|     xm mem-set 0 1600 | ||||
|     cp /var/lib/qubes/qubes.xml /var/lib/qubes/backup/qubes-$(date +%F-%T).xml | ||||
|     setup_dvm_files | ||||
|     /usr/lib/qubes/qmemman_daemon.py >/var/log/qubes/qmemman.log 2>/var/log/qubes/qmemman.errs &  | ||||
| 
 | ||||
|     MEM_CHANGE_THRESHOLD_KB=30000 | ||||
|     MEMINFO_DELAY_USEC=100000 | ||||
|     /usr/lib/qubes/meminfo-writer $MEM_CHANGE_THRESHOLD_KB $MEMINFO_DELAY_USEC & | ||||
| 
 | ||||
|     touch /var/lock/subsys/qubes_core | ||||
|     success | ||||
|     echo | ||||
|  | ||||
| @ -30,6 +30,7 @@ import time | ||||
| from qubes.qubes import QubesVmCollection | ||||
| from qubes.qubes import QubesException | ||||
| from qubes.qubes import QubesDaemonPidfile | ||||
| from qubes.qmemman_client import QMemmanClient | ||||
| 
 | ||||
| filename_seq = 50 | ||||
| pen_cmd = '/usr/lib/qubes/qubes_pencmd' | ||||
| @ -187,13 +188,11 @@ class DomainState: | ||||
| 
 | ||||
|     def handle_transfer_disposable(self, transaction_seq): | ||||
| 
 | ||||
|         mem_for_dvm = 400 | ||||
|         xenfreepages_s = subprocess.Popen(["/usr/lib/qubes/xenfreepages"],stdout=subprocess.PIPE).stdout.readline() | ||||
|         xenfree_mb = int(xenfreepages_s)*4096/1024/1024 | ||||
|         if xenfree_mb < mem_for_dvm: | ||||
|             errmsg = 'Not enough memory to create DVM: ' | ||||
|             errmsg +='have ' + str(xenfree_mb) + 'MB, need ' | ||||
|             errmsg +=str(mem_for_dvm) + 'MB. Terminate some appVM and retry.' | ||||
|         qmemman_client = QMemmanClient() | ||||
|         if not qmemman_client.request_memory(400*1024*1024): | ||||
|             qmemman_client.close() | ||||
|             errmsg = 'Not enough memory to create DVM. ' | ||||
|             errmsg +='Terminate some appVM and retry.' | ||||
|             subprocess.call(['/usr/bin/kdialog', '--sorry', errmsg]) | ||||
|             return False | ||||
| 
 | ||||
| @ -205,12 +204,14 @@ class DomainState: | ||||
|         if vm is None: | ||||
|             logproc( 'Domain ' + vmname + ' does not exist ?') | ||||
|             qvm_collection.unlock_db() | ||||
|             qmemman_client.close() | ||||
|             return False | ||||
|         retcode = subprocess.call(['/usr/lib/qubes/qubes_restore', | ||||
|             current_savefile, | ||||
|             '-c', vm.label.color, | ||||
|             '-i', vm.label.icon, | ||||
|             '-l', str(vm.label.index)]) | ||||
|         qmemman_client.close() | ||||
|         if retcode != 0: | ||||
|             subprocess.call(['/usr/bin/kdialog', '--sorry', 'DisposableVM creation failed, see qubes_restore.log']) | ||||
|             qvm_collection.unlock_db() | ||||
|  | ||||
							
								
								
									
										140
									
								
								dom0/qmemman/qmemman.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										140
									
								
								dom0/qmemman/qmemman.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,140 @@ | ||||
| import xen.lowlevel.xc | ||||
| import xen.lowlevel.xs | ||||
| import string | ||||
| import time | ||||
| import qmemman_algo | ||||
| 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 | ||||
| 
 | ||||
| class SystemState: | ||||
|     def __init__(self): | ||||
|         self.domdict = {} | ||||
|         self.xc = xen.lowlevel.xc.xc() | ||||
|         self.xs = xen.lowlevel.xs.xs() | ||||
|         self.BALOON_DELAY = 0.1 | ||||
| 
 | ||||
|     def add_domain(self, id): | ||||
|         self.domdict[id] = DomainState(id) | ||||
| 
 | ||||
|     def del_domain(self, id): | ||||
|         self.domdict.pop(id) | ||||
| 
 | ||||
|     def get_free_xen_memory(self): | ||||
|         return self.xc.physinfo()['free_memory']*1024 | ||||
| #        hosts = self.xend_session.session.xenapi.host.get_all() | ||||
| #        host_record = self.xend_session.session.xenapi.host.get_record(hosts[0]) | ||||
| #        host_metrics_record = self.xend_session.session.xenapi.host_metrics.get_record(host_record["metrics"]) | ||||
| #        ret = host_metrics_record["memory_free"] | ||||
| #        return long(ret) | ||||
| 
 | ||||
|     def refresh_memactual(self): | ||||
|         for domain in self.xc.domain_getinfo(): | ||||
|             id = str(domain['domid']) | ||||
|             if self.domdict.has_key(id): | ||||
|                 self.domdict[id].memory_actual = domain['mem_kb']*1024 | ||||
| 
 | ||||
| #the below works (and is fast), but then 'xm list' shows unchanged memory value | ||||
|     def mem_set(self, id, val): | ||||
|         print 'mem-set domain', id, 'to', val | ||||
|         self.domdict[id].last_target = val | ||||
|         self.xs.write('', '/local/domain/' + id + '/memory/target', str(val/1024)) | ||||
| #can happen in the middle of domain shutdown | ||||
| #apparently xc.lowlevel throws exceptions too | ||||
|         try: | ||||
|             self.xc.domain_set_target_mem(int(id), val/1024) | ||||
|         except: | ||||
|             pass | ||||
|      | ||||
|     def mem_set_obsolete(self, id, val): | ||||
|         uuid = self.domdict[id].uuid | ||||
|         if val >= 2**31: | ||||
|             print 'limiting memory from ', val, 'to maxint because of xml-rpc lameness' | ||||
|             val = 2**31 - 1 | ||||
|         print 'mem-set domain', id, 'to', val | ||||
|         try: | ||||
|             self.xend_session.session.xenapi.VM.set_memory_dynamic_max_live(uuid, val) | ||||
|             self.xend_session.session.xenapi.VM.set_memory_dynamic_min_live(uuid, val) | ||||
| #can happen in the middle of domain shutdown | ||||
|         except XenAPI.Failure: | ||||
|             pass | ||||
| 
 | ||||
|     def do_balloon(self, memsize): | ||||
|         MAX_TRIES = 20 | ||||
|         niter = 0 | ||||
|         prev_memory_actual = None | ||||
|         for i in self.domdict.keys(): | ||||
|             self.domdict[i].no_progress = False | ||||
|         while True: | ||||
|             xenfree = self.get_free_xen_memory() | ||||
|             print 'got xenfree=', xenfree | ||||
|             if xenfree >= memsize: | ||||
|                 return True | ||||
|             self.refresh_memactual() | ||||
|             if prev_memory_actual is not None: | ||||
|                 for i in prev_memory_actual.keys(): | ||||
|                     if prev_memory_actual[i] == self.domdict[i].memory_actual: | ||||
|                         self.domdict[i].no_progress = True | ||||
|                         print 'domain', i, 'stuck at', self.domdict[i].memory_actual | ||||
|             memset_reqs = qmemman_algo.balloon(memsize-xenfree, self.domdict) | ||||
|             print 'requests:', memset_reqs | ||||
|             if niter > MAX_TRIES or 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 | ||||
|             time.sleep(self.BALOON_DELAY) | ||||
|             niter = niter + 1 | ||||
|              | ||||
|     def refresh_meminfo(self, domid, val): | ||||
|         qmemman_algo.refresh_meminfo_for_domain(self.domdict[domid], val) | ||||
|         self.do_balance() | ||||
| 
 | ||||
|     def is_balance_req_significant(self, memset_reqs): | ||||
|         total_memory_transfer = 0 | ||||
|         MIN_TOTAL_MEMORY_TRANSFER = 150*1024*1024 | ||||
|         MIN_MEM_CHANGE_WHEN_UNDER_PREF = 15*1024*1024 | ||||
|         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 = qmemman_algo.prefmem(self.domdict[dom]) | ||||
|             if last_target > 0 and last_target < pref and memory_change > MIN_MEM_CHANGE_WHEN_UNDER_PREF: | ||||
|                 print 'dom', dom, 'is below pref, allowing balance' | ||||
|                 return True | ||||
|         return total_memory_transfer > MIN_TOTAL_MEMORY_TRANSFER | ||||
| 
 | ||||
|     def print_stats(self, xenfree, memset_reqs): | ||||
|         for i in self.domdict.keys(): | ||||
|             if self.domdict[i].meminfo is not None: | ||||
|                 print 'dom' , i, 'act/pref', self.domdict[i].memory_actual, qmemman_algo.prefmem(self.domdict[i]) | ||||
|         print 'xenfree=', xenfree, 'balance req:', memset_reqs | ||||
|                                                  | ||||
|     def do_balance(self): | ||||
|         if os.path.isfile('/var/run/qubes/do-not-membalance'): | ||||
|             return | ||||
|         self.refresh_memactual() | ||||
|         xenfree = self.get_free_xen_memory() | ||||
|         memset_reqs = qmemman_algo.balance(xenfree, self.domdict) | ||||
|         if not self.is_balance_req_significant(memset_reqs): | ||||
|             return | ||||
|              | ||||
|         self.print_stats(xenfree, memset_reqs) | ||||
| 
 | ||||
|         for rq in memset_reqs: | ||||
|             dom, mem = rq | ||||
|             self.mem_set(dom, mem) | ||||
| 
 | ||||
| #        for i in self.domdict.keys(): | ||||
| #            print 'domain ', i, ' meminfo=', self.domdict[i].meminfo, '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() | ||||
							
								
								
									
										153
									
								
								dom0/qmemman/qmemman_algo.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										153
									
								
								dom0/qmemman/qmemman_algo.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,153 @@ | ||||
| import string | ||||
| 
 | ||||
| def parse_meminfo(meminfo): | ||||
|     dict = {} | ||||
|     l1 = string.split(meminfo,"\n") | ||||
|     for i in l1: | ||||
|         l2 = string.split(i) | ||||
|         if len(l2) >= 2: | ||||
|             dict[string.rstrip(l2[0], ":")] = l2[1] | ||||
| 
 | ||||
|     try: | ||||
|         for i in ('MemTotal', 'MemFree', 'Buffers', 'Cached', 'SwapTotal', 'SwapFree'): | ||||
|             val = int(dict[i])*1024 | ||||
|             if (val < 0): | ||||
|                 return None | ||||
|             dict[i] = val | ||||
|     except: | ||||
|         return None | ||||
| 
 | ||||
|     if dict['SwapTotal'] < dict['SwapFree']: | ||||
|         return None | ||||
|     return dict | ||||
| 
 | ||||
| def is_suspicious(dom): | ||||
|     ret = False | ||||
|     if dom.meminfo['SwapTotal'] < dom.meminfo['SwapFree']: | ||||
|         ret = True | ||||
|     if dom.meminfo['MemTotal'] < dom.meminfo['MemFree'] + dom.meminfo['Cached'] + dom.meminfo['Buffers']: | ||||
|         ret = True | ||||
|     if ret: | ||||
|         print 'suspicious meminfo for domain', dom.id, 'mem actual', dom.memory_actual, dom.meminfo | ||||
|     return ret | ||||
| 
 | ||||
| def refresh_meminfo_for_domain(dom, xenstore_key): | ||||
|     meminfo = parse_meminfo(xenstore_key) | ||||
|     dom.meminfo = meminfo | ||||
|     if meminfo is None: | ||||
|         return | ||||
|     if is_suspicious(dom): | ||||
|         dom.meminfo = None | ||||
|         dom.mem_used = None | ||||
|     else: | ||||
|         dom.mem_used =  dom.meminfo['MemTotal'] - dom.meminfo['MemFree'] - dom.meminfo['Cached'] - dom.meminfo['Buffers'] + dom.meminfo['SwapTotal'] - dom.meminfo['SwapFree'] | ||||
|                          | ||||
| def prefmem(dom): | ||||
|     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 | ||||
| 
 | ||||
| def memneeded(dom): | ||||
| #do not change | ||||
| #in balance(), "distribute totalsum proportionally to mempref" relies on this exact formula | ||||
|     ret = prefmem(dom) - dom.memory_actual | ||||
|     return ret | ||||
|      | ||||
|      | ||||
| def balloon(memsize, domdict): | ||||
|     REQ_SAFETY_NET_FACTOR = 1.05 | ||||
|     donors = list() | ||||
|     request = list() | ||||
|     available = 0 | ||||
|     for i in domdict.keys(): | ||||
|         if domdict[i].meminfo is None: | ||||
|             continue | ||||
|         if domdict[i].no_progress: | ||||
|             continue | ||||
|         need = memneeded(domdict[i]) | ||||
|         if need < 0: | ||||
|             print 'balloon: dom' , i, 'has actual memory', domdict[i].memory_actual | ||||
|             donors.append((i,-need)) | ||||
|             available-=need    | ||||
|     print 'req=', memsize, 'avail=', available, 'donors', donors | ||||
|     if available<memsize: | ||||
|         return () | ||||
|     scale = 1.0*memsize/available | ||||
|     for donors_iter in donors: | ||||
|         id, mem = donors_iter | ||||
|         memborrowed = mem*scale*REQ_SAFETY_NET_FACTOR | ||||
|         print 'borrow' , memborrowed, 'from', id | ||||
|         memtarget = int(domdict[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): | ||||
|     donors_rq = list() | ||||
|     acceptors_rq = list() | ||||
|     for i in domdict.keys(): | ||||
|         if domdict[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 | ||||
| #prevent rounding errors | ||||
|         target = int(0.995*target_nonint) | ||||
|         if (target < domdict[i].memory_actual): | ||||
|             donors_rq.append((i, target)) | ||||
|         else: | ||||
|             acceptors_rq.append((i, target)) | ||||
| #    print 'balance(enough): xenfree=', xenfree, '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): | ||||
|     donors_rq = list() | ||||
|     acceptors_rq = list() | ||||
|     squeezed_mem = xenfree | ||||
|     for i in donors: | ||||
|         avail = -memneeded(domdict[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]))) | ||||
|     for i in acceptors: | ||||
|         scale = 1.0*prefmem(domdict[i])/total_mem_pref_acceptors | ||||
|         target_nonint = domdict[i].memory_actual + scale*squeezed_mem | ||||
|         acceptors_rq.append((i, int(target_nonint)))        | ||||
| #    print 'balance(low): xenfree=', xenfree, 'requests:', donors_rq + acceptors_rq | ||||
|     return donors_rq + acceptors_rq | ||||
|   | ||||
| def balance(xenfree, domdict): | ||||
|     total_memneeded = 0 | ||||
|     total_mem_pref = 0 | ||||
|     total_mem_pref_acceptors = 0 | ||||
|      | ||||
|     donors = list() | ||||
|     acceptors = list() | ||||
| #pass 1: compute the above "total" values | ||||
|     for i in domdict.keys(): | ||||
|         if domdict[i].meminfo is None: | ||||
|             continue | ||||
|         need = memneeded(domdict[i]) | ||||
| #        print 'domain' , i, 'act/pref', domdict[i].memory_actual, prefmem(domdict[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]) | ||||
| 
 | ||||
|     totalsum = xenfree - total_memneeded   | ||||
|     if totalsum > 0: | ||||
|         return balance_when_enough_memory(domdict, xenfree, total_mem_pref, totalsum) | ||||
|     else: | ||||
|         return balance_when_low_on_memory(domdict, xenfree, total_mem_pref_acceptors, donors, acceptors) | ||||
							
								
								
									
										21
									
								
								dom0/qmemman/qmemman_client.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										21
									
								
								dom0/qmemman/qmemman_client.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,21 @@ | ||||
| import socket | ||||
| import fcntl | ||||
| class QMemmanClient: | ||||
| 
 | ||||
|     def request_memory(self, amount): | ||||
|         self.sock = socket.socket(socket.AF_UNIX) | ||||
| 
 | ||||
|         flags = fcntl.fcntl(self.sock.fileno(), fcntl.F_GETFD) | ||||
|         flags |= fcntl.FD_CLOEXEC | ||||
|         fcntl.fcntl(self.sock.fileno(), fcntl.F_SETFD, flags) | ||||
| 
 | ||||
|         self.sock.connect("/var/run/qubes/qmemman.sock") | ||||
|         self.sock.send(str(amount)+"\n") | ||||
|         self.received = self.sock.recv(1024).strip() | ||||
|         if self.received == 'OK': | ||||
|             return True | ||||
|         else: | ||||
|             return False | ||||
| 
 | ||||
|     def close(self): | ||||
|         self.sock.close() | ||||
							
								
								
									
										113
									
								
								dom0/qmemman/qmemman_server.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										113
									
								
								dom0/qmemman/qmemman_server.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,113 @@ | ||||
| #!/usr/bin/python | ||||
| import SocketServer | ||||
| import thread | ||||
| import time | ||||
| import xen.lowlevel.xs | ||||
| import sys | ||||
| import os | ||||
| from qmemman import SystemState | ||||
| 
 | ||||
| system_state = SystemState() | ||||
| global_lock = thread.allocate_lock() | ||||
| 
 | ||||
| def only_in_first_list(l1, l2): | ||||
|     ret=[] | ||||
|     for i in l1: | ||||
|         if not i in l2: | ||||
|             ret.append(i) | ||||
|     return ret | ||||
| 
 | ||||
| def get_req_node(domain_id): | ||||
|     return '/local/domain/'+domain_id+'/memory/meminfo' | ||||
| 
 | ||||
|                      | ||||
| class WatchType: | ||||
|     def __init__(self, fn, param): | ||||
|         self.fn = fn | ||||
|         self.param = param | ||||
| 
 | ||||
| class XS_Watcher: | ||||
|     def __init__(self): | ||||
|         self.handle = xen.lowlevel.xs.xs() | ||||
|         self.handle.watch('/vm', WatchType(XS_Watcher.dom_list_change, None)) | ||||
|         self.watch_token_dict = {} | ||||
| 
 | ||||
|     def dom_list_change(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) | ||||
|             self.watch_token_dict[i] = watch | ||||
|             self.handle.watch(get_req_node(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]) | ||||
|             self.watch_token_dict.pop(i) | ||||
|             system_state.del_domain(i) | ||||
|         global_lock.release() | ||||
| 
 | ||||
|     def request(self, domain_id): | ||||
|         ret = self.handle.read('', get_req_node(domain_id)) | ||||
|         if ret == None or ret == '': | ||||
|             return | ||||
|         global_lock.acquire() | ||||
|         system_state.refresh_meminfo(domain_id, ret) | ||||
|         global_lock.release() | ||||
| 
 | ||||
|     def watch_loop(self): | ||||
| #        sys.stderr = file('/var/log/qubes/qfileexchgd.errors', 'a') | ||||
|         while True: | ||||
|             result = self.handle.read_watch() | ||||
|             token = result[1] | ||||
|             token.fn(self, token.param) | ||||
| 
 | ||||
| 
 | ||||
| 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. | ||||
|     """ | ||||
| 
 | ||||
|     def handle(self): | ||||
|         got_lock = False | ||||
|         # self.request is the TCP socket connected to the client | ||||
|         while True: | ||||
|             self.data = self.request.recv(1024).strip() | ||||
|             if len(self.data) == 0: | ||||
|                 print 'EOF' | ||||
|                 if got_lock: | ||||
|                     global_lock.release() | ||||
|                 return | ||||
|             if got_lock: | ||||
|                 print 'Second request over qmemman.sock ?' | ||||
|                 return | ||||
|             global_lock.acquire() | ||||
|             got_lock = True | ||||
|             if system_state.do_balloon(int(self.data)): | ||||
|                 resp = "OK\n" | ||||
|             else: | ||||
|                 resp = "FAIL\n" | ||||
|             self.request.send(resp) | ||||
| 
 | ||||
| 
 | ||||
| def start_server(): | ||||
|     SOCK_PATH='/var/run/qubes/qmemman.sock' | ||||
|     try: | ||||
|         os.unlink(SOCK_PATH) | ||||
|     except: | ||||
|         pass | ||||
|     os.umask(0) | ||||
|     server = SocketServer.UnixStreamServer(SOCK_PATH, QMemmanReqHandler) | ||||
|     os.umask(077) | ||||
|     server.serve_forever() | ||||
| 
 | ||||
| class QMemmanServer: | ||||
|     @staticmethod           | ||||
|     def main(): | ||||
|         thread.start_new_thread(start_server, tuple([])) | ||||
|         XS_Watcher().watch_loop() | ||||
							
								
								
									
										4
									
								
								dom0/qmemman/server.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										4
									
								
								dom0/qmemman/server.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,4 @@ | ||||
| #!/usr/bin/python | ||||
| from qubes.qmemman_server import QMemmanServer | ||||
| 
 | ||||
| QMemmanServer.main() | ||||
| @ -29,6 +29,7 @@ import xml.parsers.expat | ||||
| import fcntl | ||||
| import re | ||||
| import shutil | ||||
| from qmemman_client import QMemmanClient | ||||
| 
 | ||||
| # Do not use XenAPI or create/read any VM files | ||||
| # This is for testing only! | ||||
| @ -326,6 +327,18 @@ class QubesVm(object): | ||||
| 
 | ||||
|         return mem | ||||
| 
 | ||||
|     def get_mem_dynamic_max(self): | ||||
|         if dry_run: | ||||
|             return 666 | ||||
| 
 | ||||
|         try: | ||||
|             mem = int(xend_session.session.xenapi.VM.get_memory_dynamic_max(self.session_uuid)) | ||||
|         except XenAPI.Failure: | ||||
|             self.refresh_xend_session() | ||||
|             mem = int(xend_session.session.xenapi.VM.get_memory_dynamic_max(self.session_uuid)) | ||||
| 
 | ||||
|         return mem | ||||
| 
 | ||||
| 
 | ||||
|     def get_cpu_total_load(self): | ||||
|         if dry_run: | ||||
| @ -474,15 +487,11 @@ class QubesVm(object): | ||||
|         if verbose: | ||||
|             print "--> Loading the VM (type = {0})...".format(self.type) | ||||
| 
 | ||||
|         mem_required = self.get_mem_static_max() | ||||
|         dom0_mem = dom0_vm.get_mem() | ||||
|         dom0_mem_new = dom0_mem - mem_required + self.get_free_xen_memory() | ||||
|         if verbose: | ||||
|             print "--> AppVM required mem     : {0}".format(mem_required) | ||||
|             print "--> Dom0 mem after launch  : {0}".format(dom0_mem_new) | ||||
| 
 | ||||
|         if dom0_mem_new < dom0_min_memory: | ||||
|             raise MemoryError ("ERROR: starting this VM would cause Dom0 memory to go below {0}B".format(dom0_min_memory)) | ||||
|         mem_required = self.get_mem_dynamic_max() | ||||
|         qmemman_client = QMemmanClient() | ||||
|         if not qmemman_client.request_memory(mem_required): | ||||
|             qmemman_client.close() | ||||
|             raise MemoryError ("ERROR: insufficient memory to start this VM") | ||||
| 
 | ||||
|         try: | ||||
|             xend_session.session.xenapi.VM.start (self.session_uuid, True) # Starting a VM paused | ||||
| @ -490,6 +499,8 @@ class QubesVm(object): | ||||
|             self.refresh_xend_session() | ||||
|             xend_session.session.xenapi.VM.start (self.session_uuid, True) # Starting a VM paused | ||||
| 
 | ||||
|         qmemman_client.close() # let qmemman_daemon resume balancing | ||||
|          | ||||
|         xid = int (xend_session.session.xenapi.VM.get_domid (self.session_uuid)) | ||||
| 
 | ||||
|         if verbose: | ||||
|  | ||||
| @ -48,10 +48,16 @@ xenstore-read /local/domain/$ID/qubes_gateway | \ | ||||
| xm block-detach $1 /dev/xvdb | ||||
| MEM=$(xenstore-read /local/domain/$ID/device/qubes_used_mem) | ||||
| echo MEM=$MEM | ||||
| QMEMMAN_STOP=/var/run/qubes/do-not-membalance | ||||
| touch $QMEMMAN_STOP | ||||
| xm mem-set $1 $(($MEM/1000)) | ||||
| sleep 1 | ||||
| touch $2 | ||||
| if ! xm save $1 $2 ; then exit 1 ; fi | ||||
| if ! xm save $1 $2 ; then  | ||||
| 	rm -f $QMEMMAN_STOP | ||||
| 	exit 1 | ||||
| fi | ||||
| rm -f $QMEMMAN_STOP | ||||
| cd $VMDIR | ||||
| tar -Scvf saved_cows.tar root-cow.img swap-cow.img | ||||
| 
 | ||||
|  | ||||
| @ -359,6 +359,7 @@ void setup_xenstore(int netvm_id, int domid, int dvmid, char *name) | ||||
| 	snprintf(val, sizeof(val), "10.%d.255.254", netvm_id); | ||||
| 	write_xs_single(xs, domid, "qubes_secondary_dns", val); | ||||
| 	write_xs_single(xs, domid, "qubes_vm_type", "AppVM"); | ||||
| 	write_xs_single(xs, domid, "qubes_restore_complete", "True"); | ||||
| 	xs_daemon_close(xs); | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -52,6 +52,7 @@ fi | ||||
| 
 | ||||
| %build | ||||
| make clean all | ||||
| make -C ../common | ||||
| 
 | ||||
| %install | ||||
| 
 | ||||
| @ -65,6 +66,7 @@ cp qubes_timestamp qvm-copy-to-vm qvm-open-in-dvm $RPM_BUILD_ROOT/usr/bin | ||||
| mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes | ||||
| cp qubes_add_pendrive_script qubes_penctl qvm-copy-to-vm.kde $RPM_BUILD_ROOT/usr/lib/qubes | ||||
| ln -s /usr/bin/qvm-open-in-dvm $RPM_BUILD_ROOT/usr/lib/qubes/qvm-dvm-transfer  | ||||
| cp ../common/meminfo-writer $RPM_BUILD_ROOT/usr/lib/qubes | ||||
| mkdir -p $RPM_BUILD_ROOT/%{kde_service_dir} | ||||
| cp qvm-copy.desktop qvm-dvm.desktop $RPM_BUILD_ROOT/%{kde_service_dir} | ||||
| mkdir -p $RPM_BUILD_ROOT/etc/udev/rules.d | ||||
| @ -187,6 +189,7 @@ rm -rf $RPM_BUILD_ROOT | ||||
| /usr/lib/qubes/qvm-copy-to-vm.kde | ||||
| %attr(4755,root,root) /usr/bin/qvm-open-in-dvm | ||||
| /usr/lib/qubes/qvm-dvm-transfer | ||||
| /usr/lib/qubes/meminfo-writer | ||||
| %{kde_service_dir}/qvm-copy.desktop | ||||
| %{kde_service_dir}/qvm-dvm.desktop | ||||
| %attr(4755,root,root) /usr/lib/qubes/qubes_penctl | ||||
|  | ||||
| @ -44,9 +44,10 @@ Requires:	python, xen-runtime, pciutils, python-inotify, python-daemon, kernel-q | ||||
| The Qubes core files for installation on Dom0. | ||||
| 
 | ||||
| %build | ||||
| python -m compileall qvm-core | ||||
| python -O -m compileall qvm-core | ||||
| python -m compileall qvm-core qmemman | ||||
| python -O -m compileall qvm-core qmemman | ||||
| make -C restore | ||||
| make -C ../common | ||||
| 
 | ||||
| %install | ||||
| 
 | ||||
| @ -68,6 +69,8 @@ cp qvm-core/qubes.py $RPM_BUILD_ROOT%{python_sitearch}/qubes | ||||
| cp qvm-core/qubes.py[co] $RPM_BUILD_ROOT%{python_sitearch}/qubes | ||||
| cp qvm-core/__init__.py $RPM_BUILD_ROOT%{python_sitearch}/qubes | ||||
| cp qvm-core/__init__.py[co] $RPM_BUILD_ROOT%{python_sitearch}/qubes | ||||
| cp qmemman/qmemman*py $RPM_BUILD_ROOT%{python_sitearch}/qubes | ||||
| cp qmemman/qmemman*py[co] $RPM_BUILD_ROOT%{python_sitearch}/qubes | ||||
| 
 | ||||
| mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes | ||||
| cp aux-tools/patch_appvm_initramfs.sh $RPM_BUILD_ROOT/usr/lib/qubes | ||||
| @ -78,6 +81,8 @@ cp aux-tools/convert_dirtemplate2vm.sh $RPM_BUILD_ROOT/usr/lib/qubes | ||||
| cp aux-tools/create_apps_for_appvm.sh $RPM_BUILD_ROOT/usr/lib/qubes | ||||
| cp aux-tools/remove_appvm_appmenus.sh $RPM_BUILD_ROOT/usr/lib/qubes | ||||
| cp pendrive_swapper/qubes_pencmd $RPM_BUILD_ROOT/usr/lib/qubes | ||||
| cp qmemman/server.py $RPM_BUILD_ROOT/usr/lib/qubes/qmemman_daemon.py | ||||
| cp ../common/meminfo-writer $RPM_BUILD_ROOT/usr/lib/qubes/ | ||||
| 
 | ||||
| cp restore/xenstore-watch restore/qvm-create-default-dvm $RPM_BUILD_ROOT/usr/bin | ||||
| cp restore/qubes_restore restore/xenfreepages $RPM_BUILD_ROOT/usr/lib/qubes | ||||
| @ -199,6 +204,7 @@ fi | ||||
| %{python_sitearch}/qubes/__init__.py | ||||
| %{python_sitearch}/qubes/__init__.pyc | ||||
| %{python_sitearch}/qubes/__init__.pyo | ||||
| %{python_sitearch}/qubes/qmemman*.py* | ||||
| /usr/lib/qubes/patch_appvm_initramfs.sh | ||||
| /usr/lib/qubes/unbind_pci_device.sh | ||||
| /usr/lib/qubes/unbind_all_network_devices | ||||
| @ -207,6 +213,8 @@ fi | ||||
| /usr/lib/qubes/create_apps_for_appvm.sh | ||||
| /usr/lib/qubes/remove_appvm_appmenus.sh | ||||
| /usr/lib/qubes/qubes_pencmd | ||||
| /usr/lib/qubes/qmemman_daemon.py* | ||||
| /usr/lib/qubes/meminfo-writer | ||||
| %attr(770,root,qubes) %dir /var/lib/qubes | ||||
| %attr(770,root,qubes) %dir /var/lib/qubes/vm-templates | ||||
| %attr(770,root,qubes) %dir /var/lib/qubes/appvms | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Joanna Rutkowska
						Joanna Rutkowska