##// END OF EJS Templates
branching: merge with stable
marmoute -
r52735:3785814b merge default
parent child Browse files
Show More
@@ -1,340 +1,357
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
8
9 import contextlib
9 import contextlib
10 import os
10 import os
11 import signal
11 import signal
12 import subprocess
12 import subprocess
13 import sys
13
14
14 from .i18n import _
15 from .i18n import _
15 from .pycompat import (
16 from .pycompat import (
16 open,
17 open,
17 )
18 )
18 from . import (
19 from . import (
19 encoding,
20 encoding,
20 error,
21 error,
21 extensions,
22 extensions,
22 pycompat,
23 pycompat,
23 util,
24 util,
24 )
25 )
25
26
26
27
27 def _loadprofiler(ui, profiler):
28 def _loadprofiler(ui, profiler):
28 """load profiler extension. return profile method, or None on failure"""
29 """load profiler extension. return profile method, or None on failure"""
29 extname = profiler
30 extname = profiler
30 extensions.loadall(ui, whitelist=[extname])
31 extensions.loadall(ui, whitelist=[extname])
31 try:
32 try:
32 mod = extensions.find(extname)
33 mod = extensions.find(extname)
33 except KeyError:
34 except KeyError:
34 return None
35 return None
35 else:
36 else:
36 return getattr(mod, 'profile', None)
37 return getattr(mod, 'profile', None)
37
38
38
39
39 @contextlib.contextmanager
40 @contextlib.contextmanager
40 def lsprofile(ui, fp):
41 def lsprofile(ui, fp):
41 format = ui.config(b'profiling', b'format')
42 format = ui.config(b'profiling', b'format')
42 field = ui.config(b'profiling', b'sort')
43 field = ui.config(b'profiling', b'sort')
43 limit = ui.configint(b'profiling', b'limit')
44 limit = ui.configint(b'profiling', b'limit')
44 climit = ui.configint(b'profiling', b'nested')
45 climit = ui.configint(b'profiling', b'nested')
45
46
46 if format not in [b'text', b'kcachegrind']:
47 if format not in [b'text', b'kcachegrind']:
47 ui.warn(_(b"unrecognized profiling format '%s' - Ignored\n") % format)
48 ui.warn(_(b"unrecognized profiling format '%s' - Ignored\n") % format)
48 format = b'text'
49 format = b'text'
49
50
50 try:
51 try:
51 from . import lsprof
52 from . import lsprof
52 except ImportError:
53 except ImportError:
53 raise error.Abort(
54 raise error.Abort(
54 _(
55 _(
55 b'lsprof not available - install from '
56 b'lsprof not available - install from '
56 b'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'
57 b'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'
57 )
58 )
58 )
59 )
59 p = lsprof.Profiler()
60 p = lsprof.Profiler()
60 p.enable(subcalls=True)
61 try:
62 p.enable(subcalls=True)
63 except ValueError as exc:
64 if str(exc) != "Another profiling tool is already active":
65 raise
66 if not hasattr(sys, "monitoring"):
67 raise
68 # python >=3.12 prevent more than one profiler to run at the same
69 # time, tries to improve the report to help the user understand
70 # what is going on.
71 other_tool_name = sys.monitoring.get_tool(sys.monitoring.PROFILER_ID)
72 if other_tool_name == "cProfile":
73 msg = 'cannot recursively call `lsprof`'
74 raise error.Abort(msg) from None
75 else:
76 m = 'failed to start "lsprofile"; another profiler already running: %s'
77 raise error.Abort(_(m) % other_tool_name) from None
61 try:
78 try:
62 yield
79 yield
63 finally:
80 finally:
64 p.disable()
81 p.disable()
65
82
66 if format == b'kcachegrind':
83 if format == b'kcachegrind':
67 from . import lsprofcalltree
84 from . import lsprofcalltree
68
85
69 calltree = lsprofcalltree.KCacheGrind(p)
86 calltree = lsprofcalltree.KCacheGrind(p)
70 calltree.output(fp)
87 calltree.output(fp)
71 else:
88 else:
72 # format == 'text'
89 # format == 'text'
73 stats = lsprof.Stats(p.getstats())
90 stats = lsprof.Stats(p.getstats())
74 stats.sort(pycompat.sysstr(field))
91 stats.sort(pycompat.sysstr(field))
75 stats.pprint(limit=limit, file=fp, climit=climit)
92 stats.pprint(limit=limit, file=fp, climit=climit)
76 fp.flush()
93 fp.flush()
77
94
78
95
79 @contextlib.contextmanager
96 @contextlib.contextmanager
80 def flameprofile(ui, fp):
97 def flameprofile(ui, fp):
81 try:
98 try:
82 from flamegraph import flamegraph # pytype: disable=import-error
99 from flamegraph import flamegraph # pytype: disable=import-error
83 except ImportError:
100 except ImportError:
84 raise error.Abort(
101 raise error.Abort(
85 _(
102 _(
86 b'flamegraph not available - install from '
103 b'flamegraph not available - install from '
87 b'https://github.com/evanhempel/python-flamegraph'
104 b'https://github.com/evanhempel/python-flamegraph'
88 )
105 )
89 )
106 )
90 # developer config: profiling.freq
107 # developer config: profiling.freq
91 freq = ui.configint(b'profiling', b'freq')
108 freq = ui.configint(b'profiling', b'freq')
92 filter_ = None
109 filter_ = None
93 collapse_recursion = True
110 collapse_recursion = True
94 thread = flamegraph.ProfileThread(
111 thread = flamegraph.ProfileThread(
95 fp, 1.0 / freq, filter_, collapse_recursion
112 fp, 1.0 / freq, filter_, collapse_recursion
96 )
113 )
97 start_time = util.timer()
114 start_time = util.timer()
98 try:
115 try:
99 thread.start()
116 thread.start()
100 yield
117 yield
101 finally:
118 finally:
102 thread.stop()
119 thread.stop()
103 thread.join()
120 thread.join()
104 m = b'Collected %d stack frames (%d unique) in %2.2f seconds.'
121 m = b'Collected %d stack frames (%d unique) in %2.2f seconds.'
105 m %= (
122 m %= (
106 (
123 (
107 util.timer() - start_time,
124 util.timer() - start_time,
108 thread.num_frames(),
125 thread.num_frames(),
109 thread.num_frames(unique=True),
126 thread.num_frames(unique=True),
110 ),
127 ),
111 )
128 )
112 print(m, flush=True)
129 print(m, flush=True)
113
130
114
131
115 @contextlib.contextmanager
132 @contextlib.contextmanager
116 def statprofile(ui, fp):
133 def statprofile(ui, fp):
117 from . import statprof
134 from . import statprof
118
135
119 freq = ui.configint(b'profiling', b'freq')
136 freq = ui.configint(b'profiling', b'freq')
120 if freq > 0:
137 if freq > 0:
121 # Cannot reset when profiler is already active. So silently no-op.
138 # Cannot reset when profiler is already active. So silently no-op.
122 if statprof.state.profile_level == 0:
139 if statprof.state.profile_level == 0:
123 statprof.reset(freq)
140 statprof.reset(freq)
124 else:
141 else:
125 ui.warn(_(b"invalid sampling frequency '%s' - ignoring\n") % freq)
142 ui.warn(_(b"invalid sampling frequency '%s' - ignoring\n") % freq)
126
143
127 track = ui.config(
144 track = ui.config(
128 b'profiling', b'time-track', pycompat.iswindows and b'cpu' or b'real'
145 b'profiling', b'time-track', pycompat.iswindows and b'cpu' or b'real'
129 )
146 )
130 statprof.start(mechanism=b'thread', track=track)
147 statprof.start(mechanism=b'thread', track=track)
131
148
132 try:
149 try:
133 yield
150 yield
134 finally:
151 finally:
135 data = statprof.stop()
152 data = statprof.stop()
136
153
137 profformat = ui.config(b'profiling', b'statformat')
154 profformat = ui.config(b'profiling', b'statformat')
138
155
139 formats = {
156 formats = {
140 b'byline': statprof.DisplayFormats.ByLine,
157 b'byline': statprof.DisplayFormats.ByLine,
141 b'bymethod': statprof.DisplayFormats.ByMethod,
158 b'bymethod': statprof.DisplayFormats.ByMethod,
142 b'hotpath': statprof.DisplayFormats.Hotpath,
159 b'hotpath': statprof.DisplayFormats.Hotpath,
143 b'json': statprof.DisplayFormats.Json,
160 b'json': statprof.DisplayFormats.Json,
144 b'chrome': statprof.DisplayFormats.Chrome,
161 b'chrome': statprof.DisplayFormats.Chrome,
145 }
162 }
146
163
147 if profformat in formats:
164 if profformat in formats:
148 displayformat = formats[profformat]
165 displayformat = formats[profformat]
149 else:
166 else:
150 ui.warn(_(b'unknown profiler output format: %s\n') % profformat)
167 ui.warn(_(b'unknown profiler output format: %s\n') % profformat)
151 displayformat = statprof.DisplayFormats.Hotpath
168 displayformat = statprof.DisplayFormats.Hotpath
152
169
153 kwargs = {}
170 kwargs = {}
154
171
155 def fraction(s):
172 def fraction(s):
156 if isinstance(s, (float, int)):
173 if isinstance(s, (float, int)):
157 return float(s)
174 return float(s)
158 if s.endswith(b'%'):
175 if s.endswith(b'%'):
159 v = float(s[:-1]) / 100
176 v = float(s[:-1]) / 100
160 else:
177 else:
161 v = float(s)
178 v = float(s)
162 if 0 <= v <= 1:
179 if 0 <= v <= 1:
163 return v
180 return v
164 raise ValueError(s)
181 raise ValueError(s)
165
182
166 if profformat == b'chrome':
183 if profformat == b'chrome':
167 showmin = ui.configwith(fraction, b'profiling', b'showmin', 0.005)
184 showmin = ui.configwith(fraction, b'profiling', b'showmin', 0.005)
168 showmax = ui.configwith(fraction, b'profiling', b'showmax')
185 showmax = ui.configwith(fraction, b'profiling', b'showmax')
169 kwargs.update(minthreshold=showmin, maxthreshold=showmax)
186 kwargs.update(minthreshold=showmin, maxthreshold=showmax)
170 elif profformat == b'hotpath':
187 elif profformat == b'hotpath':
171 # inconsistent config: profiling.showmin
188 # inconsistent config: profiling.showmin
172 limit = ui.configwith(fraction, b'profiling', b'showmin', 0.05)
189 limit = ui.configwith(fraction, b'profiling', b'showmin', 0.05)
173 kwargs['limit'] = limit
190 kwargs['limit'] = limit
174 showtime = ui.configbool(b'profiling', b'showtime')
191 showtime = ui.configbool(b'profiling', b'showtime')
175 kwargs['showtime'] = showtime
192 kwargs['showtime'] = showtime
176
193
177 statprof.display(fp, data=data, format=displayformat, **kwargs)
194 statprof.display(fp, data=data, format=displayformat, **kwargs)
178 fp.flush()
195 fp.flush()
179
196
180
197
181 @contextlib.contextmanager
198 @contextlib.contextmanager
182 def pyspy_profile(ui, fp):
199 def pyspy_profile(ui, fp):
183 exe = ui.config(b'profiling', b'py-spy.exe')
200 exe = ui.config(b'profiling', b'py-spy.exe')
184
201
185 freq = ui.configint(b'profiling', b'py-spy.freq')
202 freq = ui.configint(b'profiling', b'py-spy.freq')
186
203
187 format = ui.config(b'profiling', b'py-spy.format')
204 format = ui.config(b'profiling', b'py-spy.format')
188
205
189 fd = fp.fileno()
206 fd = fp.fileno()
190
207
191 output_path = "/dev/fd/%d" % (fd)
208 output_path = "/dev/fd/%d" % (fd)
192
209
193 my_pid = os.getpid()
210 my_pid = os.getpid()
194
211
195 cmd = [
212 cmd = [
196 exe,
213 exe,
197 "record",
214 "record",
198 "--pid",
215 "--pid",
199 str(my_pid),
216 str(my_pid),
200 "--native",
217 "--native",
201 "--rate",
218 "--rate",
202 str(freq),
219 str(freq),
203 "--output",
220 "--output",
204 output_path,
221 output_path,
205 ]
222 ]
206
223
207 if format:
224 if format:
208 cmd.extend(["--format", format])
225 cmd.extend(["--format", format])
209
226
210 proc = subprocess.Popen(
227 proc = subprocess.Popen(
211 cmd,
228 cmd,
212 pass_fds={fd},
229 pass_fds={fd},
213 stdout=subprocess.PIPE,
230 stdout=subprocess.PIPE,
214 )
231 )
215
232
216 _ = proc.stdout.readline()
233 _ = proc.stdout.readline()
217
234
218 try:
235 try:
219 yield
236 yield
220 finally:
237 finally:
221 os.kill(proc.pid, signal.SIGINT)
238 os.kill(proc.pid, signal.SIGINT)
222 proc.communicate()
239 proc.communicate()
223
240
224
241
225 class profile:
242 class profile:
226 """Start profiling.
243 """Start profiling.
227
244
228 Profiling is active when the context manager is active. When the context
245 Profiling is active when the context manager is active. When the context
229 manager exits, profiling results will be written to the configured output.
246 manager exits, profiling results will be written to the configured output.
230 """
247 """
231
248
232 def __init__(self, ui, enabled=True):
249 def __init__(self, ui, enabled=True):
233 self._ui = ui
250 self._ui = ui
234 self._output = None
251 self._output = None
235 self._fp = None
252 self._fp = None
236 self._fpdoclose = True
253 self._fpdoclose = True
237 self._flushfp = None
254 self._flushfp = None
238 self._profiler = None
255 self._profiler = None
239 self._enabled = enabled
256 self._enabled = enabled
240 self._entered = False
257 self._entered = False
241 self._started = False
258 self._started = False
242
259
243 def __enter__(self):
260 def __enter__(self):
244 self._entered = True
261 self._entered = True
245 if self._enabled:
262 if self._enabled:
246 self.start()
263 self.start()
247 return self
264 return self
248
265
249 def start(self):
266 def start(self):
250 """Start profiling.
267 """Start profiling.
251
268
252 The profiling will stop at the context exit.
269 The profiling will stop at the context exit.
253
270
254 If the profiler was already started, this has no effect."""
271 If the profiler was already started, this has no effect."""
255 if not self._entered:
272 if not self._entered:
256 raise error.ProgrammingError(b'use a context manager to start')
273 raise error.ProgrammingError(b'use a context manager to start')
257 if self._started:
274 if self._started:
258 return
275 return
259 self._started = True
276 self._started = True
260 profiler = encoding.environ.get(b'HGPROF')
277 profiler = encoding.environ.get(b'HGPROF')
261 proffn = None
278 proffn = None
262 if profiler is None:
279 if profiler is None:
263 profiler = self._ui.config(b'profiling', b'type')
280 profiler = self._ui.config(b'profiling', b'type')
264 if profiler not in (b'ls', b'stat', b'flame', b'py-spy'):
281 if profiler not in (b'ls', b'stat', b'flame', b'py-spy'):
265 # try load profiler from extension with the same name
282 # try load profiler from extension with the same name
266 proffn = _loadprofiler(self._ui, profiler)
283 proffn = _loadprofiler(self._ui, profiler)
267 if proffn is None:
284 if proffn is None:
268 self._ui.warn(
285 self._ui.warn(
269 _(b"unrecognized profiler '%s' - ignored\n") % profiler
286 _(b"unrecognized profiler '%s' - ignored\n") % profiler
270 )
287 )
271 profiler = b'stat'
288 profiler = b'stat'
272
289
273 self._output = self._ui.config(b'profiling', b'output')
290 self._output = self._ui.config(b'profiling', b'output')
274
291
275 try:
292 try:
276 if self._output == b'blackbox':
293 if self._output == b'blackbox':
277 self._fp = util.stringio()
294 self._fp = util.stringio()
278 elif self._output:
295 elif self._output:
279 path = util.expandpath(self._output)
296 path = util.expandpath(self._output)
280 self._fp = open(path, b'wb')
297 self._fp = open(path, b'wb')
281 elif pycompat.iswindows:
298 elif pycompat.iswindows:
282 # parse escape sequence by win32print()
299 # parse escape sequence by win32print()
283 class uifp:
300 class uifp:
284 def __init__(self, ui):
301 def __init__(self, ui):
285 self._ui = ui
302 self._ui = ui
286
303
287 def write(self, data):
304 def write(self, data):
288 self._ui.write_err(data)
305 self._ui.write_err(data)
289
306
290 def flush(self):
307 def flush(self):
291 self._ui.flush()
308 self._ui.flush()
292
309
293 self._fpdoclose = False
310 self._fpdoclose = False
294 self._fp = uifp(self._ui)
311 self._fp = uifp(self._ui)
295 else:
312 else:
296 self._fpdoclose = False
313 self._fpdoclose = False
297 self._fp = self._ui.ferr
314 self._fp = self._ui.ferr
298 # Ensure we've flushed fout before writing to ferr.
315 # Ensure we've flushed fout before writing to ferr.
299 self._flushfp = self._ui.fout
316 self._flushfp = self._ui.fout
300
317
301 if proffn is not None:
318 if proffn is not None:
302 pass
319 pass
303 elif profiler == b'ls':
320 elif profiler == b'ls':
304 proffn = lsprofile
321 proffn = lsprofile
305 elif profiler == b'flame':
322 elif profiler == b'flame':
306 proffn = flameprofile
323 proffn = flameprofile
307 elif profiler == b'py-spy':
324 elif profiler == b'py-spy':
308 proffn = pyspy_profile
325 proffn = pyspy_profile
309 else:
326 else:
310 proffn = statprofile
327 proffn = statprofile
311
328
312 self._profiler = proffn(self._ui, self._fp)
329 self._profiler = proffn(self._ui, self._fp)
313 self._profiler.__enter__()
330 self._profiler.__enter__()
314 except: # re-raises
331 except: # re-raises
315 self._closefp()
332 self._closefp()
316 raise
333 raise
317
334
318 def __exit__(self, exception_type, exception_value, traceback):
335 def __exit__(self, exception_type, exception_value, traceback):
319 propagate = None
336 propagate = None
320 if self._profiler is not None:
337 if self._profiler is not None:
321 self._uiflush()
338 self._uiflush()
322 propagate = self._profiler.__exit__(
339 propagate = self._profiler.__exit__(
323 exception_type, exception_value, traceback
340 exception_type, exception_value, traceback
324 )
341 )
325 if self._output == b'blackbox':
342 if self._output == b'blackbox':
326 val = b'Profile:\n%s' % self._fp.getvalue()
343 val = b'Profile:\n%s' % self._fp.getvalue()
327 # ui.log treats the input as a format string,
344 # ui.log treats the input as a format string,
328 # so we need to escape any % signs.
345 # so we need to escape any % signs.
329 val = val.replace(b'%', b'%%')
346 val = val.replace(b'%', b'%%')
330 self._ui.log(b'profile', val)
347 self._ui.log(b'profile', val)
331 self._closefp()
348 self._closefp()
332 return propagate
349 return propagate
333
350
334 def _closefp(self):
351 def _closefp(self):
335 if self._fpdoclose and self._fp is not None:
352 if self._fpdoclose and self._fp is not None:
336 self._fp.close()
353 self._fp.close()
337
354
338 def _uiflush(self):
355 def _uiflush(self):
339 if self._flushfp:
356 if self._flushfp:
340 self._flushfp.flush()
357 self._flushfp.flush()
@@ -1,180 +1,194
1 test --time
1 test --time
2
2
3 $ hg --time help -q help 2>&1 | grep time > /dev/null
3 $ hg --time help -q help 2>&1 | grep time > /dev/null
4 $ hg init a
4 $ hg init a
5 $ cd a
5 $ cd a
6
6
7 Function to check that statprof ran
7 Function to check that statprof ran
8 $ statprofran () {
8 $ statprofran () {
9 > grep -E 'Sample count:|No samples recorded' > /dev/null
9 > grep -E 'Sample count:|No samples recorded' > /dev/null
10 > }
10 > }
11
11
12 test --profile
12 test --profile
13
13
14 $ hg st --profile 2>&1 | statprofran
14 $ hg st --profile 2>&1 | statprofran
15
15
16 Abreviated version
16 Abreviated version
17
17
18 $ hg st --prof 2>&1 | statprofran
18 $ hg st --prof 2>&1 | statprofran
19
19
20 In alias
20 In alias
21
21
22 $ hg --config "alias.profst=status --profile" profst 2>&1 | statprofran
22 $ hg --config "alias.profst=status --profile" profst 2>&1 | statprofran
23
23
24 #if lsprof
24 #if lsprof
25
25
26 $ prof () {
26 $ prof () {
27 > hg --config profiling.type=ls --profile $@
27 > hg --config profiling.type=ls --profile $@
28 > }
28 > }
29
29
30 $ prof st 2>../out
30 $ prof st 2>../out
31 $ grep CallCount ../out > /dev/null || cat ../out
31 $ grep CallCount ../out > /dev/null || cat ../out
32
32
33 $ prof --config profiling.output=../out st
33 $ prof --config profiling.output=../out st
34 $ grep CallCount ../out > /dev/null || cat ../out
34 $ grep CallCount ../out > /dev/null || cat ../out
35
35
36 $ prof --config profiling.output=blackbox --config extensions.blackbox= st
36 $ prof --config profiling.output=blackbox --config extensions.blackbox= st
37 $ grep CallCount .hg/blackbox.log > /dev/null || cat .hg/blackbox.log
37 $ grep CallCount .hg/blackbox.log > /dev/null || cat .hg/blackbox.log
38
38
39 $ prof --config profiling.format=text st 2>../out
39 $ prof --config profiling.format=text st 2>../out
40 $ grep CallCount ../out > /dev/null || cat ../out
40 $ grep CallCount ../out > /dev/null || cat ../out
41
41
42 $ echo "[profiling]" >> $HGRCPATH
42 $ echo "[profiling]" >> $HGRCPATH
43 $ echo "format=kcachegrind" >> $HGRCPATH
43 $ echo "format=kcachegrind" >> $HGRCPATH
44
44
45 $ prof st 2>../out
45 $ prof st 2>../out
46 $ grep 'events: Ticks' ../out > /dev/null || cat ../out
46 $ grep 'events: Ticks' ../out > /dev/null || cat ../out
47
47
48 $ prof --config profiling.output=../out st
48 $ prof --config profiling.output=../out st
49 $ grep 'events: Ticks' ../out > /dev/null || cat ../out
49 $ grep 'events: Ticks' ../out > /dev/null || cat ../out
50
50
51 #endif
51 #endif
52
52
53 #if lsprof serve
53 #if serve
54
54
55 Profiling of HTTP requests works
55 Profiling of HTTP requests works
56
56
57 $ prof --config profiling.format=text --config profiling.output=../profile.log serve -d -p $HGPORT --pid-file ../hg.pid -A ../access.log
57 $ stats_prof () {
58 > hg --config profiling.type=stat --profile $@
59 > }
60
61 $ stats_prof \
62 > --config profiling.format=text \
63 > --config profiling.output=../profile.log \
64 > serve -d \
65 > -p $HGPORT \
66 > --pid-file ../hg.pid \
67 > -A ../access.log \
68 > --errorlog ../error.log
58 $ cat ../hg.pid >> $DAEMON_PIDS
69 $ cat ../hg.pid >> $DAEMON_PIDS
59 $ hg -q clone -U http://localhost:$HGPORT ../clone
70 $ hg -q clone -U http://localhost:$HGPORT ../clone
71 $ cat ../error.log
60
72
61 A single profile is logged because file logging doesn't append
73 A single profile is logged because file logging doesn't append
62 $ grep CallCount ../profile.log | wc -l
74 $ grep 'Sample count:' ../profile.log | wc -l
75 \s*1 (re)
76 $ grep 'Total time:' ../profile.log | wc -l
63 \s*1 (re)
77 \s*1 (re)
64
78
65 #endif
79 #endif
66
80
67 Install an extension that can sleep and guarantee a profiler has time to run
81 Install an extension that can sleep and guarantee a profiler has time to run
68
82
69 $ cat >> sleepext_with_a_long_filename.py << EOF
83 $ cat >> sleepext_with_a_long_filename.py << EOF
70 > import time
84 > import time
71 > from mercurial import registrar
85 > from mercurial import registrar
72 > cmdtable = {}
86 > cmdtable = {}
73 > command = registrar.command(cmdtable)
87 > command = registrar.command(cmdtable)
74 > @command(b'sleep', [], b'hg sleep')
88 > @command(b'sleep', [], b'hg sleep')
75 > def sleep_for_at_least_one_stat_cycle(ui, *args, **kwargs):
89 > def sleep_for_at_least_one_stat_cycle(ui, *args, **kwargs):
76 > t = time.time() # don't use time.sleep because we need CPU time
90 > t = time.time() # don't use time.sleep because we need CPU time
77 > while time.time() - t < 0.5:
91 > while time.time() - t < 0.5:
78 > pass
92 > pass
79 > EOF
93 > EOF
80
94
81 $ cat >> $HGRCPATH << EOF
95 $ cat >> $HGRCPATH << EOF
82 > [extensions]
96 > [extensions]
83 > sleep = `pwd`/sleepext_with_a_long_filename.py
97 > sleep = `pwd`/sleepext_with_a_long_filename.py
84 > EOF
98 > EOF
85
99
86 statistical profiler works
100 statistical profiler works
87
101
88 $ hg --profile sleep 2>../out
102 $ hg --profile sleep 2>../out
89 $ cat ../out | statprofran
103 $ cat ../out | statprofran
90
104
91 Various statprof formatters work
105 Various statprof formatters work
92
106
93 $ hg --profile --config profiling.statformat=byline sleep 2>../out || cat ../out
107 $ hg --profile --config profiling.statformat=byline sleep 2>../out || cat ../out
94 $ grep -v _path_stat ../out | head -n 3
108 $ grep -v _path_stat ../out | head -n 3
95 % cumulative self
109 % cumulative self
96 time seconds seconds name
110 time seconds seconds name
97 * sleepext_with_a_long_filename.py:*:sleep_for_at_least_one_stat_cycle (glob)
111 * sleepext_with_a_long_filename.py:*:sleep_for_at_least_one_stat_cycle (glob)
98 $ cat ../out | statprofran
112 $ cat ../out | statprofran
99
113
100 $ hg --profile --config profiling.statformat=bymethod sleep 2>../out || cat ../out
114 $ hg --profile --config profiling.statformat=bymethod sleep 2>../out || cat ../out
101 $ head -n 1 ../out
115 $ head -n 1 ../out
102 % cumulative self
116 % cumulative self
103 $ cat ../out | statprofran
117 $ cat ../out | statprofran
104
118
105 Windows real time tracking is broken, only use CPU
119 Windows real time tracking is broken, only use CPU
106
120
107 #if no-windows
121 #if no-windows
108 $ hg --profile --config profiling.time-track=real --config profiling.statformat=hotpath sleep 2>../out || cat ../out
122 $ hg --profile --config profiling.time-track=real --config profiling.statformat=hotpath sleep 2>../out || cat ../out
109 $ cat ../out | statprofran
123 $ cat ../out | statprofran
110 $ grep sleepext_with_a_long_filename.py ../out | head -n 1
124 $ grep sleepext_with_a_long_filename.py ../out | head -n 1
111 .* [0-9.]+% [0-9.]+s sleepext_with_a_long_filename.py:\s*sleep_for_at_least_one_stat_cycle, line \d+:\s+(while|pass).* (re)
125 .* [0-9.]+% [0-9.]+s sleepext_with_a_long_filename.py:\s*sleep_for_at_least_one_stat_cycle, line \d+:\s+(while|pass).* (re)
112 #endif
126 #endif
113
127
114 $ hg --profile --config profiling.time-track=cpu --config profiling.statformat=hotpath sleep 2>../out || cat ../out
128 $ hg --profile --config profiling.time-track=cpu --config profiling.statformat=hotpath sleep 2>../out || cat ../out
115 $ cat ../out | statprofran
129 $ cat ../out | statprofran
116 $ grep sleepext_with_a_long_filename.py ../out | head -n 1
130 $ grep sleepext_with_a_long_filename.py ../out | head -n 1
117 .* [0-9.]+% [0-9.]+s sleepext_with_a_long_filename.py:\s*sleep_for_at_least_one_stat_cycle, line \d+:\s+(while|pass).* (re)
131 .* [0-9.]+% [0-9.]+s sleepext_with_a_long_filename.py:\s*sleep_for_at_least_one_stat_cycle, line \d+:\s+(while|pass).* (re)
118
132
119 $ hg --profile --config profiling.statformat=json sleep 2>../out || cat ../out
133 $ hg --profile --config profiling.statformat=json sleep 2>../out || cat ../out
120 $ cat ../out
134 $ cat ../out
121 \[\[-?\d+.* (re)
135 \[\[-?\d+.* (re)
122
136
123 statprof can be used as a standalone module
137 statprof can be used as a standalone module
124
138
125 $ "$PYTHON" -m mercurial.statprof hotpath
139 $ "$PYTHON" -m mercurial.statprof hotpath
126 must specify --file to load
140 must specify --file to load
127 [1]
141 [1]
128
142
129 $ cd ..
143 $ cd ..
130
144
131 #if no-chg
145 #if no-chg
132 profiler extension could be loaded before other extensions
146 profiler extension could be loaded before other extensions
133
147
134 $ cat > fooprof.py <<EOF
148 $ cat > fooprof.py <<EOF
135 > import contextlib
149 > import contextlib
136 > import sys
150 > import sys
137 > @contextlib.contextmanager
151 > @contextlib.contextmanager
138 > def profile(ui, fp):
152 > def profile(ui, fp):
139 > print('fooprof: start profile')
153 > print('fooprof: start profile')
140 > sys.stdout.flush()
154 > sys.stdout.flush()
141 > yield
155 > yield
142 > print('fooprof: end profile')
156 > print('fooprof: end profile')
143 > sys.stdout.flush()
157 > sys.stdout.flush()
144 > def extsetup(ui):
158 > def extsetup(ui):
145 > ui.write(b'fooprof: loaded\n')
159 > ui.write(b'fooprof: loaded\n')
146 > EOF
160 > EOF
147
161
148 $ cat > otherextension.py <<EOF
162 $ cat > otherextension.py <<EOF
149 > def extsetup(ui):
163 > def extsetup(ui):
150 > ui.write(b'otherextension: loaded\n')
164 > ui.write(b'otherextension: loaded\n')
151 > EOF
165 > EOF
152
166
153 $ hg init b
167 $ hg init b
154 $ cd b
168 $ cd b
155 $ cat >> .hg/hgrc <<EOF
169 $ cat >> .hg/hgrc <<EOF
156 > [extensions]
170 > [extensions]
157 > other = $TESTTMP/otherextension.py
171 > other = $TESTTMP/otherextension.py
158 > fooprof = $TESTTMP/fooprof.py
172 > fooprof = $TESTTMP/fooprof.py
159 > EOF
173 > EOF
160
174
161 $ hg root
175 $ hg root
162 otherextension: loaded
176 otherextension: loaded
163 fooprof: loaded
177 fooprof: loaded
164 $TESTTMP/b
178 $TESTTMP/b
165 $ HGPROF=fooprof hg root --profile
179 $ HGPROF=fooprof hg root --profile
166 fooprof: loaded
180 fooprof: loaded
167 fooprof: start profile
181 fooprof: start profile
168 otherextension: loaded
182 otherextension: loaded
169 $TESTTMP/b
183 $TESTTMP/b
170 fooprof: end profile
184 fooprof: end profile
171
185
172 $ HGPROF=other hg root --profile 2>&1 | head -n 2
186 $ HGPROF=other hg root --profile 2>&1 | head -n 2
173 otherextension: loaded
187 otherextension: loaded
174 unrecognized profiler 'other' - ignored
188 unrecognized profiler 'other' - ignored
175
189
176 $ HGPROF=unknown hg root --profile 2>&1 | head -n 1
190 $ HGPROF=unknown hg root --profile 2>&1 | head -n 1
177 unrecognized profiler 'unknown' - ignored
191 unrecognized profiler 'unknown' - ignored
178
192
179 $ cd ..
193 $ cd ..
180 #endif
194 #endif
General Comments 0
You need to be logged in to leave comments. Login now