Package logilab-common-0 :: Package 39 :: Package 0 :: Module proc
[frames] | no frames]

Source Code for Module logilab-common-0.39.0.proc

  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   
23 -class NoSuchProcess(Exception): pass
24
25 -def proc_exists(pid):
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
39 -class ProcInfo(Node):
40 """provide access to process information found in /proc""" 41
42 - def __init__(self, pid):
43 self.pid = int(pid) 44 Node.__init__(self, self.pid) 45 proc_exists(self.pid) 46 self.file = '/proc/%s/stat' % self.pid 47 self.ppid = int(self.status()[PPID])
48
49 - def memory_usage(self):
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
56 - def lineage_memory_usage(self):
57 return self.memory_usage() + sum(child.lineage_memory_usage() for child in self.children)
58
59 - def time(self, children=0):
60 """return the number of jiffies that this process has been scheduled 61 in user and kernel mode""" 62 status = self.status() 63 time = int(status[UTIME]) + int(status[STIME]) 64 if children: 65 time += int(status[CUTIME]) + int(status[CSTIME]) 66 return time
67
68 - def status(self):
69 """return the list of fields found in /proc/<pid>/stat""" 70 return open(self.file).read().split()
71
72 - def name(self):
73 """return the process name found in /proc/<pid>/stat 74 """ 75 return self.status()[1].strip('()')
76
77 - def age(self):
78 """return the age of the process 79 """ 80 return os.stat(self.file)[stat.ST_MTIME]
81
82 -class ProcInfoLoader:
83 """manage process information""" 84
85 - def __init__(self):
86 self._loaded = {}
87
88 - def list_pids(self):
89 """return a list of existant 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
106 - def load_all(self):
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:
119 - class ResourceError(BaseException):
120 """Error raise when resource limit is reached""" 121 limit = "Unknow Resource Limit"
122 except NameError:
123 - class ResourceError(Exception):
124 """Error raise when resource limit is reached""" 125 limit = "Unknow Resource Limit"
126 127
128 -class XCPUError(ResourceError):
129 """Error raised when CPU Time limite is reached""" 130 limit = "CPU Time"
131
132 -class LineageMemoryError(ResourceError):
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
137 -class TimeoutError(ResourceError):
138 """Error raised when the process is running for to much time""" 139 limit = "Real Time"
140 141 # Can't use subclass because the StandardError MemoryError raised 142 RESOURCE_LIMIT_EXCEPTION = (ResourceError, MemoryError) 143 144
145 -class MemorySentinel(Thread):
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
157 - def stop(self):
158 """stop ap""" 159 self._stop.set()
160
161 - def _run(self):
162 pil = ProcInfoLoader() 163 while not self._stop.isSet(): 164 if self.memory_limit <= pil.load(self.gpid).lineage_memory_usage(): 165 os.killpg(self.gpid, SIGUSR1) 166 self._stop.wait(self.interval)
167 168
169 -class ResourceController:
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
191 - def _hangle_sig_timeout(self, sig, frame):
192 raise TimeoutError()
193
194 - def _hangle_sig_memory(self, sig, frame):
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
201 - def _handle_sigxcpu(self, sig, frame):
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
208 - def _time_out(self):
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
218 - def setup_limit(self):
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
243 - def clean_limit(self):
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