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