|
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 (sys.modules.has_key('threading') and |
|
378 hasattr(sys.modules['threading'], '_start_new_thread')): |
|
379 sys.modules['threading']._start_new_thread = psyco_start_new_thread |