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