##// END OF EJS Templates
statprof: fix flake8 warnings...
Gregory Szorc -
r30254:ad4c0236 default
parent child Browse files
Show More
@@ -1,797 +1,796 b''
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 105 from __future__ import division
106 106
107 import inspect, json, os, signal, tempfile, sys, getopt, threading, traceback
107 import inspect, json, os, signal, tempfile, sys, getopt, threading
108 108 import time
109 109 from collections import defaultdict
110 110 from contextlib import contextmanager
111 from subprocess import call
112 111
113 112 __all__ = ['start', 'stop', 'reset', 'display', 'profile']
114 113
115 114 skips = set(["util.py:check", "extensions.py:closure",
116 115 "color.py:colorcmd", "dispatch.py:checkargs",
117 116 "dispatch.py:<lambda>", "dispatch.py:_runcatch",
118 117 "dispatch.py:_dispatch", "dispatch.py:_runcommand",
119 118 "pager.py:pagecmd", "dispatch.py:run",
120 119 "dispatch.py:dispatch", "dispatch.py:runcommand",
121 120 "hg.py:<module>", "evolve.py:warnobserrors",
122 121 ])
123 122
124 123 ###########################################################################
125 124 ## Utils
126 125
127 126 def clock():
128 127 times = os.times()
129 128 return times[0] + times[1]
130 129
131 130
132 131 ###########################################################################
133 132 ## Collection data structures
134 133
135 134 class ProfileState(object):
136 135 def __init__(self, frequency=None):
137 136 self.reset(frequency)
138 137
139 138 def reset(self, frequency=None):
140 139 # total so far
141 140 self.accumulated_time = 0.0
142 141 # start_time when timer is active
143 142 self.last_start_time = None
144 143 # a float
145 144 if frequency:
146 145 self.sample_interval = 1.0 / frequency
147 146 elif not hasattr(self, 'sample_interval'):
148 147 # default to 1000 Hz
149 148 self.sample_interval = 1.0 / 1000.0
150 149 else:
151 150 # leave the frequency as it was
152 151 pass
153 152 self.remaining_prof_time = None
154 153 # for user start/stop nesting
155 154 self.profile_level = 0
156 155
157 156 self.samples = []
158 157
159 158 def accumulate_time(self, stop_time):
160 159 self.accumulated_time += stop_time - self.last_start_time
161 160
162 161 def seconds_per_sample(self):
163 162 return self.accumulated_time / len(self.samples)
164 163
165 164 state = ProfileState()
166 165
167 166
168 167 class CodeSite(object):
169 168 cache = {}
170 169
171 170 __slots__ = ('path', 'lineno', 'function', 'source')
172 171
173 172 def __init__(self, path, lineno, function):
174 173 self.path = path
175 174 self.lineno = lineno
176 175 self.function = function
177 176 self.source = None
178 177
179 178 def __eq__(self, other):
180 179 try:
181 180 return (self.lineno == other.lineno and
182 181 self.path == other.path)
183 182 except:
184 183 return False
185 184
186 185 def __hash__(self):
187 186 return hash((self.lineno, self.path))
188 187
189 188 @classmethod
190 189 def get(cls, path, lineno, function):
191 190 k = (path, lineno)
192 191 try:
193 192 return cls.cache[k]
194 193 except KeyError:
195 194 v = cls(path, lineno, function)
196 195 cls.cache[k] = v
197 196 return v
198 197
199 198 def getsource(self, length):
200 199 if self.source is None:
201 200 lineno = self.lineno - 1
202 201 fp = None
203 202 try:
204 203 fp = open(self.path)
205 204 for i, line in enumerate(fp):
206 205 if i == lineno:
207 206 self.source = line.strip()
208 207 break
209 208 except:
210 209 pass
211 210 finally:
212 211 if fp:
213 212 fp.close()
214 213 if self.source is None:
215 214 self.source = ''
216 215
217 216 source = self.source
218 217 if len(source) > length:
219 218 source = source[:(length - 3)] + "..."
220 219 return source
221 220
222 221 def filename(self):
223 222 return os.path.basename(self.path)
224 223
225 224 class Sample(object):
226 225 __slots__ = ('stack', 'time')
227 226
228 227 def __init__(self, stack, time):
229 228 self.stack = stack
230 229 self.time = time
231 230
232 231 @classmethod
233 232 def from_frame(cls, frame, time):
234 233 stack = []
235 234
236 235 while frame:
237 236 stack.append(CodeSite.get(frame.f_code.co_filename, frame.f_lineno,
238 237 frame.f_code.co_name))
239 238 frame = frame.f_back
240 239
241 240 return Sample(stack, time)
242 241
243 242 ###########################################################################
244 243 ## SIGPROF handler
245 244
246 245 def profile_signal_handler(signum, frame):
247 246 if state.profile_level > 0:
248 247 now = clock()
249 248 state.accumulate_time(now)
250 249
251 250 state.samples.append(Sample.from_frame(frame, state.accumulated_time))
252 251
253 252 signal.setitimer(signal.ITIMER_PROF,
254 253 state.sample_interval, 0.0)
255 254 state.last_start_time = now
256 255
257 256 stopthread = threading.Event()
258 257 def samplerthread(tid):
259 258 while not stopthread.is_set():
260 259 now = clock()
261 260 state.accumulate_time(now)
262 261
263 262 frame = sys._current_frames()[tid]
264 263 state.samples.append(Sample.from_frame(frame, state.accumulated_time))
265 264
266 265 state.last_start_time = now
267 266 time.sleep(state.sample_interval)
268 267
269 268 stopthread.clear()
270 269
271 270 ###########################################################################
272 271 ## Profiling API
273 272
274 273 def is_active():
275 274 return state.profile_level > 0
276 275
277 276 lastmechanism = None
278 277 def start(mechanism='thread'):
279 278 '''Install the profiling signal handler, and start profiling.'''
280 279 state.profile_level += 1
281 280 if state.profile_level == 1:
282 281 state.last_start_time = clock()
283 282 rpt = state.remaining_prof_time
284 283 state.remaining_prof_time = None
285 284
286 285 global lastmechanism
287 286 lastmechanism = mechanism
288 287
289 288 if mechanism == 'signal':
290 289 signal.signal(signal.SIGPROF, profile_signal_handler)
291 290 signal.setitimer(signal.ITIMER_PROF,
292 291 rpt or state.sample_interval, 0.0)
293 292 elif mechanism == 'thread':
294 293 frame = inspect.currentframe()
295 294 tid = [k for k, f in sys._current_frames().items() if f == frame][0]
296 295 state.thread = threading.Thread(target=samplerthread,
297 296 args=(tid,), name="samplerthread")
298 297 state.thread.start()
299 298
300 299 def stop():
301 300 '''Stop profiling, and uninstall the profiling signal handler.'''
302 301 state.profile_level -= 1
303 302 if state.profile_level == 0:
304 303 if lastmechanism == 'signal':
305 304 rpt = signal.setitimer(signal.ITIMER_PROF, 0.0, 0.0)
306 305 signal.signal(signal.SIGPROF, signal.SIG_IGN)
307 306 state.remaining_prof_time = rpt[0]
308 307 elif lastmechanism == 'thread':
309 308 stopthread.set()
310 309 state.thread.join()
311 310
312 311 state.accumulate_time(clock())
313 312 state.last_start_time = None
314 313 statprofpath = os.environ.get('STATPROF_DEST')
315 314 save_data(statprofpath)
316 315
317 316 def save_data(path=None):
318 317 try:
319 318 path = path or (os.environ['HOME'] + '/statprof.data')
320 319 file = open(path, "w+")
321 320
322 321 file.write(str(state.accumulated_time) + '\n')
323 322 for sample in state.samples:
324 323 time = str(sample.time)
325 324 stack = sample.stack
326 325 sites = ['\1'.join([s.path, str(s.lineno), s.function])
327 326 for s in stack]
328 327 file.write(time + '\0' + '\0'.join(sites) + '\n')
329 328
330 329 file.close()
331 except (IOError, OSError) as ex:
330 except (IOError, OSError):
332 331 # The home directory probably didn't exist, or wasn't writable. Oh well.
333 332 pass
334 333
335 334 def load_data(path=None):
336 335 path = path or (os.environ['HOME'] + '/statprof.data')
337 336 lines = open(path, 'r').read().splitlines()
338 337
339 338 state.accumulated_time = float(lines[0])
340 339 state.samples = []
341 340 for line in lines[1:]:
342 341 parts = line.split('\0')
343 342 time = float(parts[0])
344 343 rawsites = parts[1:]
345 344 sites = []
346 345 for rawsite in rawsites:
347 346 siteparts = rawsite.split('\1')
348 347 sites.append(CodeSite.get(siteparts[0], int(siteparts[1]),
349 348 siteparts[2]))
350 349
351 350 state.samples.append(Sample(sites, time))
352 351
353 352
354 353
355 354 def reset(frequency=None):
356 355 '''Clear out the state of the profiler. Do not call while the
357 356 profiler is running.
358 357
359 358 The optional frequency argument specifies the number of samples to
360 359 collect per second.'''
361 360 assert state.profile_level == 0, "Can't reset() while statprof is running"
362 361 CodeSite.cache.clear()
363 362 state.reset(frequency)
364 363
365 364
366 365 @contextmanager
367 366 def profile():
368 367 start()
369 368 try:
370 369 yield
371 370 finally:
372 371 stop()
373 372 display()
374 373
375 374
376 375 ###########################################################################
377 376 ## Reporting API
378 377
379 378 class SiteStats(object):
380 379 def __init__(self, site):
381 380 self.site = site
382 381 self.selfcount = 0
383 382 self.totalcount = 0
384 383
385 384 def addself(self):
386 385 self.selfcount += 1
387 386
388 387 def addtotal(self):
389 388 self.totalcount += 1
390 389
391 390 def selfpercent(self):
392 391 return self.selfcount / len(state.samples) * 100
393 392
394 393 def totalpercent(self):
395 394 return self.totalcount / len(state.samples) * 100
396 395
397 396 def selfseconds(self):
398 397 return self.selfcount * state.seconds_per_sample()
399 398
400 399 def totalseconds(self):
401 400 return self.totalcount * state.seconds_per_sample()
402 401
403 402 @classmethod
404 403 def buildstats(cls, samples):
405 404 stats = {}
406 405
407 406 for sample in samples:
408 407 for i, site in enumerate(sample.stack):
409 408 sitestat = stats.get(site)
410 409 if not sitestat:
411 410 sitestat = SiteStats(site)
412 411 stats[site] = sitestat
413 412
414 413 sitestat.addtotal()
415 414
416 415 if i == 0:
417 416 sitestat.addself()
418 417
419 418 return [s for s in stats.itervalues()]
420 419
421 420 class DisplayFormats:
422 421 ByLine = 0
423 422 ByMethod = 1
424 423 AboutMethod = 2
425 424 Hotpath = 3
426 425 FlameGraph = 4
427 426 Json = 5
428 427
429 428 def display(fp=None, format=3, **kwargs):
430 429 '''Print statistics, either to stdout or the given file object.'''
431 430
432 431 if fp is None:
433 432 import sys
434 433 fp = sys.stdout
435 434 if len(state.samples) == 0:
436 435 print >> fp, ('No samples recorded.')
437 436 return
438 437
439 438 if format == DisplayFormats.ByLine:
440 439 display_by_line(fp)
441 440 elif format == DisplayFormats.ByMethod:
442 441 display_by_method(fp)
443 442 elif format == DisplayFormats.AboutMethod:
444 443 display_about_method(fp, **kwargs)
445 444 elif format == DisplayFormats.Hotpath:
446 445 display_hotpath(fp, **kwargs)
447 446 elif format == DisplayFormats.FlameGraph:
448 447 write_to_flame(fp, **kwargs)
449 448 elif format == DisplayFormats.Json:
450 449 write_to_json(fp)
451 450 else:
452 451 raise Exception("Invalid display format")
453 452
454 453 if format != DisplayFormats.Json:
455 454 print >> fp, ('---')
456 455 print >> fp, ('Sample count: %d' % len(state.samples))
457 456 print >> fp, ('Total time: %f seconds' % state.accumulated_time)
458 457
459 458 def display_by_line(fp):
460 459 '''Print the profiler data with each sample line represented
461 460 as one row in a table. Sorted by self-time per line.'''
462 461 stats = SiteStats.buildstats(state.samples)
463 462 stats.sort(reverse=True, key=lambda x: x.selfseconds())
464 463
465 464 print >> fp, ('%5.5s %10.10s %7.7s %-8.8s' %
466 465 ('% ', 'cumulative', 'self', ''))
467 466 print >> fp, ('%5.5s %9.9s %8.8s %-8.8s' %
468 467 ("time", "seconds", "seconds", "name"))
469 468
470 469 for stat in stats:
471 470 site = stat.site
472 471 sitelabel = '%s:%d:%s' % (site.filename(), site.lineno, site.function)
473 472 print >> fp, ('%6.2f %9.2f %9.2f %s' % (stat.selfpercent(),
474 473 stat.totalseconds(),
475 474 stat.selfseconds(),
476 475 sitelabel))
477 476
478 477 def display_by_method(fp):
479 478 '''Print the profiler data with each sample function represented
480 479 as one row in a table. Important lines within that function are
481 480 output as nested rows. Sorted by self-time per line.'''
482 481 print >> fp, ('%5.5s %10.10s %7.7s %-8.8s' %
483 482 ('% ', 'cumulative', 'self', ''))
484 483 print >> fp, ('%5.5s %9.9s %8.8s %-8.8s' %
485 484 ("time", "seconds", "seconds", "name"))
486 485
487 486 stats = SiteStats.buildstats(state.samples)
488 487
489 488 grouped = defaultdict(list)
490 489 for stat in stats:
491 490 grouped[stat.site.filename() + ":" + stat.site.function].append(stat)
492 491
493 492 # compute sums for each function
494 493 functiondata = []
495 494 for fname, sitestats in grouped.iteritems():
496 495 total_cum_sec = 0
497 496 total_self_sec = 0
498 497 total_percent = 0
499 498 for stat in sitestats:
500 499 total_cum_sec += stat.totalseconds()
501 500 total_self_sec += stat.selfseconds()
502 501 total_percent += stat.selfpercent()
503 502
504 503 functiondata.append((fname,
505 504 total_cum_sec,
506 505 total_self_sec,
507 506 total_percent,
508 507 sitestats))
509 508
510 509 # sort by total self sec
511 510 functiondata.sort(reverse=True, key=lambda x: x[2])
512 511
513 512 for function in functiondata:
514 513 if function[3] < 0.05:
515 514 continue
516 515 print >> fp, ('%6.2f %9.2f %9.2f %s' % (function[3], # total percent
517 516 function[1], # total cum sec
518 517 function[2], # total self sec
519 518 function[0])) # file:function
520 519 function[4].sort(reverse=True, key=lambda i: i.selfseconds())
521 520 for stat in function[4]:
522 521 # only show line numbers for significant locations (>1% time spent)
523 522 if stat.selfpercent() > 1:
524 523 source = stat.site.getsource(25)
525 524 stattuple = (stat.selfpercent(), stat.selfseconds(),
526 525 stat.site.lineno, source)
527 526
528 527 print >> fp, ('%33.0f%% %6.2f line %s: %s' % (stattuple))
529 528
530 529 def display_about_method(fp, function=None, **kwargs):
531 530 if function is None:
532 531 raise Exception("Invalid function")
533 532
534 533 filename = None
535 534 if ':' in function:
536 535 filename, function = function.split(':')
537 536
538 537 relevant_samples = 0
539 538 parents = {}
540 539 children = {}
541 540
542 541 for sample in state.samples:
543 542 for i, site in enumerate(sample.stack):
544 543 if site.function == function and (not filename
545 544 or site.filename() == filename):
546 545 relevant_samples += 1
547 546 if i != len(sample.stack) - 1:
548 547 parent = sample.stack[i + 1]
549 548 if parent in parents:
550 549 parents[parent] = parents[parent] + 1
551 550 else:
552 551 parents[parent] = 1
553 552
554 553 if site in children:
555 554 children[site] = children[site] + 1
556 555 else:
557 556 children[site] = 1
558 557
559 558 parents = [(parent, count) for parent, count in parents.iteritems()]
560 559 parents.sort(reverse=True, key=lambda x: x[1])
561 560 for parent, count in parents:
562 561 print >> fp, ('%6.2f%% %s:%s line %s: %s' %
563 562 (count / relevant_samples * 100, parent.filename(),
564 563 parent.function, parent.lineno, parent.getsource(50)))
565 564
566 565 stats = SiteStats.buildstats(state.samples)
567 566 stats = [s for s in stats
568 567 if s.site.function == function and
569 568 (not filename or s.site.filename() == filename)]
570 569
571 570 total_cum_sec = 0
572 571 total_self_sec = 0
573 572 total_self_percent = 0
574 573 total_cum_percent = 0
575 574 for stat in stats:
576 575 total_cum_sec += stat.totalseconds()
577 576 total_self_sec += stat.selfseconds()
578 577 total_self_percent += stat.selfpercent()
579 578 total_cum_percent += stat.totalpercent()
580 579
581 580 print >> fp, (
582 581 '\n %s:%s Total: %0.2fs (%0.2f%%) Self: %0.2fs (%0.2f%%)\n' %
583 582 (
584 583 filename or '___',
585 584 function,
586 585 total_cum_sec,
587 586 total_cum_percent,
588 587 total_self_sec,
589 588 total_self_percent
590 589 ))
591 590
592 591 children = [(child, count) for child, count in children.iteritems()]
593 592 children.sort(reverse=True, key=lambda x: x[1])
594 593 for child, count in children:
595 594 print >> fp, (' %6.2f%% line %s: %s' %
596 595 (count / relevant_samples * 100, child.lineno, child.getsource(50)))
597 596
598 597 def display_hotpath(fp, limit=0.05, **kwargs):
599 598 class HotNode(object):
600 599 def __init__(self, site):
601 600 self.site = site
602 601 self.count = 0
603 602 self.children = {}
604 603
605 604 def add(self, stack, time):
606 605 self.count += time
607 606 site = stack[0]
608 607 child = self.children.get(site)
609 608 if not child:
610 609 child = HotNode(site)
611 610 self.children[site] = child
612 611
613 612 if len(stack) > 1:
614 613 i = 1
615 614 # Skip boiler plate parts of the stack
616 615 while i < len(stack) and '%s:%s' % (stack[i].filename(), stack[i].function) in skips:
617 616 i += 1
618 617 if i < len(stack):
619 618 child.add(stack[i:], time)
620 619
621 620 root = HotNode(None)
622 621 lasttime = state.samples[0].time
623 622 for sample in state.samples:
624 623 root.add(sample.stack[::-1], sample.time - lasttime)
625 624 lasttime = sample.time
626 625
627 626 def _write(node, depth, multiple_siblings):
628 627 site = node.site
629 628 visiblechildren = [c for c in node.children.itervalues()
630 629 if c.count >= (limit * root.count)]
631 630 if site:
632 631 indent = depth * 2 - 1
633 632 filename = ''
634 633 function = ''
635 634 if len(node.children) > 0:
636 635 childsite = list(node.children.itervalues())[0].site
637 636 filename = (childsite.filename() + ':').ljust(15)
638 637 function = childsite.function
639 638
640 639 # lots of string formatting
641 640 listpattern = ''.ljust(indent) +\
642 641 ('\\' if multiple_siblings else '|') +\
643 642 ' %4.1f%% %s %s'
644 643 liststring = listpattern % (node.count / root.count * 100,
645 644 filename, function)
646 645 codepattern = '%' + str(55 - len(liststring)) + 's %s: %s'
647 646 codestring = codepattern % ('line', site.lineno, site.getsource(30))
648 647
649 648 finalstring = liststring + codestring
650 649 childrensamples = sum([c.count for c in node.children.itervalues()])
651 650 # Make frames that performed more than 10% of the operation red
652 651 if node.count - childrensamples > (0.1 * root.count):
653 652 finalstring = '\033[91m' + finalstring + '\033[0m'
654 653 # Make frames that didn't actually perform work dark grey
655 654 elif node.count - childrensamples == 0:
656 655 finalstring = '\033[90m' + finalstring + '\033[0m'
657 656 print >> fp, finalstring
658 657
659 658 newdepth = depth
660 659 if len(visiblechildren) > 1 or multiple_siblings:
661 660 newdepth += 1
662 661
663 662 visiblechildren.sort(reverse=True, key=lambda x: x.count)
664 663 for child in visiblechildren:
665 664 _write(child, newdepth, len(visiblechildren) > 1)
666 665
667 666 if root.count > 0:
668 667 _write(root, 0, False)
669 668
670 669 def write_to_flame(fp, scriptpath=None, outputfile=None, **kwargs):
671 670 if scriptpath is None:
672 671 scriptpath = os.environ['HOME'] + '/flamegraph.pl'
673 672 if not os.path.exists(scriptpath):
674 673 print >> fp, "error: missing %s" % scriptpath
675 674 print >> fp, "get it here: https://github.com/brendangregg/FlameGraph"
676 675 return
677 676
678 677 fd, path = tempfile.mkstemp()
679 678
680 679 file = open(path, "w+")
681 680
682 681 lines = {}
683 682 for sample in state.samples:
684 683 sites = [s.function for s in sample.stack]
685 684 sites.reverse()
686 685 line = ';'.join(sites)
687 686 if line in lines:
688 687 lines[line] = lines[line] + 1
689 688 else:
690 689 lines[line] = 1
691 690
692 691 for line, count in lines.iteritems():
693 692 file.write("%s %s\n" % (line, count))
694 693
695 694 file.close()
696 695
697 696 if outputfile is None:
698 697 outputfile = '~/flamegraph.svg'
699 698
700 699 os.system("perl ~/flamegraph.pl %s > %s" % (path, outputfile))
701 700 print "Written to %s" % outputfile
702 701
703 702 def write_to_json(fp):
704 703 samples = []
705 704
706 705 for sample in state.samples:
707 706 stack = []
708 707
709 708 for frame in sample.stack:
710 709 stack.append((frame.path, frame.lineno, frame.function))
711 710
712 711 samples.append((sample.time, stack))
713 712
714 713 print >> fp, json.dumps(samples)
715 714
716 715 def printusage():
717 716 print """
718 717 The statprof command line allows you to inspect the last profile's results in
719 718 the following forms:
720 719
721 720 usage:
722 721 hotpath [-l --limit percent]
723 722 Shows a graph of calls with the percent of time each takes.
724 723 Red calls take over 10%% of the total time themselves.
725 724 lines
726 725 Shows the actual sampled lines.
727 726 functions
728 727 Shows the samples grouped by function.
729 728 function [filename:]functionname
730 729 Shows the callers and callees of a particular function.
731 730 flame [-s --script-path] [-o --output-file path]
732 731 Writes out a flamegraph to output-file (defaults to ~/flamegraph.svg)
733 732 Requires that ~/flamegraph.pl exist.
734 733 (Specify alternate script path with --script-path.)"""
735 734
736 735 def main(argv=None):
737 736 if argv is None:
738 737 argv = sys.argv
739 738
740 739 if len(argv) == 1:
741 740 printusage()
742 741 return 0
743 742
744 743 displayargs = {}
745 744
746 745 optstart = 2
747 746 displayargs['function'] = None
748 747 if argv[1] == 'hotpath':
749 748 displayargs['format'] = DisplayFormats.Hotpath
750 749 elif argv[1] == 'lines':
751 750 displayargs['format'] = DisplayFormats.ByLine
752 751 elif argv[1] == 'functions':
753 752 displayargs['format'] = DisplayFormats.ByMethod
754 753 elif argv[1] == 'function':
755 754 displayargs['format'] = DisplayFormats.AboutMethod
756 755 displayargs['function'] = argv[2]
757 756 optstart = 3
758 757 elif argv[1] == 'flame':
759 758 displayargs['format'] = DisplayFormats.FlameGraph
760 759 else:
761 760 printusage()
762 761 return 0
763 762
764 763 # process options
765 764 try:
766 765 opts, args = getopt.getopt(sys.argv[optstart:], "hl:f:o:p:",
767 766 ["help", "limit=", "file=", "output-file=", "script-path="])
768 767 except getopt.error as msg:
769 768 print msg
770 769 printusage()
771 770 return 2
772 771
773 772 displayargs['limit'] = 0.05
774 773 path = None
775 774 for o, value in opts:
776 775 if o in ("-l", "--limit"):
777 776 displayargs['limit'] = float(value)
778 777 elif o in ("-f", "--file"):
779 778 path = value
780 779 elif o in ("-o", "--output-file"):
781 780 displayargs['outputfile'] = value
782 781 elif o in ("-p", "--script-path"):
783 782 displayargs['scriptpath'] = value
784 783 elif o in ("-h", "help"):
785 784 printusage()
786 785 return 0
787 786 else:
788 787 assert False, "unhandled option %s" % o
789 788
790 789 load_data(path=path)
791 790
792 791 display(**displayargs)
793 792
794 793 return 0
795 794
796 795 if __name__ == "__main__":
797 796 sys.exit(main())
General Comments 0
You need to be logged in to leave comments. Login now