|  | #!/usr/bin/env python | 
|  |  | 
|  | import os | 
|  | import re | 
|  | import sys | 
|  |  | 
|  | def _write_message(kind, message): | 
|  | import inspect, os, sys | 
|  |  | 
|  | # Get the file/line where this message was generated. | 
|  | f = inspect.currentframe() | 
|  | # Step out of _write_message, and then out of wrapper. | 
|  | f = f.f_back.f_back | 
|  | file,line,_,_,_ = inspect.getframeinfo(f) | 
|  | location = '%s:%d' % (os.path.basename(file), line) | 
|  |  | 
|  | print >>sys.stderr, '%s: %s: %s' % (location, kind, message) | 
|  |  | 
|  | note = lambda message: _write_message('note', message) | 
|  | warning = lambda message: _write_message('warning', message) | 
|  | error = lambda message: (_write_message('error', message), sys.exit(1)) | 
|  |  | 
|  | def re_full_match(pattern, str): | 
|  | m = re.match(pattern, str) | 
|  | if m and m.end() != len(str): | 
|  | m = None | 
|  | return m | 
|  |  | 
|  | def parse_time(value): | 
|  | minutes,value = value.split(':',1) | 
|  | if '.' in value: | 
|  | seconds,fseconds = value.split('.',1) | 
|  | else: | 
|  | seconds = value | 
|  | return int(minutes) * 60 + int(seconds) + float('.'+fseconds) | 
|  |  | 
|  | def extractExecutable(command): | 
|  | """extractExecutable - Given a string representing a command line, attempt | 
|  | to extract the executable path, even if it includes spaces.""" | 
|  |  | 
|  | # Split into potential arguments. | 
|  | args = command.split(' ') | 
|  |  | 
|  | # Scanning from the beginning, try to see if the first N args, when joined, | 
|  | # exist. If so that's probably the executable. | 
|  | for i in range(1,len(args)): | 
|  | cmd = ' '.join(args[:i]) | 
|  | if os.path.exists(cmd): | 
|  | return cmd | 
|  |  | 
|  | # Otherwise give up and return the first "argument". | 
|  | return args[0] | 
|  |  | 
|  | class Struct: | 
|  | def __init__(self, **kwargs): | 
|  | self.fields = kwargs.keys() | 
|  | self.__dict__.update(kwargs) | 
|  |  | 
|  | def __repr__(self): | 
|  | return 'Struct(%s)' % ', '.join(['%s=%r' % (k,getattr(self,k)) | 
|  | for k in self.fields]) | 
|  |  | 
|  | kExpectedPSFields = [('PID', int, 'pid'), | 
|  | ('USER', str, 'user'), | 
|  | ('COMMAND', str, 'command'), | 
|  | ('%CPU', float, 'cpu_percent'), | 
|  | ('TIME', parse_time, 'cpu_time'), | 
|  | ('VSZ', int, 'vmem_size'), | 
|  | ('RSS', int, 'rss')] | 
|  | def getProcessTable(): | 
|  | import subprocess | 
|  | p = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE, | 
|  | stderr=subprocess.PIPE) | 
|  | out,err = p.communicate() | 
|  | res = p.wait() | 
|  | if p.wait(): | 
|  | error('unable to get process table') | 
|  | elif err.strip(): | 
|  | error('unable to get process table: %s' % err) | 
|  |  | 
|  | lns = out.split('\n') | 
|  | it = iter(lns) | 
|  | header = it.next().split() | 
|  | numRows = len(header) | 
|  |  | 
|  | # Make sure we have the expected fields. | 
|  | indexes = [] | 
|  | for field in kExpectedPSFields: | 
|  | try: | 
|  | indexes.append(header.index(field[0])) | 
|  | except: | 
|  | if opts.debug: | 
|  | raise | 
|  | error('unable to get process table, no %r field.' % field[0]) | 
|  |  | 
|  | table = [] | 
|  | for i,ln in enumerate(it): | 
|  | if not ln.strip(): | 
|  | continue | 
|  |  | 
|  | fields = ln.split(None, numRows - 1) | 
|  | if len(fields) != numRows: | 
|  | warning('unable to process row: %r' % ln) | 
|  | continue | 
|  |  | 
|  | record = {} | 
|  | for field,idx in zip(kExpectedPSFields, indexes): | 
|  | value = fields[idx] | 
|  | try: | 
|  | record[field[2]] = field[1](value) | 
|  | except: | 
|  | if opts.debug: | 
|  | raise | 
|  | warning('unable to process %r in row: %r' % (field[0], ln)) | 
|  | break | 
|  | else: | 
|  | # Add our best guess at the executable. | 
|  | record['executable'] = extractExecutable(record['command']) | 
|  | table.append(Struct(**record)) | 
|  |  | 
|  | return table | 
|  |  | 
|  | def getSignalValue(name): | 
|  | import signal | 
|  | if name.startswith('SIG'): | 
|  | value = getattr(signal, name) | 
|  | if value and isinstance(value, int): | 
|  | return value | 
|  | error('unknown signal: %r' % name) | 
|  |  | 
|  | import signal | 
|  | kSignals = {} | 
|  | for name in dir(signal): | 
|  | if name.startswith('SIG') and name == name.upper() and name.isalpha(): | 
|  | kSignals[name[3:]] = getattr(signal, name) | 
|  |  | 
|  | def main(): | 
|  | global opts | 
|  | from optparse import OptionParser, OptionGroup | 
|  | parser = OptionParser("usage: %prog [options] {pid}*") | 
|  |  | 
|  | # FIXME: Add -NNN and -SIGNAME options. | 
|  |  | 
|  | parser.add_option("-s", "", dest="signalName", | 
|  | help="Name of the signal to use (default=%default)", | 
|  | action="store", default='INT', | 
|  | choices=kSignals.keys()) | 
|  | parser.add_option("-l", "", dest="listSignals", | 
|  | help="List known signal names", | 
|  | action="store_true", default=False) | 
|  |  | 
|  | parser.add_option("-n", "--dry-run", dest="dryRun", | 
|  | help="Only print the actions that would be taken", | 
|  | action="store_true", default=False) | 
|  | parser.add_option("-v", "--verbose", dest="verbose", | 
|  | help="Print more verbose output", | 
|  | action="store_true", default=False) | 
|  | parser.add_option("", "--debug", dest="debug", | 
|  | help="Enable debugging output", | 
|  | action="store_true", default=False) | 
|  | parser.add_option("", "--force", dest="force", | 
|  | help="Perform the specified commands, even if it seems like a bad idea", | 
|  | action="store_true", default=False) | 
|  |  | 
|  | inf = float('inf') | 
|  | group = OptionGroup(parser, "Process Filters") | 
|  | group.add_option("", "--name", dest="execName", metavar="REGEX", | 
|  | help="Kill processes whose name matches the given regexp", | 
|  | action="store", default=None) | 
|  | group.add_option("", "--exec", dest="execPath", metavar="REGEX", | 
|  | help="Kill processes whose executable matches the given regexp", | 
|  | action="store", default=None) | 
|  | group.add_option("", "--user", dest="userName", metavar="REGEX", | 
|  | help="Kill processes whose user matches the given regexp", | 
|  | action="store", default=None) | 
|  | group.add_option("", "--min-cpu", dest="minCPU", metavar="PCT", | 
|  | help="Kill processes with CPU usage >= PCT", | 
|  | action="store", type=float, default=None) | 
|  | group.add_option("", "--max-cpu", dest="maxCPU", metavar="PCT", | 
|  | help="Kill processes with CPU usage <= PCT", | 
|  | action="store", type=float, default=inf) | 
|  | group.add_option("", "--min-mem", dest="minMem", metavar="N", | 
|  | help="Kill processes with virtual size >= N (MB)", | 
|  | action="store", type=float, default=None) | 
|  | group.add_option("", "--max-mem", dest="maxMem", metavar="N", | 
|  | help="Kill processes with virtual size <= N (MB)", | 
|  | action="store", type=float, default=inf) | 
|  | group.add_option("", "--min-rss", dest="minRSS", metavar="N", | 
|  | help="Kill processes with RSS >= N", | 
|  | action="store", type=float, default=None) | 
|  | group.add_option("", "--max-rss", dest="maxRSS", metavar="N", | 
|  | help="Kill processes with RSS <= N", | 
|  | action="store", type=float, default=inf) | 
|  | group.add_option("", "--min-time", dest="minTime", metavar="N", | 
|  | help="Kill processes with CPU time >= N (seconds)", | 
|  | action="store", type=float, default=None) | 
|  | group.add_option("", "--max-time", dest="maxTime", metavar="N", | 
|  | help="Kill processes with CPU time <= N (seconds)", | 
|  | action="store", type=float, default=inf) | 
|  | parser.add_option_group(group) | 
|  |  | 
|  | (opts, args) = parser.parse_args() | 
|  |  | 
|  | if opts.listSignals: | 
|  | items = [(v,k) for k,v in kSignals.items()] | 
|  | items.sort() | 
|  | for i in range(0, len(items), 4): | 
|  | print '\t'.join(['%2d) SIG%s' % (k,v) | 
|  | for k,v in items[i:i+4]]) | 
|  | sys.exit(0) | 
|  |  | 
|  | # Figure out the signal to use. | 
|  | signal = kSignals[opts.signalName] | 
|  | signalValueName = str(signal) | 
|  | if opts.verbose: | 
|  | name = dict((v,k) for k,v in kSignals.items()).get(signal,None) | 
|  | if name: | 
|  | signalValueName = name | 
|  | note('using signal %d (SIG%s)' % (signal, name)) | 
|  | else: | 
|  | note('using signal %d' % signal) | 
|  |  | 
|  | # Get the pid list to consider. | 
|  | pids = set() | 
|  | for arg in args: | 
|  | try: | 
|  | pids.add(int(arg)) | 
|  | except: | 
|  | parser.error('invalid positional argument: %r' % arg) | 
|  |  | 
|  | filtered = ps = getProcessTable() | 
|  |  | 
|  | # Apply filters. | 
|  | if pids: | 
|  | filtered = [p for p in filtered | 
|  | if p.pid in pids] | 
|  | if opts.execName is not None: | 
|  | filtered = [p for p in filtered | 
|  | if re_full_match(opts.execName, | 
|  | os.path.basename(p.executable))] | 
|  | if opts.execPath is not None: | 
|  | filtered = [p for p in filtered | 
|  | if re_full_match(opts.execPath, p.executable)] | 
|  | if opts.userName is not None: | 
|  | filtered = [p for p in filtered | 
|  | if re_full_match(opts.userName, p.user)] | 
|  | filtered = [p for p in filtered | 
|  | if opts.minCPU <= p.cpu_percent <= opts.maxCPU] | 
|  | filtered = [p for p in filtered | 
|  | if opts.minMem <= float(p.vmem_size) / (1<<20) <= opts.maxMem] | 
|  | filtered = [p for p in filtered | 
|  | if opts.minRSS <= p.rss <= opts.maxRSS] | 
|  | filtered = [p for p in filtered | 
|  | if opts.minTime <= p.cpu_time <= opts.maxTime] | 
|  |  | 
|  | if len(filtered) == len(ps): | 
|  | if not opts.force and not opts.dryRun: | 
|  | error('refusing to kill all processes without --force') | 
|  |  | 
|  | if not filtered: | 
|  | warning('no processes selected') | 
|  |  | 
|  | for p in filtered: | 
|  | if opts.verbose: | 
|  | note('kill(%r, %s) # (user=%r, executable=%r, CPU=%2.2f%%, time=%r, vmem=%r, rss=%r)' % | 
|  | (p.pid, signalValueName, p.user, p.executable, p.cpu_percent, p.cpu_time, p.vmem_size, p.rss)) | 
|  | if not opts.dryRun: | 
|  | try: | 
|  | os.kill(p.pid, signal) | 
|  | except OSError: | 
|  | if opts.debug: | 
|  | raise | 
|  | warning('unable to kill PID: %r' % p.pid) | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | main() |