mercurial/profiling.py
changeset 29781 2654a0aac80d
child 29783 5d44197c208b
equal deleted inserted replaced
29780:531e85eec23c 29781:2654a0aac80d
       
     1 # profiling.py - profiling functions
       
     2 #
       
     3 # Copyright 2016 Gregory Szorc <gregory.szorc@gmail.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 from __future__ import absolute_import, print_function
       
     9 
       
    10 import os
       
    11 import sys
       
    12 import time
       
    13 
       
    14 from .i18n import _
       
    15 from . import (
       
    16     error,
       
    17     util,
       
    18 )
       
    19 
       
    20 def lsprofile(ui, func, fp):
       
    21     format = ui.config('profiling', 'format', default='text')
       
    22     field = ui.config('profiling', 'sort', default='inlinetime')
       
    23     limit = ui.configint('profiling', 'limit', default=30)
       
    24     climit = ui.configint('profiling', 'nested', default=0)
       
    25 
       
    26     if format not in ['text', 'kcachegrind']:
       
    27         ui.warn(_("unrecognized profiling format '%s'"
       
    28                     " - Ignored\n") % format)
       
    29         format = 'text'
       
    30 
       
    31     try:
       
    32         from . import lsprof
       
    33     except ImportError:
       
    34         raise error.Abort(_(
       
    35             'lsprof not available - install from '
       
    36             'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
       
    37     p = lsprof.Profiler()
       
    38     p.enable(subcalls=True)
       
    39     try:
       
    40         return func()
       
    41     finally:
       
    42         p.disable()
       
    43 
       
    44         if format == 'kcachegrind':
       
    45             from . import lsprofcalltree
       
    46             calltree = lsprofcalltree.KCacheGrind(p)
       
    47             calltree.output(fp)
       
    48         else:
       
    49             # format == 'text'
       
    50             stats = lsprof.Stats(p.getstats())
       
    51             stats.sort(field)
       
    52             stats.pprint(limit=limit, file=fp, climit=climit)
       
    53 
       
    54 def flameprofile(ui, func, fp):
       
    55     try:
       
    56         from flamegraph import flamegraph
       
    57     except ImportError:
       
    58         raise error.Abort(_(
       
    59             'flamegraph not available - install from '
       
    60             'https://github.com/evanhempel/python-flamegraph'))
       
    61     # developer config: profiling.freq
       
    62     freq = ui.configint('profiling', 'freq', default=1000)
       
    63     filter_ = None
       
    64     collapse_recursion = True
       
    65     thread = flamegraph.ProfileThread(fp, 1.0 / freq,
       
    66                                       filter_, collapse_recursion)
       
    67     start_time = time.clock()
       
    68     try:
       
    69         thread.start()
       
    70         func()
       
    71     finally:
       
    72         thread.stop()
       
    73         thread.join()
       
    74         print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
       
    75             time.clock() - start_time, thread.num_frames(),
       
    76             thread.num_frames(unique=True)))
       
    77 
       
    78 def statprofile(ui, func, fp):
       
    79     try:
       
    80         import statprof
       
    81     except ImportError:
       
    82         raise error.Abort(_(
       
    83             'statprof not available - install using "easy_install statprof"'))
       
    84 
       
    85     freq = ui.configint('profiling', 'freq', default=1000)
       
    86     if freq > 0:
       
    87         statprof.reset(freq)
       
    88     else:
       
    89         ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
       
    90 
       
    91     statprof.start()
       
    92     try:
       
    93         return func()
       
    94     finally:
       
    95         statprof.stop()
       
    96         statprof.display(fp)
       
    97 
       
    98 def profile(ui, fn):
       
    99     """Profile a function call."""
       
   100     profiler = os.getenv('HGPROF')
       
   101     if profiler is None:
       
   102         profiler = ui.config('profiling', 'type', default='ls')
       
   103     if profiler not in ('ls', 'stat', 'flame'):
       
   104         ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
       
   105         profiler = 'ls'
       
   106 
       
   107     output = ui.config('profiling', 'output')
       
   108 
       
   109     if output == 'blackbox':
       
   110         fp = util.stringio()
       
   111     elif output:
       
   112         path = ui.expandpath(output)
       
   113         fp = open(path, 'wb')
       
   114     else:
       
   115         fp = sys.stderr
       
   116 
       
   117     try:
       
   118         if profiler == 'ls':
       
   119             return lsprofile(ui, fn, fp)
       
   120         elif profiler == 'flame':
       
   121             return flameprofile(ui, fn, fp)
       
   122         else:
       
   123             return statprofile(ui, fn, fp)
       
   124     finally:
       
   125         if output:
       
   126             if output == 'blackbox':
       
   127                 val = 'Profile:\n%s' % fp.getvalue()
       
   128                 # ui.log treats the input as a format string,
       
   129                 # so we need to escape any % signs.
       
   130                 val = val.replace('%', '%%')
       
   131                 ui.log('profile', val)
       
   132             fp.close()