##// END OF EJS Templates
profiling: flush stdout before writing profile to stderr...
Kyle Lippincott -
r44653:d6d41708 default
parent child Browse files
Show More
@@ -1,282 +1,290 b''
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 .pycompat import (
13 from .pycompat import (
14 getattr,
14 getattr,
15 open,
15 open,
16 )
16 )
17 from . import (
17 from . import (
18 encoding,
18 encoding,
19 error,
19 error,
20 extensions,
20 extensions,
21 pycompat,
21 pycompat,
22 util,
22 util,
23 )
23 )
24
24
25
25
26 def _loadprofiler(ui, profiler):
26 def _loadprofiler(ui, profiler):
27 """load profiler extension. return profile method, or None on failure"""
27 """load profiler extension. return profile method, or None on failure"""
28 extname = profiler
28 extname = profiler
29 extensions.loadall(ui, whitelist=[extname])
29 extensions.loadall(ui, whitelist=[extname])
30 try:
30 try:
31 mod = extensions.find(extname)
31 mod = extensions.find(extname)
32 except KeyError:
32 except KeyError:
33 return None
33 return None
34 else:
34 else:
35 return getattr(mod, 'profile', None)
35 return getattr(mod, 'profile', None)
36
36
37
37
38 @contextlib.contextmanager
38 @contextlib.contextmanager
39 def lsprofile(ui, fp):
39 def lsprofile(ui, fp):
40 format = ui.config(b'profiling', b'format')
40 format = ui.config(b'profiling', b'format')
41 field = ui.config(b'profiling', b'sort')
41 field = ui.config(b'profiling', b'sort')
42 limit = ui.configint(b'profiling', b'limit')
42 limit = ui.configint(b'profiling', b'limit')
43 climit = ui.configint(b'profiling', b'nested')
43 climit = ui.configint(b'profiling', b'nested')
44
44
45 if format not in [b'text', b'kcachegrind']:
45 if format not in [b'text', b'kcachegrind']:
46 ui.warn(_(b"unrecognized profiling format '%s' - Ignored\n") % format)
46 ui.warn(_(b"unrecognized profiling format '%s' - Ignored\n") % format)
47 format = b'text'
47 format = b'text'
48
48
49 try:
49 try:
50 from . import lsprof
50 from . import lsprof
51 except ImportError:
51 except ImportError:
52 raise error.Abort(
52 raise error.Abort(
53 _(
53 _(
54 b'lsprof not available - install from '
54 b'lsprof not available - install from '
55 b'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'
55 b'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'
56 )
56 )
57 )
57 )
58 p = lsprof.Profiler()
58 p = lsprof.Profiler()
59 p.enable(subcalls=True)
59 p.enable(subcalls=True)
60 try:
60 try:
61 yield
61 yield
62 finally:
62 finally:
63 p.disable()
63 p.disable()
64
64
65 if format == b'kcachegrind':
65 if format == b'kcachegrind':
66 from . import lsprofcalltree
66 from . import lsprofcalltree
67
67
68 calltree = lsprofcalltree.KCacheGrind(p)
68 calltree = lsprofcalltree.KCacheGrind(p)
69 calltree.output(fp)
69 calltree.output(fp)
70 else:
70 else:
71 # format == 'text'
71 # format == 'text'
72 stats = lsprof.Stats(p.getstats())
72 stats = lsprof.Stats(p.getstats())
73 stats.sort(pycompat.sysstr(field))
73 stats.sort(pycompat.sysstr(field))
74 stats.pprint(limit=limit, file=fp, climit=climit)
74 stats.pprint(limit=limit, file=fp, climit=climit)
75
75
76
76
77 @contextlib.contextmanager
77 @contextlib.contextmanager
78 def flameprofile(ui, fp):
78 def flameprofile(ui, fp):
79 try:
79 try:
80 from flamegraph import flamegraph # pytype: disable=import-error
80 from flamegraph import flamegraph # pytype: disable=import-error
81 except ImportError:
81 except ImportError:
82 raise error.Abort(
82 raise error.Abort(
83 _(
83 _(
84 b'flamegraph not available - install from '
84 b'flamegraph not available - install from '
85 b'https://github.com/evanhempel/python-flamegraph'
85 b'https://github.com/evanhempel/python-flamegraph'
86 )
86 )
87 )
87 )
88 # developer config: profiling.freq
88 # developer config: profiling.freq
89 freq = ui.configint(b'profiling', b'freq')
89 freq = ui.configint(b'profiling', b'freq')
90 filter_ = None
90 filter_ = None
91 collapse_recursion = True
91 collapse_recursion = True
92 thread = flamegraph.ProfileThread(
92 thread = flamegraph.ProfileThread(
93 fp, 1.0 / freq, filter_, collapse_recursion
93 fp, 1.0 / freq, filter_, collapse_recursion
94 )
94 )
95 start_time = util.timer()
95 start_time = util.timer()
96 try:
96 try:
97 thread.start()
97 thread.start()
98 yield
98 yield
99 finally:
99 finally:
100 thread.stop()
100 thread.stop()
101 thread.join()
101 thread.join()
102 print(
102 print(
103 b'Collected %d stack frames (%d unique) in %2.2f seconds.'
103 b'Collected %d stack frames (%d unique) in %2.2f seconds.'
104 % (
104 % (
105 util.timer() - start_time,
105 util.timer() - start_time,
106 thread.num_frames(),
106 thread.num_frames(),
107 thread.num_frames(unique=True),
107 thread.num_frames(unique=True),
108 )
108 )
109 )
109 )
110
110
111
111
112 @contextlib.contextmanager
112 @contextlib.contextmanager
113 def statprofile(ui, fp):
113 def statprofile(ui, fp):
114 from . import statprof
114 from . import statprof
115
115
116 freq = ui.configint(b'profiling', b'freq')
116 freq = ui.configint(b'profiling', b'freq')
117 if freq > 0:
117 if freq > 0:
118 # Cannot reset when profiler is already active. So silently no-op.
118 # Cannot reset when profiler is already active. So silently no-op.
119 if statprof.state.profile_level == 0:
119 if statprof.state.profile_level == 0:
120 statprof.reset(freq)
120 statprof.reset(freq)
121 else:
121 else:
122 ui.warn(_(b"invalid sampling frequency '%s' - ignoring\n") % freq)
122 ui.warn(_(b"invalid sampling frequency '%s' - ignoring\n") % freq)
123
123
124 track = ui.config(
124 track = ui.config(
125 b'profiling', b'time-track', pycompat.iswindows and b'cpu' or b'real'
125 b'profiling', b'time-track', pycompat.iswindows and b'cpu' or b'real'
126 )
126 )
127 statprof.start(mechanism=b'thread', track=track)
127 statprof.start(mechanism=b'thread', track=track)
128
128
129 try:
129 try:
130 yield
130 yield
131 finally:
131 finally:
132 data = statprof.stop()
132 data = statprof.stop()
133
133
134 profformat = ui.config(b'profiling', b'statformat')
134 profformat = ui.config(b'profiling', b'statformat')
135
135
136 formats = {
136 formats = {
137 b'byline': statprof.DisplayFormats.ByLine,
137 b'byline': statprof.DisplayFormats.ByLine,
138 b'bymethod': statprof.DisplayFormats.ByMethod,
138 b'bymethod': statprof.DisplayFormats.ByMethod,
139 b'hotpath': statprof.DisplayFormats.Hotpath,
139 b'hotpath': statprof.DisplayFormats.Hotpath,
140 b'json': statprof.DisplayFormats.Json,
140 b'json': statprof.DisplayFormats.Json,
141 b'chrome': statprof.DisplayFormats.Chrome,
141 b'chrome': statprof.DisplayFormats.Chrome,
142 }
142 }
143
143
144 if profformat in formats:
144 if profformat in formats:
145 displayformat = formats[profformat]
145 displayformat = formats[profformat]
146 else:
146 else:
147 ui.warn(_(b'unknown profiler output format: %s\n') % profformat)
147 ui.warn(_(b'unknown profiler output format: %s\n') % profformat)
148 displayformat = statprof.DisplayFormats.Hotpath
148 displayformat = statprof.DisplayFormats.Hotpath
149
149
150 kwargs = {}
150 kwargs = {}
151
151
152 def fraction(s):
152 def fraction(s):
153 if isinstance(s, (float, int)):
153 if isinstance(s, (float, int)):
154 return float(s)
154 return float(s)
155 if s.endswith(b'%'):
155 if s.endswith(b'%'):
156 v = float(s[:-1]) / 100
156 v = float(s[:-1]) / 100
157 else:
157 else:
158 v = float(s)
158 v = float(s)
159 if 0 <= v <= 1:
159 if 0 <= v <= 1:
160 return v
160 return v
161 raise ValueError(s)
161 raise ValueError(s)
162
162
163 if profformat == b'chrome':
163 if profformat == b'chrome':
164 showmin = ui.configwith(fraction, b'profiling', b'showmin', 0.005)
164 showmin = ui.configwith(fraction, b'profiling', b'showmin', 0.005)
165 showmax = ui.configwith(fraction, b'profiling', b'showmax')
165 showmax = ui.configwith(fraction, b'profiling', b'showmax')
166 kwargs.update(minthreshold=showmin, maxthreshold=showmax)
166 kwargs.update(minthreshold=showmin, maxthreshold=showmax)
167 elif profformat == b'hotpath':
167 elif profformat == b'hotpath':
168 # inconsistent config: profiling.showmin
168 # inconsistent config: profiling.showmin
169 limit = ui.configwith(fraction, b'profiling', b'showmin', 0.05)
169 limit = ui.configwith(fraction, b'profiling', b'showmin', 0.05)
170 kwargs['limit'] = limit
170 kwargs['limit'] = limit
171 showtime = ui.configbool(b'profiling', b'showtime')
171 showtime = ui.configbool(b'profiling', b'showtime')
172 kwargs['showtime'] = showtime
172 kwargs['showtime'] = showtime
173
173
174 statprof.display(fp, data=data, format=displayformat, **kwargs)
174 statprof.display(fp, data=data, format=displayformat, **kwargs)
175
175
176
176
177 class profile(object):
177 class profile(object):
178 """Start profiling.
178 """Start profiling.
179
179
180 Profiling is active when the context manager is active. When the context
180 Profiling is active when the context manager is active. When the context
181 manager exits, profiling results will be written to the configured output.
181 manager exits, profiling results will be written to the configured output.
182 """
182 """
183
183
184 def __init__(self, ui, enabled=True):
184 def __init__(self, ui, enabled=True):
185 self._ui = ui
185 self._ui = ui
186 self._output = None
186 self._output = None
187 self._fp = None
187 self._fp = None
188 self._fpdoclose = True
188 self._fpdoclose = True
189 self._flushfp = None
189 self._profiler = None
190 self._profiler = None
190 self._enabled = enabled
191 self._enabled = enabled
191 self._entered = False
192 self._entered = False
192 self._started = False
193 self._started = False
193
194
194 def __enter__(self):
195 def __enter__(self):
195 self._entered = True
196 self._entered = True
196 if self._enabled:
197 if self._enabled:
197 self.start()
198 self.start()
198 return self
199 return self
199
200
200 def start(self):
201 def start(self):
201 """Start profiling.
202 """Start profiling.
202
203
203 The profiling will stop at the context exit.
204 The profiling will stop at the context exit.
204
205
205 If the profiler was already started, this has no effect."""
206 If the profiler was already started, this has no effect."""
206 if not self._entered:
207 if not self._entered:
207 raise error.ProgrammingError(b'use a context manager to start')
208 raise error.ProgrammingError(b'use a context manager to start')
208 if self._started:
209 if self._started:
209 return
210 return
210 self._started = True
211 self._started = True
211 profiler = encoding.environ.get(b'HGPROF')
212 profiler = encoding.environ.get(b'HGPROF')
212 proffn = None
213 proffn = None
213 if profiler is None:
214 if profiler is None:
214 profiler = self._ui.config(b'profiling', b'type')
215 profiler = self._ui.config(b'profiling', b'type')
215 if profiler not in (b'ls', b'stat', b'flame'):
216 if profiler not in (b'ls', b'stat', b'flame'):
216 # try load profiler from extension with the same name
217 # try load profiler from extension with the same name
217 proffn = _loadprofiler(self._ui, profiler)
218 proffn = _loadprofiler(self._ui, profiler)
218 if proffn is None:
219 if proffn is None:
219 self._ui.warn(
220 self._ui.warn(
220 _(b"unrecognized profiler '%s' - ignored\n") % profiler
221 _(b"unrecognized profiler '%s' - ignored\n") % profiler
221 )
222 )
222 profiler = b'stat'
223 profiler = b'stat'
223
224
224 self._output = self._ui.config(b'profiling', b'output')
225 self._output = self._ui.config(b'profiling', b'output')
225
226
226 try:
227 try:
227 if self._output == b'blackbox':
228 if self._output == b'blackbox':
228 self._fp = util.stringio()
229 self._fp = util.stringio()
229 elif self._output:
230 elif self._output:
230 path = self._ui.expandpath(self._output)
231 path = self._ui.expandpath(self._output)
231 self._fp = open(path, b'wb')
232 self._fp = open(path, b'wb')
232 elif pycompat.iswindows:
233 elif pycompat.iswindows:
233 # parse escape sequence by win32print()
234 # parse escape sequence by win32print()
234 class uifp(object):
235 class uifp(object):
235 def __init__(self, ui):
236 def __init__(self, ui):
236 self._ui = ui
237 self._ui = ui
237
238
238 def write(self, data):
239 def write(self, data):
239 self._ui.write_err(data)
240 self._ui.write_err(data)
240
241
241 def flush(self):
242 def flush(self):
242 self._ui.flush()
243 self._ui.flush()
243
244
244 self._fpdoclose = False
245 self._fpdoclose = False
245 self._fp = uifp(self._ui)
246 self._fp = uifp(self._ui)
246 else:
247 else:
247 self._fpdoclose = False
248 self._fpdoclose = False
248 self._fp = self._ui.ferr
249 self._fp = self._ui.ferr
250 # Ensure we've flushed fout before writing to ferr.
251 self._flushfp = self._ui.fout
249
252
250 if proffn is not None:
253 if proffn is not None:
251 pass
254 pass
252 elif profiler == b'ls':
255 elif profiler == b'ls':
253 proffn = lsprofile
256 proffn = lsprofile
254 elif profiler == b'flame':
257 elif profiler == b'flame':
255 proffn = flameprofile
258 proffn = flameprofile
256 else:
259 else:
257 proffn = statprofile
260 proffn = statprofile
258
261
259 self._profiler = proffn(self._ui, self._fp)
262 self._profiler = proffn(self._ui, self._fp)
260 self._profiler.__enter__()
263 self._profiler.__enter__()
261 except: # re-raises
264 except: # re-raises
262 self._closefp()
265 self._closefp()
263 raise
266 raise
264
267
265 def __exit__(self, exception_type, exception_value, traceback):
268 def __exit__(self, exception_type, exception_value, traceback):
266 propagate = None
269 propagate = None
267 if self._profiler is not None:
270 if self._profiler is not None:
271 self._uiflush()
268 propagate = self._profiler.__exit__(
272 propagate = self._profiler.__exit__(
269 exception_type, exception_value, traceback
273 exception_type, exception_value, traceback
270 )
274 )
271 if self._output == b'blackbox':
275 if self._output == b'blackbox':
272 val = b'Profile:\n%s' % self._fp.getvalue()
276 val = b'Profile:\n%s' % self._fp.getvalue()
273 # ui.log treats the input as a format string,
277 # ui.log treats the input as a format string,
274 # so we need to escape any % signs.
278 # so we need to escape any % signs.
275 val = val.replace(b'%', b'%%')
279 val = val.replace(b'%', b'%%')
276 self._ui.log(b'profile', val)
280 self._ui.log(b'profile', val)
277 self._closefp()
281 self._closefp()
278 return propagate
282 return propagate
279
283
280 def _closefp(self):
284 def _closefp(self):
281 if self._fpdoclose and self._fp is not None:
285 if self._fpdoclose and self._fp is not None:
282 self._fp.close()
286 self._fp.close()
287
288 def _uiflush(self):
289 if self._flushfp:
290 self._flushfp.flush()
General Comments 0
You need to be logged in to leave comments. Login now