| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- #!/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()
|