Browse Source

qmemman: support simple VM meminfo format

Instead of excerpt from /proc/meminfo, use just one integer. This make
qmemman handling much easier and ease implementation for non-Linux OSes
(where /proc/meminfo doesn't exist).

For now keep also support for old format.

Fixes QubesOS/qubes-issues#1312
Marek Marczykowski-Górecki 7 years ago
parent
commit
a2d9b15413
3 changed files with 55 additions and 41 deletions
  1. 2 3
      qubes/qmemman/__init__.py
  2. 50 38
      qubes/qmemman/algo.py
  3. 3 0
      qubes/vm/qubesvm.py

+ 2 - 3
qubes/qmemman/__init__.py

@@ -41,7 +41,6 @@ slow_memset_react_msg="VM didn't give back all requested memory"
 
 class DomainState:
     def __init__(self, id):
-        self.meminfo = None		#dictionary of memory info read from client
         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)
@@ -286,7 +285,7 @@ class SystemState(object):
 
     def print_stats(self, xenfree, memset_reqs):
         for i in self.domdict.keys():
-            if self.domdict[i].meminfo is not None:
+            if self.domdict[i].mem_used is not None:
                 self.log.info('stat: dom {!r} act={} pref={}'.format(i,
                     self.domdict[i].memory_actual,
                     qubes.qmemman.algo.prefmem(self.domdict[i])))
@@ -375,6 +374,6 @@ class SystemState(object):
             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, ' 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()

+ 50 - 38
qubes/qmemman/algo.py

@@ -37,64 +37,76 @@ 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 parse_meminfo(untrusted_meminfo):
+def sanitize_and_parse_meminfo(untrusted_meminfo):
+    if not untrusted_meminfo:
+        return None
+
+    # new syntax - just one int
+    try:
+        if int(untrusted_meminfo) >= 0:
+            return int(untrusted_meminfo)
+    except ValueError:
+        pass
+
+    # not new syntax - try the old one
     untrusted_dict = {}
-#split meminfo contents into lines
-    untrusted_lines = string.split(untrusted_meminfo,"\n")
+    # split meminfo contents into lines
+    untrusted_lines = string.split(untrusted_meminfo, "\n")
     for untrusted_lines_iterator in untrusted_lines:
-#split a single meminfo line into words
+        # split a single meminfo line into words
         untrusted_words = string.split(untrusted_lines_iterator)
         if len(untrusted_words) >= 2:
-            untrusted_dict[string.rstrip(untrusted_words[0], ":")] = untrusted_words[1]
+            untrusted_dict[string.rstrip(untrusted_words[0], ":")] = \
+                untrusted_words[1]
+
+    # sanitize start
+    if not is_meminfo_suspicious(untrusted_meminfo):
+        # sanitize end
+        meminfo = untrusted_meminfo
+        return meminfo['MemTotal'] - \
+            meminfo['MemFree'] - meminfo['Cached'] - meminfo['Buffers'] + \
+            meminfo['SwapTotal'] - meminfo['SwapFree']
+
+
+    return None
 
-    return untrusted_dict
 
-def is_meminfo_suspicious(domain, untrusted_meminfo):
+def is_meminfo_suspicious(untrusted_meminfo):
     log.debug('is_meminfo_suspicious('
-        'domain={!r}, untrusted_meminfo={!r})'.format(
-            domain, untrusted_meminfo))
+        'untrusted_meminfo={!r})'.format(untrusted_meminfo))
     ret = False
 
-#check whether the required keys exist and are not negative
+    # check whether the required keys exist and are not negative
     try:
-        for i in ('MemTotal', 'MemFree', 'Buffers', 'Cached', 'SwapTotal', 'SwapFree'):
+        for i in ('MemTotal', 'MemFree', 'Buffers', 'Cached',
+                'SwapTotal', 'SwapFree'):
             val = int(untrusted_meminfo[i])*1024
-            if (val < 0):
+            if val < 0:
                 ret = True
             untrusted_meminfo[i] = val
     except:
         ret = True
 
-    if not ret and untrusted_meminfo['SwapTotal'] < untrusted_meminfo['SwapFree']:
+    if untrusted_meminfo['SwapTotal'] < untrusted_meminfo['SwapFree']:
         ret = True
-    if not ret and untrusted_meminfo['MemTotal'] < untrusted_meminfo['MemFree'] + untrusted_meminfo['Cached'] + untrusted_meminfo['Buffers']:
+    if untrusted_meminfo['MemTotal'] < \
+            untrusted_meminfo['MemFree'] + \
+            untrusted_meminfo['Cached'] + untrusted_meminfo['Buffers']:
         ret = True
-#we could also impose some limits on all the above values
-#but it has little purpose - all the domain can gain by passing e.g.
-#very large SwapTotal is that it will be assigned all free Xen memory
-#it can be achieved with legal values, too, and it will not allow to
-#starve existing domains, by design
+    # we could also impose some limits on all the above values
+    # but it has little purpose - all the domain can gain by passing e.g.
+    # very large SwapTotal is that it will be assigned all free Xen memory
+    # it can be achieved with legal values, too, and it will not allow to
+    # starve existing domains, by design
     if ret:
-        log.warning('suspicious meminfo for domain {!r}'
-            ' memory_actual={!r} untrusted_meminfo={!r}'.format(domain.id,
-                domain.memory_actual, untrusted_meminfo))
+        log.warning('suspicious meminfo untrusted_meminfo={!r}'.format(untrusted_meminfo))
     return ret
 
-#called when a domain updates its 'meminfo' 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:
-        domain.meminfo = None
-        return
-#sanitize start
-    if is_meminfo_suspicious(domain, untrusted_meminfo):
-#sanitize end
-        domain.meminfo = None
-        domain.mem_used = None
-    else:
-#sanitized, can assign
-        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']
+    domain.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
@@ -158,7 +170,7 @@ def balance_when_enough_memory(domain_dictionary,
     left_memory = 0
     acceptors_count = 0
     for i in domain_dictionary.keys():
-        if domain_dictionary[i].meminfo is None:
+        if domain_dictionary[i].mem_used is None:
             continue
         if domain_dictionary[i].no_progress:
             continue
@@ -264,7 +276,7 @@ def balance(xen_free_memory, domain_dictionary):
     acceptors = list()  # domains that require more memory
 #pass 1: compute the above "total" values
     for i in domain_dictionary.keys():
-        if domain_dictionary[i].meminfo is None:
+        if domain_dictionary[i].mem_used is None:
             continue
         if domain_dictionary[i].no_progress:
             continue

+ 3 - 0
qubes/vm/qubesvm.py

@@ -1537,6 +1537,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
         domain = qubes.qmemman.DomainState(self.xid)
         qubes.qmemman.algo.refresh_meminfo_for_domain(
             domain, untrusted_meminfo_key)
+        if domain.mem_used is None:
+            # apparently invalid xenstore content
+            return 0
         domain.memory_maximum = self.get_mem_static_max() * 1024
 
         return qubes.qmemman.algo.prefmem(domain) / 1024