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