summaryrefslogtreecommitdiffstats
path: root/func/minion/modules/process.py
blob: 92c6d6adb9ecc777b3afbffe191ba6a40a30f25b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
## -*- coding: utf-8 -*-
##
## Process lister (control TBA)
##
## Copyright 2007, Red Hat, Inc
## Michael DeHaan <mdehaan@redhat.com>
##
## This software may be freely redistributed under the terms of the GNU
## general public license.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
##

# other modules
import sub_process
import codes

# our modules
from modules import func_module

# =================================

class ProcessModule(func_module.FuncModule):
    def __init__(self):
        self.methods = {
            "info"    : self.info,
            "kill"    : self.kill,
            "pkill"   : self.pkill,
            "mem"     : self.mem
        }
        func_module.FuncModule.__init__(self)

    def info(self,flags="-auxh"):
        """
        Returns a struct of hardware information.  By default, this pulls down
        all of the devices.  If you don't care about them, set with_devices to
        False.
        """

        flags.replace(";","") # prevent stupidity


        #FIXME: we need to swallow stdout/stderr as well, right now it spews to the console
        cmd = sub_process.Popen(["/bin/ps", flags] ,executable="/bin/ps", stdout=sub_process.PIPE,shell=False)
        data = cmd.communicate()[0]

        results = []

        for x in data.split("\n"):
            tokens = x.split()
            results.append(tokens)

        return results

    def mem(self):
        """
        Returns a list of per-program memory usage.

             Private  +  Shared   =  RAM used     Program

           [["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"]]

        Taken from the ps_mem.py script written by Pádraig Brady.
        http://www.pixelbeat.org/scripts/ps_mem.py
        """
        import os
        our_pid=os.getpid()
        results = []
        have_smaps=0
        have_pss=0

        def kernel_ver():
            """ (major,minor,release) """
            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()

        def getMemStats(pid):
            """ return Rss,Pss,Shared (note Private = Rss-Shared) """
            Shared_lines=[]
            Pss_lines=[]
            pagesize=os.sysconf("SC_PAGE_SIZE")/1024 #KiB
            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
        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

        for cmd in sort_list:
            results.append([
                "%sB" % human(cmd[1]-shareds[cmd[0]]),
                "%sB" % human(shareds[cmd[0]]),
                "%sB" % human(cmd[1]),
                "%s" % cmd_with_count(cmd[0], count[cmd[0]])
            ])
        if have_pss:
            results.append(["", "", "", "%sB" % human(total)])

        return results

    def kill(self,pid,signal="TERM"):
        if pid == "0":
            raise codes.FuncException("Killing pid group 0 not permitted")
        if signal == "":
            # this is default /bin/kill behaviour, it claims, but enfore it anyway
            signal = "-TERM"
        if signal[0] != "-":
            signal = "-%s" % signal
        rc = sub_process.call(["/bin/kill",signal, pid], executable="/bin/kill", shell=False)
        print rc
        return rc

    def pkill(self,name,level=""):
        # example killall("thunderbird","-9")
        rc = sub_process.call(["/usr/bin/pkill", name, level], executable="/usr/bin/pkill", shell=False)
        return rc

methods = ProcessModule()
register_rpc = methods.register_rpc