848e8475cb0aa2940296227568e9b3e191ec9086
[certmaster.git] / certmaster / minion / modules / process.py
1 ## -*- coding: utf-8 -*-
2 ##
3 ## Process lister (control TBA)
4 ##
5 ## Copyright 2007, Red Hat, Inc
6 ## Michael DeHaan <mdehaan@redhat.com>
7 ##
8 ## This software may be freely redistributed under the terms of the GNU
9 ## general public license.
10 ##
11 ## You should have received a copy of the GNU General Public License
12 ## along with this program; if not, write to the Free Software
13 ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
14 ##
15
16 # other modules
17 import sub_process
18 import codes
19
20 # our modules
21 import func_module
22
23 # =================================
24
25 class ProcessModule(func_module.FuncModule):
26
27 version = "0.0.1"
28 api_version = "0.0.1"
29 description = "Process related reporting and control."
30
31 def info(self, flags="-auxh"):
32 """
33 Returns a struct of hardware information. By default, this pulls down
34 all of the devices. If you don't care about them, set with_devices to
35 False.
36 """
37
38 flags.replace(";", "") # prevent stupidity
39
40 cmd = sub_process.Popen(["/bin/ps", flags], executable="/bin/ps",
41 stdout=sub_process.PIPE,
42 stderr=sub_process.PIPE,
43 shell=False)
44
45 data, error = cmd.communicate()
46
47 # We can get warnings for odd formatting. warnings != errors.
48 if error and error[:7] != "Warning":
49 raise codes.FuncException(error.split('\n')[0])
50
51 results = []
52 for x in data.split("\n"):
53 tokens = x.split()
54 results.append(tokens)
55
56 return results
57
58 def mem(self):
59 """
60 Returns a list of per-program memory usage.
61
62 Private + Shared = RAM used Program
63
64 [["39.4 MiB", "10.3 MiB", "49.8 MiB", "Xorg"],
65 ["42.2 MiB", "12.4 MiB", "54.6 MiB", "nautilus"],
66 ["52.3 MiB", "10.8 MiB", "63.0 MiB", "liferea-bin"]
67 ["171.6 MiB", "11.9 MiB", "183.5 MiB", "firefox-bin"]]
68
69 Taken from the ps_mem.py script written by Pádraig Brady.
70 http://www.pixelbeat.org/scripts/ps_mem.py
71 """
72 import os
73 our_pid=os.getpid()
74 results = []
75 have_smaps=0
76 have_pss=0
77
78 def kernel_ver():
79 """ (major,minor,release) """
80 kv=open("/proc/sys/kernel/osrelease").readline().split(".")[:3]
81 for char in "-_":
82 kv[2]=kv[2].split(char)[0]
83 return (int(kv[0]), int(kv[1]), int(kv[2]))
84
85 kv=kernel_ver()
86
87 def getMemStats(pid):
88 """ return Rss,Pss,Shared (note Private = Rss-Shared) """
89 Shared_lines=[]
90 Pss_lines=[]
91 pagesize=os.sysconf("SC_PAGE_SIZE")/1024 #KiB
92 Rss=int(open("/proc/"+str(pid)+"/statm").readline().split()[1])*pagesize
93 if os.path.exists("/proc/"+str(pid)+"/smaps"): #stat
94 global have_smaps
95 have_smaps=1
96 for line in open("/proc/"+str(pid)+"/smaps").readlines(): #open
97 #Note in smaps Shared+Private = Rss above
98 #The Rss in smaps includes video card mem etc.
99 if line.startswith("Shared"):
100 Shared_lines.append(line)
101 elif line.startswith("Pss"):
102 global have_pss
103 have_pss=1
104 Pss_lines.append(line)
105 Shared=sum([int(line.split()[1]) for line in Shared_lines])
106 Pss=sum([int(line.split()[1]) for line in Pss_lines])
107 elif (2,6,1) <= kv <= (2,6,9):
108 Pss=0
109 Shared=0 #lots of overestimation, but what can we do?
110 else:
111 Pss=0
112 Shared=int(open("/proc/"+str(pid)+"/statm").readline().split()[2])*pagesize
113 return (Rss, Pss, Shared)
114
115 cmds={}
116 shareds={}
117 count={}
118 for pid in os.listdir("/proc/"):
119 try:
120 pid = int(pid) #note Thread IDs not listed in /proc/
121 if pid ==our_pid: continue
122 except:
123 continue
124 cmd = file("/proc/%d/status" % pid).readline()[6:-1]
125 try:
126 exe = os.path.basename(os.path.realpath("/proc/%d/exe" % pid))
127 if exe.startswith(cmd):
128 cmd=exe #show non truncated version
129 #Note because we show the non truncated name
130 #one can have separated programs as follows:
131 #584.0 KiB + 1.0 MiB = 1.6 MiB mozilla-thunder (exe -> bash)
132 #56.0 MiB + 22.2 MiB = 78.2 MiB mozilla-thunderbird-bin
133 except:
134 #permission denied or
135 #kernel threads don't have exe links or
136 #process gone
137 continue
138 try:
139 rss, pss, shared = getMemStats(pid)
140 private = rss-shared
141 #Note shared is always a subset of rss (trs is not always)
142 except:
143 continue #process gone
144 if shareds.get(cmd):
145 if pss: #add shared portion of PSS together
146 shareds[cmd]+=pss-private
147 elif shareds[cmd] < shared: #just take largest shared val
148 shareds[cmd]=shared
149 else:
150 if pss:
151 shareds[cmd]=pss-private
152 else:
153 shareds[cmd]=shared
154 cmds[cmd]=cmds.setdefault(cmd,0)+private
155 if count.has_key(cmd):
156 count[cmd] += 1
157 else:
158 count[cmd] = 1
159
160 #Add max shared mem for each program
161 total=0
162 for cmd in cmds.keys():
163 cmds[cmd]=cmds[cmd]+shareds[cmd]
164 total+=cmds[cmd] #valid if PSS available
165
166 sort_list = cmds.items()
167 sort_list.sort(lambda x,y:cmp(x[1],y[1]))
168 sort_list=filter(lambda x:x[1],sort_list) #get rid of zero sized processes
169
170 #The following matches "du -h" output
171 def human(num, power="Ki"):
172 powers=["Ki","Mi","Gi","Ti"]
173 while num >= 1000: #4 digits
174 num /= 1024.0
175 power=powers[powers.index(power)+1]
176 return "%.1f %s" % (num,power)
177
178 def cmd_with_count(cmd, count):
179 if count>1:
180 return "%s (%u)" % (cmd, count)
181 else:
182 return cmd
183
184 for cmd in sort_list:
185 results.append([
186 "%sB" % human(cmd[1]-shareds[cmd[0]]),
187 "%sB" % human(shareds[cmd[0]]),
188 "%sB" % human(cmd[1]),
189 "%s" % cmd_with_count(cmd[0], count[cmd[0]])
190 ])
191 if have_pss:
192 results.append(["", "", "", "%sB" % human(total)])
193
194 return results
195
196 memory = mem
197
198 def kill(self,pid,signal="TERM"):
199 if pid == "0":
200 raise codes.FuncException("Killing pid group 0 not permitted")
201 if signal == "":
202 # this is default /bin/kill behaviour,
203 # it claims, but enfore it anyway
204 signal = "-TERM"
205 if signal[0] != "-":
206 signal = "-%s" % signal
207 rc = sub_process.call(["/bin/kill",signal, pid],
208 executable="/bin/kill", shell=False)
209 print rc
210 return rc
211
212 def pkill(self,name,level=""):
213 # example killall("thunderbird","-9")
214 rc = sub_process.call(["/usr/bin/pkill", name, level],
215 executable="/usr/bin/pkill", shell=False)
216 return rc