1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """module providing:
19 * process information (linux specific: rely on /proc)
20 * a class for resource control (memory / time / cpu time)
21
22 This module doesn't work on windows platforms (only tested on linux)
23
24 :organization: Logilab
25
26
27
28 """
29 __docformat__ = "restructuredtext en"
30
31 import os
32 import stat
33 from resource import getrlimit, setrlimit, RLIMIT_CPU, RLIMIT_AS
34 from signal import signal, SIGXCPU, SIGKILL, SIGUSR2, SIGUSR1
35 from threading import Timer, currentThread, Thread, Event
36 from time import time
37
38 from logilab.common.tree import Node
39
41
43 """check the a pid is registered in /proc
44 raise NoSuchProcess exception if not
45 """
46 if not os.path.exists('/proc/%s' % pid):
47 raise NoSuchProcess()
48
49 PPID = 3
50 UTIME = 13
51 STIME = 14
52 CUTIME = 15
53 CSTIME = 16
54 VSIZE = 22
55
57 """provide access to process information found in /proc"""
58
65
67 """return the memory usage of the process in Ko"""
68 try :
69 return int(self.status()[VSIZE])
70 except IOError:
71 return 0
72
76
77 - def time(self, children=0):
85
87 """return the list of fields found in /proc/<pid>/stat"""
88 return open(self.file).read().split()
89
91 """return the process name found in /proc/<pid>/stat
92 """
93 return self.status()[1].strip('()')
94
96 """return the age of the process
97 """
98 return os.stat(self.file)[stat.ST_MTIME]
99
101 """manage process information"""
102
105
107 """return a list of existent process ids"""
108 for subdir in os.listdir('/proc'):
109 if subdir.isdigit():
110 yield int(subdir)
111
112 - def load(self, pid):
113 """get a ProcInfo object for a given pid"""
114 pid = int(pid)
115 try:
116 return self._loaded[pid]
117 except KeyError:
118 procinfo = ProcInfo(pid)
119 procinfo.manager = self
120 self._loaded[pid] = procinfo
121 return procinfo
122
123
125 """load all processes information"""
126 for pid in self.list_pids():
127 try:
128 procinfo = self.load(pid)
129 if procinfo.parent is None and procinfo.ppid:
130 pprocinfo = self.load(procinfo.ppid)
131 pprocinfo.append(procinfo)
132 except NoSuchProcess:
133 pass
134
135
136 try:
138 """Error raise when resource limit is reached"""
139 limit = "Unknown Resource Limit"
140 except NameError:
142 """Error raise when resource limit is reached"""
143 limit = "Unknown Resource Limit"
144
145
147 """Error raised when CPU Time limit is reached"""
148 limit = "CPU Time"
149
151 """Error raised when the total amount of memory used by a process and
152 it's child is reached"""
153 limit = "Lineage total Memory"
154
156 """Error raised when the process is running for to much time"""
157 limit = "Real Time"
158
159
160 RESOURCE_LIMIT_EXCEPTION = (ResourceError, MemoryError)
161
162
164 """A class checking a process don't use too much memory in a separated
165 daemonic thread
166 """
167 - def __init__(self, interval, memory_limit, gpid=os.getpid()):
168 Thread.__init__(self, target=self._run, name="Test.Sentinel")
169 self.memory_limit = memory_limit
170 self._stop = Event()
171 self.interval = interval
172 self.setDaemon(True)
173 self.gpid = gpid
174
176 """stop ap"""
177 self._stop.set()
178
180 pil = ProcInfoLoader()
181 while not self._stop.isSet():
182 if self.memory_limit <= pil.load(self.gpid).lineage_memory_usage():
183 os.killpg(self.gpid, SIGUSR1)
184 self._stop.wait(self.interval)
185
186
188
189 - def __init__(self, max_cpu_time=None, max_time=None, max_memory=None,
190 max_reprieve=60):
191 if SIGXCPU == -1:
192 raise RuntimeError("Unsupported platform")
193 self.max_time = max_time
194 self.max_memory = max_memory
195 self.max_cpu_time = max_cpu_time
196 self._reprieve = max_reprieve
197 self._timer = None
198 self._msentinel = None
199 self._old_max_memory = None
200 self._old_usr1_hdlr = None
201 self._old_max_cpu_time = None
202 self._old_usr2_hdlr = None
203 self._old_sigxcpu_hdlr = None
204 self._limit_set = 0
205 self._abort_try = 0
206 self._start_time = None
207 self._elapse_time = 0
208
211
213 if self._abort_try < self._reprieve:
214 self._abort_try += 1
215 raise LineageMemoryError("Memory limit reached")
216 else:
217 os.killpg(os.getpid(), SIGKILL)
218
220 if self._abort_try < self._reprieve:
221 self._abort_try += 1
222 raise XCPUError("Soft CPU time limit reached")
223 else:
224 os.killpg(os.getpid(), SIGKILL)
225
227 if self._abort_try < self._reprieve:
228 self._abort_try += 1
229 os.killpg(os.getpid(), SIGUSR2)
230 if self._limit_set > 0:
231 self._timer = Timer(1, self._time_out)
232 self._timer.start()
233 else:
234 os.killpg(os.getpid(), SIGKILL)
235
237 """set up the process limit"""
238 assert currentThread().getName() == 'MainThread'
239 os.setpgrp()
240 if self._limit_set <= 0:
241 if self.max_time is not None:
242 self._old_usr2_hdlr = signal(SIGUSR2, self._hangle_sig_timeout)
243 self._timer = Timer(max(1, int(self.max_time) - self._elapse_time),
244 self._time_out)
245 self._start_time = int(time())
246 self._timer.start()
247 if self.max_cpu_time is not None:
248 self._old_max_cpu_time = getrlimit(RLIMIT_CPU)
249 cpu_limit = (int(self.max_cpu_time), self._old_max_cpu_time[1])
250 self._old_sigxcpu_hdlr = signal(SIGXCPU, self._handle_sigxcpu)
251 setrlimit(RLIMIT_CPU, cpu_limit)
252 if self.max_memory is not None:
253 self._msentinel = MemorySentinel(1, int(self.max_memory) )
254 self._old_max_memory = getrlimit(RLIMIT_AS)
255 self._old_usr1_hdlr = signal(SIGUSR1, self._hangle_sig_memory)
256 as_limit = (int(self.max_memory), self._old_max_memory[1])
257 setrlimit(RLIMIT_AS, as_limit)
258 self._msentinel.start()
259 self._limit_set += 1
260
262 """reinstall the old process limit"""
263 if self._limit_set > 0:
264 if self.max_time is not None:
265 self._timer.cancel()
266 self._elapse_time += int(time())-self._start_time
267 self._timer = None
268 signal(SIGUSR2, self._old_usr2_hdlr)
269 if self.max_cpu_time is not None:
270 setrlimit(RLIMIT_CPU, self._old_max_cpu_time)
271 signal(SIGXCPU, self._old_sigxcpu_hdlr)
272 if self.max_memory is not None:
273 self._msentinel.stop()
274 self._msentinel = None
275 setrlimit(RLIMIT_AS, self._old_max_memory)
276 signal(SIGUSR1, self._old_usr1_hdlr)
277 self._limit_set -= 1
278