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
75
76 - def time(self, children=0):
84
86 """return the list of fields found in /proc/<pid>/stat"""
87 return open(self.file).read().split()
88
90 """return the process name found in /proc/<pid>/stat
91 """
92 return self.status()[1].strip('()')
93
95 """return the age of the process
96 """
97 return os.stat(self.file)[stat.ST_MTIME]
98
100 """manage process information"""
101
104
106 """return a list of existent process ids"""
107 for subdir in os.listdir('/proc'):
108 if subdir.isdigit():
109 yield int(subdir)
110
111 - def load(self, pid):
112 """get a ProcInfo object for a given pid"""
113 pid = int(pid)
114 try:
115 return self._loaded[pid]
116 except KeyError:
117 procinfo = ProcInfo(pid)
118 procinfo.manager = self
119 self._loaded[pid] = procinfo
120 return procinfo
121
122
124 """load all processes information"""
125 for pid in self.list_pids():
126 try:
127 procinfo = self.load(pid)
128 if procinfo.parent is None and procinfo.ppid:
129 pprocinfo = self.load(procinfo.ppid)
130 pprocinfo.append(procinfo)
131 except NoSuchProcess:
132 pass
133
134
135 try:
137 """Error raise when resource limit is reached"""
138 limit = "Unknown Resource Limit"
139 except NameError:
141 """Error raise when resource limit is reached"""
142 limit = "Unknown Resource Limit"
143
144
146 """Error raised when CPU Time limit is reached"""
147 limit = "CPU Time"
148
150 """Error raised when the total amount of memory used by a process and
151 it's child is reached"""
152 limit = "Lineage total Memory"
153
155 """Error raised when the process is running for to much time"""
156 limit = "Real Time"
157
158
159 RESOURCE_LIMIT_EXCEPTION = (ResourceError, MemoryError)
160
161
163 """A class checking a process don't use too much memory in a separated
164 daemonic thread
165 """
166 - def __init__(self, interval, memory_limit, gpid=os.getpid()):
167 Thread.__init__(self, target=self._run, name="Test.Sentinel")
168 self.memory_limit = memory_limit
169 self._stop = Event()
170 self.interval = interval
171 self.setDaemon(True)
172 self.gpid = gpid
173
175 """stop ap"""
176 self._stop.set()
177
179 pil = ProcInfoLoader()
180 while not self._stop.isSet():
181 if self.memory_limit <= pil.load(self.gpid).lineage_memory_usage():
182 os.killpg(self.gpid, SIGUSR1)
183 self._stop.wait(self.interval)
184
185
187
188 - def __init__(self, max_cpu_time=None, max_time=None, max_memory=None,
189 max_reprieve=60):
190 if SIGXCPU == -1:
191 raise RuntimeError("Unsupported platform")
192 self.max_time = max_time
193 self.max_memory = max_memory
194 self.max_cpu_time = max_cpu_time
195 self._reprieve = max_reprieve
196 self._timer = None
197 self._msentinel = None
198 self._old_max_memory = None
199 self._old_usr1_hdlr = None
200 self._old_max_cpu_time = None
201 self._old_usr2_hdlr = None
202 self._old_sigxcpu_hdlr = None
203 self._limit_set = 0
204 self._abort_try = 0
205 self._start_time = None
206 self._elapse_time = 0
207
210
212 if self._abort_try < self._reprieve:
213 self._abort_try += 1
214 raise LineageMemoryError("Memory limit reached")
215 else:
216 os.killpg(os.getpid(), SIGKILL)
217
219 if self._abort_try < self._reprieve:
220 self._abort_try += 1
221 raise XCPUError("Soft CPU time limit reached")
222 else:
223 os.killpg(os.getpid(), SIGKILL)
224
226 if self._abort_try < self._reprieve:
227 self._abort_try += 1
228 os.killpg(os.getpid(), SIGUSR2)
229 if self._limit_set > 0:
230 self._timer = Timer(1, self._time_out)
231 self._timer.start()
232 else:
233 os.killpg(os.getpid(), SIGKILL)
234
236 """set up the process limit"""
237 assert currentThread().getName() == 'MainThread'
238 os.setpgrp()
239 if self._limit_set <= 0:
240 if self.max_time is not None:
241 self._old_usr2_hdlr = signal(SIGUSR2, self._hangle_sig_timeout)
242 self._timer = Timer(max(1, int(self.max_time) - self._elapse_time),
243 self._time_out)
244 self._start_time = int(time())
245 self._timer.start()
246 if self.max_cpu_time is not None:
247 self._old_max_cpu_time = getrlimit(RLIMIT_CPU)
248 cpu_limit = (int(self.max_cpu_time), self._old_max_cpu_time[1])
249 self._old_sigxcpu_hdlr = signal(SIGXCPU, self._handle_sigxcpu)
250 setrlimit(RLIMIT_CPU, cpu_limit)
251 if self.max_memory is not None:
252 self._msentinel = MemorySentinel(1, int(self.max_memory) )
253 self._old_max_memory = getrlimit(RLIMIT_AS)
254 self._old_usr1_hdlr = signal(SIGUSR1, self._hangle_sig_memory)
255 as_limit = (int(self.max_memory), self._old_max_memory[1])
256 setrlimit(RLIMIT_AS, as_limit)
257 self._msentinel.start()
258 self._limit_set += 1
259
261 """reinstall the old process limit"""
262 if self._limit_set > 0:
263 if self.max_time is not None:
264 self._timer.cancel()
265 self._elapse_time += int(time())-self._start_time
266 self._timer = None
267 signal(SIGUSR2, self._old_usr2_hdlr)
268 if self.max_cpu_time is not None:
269 setrlimit(RLIMIT_CPU, self._old_max_cpu_time)
270 signal(SIGXCPU, self._old_sigxcpu_hdlr)
271 if self.max_memory is not None:
272 self._msentinel.stop()
273 self._msentinel = None
274 setrlimit(RLIMIT_AS, self._old_max_memory)
275 signal(SIGUSR1, self._old_usr1_hdlr)
276 self._limit_set -= 1
277