179
|
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
|