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