OLD | NEW |
(Empty) | |
| 1 ########################################################################### |
| 2 # |
| 3 # Psyco profiler (Python part). |
| 4 # Copyright (C) 2001-2002 Armin Rigo et.al. |
| 5 |
| 6 """Psyco profiler (Python part). |
| 7 |
| 8 The implementation of the non-time-critical parts of the profiler. |
| 9 See profile() and full() in core.py for the easy interface. |
| 10 """ |
| 11 ########################################################################### |
| 12 |
| 13 import _psyco |
| 14 from support import * |
| 15 import math, time, types, atexit |
| 16 now = time.time |
| 17 try: |
| 18 import thread |
| 19 except ImportError: |
| 20 import dummy_thread as thread |
| 21 |
| 22 |
| 23 # current profiler instance |
| 24 current = None |
| 25 |
| 26 # enabled profilers, in order of priority |
| 27 profilers = [] |
| 28 |
| 29 # logger module (when enabled by core.log()) |
| 30 logger = None |
| 31 |
| 32 # a lock for a thread-safe go() |
| 33 go_lock = thread.allocate_lock() |
| 34 |
| 35 def go(stop=0): |
| 36 # run the highest-priority profiler in 'profilers' |
| 37 global current |
| 38 go_lock.acquire() |
| 39 try: |
| 40 prev = current |
| 41 if stop: |
| 42 del profilers[:] |
| 43 if prev: |
| 44 if profilers and profilers[0] is prev: |
| 45 return # best profiler already running |
| 46 prev.stop() |
| 47 current = None |
| 48 for p in profilers[:]: |
| 49 if p.start(): |
| 50 current = p |
| 51 if logger: # and p is not prev: |
| 52 logger.write("%s: starting" % p.__class__.__name__, 5) |
| 53 return |
| 54 finally: |
| 55 go_lock.release() |
| 56 # no profiler is running now |
| 57 if stop: |
| 58 if logger: |
| 59 logger.writefinalstats() |
| 60 else: |
| 61 tag2bind() |
| 62 |
| 63 atexit.register(go, 1) |
| 64 |
| 65 |
| 66 def buildfncache(globals, cache): |
| 67 if hasattr(types.IntType, '__dict__'): |
| 68 clstypes = (types.ClassType, types.TypeType) |
| 69 else: |
| 70 clstypes = types.ClassType |
| 71 for x in globals.values(): |
| 72 if isinstance(x, types.MethodType): |
| 73 x = x.im_func |
| 74 if isinstance(x, types.FunctionType): |
| 75 cache[x.func_code] = x, '' |
| 76 elif isinstance(x, clstypes): |
| 77 for y in x.__dict__.values(): |
| 78 if isinstance(y, types.MethodType): |
| 79 y = y.im_func |
| 80 if isinstance(y, types.FunctionType): |
| 81 cache[y.func_code] = y, x.__name__ |
| 82 |
| 83 # code-to-function mapping (cache) |
| 84 function_cache = {} |
| 85 |
| 86 def trytobind(co, globals, log=1): |
| 87 try: |
| 88 f, clsname = function_cache[co] |
| 89 except KeyError: |
| 90 buildfncache(globals, function_cache) |
| 91 try: |
| 92 f, clsname = function_cache[co] |
| 93 except KeyError: |
| 94 if logger: |
| 95 logger.write('warning: cannot find function %s in %s' % |
| 96 (co.co_name, globals.get('__name__', '?')), 3) |
| 97 return # give up |
| 98 if logger and log: |
| 99 modulename = globals.get('__name__', '?') |
| 100 if clsname: |
| 101 modulename += '.' + clsname |
| 102 logger.write('bind function: %s.%s' % (modulename, co.co_name), 1) |
| 103 f.func_code = _psyco.proxycode(f) |
| 104 |
| 105 |
| 106 # the list of code objects that have been tagged |
| 107 tagged_codes = [] |
| 108 |
| 109 def tag(co, globals): |
| 110 if logger: |
| 111 try: |
| 112 f, clsname = function_cache[co] |
| 113 except KeyError: |
| 114 buildfncache(globals, function_cache) |
| 115 try: |
| 116 f, clsname = function_cache[co] |
| 117 except KeyError: |
| 118 clsname = '' # give up |
| 119 modulename = globals.get('__name__', '?') |
| 120 if clsname: |
| 121 modulename += '.' + clsname |
| 122 logger.write('tag function: %s.%s' % (modulename, co.co_name), 1) |
| 123 tagged_codes.append((co, globals)) |
| 124 _psyco.turbo_frame(co) |
| 125 _psyco.turbo_code(co) |
| 126 |
| 127 def tag2bind(): |
| 128 if tagged_codes: |
| 129 if logger: |
| 130 logger.write('profiling stopped, binding %d functions' % |
| 131 len(tagged_codes), 2) |
| 132 for co, globals in tagged_codes: |
| 133 trytobind(co, globals, 0) |
| 134 function_cache.clear() |
| 135 del tagged_codes[:] |
| 136 |
| 137 |
| 138 class Profiler: |
| 139 MemoryTimerResolution = 0.103 |
| 140 |
| 141 def run(self, memory, time, memorymax, timemax): |
| 142 self.memory = memory |
| 143 self.memorymax = memorymax |
| 144 self.time = time |
| 145 if timemax is None: |
| 146 self.endtime = None |
| 147 else: |
| 148 self.endtime = now() + timemax |
| 149 self.alarms = [] |
| 150 profilers.append(self) |
| 151 go() |
| 152 |
| 153 def start(self): |
| 154 curmem = _psyco.memory() |
| 155 memlimits = [] |
| 156 if self.memorymax is not None: |
| 157 if curmem >= self.memorymax: |
| 158 if logger: |
| 159 logger.writememory() |
| 160 return self.limitreached('memorymax') |
| 161 memlimits.append(self.memorymax) |
| 162 if self.memory is not None: |
| 163 if self.memory <= 0: |
| 164 if logger: |
| 165 logger.writememory() |
| 166 return self.limitreached('memory') |
| 167 memlimits.append(curmem + self.memory) |
| 168 self.memory_at_start = curmem |
| 169 |
| 170 curtime = now() |
| 171 timelimits = [] |
| 172 if self.endtime is not None: |
| 173 if curtime >= self.endtime: |
| 174 return self.limitreached('timemax') |
| 175 timelimits.append(self.endtime - curtime) |
| 176 if self.time is not None: |
| 177 if self.time <= 0.0: |
| 178 return self.limitreached('time') |
| 179 timelimits.append(self.time) |
| 180 self.time_at_start = curtime |
| 181 |
| 182 try: |
| 183 self.do_start() |
| 184 except error, e: |
| 185 if logger: |
| 186 logger.write('%s: disabled by psyco.error:' % ( |
| 187 self.__class__.__name__), 4) |
| 188 logger.write(' %s' % str(e), 3) |
| 189 return 0 |
| 190 |
| 191 if memlimits: |
| 192 self.memlimits_args = (time.sleep, (self.MemoryTimerResolution,), |
| 193 self.check_memory, (min(memlimits),)) |
| 194 self.alarms.append(_psyco.alarm(*self.memlimits_args)) |
| 195 if timelimits: |
| 196 self.alarms.append(_psyco.alarm(time.sleep, (min(timelimits),), |
| 197 self.time_out)) |
| 198 return 1 |
| 199 |
| 200 def stop(self): |
| 201 for alarm in self.alarms: |
| 202 alarm.stop(0) |
| 203 for alarm in self.alarms: |
| 204 alarm.stop(1) # wait for parallel threads to stop |
| 205 del self.alarms[:] |
| 206 if self.time is not None: |
| 207 self.time -= now() - self.time_at_start |
| 208 if self.memory is not None: |
| 209 self.memory -= _psyco.memory() - self.memory_at_start |
| 210 |
| 211 try: |
| 212 self.do_stop() |
| 213 except error: |
| 214 return 0 |
| 215 return 1 |
| 216 |
| 217 def check_memory(self, limit): |
| 218 if _psyco.memory() < limit: |
| 219 return self.memlimits_args |
| 220 go() |
| 221 |
| 222 def time_out(self): |
| 223 self.time = 0.0 |
| 224 go() |
| 225 |
| 226 def limitreached(self, limitname): |
| 227 try: |
| 228 profilers.remove(self) |
| 229 except ValueError: |
| 230 pass |
| 231 if logger: |
| 232 logger.write('%s: disabled (%s limit reached)' % ( |
| 233 self.__class__.__name__, limitname), 4) |
| 234 return 0 |
| 235 |
| 236 |
| 237 class FullCompiler(Profiler): |
| 238 |
| 239 def do_start(self): |
| 240 _psyco.profiling('f') |
| 241 |
| 242 def do_stop(self): |
| 243 _psyco.profiling('.') |
| 244 |
| 245 |
| 246 class RunOnly(Profiler): |
| 247 |
| 248 def do_start(self): |
| 249 _psyco.profiling('n') |
| 250 |
| 251 def do_stop(self): |
| 252 _psyco.profiling('.') |
| 253 |
| 254 |
| 255 class ChargeProfiler(Profiler): |
| 256 |
| 257 def __init__(self, watermark, parentframe): |
| 258 self.watermark = watermark |
| 259 self.parent2 = parentframe * 2.0 |
| 260 self.lock = thread.allocate_lock() |
| 261 |
| 262 def init_charges(self): |
| 263 _psyco.statwrite(watermark = self.watermark, |
| 264 parent2 = self.parent2) |
| 265 |
| 266 def do_stop(self): |
| 267 _psyco.profiling('.') |
| 268 _psyco.statwrite(callback = None) |
| 269 |
| 270 |
| 271 class ActiveProfiler(ChargeProfiler): |
| 272 |
| 273 def active_start(self): |
| 274 _psyco.profiling('p') |
| 275 |
| 276 def do_start(self): |
| 277 self.init_charges() |
| 278 self.active_start() |
| 279 _psyco.statwrite(callback = self.charge_callback) |
| 280 |
| 281 def charge_callback(self, frame, charge): |
| 282 tag(frame.f_code, frame.f_globals) |
| 283 |
| 284 |
| 285 class PassiveProfiler(ChargeProfiler): |
| 286 |
| 287 initial_charge_unit = _psyco.statread('unit') |
| 288 reset_stats_after = 120 # half-lives (maximum 200!) |
| 289 reset_limit = initial_charge_unit * (2.0 ** reset_stats_after) |
| 290 |
| 291 def __init__(self, watermark, halflife, pollfreq, parentframe): |
| 292 ChargeProfiler.__init__(self, watermark, parentframe) |
| 293 self.pollfreq = pollfreq |
| 294 # self.progress is slightly more than 1.0, and computed so that |
| 295 # do_profile() will double the change_unit every 'halflife' seconds. |
| 296 self.progress = 2.0 ** (1.0 / (halflife * pollfreq)) |
| 297 |
| 298 def reset(self): |
| 299 _psyco.statwrite(unit = self.initial_charge_unit, callback = None) |
| 300 _psyco.statreset() |
| 301 if logger: |
| 302 logger.write("%s: resetting stats" % self.__class__.__name__, 1) |
| 303 |
| 304 def passive_start(self): |
| 305 self.passivealarm_args = (time.sleep, (1.0 / self.pollfreq,), |
| 306 self.do_profile) |
| 307 self.alarms.append(_psyco.alarm(*self.passivealarm_args)) |
| 308 |
| 309 def do_start(self): |
| 310 tag2bind() |
| 311 self.init_charges() |
| 312 self.passive_start() |
| 313 |
| 314 def do_profile(self): |
| 315 _psyco.statcollect() |
| 316 if logger: |
| 317 logger.dumpcharges() |
| 318 nunit = _psyco.statread('unit') * self.progress |
| 319 if nunit > self.reset_limit: |
| 320 self.reset() |
| 321 else: |
| 322 _psyco.statwrite(unit = nunit, callback = self.charge_callback) |
| 323 return self.passivealarm_args |
| 324 |
| 325 def charge_callback(self, frame, charge): |
| 326 trytobind(frame.f_code, frame.f_globals) |
| 327 |
| 328 |
| 329 class ActivePassiveProfiler(PassiveProfiler, ActiveProfiler): |
| 330 |
| 331 def do_start(self): |
| 332 self.init_charges() |
| 333 self.active_start() |
| 334 self.passive_start() |
| 335 |
| 336 def charge_callback(self, frame, charge): |
| 337 tag(frame.f_code, frame.f_globals) |
| 338 |
| 339 |
| 340 |
| 341 # |
| 342 # we register our own version of sys.settrace(), sys.setprofile() |
| 343 # and thread.start_new_thread(). |
| 344 # |
| 345 |
| 346 def psyco_settrace(*args, **kw): |
| 347 "This is the Psyco-aware version of sys.settrace()." |
| 348 result = original_settrace(*args, **kw) |
| 349 go() |
| 350 return result |
| 351 |
| 352 def psyco_setprofile(*args, **kw): |
| 353 "This is the Psyco-aware version of sys.setprofile()." |
| 354 result = original_setprofile(*args, **kw) |
| 355 go() |
| 356 return result |
| 357 |
| 358 def psyco_thread_stub(callable, args, kw): |
| 359 _psyco.statcollect() |
| 360 if kw is None: |
| 361 return callable(*args) |
| 362 else: |
| 363 return callable(*args, **kw) |
| 364 |
| 365 def psyco_start_new_thread(callable, args, kw=None): |
| 366 "This is the Psyco-aware version of thread.start_new_thread()." |
| 367 return original_start_new_thread(psyco_thread_stub, (callable, args, kw)) |
| 368 |
| 369 original_settrace = sys.settrace |
| 370 original_setprofile = sys.setprofile |
| 371 original_start_new_thread = thread.start_new_thread |
| 372 sys.settrace = psyco_settrace |
| 373 sys.setprofile = psyco_setprofile |
| 374 thread.start_new_thread = psyco_start_new_thread |
| 375 # hack to patch threading._start_new_thread if the module is |
| 376 # already loaded |
| 377 if ('threading' in sys.modules and |
| 378 hasattr(sys.modules['threading'], '_start_new_thread')): |
| 379 sys.modules['threading']._start_new_thread = psyco_start_new_thread |
OLD | NEW |