 modules/process.py |  179 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 175 insertions(+), 4 deletions(-)

diff --git a/modules/process.py b/modules/process.py
index 78e5aea..0cf4b36 100755
--- a/modules/process.py
+++ b/modules/process.py
@@ -28,7 +28,8 @@ class ProcessModule(func_module.FuncModule):
         self.methods = {
             "info"    : self.info,
             "kill"    : self.kill,
-            "pkill"   : self.pkill
+            "pkill"   : self.pkill,
+            "mem"     : self.mem
         }
         func_module.FuncModule.__init__(self)
 
@@ -54,6 +55,179 @@ class ProcessModule(func_module.FuncModule):
 
         return results
 
+    def mem(self):
+        """
+        Returns a list of per-program memory usage.
+
+           39.4 MiB +  10.3 MiB =  49.8 MiB       Xorg
+           42.2 MiB +  12.4 MiB =  54.6 MiB       nautilus
+           52.3 MiB +  10.8 MiB =  63.0 MiB       liferea-bin
+          171.6 MiB +  11.9 MiB = 183.5 MiB       firefox-bin
+
+           Private  +   Shared  =  RAM used       Program
+
+        Taken from the ps_mem.py script written by P@draigBrady.com
+        http://www.pixelbeat.org/scripts/ps_mem.py
+        """
+        import os
+        PAGESIZE=os.sysconf("SC_PAGE_SIZE")/1024 #KiB
+        our_pid=os.getpid()
+        results = []
+
+        #(major,minor,release)
+        def kernel_ver():
+            kv=open("/proc/sys/kernel/osrelease").readline().split(".")[:3]
+            for char in "-_":
+                kv[2]=kv[2].split(char)[0]
+            return (int(kv[0]), int(kv[1]), int(kv[2]))
+
+        kv=kernel_ver()
+
+        have_smaps=0
+        have_pss=0
+
+        #return Rss,Pss,Shared
+        #note Private = Rss-Shared
+        def getMemStats(pid):
+            Shared_lines=[]
+            Pss_lines=[]
+            Rss=int(open("/proc/"+str(pid)+"/statm").readline().split()[1])*PAGESIZE
+            if os.path.exists("/proc/"+str(pid)+"/smaps"): #stat
+                global have_smaps
+                have_smaps=1
+                for line in open("/proc/"+str(pid)+"/smaps").readlines(): #open
+                    #Note in smaps Shared+Private = Rss above
+                    #The Rss in smaps includes video card mem etc.
+                    if line.startswith("Shared"):
+                        Shared_lines.append(line)
+                    elif line.startswith("Pss"):
+                        global have_pss
+                        have_pss=1
+                        Pss_lines.append(line)
+                Shared=sum([int(line.split()[1]) for line in Shared_lines])
+                Pss=sum([int(line.split()[1]) for line in Pss_lines])
+            elif (2,6,1) <= kv <= (2,6,9):
+                Pss=0
+                Shared=0 #lots of overestimation, but what can we do?
+            else:
+                Pss=0
+                Shared=int(open("/proc/"+str(pid)+"/statm").readline().split()[2])*PAGESIZE
+            return (Rss, Pss, Shared)
+
+        cmds={}
+        shareds={}
+        count={}
+        for pid in os.listdir("/proc/"):
+            try:
+                pid = int(pid) #note Thread IDs not listed in /proc/
+                if pid ==our_pid: continue
+            except:
+                continue
+            cmd = file("/proc/%d/status" % pid).readline()[6:-1]
+            try:
+                exe = os.path.basename(os.path.realpath("/proc/%d/exe" % pid))
+                if exe.startswith(cmd):
+                    cmd=exe #show non truncated version
+                    #Note because we show the non truncated name
+                    #one can have separated programs as follows:
+                    #584.0 KiB +   1.0 MiB =   1.6 MiB    mozilla-thunder (exe -> bash)
+                    # 56.0 MiB +  22.2 MiB =  78.2 MiB    mozilla-thunderbird-bin
+            except:
+                #permission denied or
+                #kernel threads don't have exe links or
+                #process gone
+                continue
+            try:
+                rss, pss, shared = getMemStats(pid)
+                private = rss-shared
+                #Note shared is always a subset of rss (trs is not always)
+            except:
+                continue #process gone
+            if shareds.get(cmd):
+                if pss: #add shared portion of PSS together
+                    shareds[cmd]+=pss-private
+                elif shareds[cmd] < shared: #just take largest shared val
+                    shareds[cmd]=shared
+            else:
+                if pss:
+                    shareds[cmd]=pss-private
+                else:
+                    shareds[cmd]=shared
+            cmds[cmd]=cmds.setdefault(cmd,0)+private
+            if count.has_key(cmd):
+               count[cmd] += 1
+            else:
+               count[cmd] = 1
+
+        #Add max shared mem for each program
+        total=0
+        for cmd in cmds.keys():
+            cmds[cmd]=cmds[cmd]+shareds[cmd]
+            total+=cmds[cmd] #valid if PSS available
+
+        sort_list = cmds.items()
+        sort_list.sort(lambda x,y:cmp(x[1],y[1]))
+        sort_list=filter(lambda x:x[1],sort_list) #get rid of zero sized processes
+
+        #The following matches "du -h" output
+        #see also human.py
+        def human(num, power="Ki"):
+            powers=["Ki","Mi","Gi","Ti"]
+            while num >= 1000: #4 digits
+                num /= 1024.0
+                power=powers[powers.index(power)+1]
+            return "%.1f %s" % (num,power)
+
+        def cmd_with_count(cmd, count):
+            if count>1:
+               return "%s (%u)" % (cmd, count)
+            else:
+               return cmd
+
+        results.append(" Private  +   Shared  =  RAM used       Program")
+        for cmd in sort_list:
+            results.append("%8sB + %8sB = %8sB       %s" % (human(cmd[1]-shareds[cmd[0]]), human(shareds[cmd[0]]), human(cmd[1]), cmd_with_count(cmd[0], count[cmd[0]])))
+        if have_pss:
+            results.append("-" * 33)
+            results.append("                        %sB" % human(total))
+            results.append("=" * 33)
+        results.append(" Private  +   Shared  =  RAM used       Program")
+
+        #Warn of possible inaccuracies
+        #2 = accurate & can total
+        #1 = accurate only considering each process in isolation
+        #0 = some shared mem not reported
+        #-1= all shared mem not reported
+        def shared_val_accuracy():
+            """http://wiki.apache.org/spamassassin/TopSharedMemoryBug"""
+            if kv[:2] == (2,4):
+                if open("/proc/meminfo").read().find("Inact_") == -1:
+                    return 1
+                return 0
+            elif kv[:2] == (2,6):
+                if os.path.exists("/proc/"+str(os.getpid())+"/smaps"):
+                    if open("/proc/"+str(os.getpid())+"/smaps").read().find("Pss:") != -1:
+                        return 2
+                    else:
+                        return 1
+                if (2,6,1) <= kv <= (2,6,9):
+                    return -1
+                return 0
+            else:
+                return 1
+
+        vm_accuracy = shared_val_accuracy()
+        if vm_accuracy == -1:
+            results.append("Warning: Shared memory is not reported by this system.")
+            results.append("Values reported will be too large, and totals are not reported")
+        elif vm_accuracy == 0:
+            results.append("Warning: Shared memory is not reported accurately by this system.")
+            results.append("Values reported could be too large, and totals are not reported")
+        elif vm_accuracy == 1:
+            results.append("Warning: Shared memory is slightly over-estimated by this system")
+            results.append("for each program, so totals are not reported.")
+
+        return results
 
     def kill(self,pid,signal="TERM"):
         if pid == "0":
@@ -74,6 +248,3 @@ class ProcessModule(func_module.FuncModule):
 
 methods = ProcessModule()
 register_rpc = methods.register_rpc
-
-
-
-- 
1.5.2.4

