Skip to content
Newer
Older
100755 248 lines (203 sloc) 6.77 KB
4cd60b9 @jschauma Initial import of sources from Yahoo!
authored Feb 5, 2011
1 #! /usr/bin/env python
2 #
3 # Copyright (c) 2007,2011 Yahoo! Inc.
4 # All rights reserved.
5 #
6 # Originally written by Jan Schaumann <jschauma@yahoo-inc.com> in July 2007.
7 #
8 # Redistribution and use of this software in source and binary forms,
9 # with or without modification, are permitted provided that the following
10 # conditions are met:
11 #
12 # * Redistributions of source code must retain the above
13 # copyright notice, this list of conditions and the
14 # following disclaimer.
15 #
16 # * Redistributions in binary form must reproduce the above
17 # copyright notice, this list of conditions and the
18 # following disclaimer in the documentation and/or other
19 # materials provided with the distribution.
20 #
21 # * Neither the name of Yahoo! Inc. nor the names of its
22 # contributors may be used to endorse or promote products
23 # derived from this software without specific prior
24 # written permission of Yahoo! Inc.
25 #
26 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
27 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
29 # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 #
38 #
39 # This program kills a given process or process group if it has been running
40 # for longer than a specified time.
41 #
42 # See http://mail-index.netbsd.org/tech-userlevel/2007/07/26/0003.html for
43 # a patch to NetBSD's pkill(1) sources.
44
45 import getopt
46 import os
47 import re
48 import string
49 import sys
50 import time
51
52 ###
53 ### Globals
54 ###
55
56 EXIT_ERROR = 1
57 EXIT_SUCCESS = 0
58
59 PS_CMD = "ps -awwx -o lstart,pid,ppid,command"
60
61 SIGNALS = [ "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE",
62 "KILL", "BUS", "SEGV", "SYS", "PIPE", "ALRM", "TERM", "URG",
63 "STOP", "TSTP", "CONT", "CHLD", "TTIN", "TTOU", "IO", "XCPU",
64 "XFSZ", "VTALRM","PROF","WINCH","INFO", "USR1", "USR2", "PWR" ]
65
66 CMD = ""
67 LOOSE_MATCH = False
68 PARENT = False
69 PIDS = []
70 SIG = "TERM"
71 TIMEOUT = 300
72
73 ###
74 ### Subroutines
75 ###
76
77 def usage():
78 """print short usage"""
79
80 print 'Usage: %s [-P] [-h] [-l] [-t timeout] [-s signal] (pid [...] | -c cmd)' \
81 % sys.argv[0]
82 print '\t-C cmd kill processes containing the given command string'
83 print '\t-P kill all processes whose parent is pid'
84 print '\t-c cmd kill processes matching given command exactly'
85 print '\t-h print this help and exit'
86 print '\t-l print list of valid signals'
87 print '\t-t timeout specify timeout in seconds (default: 300)'
88 print '\t-s signal kill with given signal (default: TERM)'
89
90
91
92 def isValidSignal(sig):
93 """validate the given signal by checking if it is a valid numerical signal
94 or as a string in the array of signals we know"""
95
96 try:
97 s = string.atoi(sig)
98 if (s < 1) or (s > 32):
99 raise ValueError("illegal numerical signal %d" % sig)
100 except:
101 for s in SIGNALS:
102 if sig == s:
103 return sig
104 raise ValueError("illegal numerical signal %s" % sig)
105
106 return sig
107
108
109
110 def parseOpts():
111 """parse command-line options and act on them"""
112
113 global CMD, LOOSE_MATCH, PARENT, SIG, TIMEOUT
114
115 try:
116 opts, args = getopt.getopt(sys.argv[1:], "C:Pc:hlt:s:")
117 except getopt.GetoptError:
118 usage()
119 sys.exit(EXIT_ERROR)
120 # NOTREACHED
121
122 for o, a in opts:
123 if o in ("-C"):
124 CMD = a
125 LOOSE_MATCH = True
126 if o in ("-P"):
127 PARENT = True
128 if o in ("-c"):
129 CMD = a
130 if o in ("-h"):
131 usage()
132 sys.exit(EXIT_SUCCESS)
133 # NOTREACHED
134 if o in ("-l"):
135 for s in SIGNALS:
136 sys.stdout.write("%s " % s)
137 sys.stdout.write("\n")
138 sys.exit(EXIT_SUCCESS)
139 # NOTREACHED
140 if o in ("-t"):
141 try:
142 TIMEOUT = string.atoi(a)
143 except:
144 sys.stderr.write('%s: -t: not a number: "%s"\n' % (sys.argv[0], a))
145 sys.exit(EXIT_ERROR)
146 # NOTREACHED
147 if o in ("-s"):
148 try:
149 SIG = isValidSignal(a)
150 except ValueError:
151 sys.stderr.write('%s: -s: bad signal `%s\'\n' % (sys.argv[0], a))
152 sys.exit(EXIT_ERROR)
153 # NOTREACHED
154
155 if args:
156 if CMD:
157 usage()
158 sys.exit(EXIT_ERROR)
159 # NOTREACHED
160 else:
161 try:
162 for p in args:
163 PIDS.append(string.atoi(p))
164 except ValueError:
165 sys.stderr.write('%s: bad pid `%s\'\n' % (sys.argv[0], p))
166 sys.exit(EXIT_ERROR)
167 # NOTREACHED
168 else:
169 if not CMD:
170 usage()
171 sys.exit(EXIT_ERROR)
172 # NOTREACHED
173
174
175
176 def isStalePid(start):
177 """return true if the given processes running time has been longer than
178 the limit"""
179
180 global TIMEOUT
181
182 now = time.time()
183 if start < (now - TIMEOUT):
184 return True
185
186 return False
187
188
189
190 def killAllStalePids():
191 """open a pipe to the PS command and parse the output"""
192
193 global CMD, LOOSE_MATCH, PARENT, PIDS, PS_CMD
194
195 ps = os.popen(PS_CMD)
196 while 1:
197 line = ps.readline()
198 if not line:
199 break
200 p = re.compile("^(\S+ \S+ +\d+ [0-9:]+ \d+)\s+(\d+)\s+(\d+)\s+(.*)$")
201 m = p.match(line)
202
203 if not m:
204 continue
205
206 (start, pid, ppid, comm) = m.groups()
207
208 t = time.strptime(start)
209 start = time.mktime(t)
210
211 pid = string.atoi(pid)
212 ppid = string.atoi(ppid)
213
214 if ( ((CMD == comm.strip()) or \
215 (LOOSE_MATCH and (comm.find(CMD) >= 0))) \
216 and isStalePid(start) ):
217 killPid(pid)
218
219 for p in PIDS:
220 if not isStalePid(start):
221 continue
222 if PARENT:
223 if p == ppid:
224 killPid(p)
225 else:
226 if p == pid:
227 killPid(p)
228
229
230
231 def killPid(p):
232 """kill a process"""
233
234 s = SIGNALS.index(SIG) + 1
235 try:
236 os.kill(p,s)
237 except Exception, (errno, strerror):
238 sys.stderr.write('%s: unable to kill PID %d: %s\n' % \
239 (sys.argv[0], p, strerror))
240
241 ###
242 ### Main
243 ###
244
245 if __name__ == "__main__":
246 parseOpts()
247 killAllStalePids()
Something went wrong with that request. Please try again.