Mercurial > hg
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""" |