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