Edgewall Software

Ticket #8507: tracdev.py

File tracdev.py, 8.9 KB (added by Shane Caraveo <shanec@…>, 8 years ago)

tracdev.py contains all the guts for profilers and debuggers

Line 
1import sys, os, time
2
3class cpu_time_exception():
4 def __init__(self):
5 import psutil
6 self.s = time.time()
7 self.pid = os.getpid()
8 self.p = psutil.Process(self.pid)
9 print "cpu time startup %f %f" % (self.p.get_cpu_percent(), self.s,)
10 self.starttrace()
11
12 def __del__(self):
13 print "cpu time end %f %f" % (self.p.get_cpu_percent(), time.time()-self.s,)
14 self.stoptrace()
15
16 def starttrace(self):
17 sys.settrace(self.trace_dispatch)
18
19 def stoptrace(self):
20 sys.settrace(None)
21
22 def trace_dispatch(self, frame, event, arg):
23 import psutil
24 import time, resource
25 c = time.time()
26 if c - self.s > 0.1:
27 usage = self.p.get_cpu_percent()
28 if usage > 49.0:
29 raise Exception("HIGH CPU %f", usage)
30 print >> sys.stderr, "cpu: %d %f total: %f" % (psutil.NUM_CPUS,self.p.get_cpu_percent(),psutil.cpu_percent())
31 print >> sys.stderr, " process times %s" % (self.p.get_cpu_times(),)
32 print >> sys.stderr, " system times %s" % (psutil.cpu_times(),)
33 self.s=c
34
35# Guard the import of cProfile such that 2.4 people without lsprof can still use
36# this script.
37try:
38 from cProfile import Profile
39except ImportError:
40 try:
41 from lsprof import Profile
42 except ImportError:
43 from profile import Profile
44
45class ContextualProfile(Profile):
46 """ A subclass of Profile that adds a context manager for Python
47 2.5 with: statements and a decorator.
48 source: kernprof.py
49 """
50
51 def __init__(self, *args, **kwds):
52 super(ContextualProfile, self).__init__(*args, **kwds)
53 self.enable_count = 0
54
55 def enable_by_count(self, subcalls=True, builtins=True):
56 """ Enable the profiler if it hasn't been enabled before.
57 """
58 if self.enable_count == 0:
59 self.enable(subcalls=subcalls, builtins=builtins)
60 self.enable_count += 1
61
62 def disable_by_count(self):
63 """ Disable the profiler if the number of disable requests matches the
64 number of enable requests.
65 """
66 if self.enable_count > 0:
67 self.enable_count -= 1
68 if self.enable_count == 0:
69 self.disable()
70
71 def __call__(self, func):
72 """ Decorate a function to start the profiler on function entry and stop
73 it on function exit.
74 """
75 def f(*args, **kwds):
76 self.enable_by_count()
77 try:
78 result = func(*args, **kwds)
79 finally:
80 self.disable_by_count()
81 return result
82 f.__name__ = func.__name__
83 f.__doc__ = func.__doc__
84 f.__dict__.update(func.__dict__)
85 return f
86
87 def __enter__(self):
88 self.enable_by_count()
89
90 def __exit__(self, exc_type, exc_val, exc_tb):
91 self.disable_by_count()
92
93# avoid having to remove all @profile decorators if you want to do
94# a quick change to call profiling
95def empty_profiler(f):
96 return f
97
98import __builtin__
99__builtin__.__dict__['profile'] = empty_profiler
100
101class ProfilerHandler():
102 """Callable which profiles the subsequent handlers and outputs cachegrind files.
103
104 In Apache with wsgi:
105
106 SetEnv trac.profile_enabled 0|1
107 # profile_type = line or call
108 SetEnv trac.profile_type call|line
109 # not used with line profiler, sort var is from cProfile
110 SetEnv trac.profile_sort time
111 # run a contextual profile
112 SetEnv trac.profile_builtin 0|1
113 # dump to stderr
114 SetEnv trac.profile_print 0|1
115 # convert to cachegrind (not used with line profiler)
116 SetEnv trac.profile_grind 0|1
117 # where to save profile data
118 SetEnv trac.profile_dir _TRAC_PREFIX/profile
119
120 Be sure apache has permission to write to profile_dir.
121
122 Repeated runs will produce new profile output, remember to clean out
123 the profile directoy on occasion.
124
125
126 based on code from kernprof.py
127 """
128
129 def __init__(self, callme, environ, start_request):
130 self.callme = callme
131 self.environ = environ
132 self.start_request = start_request
133
134 def __call__(self):
135 """
136 Profile this request and output results in a cachegrind compatible format.
137 """
138 import __builtin__
139 try:
140 import lsprofcalltree
141 calltree_enabled = True
142 except ImportError:
143 calltree_enabled = False
144
145 import sys, os, time
146
147 pstat_fn = None
148 cg_fn = None
149 profile_type = self.environ.get('trac.profile_type', 'call')
150 profile_print = int(self.environ.get('trac.profile_print', '0'))
151 profile_sort = self.environ.get('trac.profile_sort', 'time')
152 profile_grind = int(self.environ.get('trac.profile_grind', '0'))
153 profile_builtin = int(self.environ.get('trac.profile_builtin', '0'))
154 profile_data_dir = self.environ.get('trac.profile_dir', None)
155
156 calltree_enabled = calltree_enabled and profile_grind
157
158 if profile_data_dir:
159 count = 1
160 path = self.environ.get('PATH_INFO', '/tmp')
161 if path == '/':
162 path = 'root'
163 path = path.strip("/").replace("/", "_")
164 pid = os.getpid()
165 t = time.time()
166 pstat_fn = os.path.join(profile_data_dir,"prof.out.%s.%d.%d" % (path, pid, t))
167 if calltree_enabled:
168 cg_fn = os.path.join(profile_data_dir,"cachegrind.out.%s.%d.%d" % (path, pid, t))
169
170 if profile_type == 'line':
171 import line_profiler
172 p = prof = line_profiler.LineProfiler()
173 # line profiler aparently needs to be a builtin
174 profile_builtin = True
175 # line profiler has no get_stat, so converting to cachegrind
176 # will not work
177 calltree_enabled = False
178 else:
179 p = prof = ContextualProfile()
180
181 if profile_builtin:
182 p = __builtin__.__dict__.get('profile', None)
183 if not p or p == empty_profiler:
184 p = __builtin__.__dict__['profile'] = prof
185
186 if profile_type == 'line':
187 # reset the profile for the next run
188 for k in p.code_map.keys():
189 p.code_map[k] = {}
190
191 try:
192 if profile_builtin:
193 self._real_call()
194 else:
195 p.runctx('self._real_call()', globals(), locals())
196
197 finally:
198 if profile_print:
199 if profile_type == 'line':
200 # line profiler doesn't support sort
201 p.print_stats()
202 else:
203 p.print_stats(sort=profile_sort)
204
205 if pstat_fn:
206 print >> sys.stderr, "writing profile data to %s" % pstat_fn
207 p.dump_stats(pstat_fn)
208
209 if calltree_enabled:
210 print >> sys.stderr, "writing cachegrind output to %s" % cg_fn
211 k = lsprofcalltree.KCacheGrind(p)
212 data = open(cg_fn, 'w+')
213 k.output(data)
214 data.close()
215 return self.result
216
217 def _real_call(self):
218 """Call the next handler and store its result."""
219 self.result = self.callme(self.environ, self.start_request)
220
221
222class DBGPHandler():
223 """Callable which loads the PyDBGP debugger.
224
225 In Apache with wsgi:
226
227 dbgp.idekey character key for use with dbgp proxy
228 dbgp.host machine client debugger (e.g. Komodo IDE) or proxy runs on
229 dbgp.port port the client debugger or proxy listens on
230 dbgp.breakonexcept only start debugging when an uncaught exception occurs
231 """
232
233 def __init__(self, callme, environ, start_request):
234 self.callme = callme
235 self.environ = environ
236 self.start_request = start_request
237
238 def __call__(self):
239 """
240 Debug this request.
241 """
242 from dbgp import client
243 environ = self.environ
244 idekey = environ.get('dbgp.idekey', '') # dbgp proxy support
245 host = environ.get('dbgp.host', '127.0.0.1')
246 port = int(environ.get('dbgp.port', '9000'))
247 brk = int(environ.get('dbgp.breakonexcept', '0'))
248 if brk:
249 # breaks on uncaught exceptions
250 client.brkOnExcept(host, port, idekey)
251 else:
252 # breaks on the next executed line
253 client.brk(host, port, idekey)
254
255 # we really want to do this, a bug in the current version of
256 # DBGP doesn't return the result from runThread
257 #client = dbgp.client.backendCmd(idekey)
258 #client.stdin_enabled = 0
259 #client.connect(host, port, 'application', [__file__])
260 #
261 ## despite it's name, this is how to run a function, it does not
262 ## start a thread
263 #return client.runThread(self.callme, (self.environ, self.start_request), {})
264
265 return self.callme(self.environ, self.start_request)