1 """module providing:
2 * process information (linux specific: rely on /proc)
3 * a class for resource control (memory / time / cpu time)
4
5 This module doesn't work on windows platforms (only tested on linux)
6
7 :organization: Logilab
8 :copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
9 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
10 :license: General Public License version 2 - http://www.gnu.org/licenses
11 """
12 __docformat__ = "restructuredtext en"
13
14 import os
15 import stat
16 from resource import getrlimit, setrlimit, RLIMIT_CPU, RLIMIT_AS
17 from signal import signal, SIGXCPU, SIGKILL, SIGUSR2, SIGUSR1
18 from threading import Timer, currentThread, Thread, Event
19 from time import time
20
21 from logilab.common.tree import Node
22
24
26 """check the a pid is registered in /proc
27 raise NoSuchProcess exception if not
28 """
29 if not os.path.exists('/proc/%s' % pid):
30 raise NoSuchProcess()
31
32 PPID = 3
33 UTIME = 13
34 STIME = 14
35 CUTIME = 15
36 CSTIME = 16
37 VSIZE = 22
38
40 """provide access to process information found in /proc"""
41
48
50 """return the memory usage of the process in Ko"""
51 try :
52 return int(self.status()[VSIZE])
53 except IOError:
54 return 0
55
58
59 - def time(self, children=0):
67
69 """return the list of fields found in /proc/<pid>/stat"""
70 return open(self.file).read().split()
71
73 """return the process name found in /proc/<pid>/stat
74 """
75 return self.status()[1].strip('()')
76
78 """return the age of the process
79 """
80 return os.stat(self.file)[stat.ST_MTIME]
81
83 """manage process information"""
84
87
89 """return a list of existent process ids"""
90 for subdir in os.listdir('/proc'):
91 if subdir.isdigit():
92 yield int(subdir)
93
94 - def load(self, pid):
95 """get a ProcInfo object for a given pid"""
96 pid = int(pid)
97 try:
98 return self._loaded[pid]
99 except KeyError:
100 procinfo = ProcInfo(pid)
101 procinfo.manager = self
102 self._loaded[pid] = procinfo
103 return procinfo
104
105
107 """load all processes information"""
108 for pid in self.list_pids():
109 try:
110 procinfo = self.load(pid)
111 if procinfo.parent is None and procinfo.ppid:
112 pprocinfo = self.load(procinfo.ppid)
113 pprocinfo.append(procinfo)
114 except NoSuchProcess:
115 pass
116
117
118 try:
120 """Error raise when resource limit is reached"""
121 limit = "Unknown Resource Limit"
122 except NameError:
124 """Error raise when resource limit is reached"""
125 limit = "Unknown Resource Limit"
126
127
129 """Error raised when CPU Time limit is reached"""
130 limit = "CPU Time"
131
133 """Error raised when the total amount of memory used by a process and
134 it's child is reached"""
135 limit = "Lineage total Memory"
136
138 """Error raised when the process is running for to much time"""
139 limit = "Real Time"
140
141
142 RESOURCE_LIMIT_EXCEPTION = (ResourceError, MemoryError)
143
144
146 """A class checking a process don't use too much memory in a separated
147 daemonic thread
148 """
149 - def __init__(self, interval, memory_limit, gpid=os.getpid()):
150 Thread.__init__(self, target=self._run, name="Test.Sentinel")
151 self.memory_limit = memory_limit
152 self._stop = Event()
153 self.interval = interval
154 self.setDaemon(True)
155 self.gpid = gpid
156
158 """stop ap"""
159 self._stop.set()
160
167
168
170
171 - def __init__(self, max_cpu_time=None, max_time=None, max_memory=None,
172 max_reprieve=60):
173 if SIGXCPU == -1:
174 raise RuntimeError("Unsupported platform")
175 self.max_time = max_time
176 self.max_memory = max_memory
177 self.max_cpu_time = max_cpu_time
178 self._reprieve = max_reprieve
179 self._timer = None
180 self._msentinel = None
181 self._old_max_memory = None
182 self._old_usr1_hdlr = None
183 self._old_max_cpu_time = None
184 self._old_usr2_hdlr = None
185 self._old_sigxcpu_hdlr = None
186 self._limit_set = 0
187 self._abort_try = 0
188 self._start_time = None
189 self._elapse_time = 0
190
193
195 if self._abort_try < self._reprieve:
196 self._abort_try += 1
197 raise LineageMemoryError("Memory limit reached")
198 else:
199 os.killpg(os.getpid(), SIGKILL)
200
202 if self._abort_try < self._reprieve:
203 self._abort_try += 1
204 raise XCPUError("Soft CPU time limit reached")
205 else:
206 os.killpg(os.getpid(), SIGKILL)
207
209 if self._abort_try < self._reprieve:
210 self._abort_try += 1
211 os.killpg(os.getpid(), SIGUSR2)
212 if self._limit_set > 0:
213 self._timer = Timer(1, self._time_out)
214 self._timer.start()
215 else:
216 os.killpg(os.getpid(), SIGKILL)
217
219 """set up the process limit"""
220 assert currentThread().getName() == 'MainThread'
221 os.setpgrp()
222 if self._limit_set <= 0:
223 if self.max_time is not None:
224 self._old_usr2_hdlr = signal(SIGUSR2, self._hangle_sig_timeout)
225 self._timer = Timer(max(1, int(self.max_time) - self._elapse_time),
226 self._time_out)
227 self._start_time = int(time())
228 self._timer.start()
229 if self.max_cpu_time is not None:
230 self._old_max_cpu_time = getrlimit(RLIMIT_CPU)
231 cpu_limit = (int(self.max_cpu_time), self._old_max_cpu_time[1])
232 self._old_sigxcpu_hdlr = signal(SIGXCPU, self._handle_sigxcpu)
233 setrlimit(RLIMIT_CPU, cpu_limit)
234 if self.max_memory is not None:
235 self._msentinel = MemorySentinel(1, int(self.max_memory) )
236 self._old_max_memory = getrlimit(RLIMIT_AS)
237 self._old_usr1_hdlr = signal(SIGUSR1, self._hangle_sig_memory)
238 as_limit = (int(self.max_memory), self._old_max_memory[1])
239 setrlimit(RLIMIT_AS, as_limit)
240 self._msentinel.start()
241 self._limit_set += 1
242
244 """reinstall the old process limit"""
245 if self._limit_set > 0:
246 if self.max_time is not None:
247 self._timer.cancel()
248 self._elapse_time += int(time())-self._start_time
249 self._timer = None
250 signal(SIGUSR2, self._old_usr2_hdlr)
251 if self.max_cpu_time is not None:
252 setrlimit(RLIMIT_CPU, self._old_max_cpu_time)
253 signal(SIGXCPU, self._old_sigxcpu_hdlr)
254 if self.max_memory is not None:
255 self._msentinel.stop()
256 self._msentinel = None
257 setrlimit(RLIMIT_AS, self._old_max_memory)
258 signal(SIGUSR1, self._old_usr1_hdlr)
259 self._limit_set -= 1
260