##// END OF EJS Templates
profile: introduce a knob to control if the context is actually profiling...
marmoute -
r32785:37ec8f24 default
parent child Browse files
Show More
@@ -1,235 +1,234 b''
1 1 # profiling.py - profiling functions
2 2 #
3 3 # Copyright 2016 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import, print_function
9 9
10 10 import contextlib
11 11
12 12 from .i18n import _
13 13 from . import (
14 14 encoding,
15 15 error,
16 16 extensions,
17 17 util,
18 18 )
19 19
20 20 def _loadprofiler(ui, profiler):
21 21 """load profiler extension. return profile method, or None on failure"""
22 22 extname = profiler
23 23 extensions.loadall(ui, whitelist=[extname])
24 24 try:
25 25 mod = extensions.find(extname)
26 26 except KeyError:
27 27 return None
28 28 else:
29 29 return getattr(mod, 'profile', None)
30 30
31 31 @contextlib.contextmanager
32 32 def lsprofile(ui, fp):
33 33 format = ui.config('profiling', 'format', default='text')
34 34 field = ui.config('profiling', 'sort', default='inlinetime')
35 35 limit = ui.configint('profiling', 'limit', default=30)
36 36 climit = ui.configint('profiling', 'nested', default=0)
37 37
38 38 if format not in ['text', 'kcachegrind']:
39 39 ui.warn(_("unrecognized profiling format '%s'"
40 40 " - Ignored\n") % format)
41 41 format = 'text'
42 42
43 43 try:
44 44 from . import lsprof
45 45 except ImportError:
46 46 raise error.Abort(_(
47 47 'lsprof not available - install from '
48 48 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
49 49 p = lsprof.Profiler()
50 50 p.enable(subcalls=True)
51 51 try:
52 52 yield
53 53 finally:
54 54 p.disable()
55 55
56 56 if format == 'kcachegrind':
57 57 from . import lsprofcalltree
58 58 calltree = lsprofcalltree.KCacheGrind(p)
59 59 calltree.output(fp)
60 60 else:
61 61 # format == 'text'
62 62 stats = lsprof.Stats(p.getstats())
63 63 stats.sort(field)
64 64 stats.pprint(limit=limit, file=fp, climit=climit)
65 65
66 66 @contextlib.contextmanager
67 67 def flameprofile(ui, fp):
68 68 try:
69 69 from flamegraph import flamegraph
70 70 except ImportError:
71 71 raise error.Abort(_(
72 72 'flamegraph not available - install from '
73 73 'https://github.com/evanhempel/python-flamegraph'))
74 74 # developer config: profiling.freq
75 75 freq = ui.configint('profiling', 'freq', default=1000)
76 76 filter_ = None
77 77 collapse_recursion = True
78 78 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
79 79 filter_, collapse_recursion)
80 80 start_time = util.timer()
81 81 try:
82 82 thread.start()
83 83 yield
84 84 finally:
85 85 thread.stop()
86 86 thread.join()
87 87 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
88 88 util.timer() - start_time, thread.num_frames(),
89 89 thread.num_frames(unique=True)))
90 90
91 91 @contextlib.contextmanager
92 92 def statprofile(ui, fp):
93 93 from . import statprof
94 94
95 95 freq = ui.configint('profiling', 'freq', default=1000)
96 96 if freq > 0:
97 97 # Cannot reset when profiler is already active. So silently no-op.
98 98 if statprof.state.profile_level == 0:
99 99 statprof.reset(freq)
100 100 else:
101 101 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
102 102
103 103 statprof.start(mechanism='thread')
104 104
105 105 try:
106 106 yield
107 107 finally:
108 108 data = statprof.stop()
109 109
110 110 profformat = ui.config('profiling', 'statformat', 'hotpath')
111 111
112 112 formats = {
113 113 'byline': statprof.DisplayFormats.ByLine,
114 114 'bymethod': statprof.DisplayFormats.ByMethod,
115 115 'hotpath': statprof.DisplayFormats.Hotpath,
116 116 'json': statprof.DisplayFormats.Json,
117 117 'chrome': statprof.DisplayFormats.Chrome,
118 118 }
119 119
120 120 if profformat in formats:
121 121 displayformat = formats[profformat]
122 122 else:
123 123 ui.warn(_('unknown profiler output format: %s\n') % profformat)
124 124 displayformat = statprof.DisplayFormats.Hotpath
125 125
126 126 kwargs = {}
127 127
128 128 def fraction(s):
129 129 if s.endswith('%'):
130 130 v = float(s[:-1]) / 100
131 131 else:
132 132 v = float(s)
133 133 if 0 <= v <= 1:
134 134 return v
135 135 raise ValueError(s)
136 136
137 137 if profformat == 'chrome':
138 138 showmin = ui.configwith(fraction, 'profiling', 'showmin', 0.005)
139 139 showmax = ui.configwith(fraction, 'profiling', 'showmax', 0.999)
140 140 kwargs.update(minthreshold=showmin, maxthreshold=showmax)
141 141
142 142 statprof.display(fp, data=data, format=displayformat, **kwargs)
143 143
144 144 class profile(object):
145 145 """Start profiling.
146 146
147 147 Profiling is active when the context manager is active. When the context
148 148 manager exits, profiling results will be written to the configured output.
149 149 """
150 def __init__(self, ui):
150 def __init__(self, ui, enabled=True):
151 151 self._ui = ui
152 152 self._output = None
153 153 self._fp = None
154 154 self._profiler = None
155 self._enabled = enabled
155 156 self._entered = False
156 157 self._started = False
157 158
158 159 def __enter__(self):
159 160 self._entered = True
160 self.start()
161 if self._enabled:
162 self.start()
161 163
162 164 def start(self):
163 165 """Start profiling.
164 166
165 167 The profiling will stop at the context exit.
166 168
167 169 If the profiler was already started, this has no effect."""
168 170 if not self._entered:
169 171 raise error.ProgrammingError()
170 172 if self._started:
171 173 return
172 174 self._started = True
173 175 profiler = encoding.environ.get('HGPROF')
174 176 proffn = None
175 177 if profiler is None:
176 178 profiler = self._ui.config('profiling', 'type', default='stat')
177 179 if profiler not in ('ls', 'stat', 'flame'):
178 180 # try load profiler from extension with the same name
179 181 proffn = _loadprofiler(self._ui, profiler)
180 182 if proffn is None:
181 183 self._ui.warn(_("unrecognized profiler '%s' - ignored\n")
182 184 % profiler)
183 185 profiler = 'stat'
184 186
185 187 self._output = self._ui.config('profiling', 'output')
186 188
187 189 if self._output == 'blackbox':
188 190 self._fp = util.stringio()
189 191 elif self._output:
190 192 path = self._ui.expandpath(self._output)
191 193 self._fp = open(path, 'wb')
192 194 else:
193 195 self._fp = self._ui.ferr
194 196
195 197 if proffn is not None:
196 198 pass
197 199 elif profiler == 'ls':
198 200 proffn = lsprofile
199 201 elif profiler == 'flame':
200 202 proffn = flameprofile
201 203 else:
202 204 proffn = statprofile
203 205
204 206 self._profiler = proffn(self._ui, self._fp)
205 207 self._profiler.__enter__()
206 208
207 209 def __exit__(self, exception_type, exception_value, traceback):
208 210 if self._profiler is None:
209 211 return
210 212 self._profiler.__exit__(exception_type, exception_value, traceback)
211 213 if self._output:
212 214 if self._output == 'blackbox':
213 215 val = 'Profile:\n%s' % self._fp.getvalue()
214 216 # ui.log treats the input as a format string,
215 217 # so we need to escape any % signs.
216 218 val = val.replace('%', '%%')
217 219 self._ui.log('profile', val)
218 220 self._fp.close()
219 221
220 222 @contextlib.contextmanager
221 223 def maybeprofile(ui):
222 224 """Profile if enabled, else do nothing.
223 225
224 226 This context manager can be used to optionally profile if profiling
225 227 is enabled. Otherwise, it does nothing.
226 228
227 229 The purpose of this context manager is to make calling code simpler:
228 230 just use a single code path for calling into code you may want to profile
229 231 and this function determines whether to start profiling.
230 232 """
231 if ui.configbool('profiling', 'enabled'):
232 with profile(ui):
233 yield
234 else:
233 with profile(ui, enabled=ui.configbool('profiling', 'enabled')):
235 234 yield
General Comments 0
You need to be logged in to leave comments. Login now