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 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
|
#!/usr/bin/env python3
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()
|