##// END OF EJS Templates
profile: properly propagate exception from the sub-context manager...
marmoute -
r32810:6675d23d default
parent child Browse files
Show More
@@ -1,229 +1,232
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 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._fpdoclose = True
155 155 self._profiler = None
156 156 self._enabled = enabled
157 157 self._entered = False
158 158 self._started = False
159 159
160 160 def __enter__(self):
161 161 self._entered = True
162 162 if self._enabled:
163 163 self.start()
164 164 return self
165 165
166 166 def start(self):
167 167 """Start profiling.
168 168
169 169 The profiling will stop at the context exit.
170 170
171 171 If the profiler was already started, this has no effect."""
172 172 if not self._entered:
173 173 raise error.ProgrammingError()
174 174 if self._started:
175 175 return
176 176 self._started = True
177 177 profiler = encoding.environ.get('HGPROF')
178 178 proffn = None
179 179 if profiler is None:
180 180 profiler = self._ui.config('profiling', 'type', default='stat')
181 181 if profiler not in ('ls', 'stat', 'flame'):
182 182 # try load profiler from extension with the same name
183 183 proffn = _loadprofiler(self._ui, profiler)
184 184 if proffn is None:
185 185 self._ui.warn(_("unrecognized profiler '%s' - ignored\n")
186 186 % profiler)
187 187 profiler = 'stat'
188 188
189 189 self._output = self._ui.config('profiling', 'output')
190 190
191 191 try:
192 192 if self._output == 'blackbox':
193 193 self._fp = util.stringio()
194 194 elif self._output:
195 195 path = self._ui.expandpath(self._output)
196 196 self._fp = open(path, 'wb')
197 197 else:
198 198 self._fpdoclose = False
199 199 self._fp = self._ui.ferr
200 200
201 201 if proffn is not None:
202 202 pass
203 203 elif profiler == 'ls':
204 204 proffn = lsprofile
205 205 elif profiler == 'flame':
206 206 proffn = flameprofile
207 207 else:
208 208 proffn = statprofile
209 209
210 210 self._profiler = proffn(self._ui, self._fp)
211 211 self._profiler.__enter__()
212 212 except: # re-raises
213 213 self._closefp()
214 214 raise
215 215
216 216 def __exit__(self, exception_type, exception_value, traceback):
217 propagate = None
217 218 if self._profiler is not None:
218 self._profiler.__exit__(exception_type, exception_value, traceback)
219 propagate = self._profiler.__exit__(exception_type, exception_value,
220 traceback)
219 221 if self._output == 'blackbox':
220 222 val = 'Profile:\n%s' % self._fp.getvalue()
221 223 # ui.log treats the input as a format string,
222 224 # so we need to escape any % signs.
223 225 val = val.replace('%', '%%')
224 226 self._ui.log('profile', val)
225 227 self._closefp()
228 return propagate
226 229
227 230 def _closefp(self):
228 231 if self._fpdoclose and self._fp is not None:
229 232 self._fp.close()
General Comments 0
You need to be logged in to leave comments. Login now