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