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