comparison mercurial/dispatch.py @ 29781:2654a0aac80d

profiling: move profiling code from dispatch.py (API) Currently, profiling code lives in dispatch.py, which is a low-level module centered around command dispatch. Furthermore, dispatch.py imports a lot of other modules, meaning that importing dispatch.py to get at profiling functionality would often result in a module import cycle. Profiling is a generic activity. It shouldn't be limited to command dispatch. This patch moves profiling code from dispatch.py to the new profiling.py. The low-level "run a profiler against a function" functions have been moved verbatim. The code for determining how to invoke the profiler has been extracted to its own function. I decided to create a new module rather than stick this code elsewhere (such as util.py) because util.py is already quite large. And, I foresee this file growing larger once Facebook's profiling enhancements get added to it.
author Gregory Szorc <gregory.szorc@gmail.com>
date Sun, 14 Aug 2016 16:30:44 -0700
parents 39149b6036e6
children 97bfc2e5fba5
comparison
equal deleted inserted replaced
29780:531e85eec23c 29781:2654a0aac80d
32 extensions, 32 extensions,
33 fancyopts, 33 fancyopts,
34 fileset, 34 fileset,
35 hg, 35 hg,
36 hook, 36 hook,
37 profiling,
37 revset, 38 revset,
38 templatefilters, 39 templatefilters,
39 templatekw, 40 templatekw,
40 templater, 41 templater,
41 ui as uimod, 42 ui as uimod,
890 cmdpats, cmdoptions) 891 cmdpats, cmdoptions)
891 finally: 892 finally:
892 if repo and repo != req.repo: 893 if repo and repo != req.repo:
893 repo.close() 894 repo.close()
894 895
895 def lsprofile(ui, func, fp):
896 format = ui.config('profiling', 'format', default='text')
897 field = ui.config('profiling', 'sort', default='inlinetime')
898 limit = ui.configint('profiling', 'limit', default=30)
899 climit = ui.configint('profiling', 'nested', default=0)
900
901 if format not in ['text', 'kcachegrind']:
902 ui.warn(_("unrecognized profiling format '%s'"
903 " - Ignored\n") % format)
904 format = 'text'
905
906 try:
907 from . import lsprof
908 except ImportError:
909 raise error.Abort(_(
910 'lsprof not available - install from '
911 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
912 p = lsprof.Profiler()
913 p.enable(subcalls=True)
914 try:
915 return func()
916 finally:
917 p.disable()
918
919 if format == 'kcachegrind':
920 from . import lsprofcalltree
921 calltree = lsprofcalltree.KCacheGrind(p)
922 calltree.output(fp)
923 else:
924 # format == 'text'
925 stats = lsprof.Stats(p.getstats())
926 stats.sort(field)
927 stats.pprint(limit=limit, file=fp, climit=climit)
928
929 def flameprofile(ui, func, fp):
930 try:
931 from flamegraph import flamegraph
932 except ImportError:
933 raise error.Abort(_(
934 'flamegraph not available - install from '
935 'https://github.com/evanhempel/python-flamegraph'))
936 # developer config: profiling.freq
937 freq = ui.configint('profiling', 'freq', default=1000)
938 filter_ = None
939 collapse_recursion = True
940 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
941 filter_, collapse_recursion)
942 start_time = time.clock()
943 try:
944 thread.start()
945 func()
946 finally:
947 thread.stop()
948 thread.join()
949 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
950 time.clock() - start_time, thread.num_frames(),
951 thread.num_frames(unique=True)))
952
953
954 def statprofile(ui, func, fp):
955 try:
956 import statprof
957 except ImportError:
958 raise error.Abort(_(
959 'statprof not available - install using "easy_install statprof"'))
960
961 freq = ui.configint('profiling', 'freq', default=1000)
962 if freq > 0:
963 statprof.reset(freq)
964 else:
965 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
966
967 statprof.start()
968 try:
969 return func()
970 finally:
971 statprof.stop()
972 statprof.display(fp)
973
974 def _runcommand(ui, options, cmd, cmdfunc): 896 def _runcommand(ui, options, cmd, cmdfunc):
975 """Enables the profiler if applicable. 897 """Enables the profiler if applicable.
976 898
977 ``profiling.enabled`` - boolean config that enables or disables profiling 899 ``profiling.enabled`` - boolean config that enables or disables profiling
978 """ 900 """
981 return cmdfunc() 903 return cmdfunc()
982 except error.SignatureError: 904 except error.SignatureError:
983 raise error.CommandError(cmd, _("invalid arguments")) 905 raise error.CommandError(cmd, _("invalid arguments"))
984 906
985 if options['profile'] or ui.configbool('profiling', 'enabled'): 907 if options['profile'] or ui.configbool('profiling', 'enabled'):
986 profiler = os.getenv('HGPROF') 908 return profiling.profile(ui, checkargs)
987 if profiler is None:
988 profiler = ui.config('profiling', 'type', default='ls')
989 if profiler not in ('ls', 'stat', 'flame'):
990 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
991 profiler = 'ls'
992
993 output = ui.config('profiling', 'output')
994
995 if output == 'blackbox':
996 fp = util.stringio()
997 elif output:
998 path = ui.expandpath(output)
999 fp = open(path, 'wb')
1000 else:
1001 fp = sys.stderr
1002
1003 try:
1004 if profiler == 'ls':
1005 return lsprofile(ui, checkargs, fp)
1006 elif profiler == 'flame':
1007 return flameprofile(ui, checkargs, fp)
1008 else:
1009 return statprofile(ui, checkargs, fp)
1010 finally:
1011 if output:
1012 if output == 'blackbox':
1013 val = "Profile:\n%s" % fp.getvalue()
1014 # ui.log treats the input as a format string,
1015 # so we need to escape any % signs.
1016 val = val.replace('%', '%%')
1017 ui.log('profile', val)
1018 fp.close()
1019 else: 909 else:
1020 return checkargs() 910 return checkargs()
1021 911
1022 def _exceptionwarning(ui): 912 def _exceptionwarning(ui):
1023 """Produce a warning message for the current active exception""" 913 """Produce a warning message for the current active exception"""