mercurial/statprof.py
changeset 40198 9d3034348c4f
parent 40197 113adb1b3f24
child 40199 b594db74dc13
equal deleted inserted replaced
40197:113adb1b3f24 40198:9d3034348c4f
   467 
   467 
   468     if fp is None:
   468     if fp is None:
   469         import sys
   469         import sys
   470         fp = sys.stdout
   470         fp = sys.stdout
   471     if len(data.samples) == 0:
   471     if len(data.samples) == 0:
   472         print('No samples recorded.', file=fp)
   472         fp.write(b'No samples recorded.\n')
   473         return
   473         return
   474 
   474 
   475     if format == DisplayFormats.ByLine:
   475     if format == DisplayFormats.ByLine:
   476         display_by_line(data, fp)
   476         display_by_line(data, fp)
   477     elif format == DisplayFormats.ByMethod:
   477     elif format == DisplayFormats.ByMethod:
   488         write_to_chrome(data, fp, **kwargs)
   488         write_to_chrome(data, fp, **kwargs)
   489     else:
   489     else:
   490         raise Exception("Invalid display format")
   490         raise Exception("Invalid display format")
   491 
   491 
   492     if format not in (DisplayFormats.Json, DisplayFormats.Chrome):
   492     if format not in (DisplayFormats.Json, DisplayFormats.Chrome):
   493         print('---', file=fp)
   493         fp.write(b'---\n')
   494         print('Sample count: %d' % len(data.samples), file=fp)
   494         fp.write(b'Sample count: %d\n' % len(data.samples))
   495         print('Total time: %f seconds (%f wall)' % data.accumulated_time,
   495         fp.write(b'Total time: %f seconds (%f wall)\n' % data.accumulated_time)
   496               file=fp)
       
   497 
   496 
   498 def display_by_line(data, fp):
   497 def display_by_line(data, fp):
   499     '''Print the profiler data with each sample line represented
   498     '''Print the profiler data with each sample line represented
   500     as one row in a table.  Sorted by self-time per line.'''
   499     as one row in a table.  Sorted by self-time per line.'''
   501     stats = SiteStats.buildstats(data.samples)
   500     stats = SiteStats.buildstats(data.samples)
   502     stats.sort(reverse=True, key=lambda x: x.selfseconds())
   501     stats.sort(reverse=True, key=lambda x: x.selfseconds())
   503 
   502 
   504     print('%5.5s %10.10s   %7.7s  %-8.8s' %
   503     fp.write(b'%5.5s %10.10s   %7.7s  %-8.8s\n' % (
   505           ('%  ', 'cumulative', 'self', ''), file=fp)
   504         b'%  ', b'cumulative', b'self', b''))
   506     print('%5.5s  %9.9s  %8.8s  %-8.8s' %
   505     fp.write(b'%5.5s  %9.9s  %8.8s  %-8.8s\n' % (
   507           ("time", "seconds", "seconds", "name"), file=fp)
   506         b"time", b"seconds", b"seconds", b"name"))
   508 
   507 
   509     for stat in stats:
   508     for stat in stats:
   510         site = stat.site
   509         site = stat.site
   511         sitelabel = '%s:%d:%s' % (site.filename(), site.lineno, site.function)
   510         sitelabel = '%s:%d:%s' % (site.filename(), site.lineno, site.function)
   512         print('%6.2f %9.2f %9.2f  %s' % (stat.selfpercent(),
   511         fp.write(b'%6.2f %9.2f %9.2f  %s\n' % (
   513                                          stat.totalseconds(),
   512             stat.selfpercent(), stat.totalseconds(),
   514                                          stat.selfseconds(),
   513             stat.selfseconds(), sitelabel))
   515                                          sitelabel),
       
   516               file=fp)
       
   517 
   514 
   518 def display_by_method(data, fp):
   515 def display_by_method(data, fp):
   519     '''Print the profiler data with each sample function represented
   516     '''Print the profiler data with each sample function represented
   520     as one row in a table.  Important lines within that function are
   517     as one row in a table.  Important lines within that function are
   521     output as nested rows.  Sorted by self-time per line.'''
   518     output as nested rows.  Sorted by self-time per line.'''
   522     print('%5.5s %10.10s   %7.7s  %-8.8s' %
   519     fp.write(b'%5.5s %10.10s   %7.7s  %-8.8s\n' %
   523           ('%  ', 'cumulative', 'self', ''), file=fp)
   520           ('%  ', 'cumulative', 'self', ''))
   524     print('%5.5s  %9.9s  %8.8s  %-8.8s' %
   521     fp.write(b'%5.5s  %9.9s  %8.8s  %-8.8s\n' %
   525           ("time", "seconds", "seconds", "name"), file=fp)
   522           ("time", "seconds", "seconds", "name"))
   526 
   523 
   527     stats = SiteStats.buildstats(data.samples)
   524     stats = SiteStats.buildstats(data.samples)
   528 
   525 
   529     grouped = defaultdict(list)
   526     grouped = defaultdict(list)
   530     for stat in stats:
   527     for stat in stats:
   551     functiondata.sort(reverse=True, key=lambda x: x[2])
   548     functiondata.sort(reverse=True, key=lambda x: x[2])
   552 
   549 
   553     for function in functiondata:
   550     for function in functiondata:
   554         if function[3] < 0.05:
   551         if function[3] < 0.05:
   555             continue
   552             continue
   556         print('%6.2f %9.2f %9.2f  %s' % (function[3], # total percent
   553         fp.write(b'%6.2f %9.2f %9.2f  %s\n' % (
   557                                          function[1], # total cum sec
   554             function[3], # total percent
   558                                          function[2], # total self sec
   555             function[1], # total cum sec
   559                                          function[0]), # file:function
   556             function[2], # total self sec
   560               file=fp)
   557             function[0])) # file:function
       
   558 
   561         function[4].sort(reverse=True, key=lambda i: i.selfseconds())
   559         function[4].sort(reverse=True, key=lambda i: i.selfseconds())
   562         for stat in function[4]:
   560         for stat in function[4]:
   563             # only show line numbers for significant locations (>1% time spent)
   561             # only show line numbers for significant locations (>1% time spent)
   564             if stat.selfpercent() > 1:
   562             if stat.selfpercent() > 1:
   565                 source = stat.site.getsource(25)
   563                 source = stat.site.getsource(25)
   566                 stattuple = (stat.selfpercent(), stat.selfseconds(),
   564                 stattuple = (stat.selfpercent(), stat.selfseconds(),
   567                              stat.site.lineno, source)
   565                              stat.site.lineno, source)
   568 
   566 
   569                 print('%33.0f%% %6.2f   line %d: %s' % (stattuple), file=fp)
   567                 fp.write(b'%33.0f%% %6.2f   line %d: %s\n' % stattuple)
   570 
   568 
   571 def display_about_method(data, fp, function=None, **kwargs):
   569 def display_about_method(data, fp, function=None, **kwargs):
   572     if function is None:
   570     if function is None:
   573         raise Exception("Invalid function")
   571         raise Exception("Invalid function")
   574 
   572 
   598                     children[site] = 1
   596                     children[site] = 1
   599 
   597 
   600     parents = [(parent, count) for parent, count in parents.iteritems()]
   598     parents = [(parent, count) for parent, count in parents.iteritems()]
   601     parents.sort(reverse=True, key=lambda x: x[1])
   599     parents.sort(reverse=True, key=lambda x: x[1])
   602     for parent, count in parents:
   600     for parent, count in parents:
   603         print('%6.2f%%   %s:%s   line %s: %s' %
   601         fp.write(b'%6.2f%%   %s:%s   line %s: %s\n' %
   604             (count / relevant_samples * 100, parent.filename(),
   602             (count / relevant_samples * 100, parent.filename(),
   605             parent.function, parent.lineno, parent.getsource(50)), file=fp)
   603             parent.function, parent.lineno, parent.getsource(50)))
   606 
   604 
   607     stats = SiteStats.buildstats(data.samples)
   605     stats = SiteStats.buildstats(data.samples)
   608     stats = [s for s in stats
   606     stats = [s for s in stats
   609                if s.site.function == function and
   607                if s.site.function == function and
   610                (not filename or s.site.filename() == filename)]
   608                (not filename or s.site.filename() == filename)]
   617         total_cum_sec += stat.totalseconds()
   615         total_cum_sec += stat.totalseconds()
   618         total_self_sec += stat.selfseconds()
   616         total_self_sec += stat.selfseconds()
   619         total_self_percent += stat.selfpercent()
   617         total_self_percent += stat.selfpercent()
   620         total_cum_percent += stat.totalpercent()
   618         total_cum_percent += stat.totalpercent()
   621 
   619 
   622     print(
   620     fp.write(
   623         '\n    %s:%s    Total: %0.2fs (%0.2f%%)    Self: %0.2fs (%0.2f%%)\n' %
   621         b'\n    %s:%s    Total: %0.2fs (%0.2f%%)    Self: %0.2fs (%0.2f%%)\n\n'
   624         (
   622         % (
   625         filename or '___',
   623         filename or '___',
   626         function,
   624         function,
   627         total_cum_sec,
   625         total_cum_sec,
   628         total_cum_percent,
   626         total_cum_percent,
   629         total_self_sec,
   627         total_self_sec,
   630         total_self_percent
   628         total_self_percent
   631         ), file=fp)
   629         ))
   632 
   630 
   633     children = [(child, count) for child, count in children.iteritems()]
   631     children = [(child, count) for child, count in children.iteritems()]
   634     children.sort(reverse=True, key=lambda x: x[1])
   632     children.sort(reverse=True, key=lambda x: x[1])
   635     for child, count in children:
   633     for child, count in children:
   636         print('        %6.2f%%   line %s: %s' %
   634         fp.write(b'        %6.2f%%   line %s: %s\n' %
   637               (count / relevant_samples * 100, child.lineno,
   635               (count / relevant_samples * 100, child.lineno,
   638                child.getsource(50)), file=fp)
   636                child.getsource(50)))
   639 
   637 
   640 def display_hotpath(data, fp, limit=0.05, **kwargs):
   638 def display_hotpath(data, fp, limit=0.05, **kwargs):
   641     class HotNode(object):
   639     class HotNode(object):
   642         def __init__(self, site):
   640         def __init__(self, site):
   643             self.site = site
   641             self.site = site
   695             if node.count - childrensamples > (0.1 * root.count):
   693             if node.count - childrensamples > (0.1 * root.count):
   696                 finalstring = '\033[91m' + finalstring + '\033[0m'
   694                 finalstring = '\033[91m' + finalstring + '\033[0m'
   697             # Make frames that didn't actually perform work dark grey
   695             # Make frames that didn't actually perform work dark grey
   698             elif node.count - childrensamples == 0:
   696             elif node.count - childrensamples == 0:
   699                 finalstring = '\033[90m' + finalstring + '\033[0m'
   697                 finalstring = '\033[90m' + finalstring + '\033[0m'
   700             print(finalstring, file=fp)
   698                 fp.write(finalstring + b'\n')
   701 
   699 
   702         newdepth = depth
   700         newdepth = depth
   703         if len(visiblechildren) > 1 or multiple_siblings:
   701         if len(visiblechildren) > 1 or multiple_siblings:
   704             newdepth += 1
   702             newdepth += 1
   705 
   703 
   712 
   710 
   713 def write_to_flame(data, fp, scriptpath=None, outputfile=None, **kwargs):
   711 def write_to_flame(data, fp, scriptpath=None, outputfile=None, **kwargs):
   714     if scriptpath is None:
   712     if scriptpath is None:
   715         scriptpath = encoding.environ['HOME'] + '/flamegraph.pl'
   713         scriptpath = encoding.environ['HOME'] + '/flamegraph.pl'
   716     if not os.path.exists(scriptpath):
   714     if not os.path.exists(scriptpath):
   717         print("error: missing %s" % scriptpath, file=fp)
   715         fp.write(b'error: missing %s\n' % scriptpath)
   718         print("get it here: https://github.com/brendangregg/FlameGraph",
   716         fp.write(b'get it here: https://github.com/brendangregg/FlameGraph\n')
   719               file=fp)
       
   720         return
   717         return
   721 
   718 
   722     fd, path = pycompat.mkstemp()
   719     fd, path = pycompat.mkstemp()
   723 
   720 
   724     file = open(path, "w+")
   721     file = open(path, "w+")
   740 
   737 
   741     if outputfile is None:
   738     if outputfile is None:
   742         outputfile = '~/flamegraph.svg'
   739         outputfile = '~/flamegraph.svg'
   743 
   740 
   744     os.system("perl ~/flamegraph.pl %s > %s" % (path, outputfile))
   741     os.system("perl ~/flamegraph.pl %s > %s" % (path, outputfile))
   745     print("Written to %s" % outputfile, file=fp)
   742     fp.write(b'Written to %s\n' % outputfile)
   746 
   743 
   747 _pathcache = {}
   744 _pathcache = {}
   748 def simplifypath(path):
   745 def simplifypath(path):
   749     '''Attempt to make the path to a Python module easier to read by
   746     '''Attempt to make the path to a Python module easier to read by
   750     removing whatever part of the Python search path it was found
   747     removing whatever part of the Python search path it was found
   872                                      for (k,v) in enumerate(id2stack))
   869                                      for (k,v) in enumerate(id2stack))
   873     json.dump(dict(traceEvents=events, stackFrames=frames), fp, indent=1)
   870     json.dump(dict(traceEvents=events, stackFrames=frames), fp, indent=1)
   874     fp.write('\n')
   871     fp.write('\n')
   875 
   872 
   876 def printusage():
   873 def printusage():
   877     print("""
   874     print(r"""
   878 The statprof command line allows you to inspect the last profile's results in
   875 The statprof command line allows you to inspect the last profile's results in
   879 the following forms:
   876 the following forms:
   880 
   877 
   881 usage:
   878 usage:
   882     hotpath [-l --limit percent]
   879     hotpath [-l --limit percent]