Mercurial > hg
comparison mercurial/profiling.py @ 32783:4483696dacee
profile: upgrade the "profile" context manager to a full class
So far we have been able to use a simple decorator for this. However using the
current context manager makes the scope of the profiling in dispatch
constrainted and the time frame to decide to enable profiling quite limited
(using "maybeprofile")
This is the first step toward the ability to enable the profiling from within
the profiling scope. eg::
with maybeprofiling(ui) as profiler:
...
bar.foo():
...
if options['profile']:
profiler.start()
...
fooz()
...
My target usecase is adding support for "--profile" to alias definitions with
effect. These are to be used with "profiling.output=blackbox" to gather data
about operation that get slow from time to time (eg: pull being minutes instead
of seconds from time to time).
Of course, in such case, the scope of the profiling would be smaller since
profiler would be started after running extensions 'reposetup' (and other
potentially costly logic), but these are not relevant for my target usecase
(multiple second commits, multiple tens of seconds pull).
Currently adding '--profile' to a command through alias requires to re-spin a
Mercurial binary (using "!$HG" in alias), which as a significant performance
impact, especially in context where startup performance is being worked on...
An alternative approach would be to stop using the context manager in dispatch
and move back to a try/finally setup.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Thu, 08 Jun 2017 01:38:48 +0100 |
parents | f40dc6f7c12f |
children | 086c1ef0f666 |
comparison
equal
deleted
inserted
replaced
32782:9a4adc76c88a | 32783:4483696dacee |
---|---|
139 showmax = ui.configwith(fraction, 'profiling', 'showmax', 0.999) | 139 showmax = ui.configwith(fraction, 'profiling', 'showmax', 0.999) |
140 kwargs.update(minthreshold=showmin, maxthreshold=showmax) | 140 kwargs.update(minthreshold=showmin, maxthreshold=showmax) |
141 | 141 |
142 statprof.display(fp, data=data, format=displayformat, **kwargs) | 142 statprof.display(fp, data=data, format=displayformat, **kwargs) |
143 | 143 |
144 @contextlib.contextmanager | 144 class profile(object): |
145 def profile(ui): | |
146 """Start profiling. | 145 """Start profiling. |
147 | 146 |
148 Profiling is active when the context manager is active. When the context | 147 Profiling is active when the context manager is active. When the context |
149 manager exits, profiling results will be written to the configured output. | 148 manager exits, profiling results will be written to the configured output. |
150 """ | 149 """ |
151 profiler = encoding.environ.get('HGPROF') | 150 def __init__(self, ui): |
152 proffn = None | 151 self._ui = ui |
153 if profiler is None: | 152 self._output = None |
154 profiler = ui.config('profiling', 'type', default='stat') | 153 self._fp = None |
155 if profiler not in ('ls', 'stat', 'flame'): | 154 self._profiler = None |
156 # try load profiler from extension with the same name | 155 |
157 proffn = _loadprofiler(ui, profiler) | 156 def __enter__(self): |
158 if proffn is None: | 157 profiler = encoding.environ.get('HGPROF') |
159 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler) | 158 proffn = None |
160 profiler = 'stat' | 159 if profiler is None: |
161 | 160 profiler = self._ui.config('profiling', 'type', default='stat') |
162 output = ui.config('profiling', 'output') | 161 if profiler not in ('ls', 'stat', 'flame'): |
163 | 162 # try load profiler from extension with the same name |
164 if output == 'blackbox': | 163 proffn = _loadprofiler(self._ui, profiler) |
165 fp = util.stringio() | 164 if proffn is None: |
166 elif output: | 165 self._ui.warn(_("unrecognized profiler '%s' - ignored\n") |
167 path = ui.expandpath(output) | 166 % profiler) |
168 fp = open(path, 'wb') | 167 profiler = 'stat' |
169 else: | 168 |
170 fp = ui.ferr | 169 self._output = self._ui.config('profiling', 'output') |
171 | 170 |
172 try: | 171 if self._output == 'blackbox': |
172 self._fp = util.stringio() | |
173 elif self._output: | |
174 path = self._ui.expandpath(self._output) | |
175 self._fp = open(path, 'wb') | |
176 else: | |
177 self._fp = self._ui.ferr | |
178 | |
173 if proffn is not None: | 179 if proffn is not None: |
174 pass | 180 pass |
175 elif profiler == 'ls': | 181 elif profiler == 'ls': |
176 proffn = lsprofile | 182 proffn = lsprofile |
177 elif profiler == 'flame': | 183 elif profiler == 'flame': |
178 proffn = flameprofile | 184 proffn = flameprofile |
179 else: | 185 else: |
180 proffn = statprofile | 186 proffn = statprofile |
181 | 187 |
182 with proffn(ui, fp): | 188 self._profiler = proffn(self._ui, self._fp) |
183 yield | 189 self._profiler.__enter__() |
184 | 190 |
185 finally: | 191 def __exit__(self, exception_type, exception_value, traceback): |
186 if output: | 192 if self._profiler is None: |
187 if output == 'blackbox': | 193 return |
188 val = 'Profile:\n%s' % fp.getvalue() | 194 self._profiler.__exit__(exception_type, exception_value, traceback) |
195 if self._output: | |
196 if self._output == 'blackbox': | |
197 val = 'Profile:\n%s' % self._fp.getvalue() | |
189 # ui.log treats the input as a format string, | 198 # ui.log treats the input as a format string, |
190 # so we need to escape any % signs. | 199 # so we need to escape any % signs. |
191 val = val.replace('%', '%%') | 200 val = val.replace('%', '%%') |
192 ui.log('profile', val) | 201 self._ui.log('profile', val) |
193 fp.close() | 202 self._fp.close() |
194 | 203 |
195 @contextlib.contextmanager | 204 @contextlib.contextmanager |
196 def maybeprofile(ui): | 205 def maybeprofile(ui): |
197 """Profile if enabled, else do nothing. | 206 """Profile if enabled, else do nothing. |
198 | 207 |