llvm.org GIT mirror llvm / 7e3089d
Add a 'zkill' script, which is more-or-less a fancy (although not necessarily very robust) version of killall. Because I like making shiny new wheels out of spare parts. For use by buildbots when people insist on making cc1 infinite loop. :) git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@86484 91177308-0d34-0410-b5e6-96231b3b80d8 Daniel Dunbar 11 years ago
1 changed file(s) with 276 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 #!/usr/bin/env python
1
2 import os
3 import re
4 import sys
5
6 def _write_message(kind, message):
7 import inspect, os, sys
8
9 # Get the file/line where this message was generated.
10 f = inspect.currentframe()
11 # Step out of _write_message, and then out of wrapper.
12 f = f.f_back.f_back
13 file,line,_,_,_ = inspect.getframeinfo(f)
14 location = '%s:%d' % (os.path.basename(file), line)
15
16 print >>sys.stderr, '%s: %s: %s' % (location, kind, message)
17
18 note = lambda message: _write_message('note', message)
19 warning = lambda message: _write_message('warning', message)
20 error = lambda message: (_write_message('error', message), sys.exit(1))
21
22 def re_full_match(pattern, str):
23 m = re.match(pattern, str)
24 if m and m.end() != len(str):
25 m = None
26 return m
27
28 def parse_time(value):
29 minutes,value = value.split(':',1)
30 if '.' in value:
31 seconds,fseconds = value.split('.',1)
32 else:
33 seconds = value
34 return int(minutes) * 60 + int(seconds) + float('.'+fseconds)
35
36 def extractExecutable(command):
37 """extractExecutable - Given a string representing a command line, attempt
38 to extract the executable path, even if it includes spaces."""
39
40 # Split into potential arguments.
41 args = command.split(' ')
42
43 # Scanning from the beginning, try to see if the first N args, when joined,
44 # exist. If so that's probably the executable.
45 for i in range(1,len(args)):
46 cmd = ' '.join(args[:i])
47 if os.path.exists(cmd):
48 return cmd
49
50 # Otherwise give up and return the first "argument".
51 return args[0]
52
53 class Struct:
54 def __init__(self, **kwargs):
55 self.fields = kwargs.keys()
56 self.__dict__.update(kwargs)
57
58 def __repr__(self):
59 return 'Struct(%s)' % ', '.join(['%s=%r' % (k,getattr(self,k))
60 for k in self.fields])
61
62 kExpectedPSFields = [('PID', int, 'pid'),
63 ('USER', str, 'user'),
64 ('COMMAND', str, 'command'),
65 ('%CPU', float, 'cpu_percent'),
66 ('TIME', parse_time, 'cpu_time'),
67 ('VSZ', int, 'vmem_size'),
68 ('RSS', int, 'rss')]
69 def getProcessTable():
70 import subprocess
71 p = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE,
72 stderr=subprocess.PIPE)
73 out,err = p.communicate()
74 res = p.wait()
75 if p.wait():
76 error('unable to get process table')
77 elif err.strip():
78 error('unable to get process table: %s' % err)
79
80 lns = out.split('\n')
81 it = iter(lns)
82 header = it.next().split()
83 numRows = len(header)
84
85 # Make sure we have the expected fields.
86 indexes = []
87 for field in kExpectedPSFields:
88 try:
89 indexes.append(header.index(field[0]))
90 except:
91 if opts.debug:
92 raise
93 error('unable to get process table, no %r field.' % field[0])
94
95 table = []
96 for i,ln in enumerate(it):
97 if not ln.strip():
98 continue
99
100 fields = ln.split(None, numRows - 1)
101 if len(fields) != numRows:
102 warning('unable to process row: %r' % ln)
103 continue
104
105 record = {}
106 for field,idx in zip(kExpectedPSFields, indexes):
107 value = fields[idx]
108 try:
109 record[field[2]] = field[1](value)
110 except:
111 if opts.debug:
112 raise
113 warning('unable to process %r in row: %r' % (field[0], ln))
114 break
115 else:
116 # Add our best guess at the executable.
117 record['executable'] = extractExecutable(record['command'])
118 table.append(Struct(**record))
119
120 return table
121
122 def getSignalValue(name):
123 import signal
124 if name.startswith('SIG'):
125 value = getattr(signal, name)
126 if value and isinstance(value, int):
127 return value
128 error('unknown signal: %r' % name)
129
130 import signal
131 kSignals = {}
132 for name in dir(signal):
133 if name.startswith('SIG') and name == name.upper() and name.isalpha():
134 kSignals[name[3:]] = getattr(signal, name)
135
136 def main():
137 global opts
138 from optparse import OptionParser, OptionGroup
139 parser = OptionParser("usage: %prog [options] {pid}*")
140
141 # FIXME: Add -NNN and -SIGNAME options.
142
143 parser.add_option("-s", "", dest="signalName",
144 help="Name of the signal to use (default=%default)",
145 action="store", default='INT',
146 choices=kSignals.keys())
147 parser.add_option("-l", "", dest="listSignals",
148 help="List known signal names",
149 action="store_true", default=False)
150
151 parser.add_option("-n", "--dry-run", dest="dryRun",
152 help="Only print the actions that would be taken",
153 action="store_true", default=False)
154 parser.add_option("-v", "--verbose", dest="verbose",
155 help="Print more verbose output",
156 action="store_true", default=False)
157 parser.add_option("", "--debug", dest="debug",
158 help="Enable debugging output",
159 action="store_true", default=False)
160 parser.add_option("", "--force", dest="force",
161 help="Perform the specified commands, even if it seems like a bad idea",
162 action="store_true", default=False)
163
164 inf = float('inf')
165 group = OptionGroup(parser, "Process Filters")
166 group.add_option("", "--name", dest="execName", metavar="REGEX",
167 help="Kill processes whose name matches the given regexp",
168 action="store", default=None)
169 group.add_option("", "--exec", dest="execPath", metavar="REGEX",
170 help="Kill processes whose executable matches the given regexp",
171 action="store", default=None)
172 group.add_option("", "--user", dest="userName", metavar="REGEX",
173 help="Kill processes whose user matches the given regexp",
174 action="store", default=None)
175 group.add_option("", "--min-cpu", dest="minCPU", metavar="PCT",
176 help="Kill processes with CPU usage >= PCT",
177 action="store", type=float, default=None)
178 group.add_option("", "--max-cpu", dest="maxCPU", metavar="PCT",
179 help="Kill processes with CPU usage <= PCT",
180 action="store", type=float, default=inf)
181 group.add_option("", "--min-mem", dest="minMem", metavar="N",
182 help="Kill processes with virtual size >= N (MB)",
183 action="store", type=float, default=None)
184 group.add_option("", "--max-mem", dest="maxMem", metavar="N",
185 help="Kill processes with virtual size <= N (MB)",
186 action="store", type=float, default=inf)
187 group.add_option("", "--min-rss", dest="minRSS", metavar="N",
188 help="Kill processes with RSS >= N",
189 action="store", type=float, default=None)
190 group.add_option("", "--max-rss", dest="maxRSS", metavar="N",
191 help="Kill processes with RSS <= N",
192 action="store", type=float, default=inf)
193 group.add_option("", "--min-time", dest="minTime", metavar="N",
194 help="Kill processes with CPU time >= N (seconds)",
195 action="store", type=float, default=None)
196 group.add_option("", "--max-time", dest="maxTime", metavar="N",
197 help="Kill processes with CPU time <= N (seconds)",
198 action="store", type=float, default=inf)
199 parser.add_option_group(group)
200
201 (opts, args) = parser.parse_args()
202
203 if opts.listSignals:
204 items = [(v,k) for k,v in kSignals.items()]
205 items.sort()
206 for i in range(0, len(items), 4):
207 print '\t'.join(['%2d) SIG%s' % (k,v)
208 for k,v in items[i:i+4]])
209 sys.exit(0)
210
211 # Figure out the signal to use.
212 signal = kSignals[opts.signalName]
213 signalValueName = str(signal)
214 if opts.verbose:
215 name = dict((v,k) for k,v in kSignals.items()).get(signal,None)
216 if name:
217 signalValueName = name
218 note('using signal %d (SIG%s)' % (signal, name))
219 else:
220 note('using signal %d' % signal)
221
222 # Get the pid list to consider.
223 pids = set()
224 for arg in args:
225 try:
226 pids.add(int(arg))
227 except:
228 parser.error('invalid positional argument: %r' % arg)
229
230 filtered = ps = getProcessTable()
231
232 # Apply filters.
233 if pids:
234 filtered = [p for p in filtered
235 if p.pid in pids]
236 if opts.execName is not None:
237 filtered = [p for p in filtered
238 if re_full_match(opts.execName,
239 os.path.basename(p.executable))]
240 if opts.execPath is not None:
241 filtered = [p for p in filtered
242 if re_full_match(opts.execPath, p.executable)]
243 if opts.userName is not None:
244 filtered = [p for p in filtered
245 if re_full_match(opts.userName, p.user)]
246 filtered = [p for p in filtered
247 if opts.minCPU <= p.cpu_percent <= opts.maxCPU]
248 filtered = [p for p in filtered
249 if opts.minMem <= float(p.vmem_size) / (1<<20) <= opts.maxMem]
250 filtered = [p for p in filtered
251 if opts.minRSS <= p.rss <= opts.maxRSS]
252 filtered = [p for p in filtered
253 if opts.minTime <= p.cpu_time <= opts.maxTime]
254
255 if len(filtered) == len(ps):
256 if not opts.force and not opts.dryRun:
257 error('refusing to kill all processes without --force')
258
259 if not filtered:
260 warning('no processes selected')
261
262 for p in filtered:
263 if opts.verbose:
264 note('kill(%r, %s) # (user=%r, executable=%r, CPU=%2.2f%%, time=%r, vmem=%r, rss=%r)' %
265 (p.pid, signalValueName, p.user, p.executable, p.cpu_percent, p.cpu_time, p.vmem_size, p.rss))
266 if not opts.dryRun:
267 try:
268 os.kill(p.pid, signal)
269 except OSError:
270 if opts.debug:
271 raise
272 warning('unable to kill PID: %r' % p.pid)
273
274 if __name__ == '__main__':
275 main()