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