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