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