##// END OF EJS Templates
statprof: use absolute_imports...
Gregory Szorc -
r30256:2ed0b3f9 default
parent child Browse files
Show More
@@ -1,788 +1,798
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 ## statprof.py
2 ## statprof.py
3 ## Copyright (C) 2012 Bryan O'Sullivan <bos@serpentine.com>
3 ## Copyright (C) 2012 Bryan O'Sullivan <bos@serpentine.com>
4 ## Copyright (C) 2011 Alex Fraser <alex at phatcore dot com>
4 ## Copyright (C) 2011 Alex Fraser <alex at phatcore dot com>
5 ## Copyright (C) 2004,2005 Andy Wingo <wingo at pobox dot com>
5 ## Copyright (C) 2004,2005 Andy Wingo <wingo at pobox dot com>
6 ## Copyright (C) 2001 Rob Browning <rlb at defaultvalue dot org>
6 ## Copyright (C) 2001 Rob Browning <rlb at defaultvalue dot org>
7
7
8 ## This library is free software; you can redistribute it and/or
8 ## This library is free software; you can redistribute it and/or
9 ## modify it under the terms of the GNU Lesser General Public
9 ## modify it under the terms of the GNU Lesser General Public
10 ## License as published by the Free Software Foundation; either
10 ## License as published by the Free Software Foundation; either
11 ## version 2.1 of the License, or (at your option) any later version.
11 ## version 2.1 of the License, or (at your option) any later version.
12 ##
12 ##
13 ## This library is distributed in the hope that it will be useful,
13 ## This library is distributed in the hope that it will be useful,
14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 ## Lesser General Public License for more details.
16 ## Lesser General Public License for more details.
17 ##
17 ##
18 ## You should have received a copy of the GNU Lesser General Public
18 ## You should have received a copy of the GNU Lesser General Public
19 ## License along with this program; if not, contact:
19 ## License along with this program; if not, contact:
20 ##
20 ##
21 ## Free Software Foundation Voice: +1-617-542-5942
21 ## Free Software Foundation Voice: +1-617-542-5942
22 ## 59 Temple Place - Suite 330 Fax: +1-617-542-2652
22 ## 59 Temple Place - Suite 330 Fax: +1-617-542-2652
23 ## Boston, MA 02111-1307, USA gnu@gnu.org
23 ## Boston, MA 02111-1307, USA gnu@gnu.org
24
24
25 """
25 """
26 statprof is intended to be a fairly simple statistical profiler for
26 statprof is intended to be a fairly simple statistical profiler for
27 python. It was ported directly from a statistical profiler for guile,
27 python. It was ported directly from a statistical profiler for guile,
28 also named statprof, available from guile-lib [0].
28 also named statprof, available from guile-lib [0].
29
29
30 [0] http://wingolog.org/software/guile-lib/statprof/
30 [0] http://wingolog.org/software/guile-lib/statprof/
31
31
32 To start profiling, call statprof.start():
32 To start profiling, call statprof.start():
33 >>> start()
33 >>> start()
34
34
35 Then run whatever it is that you want to profile, for example:
35 Then run whatever it is that you want to profile, for example:
36 >>> import test.pystone; test.pystone.pystones()
36 >>> import test.pystone; test.pystone.pystones()
37
37
38 Then stop the profiling and print out the results:
38 Then stop the profiling and print out the results:
39 >>> stop()
39 >>> stop()
40 >>> display()
40 >>> display()
41 % cumulative self
41 % cumulative self
42 time seconds seconds name
42 time seconds seconds name
43 26.72 1.40 0.37 pystone.py:79:Proc0
43 26.72 1.40 0.37 pystone.py:79:Proc0
44 13.79 0.56 0.19 pystone.py:133:Proc1
44 13.79 0.56 0.19 pystone.py:133:Proc1
45 13.79 0.19 0.19 pystone.py:208:Proc8
45 13.79 0.19 0.19 pystone.py:208:Proc8
46 10.34 0.16 0.14 pystone.py:229:Func2
46 10.34 0.16 0.14 pystone.py:229:Func2
47 6.90 0.10 0.10 pystone.py:45:__init__
47 6.90 0.10 0.10 pystone.py:45:__init__
48 4.31 0.16 0.06 pystone.py:53:copy
48 4.31 0.16 0.06 pystone.py:53:copy
49 ...
49 ...
50
50
51 All of the numerical data is statistically approximate. In the
51 All of the numerical data is statistically approximate. In the
52 following column descriptions, and in all of statprof, "time" refers
52 following column descriptions, and in all of statprof, "time" refers
53 to execution time (both user and system), not wall clock time.
53 to execution time (both user and system), not wall clock time.
54
54
55 % time
55 % time
56 The percent of the time spent inside the procedure itself (not
56 The percent of the time spent inside the procedure itself (not
57 counting children).
57 counting children).
58
58
59 cumulative seconds
59 cumulative seconds
60 The total number of seconds spent in the procedure, including
60 The total number of seconds spent in the procedure, including
61 children.
61 children.
62
62
63 self seconds
63 self seconds
64 The total number of seconds spent in the procedure itself (not
64 The total number of seconds spent in the procedure itself (not
65 counting children).
65 counting children).
66
66
67 name
67 name
68 The name of the procedure.
68 The name of the procedure.
69
69
70 By default statprof keeps the data collected from previous runs. If you
70 By default statprof keeps the data collected from previous runs. If you
71 want to clear the collected data, call reset():
71 want to clear the collected data, call reset():
72 >>> reset()
72 >>> reset()
73
73
74 reset() can also be used to change the sampling frequency from the
74 reset() can also be used to change the sampling frequency from the
75 default of 1000 Hz. For example, to tell statprof to sample 50 times a
75 default of 1000 Hz. For example, to tell statprof to sample 50 times a
76 second:
76 second:
77 >>> reset(50)
77 >>> reset(50)
78
78
79 This means that statprof will sample the call stack after every 1/50 of
79 This means that statprof will sample the call stack after every 1/50 of
80 a second of user + system time spent running on behalf of the python
80 a second of user + system time spent running on behalf of the python
81 process. When your process is idle (for example, blocking in a read(),
81 process. When your process is idle (for example, blocking in a read(),
82 as is the case at the listener), the clock does not advance. For this
82 as is the case at the listener), the clock does not advance. For this
83 reason statprof is not currently not suitable for profiling io-bound
83 reason statprof is not currently not suitable for profiling io-bound
84 operations.
84 operations.
85
85
86 The profiler uses the hash of the code object itself to identify the
86 The profiler uses the hash of the code object itself to identify the
87 procedures, so it won't confuse different procedures with the same name.
87 procedures, so it won't confuse different procedures with the same name.
88 They will show up as two different rows in the output.
88 They will show up as two different rows in the output.
89
89
90 Right now the profiler is quite simplistic. I cannot provide
90 Right now the profiler is quite simplistic. I cannot provide
91 call-graphs or other higher level information. What you see in the
91 call-graphs or other higher level information. What you see in the
92 table is pretty much all there is. Patches are welcome :-)
92 table is pretty much all there is. Patches are welcome :-)
93
93
94
94
95 Threading
95 Threading
96 ---------
96 ---------
97
97
98 Because signals only get delivered to the main thread in Python,
98 Because signals only get delivered to the main thread in Python,
99 statprof only profiles the main thread. However because the time
99 statprof only profiles the main thread. However because the time
100 reporting function uses per-process timers, the results can be
100 reporting function uses per-process timers, the results can be
101 significantly off if other threads' work patterns are not similar to the
101 significantly off if other threads' work patterns are not similar to the
102 main thread's work patterns.
102 main thread's work patterns.
103 """
103 """
104 # no-check-code
104 # no-check-code
105 from __future__ import division
105 from __future__ import absolute_import, division
106
106
107 import inspect, json, os, signal, tempfile, sys, getopt, threading
107 import collections
108 import contextlib
109 import getopt
110 import inspect
111 import json
112 import os
113 import signal
114 import sys
115 import tempfile
116 import threading
108 import time
117 import time
109 from collections import defaultdict
118
110 from contextlib import contextmanager
119 defaultdict = collections.defaultdict
120 contextmanager = contextlib.contextmanager
111
121
112 __all__ = ['start', 'stop', 'reset', 'display', 'profile']
122 __all__ = ['start', 'stop', 'reset', 'display', 'profile']
113
123
114 skips = set(["util.py:check", "extensions.py:closure",
124 skips = set(["util.py:check", "extensions.py:closure",
115 "color.py:colorcmd", "dispatch.py:checkargs",
125 "color.py:colorcmd", "dispatch.py:checkargs",
116 "dispatch.py:<lambda>", "dispatch.py:_runcatch",
126 "dispatch.py:<lambda>", "dispatch.py:_runcatch",
117 "dispatch.py:_dispatch", "dispatch.py:_runcommand",
127 "dispatch.py:_dispatch", "dispatch.py:_runcommand",
118 "pager.py:pagecmd", "dispatch.py:run",
128 "pager.py:pagecmd", "dispatch.py:run",
119 "dispatch.py:dispatch", "dispatch.py:runcommand",
129 "dispatch.py:dispatch", "dispatch.py:runcommand",
120 "hg.py:<module>", "evolve.py:warnobserrors",
130 "hg.py:<module>", "evolve.py:warnobserrors",
121 ])
131 ])
122
132
123 ###########################################################################
133 ###########################################################################
124 ## Utils
134 ## Utils
125
135
126 def clock():
136 def clock():
127 times = os.times()
137 times = os.times()
128 return times[0] + times[1]
138 return times[0] + times[1]
129
139
130
140
131 ###########################################################################
141 ###########################################################################
132 ## Collection data structures
142 ## Collection data structures
133
143
134 class ProfileState(object):
144 class ProfileState(object):
135 def __init__(self, frequency=None):
145 def __init__(self, frequency=None):
136 self.reset(frequency)
146 self.reset(frequency)
137
147
138 def reset(self, frequency=None):
148 def reset(self, frequency=None):
139 # total so far
149 # total so far
140 self.accumulated_time = 0.0
150 self.accumulated_time = 0.0
141 # start_time when timer is active
151 # start_time when timer is active
142 self.last_start_time = None
152 self.last_start_time = None
143 # a float
153 # a float
144 if frequency:
154 if frequency:
145 self.sample_interval = 1.0 / frequency
155 self.sample_interval = 1.0 / frequency
146 elif not hasattr(self, 'sample_interval'):
156 elif not hasattr(self, 'sample_interval'):
147 # default to 1000 Hz
157 # default to 1000 Hz
148 self.sample_interval = 1.0 / 1000.0
158 self.sample_interval = 1.0 / 1000.0
149 else:
159 else:
150 # leave the frequency as it was
160 # leave the frequency as it was
151 pass
161 pass
152 self.remaining_prof_time = None
162 self.remaining_prof_time = None
153 # for user start/stop nesting
163 # for user start/stop nesting
154 self.profile_level = 0
164 self.profile_level = 0
155
165
156 self.samples = []
166 self.samples = []
157
167
158 def accumulate_time(self, stop_time):
168 def accumulate_time(self, stop_time):
159 self.accumulated_time += stop_time - self.last_start_time
169 self.accumulated_time += stop_time - self.last_start_time
160
170
161 def seconds_per_sample(self):
171 def seconds_per_sample(self):
162 return self.accumulated_time / len(self.samples)
172 return self.accumulated_time / len(self.samples)
163
173
164 state = ProfileState()
174 state = ProfileState()
165
175
166
176
167 class CodeSite(object):
177 class CodeSite(object):
168 cache = {}
178 cache = {}
169
179
170 __slots__ = ('path', 'lineno', 'function', 'source')
180 __slots__ = ('path', 'lineno', 'function', 'source')
171
181
172 def __init__(self, path, lineno, function):
182 def __init__(self, path, lineno, function):
173 self.path = path
183 self.path = path
174 self.lineno = lineno
184 self.lineno = lineno
175 self.function = function
185 self.function = function
176 self.source = None
186 self.source = None
177
187
178 def __eq__(self, other):
188 def __eq__(self, other):
179 try:
189 try:
180 return (self.lineno == other.lineno and
190 return (self.lineno == other.lineno and
181 self.path == other.path)
191 self.path == other.path)
182 except:
192 except:
183 return False
193 return False
184
194
185 def __hash__(self):
195 def __hash__(self):
186 return hash((self.lineno, self.path))
196 return hash((self.lineno, self.path))
187
197
188 @classmethod
198 @classmethod
189 def get(cls, path, lineno, function):
199 def get(cls, path, lineno, function):
190 k = (path, lineno)
200 k = (path, lineno)
191 try:
201 try:
192 return cls.cache[k]
202 return cls.cache[k]
193 except KeyError:
203 except KeyError:
194 v = cls(path, lineno, function)
204 v = cls(path, lineno, function)
195 cls.cache[k] = v
205 cls.cache[k] = v
196 return v
206 return v
197
207
198 def getsource(self, length):
208 def getsource(self, length):
199 if self.source is None:
209 if self.source is None:
200 lineno = self.lineno - 1
210 lineno = self.lineno - 1
201 fp = None
211 fp = None
202 try:
212 try:
203 fp = open(self.path)
213 fp = open(self.path)
204 for i, line in enumerate(fp):
214 for i, line in enumerate(fp):
205 if i == lineno:
215 if i == lineno:
206 self.source = line.strip()
216 self.source = line.strip()
207 break
217 break
208 except:
218 except:
209 pass
219 pass
210 finally:
220 finally:
211 if fp:
221 if fp:
212 fp.close()
222 fp.close()
213 if self.source is None:
223 if self.source is None:
214 self.source = ''
224 self.source = ''
215
225
216 source = self.source
226 source = self.source
217 if len(source) > length:
227 if len(source) > length:
218 source = source[:(length - 3)] + "..."
228 source = source[:(length - 3)] + "..."
219 return source
229 return source
220
230
221 def filename(self):
231 def filename(self):
222 return os.path.basename(self.path)
232 return os.path.basename(self.path)
223
233
224 class Sample(object):
234 class Sample(object):
225 __slots__ = ('stack', 'time')
235 __slots__ = ('stack', 'time')
226
236
227 def __init__(self, stack, time):
237 def __init__(self, stack, time):
228 self.stack = stack
238 self.stack = stack
229 self.time = time
239 self.time = time
230
240
231 @classmethod
241 @classmethod
232 def from_frame(cls, frame, time):
242 def from_frame(cls, frame, time):
233 stack = []
243 stack = []
234
244
235 while frame:
245 while frame:
236 stack.append(CodeSite.get(frame.f_code.co_filename, frame.f_lineno,
246 stack.append(CodeSite.get(frame.f_code.co_filename, frame.f_lineno,
237 frame.f_code.co_name))
247 frame.f_code.co_name))
238 frame = frame.f_back
248 frame = frame.f_back
239
249
240 return Sample(stack, time)
250 return Sample(stack, time)
241
251
242 ###########################################################################
252 ###########################################################################
243 ## SIGPROF handler
253 ## SIGPROF handler
244
254
245 def profile_signal_handler(signum, frame):
255 def profile_signal_handler(signum, frame):
246 if state.profile_level > 0:
256 if state.profile_level > 0:
247 now = clock()
257 now = clock()
248 state.accumulate_time(now)
258 state.accumulate_time(now)
249
259
250 state.samples.append(Sample.from_frame(frame, state.accumulated_time))
260 state.samples.append(Sample.from_frame(frame, state.accumulated_time))
251
261
252 signal.setitimer(signal.ITIMER_PROF,
262 signal.setitimer(signal.ITIMER_PROF,
253 state.sample_interval, 0.0)
263 state.sample_interval, 0.0)
254 state.last_start_time = now
264 state.last_start_time = now
255
265
256 stopthread = threading.Event()
266 stopthread = threading.Event()
257 def samplerthread(tid):
267 def samplerthread(tid):
258 while not stopthread.is_set():
268 while not stopthread.is_set():
259 now = clock()
269 now = clock()
260 state.accumulate_time(now)
270 state.accumulate_time(now)
261
271
262 frame = sys._current_frames()[tid]
272 frame = sys._current_frames()[tid]
263 state.samples.append(Sample.from_frame(frame, state.accumulated_time))
273 state.samples.append(Sample.from_frame(frame, state.accumulated_time))
264
274
265 state.last_start_time = now
275 state.last_start_time = now
266 time.sleep(state.sample_interval)
276 time.sleep(state.sample_interval)
267
277
268 stopthread.clear()
278 stopthread.clear()
269
279
270 ###########################################################################
280 ###########################################################################
271 ## Profiling API
281 ## Profiling API
272
282
273 def is_active():
283 def is_active():
274 return state.profile_level > 0
284 return state.profile_level > 0
275
285
276 lastmechanism = None
286 lastmechanism = None
277 def start(mechanism='thread'):
287 def start(mechanism='thread'):
278 '''Install the profiling signal handler, and start profiling.'''
288 '''Install the profiling signal handler, and start profiling.'''
279 state.profile_level += 1
289 state.profile_level += 1
280 if state.profile_level == 1:
290 if state.profile_level == 1:
281 state.last_start_time = clock()
291 state.last_start_time = clock()
282 rpt = state.remaining_prof_time
292 rpt = state.remaining_prof_time
283 state.remaining_prof_time = None
293 state.remaining_prof_time = None
284
294
285 global lastmechanism
295 global lastmechanism
286 lastmechanism = mechanism
296 lastmechanism = mechanism
287
297
288 if mechanism == 'signal':
298 if mechanism == 'signal':
289 signal.signal(signal.SIGPROF, profile_signal_handler)
299 signal.signal(signal.SIGPROF, profile_signal_handler)
290 signal.setitimer(signal.ITIMER_PROF,
300 signal.setitimer(signal.ITIMER_PROF,
291 rpt or state.sample_interval, 0.0)
301 rpt or state.sample_interval, 0.0)
292 elif mechanism == 'thread':
302 elif mechanism == 'thread':
293 frame = inspect.currentframe()
303 frame = inspect.currentframe()
294 tid = [k for k, f in sys._current_frames().items() if f == frame][0]
304 tid = [k for k, f in sys._current_frames().items() if f == frame][0]
295 state.thread = threading.Thread(target=samplerthread,
305 state.thread = threading.Thread(target=samplerthread,
296 args=(tid,), name="samplerthread")
306 args=(tid,), name="samplerthread")
297 state.thread.start()
307 state.thread.start()
298
308
299 def stop():
309 def stop():
300 '''Stop profiling, and uninstall the profiling signal handler.'''
310 '''Stop profiling, and uninstall the profiling signal handler.'''
301 state.profile_level -= 1
311 state.profile_level -= 1
302 if state.profile_level == 0:
312 if state.profile_level == 0:
303 if lastmechanism == 'signal':
313 if lastmechanism == 'signal':
304 rpt = signal.setitimer(signal.ITIMER_PROF, 0.0, 0.0)
314 rpt = signal.setitimer(signal.ITIMER_PROF, 0.0, 0.0)
305 signal.signal(signal.SIGPROF, signal.SIG_IGN)
315 signal.signal(signal.SIGPROF, signal.SIG_IGN)
306 state.remaining_prof_time = rpt[0]
316 state.remaining_prof_time = rpt[0]
307 elif lastmechanism == 'thread':
317 elif lastmechanism == 'thread':
308 stopthread.set()
318 stopthread.set()
309 state.thread.join()
319 state.thread.join()
310
320
311 state.accumulate_time(clock())
321 state.accumulate_time(clock())
312 state.last_start_time = None
322 state.last_start_time = None
313 statprofpath = os.environ.get('STATPROF_DEST')
323 statprofpath = os.environ.get('STATPROF_DEST')
314 if statprofpath:
324 if statprofpath:
315 save_data(statprofpath)
325 save_data(statprofpath)
316
326
317 def save_data(path):
327 def save_data(path):
318 with open(path, 'w+') as file:
328 with open(path, 'w+') as file:
319 file.write(str(state.accumulated_time) + '\n')
329 file.write(str(state.accumulated_time) + '\n')
320 for sample in state.samples:
330 for sample in state.samples:
321 time = str(sample.time)
331 time = str(sample.time)
322 stack = sample.stack
332 stack = sample.stack
323 sites = ['\1'.join([s.path, str(s.lineno), s.function])
333 sites = ['\1'.join([s.path, str(s.lineno), s.function])
324 for s in stack]
334 for s in stack]
325 file.write(time + '\0' + '\0'.join(sites) + '\n')
335 file.write(time + '\0' + '\0'.join(sites) + '\n')
326
336
327 def load_data(path):
337 def load_data(path):
328 lines = open(path, 'r').read().splitlines()
338 lines = open(path, 'r').read().splitlines()
329
339
330 state.accumulated_time = float(lines[0])
340 state.accumulated_time = float(lines[0])
331 state.samples = []
341 state.samples = []
332 for line in lines[1:]:
342 for line in lines[1:]:
333 parts = line.split('\0')
343 parts = line.split('\0')
334 time = float(parts[0])
344 time = float(parts[0])
335 rawsites = parts[1:]
345 rawsites = parts[1:]
336 sites = []
346 sites = []
337 for rawsite in rawsites:
347 for rawsite in rawsites:
338 siteparts = rawsite.split('\1')
348 siteparts = rawsite.split('\1')
339 sites.append(CodeSite.get(siteparts[0], int(siteparts[1]),
349 sites.append(CodeSite.get(siteparts[0], int(siteparts[1]),
340 siteparts[2]))
350 siteparts[2]))
341
351
342 state.samples.append(Sample(sites, time))
352 state.samples.append(Sample(sites, time))
343
353
344
354
345
355
346 def reset(frequency=None):
356 def reset(frequency=None):
347 '''Clear out the state of the profiler. Do not call while the
357 '''Clear out the state of the profiler. Do not call while the
348 profiler is running.
358 profiler is running.
349
359
350 The optional frequency argument specifies the number of samples to
360 The optional frequency argument specifies the number of samples to
351 collect per second.'''
361 collect per second.'''
352 assert state.profile_level == 0, "Can't reset() while statprof is running"
362 assert state.profile_level == 0, "Can't reset() while statprof is running"
353 CodeSite.cache.clear()
363 CodeSite.cache.clear()
354 state.reset(frequency)
364 state.reset(frequency)
355
365
356
366
357 @contextmanager
367 @contextmanager
358 def profile():
368 def profile():
359 start()
369 start()
360 try:
370 try:
361 yield
371 yield
362 finally:
372 finally:
363 stop()
373 stop()
364 display()
374 display()
365
375
366
376
367 ###########################################################################
377 ###########################################################################
368 ## Reporting API
378 ## Reporting API
369
379
370 class SiteStats(object):
380 class SiteStats(object):
371 def __init__(self, site):
381 def __init__(self, site):
372 self.site = site
382 self.site = site
373 self.selfcount = 0
383 self.selfcount = 0
374 self.totalcount = 0
384 self.totalcount = 0
375
385
376 def addself(self):
386 def addself(self):
377 self.selfcount += 1
387 self.selfcount += 1
378
388
379 def addtotal(self):
389 def addtotal(self):
380 self.totalcount += 1
390 self.totalcount += 1
381
391
382 def selfpercent(self):
392 def selfpercent(self):
383 return self.selfcount / len(state.samples) * 100
393 return self.selfcount / len(state.samples) * 100
384
394
385 def totalpercent(self):
395 def totalpercent(self):
386 return self.totalcount / len(state.samples) * 100
396 return self.totalcount / len(state.samples) * 100
387
397
388 def selfseconds(self):
398 def selfseconds(self):
389 return self.selfcount * state.seconds_per_sample()
399 return self.selfcount * state.seconds_per_sample()
390
400
391 def totalseconds(self):
401 def totalseconds(self):
392 return self.totalcount * state.seconds_per_sample()
402 return self.totalcount * state.seconds_per_sample()
393
403
394 @classmethod
404 @classmethod
395 def buildstats(cls, samples):
405 def buildstats(cls, samples):
396 stats = {}
406 stats = {}
397
407
398 for sample in samples:
408 for sample in samples:
399 for i, site in enumerate(sample.stack):
409 for i, site in enumerate(sample.stack):
400 sitestat = stats.get(site)
410 sitestat = stats.get(site)
401 if not sitestat:
411 if not sitestat:
402 sitestat = SiteStats(site)
412 sitestat = SiteStats(site)
403 stats[site] = sitestat
413 stats[site] = sitestat
404
414
405 sitestat.addtotal()
415 sitestat.addtotal()
406
416
407 if i == 0:
417 if i == 0:
408 sitestat.addself()
418 sitestat.addself()
409
419
410 return [s for s in stats.itervalues()]
420 return [s for s in stats.itervalues()]
411
421
412 class DisplayFormats:
422 class DisplayFormats:
413 ByLine = 0
423 ByLine = 0
414 ByMethod = 1
424 ByMethod = 1
415 AboutMethod = 2
425 AboutMethod = 2
416 Hotpath = 3
426 Hotpath = 3
417 FlameGraph = 4
427 FlameGraph = 4
418 Json = 5
428 Json = 5
419
429
420 def display(fp=None, format=3, **kwargs):
430 def display(fp=None, format=3, **kwargs):
421 '''Print statistics, either to stdout or the given file object.'''
431 '''Print statistics, either to stdout or the given file object.'''
422
432
423 if fp is None:
433 if fp is None:
424 import sys
434 import sys
425 fp = sys.stdout
435 fp = sys.stdout
426 if len(state.samples) == 0:
436 if len(state.samples) == 0:
427 print >> fp, ('No samples recorded.')
437 print >> fp, ('No samples recorded.')
428 return
438 return
429
439
430 if format == DisplayFormats.ByLine:
440 if format == DisplayFormats.ByLine:
431 display_by_line(fp)
441 display_by_line(fp)
432 elif format == DisplayFormats.ByMethod:
442 elif format == DisplayFormats.ByMethod:
433 display_by_method(fp)
443 display_by_method(fp)
434 elif format == DisplayFormats.AboutMethod:
444 elif format == DisplayFormats.AboutMethod:
435 display_about_method(fp, **kwargs)
445 display_about_method(fp, **kwargs)
436 elif format == DisplayFormats.Hotpath:
446 elif format == DisplayFormats.Hotpath:
437 display_hotpath(fp, **kwargs)
447 display_hotpath(fp, **kwargs)
438 elif format == DisplayFormats.FlameGraph:
448 elif format == DisplayFormats.FlameGraph:
439 write_to_flame(fp, **kwargs)
449 write_to_flame(fp, **kwargs)
440 elif format == DisplayFormats.Json:
450 elif format == DisplayFormats.Json:
441 write_to_json(fp)
451 write_to_json(fp)
442 else:
452 else:
443 raise Exception("Invalid display format")
453 raise Exception("Invalid display format")
444
454
445 if format != DisplayFormats.Json:
455 if format != DisplayFormats.Json:
446 print >> fp, ('---')
456 print >> fp, ('---')
447 print >> fp, ('Sample count: %d' % len(state.samples))
457 print >> fp, ('Sample count: %d' % len(state.samples))
448 print >> fp, ('Total time: %f seconds' % state.accumulated_time)
458 print >> fp, ('Total time: %f seconds' % state.accumulated_time)
449
459
450 def display_by_line(fp):
460 def display_by_line(fp):
451 '''Print the profiler data with each sample line represented
461 '''Print the profiler data with each sample line represented
452 as one row in a table. Sorted by self-time per line.'''
462 as one row in a table. Sorted by self-time per line.'''
453 stats = SiteStats.buildstats(state.samples)
463 stats = SiteStats.buildstats(state.samples)
454 stats.sort(reverse=True, key=lambda x: x.selfseconds())
464 stats.sort(reverse=True, key=lambda x: x.selfseconds())
455
465
456 print >> fp, ('%5.5s %10.10s %7.7s %-8.8s' %
466 print >> fp, ('%5.5s %10.10s %7.7s %-8.8s' %
457 ('% ', 'cumulative', 'self', ''))
467 ('% ', 'cumulative', 'self', ''))
458 print >> fp, ('%5.5s %9.9s %8.8s %-8.8s' %
468 print >> fp, ('%5.5s %9.9s %8.8s %-8.8s' %
459 ("time", "seconds", "seconds", "name"))
469 ("time", "seconds", "seconds", "name"))
460
470
461 for stat in stats:
471 for stat in stats:
462 site = stat.site
472 site = stat.site
463 sitelabel = '%s:%d:%s' % (site.filename(), site.lineno, site.function)
473 sitelabel = '%s:%d:%s' % (site.filename(), site.lineno, site.function)
464 print >> fp, ('%6.2f %9.2f %9.2f %s' % (stat.selfpercent(),
474 print >> fp, ('%6.2f %9.2f %9.2f %s' % (stat.selfpercent(),
465 stat.totalseconds(),
475 stat.totalseconds(),
466 stat.selfseconds(),
476 stat.selfseconds(),
467 sitelabel))
477 sitelabel))
468
478
469 def display_by_method(fp):
479 def display_by_method(fp):
470 '''Print the profiler data with each sample function represented
480 '''Print the profiler data with each sample function represented
471 as one row in a table. Important lines within that function are
481 as one row in a table. Important lines within that function are
472 output as nested rows. Sorted by self-time per line.'''
482 output as nested rows. Sorted by self-time per line.'''
473 print >> fp, ('%5.5s %10.10s %7.7s %-8.8s' %
483 print >> fp, ('%5.5s %10.10s %7.7s %-8.8s' %
474 ('% ', 'cumulative', 'self', ''))
484 ('% ', 'cumulative', 'self', ''))
475 print >> fp, ('%5.5s %9.9s %8.8s %-8.8s' %
485 print >> fp, ('%5.5s %9.9s %8.8s %-8.8s' %
476 ("time", "seconds", "seconds", "name"))
486 ("time", "seconds", "seconds", "name"))
477
487
478 stats = SiteStats.buildstats(state.samples)
488 stats = SiteStats.buildstats(state.samples)
479
489
480 grouped = defaultdict(list)
490 grouped = defaultdict(list)
481 for stat in stats:
491 for stat in stats:
482 grouped[stat.site.filename() + ":" + stat.site.function].append(stat)
492 grouped[stat.site.filename() + ":" + stat.site.function].append(stat)
483
493
484 # compute sums for each function
494 # compute sums for each function
485 functiondata = []
495 functiondata = []
486 for fname, sitestats in grouped.iteritems():
496 for fname, sitestats in grouped.iteritems():
487 total_cum_sec = 0
497 total_cum_sec = 0
488 total_self_sec = 0
498 total_self_sec = 0
489 total_percent = 0
499 total_percent = 0
490 for stat in sitestats:
500 for stat in sitestats:
491 total_cum_sec += stat.totalseconds()
501 total_cum_sec += stat.totalseconds()
492 total_self_sec += stat.selfseconds()
502 total_self_sec += stat.selfseconds()
493 total_percent += stat.selfpercent()
503 total_percent += stat.selfpercent()
494
504
495 functiondata.append((fname,
505 functiondata.append((fname,
496 total_cum_sec,
506 total_cum_sec,
497 total_self_sec,
507 total_self_sec,
498 total_percent,
508 total_percent,
499 sitestats))
509 sitestats))
500
510
501 # sort by total self sec
511 # sort by total self sec
502 functiondata.sort(reverse=True, key=lambda x: x[2])
512 functiondata.sort(reverse=True, key=lambda x: x[2])
503
513
504 for function in functiondata:
514 for function in functiondata:
505 if function[3] < 0.05:
515 if function[3] < 0.05:
506 continue
516 continue
507 print >> fp, ('%6.2f %9.2f %9.2f %s' % (function[3], # total percent
517 print >> fp, ('%6.2f %9.2f %9.2f %s' % (function[3], # total percent
508 function[1], # total cum sec
518 function[1], # total cum sec
509 function[2], # total self sec
519 function[2], # total self sec
510 function[0])) # file:function
520 function[0])) # file:function
511 function[4].sort(reverse=True, key=lambda i: i.selfseconds())
521 function[4].sort(reverse=True, key=lambda i: i.selfseconds())
512 for stat in function[4]:
522 for stat in function[4]:
513 # only show line numbers for significant locations (>1% time spent)
523 # only show line numbers for significant locations (>1% time spent)
514 if stat.selfpercent() > 1:
524 if stat.selfpercent() > 1:
515 source = stat.site.getsource(25)
525 source = stat.site.getsource(25)
516 stattuple = (stat.selfpercent(), stat.selfseconds(),
526 stattuple = (stat.selfpercent(), stat.selfseconds(),
517 stat.site.lineno, source)
527 stat.site.lineno, source)
518
528
519 print >> fp, ('%33.0f%% %6.2f line %s: %s' % (stattuple))
529 print >> fp, ('%33.0f%% %6.2f line %s: %s' % (stattuple))
520
530
521 def display_about_method(fp, function=None, **kwargs):
531 def display_about_method(fp, function=None, **kwargs):
522 if function is None:
532 if function is None:
523 raise Exception("Invalid function")
533 raise Exception("Invalid function")
524
534
525 filename = None
535 filename = None
526 if ':' in function:
536 if ':' in function:
527 filename, function = function.split(':')
537 filename, function = function.split(':')
528
538
529 relevant_samples = 0
539 relevant_samples = 0
530 parents = {}
540 parents = {}
531 children = {}
541 children = {}
532
542
533 for sample in state.samples:
543 for sample in state.samples:
534 for i, site in enumerate(sample.stack):
544 for i, site in enumerate(sample.stack):
535 if site.function == function and (not filename
545 if site.function == function and (not filename
536 or site.filename() == filename):
546 or site.filename() == filename):
537 relevant_samples += 1
547 relevant_samples += 1
538 if i != len(sample.stack) - 1:
548 if i != len(sample.stack) - 1:
539 parent = sample.stack[i + 1]
549 parent = sample.stack[i + 1]
540 if parent in parents:
550 if parent in parents:
541 parents[parent] = parents[parent] + 1
551 parents[parent] = parents[parent] + 1
542 else:
552 else:
543 parents[parent] = 1
553 parents[parent] = 1
544
554
545 if site in children:
555 if site in children:
546 children[site] = children[site] + 1
556 children[site] = children[site] + 1
547 else:
557 else:
548 children[site] = 1
558 children[site] = 1
549
559
550 parents = [(parent, count) for parent, count in parents.iteritems()]
560 parents = [(parent, count) for parent, count in parents.iteritems()]
551 parents.sort(reverse=True, key=lambda x: x[1])
561 parents.sort(reverse=True, key=lambda x: x[1])
552 for parent, count in parents:
562 for parent, count in parents:
553 print >> fp, ('%6.2f%% %s:%s line %s: %s' %
563 print >> fp, ('%6.2f%% %s:%s line %s: %s' %
554 (count / relevant_samples * 100, parent.filename(),
564 (count / relevant_samples * 100, parent.filename(),
555 parent.function, parent.lineno, parent.getsource(50)))
565 parent.function, parent.lineno, parent.getsource(50)))
556
566
557 stats = SiteStats.buildstats(state.samples)
567 stats = SiteStats.buildstats(state.samples)
558 stats = [s for s in stats
568 stats = [s for s in stats
559 if s.site.function == function and
569 if s.site.function == function and
560 (not filename or s.site.filename() == filename)]
570 (not filename or s.site.filename() == filename)]
561
571
562 total_cum_sec = 0
572 total_cum_sec = 0
563 total_self_sec = 0
573 total_self_sec = 0
564 total_self_percent = 0
574 total_self_percent = 0
565 total_cum_percent = 0
575 total_cum_percent = 0
566 for stat in stats:
576 for stat in stats:
567 total_cum_sec += stat.totalseconds()
577 total_cum_sec += stat.totalseconds()
568 total_self_sec += stat.selfseconds()
578 total_self_sec += stat.selfseconds()
569 total_self_percent += stat.selfpercent()
579 total_self_percent += stat.selfpercent()
570 total_cum_percent += stat.totalpercent()
580 total_cum_percent += stat.totalpercent()
571
581
572 print >> fp, (
582 print >> fp, (
573 '\n %s:%s Total: %0.2fs (%0.2f%%) Self: %0.2fs (%0.2f%%)\n' %
583 '\n %s:%s Total: %0.2fs (%0.2f%%) Self: %0.2fs (%0.2f%%)\n' %
574 (
584 (
575 filename or '___',
585 filename or '___',
576 function,
586 function,
577 total_cum_sec,
587 total_cum_sec,
578 total_cum_percent,
588 total_cum_percent,
579 total_self_sec,
589 total_self_sec,
580 total_self_percent
590 total_self_percent
581 ))
591 ))
582
592
583 children = [(child, count) for child, count in children.iteritems()]
593 children = [(child, count) for child, count in children.iteritems()]
584 children.sort(reverse=True, key=lambda x: x[1])
594 children.sort(reverse=True, key=lambda x: x[1])
585 for child, count in children:
595 for child, count in children:
586 print >> fp, (' %6.2f%% line %s: %s' %
596 print >> fp, (' %6.2f%% line %s: %s' %
587 (count / relevant_samples * 100, child.lineno, child.getsource(50)))
597 (count / relevant_samples * 100, child.lineno, child.getsource(50)))
588
598
589 def display_hotpath(fp, limit=0.05, **kwargs):
599 def display_hotpath(fp, limit=0.05, **kwargs):
590 class HotNode(object):
600 class HotNode(object):
591 def __init__(self, site):
601 def __init__(self, site):
592 self.site = site
602 self.site = site
593 self.count = 0
603 self.count = 0
594 self.children = {}
604 self.children = {}
595
605
596 def add(self, stack, time):
606 def add(self, stack, time):
597 self.count += time
607 self.count += time
598 site = stack[0]
608 site = stack[0]
599 child = self.children.get(site)
609 child = self.children.get(site)
600 if not child:
610 if not child:
601 child = HotNode(site)
611 child = HotNode(site)
602 self.children[site] = child
612 self.children[site] = child
603
613
604 if len(stack) > 1:
614 if len(stack) > 1:
605 i = 1
615 i = 1
606 # Skip boiler plate parts of the stack
616 # Skip boiler plate parts of the stack
607 while i < len(stack) and '%s:%s' % (stack[i].filename(), stack[i].function) in skips:
617 while i < len(stack) and '%s:%s' % (stack[i].filename(), stack[i].function) in skips:
608 i += 1
618 i += 1
609 if i < len(stack):
619 if i < len(stack):
610 child.add(stack[i:], time)
620 child.add(stack[i:], time)
611
621
612 root = HotNode(None)
622 root = HotNode(None)
613 lasttime = state.samples[0].time
623 lasttime = state.samples[0].time
614 for sample in state.samples:
624 for sample in state.samples:
615 root.add(sample.stack[::-1], sample.time - lasttime)
625 root.add(sample.stack[::-1], sample.time - lasttime)
616 lasttime = sample.time
626 lasttime = sample.time
617
627
618 def _write(node, depth, multiple_siblings):
628 def _write(node, depth, multiple_siblings):
619 site = node.site
629 site = node.site
620 visiblechildren = [c for c in node.children.itervalues()
630 visiblechildren = [c for c in node.children.itervalues()
621 if c.count >= (limit * root.count)]
631 if c.count >= (limit * root.count)]
622 if site:
632 if site:
623 indent = depth * 2 - 1
633 indent = depth * 2 - 1
624 filename = ''
634 filename = ''
625 function = ''
635 function = ''
626 if len(node.children) > 0:
636 if len(node.children) > 0:
627 childsite = list(node.children.itervalues())[0].site
637 childsite = list(node.children.itervalues())[0].site
628 filename = (childsite.filename() + ':').ljust(15)
638 filename = (childsite.filename() + ':').ljust(15)
629 function = childsite.function
639 function = childsite.function
630
640
631 # lots of string formatting
641 # lots of string formatting
632 listpattern = ''.ljust(indent) +\
642 listpattern = ''.ljust(indent) +\
633 ('\\' if multiple_siblings else '|') +\
643 ('\\' if multiple_siblings else '|') +\
634 ' %4.1f%% %s %s'
644 ' %4.1f%% %s %s'
635 liststring = listpattern % (node.count / root.count * 100,
645 liststring = listpattern % (node.count / root.count * 100,
636 filename, function)
646 filename, function)
637 codepattern = '%' + str(55 - len(liststring)) + 's %s: %s'
647 codepattern = '%' + str(55 - len(liststring)) + 's %s: %s'
638 codestring = codepattern % ('line', site.lineno, site.getsource(30))
648 codestring = codepattern % ('line', site.lineno, site.getsource(30))
639
649
640 finalstring = liststring + codestring
650 finalstring = liststring + codestring
641 childrensamples = sum([c.count for c in node.children.itervalues()])
651 childrensamples = sum([c.count for c in node.children.itervalues()])
642 # Make frames that performed more than 10% of the operation red
652 # Make frames that performed more than 10% of the operation red
643 if node.count - childrensamples > (0.1 * root.count):
653 if node.count - childrensamples > (0.1 * root.count):
644 finalstring = '\033[91m' + finalstring + '\033[0m'
654 finalstring = '\033[91m' + finalstring + '\033[0m'
645 # Make frames that didn't actually perform work dark grey
655 # Make frames that didn't actually perform work dark grey
646 elif node.count - childrensamples == 0:
656 elif node.count - childrensamples == 0:
647 finalstring = '\033[90m' + finalstring + '\033[0m'
657 finalstring = '\033[90m' + finalstring + '\033[0m'
648 print >> fp, finalstring
658 print >> fp, finalstring
649
659
650 newdepth = depth
660 newdepth = depth
651 if len(visiblechildren) > 1 or multiple_siblings:
661 if len(visiblechildren) > 1 or multiple_siblings:
652 newdepth += 1
662 newdepth += 1
653
663
654 visiblechildren.sort(reverse=True, key=lambda x: x.count)
664 visiblechildren.sort(reverse=True, key=lambda x: x.count)
655 for child in visiblechildren:
665 for child in visiblechildren:
656 _write(child, newdepth, len(visiblechildren) > 1)
666 _write(child, newdepth, len(visiblechildren) > 1)
657
667
658 if root.count > 0:
668 if root.count > 0:
659 _write(root, 0, False)
669 _write(root, 0, False)
660
670
661 def write_to_flame(fp, scriptpath=None, outputfile=None, **kwargs):
671 def write_to_flame(fp, scriptpath=None, outputfile=None, **kwargs):
662 if scriptpath is None:
672 if scriptpath is None:
663 scriptpath = os.environ['HOME'] + '/flamegraph.pl'
673 scriptpath = os.environ['HOME'] + '/flamegraph.pl'
664 if not os.path.exists(scriptpath):
674 if not os.path.exists(scriptpath):
665 print >> fp, "error: missing %s" % scriptpath
675 print >> fp, "error: missing %s" % scriptpath
666 print >> fp, "get it here: https://github.com/brendangregg/FlameGraph"
676 print >> fp, "get it here: https://github.com/brendangregg/FlameGraph"
667 return
677 return
668
678
669 fd, path = tempfile.mkstemp()
679 fd, path = tempfile.mkstemp()
670
680
671 file = open(path, "w+")
681 file = open(path, "w+")
672
682
673 lines = {}
683 lines = {}
674 for sample in state.samples:
684 for sample in state.samples:
675 sites = [s.function for s in sample.stack]
685 sites = [s.function for s in sample.stack]
676 sites.reverse()
686 sites.reverse()
677 line = ';'.join(sites)
687 line = ';'.join(sites)
678 if line in lines:
688 if line in lines:
679 lines[line] = lines[line] + 1
689 lines[line] = lines[line] + 1
680 else:
690 else:
681 lines[line] = 1
691 lines[line] = 1
682
692
683 for line, count in lines.iteritems():
693 for line, count in lines.iteritems():
684 file.write("%s %s\n" % (line, count))
694 file.write("%s %s\n" % (line, count))
685
695
686 file.close()
696 file.close()
687
697
688 if outputfile is None:
698 if outputfile is None:
689 outputfile = '~/flamegraph.svg'
699 outputfile = '~/flamegraph.svg'
690
700
691 os.system("perl ~/flamegraph.pl %s > %s" % (path, outputfile))
701 os.system("perl ~/flamegraph.pl %s > %s" % (path, outputfile))
692 print "Written to %s" % outputfile
702 print "Written to %s" % outputfile
693
703
694 def write_to_json(fp):
704 def write_to_json(fp):
695 samples = []
705 samples = []
696
706
697 for sample in state.samples:
707 for sample in state.samples:
698 stack = []
708 stack = []
699
709
700 for frame in sample.stack:
710 for frame in sample.stack:
701 stack.append((frame.path, frame.lineno, frame.function))
711 stack.append((frame.path, frame.lineno, frame.function))
702
712
703 samples.append((sample.time, stack))
713 samples.append((sample.time, stack))
704
714
705 print >> fp, json.dumps(samples)
715 print >> fp, json.dumps(samples)
706
716
707 def printusage():
717 def printusage():
708 print """
718 print """
709 The statprof command line allows you to inspect the last profile's results in
719 The statprof command line allows you to inspect the last profile's results in
710 the following forms:
720 the following forms:
711
721
712 usage:
722 usage:
713 hotpath [-l --limit percent]
723 hotpath [-l --limit percent]
714 Shows a graph of calls with the percent of time each takes.
724 Shows a graph of calls with the percent of time each takes.
715 Red calls take over 10%% of the total time themselves.
725 Red calls take over 10%% of the total time themselves.
716 lines
726 lines
717 Shows the actual sampled lines.
727 Shows the actual sampled lines.
718 functions
728 functions
719 Shows the samples grouped by function.
729 Shows the samples grouped by function.
720 function [filename:]functionname
730 function [filename:]functionname
721 Shows the callers and callees of a particular function.
731 Shows the callers and callees of a particular function.
722 flame [-s --script-path] [-o --output-file path]
732 flame [-s --script-path] [-o --output-file path]
723 Writes out a flamegraph to output-file (defaults to ~/flamegraph.svg)
733 Writes out a flamegraph to output-file (defaults to ~/flamegraph.svg)
724 Requires that ~/flamegraph.pl exist.
734 Requires that ~/flamegraph.pl exist.
725 (Specify alternate script path with --script-path.)"""
735 (Specify alternate script path with --script-path.)"""
726
736
727 def main(argv=None):
737 def main(argv=None):
728 if argv is None:
738 if argv is None:
729 argv = sys.argv
739 argv = sys.argv
730
740
731 if len(argv) == 1:
741 if len(argv) == 1:
732 printusage()
742 printusage()
733 return 0
743 return 0
734
744
735 displayargs = {}
745 displayargs = {}
736
746
737 optstart = 2
747 optstart = 2
738 displayargs['function'] = None
748 displayargs['function'] = None
739 if argv[1] == 'hotpath':
749 if argv[1] == 'hotpath':
740 displayargs['format'] = DisplayFormats.Hotpath
750 displayargs['format'] = DisplayFormats.Hotpath
741 elif argv[1] == 'lines':
751 elif argv[1] == 'lines':
742 displayargs['format'] = DisplayFormats.ByLine
752 displayargs['format'] = DisplayFormats.ByLine
743 elif argv[1] == 'functions':
753 elif argv[1] == 'functions':
744 displayargs['format'] = DisplayFormats.ByMethod
754 displayargs['format'] = DisplayFormats.ByMethod
745 elif argv[1] == 'function':
755 elif argv[1] == 'function':
746 displayargs['format'] = DisplayFormats.AboutMethod
756 displayargs['format'] = DisplayFormats.AboutMethod
747 displayargs['function'] = argv[2]
757 displayargs['function'] = argv[2]
748 optstart = 3
758 optstart = 3
749 elif argv[1] == 'flame':
759 elif argv[1] == 'flame':
750 displayargs['format'] = DisplayFormats.FlameGraph
760 displayargs['format'] = DisplayFormats.FlameGraph
751 else:
761 else:
752 printusage()
762 printusage()
753 return 0
763 return 0
754
764
755 # process options
765 # process options
756 try:
766 try:
757 opts, args = getopt.getopt(sys.argv[optstart:], "hl:f:o:p:",
767 opts, args = getopt.getopt(sys.argv[optstart:], "hl:f:o:p:",
758 ["help", "limit=", "file=", "output-file=", "script-path="])
768 ["help", "limit=", "file=", "output-file=", "script-path="])
759 except getopt.error as msg:
769 except getopt.error as msg:
760 print msg
770 print msg
761 printusage()
771 printusage()
762 return 2
772 return 2
763
773
764 displayargs['limit'] = 0.05
774 displayargs['limit'] = 0.05
765 path = None
775 path = None
766 for o, value in opts:
776 for o, value in opts:
767 if o in ("-l", "--limit"):
777 if o in ("-l", "--limit"):
768 displayargs['limit'] = float(value)
778 displayargs['limit'] = float(value)
769 elif o in ("-f", "--file"):
779 elif o in ("-f", "--file"):
770 path = value
780 path = value
771 elif o in ("-o", "--output-file"):
781 elif o in ("-o", "--output-file"):
772 displayargs['outputfile'] = value
782 displayargs['outputfile'] = value
773 elif o in ("-p", "--script-path"):
783 elif o in ("-p", "--script-path"):
774 displayargs['scriptpath'] = value
784 displayargs['scriptpath'] = value
775 elif o in ("-h", "help"):
785 elif o in ("-h", "help"):
776 printusage()
786 printusage()
777 return 0
787 return 0
778 else:
788 else:
779 assert False, "unhandled option %s" % o
789 assert False, "unhandled option %s" % o
780
790
781 load_data(path=path)
791 load_data(path=path)
782
792
783 display(**displayargs)
793 display(**displayargs)
784
794
785 return 0
795 return 0
786
796
787 if __name__ == "__main__":
797 if __name__ == "__main__":
788 sys.exit(main())
798 sys.exit(main())
@@ -1,37 +1,36
1 #require test-repo
1 #require test-repo
2
2
3 $ . "$TESTDIR/helpers-testrepo.sh"
3 $ . "$TESTDIR/helpers-testrepo.sh"
4 $ cd "$TESTDIR"/..
4 $ cd "$TESTDIR"/..
5
5
6 $ hg files 'set:(**.py)' | sed 's|\\|/|g' | xargs python contrib/check-py3-compat.py
6 $ hg files 'set:(**.py)' | sed 's|\\|/|g' | xargs python contrib/check-py3-compat.py
7 hgext/fsmonitor/pywatchman/__init__.py not using absolute_import
7 hgext/fsmonitor/pywatchman/__init__.py not using absolute_import
8 hgext/fsmonitor/pywatchman/__init__.py requires print_function
8 hgext/fsmonitor/pywatchman/__init__.py requires print_function
9 hgext/fsmonitor/pywatchman/capabilities.py not using absolute_import
9 hgext/fsmonitor/pywatchman/capabilities.py not using absolute_import
10 hgext/fsmonitor/pywatchman/pybser.py not using absolute_import
10 hgext/fsmonitor/pywatchman/pybser.py not using absolute_import
11 i18n/check-translation.py not using absolute_import
11 i18n/check-translation.py not using absolute_import
12 mercurial/statprof.py not using absolute_import
13 mercurial/statprof.py requires print_function
12 mercurial/statprof.py requires print_function
14 setup.py not using absolute_import
13 setup.py not using absolute_import
15 tests/test-demandimport.py not using absolute_import
14 tests/test-demandimport.py not using absolute_import
16
15
17 #if py3exe
16 #if py3exe
18 $ hg files 'set:(**.py) - grep(pygments)' | sed 's|\\|/|g' \
17 $ hg files 'set:(**.py) - grep(pygments)' | sed 's|\\|/|g' \
19 > | xargs $PYTHON3 contrib/check-py3-compat.py \
18 > | xargs $PYTHON3 contrib/check-py3-compat.py \
20 > | sed 's/[0-9][0-9]*)$/*)/'
19 > | sed 's/[0-9][0-9]*)$/*)/'
21 hgext/convert/transport.py: error importing: <ImportError> No module named 'svn.client' (error at transport.py:*)
20 hgext/convert/transport.py: error importing: <ImportError> No module named 'svn.client' (error at transport.py:*)
22 hgext/fsmonitor/pywatchman/capabilities.py: error importing: <ImportError> No module named 'pybser' (error at __init__.py:*)
21 hgext/fsmonitor/pywatchman/capabilities.py: error importing: <ImportError> No module named 'pybser' (error at __init__.py:*)
23 hgext/fsmonitor/pywatchman/pybser.py: error importing: <ImportError> No module named 'pybser' (error at __init__.py:*)
22 hgext/fsmonitor/pywatchman/pybser.py: error importing: <ImportError> No module named 'pybser' (error at __init__.py:*)
24 hgext/fsmonitor/watchmanclient.py: error importing: <ImportError> No module named 'pybser' (error at __init__.py:*)
23 hgext/fsmonitor/watchmanclient.py: error importing: <ImportError> No module named 'pybser' (error at __init__.py:*)
25 hgext/mq.py: error importing: <TypeError> __import__() argument 1 must be str, not bytes (error at extensions.py:*)
24 hgext/mq.py: error importing: <TypeError> __import__() argument 1 must be str, not bytes (error at extensions.py:*)
26 mercurial/scmwindows.py: error importing: <ImportError> No module named 'winreg' (error at scmwindows.py:*)
25 mercurial/scmwindows.py: error importing: <ImportError> No module named 'winreg' (error at scmwindows.py:*)
27 mercurial/statprof.py: invalid syntax: Missing parentheses in call to 'print' (<unknown>, line *)
26 mercurial/statprof.py: invalid syntax: Missing parentheses in call to 'print' (<unknown>, line *)
28 mercurial/win32.py: error importing: <ImportError> No module named 'msvcrt' (error at win32.py:*)
27 mercurial/win32.py: error importing: <ImportError> No module named 'msvcrt' (error at win32.py:*)
29 mercurial/windows.py: error importing: <ImportError> No module named 'msvcrt' (error at windows.py:*)
28 mercurial/windows.py: error importing: <ImportError> No module named 'msvcrt' (error at windows.py:*)
30
29
31 #endif
30 #endif
32
31
33 #if py3exe py3pygments
32 #if py3exe py3pygments
34 $ hg files 'set:(**.py) and grep(pygments)' | sed 's|\\|/|g' \
33 $ hg files 'set:(**.py) and grep(pygments)' | sed 's|\\|/|g' \
35 > | xargs $PYTHON3 contrib/check-py3-compat.py \
34 > | xargs $PYTHON3 contrib/check-py3-compat.py \
36 > | sed 's/[0-9][0-9]*)$/*)/'
35 > | sed 's/[0-9][0-9]*)$/*)/'
37 #endif
36 #endif
General Comments 0
You need to be logged in to leave comments. Login now