##// END OF EJS Templates
profiling: allow loading profiling extension before everything else...
Jun Wu -
r32417:f40dc6f7 default
parent child Browse files
Show More
@@ -1,578 +1,578 b''
1 # extensions.py - extension handling for mercurial
1 # extensions.py - extension handling for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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
8 from __future__ import absolute_import
9
9
10 import imp
10 import imp
11 import inspect
11 import inspect
12 import os
12 import os
13
13
14 from .i18n import (
14 from .i18n import (
15 _,
15 _,
16 gettext,
16 gettext,
17 )
17 )
18
18
19 from . import (
19 from . import (
20 cmdutil,
20 cmdutil,
21 encoding,
21 encoding,
22 error,
22 error,
23 pycompat,
23 pycompat,
24 util,
24 util,
25 )
25 )
26
26
27 _extensions = {}
27 _extensions = {}
28 _disabledextensions = {}
28 _disabledextensions = {}
29 _aftercallbacks = {}
29 _aftercallbacks = {}
30 _order = []
30 _order = []
31 _builtin = {'hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
31 _builtin = {'hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
32 'inotify', 'hgcia'}
32 'inotify', 'hgcia'}
33
33
34 def extensions(ui=None):
34 def extensions(ui=None):
35 if ui:
35 if ui:
36 def enabled(name):
36 def enabled(name):
37 for format in ['%s', 'hgext.%s']:
37 for format in ['%s', 'hgext.%s']:
38 conf = ui.config('extensions', format % name)
38 conf = ui.config('extensions', format % name)
39 if conf is not None and not conf.startswith('!'):
39 if conf is not None and not conf.startswith('!'):
40 return True
40 return True
41 else:
41 else:
42 enabled = lambda name: True
42 enabled = lambda name: True
43 for name in _order:
43 for name in _order:
44 module = _extensions[name]
44 module = _extensions[name]
45 if module and enabled(name):
45 if module and enabled(name):
46 yield name, module
46 yield name, module
47
47
48 def find(name):
48 def find(name):
49 '''return module with given extension name'''
49 '''return module with given extension name'''
50 mod = None
50 mod = None
51 try:
51 try:
52 mod = _extensions[name]
52 mod = _extensions[name]
53 except KeyError:
53 except KeyError:
54 for k, v in _extensions.iteritems():
54 for k, v in _extensions.iteritems():
55 if k.endswith('.' + name) or k.endswith('/' + name):
55 if k.endswith('.' + name) or k.endswith('/' + name):
56 mod = v
56 mod = v
57 break
57 break
58 if not mod:
58 if not mod:
59 raise KeyError(name)
59 raise KeyError(name)
60 return mod
60 return mod
61
61
62 def loadpath(path, module_name):
62 def loadpath(path, module_name):
63 module_name = module_name.replace('.', '_')
63 module_name = module_name.replace('.', '_')
64 path = util.normpath(util.expandpath(path))
64 path = util.normpath(util.expandpath(path))
65 module_name = pycompat.fsdecode(module_name)
65 module_name = pycompat.fsdecode(module_name)
66 path = pycompat.fsdecode(path)
66 path = pycompat.fsdecode(path)
67 if os.path.isdir(path):
67 if os.path.isdir(path):
68 # module/__init__.py style
68 # module/__init__.py style
69 d, f = os.path.split(path)
69 d, f = os.path.split(path)
70 fd, fpath, desc = imp.find_module(f, [d])
70 fd, fpath, desc = imp.find_module(f, [d])
71 return imp.load_module(module_name, fd, fpath, desc)
71 return imp.load_module(module_name, fd, fpath, desc)
72 else:
72 else:
73 try:
73 try:
74 return imp.load_source(module_name, path)
74 return imp.load_source(module_name, path)
75 except IOError as exc:
75 except IOError as exc:
76 if not exc.filename:
76 if not exc.filename:
77 exc.filename = path # python does not fill this
77 exc.filename = path # python does not fill this
78 raise
78 raise
79
79
80 def _importh(name):
80 def _importh(name):
81 """import and return the <name> module"""
81 """import and return the <name> module"""
82 mod = __import__(pycompat.sysstr(name))
82 mod = __import__(pycompat.sysstr(name))
83 components = name.split('.')
83 components = name.split('.')
84 for comp in components[1:]:
84 for comp in components[1:]:
85 mod = getattr(mod, comp)
85 mod = getattr(mod, comp)
86 return mod
86 return mod
87
87
88 def _importext(name, path=None, reportfunc=None):
88 def _importext(name, path=None, reportfunc=None):
89 if path:
89 if path:
90 # the module will be loaded in sys.modules
90 # the module will be loaded in sys.modules
91 # choose an unique name so that it doesn't
91 # choose an unique name so that it doesn't
92 # conflicts with other modules
92 # conflicts with other modules
93 mod = loadpath(path, 'hgext.%s' % name)
93 mod = loadpath(path, 'hgext.%s' % name)
94 else:
94 else:
95 try:
95 try:
96 mod = _importh("hgext.%s" % name)
96 mod = _importh("hgext.%s" % name)
97 except ImportError as err:
97 except ImportError as err:
98 if reportfunc:
98 if reportfunc:
99 reportfunc(err, "hgext.%s" % name, "hgext3rd.%s" % name)
99 reportfunc(err, "hgext.%s" % name, "hgext3rd.%s" % name)
100 try:
100 try:
101 mod = _importh("hgext3rd.%s" % name)
101 mod = _importh("hgext3rd.%s" % name)
102 except ImportError as err:
102 except ImportError as err:
103 if reportfunc:
103 if reportfunc:
104 reportfunc(err, "hgext3rd.%s" % name, name)
104 reportfunc(err, "hgext3rd.%s" % name, name)
105 mod = _importh(name)
105 mod = _importh(name)
106 return mod
106 return mod
107
107
108 def _forbytes(inst):
108 def _forbytes(inst):
109 """Portably format an import error into a form suitable for
109 """Portably format an import error into a form suitable for
110 %-formatting into bytestrings."""
110 %-formatting into bytestrings."""
111 return encoding.strtolocal(str(inst))
111 return encoding.strtolocal(str(inst))
112
112
113 def _reportimporterror(ui, err, failed, next):
113 def _reportimporterror(ui, err, failed, next):
114 # note: this ui.debug happens before --debug is processed,
114 # note: this ui.debug happens before --debug is processed,
115 # Use --config ui.debug=1 to see them.
115 # Use --config ui.debug=1 to see them.
116 ui.debug('could not import %s (%s): trying %s\n'
116 ui.debug('could not import %s (%s): trying %s\n'
117 % (failed, _forbytes(err), next))
117 % (failed, _forbytes(err), next))
118 if ui.debugflag:
118 if ui.debugflag:
119 ui.traceback()
119 ui.traceback()
120
120
121 # attributes set by registrar.command
121 # attributes set by registrar.command
122 _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo')
122 _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo')
123
123
124 def _validatecmdtable(ui, cmdtable):
124 def _validatecmdtable(ui, cmdtable):
125 """Check if extension commands have required attributes"""
125 """Check if extension commands have required attributes"""
126 for c, e in cmdtable.iteritems():
126 for c, e in cmdtable.iteritems():
127 f = e[0]
127 f = e[0]
128 if getattr(f, '_deprecatedregistrar', False):
128 if getattr(f, '_deprecatedregistrar', False):
129 ui.deprecwarn("cmdutil.command is deprecated, use "
129 ui.deprecwarn("cmdutil.command is deprecated, use "
130 "registrar.command to register '%s'" % c, '4.6')
130 "registrar.command to register '%s'" % c, '4.6')
131 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
131 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
132 if not missing:
132 if not missing:
133 continue
133 continue
134 raise error.ProgrammingError(
134 raise error.ProgrammingError(
135 'missing attributes: %s' % ', '.join(missing),
135 'missing attributes: %s' % ', '.join(missing),
136 hint="use @command decorator to register '%s'" % c)
136 hint="use @command decorator to register '%s'" % c)
137
137
138 def load(ui, name, path):
138 def load(ui, name, path):
139 if name.startswith('hgext.') or name.startswith('hgext/'):
139 if name.startswith('hgext.') or name.startswith('hgext/'):
140 shortname = name[6:]
140 shortname = name[6:]
141 else:
141 else:
142 shortname = name
142 shortname = name
143 if shortname in _builtin:
143 if shortname in _builtin:
144 return None
144 return None
145 if shortname in _extensions:
145 if shortname in _extensions:
146 return _extensions[shortname]
146 return _extensions[shortname]
147 _extensions[shortname] = None
147 _extensions[shortname] = None
148 mod = _importext(name, path, bind(_reportimporterror, ui))
148 mod = _importext(name, path, bind(_reportimporterror, ui))
149
149
150 # Before we do anything with the extension, check against minimum stated
150 # Before we do anything with the extension, check against minimum stated
151 # compatibility. This gives extension authors a mechanism to have their
151 # compatibility. This gives extension authors a mechanism to have their
152 # extensions short circuit when loaded with a known incompatible version
152 # extensions short circuit when loaded with a known incompatible version
153 # of Mercurial.
153 # of Mercurial.
154 minver = getattr(mod, 'minimumhgversion', None)
154 minver = getattr(mod, 'minimumhgversion', None)
155 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
155 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
156 ui.warn(_('(third party extension %s requires version %s or newer '
156 ui.warn(_('(third party extension %s requires version %s or newer '
157 'of Mercurial; disabling)\n') % (shortname, minver))
157 'of Mercurial; disabling)\n') % (shortname, minver))
158 return
158 return
159 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
159 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
160
160
161 _extensions[shortname] = mod
161 _extensions[shortname] = mod
162 _order.append(shortname)
162 _order.append(shortname)
163 for fn in _aftercallbacks.get(shortname, []):
163 for fn in _aftercallbacks.get(shortname, []):
164 fn(loaded=True)
164 fn(loaded=True)
165 return mod
165 return mod
166
166
167 def _runuisetup(name, ui):
167 def _runuisetup(name, ui):
168 uisetup = getattr(_extensions[name], 'uisetup', None)
168 uisetup = getattr(_extensions[name], 'uisetup', None)
169 if uisetup:
169 if uisetup:
170 uisetup(ui)
170 uisetup(ui)
171
171
172 def _runextsetup(name, ui):
172 def _runextsetup(name, ui):
173 extsetup = getattr(_extensions[name], 'extsetup', None)
173 extsetup = getattr(_extensions[name], 'extsetup', None)
174 if extsetup:
174 if extsetup:
175 try:
175 try:
176 extsetup(ui)
176 extsetup(ui)
177 except TypeError:
177 except TypeError:
178 if inspect.getargspec(extsetup).args:
178 if inspect.getargspec(extsetup).args:
179 raise
179 raise
180 extsetup() # old extsetup with no ui argument
180 extsetup() # old extsetup with no ui argument
181
181
182 def loadall(ui, whitelist=None):
182 def loadall(ui, whitelist=None):
183 result = ui.configitems("extensions")
183 result = ui.configitems("extensions")
184 if whitelist:
184 if whitelist is not None:
185 result = [(k, v) for (k, v) in result if k in whitelist]
185 result = [(k, v) for (k, v) in result if k in whitelist]
186 newindex = len(_order)
186 newindex = len(_order)
187 for (name, path) in result:
187 for (name, path) in result:
188 if path:
188 if path:
189 if path[0:1] == '!':
189 if path[0:1] == '!':
190 _disabledextensions[name] = path[1:]
190 _disabledextensions[name] = path[1:]
191 continue
191 continue
192 try:
192 try:
193 load(ui, name, path)
193 load(ui, name, path)
194 except Exception as inst:
194 except Exception as inst:
195 msg = _forbytes(inst)
195 msg = _forbytes(inst)
196 if path:
196 if path:
197 ui.warn(_("*** failed to import extension %s from %s: %s\n")
197 ui.warn(_("*** failed to import extension %s from %s: %s\n")
198 % (name, path, msg))
198 % (name, path, msg))
199 else:
199 else:
200 ui.warn(_("*** failed to import extension %s: %s\n")
200 ui.warn(_("*** failed to import extension %s: %s\n")
201 % (name, msg))
201 % (name, msg))
202 if isinstance(inst, error.Hint) and inst.hint:
202 if isinstance(inst, error.Hint) and inst.hint:
203 ui.warn(_("*** (%s)\n") % inst.hint)
203 ui.warn(_("*** (%s)\n") % inst.hint)
204 ui.traceback()
204 ui.traceback()
205
205
206 for name in _order[newindex:]:
206 for name in _order[newindex:]:
207 _runuisetup(name, ui)
207 _runuisetup(name, ui)
208
208
209 for name in _order[newindex:]:
209 for name in _order[newindex:]:
210 _runextsetup(name, ui)
210 _runextsetup(name, ui)
211
211
212 # Call aftercallbacks that were never met.
212 # Call aftercallbacks that were never met.
213 for shortname in _aftercallbacks:
213 for shortname in _aftercallbacks:
214 if shortname in _extensions:
214 if shortname in _extensions:
215 continue
215 continue
216
216
217 for fn in _aftercallbacks[shortname]:
217 for fn in _aftercallbacks[shortname]:
218 fn(loaded=False)
218 fn(loaded=False)
219
219
220 # loadall() is called multiple times and lingering _aftercallbacks
220 # loadall() is called multiple times and lingering _aftercallbacks
221 # entries could result in double execution. See issue4646.
221 # entries could result in double execution. See issue4646.
222 _aftercallbacks.clear()
222 _aftercallbacks.clear()
223
223
224 def afterloaded(extension, callback):
224 def afterloaded(extension, callback):
225 '''Run the specified function after a named extension is loaded.
225 '''Run the specified function after a named extension is loaded.
226
226
227 If the named extension is already loaded, the callback will be called
227 If the named extension is already loaded, the callback will be called
228 immediately.
228 immediately.
229
229
230 If the named extension never loads, the callback will be called after
230 If the named extension never loads, the callback will be called after
231 all extensions have been loaded.
231 all extensions have been loaded.
232
232
233 The callback receives the named argument ``loaded``, which is a boolean
233 The callback receives the named argument ``loaded``, which is a boolean
234 indicating whether the dependent extension actually loaded.
234 indicating whether the dependent extension actually loaded.
235 '''
235 '''
236
236
237 if extension in _extensions:
237 if extension in _extensions:
238 callback(loaded=True)
238 callback(loaded=True)
239 else:
239 else:
240 _aftercallbacks.setdefault(extension, []).append(callback)
240 _aftercallbacks.setdefault(extension, []).append(callback)
241
241
242 def bind(func, *args):
242 def bind(func, *args):
243 '''Partial function application
243 '''Partial function application
244
244
245 Returns a new function that is the partial application of args and kwargs
245 Returns a new function that is the partial application of args and kwargs
246 to func. For example,
246 to func. For example,
247
247
248 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
248 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
249 assert callable(func)
249 assert callable(func)
250 def closure(*a, **kw):
250 def closure(*a, **kw):
251 return func(*(args + a), **kw)
251 return func(*(args + a), **kw)
252 return closure
252 return closure
253
253
254 def _updatewrapper(wrap, origfn, unboundwrapper):
254 def _updatewrapper(wrap, origfn, unboundwrapper):
255 '''Copy and add some useful attributes to wrapper'''
255 '''Copy and add some useful attributes to wrapper'''
256 wrap.__module__ = getattr(origfn, '__module__')
256 wrap.__module__ = getattr(origfn, '__module__')
257 wrap.__doc__ = getattr(origfn, '__doc__')
257 wrap.__doc__ = getattr(origfn, '__doc__')
258 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
258 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
259 wrap._origfunc = origfn
259 wrap._origfunc = origfn
260 wrap._unboundwrapper = unboundwrapper
260 wrap._unboundwrapper = unboundwrapper
261
261
262 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
262 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
263 '''Wrap the command named `command' in table
263 '''Wrap the command named `command' in table
264
264
265 Replace command in the command table with wrapper. The wrapped command will
265 Replace command in the command table with wrapper. The wrapped command will
266 be inserted into the command table specified by the table argument.
266 be inserted into the command table specified by the table argument.
267
267
268 The wrapper will be called like
268 The wrapper will be called like
269
269
270 wrapper(orig, *args, **kwargs)
270 wrapper(orig, *args, **kwargs)
271
271
272 where orig is the original (wrapped) function, and *args, **kwargs
272 where orig is the original (wrapped) function, and *args, **kwargs
273 are the arguments passed to it.
273 are the arguments passed to it.
274
274
275 Optionally append to the command synopsis and docstring, used for help.
275 Optionally append to the command synopsis and docstring, used for help.
276 For example, if your extension wraps the ``bookmarks`` command to add the
276 For example, if your extension wraps the ``bookmarks`` command to add the
277 flags ``--remote`` and ``--all`` you might call this function like so:
277 flags ``--remote`` and ``--all`` you might call this function like so:
278
278
279 synopsis = ' [-a] [--remote]'
279 synopsis = ' [-a] [--remote]'
280 docstring = """
280 docstring = """
281
281
282 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
282 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
283 flags to the bookmarks command. Either flag will show the remote bookmarks
283 flags to the bookmarks command. Either flag will show the remote bookmarks
284 known to the repository; ``--remote`` will also suppress the output of the
284 known to the repository; ``--remote`` will also suppress the output of the
285 local bookmarks.
285 local bookmarks.
286 """
286 """
287
287
288 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
288 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
289 synopsis, docstring)
289 synopsis, docstring)
290 '''
290 '''
291 assert callable(wrapper)
291 assert callable(wrapper)
292 aliases, entry = cmdutil.findcmd(command, table)
292 aliases, entry = cmdutil.findcmd(command, table)
293 for alias, e in table.iteritems():
293 for alias, e in table.iteritems():
294 if e is entry:
294 if e is entry:
295 key = alias
295 key = alias
296 break
296 break
297
297
298 origfn = entry[0]
298 origfn = entry[0]
299 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
299 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
300 _updatewrapper(wrap, origfn, wrapper)
300 _updatewrapper(wrap, origfn, wrapper)
301 if docstring is not None:
301 if docstring is not None:
302 wrap.__doc__ += docstring
302 wrap.__doc__ += docstring
303
303
304 newentry = list(entry)
304 newentry = list(entry)
305 newentry[0] = wrap
305 newentry[0] = wrap
306 if synopsis is not None:
306 if synopsis is not None:
307 newentry[2] += synopsis
307 newentry[2] += synopsis
308 table[key] = tuple(newentry)
308 table[key] = tuple(newentry)
309 return entry
309 return entry
310
310
311 def wrapfunction(container, funcname, wrapper):
311 def wrapfunction(container, funcname, wrapper):
312 '''Wrap the function named funcname in container
312 '''Wrap the function named funcname in container
313
313
314 Replace the funcname member in the given container with the specified
314 Replace the funcname member in the given container with the specified
315 wrapper. The container is typically a module, class, or instance.
315 wrapper. The container is typically a module, class, or instance.
316
316
317 The wrapper will be called like
317 The wrapper will be called like
318
318
319 wrapper(orig, *args, **kwargs)
319 wrapper(orig, *args, **kwargs)
320
320
321 where orig is the original (wrapped) function, and *args, **kwargs
321 where orig is the original (wrapped) function, and *args, **kwargs
322 are the arguments passed to it.
322 are the arguments passed to it.
323
323
324 Wrapping methods of the repository object is not recommended since
324 Wrapping methods of the repository object is not recommended since
325 it conflicts with extensions that extend the repository by
325 it conflicts with extensions that extend the repository by
326 subclassing. All extensions that need to extend methods of
326 subclassing. All extensions that need to extend methods of
327 localrepository should use this subclassing trick: namely,
327 localrepository should use this subclassing trick: namely,
328 reposetup() should look like
328 reposetup() should look like
329
329
330 def reposetup(ui, repo):
330 def reposetup(ui, repo):
331 class myrepo(repo.__class__):
331 class myrepo(repo.__class__):
332 def whatever(self, *args, **kwargs):
332 def whatever(self, *args, **kwargs):
333 [...extension stuff...]
333 [...extension stuff...]
334 super(myrepo, self).whatever(*args, **kwargs)
334 super(myrepo, self).whatever(*args, **kwargs)
335 [...extension stuff...]
335 [...extension stuff...]
336
336
337 repo.__class__ = myrepo
337 repo.__class__ = myrepo
338
338
339 In general, combining wrapfunction() with subclassing does not
339 In general, combining wrapfunction() with subclassing does not
340 work. Since you cannot control what other extensions are loaded by
340 work. Since you cannot control what other extensions are loaded by
341 your end users, you should play nicely with others by using the
341 your end users, you should play nicely with others by using the
342 subclass trick.
342 subclass trick.
343 '''
343 '''
344 assert callable(wrapper)
344 assert callable(wrapper)
345
345
346 origfn = getattr(container, funcname)
346 origfn = getattr(container, funcname)
347 assert callable(origfn)
347 assert callable(origfn)
348 wrap = bind(wrapper, origfn)
348 wrap = bind(wrapper, origfn)
349 _updatewrapper(wrap, origfn, wrapper)
349 _updatewrapper(wrap, origfn, wrapper)
350 setattr(container, funcname, wrap)
350 setattr(container, funcname, wrap)
351 return origfn
351 return origfn
352
352
353 def unwrapfunction(container, funcname, wrapper=None):
353 def unwrapfunction(container, funcname, wrapper=None):
354 '''undo wrapfunction
354 '''undo wrapfunction
355
355
356 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
356 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
357 from the chain of wrappers.
357 from the chain of wrappers.
358
358
359 Return the removed wrapper.
359 Return the removed wrapper.
360 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
360 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
361 wrapper is not None but is not found in the wrapper chain.
361 wrapper is not None but is not found in the wrapper chain.
362 '''
362 '''
363 chain = getwrapperchain(container, funcname)
363 chain = getwrapperchain(container, funcname)
364 origfn = chain.pop()
364 origfn = chain.pop()
365 if wrapper is None:
365 if wrapper is None:
366 wrapper = chain[0]
366 wrapper = chain[0]
367 chain.remove(wrapper)
367 chain.remove(wrapper)
368 setattr(container, funcname, origfn)
368 setattr(container, funcname, origfn)
369 for w in reversed(chain):
369 for w in reversed(chain):
370 wrapfunction(container, funcname, w)
370 wrapfunction(container, funcname, w)
371 return wrapper
371 return wrapper
372
372
373 def getwrapperchain(container, funcname):
373 def getwrapperchain(container, funcname):
374 '''get a chain of wrappers of a function
374 '''get a chain of wrappers of a function
375
375
376 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
376 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
377
377
378 The wrapper functions are the ones passed to wrapfunction, whose first
378 The wrapper functions are the ones passed to wrapfunction, whose first
379 argument is origfunc.
379 argument is origfunc.
380 '''
380 '''
381 result = []
381 result = []
382 fn = getattr(container, funcname)
382 fn = getattr(container, funcname)
383 while fn:
383 while fn:
384 assert callable(fn)
384 assert callable(fn)
385 result.append(getattr(fn, '_unboundwrapper', fn))
385 result.append(getattr(fn, '_unboundwrapper', fn))
386 fn = getattr(fn, '_origfunc', None)
386 fn = getattr(fn, '_origfunc', None)
387 return result
387 return result
388
388
389 def _disabledpaths(strip_init=False):
389 def _disabledpaths(strip_init=False):
390 '''find paths of disabled extensions. returns a dict of {name: path}
390 '''find paths of disabled extensions. returns a dict of {name: path}
391 removes /__init__.py from packages if strip_init is True'''
391 removes /__init__.py from packages if strip_init is True'''
392 import hgext
392 import hgext
393 extpath = os.path.dirname(
393 extpath = os.path.dirname(
394 os.path.abspath(pycompat.fsencode(hgext.__file__)))
394 os.path.abspath(pycompat.fsencode(hgext.__file__)))
395 try: # might not be a filesystem path
395 try: # might not be a filesystem path
396 files = os.listdir(extpath)
396 files = os.listdir(extpath)
397 except OSError:
397 except OSError:
398 return {}
398 return {}
399
399
400 exts = {}
400 exts = {}
401 for e in files:
401 for e in files:
402 if e.endswith('.py'):
402 if e.endswith('.py'):
403 name = e.rsplit('.', 1)[0]
403 name = e.rsplit('.', 1)[0]
404 path = os.path.join(extpath, e)
404 path = os.path.join(extpath, e)
405 else:
405 else:
406 name = e
406 name = e
407 path = os.path.join(extpath, e, '__init__.py')
407 path = os.path.join(extpath, e, '__init__.py')
408 if not os.path.exists(path):
408 if not os.path.exists(path):
409 continue
409 continue
410 if strip_init:
410 if strip_init:
411 path = os.path.dirname(path)
411 path = os.path.dirname(path)
412 if name in exts or name in _order or name == '__init__':
412 if name in exts or name in _order or name == '__init__':
413 continue
413 continue
414 exts[name] = path
414 exts[name] = path
415 exts.update(_disabledextensions)
415 exts.update(_disabledextensions)
416 return exts
416 return exts
417
417
418 def _moduledoc(file):
418 def _moduledoc(file):
419 '''return the top-level python documentation for the given file
419 '''return the top-level python documentation for the given file
420
420
421 Loosely inspired by pydoc.source_synopsis(), but rewritten to
421 Loosely inspired by pydoc.source_synopsis(), but rewritten to
422 handle triple quotes and to return the whole text instead of just
422 handle triple quotes and to return the whole text instead of just
423 the synopsis'''
423 the synopsis'''
424 result = []
424 result = []
425
425
426 line = file.readline()
426 line = file.readline()
427 while line[:1] == '#' or not line.strip():
427 while line[:1] == '#' or not line.strip():
428 line = file.readline()
428 line = file.readline()
429 if not line:
429 if not line:
430 break
430 break
431
431
432 start = line[:3]
432 start = line[:3]
433 if start == '"""' or start == "'''":
433 if start == '"""' or start == "'''":
434 line = line[3:]
434 line = line[3:]
435 while line:
435 while line:
436 if line.rstrip().endswith(start):
436 if line.rstrip().endswith(start):
437 line = line.split(start)[0]
437 line = line.split(start)[0]
438 if line:
438 if line:
439 result.append(line)
439 result.append(line)
440 break
440 break
441 elif not line:
441 elif not line:
442 return None # unmatched delimiter
442 return None # unmatched delimiter
443 result.append(line)
443 result.append(line)
444 line = file.readline()
444 line = file.readline()
445 else:
445 else:
446 return None
446 return None
447
447
448 return ''.join(result)
448 return ''.join(result)
449
449
450 def _disabledhelp(path):
450 def _disabledhelp(path):
451 '''retrieve help synopsis of a disabled extension (without importing)'''
451 '''retrieve help synopsis of a disabled extension (without importing)'''
452 try:
452 try:
453 file = open(path)
453 file = open(path)
454 except IOError:
454 except IOError:
455 return
455 return
456 else:
456 else:
457 doc = _moduledoc(file)
457 doc = _moduledoc(file)
458 file.close()
458 file.close()
459
459
460 if doc: # extracting localized synopsis
460 if doc: # extracting localized synopsis
461 return gettext(doc)
461 return gettext(doc)
462 else:
462 else:
463 return _('(no help text available)')
463 return _('(no help text available)')
464
464
465 def disabled():
465 def disabled():
466 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
466 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
467 try:
467 try:
468 from hgext import __index__
468 from hgext import __index__
469 return dict((name, gettext(desc))
469 return dict((name, gettext(desc))
470 for name, desc in __index__.docs.iteritems()
470 for name, desc in __index__.docs.iteritems()
471 if name not in _order)
471 if name not in _order)
472 except (ImportError, AttributeError):
472 except (ImportError, AttributeError):
473 pass
473 pass
474
474
475 paths = _disabledpaths()
475 paths = _disabledpaths()
476 if not paths:
476 if not paths:
477 return {}
477 return {}
478
478
479 exts = {}
479 exts = {}
480 for name, path in paths.iteritems():
480 for name, path in paths.iteritems():
481 doc = _disabledhelp(path)
481 doc = _disabledhelp(path)
482 if doc:
482 if doc:
483 exts[name] = doc.splitlines()[0]
483 exts[name] = doc.splitlines()[0]
484
484
485 return exts
485 return exts
486
486
487 def disabledext(name):
487 def disabledext(name):
488 '''find a specific disabled extension from hgext. returns desc'''
488 '''find a specific disabled extension from hgext. returns desc'''
489 try:
489 try:
490 from hgext import __index__
490 from hgext import __index__
491 if name in _order: # enabled
491 if name in _order: # enabled
492 return
492 return
493 else:
493 else:
494 return gettext(__index__.docs.get(name))
494 return gettext(__index__.docs.get(name))
495 except (ImportError, AttributeError):
495 except (ImportError, AttributeError):
496 pass
496 pass
497
497
498 paths = _disabledpaths()
498 paths = _disabledpaths()
499 if name in paths:
499 if name in paths:
500 return _disabledhelp(paths[name])
500 return _disabledhelp(paths[name])
501
501
502 def disabledcmd(ui, cmd, strict=False):
502 def disabledcmd(ui, cmd, strict=False):
503 '''import disabled extensions until cmd is found.
503 '''import disabled extensions until cmd is found.
504 returns (cmdname, extname, module)'''
504 returns (cmdname, extname, module)'''
505
505
506 paths = _disabledpaths(strip_init=True)
506 paths = _disabledpaths(strip_init=True)
507 if not paths:
507 if not paths:
508 raise error.UnknownCommand(cmd)
508 raise error.UnknownCommand(cmd)
509
509
510 def findcmd(cmd, name, path):
510 def findcmd(cmd, name, path):
511 try:
511 try:
512 mod = loadpath(path, 'hgext.%s' % name)
512 mod = loadpath(path, 'hgext.%s' % name)
513 except Exception:
513 except Exception:
514 return
514 return
515 try:
515 try:
516 aliases, entry = cmdutil.findcmd(cmd,
516 aliases, entry = cmdutil.findcmd(cmd,
517 getattr(mod, 'cmdtable', {}), strict)
517 getattr(mod, 'cmdtable', {}), strict)
518 except (error.AmbiguousCommand, error.UnknownCommand):
518 except (error.AmbiguousCommand, error.UnknownCommand):
519 return
519 return
520 except Exception:
520 except Exception:
521 ui.warn(_('warning: error finding commands in %s\n') % path)
521 ui.warn(_('warning: error finding commands in %s\n') % path)
522 ui.traceback()
522 ui.traceback()
523 return
523 return
524 for c in aliases:
524 for c in aliases:
525 if c.startswith(cmd):
525 if c.startswith(cmd):
526 cmd = c
526 cmd = c
527 break
527 break
528 else:
528 else:
529 cmd = aliases[0]
529 cmd = aliases[0]
530 return (cmd, name, mod)
530 return (cmd, name, mod)
531
531
532 ext = None
532 ext = None
533 # first, search for an extension with the same name as the command
533 # first, search for an extension with the same name as the command
534 path = paths.pop(cmd, None)
534 path = paths.pop(cmd, None)
535 if path:
535 if path:
536 ext = findcmd(cmd, cmd, path)
536 ext = findcmd(cmd, cmd, path)
537 if not ext:
537 if not ext:
538 # otherwise, interrogate each extension until there's a match
538 # otherwise, interrogate each extension until there's a match
539 for name, path in paths.iteritems():
539 for name, path in paths.iteritems():
540 ext = findcmd(cmd, name, path)
540 ext = findcmd(cmd, name, path)
541 if ext:
541 if ext:
542 break
542 break
543 if ext and 'DEPRECATED' not in ext.__doc__:
543 if ext and 'DEPRECATED' not in ext.__doc__:
544 return ext
544 return ext
545
545
546 raise error.UnknownCommand(cmd)
546 raise error.UnknownCommand(cmd)
547
547
548 def enabled(shortname=True):
548 def enabled(shortname=True):
549 '''return a dict of {name: desc} of extensions'''
549 '''return a dict of {name: desc} of extensions'''
550 exts = {}
550 exts = {}
551 for ename, ext in extensions():
551 for ename, ext in extensions():
552 doc = (gettext(ext.__doc__) or _('(no help text available)'))
552 doc = (gettext(ext.__doc__) or _('(no help text available)'))
553 if shortname:
553 if shortname:
554 ename = ename.split('.')[-1]
554 ename = ename.split('.')[-1]
555 exts[ename] = doc.splitlines()[0].strip()
555 exts[ename] = doc.splitlines()[0].strip()
556
556
557 return exts
557 return exts
558
558
559 def notloaded():
559 def notloaded():
560 '''return short names of extensions that failed to load'''
560 '''return short names of extensions that failed to load'''
561 return [name for name, mod in _extensions.iteritems() if mod is None]
561 return [name for name, mod in _extensions.iteritems() if mod is None]
562
562
563 def moduleversion(module):
563 def moduleversion(module):
564 '''return version information from given module as a string'''
564 '''return version information from given module as a string'''
565 if (util.safehasattr(module, 'getversion')
565 if (util.safehasattr(module, 'getversion')
566 and callable(module.getversion)):
566 and callable(module.getversion)):
567 version = module.getversion()
567 version = module.getversion()
568 elif util.safehasattr(module, '__version__'):
568 elif util.safehasattr(module, '__version__'):
569 version = module.__version__
569 version = module.__version__
570 else:
570 else:
571 version = ''
571 version = ''
572 if isinstance(version, (list, tuple)):
572 if isinstance(version, (list, tuple)):
573 version = '.'.join(str(o) for o in version)
573 version = '.'.join(str(o) for o in version)
574 return version
574 return version
575
575
576 def ismoduleinternal(module):
576 def ismoduleinternal(module):
577 exttestedwith = getattr(module, 'testedwith', None)
577 exttestedwith = getattr(module, 'testedwith', None)
578 return exttestedwith == "ships-with-hg-core"
578 return exttestedwith == "ships-with-hg-core"
@@ -1,192 +1,210 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 util,
17 util,
17 )
18 )
18
19
20 def _loadprofiler(ui, profiler):
21 """load profiler extension. return profile method, or None on failure"""
22 extname = profiler
23 extensions.loadall(ui, whitelist=[extname])
24 try:
25 mod = extensions.find(extname)
26 except KeyError:
27 return None
28 else:
29 return getattr(mod, 'profile', None)
30
19 @contextlib.contextmanager
31 @contextlib.contextmanager
20 def lsprofile(ui, fp):
32 def lsprofile(ui, fp):
21 format = ui.config('profiling', 'format', default='text')
33 format = ui.config('profiling', 'format', default='text')
22 field = ui.config('profiling', 'sort', default='inlinetime')
34 field = ui.config('profiling', 'sort', default='inlinetime')
23 limit = ui.configint('profiling', 'limit', default=30)
35 limit = ui.configint('profiling', 'limit', default=30)
24 climit = ui.configint('profiling', 'nested', default=0)
36 climit = ui.configint('profiling', 'nested', default=0)
25
37
26 if format not in ['text', 'kcachegrind']:
38 if format not in ['text', 'kcachegrind']:
27 ui.warn(_("unrecognized profiling format '%s'"
39 ui.warn(_("unrecognized profiling format '%s'"
28 " - Ignored\n") % format)
40 " - Ignored\n") % format)
29 format = 'text'
41 format = 'text'
30
42
31 try:
43 try:
32 from . import lsprof
44 from . import lsprof
33 except ImportError:
45 except ImportError:
34 raise error.Abort(_(
46 raise error.Abort(_(
35 'lsprof not available - install from '
47 'lsprof not available - install from '
36 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
48 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
37 p = lsprof.Profiler()
49 p = lsprof.Profiler()
38 p.enable(subcalls=True)
50 p.enable(subcalls=True)
39 try:
51 try:
40 yield
52 yield
41 finally:
53 finally:
42 p.disable()
54 p.disable()
43
55
44 if format == 'kcachegrind':
56 if format == 'kcachegrind':
45 from . import lsprofcalltree
57 from . import lsprofcalltree
46 calltree = lsprofcalltree.KCacheGrind(p)
58 calltree = lsprofcalltree.KCacheGrind(p)
47 calltree.output(fp)
59 calltree.output(fp)
48 else:
60 else:
49 # format == 'text'
61 # format == 'text'
50 stats = lsprof.Stats(p.getstats())
62 stats = lsprof.Stats(p.getstats())
51 stats.sort(field)
63 stats.sort(field)
52 stats.pprint(limit=limit, file=fp, climit=climit)
64 stats.pprint(limit=limit, file=fp, climit=climit)
53
65
54 @contextlib.contextmanager
66 @contextlib.contextmanager
55 def flameprofile(ui, fp):
67 def flameprofile(ui, fp):
56 try:
68 try:
57 from flamegraph import flamegraph
69 from flamegraph import flamegraph
58 except ImportError:
70 except ImportError:
59 raise error.Abort(_(
71 raise error.Abort(_(
60 'flamegraph not available - install from '
72 'flamegraph not available - install from '
61 'https://github.com/evanhempel/python-flamegraph'))
73 'https://github.com/evanhempel/python-flamegraph'))
62 # developer config: profiling.freq
74 # developer config: profiling.freq
63 freq = ui.configint('profiling', 'freq', default=1000)
75 freq = ui.configint('profiling', 'freq', default=1000)
64 filter_ = None
76 filter_ = None
65 collapse_recursion = True
77 collapse_recursion = True
66 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
78 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
67 filter_, collapse_recursion)
79 filter_, collapse_recursion)
68 start_time = util.timer()
80 start_time = util.timer()
69 try:
81 try:
70 thread.start()
82 thread.start()
71 yield
83 yield
72 finally:
84 finally:
73 thread.stop()
85 thread.stop()
74 thread.join()
86 thread.join()
75 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
87 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
76 util.timer() - start_time, thread.num_frames(),
88 util.timer() - start_time, thread.num_frames(),
77 thread.num_frames(unique=True)))
89 thread.num_frames(unique=True)))
78
90
79 @contextlib.contextmanager
91 @contextlib.contextmanager
80 def statprofile(ui, fp):
92 def statprofile(ui, fp):
81 from . import statprof
93 from . import statprof
82
94
83 freq = ui.configint('profiling', 'freq', default=1000)
95 freq = ui.configint('profiling', 'freq', default=1000)
84 if freq > 0:
96 if freq > 0:
85 # Cannot reset when profiler is already active. So silently no-op.
97 # Cannot reset when profiler is already active. So silently no-op.
86 if statprof.state.profile_level == 0:
98 if statprof.state.profile_level == 0:
87 statprof.reset(freq)
99 statprof.reset(freq)
88 else:
100 else:
89 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
101 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
90
102
91 statprof.start(mechanism='thread')
103 statprof.start(mechanism='thread')
92
104
93 try:
105 try:
94 yield
106 yield
95 finally:
107 finally:
96 data = statprof.stop()
108 data = statprof.stop()
97
109
98 profformat = ui.config('profiling', 'statformat', 'hotpath')
110 profformat = ui.config('profiling', 'statformat', 'hotpath')
99
111
100 formats = {
112 formats = {
101 'byline': statprof.DisplayFormats.ByLine,
113 'byline': statprof.DisplayFormats.ByLine,
102 'bymethod': statprof.DisplayFormats.ByMethod,
114 'bymethod': statprof.DisplayFormats.ByMethod,
103 'hotpath': statprof.DisplayFormats.Hotpath,
115 'hotpath': statprof.DisplayFormats.Hotpath,
104 'json': statprof.DisplayFormats.Json,
116 'json': statprof.DisplayFormats.Json,
105 'chrome': statprof.DisplayFormats.Chrome,
117 'chrome': statprof.DisplayFormats.Chrome,
106 }
118 }
107
119
108 if profformat in formats:
120 if profformat in formats:
109 displayformat = formats[profformat]
121 displayformat = formats[profformat]
110 else:
122 else:
111 ui.warn(_('unknown profiler output format: %s\n') % profformat)
123 ui.warn(_('unknown profiler output format: %s\n') % profformat)
112 displayformat = statprof.DisplayFormats.Hotpath
124 displayformat = statprof.DisplayFormats.Hotpath
113
125
114 kwargs = {}
126 kwargs = {}
115
127
116 def fraction(s):
128 def fraction(s):
117 if s.endswith('%'):
129 if s.endswith('%'):
118 v = float(s[:-1]) / 100
130 v = float(s[:-1]) / 100
119 else:
131 else:
120 v = float(s)
132 v = float(s)
121 if 0 <= v <= 1:
133 if 0 <= v <= 1:
122 return v
134 return v
123 raise ValueError(s)
135 raise ValueError(s)
124
136
125 if profformat == 'chrome':
137 if profformat == 'chrome':
126 showmin = ui.configwith(fraction, 'profiling', 'showmin', 0.005)
138 showmin = ui.configwith(fraction, 'profiling', 'showmin', 0.005)
127 showmax = ui.configwith(fraction, 'profiling', 'showmax', 0.999)
139 showmax = ui.configwith(fraction, 'profiling', 'showmax', 0.999)
128 kwargs.update(minthreshold=showmin, maxthreshold=showmax)
140 kwargs.update(minthreshold=showmin, maxthreshold=showmax)
129
141
130 statprof.display(fp, data=data, format=displayformat, **kwargs)
142 statprof.display(fp, data=data, format=displayformat, **kwargs)
131
143
132 @contextlib.contextmanager
144 @contextlib.contextmanager
133 def profile(ui):
145 def profile(ui):
134 """Start profiling.
146 """Start profiling.
135
147
136 Profiling is active when the context manager is active. When the context
148 Profiling is active when the context manager is active. When the context
137 manager exits, profiling results will be written to the configured output.
149 manager exits, profiling results will be written to the configured output.
138 """
150 """
139 profiler = encoding.environ.get('HGPROF')
151 profiler = encoding.environ.get('HGPROF')
152 proffn = None
140 if profiler is None:
153 if profiler is None:
141 profiler = ui.config('profiling', 'type', default='stat')
154 profiler = ui.config('profiling', 'type', default='stat')
142 if profiler not in ('ls', 'stat', 'flame'):
155 if profiler not in ('ls', 'stat', 'flame'):
156 # try load profiler from extension with the same name
157 proffn = _loadprofiler(ui, profiler)
158 if proffn is None:
143 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
159 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
144 profiler = 'stat'
160 profiler = 'stat'
145
161
146 output = ui.config('profiling', 'output')
162 output = ui.config('profiling', 'output')
147
163
148 if output == 'blackbox':
164 if output == 'blackbox':
149 fp = util.stringio()
165 fp = util.stringio()
150 elif output:
166 elif output:
151 path = ui.expandpath(output)
167 path = ui.expandpath(output)
152 fp = open(path, 'wb')
168 fp = open(path, 'wb')
153 else:
169 else:
154 fp = ui.ferr
170 fp = ui.ferr
155
171
156 try:
172 try:
157 if profiler == 'ls':
173 if proffn is not None:
174 pass
175 elif profiler == 'ls':
158 proffn = lsprofile
176 proffn = lsprofile
159 elif profiler == 'flame':
177 elif profiler == 'flame':
160 proffn = flameprofile
178 proffn = flameprofile
161 else:
179 else:
162 proffn = statprofile
180 proffn = statprofile
163
181
164 with proffn(ui, fp):
182 with proffn(ui, fp):
165 yield
183 yield
166
184
167 finally:
185 finally:
168 if output:
186 if output:
169 if output == 'blackbox':
187 if output == 'blackbox':
170 val = 'Profile:\n%s' % fp.getvalue()
188 val = 'Profile:\n%s' % fp.getvalue()
171 # ui.log treats the input as a format string,
189 # ui.log treats the input as a format string,
172 # so we need to escape any % signs.
190 # so we need to escape any % signs.
173 val = val.replace('%', '%%')
191 val = val.replace('%', '%%')
174 ui.log('profile', val)
192 ui.log('profile', val)
175 fp.close()
193 fp.close()
176
194
177 @contextlib.contextmanager
195 @contextlib.contextmanager
178 def maybeprofile(ui):
196 def maybeprofile(ui):
179 """Profile if enabled, else do nothing.
197 """Profile if enabled, else do nothing.
180
198
181 This context manager can be used to optionally profile if profiling
199 This context manager can be used to optionally profile if profiling
182 is enabled. Otherwise, it does nothing.
200 is enabled. Otherwise, it does nothing.
183
201
184 The purpose of this context manager is to make calling code simpler:
202 The purpose of this context manager is to make calling code simpler:
185 just use a single code path for calling into code you may want to profile
203 just use a single code path for calling into code you may want to profile
186 and this function determines whether to start profiling.
204 and this function determines whether to start profiling.
187 """
205 """
188 if ui.configbool('profiling', 'enabled'):
206 if ui.configbool('profiling', 'enabled'):
189 with profile(ui):
207 with profile(ui):
190 yield
208 yield
191 else:
209 else:
192 yield
210 yield
@@ -1,101 +1,149 b''
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 #if lsprof
7 #if lsprof
8
8
9 test --profile
9 test --profile
10
10
11 $ prof='hg --config profiling.type=ls --profile'
11 $ prof='hg --config profiling.type=ls --profile'
12
12
13 $ $prof st 2>../out
13 $ $prof st 2>../out
14 $ grep CallCount ../out > /dev/null || cat ../out
14 $ grep CallCount ../out > /dev/null || cat ../out
15
15
16 $ $prof --config profiling.output=../out st
16 $ $prof --config profiling.output=../out st
17 $ grep CallCount ../out > /dev/null || cat ../out
17 $ grep CallCount ../out > /dev/null || cat ../out
18
18
19 $ $prof --config profiling.output=blackbox --config extensions.blackbox= st
19 $ $prof --config profiling.output=blackbox --config extensions.blackbox= st
20 $ grep CallCount .hg/blackbox.log > /dev/null || cat .hg/blackbox.log
20 $ grep CallCount .hg/blackbox.log > /dev/null || cat .hg/blackbox.log
21
21
22 $ $prof --config profiling.format=text st 2>../out
22 $ $prof --config profiling.format=text st 2>../out
23 $ grep CallCount ../out > /dev/null || cat ../out
23 $ grep CallCount ../out > /dev/null || cat ../out
24
24
25 $ echo "[profiling]" >> $HGRCPATH
25 $ echo "[profiling]" >> $HGRCPATH
26 $ echo "format=kcachegrind" >> $HGRCPATH
26 $ echo "format=kcachegrind" >> $HGRCPATH
27
27
28 $ $prof st 2>../out
28 $ $prof st 2>../out
29 $ grep 'events: Ticks' ../out > /dev/null || cat ../out
29 $ grep 'events: Ticks' ../out > /dev/null || cat ../out
30
30
31 $ $prof --config profiling.output=../out st
31 $ $prof --config profiling.output=../out st
32 $ grep 'events: Ticks' ../out > /dev/null || cat ../out
32 $ grep 'events: Ticks' ../out > /dev/null || cat ../out
33
33
34 #endif
34 #endif
35
35
36 #if lsprof serve
36 #if lsprof serve
37
37
38 Profiling of HTTP requests works
38 Profiling of HTTP requests works
39
39
40 $ $prof --config profiling.format=text --config profiling.output=../profile.log serve -d -p $HGPORT --pid-file ../hg.pid -A ../access.log
40 $ $prof --config profiling.format=text --config profiling.output=../profile.log serve -d -p $HGPORT --pid-file ../hg.pid -A ../access.log
41 $ cat ../hg.pid >> $DAEMON_PIDS
41 $ cat ../hg.pid >> $DAEMON_PIDS
42 $ hg -q clone -U http://localhost:$HGPORT ../clone
42 $ hg -q clone -U http://localhost:$HGPORT ../clone
43
43
44 A single profile is logged because file logging doesn't append
44 A single profile is logged because file logging doesn't append
45 $ grep CallCount ../profile.log | wc -l
45 $ grep CallCount ../profile.log | wc -l
46 \s*1 (re)
46 \s*1 (re)
47
47
48 #endif
48 #endif
49
49
50 Install an extension that can sleep and guarantee a profiler has time to run
50 Install an extension that can sleep and guarantee a profiler has time to run
51
51
52 $ cat >> sleepext.py << EOF
52 $ cat >> sleepext.py << EOF
53 > import time
53 > import time
54 > from mercurial import registrar, commands
54 > from mercurial import registrar, commands
55 > cmdtable = {}
55 > cmdtable = {}
56 > command = registrar.command(cmdtable)
56 > command = registrar.command(cmdtable)
57 > @command('sleep', [], 'hg sleep')
57 > @command('sleep', [], 'hg sleep')
58 > def sleep(ui, *args, **kwargs):
58 > def sleep(ui, *args, **kwargs):
59 > time.sleep(0.1)
59 > time.sleep(0.1)
60 > EOF
60 > EOF
61
61
62 $ cat >> $HGRCPATH << EOF
62 $ cat >> $HGRCPATH << EOF
63 > [extensions]
63 > [extensions]
64 > sleep = `pwd`/sleepext.py
64 > sleep = `pwd`/sleepext.py
65 > EOF
65 > EOF
66
66
67 statistical profiler works
67 statistical profiler works
68
68
69 $ hg --profile sleep 2>../out
69 $ hg --profile sleep 2>../out
70 $ grep Sample ../out
70 $ grep Sample ../out
71 Sample count: \d+ (re)
71 Sample count: \d+ (re)
72
72
73 Various statprof formatters work
73 Various statprof formatters work
74
74
75 $ hg --profile --config profiling.statformat=byline sleep 2>../out
75 $ hg --profile --config profiling.statformat=byline sleep 2>../out
76 $ head -n 1 ../out
76 $ head -n 1 ../out
77 % cumulative self
77 % cumulative self
78 $ grep Sample ../out
78 $ grep Sample ../out
79 Sample count: \d+ (re)
79 Sample count: \d+ (re)
80
80
81 $ hg --profile --config profiling.statformat=bymethod sleep 2>../out
81 $ hg --profile --config profiling.statformat=bymethod sleep 2>../out
82 $ head -n 1 ../out
82 $ head -n 1 ../out
83 % cumulative self
83 % cumulative self
84 $ grep Sample ../out
84 $ grep Sample ../out
85 Sample count: \d+ (re)
85 Sample count: \d+ (re)
86
86
87 $ hg --profile --config profiling.statformat=hotpath sleep 2>../out
87 $ hg --profile --config profiling.statformat=hotpath sleep 2>../out
88 $ grep Sample ../out
88 $ grep Sample ../out
89 Sample count: \d+ (re)
89 Sample count: \d+ (re)
90
90
91 $ hg --profile --config profiling.statformat=json sleep 2>../out
91 $ hg --profile --config profiling.statformat=json sleep 2>../out
92 $ cat ../out
92 $ cat ../out
93 \[\[-?\d+.* (re)
93 \[\[-?\d+.* (re)
94
94
95 statprof can be used as a standalone module
95 statprof can be used as a standalone module
96
96
97 $ $PYTHON -m mercurial.statprof hotpath
97 $ $PYTHON -m mercurial.statprof hotpath
98 must specify --file to load
98 must specify --file to load
99 [1]
99 [1]
100
100
101 $ cd ..
101 $ cd ..
102
103 profiler extension could be loaded before other extensions
104
105 $ cat > fooprof.py <<EOF
106 > from __future__ import absolute_import
107 > import contextlib
108 > @contextlib.contextmanager
109 > def profile(ui, fp):
110 > print('fooprof: start profile')
111 > yield
112 > print('fooprof: end profile')
113 > def extsetup(ui):
114 > ui.write('fooprof: loaded\n')
115 > EOF
116
117 $ cat > otherextension.py <<EOF
118 > from __future__ import absolute_import
119 > def extsetup(ui):
120 > ui.write('otherextension: loaded\n')
121 > EOF
122
123 $ hg init b
124 $ cd b
125 $ cat >> .hg/hgrc <<EOF
126 > [extensions]
127 > other = $TESTTMP/otherextension.py
128 > fooprof = $TESTTMP/fooprof.py
129 > EOF
130
131 $ hg root
132 otherextension: loaded
133 fooprof: loaded
134 $TESTTMP/b (glob)
135 $ HGPROF=fooprof hg root --profile
136 fooprof: loaded
137 fooprof: start profile
138 otherextension: loaded
139 $TESTTMP/b (glob)
140 fooprof: end profile
141
142 $ HGPROF=other hg root --profile 2>&1 | head -n 2
143 otherextension: loaded
144 unrecognized profiler 'other' - ignored
145
146 $ HGPROF=unknown hg root --profile 2>&1 | head -n 1
147 unrecognized profiler 'unknown' - ignored
148
149 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now