[PATCH] per-program memory usage

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I find myself using the ps_mem.py[0] script on almost every machine I have
access to, so I hacked 'mem' support into the process module.  This is a proof
of concept, as I'm not entirely sure that is the optimal place for it, nor do I
think the code couldn't use a little polish.  I literally copied and pasted
into a mem() method.  If we want to rely on an external code such as ps_mem.py,
we could even think about pulling it in from the Makefile, and calling it from
a module using sub_process.

Attached is a patch that adds ProcessModule.mem(), which lets you do the
do the following to your minions:

    [lmacken@crow ~]$ sudo func tomservo process mem
    on https://tomservo:51234 running process mem ()
    [' Private  +   Shared  =  RAM used       Program',
     '  4.0 MiB +   3.1 MiB =   7.1 MiB       openbox',
     '  2.2 MiB +   5.2 MiB =   7.4 MiB       clock-applet',
     '  2.8 MiB +   8.6 MiB =  11.4 MiB       wnck-applet',
     '  1.7 MiB +   9.8 MiB =  11.5 MiB       nm-applet',
     '  2.6 MiB +   9.4 MiB =  11.9 MiB       spamd (3)',
     '  3.7 MiB +   9.5 MiB =  13.2 MiB       gnome-panel',
     ' 14.8 MiB +   4.3 MiB =  19.2 MiB       Xorg',
     ' 11.0 MiB +   9.6 MiB =  20.6 MiB       nautilus',
     ' 10.5 MiB +  10.3 MiB =  20.8 MiB       gnome-terminal',
     '128.3 MiB +  11.3 MiB = 139.5 MiB       firefox-bin',
     ' Private  +   Shared  =  RAM used       Program']

Comments/suggestions?

luke

[0]: http://www.pixelbeat.org/scripts/ps_mem.py
 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@xxxxxxxxxxxxxx
+        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


[Index of Archives]     [Fedora Users]     [Linux Networking]     [Fedora Legacy List]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]

  Powered by Linux