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