comparison mercurial/statprof.py @ 30258:eea89068a98d

statprof: pass data structure to display functions Currently, statprof maintains a global "state" variable that is used by several functions. Global variables hinder adaptability of code. So pass state to display functions so we can make changes to how "state" works in future patches.
author Gregory Szorc <gregory.szorc@gmail.com>
date Tue, 01 Nov 2016 19:03:11 -0700
parents 7428223ed7c2
children 1e5346313963
comparison
equal deleted inserted replaced
30257:7428223ed7c2 30258:eea89068a98d
425 AboutMethod = 2 425 AboutMethod = 2
426 Hotpath = 3 426 Hotpath = 3
427 FlameGraph = 4 427 FlameGraph = 4
428 Json = 5 428 Json = 5
429 429
430 def display(fp=None, format=3, **kwargs): 430 def display(fp=None, format=3, data=None, **kwargs):
431 '''Print statistics, either to stdout or the given file object.''' 431 '''Print statistics, either to stdout or the given file object.'''
432 data = data or state
432 433
433 if fp is None: 434 if fp is None:
434 import sys 435 import sys
435 fp = sys.stdout 436 fp = sys.stdout
436 if len(state.samples) == 0: 437 if len(data.samples) == 0:
437 print('No samples recorded.', file=fp) 438 print('No samples recorded.', file=fp)
438 return 439 return
439 440
440 if format == DisplayFormats.ByLine: 441 if format == DisplayFormats.ByLine:
441 display_by_line(fp) 442 display_by_line(data, fp)
442 elif format == DisplayFormats.ByMethod: 443 elif format == DisplayFormats.ByMethod:
443 display_by_method(fp) 444 display_by_method(data, fp)
444 elif format == DisplayFormats.AboutMethod: 445 elif format == DisplayFormats.AboutMethod:
445 display_about_method(fp, **kwargs) 446 display_about_method(data, fp, **kwargs)
446 elif format == DisplayFormats.Hotpath: 447 elif format == DisplayFormats.Hotpath:
447 display_hotpath(fp, **kwargs) 448 display_hotpath(data, fp, **kwargs)
448 elif format == DisplayFormats.FlameGraph: 449 elif format == DisplayFormats.FlameGraph:
449 write_to_flame(fp, **kwargs) 450 write_to_flame(data, fp, **kwargs)
450 elif format == DisplayFormats.Json: 451 elif format == DisplayFormats.Json:
451 write_to_json(fp) 452 write_to_json(data, fp)
452 else: 453 else:
453 raise Exception("Invalid display format") 454 raise Exception("Invalid display format")
454 455
455 if format != DisplayFormats.Json: 456 if format != DisplayFormats.Json:
456 print('---', file=fp) 457 print('---', file=fp)
457 print('Sample count: %d' % len(state.samples), file=fp) 458 print('Sample count: %d' % len(data.samples), file=fp)
458 print('Total time: %f seconds' % state.accumulated_time, file=fp) 459 print('Total time: %f seconds' % data.accumulated_time, file=fp)
459 460
460 def display_by_line(fp): 461 def display_by_line(data, fp):
461 '''Print the profiler data with each sample line represented 462 '''Print the profiler data with each sample line represented
462 as one row in a table. Sorted by self-time per line.''' 463 as one row in a table. Sorted by self-time per line.'''
463 stats = SiteStats.buildstats(state.samples) 464 stats = SiteStats.buildstats(data.samples)
464 stats.sort(reverse=True, key=lambda x: x.selfseconds()) 465 stats.sort(reverse=True, key=lambda x: x.selfseconds())
465 466
466 print('%5.5s %10.10s %7.7s %-8.8s' % 467 print('%5.5s %10.10s %7.7s %-8.8s' %
467 ('% ', 'cumulative', 'self', ''), file=fp) 468 ('% ', 'cumulative', 'self', ''), file=fp)
468 print('%5.5s %9.9s %8.8s %-8.8s' % 469 print('%5.5s %9.9s %8.8s %-8.8s' %
475 stat.totalseconds(), 476 stat.totalseconds(),
476 stat.selfseconds(), 477 stat.selfseconds(),
477 sitelabel), 478 sitelabel),
478 file=fp) 479 file=fp)
479 480
480 def display_by_method(fp): 481 def display_by_method(data, fp):
481 '''Print the profiler data with each sample function represented 482 '''Print the profiler data with each sample function represented
482 as one row in a table. Important lines within that function are 483 as one row in a table. Important lines within that function are
483 output as nested rows. Sorted by self-time per line.''' 484 output as nested rows. Sorted by self-time per line.'''
484 print('%5.5s %10.10s %7.7s %-8.8s' % 485 print('%5.5s %10.10s %7.7s %-8.8s' %
485 ('% ', 'cumulative', 'self', ''), file=fp) 486 ('% ', 'cumulative', 'self', ''), file=fp)
486 print('%5.5s %9.9s %8.8s %-8.8s' % 487 print('%5.5s %9.9s %8.8s %-8.8s' %
487 ("time", "seconds", "seconds", "name"), file=fp) 488 ("time", "seconds", "seconds", "name"), file=fp)
488 489
489 stats = SiteStats.buildstats(state.samples) 490 stats = SiteStats.buildstats(data.samples)
490 491
491 grouped = defaultdict(list) 492 grouped = defaultdict(list)
492 for stat in stats: 493 for stat in stats:
493 grouped[stat.site.filename() + ":" + stat.site.function].append(stat) 494 grouped[stat.site.filename() + ":" + stat.site.function].append(stat)
494 495
528 stattuple = (stat.selfpercent(), stat.selfseconds(), 529 stattuple = (stat.selfpercent(), stat.selfseconds(),
529 stat.site.lineno, source) 530 stat.site.lineno, source)
530 531
531 print('%33.0f%% %6.2f line %s: %s' % (stattuple), file=fp) 532 print('%33.0f%% %6.2f line %s: %s' % (stattuple), file=fp)
532 533
533 def display_about_method(fp, function=None, **kwargs): 534 def display_about_method(data, fp, function=None, **kwargs):
534 if function is None: 535 if function is None:
535 raise Exception("Invalid function") 536 raise Exception("Invalid function")
536 537
537 filename = None 538 filename = None
538 if ':' in function: 539 if ':' in function:
540 541
541 relevant_samples = 0 542 relevant_samples = 0
542 parents = {} 543 parents = {}
543 children = {} 544 children = {}
544 545
545 for sample in state.samples: 546 for sample in data.samples:
546 for i, site in enumerate(sample.stack): 547 for i, site in enumerate(sample.stack):
547 if site.function == function and (not filename 548 if site.function == function and (not filename
548 or site.filename() == filename): 549 or site.filename() == filename):
549 relevant_samples += 1 550 relevant_samples += 1
550 if i != len(sample.stack) - 1: 551 if i != len(sample.stack) - 1:
564 for parent, count in parents: 565 for parent, count in parents:
565 print('%6.2f%% %s:%s line %s: %s' % 566 print('%6.2f%% %s:%s line %s: %s' %
566 (count / relevant_samples * 100, parent.filename(), 567 (count / relevant_samples * 100, parent.filename(),
567 parent.function, parent.lineno, parent.getsource(50)), file=fp) 568 parent.function, parent.lineno, parent.getsource(50)), file=fp)
568 569
569 stats = SiteStats.buildstats(state.samples) 570 stats = SiteStats.buildstats(data.samples)
570 stats = [s for s in stats 571 stats = [s for s in stats
571 if s.site.function == function and 572 if s.site.function == function and
572 (not filename or s.site.filename() == filename)] 573 (not filename or s.site.filename() == filename)]
573 574
574 total_cum_sec = 0 575 total_cum_sec = 0
597 for child, count in children: 598 for child, count in children:
598 print(' %6.2f%% line %s: %s' % 599 print(' %6.2f%% line %s: %s' %
599 (count / relevant_samples * 100, child.lineno, 600 (count / relevant_samples * 100, child.lineno,
600 child.getsource(50)), file=fp) 601 child.getsource(50)), file=fp)
601 602
602 def display_hotpath(fp, limit=0.05, **kwargs): 603 def display_hotpath(data, fp, limit=0.05, **kwargs):
603 class HotNode(object): 604 class HotNode(object):
604 def __init__(self, site): 605 def __init__(self, site):
605 self.site = site 606 self.site = site
606 self.count = 0 607 self.count = 0
607 self.children = {} 608 self.children = {}
621 i += 1 622 i += 1
622 if i < len(stack): 623 if i < len(stack):
623 child.add(stack[i:], time) 624 child.add(stack[i:], time)
624 625
625 root = HotNode(None) 626 root = HotNode(None)
626 lasttime = state.samples[0].time 627 lasttime = data.samples[0].time
627 for sample in state.samples: 628 for sample in data.samples:
628 root.add(sample.stack[::-1], sample.time - lasttime) 629 root.add(sample.stack[::-1], sample.time - lasttime)
629 lasttime = sample.time 630 lasttime = sample.time
630 631
631 def _write(node, depth, multiple_siblings): 632 def _write(node, depth, multiple_siblings):
632 site = node.site 633 site = node.site
669 _write(child, newdepth, len(visiblechildren) > 1) 670 _write(child, newdepth, len(visiblechildren) > 1)
670 671
671 if root.count > 0: 672 if root.count > 0:
672 _write(root, 0, False) 673 _write(root, 0, False)
673 674
674 def write_to_flame(fp, scriptpath=None, outputfile=None, **kwargs): 675 def write_to_flame(data, fp, scriptpath=None, outputfile=None, **kwargs):
675 if scriptpath is None: 676 if scriptpath is None:
676 scriptpath = os.environ['HOME'] + '/flamegraph.pl' 677 scriptpath = os.environ['HOME'] + '/flamegraph.pl'
677 if not os.path.exists(scriptpath): 678 if not os.path.exists(scriptpath):
678 print("error: missing %s" % scriptpath, file=fp) 679 print("error: missing %s" % scriptpath, file=fp)
679 print("get it here: https://github.com/brendangregg/FlameGraph", 680 print("get it here: https://github.com/brendangregg/FlameGraph",
683 fd, path = tempfile.mkstemp() 684 fd, path = tempfile.mkstemp()
684 685
685 file = open(path, "w+") 686 file = open(path, "w+")
686 687
687 lines = {} 688 lines = {}
688 for sample in state.samples: 689 for sample in data.samples:
689 sites = [s.function for s in sample.stack] 690 sites = [s.function for s in sample.stack]
690 sites.reverse() 691 sites.reverse()
691 line = ';'.join(sites) 692 line = ';'.join(sites)
692 if line in lines: 693 if line in lines:
693 lines[line] = lines[line] + 1 694 lines[line] = lines[line] + 1
703 outputfile = '~/flamegraph.svg' 704 outputfile = '~/flamegraph.svg'
704 705
705 os.system("perl ~/flamegraph.pl %s > %s" % (path, outputfile)) 706 os.system("perl ~/flamegraph.pl %s > %s" % (path, outputfile))
706 print("Written to %s" % outputfile, file=fp) 707 print("Written to %s" % outputfile, file=fp)
707 708
708 def write_to_json(fp): 709 def write_to_json(data, fp):
709 samples = [] 710 samples = []
710 711
711 for sample in state.samples: 712 for sample in data.samples:
712 stack = [] 713 stack = []
713 714
714 for frame in sample.stack: 715 for frame in sample.stack:
715 stack.append((frame.path, frame.lineno, frame.function)) 716 stack.append((frame.path, frame.lineno, frame.function))
716 717