Mercurial > public > mercurial-scm > hg
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 |