##// END OF EJS Templates
perftemplating: move revision argument to flag only...
Boris Feld -
r38275:b8f75bc9 default
parent child Browse files
Show More
@@ -1,1777 +1,1779
1 # perf.py - performance test routines
1 # perf.py - performance test routines
2 '''helper extension to measure performance'''
2 '''helper extension to measure performance'''
3
3
4 # "historical portability" policy of perf.py:
4 # "historical portability" policy of perf.py:
5 #
5 #
6 # We have to do:
6 # We have to do:
7 # - make perf.py "loadable" with as wide Mercurial version as possible
7 # - make perf.py "loadable" with as wide Mercurial version as possible
8 # This doesn't mean that perf commands work correctly with that Mercurial.
8 # This doesn't mean that perf commands work correctly with that Mercurial.
9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
10 # - make historical perf command work correctly with as wide Mercurial
10 # - make historical perf command work correctly with as wide Mercurial
11 # version as possible
11 # version as possible
12 #
12 #
13 # We have to do, if possible with reasonable cost:
13 # We have to do, if possible with reasonable cost:
14 # - make recent perf command for historical feature work correctly
14 # - make recent perf command for historical feature work correctly
15 # with early Mercurial
15 # with early Mercurial
16 #
16 #
17 # We don't have to do:
17 # We don't have to do:
18 # - make perf command for recent feature work correctly with early
18 # - make perf command for recent feature work correctly with early
19 # Mercurial
19 # Mercurial
20
20
21 from __future__ import absolute_import
21 from __future__ import absolute_import
22 import functools
22 import functools
23 import gc
23 import gc
24 import os
24 import os
25 import random
25 import random
26 import struct
26 import struct
27 import sys
27 import sys
28 import threading
28 import threading
29 import time
29 import time
30 from mercurial import (
30 from mercurial import (
31 changegroup,
31 changegroup,
32 cmdutil,
32 cmdutil,
33 commands,
33 commands,
34 copies,
34 copies,
35 error,
35 error,
36 extensions,
36 extensions,
37 mdiff,
37 mdiff,
38 merge,
38 merge,
39 revlog,
39 revlog,
40 util,
40 util,
41 )
41 )
42
42
43 # for "historical portability":
43 # for "historical portability":
44 # try to import modules separately (in dict order), and ignore
44 # try to import modules separately (in dict order), and ignore
45 # failure, because these aren't available with early Mercurial
45 # failure, because these aren't available with early Mercurial
46 try:
46 try:
47 from mercurial import branchmap # since 2.5 (or bcee63733aad)
47 from mercurial import branchmap # since 2.5 (or bcee63733aad)
48 except ImportError:
48 except ImportError:
49 pass
49 pass
50 try:
50 try:
51 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
51 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
52 except ImportError:
52 except ImportError:
53 pass
53 pass
54 try:
54 try:
55 from mercurial import registrar # since 3.7 (or 37d50250b696)
55 from mercurial import registrar # since 3.7 (or 37d50250b696)
56 dir(registrar) # forcibly load it
56 dir(registrar) # forcibly load it
57 except ImportError:
57 except ImportError:
58 registrar = None
58 registrar = None
59 try:
59 try:
60 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
60 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
61 except ImportError:
61 except ImportError:
62 pass
62 pass
63 try:
63 try:
64 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
64 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
65 except ImportError:
65 except ImportError:
66 pass
66 pass
67 try:
67 try:
68 from mercurial import pycompat
68 from mercurial import pycompat
69 getargspec = pycompat.getargspec # added to module after 4.5
69 getargspec = pycompat.getargspec # added to module after 4.5
70 except (ImportError, AttributeError):
70 except (ImportError, AttributeError):
71 import inspect
71 import inspect
72 getargspec = inspect.getargspec
72 getargspec = inspect.getargspec
73
73
74 try:
74 try:
75 # 4.7+
75 # 4.7+
76 queue = pycompat.queue.Queue
76 queue = pycompat.queue.Queue
77 except (AttributeError, ImportError):
77 except (AttributeError, ImportError):
78 # <4.7.
78 # <4.7.
79 try:
79 try:
80 queue = pycompat.queue
80 queue = pycompat.queue
81 except (AttributeError, ImportError):
81 except (AttributeError, ImportError):
82 queue = util.queue
82 queue = util.queue
83
83
84 # for "historical portability":
84 # for "historical portability":
85 # define util.safehasattr forcibly, because util.safehasattr has been
85 # define util.safehasattr forcibly, because util.safehasattr has been
86 # available since 1.9.3 (or 94b200a11cf7)
86 # available since 1.9.3 (or 94b200a11cf7)
87 _undefined = object()
87 _undefined = object()
88 def safehasattr(thing, attr):
88 def safehasattr(thing, attr):
89 return getattr(thing, attr, _undefined) is not _undefined
89 return getattr(thing, attr, _undefined) is not _undefined
90 setattr(util, 'safehasattr', safehasattr)
90 setattr(util, 'safehasattr', safehasattr)
91
91
92 # for "historical portability":
92 # for "historical portability":
93 # define util.timer forcibly, because util.timer has been available
93 # define util.timer forcibly, because util.timer has been available
94 # since ae5d60bb70c9
94 # since ae5d60bb70c9
95 if safehasattr(time, 'perf_counter'):
95 if safehasattr(time, 'perf_counter'):
96 util.timer = time.perf_counter
96 util.timer = time.perf_counter
97 elif os.name == 'nt':
97 elif os.name == 'nt':
98 util.timer = time.clock
98 util.timer = time.clock
99 else:
99 else:
100 util.timer = time.time
100 util.timer = time.time
101
101
102 # for "historical portability":
102 # for "historical portability":
103 # use locally defined empty option list, if formatteropts isn't
103 # use locally defined empty option list, if formatteropts isn't
104 # available, because commands.formatteropts has been available since
104 # available, because commands.formatteropts has been available since
105 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
105 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
106 # available since 2.2 (or ae5f92e154d3)
106 # available since 2.2 (or ae5f92e154d3)
107 formatteropts = getattr(cmdutil, "formatteropts",
107 formatteropts = getattr(cmdutil, "formatteropts",
108 getattr(commands, "formatteropts", []))
108 getattr(commands, "formatteropts", []))
109
109
110 # for "historical portability":
110 # for "historical portability":
111 # use locally defined option list, if debugrevlogopts isn't available,
111 # use locally defined option list, if debugrevlogopts isn't available,
112 # because commands.debugrevlogopts has been available since 3.7 (or
112 # because commands.debugrevlogopts has been available since 3.7 (or
113 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
113 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
114 # since 1.9 (or a79fea6b3e77).
114 # since 1.9 (or a79fea6b3e77).
115 revlogopts = getattr(cmdutil, "debugrevlogopts",
115 revlogopts = getattr(cmdutil, "debugrevlogopts",
116 getattr(commands, "debugrevlogopts", [
116 getattr(commands, "debugrevlogopts", [
117 ('c', 'changelog', False, ('open changelog')),
117 ('c', 'changelog', False, ('open changelog')),
118 ('m', 'manifest', False, ('open manifest')),
118 ('m', 'manifest', False, ('open manifest')),
119 ('', 'dir', False, ('open directory manifest')),
119 ('', 'dir', False, ('open directory manifest')),
120 ]))
120 ]))
121
121
122 cmdtable = {}
122 cmdtable = {}
123
123
124 # for "historical portability":
124 # for "historical portability":
125 # define parsealiases locally, because cmdutil.parsealiases has been
125 # define parsealiases locally, because cmdutil.parsealiases has been
126 # available since 1.5 (or 6252852b4332)
126 # available since 1.5 (or 6252852b4332)
127 def parsealiases(cmd):
127 def parsealiases(cmd):
128 return cmd.lstrip("^").split("|")
128 return cmd.lstrip("^").split("|")
129
129
130 if safehasattr(registrar, 'command'):
130 if safehasattr(registrar, 'command'):
131 command = registrar.command(cmdtable)
131 command = registrar.command(cmdtable)
132 elif safehasattr(cmdutil, 'command'):
132 elif safehasattr(cmdutil, 'command'):
133 command = cmdutil.command(cmdtable)
133 command = cmdutil.command(cmdtable)
134 if 'norepo' not in getargspec(command).args:
134 if 'norepo' not in getargspec(command).args:
135 # for "historical portability":
135 # for "historical portability":
136 # wrap original cmdutil.command, because "norepo" option has
136 # wrap original cmdutil.command, because "norepo" option has
137 # been available since 3.1 (or 75a96326cecb)
137 # been available since 3.1 (or 75a96326cecb)
138 _command = command
138 _command = command
139 def command(name, options=(), synopsis=None, norepo=False):
139 def command(name, options=(), synopsis=None, norepo=False):
140 if norepo:
140 if norepo:
141 commands.norepo += ' %s' % ' '.join(parsealiases(name))
141 commands.norepo += ' %s' % ' '.join(parsealiases(name))
142 return _command(name, list(options), synopsis)
142 return _command(name, list(options), synopsis)
143 else:
143 else:
144 # for "historical portability":
144 # for "historical portability":
145 # define "@command" annotation locally, because cmdutil.command
145 # define "@command" annotation locally, because cmdutil.command
146 # has been available since 1.9 (or 2daa5179e73f)
146 # has been available since 1.9 (or 2daa5179e73f)
147 def command(name, options=(), synopsis=None, norepo=False):
147 def command(name, options=(), synopsis=None, norepo=False):
148 def decorator(func):
148 def decorator(func):
149 if synopsis:
149 if synopsis:
150 cmdtable[name] = func, list(options), synopsis
150 cmdtable[name] = func, list(options), synopsis
151 else:
151 else:
152 cmdtable[name] = func, list(options)
152 cmdtable[name] = func, list(options)
153 if norepo:
153 if norepo:
154 commands.norepo += ' %s' % ' '.join(parsealiases(name))
154 commands.norepo += ' %s' % ' '.join(parsealiases(name))
155 return func
155 return func
156 return decorator
156 return decorator
157
157
158 try:
158 try:
159 import mercurial.registrar
159 import mercurial.registrar
160 import mercurial.configitems
160 import mercurial.configitems
161 configtable = {}
161 configtable = {}
162 configitem = mercurial.registrar.configitem(configtable)
162 configitem = mercurial.registrar.configitem(configtable)
163 configitem('perf', 'presleep',
163 configitem('perf', 'presleep',
164 default=mercurial.configitems.dynamicdefault,
164 default=mercurial.configitems.dynamicdefault,
165 )
165 )
166 configitem('perf', 'stub',
166 configitem('perf', 'stub',
167 default=mercurial.configitems.dynamicdefault,
167 default=mercurial.configitems.dynamicdefault,
168 )
168 )
169 configitem('perf', 'parentscount',
169 configitem('perf', 'parentscount',
170 default=mercurial.configitems.dynamicdefault,
170 default=mercurial.configitems.dynamicdefault,
171 )
171 )
172 except (ImportError, AttributeError):
172 except (ImportError, AttributeError):
173 pass
173 pass
174
174
175 def getlen(ui):
175 def getlen(ui):
176 if ui.configbool("perf", "stub", False):
176 if ui.configbool("perf", "stub", False):
177 return lambda x: 1
177 return lambda x: 1
178 return len
178 return len
179
179
180 def gettimer(ui, opts=None):
180 def gettimer(ui, opts=None):
181 """return a timer function and formatter: (timer, formatter)
181 """return a timer function and formatter: (timer, formatter)
182
182
183 This function exists to gather the creation of formatter in a single
183 This function exists to gather the creation of formatter in a single
184 place instead of duplicating it in all performance commands."""
184 place instead of duplicating it in all performance commands."""
185
185
186 # enforce an idle period before execution to counteract power management
186 # enforce an idle period before execution to counteract power management
187 # experimental config: perf.presleep
187 # experimental config: perf.presleep
188 time.sleep(getint(ui, "perf", "presleep", 1))
188 time.sleep(getint(ui, "perf", "presleep", 1))
189
189
190 if opts is None:
190 if opts is None:
191 opts = {}
191 opts = {}
192 # redirect all to stderr unless buffer api is in use
192 # redirect all to stderr unless buffer api is in use
193 if not ui._buffers:
193 if not ui._buffers:
194 ui = ui.copy()
194 ui = ui.copy()
195 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
195 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
196 if uifout:
196 if uifout:
197 # for "historical portability":
197 # for "historical portability":
198 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
198 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
199 uifout.set(ui.ferr)
199 uifout.set(ui.ferr)
200
200
201 # get a formatter
201 # get a formatter
202 uiformatter = getattr(ui, 'formatter', None)
202 uiformatter = getattr(ui, 'formatter', None)
203 if uiformatter:
203 if uiformatter:
204 fm = uiformatter('perf', opts)
204 fm = uiformatter('perf', opts)
205 else:
205 else:
206 # for "historical portability":
206 # for "historical portability":
207 # define formatter locally, because ui.formatter has been
207 # define formatter locally, because ui.formatter has been
208 # available since 2.2 (or ae5f92e154d3)
208 # available since 2.2 (or ae5f92e154d3)
209 from mercurial import node
209 from mercurial import node
210 class defaultformatter(object):
210 class defaultformatter(object):
211 """Minimized composition of baseformatter and plainformatter
211 """Minimized composition of baseformatter and plainformatter
212 """
212 """
213 def __init__(self, ui, topic, opts):
213 def __init__(self, ui, topic, opts):
214 self._ui = ui
214 self._ui = ui
215 if ui.debugflag:
215 if ui.debugflag:
216 self.hexfunc = node.hex
216 self.hexfunc = node.hex
217 else:
217 else:
218 self.hexfunc = node.short
218 self.hexfunc = node.short
219 def __nonzero__(self):
219 def __nonzero__(self):
220 return False
220 return False
221 __bool__ = __nonzero__
221 __bool__ = __nonzero__
222 def startitem(self):
222 def startitem(self):
223 pass
223 pass
224 def data(self, **data):
224 def data(self, **data):
225 pass
225 pass
226 def write(self, fields, deftext, *fielddata, **opts):
226 def write(self, fields, deftext, *fielddata, **opts):
227 self._ui.write(deftext % fielddata, **opts)
227 self._ui.write(deftext % fielddata, **opts)
228 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
228 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
229 if cond:
229 if cond:
230 self._ui.write(deftext % fielddata, **opts)
230 self._ui.write(deftext % fielddata, **opts)
231 def plain(self, text, **opts):
231 def plain(self, text, **opts):
232 self._ui.write(text, **opts)
232 self._ui.write(text, **opts)
233 def end(self):
233 def end(self):
234 pass
234 pass
235 fm = defaultformatter(ui, 'perf', opts)
235 fm = defaultformatter(ui, 'perf', opts)
236
236
237 # stub function, runs code only once instead of in a loop
237 # stub function, runs code only once instead of in a loop
238 # experimental config: perf.stub
238 # experimental config: perf.stub
239 if ui.configbool("perf", "stub", False):
239 if ui.configbool("perf", "stub", False):
240 return functools.partial(stub_timer, fm), fm
240 return functools.partial(stub_timer, fm), fm
241 return functools.partial(_timer, fm), fm
241 return functools.partial(_timer, fm), fm
242
242
243 def stub_timer(fm, func, title=None):
243 def stub_timer(fm, func, title=None):
244 func()
244 func()
245
245
246 def _timer(fm, func, title=None):
246 def _timer(fm, func, title=None):
247 gc.collect()
247 gc.collect()
248 results = []
248 results = []
249 begin = util.timer()
249 begin = util.timer()
250 count = 0
250 count = 0
251 while True:
251 while True:
252 ostart = os.times()
252 ostart = os.times()
253 cstart = util.timer()
253 cstart = util.timer()
254 r = func()
254 r = func()
255 cstop = util.timer()
255 cstop = util.timer()
256 ostop = os.times()
256 ostop = os.times()
257 count += 1
257 count += 1
258 a, b = ostart, ostop
258 a, b = ostart, ostop
259 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
259 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
260 if cstop - begin > 3 and count >= 100:
260 if cstop - begin > 3 and count >= 100:
261 break
261 break
262 if cstop - begin > 10 and count >= 3:
262 if cstop - begin > 10 and count >= 3:
263 break
263 break
264
264
265 fm.startitem()
265 fm.startitem()
266
266
267 if title:
267 if title:
268 fm.write('title', '! %s\n', title)
268 fm.write('title', '! %s\n', title)
269 if r:
269 if r:
270 fm.write('result', '! result: %s\n', r)
270 fm.write('result', '! result: %s\n', r)
271 m = min(results)
271 m = min(results)
272 fm.plain('!')
272 fm.plain('!')
273 fm.write('wall', ' wall %f', m[0])
273 fm.write('wall', ' wall %f', m[0])
274 fm.write('comb', ' comb %f', m[1] + m[2])
274 fm.write('comb', ' comb %f', m[1] + m[2])
275 fm.write('user', ' user %f', m[1])
275 fm.write('user', ' user %f', m[1])
276 fm.write('sys', ' sys %f', m[2])
276 fm.write('sys', ' sys %f', m[2])
277 fm.write('count', ' (best of %d)', count)
277 fm.write('count', ' (best of %d)', count)
278 fm.plain('\n')
278 fm.plain('\n')
279
279
280 # utilities for historical portability
280 # utilities for historical portability
281
281
282 def getint(ui, section, name, default):
282 def getint(ui, section, name, default):
283 # for "historical portability":
283 # for "historical portability":
284 # ui.configint has been available since 1.9 (or fa2b596db182)
284 # ui.configint has been available since 1.9 (or fa2b596db182)
285 v = ui.config(section, name, None)
285 v = ui.config(section, name, None)
286 if v is None:
286 if v is None:
287 return default
287 return default
288 try:
288 try:
289 return int(v)
289 return int(v)
290 except ValueError:
290 except ValueError:
291 raise error.ConfigError(("%s.%s is not an integer ('%s')")
291 raise error.ConfigError(("%s.%s is not an integer ('%s')")
292 % (section, name, v))
292 % (section, name, v))
293
293
294 def safeattrsetter(obj, name, ignoremissing=False):
294 def safeattrsetter(obj, name, ignoremissing=False):
295 """Ensure that 'obj' has 'name' attribute before subsequent setattr
295 """Ensure that 'obj' has 'name' attribute before subsequent setattr
296
296
297 This function is aborted, if 'obj' doesn't have 'name' attribute
297 This function is aborted, if 'obj' doesn't have 'name' attribute
298 at runtime. This avoids overlooking removal of an attribute, which
298 at runtime. This avoids overlooking removal of an attribute, which
299 breaks assumption of performance measurement, in the future.
299 breaks assumption of performance measurement, in the future.
300
300
301 This function returns the object to (1) assign a new value, and
301 This function returns the object to (1) assign a new value, and
302 (2) restore an original value to the attribute.
302 (2) restore an original value to the attribute.
303
303
304 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
304 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
305 abortion, and this function returns None. This is useful to
305 abortion, and this function returns None. This is useful to
306 examine an attribute, which isn't ensured in all Mercurial
306 examine an attribute, which isn't ensured in all Mercurial
307 versions.
307 versions.
308 """
308 """
309 if not util.safehasattr(obj, name):
309 if not util.safehasattr(obj, name):
310 if ignoremissing:
310 if ignoremissing:
311 return None
311 return None
312 raise error.Abort(("missing attribute %s of %s might break assumption"
312 raise error.Abort(("missing attribute %s of %s might break assumption"
313 " of performance measurement") % (name, obj))
313 " of performance measurement") % (name, obj))
314
314
315 origvalue = getattr(obj, name)
315 origvalue = getattr(obj, name)
316 class attrutil(object):
316 class attrutil(object):
317 def set(self, newvalue):
317 def set(self, newvalue):
318 setattr(obj, name, newvalue)
318 setattr(obj, name, newvalue)
319 def restore(self):
319 def restore(self):
320 setattr(obj, name, origvalue)
320 setattr(obj, name, origvalue)
321
321
322 return attrutil()
322 return attrutil()
323
323
324 # utilities to examine each internal API changes
324 # utilities to examine each internal API changes
325
325
326 def getbranchmapsubsettable():
326 def getbranchmapsubsettable():
327 # for "historical portability":
327 # for "historical portability":
328 # subsettable is defined in:
328 # subsettable is defined in:
329 # - branchmap since 2.9 (or 175c6fd8cacc)
329 # - branchmap since 2.9 (or 175c6fd8cacc)
330 # - repoview since 2.5 (or 59a9f18d4587)
330 # - repoview since 2.5 (or 59a9f18d4587)
331 for mod in (branchmap, repoview):
331 for mod in (branchmap, repoview):
332 subsettable = getattr(mod, 'subsettable', None)
332 subsettable = getattr(mod, 'subsettable', None)
333 if subsettable:
333 if subsettable:
334 return subsettable
334 return subsettable
335
335
336 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
336 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
337 # branchmap and repoview modules exist, but subsettable attribute
337 # branchmap and repoview modules exist, but subsettable attribute
338 # doesn't)
338 # doesn't)
339 raise error.Abort(("perfbranchmap not available with this Mercurial"),
339 raise error.Abort(("perfbranchmap not available with this Mercurial"),
340 hint="use 2.5 or later")
340 hint="use 2.5 or later")
341
341
342 def getsvfs(repo):
342 def getsvfs(repo):
343 """Return appropriate object to access files under .hg/store
343 """Return appropriate object to access files under .hg/store
344 """
344 """
345 # for "historical portability":
345 # for "historical portability":
346 # repo.svfs has been available since 2.3 (or 7034365089bf)
346 # repo.svfs has been available since 2.3 (or 7034365089bf)
347 svfs = getattr(repo, 'svfs', None)
347 svfs = getattr(repo, 'svfs', None)
348 if svfs:
348 if svfs:
349 return svfs
349 return svfs
350 else:
350 else:
351 return getattr(repo, 'sopener')
351 return getattr(repo, 'sopener')
352
352
353 def getvfs(repo):
353 def getvfs(repo):
354 """Return appropriate object to access files under .hg
354 """Return appropriate object to access files under .hg
355 """
355 """
356 # for "historical portability":
356 # for "historical portability":
357 # repo.vfs has been available since 2.3 (or 7034365089bf)
357 # repo.vfs has been available since 2.3 (or 7034365089bf)
358 vfs = getattr(repo, 'vfs', None)
358 vfs = getattr(repo, 'vfs', None)
359 if vfs:
359 if vfs:
360 return vfs
360 return vfs
361 else:
361 else:
362 return getattr(repo, 'opener')
362 return getattr(repo, 'opener')
363
363
364 def repocleartagscachefunc(repo):
364 def repocleartagscachefunc(repo):
365 """Return the function to clear tags cache according to repo internal API
365 """Return the function to clear tags cache according to repo internal API
366 """
366 """
367 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
367 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
368 # in this case, setattr(repo, '_tagscache', None) or so isn't
368 # in this case, setattr(repo, '_tagscache', None) or so isn't
369 # correct way to clear tags cache, because existing code paths
369 # correct way to clear tags cache, because existing code paths
370 # expect _tagscache to be a structured object.
370 # expect _tagscache to be a structured object.
371 def clearcache():
371 def clearcache():
372 # _tagscache has been filteredpropertycache since 2.5 (or
372 # _tagscache has been filteredpropertycache since 2.5 (or
373 # 98c867ac1330), and delattr() can't work in such case
373 # 98c867ac1330), and delattr() can't work in such case
374 if '_tagscache' in vars(repo):
374 if '_tagscache' in vars(repo):
375 del repo.__dict__['_tagscache']
375 del repo.__dict__['_tagscache']
376 return clearcache
376 return clearcache
377
377
378 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
378 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
379 if repotags: # since 1.4 (or 5614a628d173)
379 if repotags: # since 1.4 (or 5614a628d173)
380 return lambda : repotags.set(None)
380 return lambda : repotags.set(None)
381
381
382 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
382 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
383 if repotagscache: # since 0.6 (or d7df759d0e97)
383 if repotagscache: # since 0.6 (or d7df759d0e97)
384 return lambda : repotagscache.set(None)
384 return lambda : repotagscache.set(None)
385
385
386 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
386 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
387 # this point, but it isn't so problematic, because:
387 # this point, but it isn't so problematic, because:
388 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
388 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
389 # in perftags() causes failure soon
389 # in perftags() causes failure soon
390 # - perf.py itself has been available since 1.1 (or eb240755386d)
390 # - perf.py itself has been available since 1.1 (or eb240755386d)
391 raise error.Abort(("tags API of this hg command is unknown"))
391 raise error.Abort(("tags API of this hg command is unknown"))
392
392
393 # utilities to clear cache
393 # utilities to clear cache
394
394
395 def clearfilecache(repo, attrname):
395 def clearfilecache(repo, attrname):
396 unfi = repo.unfiltered()
396 unfi = repo.unfiltered()
397 if attrname in vars(unfi):
397 if attrname in vars(unfi):
398 delattr(unfi, attrname)
398 delattr(unfi, attrname)
399 unfi._filecache.pop(attrname, None)
399 unfi._filecache.pop(attrname, None)
400
400
401 # perf commands
401 # perf commands
402
402
403 @command('perfwalk', formatteropts)
403 @command('perfwalk', formatteropts)
404 def perfwalk(ui, repo, *pats, **opts):
404 def perfwalk(ui, repo, *pats, **opts):
405 timer, fm = gettimer(ui, opts)
405 timer, fm = gettimer(ui, opts)
406 m = scmutil.match(repo[None], pats, {})
406 m = scmutil.match(repo[None], pats, {})
407 timer(lambda: len(list(repo.dirstate.walk(m, subrepos=[], unknown=True,
407 timer(lambda: len(list(repo.dirstate.walk(m, subrepos=[], unknown=True,
408 ignored=False))))
408 ignored=False))))
409 fm.end()
409 fm.end()
410
410
411 @command('perfannotate', formatteropts)
411 @command('perfannotate', formatteropts)
412 def perfannotate(ui, repo, f, **opts):
412 def perfannotate(ui, repo, f, **opts):
413 timer, fm = gettimer(ui, opts)
413 timer, fm = gettimer(ui, opts)
414 fc = repo['.'][f]
414 fc = repo['.'][f]
415 timer(lambda: len(fc.annotate(True)))
415 timer(lambda: len(fc.annotate(True)))
416 fm.end()
416 fm.end()
417
417
418 @command('perfstatus',
418 @command('perfstatus',
419 [('u', 'unknown', False,
419 [('u', 'unknown', False,
420 'ask status to look for unknown files')] + formatteropts)
420 'ask status to look for unknown files')] + formatteropts)
421 def perfstatus(ui, repo, **opts):
421 def perfstatus(ui, repo, **opts):
422 #m = match.always(repo.root, repo.getcwd())
422 #m = match.always(repo.root, repo.getcwd())
423 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
423 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
424 # False))))
424 # False))))
425 timer, fm = gettimer(ui, opts)
425 timer, fm = gettimer(ui, opts)
426 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
426 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
427 fm.end()
427 fm.end()
428
428
429 @command('perfaddremove', formatteropts)
429 @command('perfaddremove', formatteropts)
430 def perfaddremove(ui, repo, **opts):
430 def perfaddremove(ui, repo, **opts):
431 timer, fm = gettimer(ui, opts)
431 timer, fm = gettimer(ui, opts)
432 try:
432 try:
433 oldquiet = repo.ui.quiet
433 oldquiet = repo.ui.quiet
434 repo.ui.quiet = True
434 repo.ui.quiet = True
435 matcher = scmutil.match(repo[None])
435 matcher = scmutil.match(repo[None])
436 opts['dry_run'] = True
436 opts['dry_run'] = True
437 timer(lambda: scmutil.addremove(repo, matcher, "", opts))
437 timer(lambda: scmutil.addremove(repo, matcher, "", opts))
438 finally:
438 finally:
439 repo.ui.quiet = oldquiet
439 repo.ui.quiet = oldquiet
440 fm.end()
440 fm.end()
441
441
442 def clearcaches(cl):
442 def clearcaches(cl):
443 # behave somewhat consistently across internal API changes
443 # behave somewhat consistently across internal API changes
444 if util.safehasattr(cl, 'clearcaches'):
444 if util.safehasattr(cl, 'clearcaches'):
445 cl.clearcaches()
445 cl.clearcaches()
446 elif util.safehasattr(cl, '_nodecache'):
446 elif util.safehasattr(cl, '_nodecache'):
447 from mercurial.node import nullid, nullrev
447 from mercurial.node import nullid, nullrev
448 cl._nodecache = {nullid: nullrev}
448 cl._nodecache = {nullid: nullrev}
449 cl._nodepos = None
449 cl._nodepos = None
450
450
451 @command('perfheads', formatteropts)
451 @command('perfheads', formatteropts)
452 def perfheads(ui, repo, **opts):
452 def perfheads(ui, repo, **opts):
453 timer, fm = gettimer(ui, opts)
453 timer, fm = gettimer(ui, opts)
454 cl = repo.changelog
454 cl = repo.changelog
455 def d():
455 def d():
456 len(cl.headrevs())
456 len(cl.headrevs())
457 clearcaches(cl)
457 clearcaches(cl)
458 timer(d)
458 timer(d)
459 fm.end()
459 fm.end()
460
460
461 @command('perftags', formatteropts)
461 @command('perftags', formatteropts)
462 def perftags(ui, repo, **opts):
462 def perftags(ui, repo, **opts):
463 import mercurial.changelog
463 import mercurial.changelog
464 import mercurial.manifest
464 import mercurial.manifest
465 timer, fm = gettimer(ui, opts)
465 timer, fm = gettimer(ui, opts)
466 svfs = getsvfs(repo)
466 svfs = getsvfs(repo)
467 repocleartagscache = repocleartagscachefunc(repo)
467 repocleartagscache = repocleartagscachefunc(repo)
468 def t():
468 def t():
469 repo.changelog = mercurial.changelog.changelog(svfs)
469 repo.changelog = mercurial.changelog.changelog(svfs)
470 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
470 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
471 repocleartagscache()
471 repocleartagscache()
472 return len(repo.tags())
472 return len(repo.tags())
473 timer(t)
473 timer(t)
474 fm.end()
474 fm.end()
475
475
476 @command('perfancestors', formatteropts)
476 @command('perfancestors', formatteropts)
477 def perfancestors(ui, repo, **opts):
477 def perfancestors(ui, repo, **opts):
478 timer, fm = gettimer(ui, opts)
478 timer, fm = gettimer(ui, opts)
479 heads = repo.changelog.headrevs()
479 heads = repo.changelog.headrevs()
480 def d():
480 def d():
481 for a in repo.changelog.ancestors(heads):
481 for a in repo.changelog.ancestors(heads):
482 pass
482 pass
483 timer(d)
483 timer(d)
484 fm.end()
484 fm.end()
485
485
486 @command('perfancestorset', formatteropts)
486 @command('perfancestorset', formatteropts)
487 def perfancestorset(ui, repo, revset, **opts):
487 def perfancestorset(ui, repo, revset, **opts):
488 timer, fm = gettimer(ui, opts)
488 timer, fm = gettimer(ui, opts)
489 revs = repo.revs(revset)
489 revs = repo.revs(revset)
490 heads = repo.changelog.headrevs()
490 heads = repo.changelog.headrevs()
491 def d():
491 def d():
492 s = repo.changelog.ancestors(heads)
492 s = repo.changelog.ancestors(heads)
493 for rev in revs:
493 for rev in revs:
494 rev in s
494 rev in s
495 timer(d)
495 timer(d)
496 fm.end()
496 fm.end()
497
497
498 @command('perfbookmarks', formatteropts)
498 @command('perfbookmarks', formatteropts)
499 def perfbookmarks(ui, repo, **opts):
499 def perfbookmarks(ui, repo, **opts):
500 """benchmark parsing bookmarks from disk to memory"""
500 """benchmark parsing bookmarks from disk to memory"""
501 timer, fm = gettimer(ui, opts)
501 timer, fm = gettimer(ui, opts)
502 def d():
502 def d():
503 clearfilecache(repo, '_bookmarks')
503 clearfilecache(repo, '_bookmarks')
504 repo._bookmarks
504 repo._bookmarks
505 timer(d)
505 timer(d)
506 fm.end()
506 fm.end()
507
507
508 @command('perfbundleread', formatteropts, 'BUNDLE')
508 @command('perfbundleread', formatteropts, 'BUNDLE')
509 def perfbundleread(ui, repo, bundlepath, **opts):
509 def perfbundleread(ui, repo, bundlepath, **opts):
510 """Benchmark reading of bundle files.
510 """Benchmark reading of bundle files.
511
511
512 This command is meant to isolate the I/O part of bundle reading as
512 This command is meant to isolate the I/O part of bundle reading as
513 much as possible.
513 much as possible.
514 """
514 """
515 from mercurial import (
515 from mercurial import (
516 bundle2,
516 bundle2,
517 exchange,
517 exchange,
518 streamclone,
518 streamclone,
519 )
519 )
520
520
521 def makebench(fn):
521 def makebench(fn):
522 def run():
522 def run():
523 with open(bundlepath, 'rb') as fh:
523 with open(bundlepath, 'rb') as fh:
524 bundle = exchange.readbundle(ui, fh, bundlepath)
524 bundle = exchange.readbundle(ui, fh, bundlepath)
525 fn(bundle)
525 fn(bundle)
526
526
527 return run
527 return run
528
528
529 def makereadnbytes(size):
529 def makereadnbytes(size):
530 def run():
530 def run():
531 with open(bundlepath, 'rb') as fh:
531 with open(bundlepath, 'rb') as fh:
532 bundle = exchange.readbundle(ui, fh, bundlepath)
532 bundle = exchange.readbundle(ui, fh, bundlepath)
533 while bundle.read(size):
533 while bundle.read(size):
534 pass
534 pass
535
535
536 return run
536 return run
537
537
538 def makestdioread(size):
538 def makestdioread(size):
539 def run():
539 def run():
540 with open(bundlepath, 'rb') as fh:
540 with open(bundlepath, 'rb') as fh:
541 while fh.read(size):
541 while fh.read(size):
542 pass
542 pass
543
543
544 return run
544 return run
545
545
546 # bundle1
546 # bundle1
547
547
548 def deltaiter(bundle):
548 def deltaiter(bundle):
549 for delta in bundle.deltaiter():
549 for delta in bundle.deltaiter():
550 pass
550 pass
551
551
552 def iterchunks(bundle):
552 def iterchunks(bundle):
553 for chunk in bundle.getchunks():
553 for chunk in bundle.getchunks():
554 pass
554 pass
555
555
556 # bundle2
556 # bundle2
557
557
558 def forwardchunks(bundle):
558 def forwardchunks(bundle):
559 for chunk in bundle._forwardchunks():
559 for chunk in bundle._forwardchunks():
560 pass
560 pass
561
561
562 def iterparts(bundle):
562 def iterparts(bundle):
563 for part in bundle.iterparts():
563 for part in bundle.iterparts():
564 pass
564 pass
565
565
566 def iterpartsseekable(bundle):
566 def iterpartsseekable(bundle):
567 for part in bundle.iterparts(seekable=True):
567 for part in bundle.iterparts(seekable=True):
568 pass
568 pass
569
569
570 def seek(bundle):
570 def seek(bundle):
571 for part in bundle.iterparts(seekable=True):
571 for part in bundle.iterparts(seekable=True):
572 part.seek(0, os.SEEK_END)
572 part.seek(0, os.SEEK_END)
573
573
574 def makepartreadnbytes(size):
574 def makepartreadnbytes(size):
575 def run():
575 def run():
576 with open(bundlepath, 'rb') as fh:
576 with open(bundlepath, 'rb') as fh:
577 bundle = exchange.readbundle(ui, fh, bundlepath)
577 bundle = exchange.readbundle(ui, fh, bundlepath)
578 for part in bundle.iterparts():
578 for part in bundle.iterparts():
579 while part.read(size):
579 while part.read(size):
580 pass
580 pass
581
581
582 return run
582 return run
583
583
584 benches = [
584 benches = [
585 (makestdioread(8192), 'read(8k)'),
585 (makestdioread(8192), 'read(8k)'),
586 (makestdioread(16384), 'read(16k)'),
586 (makestdioread(16384), 'read(16k)'),
587 (makestdioread(32768), 'read(32k)'),
587 (makestdioread(32768), 'read(32k)'),
588 (makestdioread(131072), 'read(128k)'),
588 (makestdioread(131072), 'read(128k)'),
589 ]
589 ]
590
590
591 with open(bundlepath, 'rb') as fh:
591 with open(bundlepath, 'rb') as fh:
592 bundle = exchange.readbundle(ui, fh, bundlepath)
592 bundle = exchange.readbundle(ui, fh, bundlepath)
593
593
594 if isinstance(bundle, changegroup.cg1unpacker):
594 if isinstance(bundle, changegroup.cg1unpacker):
595 benches.extend([
595 benches.extend([
596 (makebench(deltaiter), 'cg1 deltaiter()'),
596 (makebench(deltaiter), 'cg1 deltaiter()'),
597 (makebench(iterchunks), 'cg1 getchunks()'),
597 (makebench(iterchunks), 'cg1 getchunks()'),
598 (makereadnbytes(8192), 'cg1 read(8k)'),
598 (makereadnbytes(8192), 'cg1 read(8k)'),
599 (makereadnbytes(16384), 'cg1 read(16k)'),
599 (makereadnbytes(16384), 'cg1 read(16k)'),
600 (makereadnbytes(32768), 'cg1 read(32k)'),
600 (makereadnbytes(32768), 'cg1 read(32k)'),
601 (makereadnbytes(131072), 'cg1 read(128k)'),
601 (makereadnbytes(131072), 'cg1 read(128k)'),
602 ])
602 ])
603 elif isinstance(bundle, bundle2.unbundle20):
603 elif isinstance(bundle, bundle2.unbundle20):
604 benches.extend([
604 benches.extend([
605 (makebench(forwardchunks), 'bundle2 forwardchunks()'),
605 (makebench(forwardchunks), 'bundle2 forwardchunks()'),
606 (makebench(iterparts), 'bundle2 iterparts()'),
606 (makebench(iterparts), 'bundle2 iterparts()'),
607 (makebench(iterpartsseekable), 'bundle2 iterparts() seekable'),
607 (makebench(iterpartsseekable), 'bundle2 iterparts() seekable'),
608 (makebench(seek), 'bundle2 part seek()'),
608 (makebench(seek), 'bundle2 part seek()'),
609 (makepartreadnbytes(8192), 'bundle2 part read(8k)'),
609 (makepartreadnbytes(8192), 'bundle2 part read(8k)'),
610 (makepartreadnbytes(16384), 'bundle2 part read(16k)'),
610 (makepartreadnbytes(16384), 'bundle2 part read(16k)'),
611 (makepartreadnbytes(32768), 'bundle2 part read(32k)'),
611 (makepartreadnbytes(32768), 'bundle2 part read(32k)'),
612 (makepartreadnbytes(131072), 'bundle2 part read(128k)'),
612 (makepartreadnbytes(131072), 'bundle2 part read(128k)'),
613 ])
613 ])
614 elif isinstance(bundle, streamclone.streamcloneapplier):
614 elif isinstance(bundle, streamclone.streamcloneapplier):
615 raise error.Abort('stream clone bundles not supported')
615 raise error.Abort('stream clone bundles not supported')
616 else:
616 else:
617 raise error.Abort('unhandled bundle type: %s' % type(bundle))
617 raise error.Abort('unhandled bundle type: %s' % type(bundle))
618
618
619 for fn, title in benches:
619 for fn, title in benches:
620 timer, fm = gettimer(ui, opts)
620 timer, fm = gettimer(ui, opts)
621 timer(fn, title=title)
621 timer(fn, title=title)
622 fm.end()
622 fm.end()
623
623
624 @command('perfchangegroupchangelog', formatteropts +
624 @command('perfchangegroupchangelog', formatteropts +
625 [('', 'version', '02', 'changegroup version'),
625 [('', 'version', '02', 'changegroup version'),
626 ('r', 'rev', '', 'revisions to add to changegroup')])
626 ('r', 'rev', '', 'revisions to add to changegroup')])
627 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
627 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
628 """Benchmark producing a changelog group for a changegroup.
628 """Benchmark producing a changelog group for a changegroup.
629
629
630 This measures the time spent processing the changelog during a
630 This measures the time spent processing the changelog during a
631 bundle operation. This occurs during `hg bundle` and on a server
631 bundle operation. This occurs during `hg bundle` and on a server
632 processing a `getbundle` wire protocol request (handles clones
632 processing a `getbundle` wire protocol request (handles clones
633 and pull requests).
633 and pull requests).
634
634
635 By default, all revisions are added to the changegroup.
635 By default, all revisions are added to the changegroup.
636 """
636 """
637 cl = repo.changelog
637 cl = repo.changelog
638 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
638 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
639 bundler = changegroup.getbundler(version, repo)
639 bundler = changegroup.getbundler(version, repo)
640
640
641 def lookup(node):
641 def lookup(node):
642 # The real bundler reads the revision in order to access the
642 # The real bundler reads the revision in order to access the
643 # manifest node and files list. Do that here.
643 # manifest node and files list. Do that here.
644 cl.read(node)
644 cl.read(node)
645 return node
645 return node
646
646
647 def d():
647 def d():
648 for chunk in bundler.group(revs, cl, lookup):
648 for chunk in bundler.group(revs, cl, lookup):
649 pass
649 pass
650
650
651 timer, fm = gettimer(ui, opts)
651 timer, fm = gettimer(ui, opts)
652 timer(d)
652 timer(d)
653 fm.end()
653 fm.end()
654
654
655 @command('perfdirs', formatteropts)
655 @command('perfdirs', formatteropts)
656 def perfdirs(ui, repo, **opts):
656 def perfdirs(ui, repo, **opts):
657 timer, fm = gettimer(ui, opts)
657 timer, fm = gettimer(ui, opts)
658 dirstate = repo.dirstate
658 dirstate = repo.dirstate
659 'a' in dirstate
659 'a' in dirstate
660 def d():
660 def d():
661 dirstate.hasdir('a')
661 dirstate.hasdir('a')
662 del dirstate._map._dirs
662 del dirstate._map._dirs
663 timer(d)
663 timer(d)
664 fm.end()
664 fm.end()
665
665
666 @command('perfdirstate', formatteropts)
666 @command('perfdirstate', formatteropts)
667 def perfdirstate(ui, repo, **opts):
667 def perfdirstate(ui, repo, **opts):
668 timer, fm = gettimer(ui, opts)
668 timer, fm = gettimer(ui, opts)
669 "a" in repo.dirstate
669 "a" in repo.dirstate
670 def d():
670 def d():
671 repo.dirstate.invalidate()
671 repo.dirstate.invalidate()
672 "a" in repo.dirstate
672 "a" in repo.dirstate
673 timer(d)
673 timer(d)
674 fm.end()
674 fm.end()
675
675
676 @command('perfdirstatedirs', formatteropts)
676 @command('perfdirstatedirs', formatteropts)
677 def perfdirstatedirs(ui, repo, **opts):
677 def perfdirstatedirs(ui, repo, **opts):
678 timer, fm = gettimer(ui, opts)
678 timer, fm = gettimer(ui, opts)
679 "a" in repo.dirstate
679 "a" in repo.dirstate
680 def d():
680 def d():
681 repo.dirstate.hasdir("a")
681 repo.dirstate.hasdir("a")
682 del repo.dirstate._map._dirs
682 del repo.dirstate._map._dirs
683 timer(d)
683 timer(d)
684 fm.end()
684 fm.end()
685
685
686 @command('perfdirstatefoldmap', formatteropts)
686 @command('perfdirstatefoldmap', formatteropts)
687 def perfdirstatefoldmap(ui, repo, **opts):
687 def perfdirstatefoldmap(ui, repo, **opts):
688 timer, fm = gettimer(ui, opts)
688 timer, fm = gettimer(ui, opts)
689 dirstate = repo.dirstate
689 dirstate = repo.dirstate
690 'a' in dirstate
690 'a' in dirstate
691 def d():
691 def d():
692 dirstate._map.filefoldmap.get('a')
692 dirstate._map.filefoldmap.get('a')
693 del dirstate._map.filefoldmap
693 del dirstate._map.filefoldmap
694 timer(d)
694 timer(d)
695 fm.end()
695 fm.end()
696
696
697 @command('perfdirfoldmap', formatteropts)
697 @command('perfdirfoldmap', formatteropts)
698 def perfdirfoldmap(ui, repo, **opts):
698 def perfdirfoldmap(ui, repo, **opts):
699 timer, fm = gettimer(ui, opts)
699 timer, fm = gettimer(ui, opts)
700 dirstate = repo.dirstate
700 dirstate = repo.dirstate
701 'a' in dirstate
701 'a' in dirstate
702 def d():
702 def d():
703 dirstate._map.dirfoldmap.get('a')
703 dirstate._map.dirfoldmap.get('a')
704 del dirstate._map.dirfoldmap
704 del dirstate._map.dirfoldmap
705 del dirstate._map._dirs
705 del dirstate._map._dirs
706 timer(d)
706 timer(d)
707 fm.end()
707 fm.end()
708
708
709 @command('perfdirstatewrite', formatteropts)
709 @command('perfdirstatewrite', formatteropts)
710 def perfdirstatewrite(ui, repo, **opts):
710 def perfdirstatewrite(ui, repo, **opts):
711 timer, fm = gettimer(ui, opts)
711 timer, fm = gettimer(ui, opts)
712 ds = repo.dirstate
712 ds = repo.dirstate
713 "a" in ds
713 "a" in ds
714 def d():
714 def d():
715 ds._dirty = True
715 ds._dirty = True
716 ds.write(repo.currenttransaction())
716 ds.write(repo.currenttransaction())
717 timer(d)
717 timer(d)
718 fm.end()
718 fm.end()
719
719
720 @command('perfmergecalculate',
720 @command('perfmergecalculate',
721 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
721 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
722 def perfmergecalculate(ui, repo, rev, **opts):
722 def perfmergecalculate(ui, repo, rev, **opts):
723 timer, fm = gettimer(ui, opts)
723 timer, fm = gettimer(ui, opts)
724 wctx = repo[None]
724 wctx = repo[None]
725 rctx = scmutil.revsingle(repo, rev, rev)
725 rctx = scmutil.revsingle(repo, rev, rev)
726 ancestor = wctx.ancestor(rctx)
726 ancestor = wctx.ancestor(rctx)
727 # we don't want working dir files to be stat'd in the benchmark, so prime
727 # we don't want working dir files to be stat'd in the benchmark, so prime
728 # that cache
728 # that cache
729 wctx.dirty()
729 wctx.dirty()
730 def d():
730 def d():
731 # acceptremote is True because we don't want prompts in the middle of
731 # acceptremote is True because we don't want prompts in the middle of
732 # our benchmark
732 # our benchmark
733 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
733 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
734 acceptremote=True, followcopies=True)
734 acceptremote=True, followcopies=True)
735 timer(d)
735 timer(d)
736 fm.end()
736 fm.end()
737
737
738 @command('perfpathcopies', [], "REV REV")
738 @command('perfpathcopies', [], "REV REV")
739 def perfpathcopies(ui, repo, rev1, rev2, **opts):
739 def perfpathcopies(ui, repo, rev1, rev2, **opts):
740 timer, fm = gettimer(ui, opts)
740 timer, fm = gettimer(ui, opts)
741 ctx1 = scmutil.revsingle(repo, rev1, rev1)
741 ctx1 = scmutil.revsingle(repo, rev1, rev1)
742 ctx2 = scmutil.revsingle(repo, rev2, rev2)
742 ctx2 = scmutil.revsingle(repo, rev2, rev2)
743 def d():
743 def d():
744 copies.pathcopies(ctx1, ctx2)
744 copies.pathcopies(ctx1, ctx2)
745 timer(d)
745 timer(d)
746 fm.end()
746 fm.end()
747
747
748 @command('perfphases',
748 @command('perfphases',
749 [('', 'full', False, 'include file reading time too'),
749 [('', 'full', False, 'include file reading time too'),
750 ], "")
750 ], "")
751 def perfphases(ui, repo, **opts):
751 def perfphases(ui, repo, **opts):
752 """benchmark phasesets computation"""
752 """benchmark phasesets computation"""
753 timer, fm = gettimer(ui, opts)
753 timer, fm = gettimer(ui, opts)
754 _phases = repo._phasecache
754 _phases = repo._phasecache
755 full = opts.get('full')
755 full = opts.get('full')
756 def d():
756 def d():
757 phases = _phases
757 phases = _phases
758 if full:
758 if full:
759 clearfilecache(repo, '_phasecache')
759 clearfilecache(repo, '_phasecache')
760 phases = repo._phasecache
760 phases = repo._phasecache
761 phases.invalidate()
761 phases.invalidate()
762 phases.loadphaserevs(repo)
762 phases.loadphaserevs(repo)
763 timer(d)
763 timer(d)
764 fm.end()
764 fm.end()
765
765
766 @command('perfmanifest', [], 'REV')
766 @command('perfmanifest', [], 'REV')
767 def perfmanifest(ui, repo, rev, **opts):
767 def perfmanifest(ui, repo, rev, **opts):
768 timer, fm = gettimer(ui, opts)
768 timer, fm = gettimer(ui, opts)
769 ctx = scmutil.revsingle(repo, rev, rev)
769 ctx = scmutil.revsingle(repo, rev, rev)
770 t = ctx.manifestnode()
770 t = ctx.manifestnode()
771 def d():
771 def d():
772 repo.manifestlog.clearcaches()
772 repo.manifestlog.clearcaches()
773 repo.manifestlog[t].read()
773 repo.manifestlog[t].read()
774 timer(d)
774 timer(d)
775 fm.end()
775 fm.end()
776
776
777 @command('perfchangeset', formatteropts)
777 @command('perfchangeset', formatteropts)
778 def perfchangeset(ui, repo, rev, **opts):
778 def perfchangeset(ui, repo, rev, **opts):
779 timer, fm = gettimer(ui, opts)
779 timer, fm = gettimer(ui, opts)
780 n = scmutil.revsingle(repo, rev).node()
780 n = scmutil.revsingle(repo, rev).node()
781 def d():
781 def d():
782 repo.changelog.read(n)
782 repo.changelog.read(n)
783 #repo.changelog._cache = None
783 #repo.changelog._cache = None
784 timer(d)
784 timer(d)
785 fm.end()
785 fm.end()
786
786
787 @command('perfindex', formatteropts)
787 @command('perfindex', formatteropts)
788 def perfindex(ui, repo, **opts):
788 def perfindex(ui, repo, **opts):
789 import mercurial.revlog
789 import mercurial.revlog
790 timer, fm = gettimer(ui, opts)
790 timer, fm = gettimer(ui, opts)
791 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
791 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
792 n = repo["tip"].node()
792 n = repo["tip"].node()
793 svfs = getsvfs(repo)
793 svfs = getsvfs(repo)
794 def d():
794 def d():
795 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
795 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
796 cl.rev(n)
796 cl.rev(n)
797 timer(d)
797 timer(d)
798 fm.end()
798 fm.end()
799
799
800 @command('perfstartup', formatteropts)
800 @command('perfstartup', formatteropts)
801 def perfstartup(ui, repo, **opts):
801 def perfstartup(ui, repo, **opts):
802 timer, fm = gettimer(ui, opts)
802 timer, fm = gettimer(ui, opts)
803 cmd = sys.argv[0]
803 cmd = sys.argv[0]
804 def d():
804 def d():
805 if os.name != 'nt':
805 if os.name != 'nt':
806 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
806 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
807 else:
807 else:
808 os.environ['HGRCPATH'] = ' '
808 os.environ['HGRCPATH'] = ' '
809 os.system("%s version -q > NUL" % cmd)
809 os.system("%s version -q > NUL" % cmd)
810 timer(d)
810 timer(d)
811 fm.end()
811 fm.end()
812
812
813 @command('perfparents', formatteropts)
813 @command('perfparents', formatteropts)
814 def perfparents(ui, repo, **opts):
814 def perfparents(ui, repo, **opts):
815 timer, fm = gettimer(ui, opts)
815 timer, fm = gettimer(ui, opts)
816 # control the number of commits perfparents iterates over
816 # control the number of commits perfparents iterates over
817 # experimental config: perf.parentscount
817 # experimental config: perf.parentscount
818 count = getint(ui, "perf", "parentscount", 1000)
818 count = getint(ui, "perf", "parentscount", 1000)
819 if len(repo.changelog) < count:
819 if len(repo.changelog) < count:
820 raise error.Abort("repo needs %d commits for this test" % count)
820 raise error.Abort("repo needs %d commits for this test" % count)
821 repo = repo.unfiltered()
821 repo = repo.unfiltered()
822 nl = [repo.changelog.node(i) for i in xrange(count)]
822 nl = [repo.changelog.node(i) for i in xrange(count)]
823 def d():
823 def d():
824 for n in nl:
824 for n in nl:
825 repo.changelog.parents(n)
825 repo.changelog.parents(n)
826 timer(d)
826 timer(d)
827 fm.end()
827 fm.end()
828
828
829 @command('perfctxfiles', formatteropts)
829 @command('perfctxfiles', formatteropts)
830 def perfctxfiles(ui, repo, x, **opts):
830 def perfctxfiles(ui, repo, x, **opts):
831 x = int(x)
831 x = int(x)
832 timer, fm = gettimer(ui, opts)
832 timer, fm = gettimer(ui, opts)
833 def d():
833 def d():
834 len(repo[x].files())
834 len(repo[x].files())
835 timer(d)
835 timer(d)
836 fm.end()
836 fm.end()
837
837
838 @command('perfrawfiles', formatteropts)
838 @command('perfrawfiles', formatteropts)
839 def perfrawfiles(ui, repo, x, **opts):
839 def perfrawfiles(ui, repo, x, **opts):
840 x = int(x)
840 x = int(x)
841 timer, fm = gettimer(ui, opts)
841 timer, fm = gettimer(ui, opts)
842 cl = repo.changelog
842 cl = repo.changelog
843 def d():
843 def d():
844 len(cl.read(x)[3])
844 len(cl.read(x)[3])
845 timer(d)
845 timer(d)
846 fm.end()
846 fm.end()
847
847
848 @command('perflookup', formatteropts)
848 @command('perflookup', formatteropts)
849 def perflookup(ui, repo, rev, **opts):
849 def perflookup(ui, repo, rev, **opts):
850 timer, fm = gettimer(ui, opts)
850 timer, fm = gettimer(ui, opts)
851 timer(lambda: len(repo.lookup(rev)))
851 timer(lambda: len(repo.lookup(rev)))
852 fm.end()
852 fm.end()
853
853
854 @command('perfrevrange', formatteropts)
854 @command('perfrevrange', formatteropts)
855 def perfrevrange(ui, repo, *specs, **opts):
855 def perfrevrange(ui, repo, *specs, **opts):
856 timer, fm = gettimer(ui, opts)
856 timer, fm = gettimer(ui, opts)
857 revrange = scmutil.revrange
857 revrange = scmutil.revrange
858 timer(lambda: len(revrange(repo, specs)))
858 timer(lambda: len(revrange(repo, specs)))
859 fm.end()
859 fm.end()
860
860
861 @command('perfnodelookup', formatteropts)
861 @command('perfnodelookup', formatteropts)
862 def perfnodelookup(ui, repo, rev, **opts):
862 def perfnodelookup(ui, repo, rev, **opts):
863 timer, fm = gettimer(ui, opts)
863 timer, fm = gettimer(ui, opts)
864 import mercurial.revlog
864 import mercurial.revlog
865 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
865 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
866 n = scmutil.revsingle(repo, rev).node()
866 n = scmutil.revsingle(repo, rev).node()
867 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
867 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
868 def d():
868 def d():
869 cl.rev(n)
869 cl.rev(n)
870 clearcaches(cl)
870 clearcaches(cl)
871 timer(d)
871 timer(d)
872 fm.end()
872 fm.end()
873
873
874 @command('perflog',
874 @command('perflog',
875 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
875 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
876 def perflog(ui, repo, rev=None, **opts):
876 def perflog(ui, repo, rev=None, **opts):
877 if rev is None:
877 if rev is None:
878 rev=[]
878 rev=[]
879 timer, fm = gettimer(ui, opts)
879 timer, fm = gettimer(ui, opts)
880 ui.pushbuffer()
880 ui.pushbuffer()
881 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
881 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
882 copies=opts.get('rename')))
882 copies=opts.get('rename')))
883 ui.popbuffer()
883 ui.popbuffer()
884 fm.end()
884 fm.end()
885
885
886 @command('perfmoonwalk', formatteropts)
886 @command('perfmoonwalk', formatteropts)
887 def perfmoonwalk(ui, repo, **opts):
887 def perfmoonwalk(ui, repo, **opts):
888 """benchmark walking the changelog backwards
888 """benchmark walking the changelog backwards
889
889
890 This also loads the changelog data for each revision in the changelog.
890 This also loads the changelog data for each revision in the changelog.
891 """
891 """
892 timer, fm = gettimer(ui, opts)
892 timer, fm = gettimer(ui, opts)
893 def moonwalk():
893 def moonwalk():
894 for i in xrange(len(repo), -1, -1):
894 for i in xrange(len(repo), -1, -1):
895 ctx = repo[i]
895 ctx = repo[i]
896 ctx.branch() # read changelog data (in addition to the index)
896 ctx.branch() # read changelog data (in addition to the index)
897 timer(moonwalk)
897 timer(moonwalk)
898 fm.end()
898 fm.end()
899
899
900 @command('perftemplating', formatteropts)
900 @command('perftemplating',
901 def perftemplating(ui, repo, *revs, **opts):
901 [('r', 'rev', [], 'revisions to run the template on'),
902 ] + formatteropts)
903 def perftemplating(ui, repo, **opts):
902 nullui = ui.copy()
904 nullui = ui.copy()
903 nullui.fout = open(os.devnull, 'wb')
905 nullui.fout = open(os.devnull, 'wb')
904 nullui.disablepager()
906 nullui.disablepager()
905
907 revs = opts.get('rev')
906 def format():
908 def format():
907 commands.log(nullui, repo, rev=revs, date='', user='',
909 commands.log(nullui, repo, rev=revs, date='', user='',
908 template='{date|shortdate} [{rev}:{node|short}]'
910 template='{date|shortdate} [{rev}:{node|short}]'
909 ' {author|person}: {desc|firstline}\n')
911 ' {author|person}: {desc|firstline}\n')
910
912
911 timer, fm = gettimer(ui, opts)
913 timer, fm = gettimer(ui, opts)
912 timer(format)
914 timer(format)
913 fm.end()
915 fm.end()
914
916
915 @command('perfcca', formatteropts)
917 @command('perfcca', formatteropts)
916 def perfcca(ui, repo, **opts):
918 def perfcca(ui, repo, **opts):
917 timer, fm = gettimer(ui, opts)
919 timer, fm = gettimer(ui, opts)
918 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
920 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
919 fm.end()
921 fm.end()
920
922
921 @command('perffncacheload', formatteropts)
923 @command('perffncacheload', formatteropts)
922 def perffncacheload(ui, repo, **opts):
924 def perffncacheload(ui, repo, **opts):
923 timer, fm = gettimer(ui, opts)
925 timer, fm = gettimer(ui, opts)
924 s = repo.store
926 s = repo.store
925 def d():
927 def d():
926 s.fncache._load()
928 s.fncache._load()
927 timer(d)
929 timer(d)
928 fm.end()
930 fm.end()
929
931
930 @command('perffncachewrite', formatteropts)
932 @command('perffncachewrite', formatteropts)
931 def perffncachewrite(ui, repo, **opts):
933 def perffncachewrite(ui, repo, **opts):
932 timer, fm = gettimer(ui, opts)
934 timer, fm = gettimer(ui, opts)
933 s = repo.store
935 s = repo.store
934 s.fncache._load()
936 s.fncache._load()
935 lock = repo.lock()
937 lock = repo.lock()
936 tr = repo.transaction('perffncachewrite')
938 tr = repo.transaction('perffncachewrite')
937 def d():
939 def d():
938 s.fncache._dirty = True
940 s.fncache._dirty = True
939 s.fncache.write(tr)
941 s.fncache.write(tr)
940 timer(d)
942 timer(d)
941 tr.close()
943 tr.close()
942 lock.release()
944 lock.release()
943 fm.end()
945 fm.end()
944
946
945 @command('perffncacheencode', formatteropts)
947 @command('perffncacheencode', formatteropts)
946 def perffncacheencode(ui, repo, **opts):
948 def perffncacheencode(ui, repo, **opts):
947 timer, fm = gettimer(ui, opts)
949 timer, fm = gettimer(ui, opts)
948 s = repo.store
950 s = repo.store
949 s.fncache._load()
951 s.fncache._load()
950 def d():
952 def d():
951 for p in s.fncache.entries:
953 for p in s.fncache.entries:
952 s.encode(p)
954 s.encode(p)
953 timer(d)
955 timer(d)
954 fm.end()
956 fm.end()
955
957
956 def _bdiffworker(q, blocks, xdiff, ready, done):
958 def _bdiffworker(q, blocks, xdiff, ready, done):
957 while not done.is_set():
959 while not done.is_set():
958 pair = q.get()
960 pair = q.get()
959 while pair is not None:
961 while pair is not None:
960 if xdiff:
962 if xdiff:
961 mdiff.bdiff.xdiffblocks(*pair)
963 mdiff.bdiff.xdiffblocks(*pair)
962 elif blocks:
964 elif blocks:
963 mdiff.bdiff.blocks(*pair)
965 mdiff.bdiff.blocks(*pair)
964 else:
966 else:
965 mdiff.textdiff(*pair)
967 mdiff.textdiff(*pair)
966 q.task_done()
968 q.task_done()
967 pair = q.get()
969 pair = q.get()
968 q.task_done() # for the None one
970 q.task_done() # for the None one
969 with ready:
971 with ready:
970 ready.wait()
972 ready.wait()
971
973
972 @command('perfbdiff', revlogopts + formatteropts + [
974 @command('perfbdiff', revlogopts + formatteropts + [
973 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
975 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
974 ('', 'alldata', False, 'test bdiffs for all associated revisions'),
976 ('', 'alldata', False, 'test bdiffs for all associated revisions'),
975 ('', 'threads', 0, 'number of thread to use (disable with 0)'),
977 ('', 'threads', 0, 'number of thread to use (disable with 0)'),
976 ('', 'blocks', False, 'test computing diffs into blocks'),
978 ('', 'blocks', False, 'test computing diffs into blocks'),
977 ('', 'xdiff', False, 'use xdiff algorithm'),
979 ('', 'xdiff', False, 'use xdiff algorithm'),
978 ],
980 ],
979
981
980 '-c|-m|FILE REV')
982 '-c|-m|FILE REV')
981 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
983 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
982 """benchmark a bdiff between revisions
984 """benchmark a bdiff between revisions
983
985
984 By default, benchmark a bdiff between its delta parent and itself.
986 By default, benchmark a bdiff between its delta parent and itself.
985
987
986 With ``--count``, benchmark bdiffs between delta parents and self for N
988 With ``--count``, benchmark bdiffs between delta parents and self for N
987 revisions starting at the specified revision.
989 revisions starting at the specified revision.
988
990
989 With ``--alldata``, assume the requested revision is a changeset and
991 With ``--alldata``, assume the requested revision is a changeset and
990 measure bdiffs for all changes related to that changeset (manifest
992 measure bdiffs for all changes related to that changeset (manifest
991 and filelogs).
993 and filelogs).
992 """
994 """
993 opts = pycompat.byteskwargs(opts)
995 opts = pycompat.byteskwargs(opts)
994
996
995 if opts['xdiff'] and not opts['blocks']:
997 if opts['xdiff'] and not opts['blocks']:
996 raise error.CommandError('perfbdiff', '--xdiff requires --blocks')
998 raise error.CommandError('perfbdiff', '--xdiff requires --blocks')
997
999
998 if opts['alldata']:
1000 if opts['alldata']:
999 opts['changelog'] = True
1001 opts['changelog'] = True
1000
1002
1001 if opts.get('changelog') or opts.get('manifest'):
1003 if opts.get('changelog') or opts.get('manifest'):
1002 file_, rev = None, file_
1004 file_, rev = None, file_
1003 elif rev is None:
1005 elif rev is None:
1004 raise error.CommandError('perfbdiff', 'invalid arguments')
1006 raise error.CommandError('perfbdiff', 'invalid arguments')
1005
1007
1006 blocks = opts['blocks']
1008 blocks = opts['blocks']
1007 xdiff = opts['xdiff']
1009 xdiff = opts['xdiff']
1008 textpairs = []
1010 textpairs = []
1009
1011
1010 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
1012 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
1011
1013
1012 startrev = r.rev(r.lookup(rev))
1014 startrev = r.rev(r.lookup(rev))
1013 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1015 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1014 if opts['alldata']:
1016 if opts['alldata']:
1015 # Load revisions associated with changeset.
1017 # Load revisions associated with changeset.
1016 ctx = repo[rev]
1018 ctx = repo[rev]
1017 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
1019 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
1018 for pctx in ctx.parents():
1020 for pctx in ctx.parents():
1019 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
1021 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
1020 textpairs.append((pman, mtext))
1022 textpairs.append((pman, mtext))
1021
1023
1022 # Load filelog revisions by iterating manifest delta.
1024 # Load filelog revisions by iterating manifest delta.
1023 man = ctx.manifest()
1025 man = ctx.manifest()
1024 pman = ctx.p1().manifest()
1026 pman = ctx.p1().manifest()
1025 for filename, change in pman.diff(man).items():
1027 for filename, change in pman.diff(man).items():
1026 fctx = repo.file(filename)
1028 fctx = repo.file(filename)
1027 f1 = fctx.revision(change[0][0] or -1)
1029 f1 = fctx.revision(change[0][0] or -1)
1028 f2 = fctx.revision(change[1][0] or -1)
1030 f2 = fctx.revision(change[1][0] or -1)
1029 textpairs.append((f1, f2))
1031 textpairs.append((f1, f2))
1030 else:
1032 else:
1031 dp = r.deltaparent(rev)
1033 dp = r.deltaparent(rev)
1032 textpairs.append((r.revision(dp), r.revision(rev)))
1034 textpairs.append((r.revision(dp), r.revision(rev)))
1033
1035
1034 withthreads = threads > 0
1036 withthreads = threads > 0
1035 if not withthreads:
1037 if not withthreads:
1036 def d():
1038 def d():
1037 for pair in textpairs:
1039 for pair in textpairs:
1038 if xdiff:
1040 if xdiff:
1039 mdiff.bdiff.xdiffblocks(*pair)
1041 mdiff.bdiff.xdiffblocks(*pair)
1040 elif blocks:
1042 elif blocks:
1041 mdiff.bdiff.blocks(*pair)
1043 mdiff.bdiff.blocks(*pair)
1042 else:
1044 else:
1043 mdiff.textdiff(*pair)
1045 mdiff.textdiff(*pair)
1044 else:
1046 else:
1045 q = queue()
1047 q = queue()
1046 for i in xrange(threads):
1048 for i in xrange(threads):
1047 q.put(None)
1049 q.put(None)
1048 ready = threading.Condition()
1050 ready = threading.Condition()
1049 done = threading.Event()
1051 done = threading.Event()
1050 for i in xrange(threads):
1052 for i in xrange(threads):
1051 threading.Thread(target=_bdiffworker,
1053 threading.Thread(target=_bdiffworker,
1052 args=(q, blocks, xdiff, ready, done)).start()
1054 args=(q, blocks, xdiff, ready, done)).start()
1053 q.join()
1055 q.join()
1054 def d():
1056 def d():
1055 for pair in textpairs:
1057 for pair in textpairs:
1056 q.put(pair)
1058 q.put(pair)
1057 for i in xrange(threads):
1059 for i in xrange(threads):
1058 q.put(None)
1060 q.put(None)
1059 with ready:
1061 with ready:
1060 ready.notify_all()
1062 ready.notify_all()
1061 q.join()
1063 q.join()
1062 timer, fm = gettimer(ui, opts)
1064 timer, fm = gettimer(ui, opts)
1063 timer(d)
1065 timer(d)
1064 fm.end()
1066 fm.end()
1065
1067
1066 if withthreads:
1068 if withthreads:
1067 done.set()
1069 done.set()
1068 for i in xrange(threads):
1070 for i in xrange(threads):
1069 q.put(None)
1071 q.put(None)
1070 with ready:
1072 with ready:
1071 ready.notify_all()
1073 ready.notify_all()
1072
1074
1073 @command('perfunidiff', revlogopts + formatteropts + [
1075 @command('perfunidiff', revlogopts + formatteropts + [
1074 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
1076 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
1075 ('', 'alldata', False, 'test unidiffs for all associated revisions'),
1077 ('', 'alldata', False, 'test unidiffs for all associated revisions'),
1076 ], '-c|-m|FILE REV')
1078 ], '-c|-m|FILE REV')
1077 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
1079 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
1078 """benchmark a unified diff between revisions
1080 """benchmark a unified diff between revisions
1079
1081
1080 This doesn't include any copy tracing - it's just a unified diff
1082 This doesn't include any copy tracing - it's just a unified diff
1081 of the texts.
1083 of the texts.
1082
1084
1083 By default, benchmark a diff between its delta parent and itself.
1085 By default, benchmark a diff between its delta parent and itself.
1084
1086
1085 With ``--count``, benchmark diffs between delta parents and self for N
1087 With ``--count``, benchmark diffs between delta parents and self for N
1086 revisions starting at the specified revision.
1088 revisions starting at the specified revision.
1087
1089
1088 With ``--alldata``, assume the requested revision is a changeset and
1090 With ``--alldata``, assume the requested revision is a changeset and
1089 measure diffs for all changes related to that changeset (manifest
1091 measure diffs for all changes related to that changeset (manifest
1090 and filelogs).
1092 and filelogs).
1091 """
1093 """
1092 if opts['alldata']:
1094 if opts['alldata']:
1093 opts['changelog'] = True
1095 opts['changelog'] = True
1094
1096
1095 if opts.get('changelog') or opts.get('manifest'):
1097 if opts.get('changelog') or opts.get('manifest'):
1096 file_, rev = None, file_
1098 file_, rev = None, file_
1097 elif rev is None:
1099 elif rev is None:
1098 raise error.CommandError('perfunidiff', 'invalid arguments')
1100 raise error.CommandError('perfunidiff', 'invalid arguments')
1099
1101
1100 textpairs = []
1102 textpairs = []
1101
1103
1102 r = cmdutil.openrevlog(repo, 'perfunidiff', file_, opts)
1104 r = cmdutil.openrevlog(repo, 'perfunidiff', file_, opts)
1103
1105
1104 startrev = r.rev(r.lookup(rev))
1106 startrev = r.rev(r.lookup(rev))
1105 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1107 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1106 if opts['alldata']:
1108 if opts['alldata']:
1107 # Load revisions associated with changeset.
1109 # Load revisions associated with changeset.
1108 ctx = repo[rev]
1110 ctx = repo[rev]
1109 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
1111 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
1110 for pctx in ctx.parents():
1112 for pctx in ctx.parents():
1111 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
1113 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
1112 textpairs.append((pman, mtext))
1114 textpairs.append((pman, mtext))
1113
1115
1114 # Load filelog revisions by iterating manifest delta.
1116 # Load filelog revisions by iterating manifest delta.
1115 man = ctx.manifest()
1117 man = ctx.manifest()
1116 pman = ctx.p1().manifest()
1118 pman = ctx.p1().manifest()
1117 for filename, change in pman.diff(man).items():
1119 for filename, change in pman.diff(man).items():
1118 fctx = repo.file(filename)
1120 fctx = repo.file(filename)
1119 f1 = fctx.revision(change[0][0] or -1)
1121 f1 = fctx.revision(change[0][0] or -1)
1120 f2 = fctx.revision(change[1][0] or -1)
1122 f2 = fctx.revision(change[1][0] or -1)
1121 textpairs.append((f1, f2))
1123 textpairs.append((f1, f2))
1122 else:
1124 else:
1123 dp = r.deltaparent(rev)
1125 dp = r.deltaparent(rev)
1124 textpairs.append((r.revision(dp), r.revision(rev)))
1126 textpairs.append((r.revision(dp), r.revision(rev)))
1125
1127
1126 def d():
1128 def d():
1127 for left, right in textpairs:
1129 for left, right in textpairs:
1128 # The date strings don't matter, so we pass empty strings.
1130 # The date strings don't matter, so we pass empty strings.
1129 headerlines, hunks = mdiff.unidiff(
1131 headerlines, hunks = mdiff.unidiff(
1130 left, '', right, '', 'left', 'right', binary=False)
1132 left, '', right, '', 'left', 'right', binary=False)
1131 # consume iterators in roughly the way patch.py does
1133 # consume iterators in roughly the way patch.py does
1132 b'\n'.join(headerlines)
1134 b'\n'.join(headerlines)
1133 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
1135 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
1134 timer, fm = gettimer(ui, opts)
1136 timer, fm = gettimer(ui, opts)
1135 timer(d)
1137 timer(d)
1136 fm.end()
1138 fm.end()
1137
1139
1138 @command('perfdiffwd', formatteropts)
1140 @command('perfdiffwd', formatteropts)
1139 def perfdiffwd(ui, repo, **opts):
1141 def perfdiffwd(ui, repo, **opts):
1140 """Profile diff of working directory changes"""
1142 """Profile diff of working directory changes"""
1141 timer, fm = gettimer(ui, opts)
1143 timer, fm = gettimer(ui, opts)
1142 options = {
1144 options = {
1143 'w': 'ignore_all_space',
1145 'w': 'ignore_all_space',
1144 'b': 'ignore_space_change',
1146 'b': 'ignore_space_change',
1145 'B': 'ignore_blank_lines',
1147 'B': 'ignore_blank_lines',
1146 }
1148 }
1147
1149
1148 for diffopt in ('', 'w', 'b', 'B', 'wB'):
1150 for diffopt in ('', 'w', 'b', 'B', 'wB'):
1149 opts = dict((options[c], '1') for c in diffopt)
1151 opts = dict((options[c], '1') for c in diffopt)
1150 def d():
1152 def d():
1151 ui.pushbuffer()
1153 ui.pushbuffer()
1152 commands.diff(ui, repo, **opts)
1154 commands.diff(ui, repo, **opts)
1153 ui.popbuffer()
1155 ui.popbuffer()
1154 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
1156 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
1155 timer(d, title)
1157 timer(d, title)
1156 fm.end()
1158 fm.end()
1157
1159
1158 @command('perfrevlogindex', revlogopts + formatteropts,
1160 @command('perfrevlogindex', revlogopts + formatteropts,
1159 '-c|-m|FILE')
1161 '-c|-m|FILE')
1160 def perfrevlogindex(ui, repo, file_=None, **opts):
1162 def perfrevlogindex(ui, repo, file_=None, **opts):
1161 """Benchmark operations against a revlog index.
1163 """Benchmark operations against a revlog index.
1162
1164
1163 This tests constructing a revlog instance, reading index data,
1165 This tests constructing a revlog instance, reading index data,
1164 parsing index data, and performing various operations related to
1166 parsing index data, and performing various operations related to
1165 index data.
1167 index data.
1166 """
1168 """
1167
1169
1168 rl = cmdutil.openrevlog(repo, 'perfrevlogindex', file_, opts)
1170 rl = cmdutil.openrevlog(repo, 'perfrevlogindex', file_, opts)
1169
1171
1170 opener = getattr(rl, 'opener') # trick linter
1172 opener = getattr(rl, 'opener') # trick linter
1171 indexfile = rl.indexfile
1173 indexfile = rl.indexfile
1172 data = opener.read(indexfile)
1174 data = opener.read(indexfile)
1173
1175
1174 header = struct.unpack('>I', data[0:4])[0]
1176 header = struct.unpack('>I', data[0:4])[0]
1175 version = header & 0xFFFF
1177 version = header & 0xFFFF
1176 if version == 1:
1178 if version == 1:
1177 revlogio = revlog.revlogio()
1179 revlogio = revlog.revlogio()
1178 inline = header & (1 << 16)
1180 inline = header & (1 << 16)
1179 else:
1181 else:
1180 raise error.Abort(('unsupported revlog version: %d') % version)
1182 raise error.Abort(('unsupported revlog version: %d') % version)
1181
1183
1182 rllen = len(rl)
1184 rllen = len(rl)
1183
1185
1184 node0 = rl.node(0)
1186 node0 = rl.node(0)
1185 node25 = rl.node(rllen // 4)
1187 node25 = rl.node(rllen // 4)
1186 node50 = rl.node(rllen // 2)
1188 node50 = rl.node(rllen // 2)
1187 node75 = rl.node(rllen // 4 * 3)
1189 node75 = rl.node(rllen // 4 * 3)
1188 node100 = rl.node(rllen - 1)
1190 node100 = rl.node(rllen - 1)
1189
1191
1190 allrevs = range(rllen)
1192 allrevs = range(rllen)
1191 allrevsrev = list(reversed(allrevs))
1193 allrevsrev = list(reversed(allrevs))
1192 allnodes = [rl.node(rev) for rev in range(rllen)]
1194 allnodes = [rl.node(rev) for rev in range(rllen)]
1193 allnodesrev = list(reversed(allnodes))
1195 allnodesrev = list(reversed(allnodes))
1194
1196
1195 def constructor():
1197 def constructor():
1196 revlog.revlog(opener, indexfile)
1198 revlog.revlog(opener, indexfile)
1197
1199
1198 def read():
1200 def read():
1199 with opener(indexfile) as fh:
1201 with opener(indexfile) as fh:
1200 fh.read()
1202 fh.read()
1201
1203
1202 def parseindex():
1204 def parseindex():
1203 revlogio.parseindex(data, inline)
1205 revlogio.parseindex(data, inline)
1204
1206
1205 def getentry(revornode):
1207 def getentry(revornode):
1206 index = revlogio.parseindex(data, inline)[0]
1208 index = revlogio.parseindex(data, inline)[0]
1207 index[revornode]
1209 index[revornode]
1208
1210
1209 def getentries(revs, count=1):
1211 def getentries(revs, count=1):
1210 index = revlogio.parseindex(data, inline)[0]
1212 index = revlogio.parseindex(data, inline)[0]
1211
1213
1212 for i in range(count):
1214 for i in range(count):
1213 for rev in revs:
1215 for rev in revs:
1214 index[rev]
1216 index[rev]
1215
1217
1216 def resolvenode(node):
1218 def resolvenode(node):
1217 nodemap = revlogio.parseindex(data, inline)[1]
1219 nodemap = revlogio.parseindex(data, inline)[1]
1218 # This only works for the C code.
1220 # This only works for the C code.
1219 if nodemap is None:
1221 if nodemap is None:
1220 return
1222 return
1221
1223
1222 try:
1224 try:
1223 nodemap[node]
1225 nodemap[node]
1224 except error.RevlogError:
1226 except error.RevlogError:
1225 pass
1227 pass
1226
1228
1227 def resolvenodes(nodes, count=1):
1229 def resolvenodes(nodes, count=1):
1228 nodemap = revlogio.parseindex(data, inline)[1]
1230 nodemap = revlogio.parseindex(data, inline)[1]
1229 if nodemap is None:
1231 if nodemap is None:
1230 return
1232 return
1231
1233
1232 for i in range(count):
1234 for i in range(count):
1233 for node in nodes:
1235 for node in nodes:
1234 try:
1236 try:
1235 nodemap[node]
1237 nodemap[node]
1236 except error.RevlogError:
1238 except error.RevlogError:
1237 pass
1239 pass
1238
1240
1239 benches = [
1241 benches = [
1240 (constructor, 'revlog constructor'),
1242 (constructor, 'revlog constructor'),
1241 (read, 'read'),
1243 (read, 'read'),
1242 (parseindex, 'create index object'),
1244 (parseindex, 'create index object'),
1243 (lambda: getentry(0), 'retrieve index entry for rev 0'),
1245 (lambda: getentry(0), 'retrieve index entry for rev 0'),
1244 (lambda: resolvenode('a' * 20), 'look up missing node'),
1246 (lambda: resolvenode('a' * 20), 'look up missing node'),
1245 (lambda: resolvenode(node0), 'look up node at rev 0'),
1247 (lambda: resolvenode(node0), 'look up node at rev 0'),
1246 (lambda: resolvenode(node25), 'look up node at 1/4 len'),
1248 (lambda: resolvenode(node25), 'look up node at 1/4 len'),
1247 (lambda: resolvenode(node50), 'look up node at 1/2 len'),
1249 (lambda: resolvenode(node50), 'look up node at 1/2 len'),
1248 (lambda: resolvenode(node75), 'look up node at 3/4 len'),
1250 (lambda: resolvenode(node75), 'look up node at 3/4 len'),
1249 (lambda: resolvenode(node100), 'look up node at tip'),
1251 (lambda: resolvenode(node100), 'look up node at tip'),
1250 # 2x variation is to measure caching impact.
1252 # 2x variation is to measure caching impact.
1251 (lambda: resolvenodes(allnodes),
1253 (lambda: resolvenodes(allnodes),
1252 'look up all nodes (forward)'),
1254 'look up all nodes (forward)'),
1253 (lambda: resolvenodes(allnodes, 2),
1255 (lambda: resolvenodes(allnodes, 2),
1254 'look up all nodes 2x (forward)'),
1256 'look up all nodes 2x (forward)'),
1255 (lambda: resolvenodes(allnodesrev),
1257 (lambda: resolvenodes(allnodesrev),
1256 'look up all nodes (reverse)'),
1258 'look up all nodes (reverse)'),
1257 (lambda: resolvenodes(allnodesrev, 2),
1259 (lambda: resolvenodes(allnodesrev, 2),
1258 'look up all nodes 2x (reverse)'),
1260 'look up all nodes 2x (reverse)'),
1259 (lambda: getentries(allrevs),
1261 (lambda: getentries(allrevs),
1260 'retrieve all index entries (forward)'),
1262 'retrieve all index entries (forward)'),
1261 (lambda: getentries(allrevs, 2),
1263 (lambda: getentries(allrevs, 2),
1262 'retrieve all index entries 2x (forward)'),
1264 'retrieve all index entries 2x (forward)'),
1263 (lambda: getentries(allrevsrev),
1265 (lambda: getentries(allrevsrev),
1264 'retrieve all index entries (reverse)'),
1266 'retrieve all index entries (reverse)'),
1265 (lambda: getentries(allrevsrev, 2),
1267 (lambda: getentries(allrevsrev, 2),
1266 'retrieve all index entries 2x (reverse)'),
1268 'retrieve all index entries 2x (reverse)'),
1267 ]
1269 ]
1268
1270
1269 for fn, title in benches:
1271 for fn, title in benches:
1270 timer, fm = gettimer(ui, opts)
1272 timer, fm = gettimer(ui, opts)
1271 timer(fn, title=title)
1273 timer(fn, title=title)
1272 fm.end()
1274 fm.end()
1273
1275
1274 @command('perfrevlogrevisions', revlogopts + formatteropts +
1276 @command('perfrevlogrevisions', revlogopts + formatteropts +
1275 [('d', 'dist', 100, 'distance between the revisions'),
1277 [('d', 'dist', 100, 'distance between the revisions'),
1276 ('s', 'startrev', 0, 'revision to start reading at'),
1278 ('s', 'startrev', 0, 'revision to start reading at'),
1277 ('', 'reverse', False, 'read in reverse')],
1279 ('', 'reverse', False, 'read in reverse')],
1278 '-c|-m|FILE')
1280 '-c|-m|FILE')
1279 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
1281 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
1280 **opts):
1282 **opts):
1281 """Benchmark reading a series of revisions from a revlog.
1283 """Benchmark reading a series of revisions from a revlog.
1282
1284
1283 By default, we read every ``-d/--dist`` revision from 0 to tip of
1285 By default, we read every ``-d/--dist`` revision from 0 to tip of
1284 the specified revlog.
1286 the specified revlog.
1285
1287
1286 The start revision can be defined via ``-s/--startrev``.
1288 The start revision can be defined via ``-s/--startrev``.
1287 """
1289 """
1288 rl = cmdutil.openrevlog(repo, 'perfrevlogrevisions', file_, opts)
1290 rl = cmdutil.openrevlog(repo, 'perfrevlogrevisions', file_, opts)
1289 rllen = getlen(ui)(rl)
1291 rllen = getlen(ui)(rl)
1290
1292
1291 def d():
1293 def d():
1292 rl.clearcaches()
1294 rl.clearcaches()
1293
1295
1294 beginrev = startrev
1296 beginrev = startrev
1295 endrev = rllen
1297 endrev = rllen
1296 dist = opts['dist']
1298 dist = opts['dist']
1297
1299
1298 if reverse:
1300 if reverse:
1299 beginrev, endrev = endrev, beginrev
1301 beginrev, endrev = endrev, beginrev
1300 dist = -1 * dist
1302 dist = -1 * dist
1301
1303
1302 for x in xrange(beginrev, endrev, dist):
1304 for x in xrange(beginrev, endrev, dist):
1303 # Old revisions don't support passing int.
1305 # Old revisions don't support passing int.
1304 n = rl.node(x)
1306 n = rl.node(x)
1305 rl.revision(n)
1307 rl.revision(n)
1306
1308
1307 timer, fm = gettimer(ui, opts)
1309 timer, fm = gettimer(ui, opts)
1308 timer(d)
1310 timer(d)
1309 fm.end()
1311 fm.end()
1310
1312
1311 @command('perfrevlogchunks', revlogopts + formatteropts +
1313 @command('perfrevlogchunks', revlogopts + formatteropts +
1312 [('e', 'engines', '', 'compression engines to use'),
1314 [('e', 'engines', '', 'compression engines to use'),
1313 ('s', 'startrev', 0, 'revision to start at')],
1315 ('s', 'startrev', 0, 'revision to start at')],
1314 '-c|-m|FILE')
1316 '-c|-m|FILE')
1315 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1317 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1316 """Benchmark operations on revlog chunks.
1318 """Benchmark operations on revlog chunks.
1317
1319
1318 Logically, each revlog is a collection of fulltext revisions. However,
1320 Logically, each revlog is a collection of fulltext revisions. However,
1319 stored within each revlog are "chunks" of possibly compressed data. This
1321 stored within each revlog are "chunks" of possibly compressed data. This
1320 data needs to be read and decompressed or compressed and written.
1322 data needs to be read and decompressed or compressed and written.
1321
1323
1322 This command measures the time it takes to read+decompress and recompress
1324 This command measures the time it takes to read+decompress and recompress
1323 chunks in a revlog. It effectively isolates I/O and compression performance.
1325 chunks in a revlog. It effectively isolates I/O and compression performance.
1324 For measurements of higher-level operations like resolving revisions,
1326 For measurements of higher-level operations like resolving revisions,
1325 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1327 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1326 """
1328 """
1327 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
1329 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
1328
1330
1329 # _chunkraw was renamed to _getsegmentforrevs.
1331 # _chunkraw was renamed to _getsegmentforrevs.
1330 try:
1332 try:
1331 segmentforrevs = rl._getsegmentforrevs
1333 segmentforrevs = rl._getsegmentforrevs
1332 except AttributeError:
1334 except AttributeError:
1333 segmentforrevs = rl._chunkraw
1335 segmentforrevs = rl._chunkraw
1334
1336
1335 # Verify engines argument.
1337 # Verify engines argument.
1336 if engines:
1338 if engines:
1337 engines = set(e.strip() for e in engines.split(','))
1339 engines = set(e.strip() for e in engines.split(','))
1338 for engine in engines:
1340 for engine in engines:
1339 try:
1341 try:
1340 util.compressionengines[engine]
1342 util.compressionengines[engine]
1341 except KeyError:
1343 except KeyError:
1342 raise error.Abort('unknown compression engine: %s' % engine)
1344 raise error.Abort('unknown compression engine: %s' % engine)
1343 else:
1345 else:
1344 engines = []
1346 engines = []
1345 for e in util.compengines:
1347 for e in util.compengines:
1346 engine = util.compengines[e]
1348 engine = util.compengines[e]
1347 try:
1349 try:
1348 if engine.available():
1350 if engine.available():
1349 engine.revlogcompressor().compress('dummy')
1351 engine.revlogcompressor().compress('dummy')
1350 engines.append(e)
1352 engines.append(e)
1351 except NotImplementedError:
1353 except NotImplementedError:
1352 pass
1354 pass
1353
1355
1354 revs = list(rl.revs(startrev, len(rl) - 1))
1356 revs = list(rl.revs(startrev, len(rl) - 1))
1355
1357
1356 def rlfh(rl):
1358 def rlfh(rl):
1357 if rl._inline:
1359 if rl._inline:
1358 return getsvfs(repo)(rl.indexfile)
1360 return getsvfs(repo)(rl.indexfile)
1359 else:
1361 else:
1360 return getsvfs(repo)(rl.datafile)
1362 return getsvfs(repo)(rl.datafile)
1361
1363
1362 def doread():
1364 def doread():
1363 rl.clearcaches()
1365 rl.clearcaches()
1364 for rev in revs:
1366 for rev in revs:
1365 segmentforrevs(rev, rev)
1367 segmentforrevs(rev, rev)
1366
1368
1367 def doreadcachedfh():
1369 def doreadcachedfh():
1368 rl.clearcaches()
1370 rl.clearcaches()
1369 fh = rlfh(rl)
1371 fh = rlfh(rl)
1370 for rev in revs:
1372 for rev in revs:
1371 segmentforrevs(rev, rev, df=fh)
1373 segmentforrevs(rev, rev, df=fh)
1372
1374
1373 def doreadbatch():
1375 def doreadbatch():
1374 rl.clearcaches()
1376 rl.clearcaches()
1375 segmentforrevs(revs[0], revs[-1])
1377 segmentforrevs(revs[0], revs[-1])
1376
1378
1377 def doreadbatchcachedfh():
1379 def doreadbatchcachedfh():
1378 rl.clearcaches()
1380 rl.clearcaches()
1379 fh = rlfh(rl)
1381 fh = rlfh(rl)
1380 segmentforrevs(revs[0], revs[-1], df=fh)
1382 segmentforrevs(revs[0], revs[-1], df=fh)
1381
1383
1382 def dochunk():
1384 def dochunk():
1383 rl.clearcaches()
1385 rl.clearcaches()
1384 fh = rlfh(rl)
1386 fh = rlfh(rl)
1385 for rev in revs:
1387 for rev in revs:
1386 rl._chunk(rev, df=fh)
1388 rl._chunk(rev, df=fh)
1387
1389
1388 chunks = [None]
1390 chunks = [None]
1389
1391
1390 def dochunkbatch():
1392 def dochunkbatch():
1391 rl.clearcaches()
1393 rl.clearcaches()
1392 fh = rlfh(rl)
1394 fh = rlfh(rl)
1393 # Save chunks as a side-effect.
1395 # Save chunks as a side-effect.
1394 chunks[0] = rl._chunks(revs, df=fh)
1396 chunks[0] = rl._chunks(revs, df=fh)
1395
1397
1396 def docompress(compressor):
1398 def docompress(compressor):
1397 rl.clearcaches()
1399 rl.clearcaches()
1398
1400
1399 try:
1401 try:
1400 # Swap in the requested compression engine.
1402 # Swap in the requested compression engine.
1401 oldcompressor = rl._compressor
1403 oldcompressor = rl._compressor
1402 rl._compressor = compressor
1404 rl._compressor = compressor
1403 for chunk in chunks[0]:
1405 for chunk in chunks[0]:
1404 rl.compress(chunk)
1406 rl.compress(chunk)
1405 finally:
1407 finally:
1406 rl._compressor = oldcompressor
1408 rl._compressor = oldcompressor
1407
1409
1408 benches = [
1410 benches = [
1409 (lambda: doread(), 'read'),
1411 (lambda: doread(), 'read'),
1410 (lambda: doreadcachedfh(), 'read w/ reused fd'),
1412 (lambda: doreadcachedfh(), 'read w/ reused fd'),
1411 (lambda: doreadbatch(), 'read batch'),
1413 (lambda: doreadbatch(), 'read batch'),
1412 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
1414 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
1413 (lambda: dochunk(), 'chunk'),
1415 (lambda: dochunk(), 'chunk'),
1414 (lambda: dochunkbatch(), 'chunk batch'),
1416 (lambda: dochunkbatch(), 'chunk batch'),
1415 ]
1417 ]
1416
1418
1417 for engine in sorted(engines):
1419 for engine in sorted(engines):
1418 compressor = util.compengines[engine].revlogcompressor()
1420 compressor = util.compengines[engine].revlogcompressor()
1419 benches.append((functools.partial(docompress, compressor),
1421 benches.append((functools.partial(docompress, compressor),
1420 'compress w/ %s' % engine))
1422 'compress w/ %s' % engine))
1421
1423
1422 for fn, title in benches:
1424 for fn, title in benches:
1423 timer, fm = gettimer(ui, opts)
1425 timer, fm = gettimer(ui, opts)
1424 timer(fn, title=title)
1426 timer(fn, title=title)
1425 fm.end()
1427 fm.end()
1426
1428
1427 @command('perfrevlogrevision', revlogopts + formatteropts +
1429 @command('perfrevlogrevision', revlogopts + formatteropts +
1428 [('', 'cache', False, 'use caches instead of clearing')],
1430 [('', 'cache', False, 'use caches instead of clearing')],
1429 '-c|-m|FILE REV')
1431 '-c|-m|FILE REV')
1430 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1432 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1431 """Benchmark obtaining a revlog revision.
1433 """Benchmark obtaining a revlog revision.
1432
1434
1433 Obtaining a revlog revision consists of roughly the following steps:
1435 Obtaining a revlog revision consists of roughly the following steps:
1434
1436
1435 1. Compute the delta chain
1437 1. Compute the delta chain
1436 2. Obtain the raw chunks for that delta chain
1438 2. Obtain the raw chunks for that delta chain
1437 3. Decompress each raw chunk
1439 3. Decompress each raw chunk
1438 4. Apply binary patches to obtain fulltext
1440 4. Apply binary patches to obtain fulltext
1439 5. Verify hash of fulltext
1441 5. Verify hash of fulltext
1440
1442
1441 This command measures the time spent in each of these phases.
1443 This command measures the time spent in each of these phases.
1442 """
1444 """
1443 if opts.get('changelog') or opts.get('manifest'):
1445 if opts.get('changelog') or opts.get('manifest'):
1444 file_, rev = None, file_
1446 file_, rev = None, file_
1445 elif rev is None:
1447 elif rev is None:
1446 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
1448 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
1447
1449
1448 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
1450 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
1449
1451
1450 # _chunkraw was renamed to _getsegmentforrevs.
1452 # _chunkraw was renamed to _getsegmentforrevs.
1451 try:
1453 try:
1452 segmentforrevs = r._getsegmentforrevs
1454 segmentforrevs = r._getsegmentforrevs
1453 except AttributeError:
1455 except AttributeError:
1454 segmentforrevs = r._chunkraw
1456 segmentforrevs = r._chunkraw
1455
1457
1456 node = r.lookup(rev)
1458 node = r.lookup(rev)
1457 rev = r.rev(node)
1459 rev = r.rev(node)
1458
1460
1459 def getrawchunks(data, chain):
1461 def getrawchunks(data, chain):
1460 start = r.start
1462 start = r.start
1461 length = r.length
1463 length = r.length
1462 inline = r._inline
1464 inline = r._inline
1463 iosize = r._io.size
1465 iosize = r._io.size
1464 buffer = util.buffer
1466 buffer = util.buffer
1465 offset = start(chain[0])
1467 offset = start(chain[0])
1466
1468
1467 chunks = []
1469 chunks = []
1468 ladd = chunks.append
1470 ladd = chunks.append
1469
1471
1470 for rev in chain:
1472 for rev in chain:
1471 chunkstart = start(rev)
1473 chunkstart = start(rev)
1472 if inline:
1474 if inline:
1473 chunkstart += (rev + 1) * iosize
1475 chunkstart += (rev + 1) * iosize
1474 chunklength = length(rev)
1476 chunklength = length(rev)
1475 ladd(buffer(data, chunkstart - offset, chunklength))
1477 ladd(buffer(data, chunkstart - offset, chunklength))
1476
1478
1477 return chunks
1479 return chunks
1478
1480
1479 def dodeltachain(rev):
1481 def dodeltachain(rev):
1480 if not cache:
1482 if not cache:
1481 r.clearcaches()
1483 r.clearcaches()
1482 r._deltachain(rev)
1484 r._deltachain(rev)
1483
1485
1484 def doread(chain):
1486 def doread(chain):
1485 if not cache:
1487 if not cache:
1486 r.clearcaches()
1488 r.clearcaches()
1487 segmentforrevs(chain[0], chain[-1])
1489 segmentforrevs(chain[0], chain[-1])
1488
1490
1489 def dorawchunks(data, chain):
1491 def dorawchunks(data, chain):
1490 if not cache:
1492 if not cache:
1491 r.clearcaches()
1493 r.clearcaches()
1492 getrawchunks(data, chain)
1494 getrawchunks(data, chain)
1493
1495
1494 def dodecompress(chunks):
1496 def dodecompress(chunks):
1495 decomp = r.decompress
1497 decomp = r.decompress
1496 for chunk in chunks:
1498 for chunk in chunks:
1497 decomp(chunk)
1499 decomp(chunk)
1498
1500
1499 def dopatch(text, bins):
1501 def dopatch(text, bins):
1500 if not cache:
1502 if not cache:
1501 r.clearcaches()
1503 r.clearcaches()
1502 mdiff.patches(text, bins)
1504 mdiff.patches(text, bins)
1503
1505
1504 def dohash(text):
1506 def dohash(text):
1505 if not cache:
1507 if not cache:
1506 r.clearcaches()
1508 r.clearcaches()
1507 r.checkhash(text, node, rev=rev)
1509 r.checkhash(text, node, rev=rev)
1508
1510
1509 def dorevision():
1511 def dorevision():
1510 if not cache:
1512 if not cache:
1511 r.clearcaches()
1513 r.clearcaches()
1512 r.revision(node)
1514 r.revision(node)
1513
1515
1514 chain = r._deltachain(rev)[0]
1516 chain = r._deltachain(rev)[0]
1515 data = segmentforrevs(chain[0], chain[-1])[1]
1517 data = segmentforrevs(chain[0], chain[-1])[1]
1516 rawchunks = getrawchunks(data, chain)
1518 rawchunks = getrawchunks(data, chain)
1517 bins = r._chunks(chain)
1519 bins = r._chunks(chain)
1518 text = str(bins[0])
1520 text = str(bins[0])
1519 bins = bins[1:]
1521 bins = bins[1:]
1520 text = mdiff.patches(text, bins)
1522 text = mdiff.patches(text, bins)
1521
1523
1522 benches = [
1524 benches = [
1523 (lambda: dorevision(), 'full'),
1525 (lambda: dorevision(), 'full'),
1524 (lambda: dodeltachain(rev), 'deltachain'),
1526 (lambda: dodeltachain(rev), 'deltachain'),
1525 (lambda: doread(chain), 'read'),
1527 (lambda: doread(chain), 'read'),
1526 (lambda: dorawchunks(data, chain), 'rawchunks'),
1528 (lambda: dorawchunks(data, chain), 'rawchunks'),
1527 (lambda: dodecompress(rawchunks), 'decompress'),
1529 (lambda: dodecompress(rawchunks), 'decompress'),
1528 (lambda: dopatch(text, bins), 'patch'),
1530 (lambda: dopatch(text, bins), 'patch'),
1529 (lambda: dohash(text), 'hash'),
1531 (lambda: dohash(text), 'hash'),
1530 ]
1532 ]
1531
1533
1532 for fn, title in benches:
1534 for fn, title in benches:
1533 timer, fm = gettimer(ui, opts)
1535 timer, fm = gettimer(ui, opts)
1534 timer(fn, title=title)
1536 timer(fn, title=title)
1535 fm.end()
1537 fm.end()
1536
1538
1537 @command('perfrevset',
1539 @command('perfrevset',
1538 [('C', 'clear', False, 'clear volatile cache between each call.'),
1540 [('C', 'clear', False, 'clear volatile cache between each call.'),
1539 ('', 'contexts', False, 'obtain changectx for each revision')]
1541 ('', 'contexts', False, 'obtain changectx for each revision')]
1540 + formatteropts, "REVSET")
1542 + formatteropts, "REVSET")
1541 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1543 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1542 """benchmark the execution time of a revset
1544 """benchmark the execution time of a revset
1543
1545
1544 Use the --clean option if need to evaluate the impact of build volatile
1546 Use the --clean option if need to evaluate the impact of build volatile
1545 revisions set cache on the revset execution. Volatile cache hold filtered
1547 revisions set cache on the revset execution. Volatile cache hold filtered
1546 and obsolete related cache."""
1548 and obsolete related cache."""
1547 timer, fm = gettimer(ui, opts)
1549 timer, fm = gettimer(ui, opts)
1548 def d():
1550 def d():
1549 if clear:
1551 if clear:
1550 repo.invalidatevolatilesets()
1552 repo.invalidatevolatilesets()
1551 if contexts:
1553 if contexts:
1552 for ctx in repo.set(expr): pass
1554 for ctx in repo.set(expr): pass
1553 else:
1555 else:
1554 for r in repo.revs(expr): pass
1556 for r in repo.revs(expr): pass
1555 timer(d)
1557 timer(d)
1556 fm.end()
1558 fm.end()
1557
1559
1558 @command('perfvolatilesets',
1560 @command('perfvolatilesets',
1559 [('', 'clear-obsstore', False, 'drop obsstore between each call.'),
1561 [('', 'clear-obsstore', False, 'drop obsstore between each call.'),
1560 ] + formatteropts)
1562 ] + formatteropts)
1561 def perfvolatilesets(ui, repo, *names, **opts):
1563 def perfvolatilesets(ui, repo, *names, **opts):
1562 """benchmark the computation of various volatile set
1564 """benchmark the computation of various volatile set
1563
1565
1564 Volatile set computes element related to filtering and obsolescence."""
1566 Volatile set computes element related to filtering and obsolescence."""
1565 timer, fm = gettimer(ui, opts)
1567 timer, fm = gettimer(ui, opts)
1566 repo = repo.unfiltered()
1568 repo = repo.unfiltered()
1567
1569
1568 def getobs(name):
1570 def getobs(name):
1569 def d():
1571 def d():
1570 repo.invalidatevolatilesets()
1572 repo.invalidatevolatilesets()
1571 if opts['clear_obsstore']:
1573 if opts['clear_obsstore']:
1572 clearfilecache(repo, 'obsstore')
1574 clearfilecache(repo, 'obsstore')
1573 obsolete.getrevs(repo, name)
1575 obsolete.getrevs(repo, name)
1574 return d
1576 return d
1575
1577
1576 allobs = sorted(obsolete.cachefuncs)
1578 allobs = sorted(obsolete.cachefuncs)
1577 if names:
1579 if names:
1578 allobs = [n for n in allobs if n in names]
1580 allobs = [n for n in allobs if n in names]
1579
1581
1580 for name in allobs:
1582 for name in allobs:
1581 timer(getobs(name), title=name)
1583 timer(getobs(name), title=name)
1582
1584
1583 def getfiltered(name):
1585 def getfiltered(name):
1584 def d():
1586 def d():
1585 repo.invalidatevolatilesets()
1587 repo.invalidatevolatilesets()
1586 if opts['clear_obsstore']:
1588 if opts['clear_obsstore']:
1587 clearfilecache(repo, 'obsstore')
1589 clearfilecache(repo, 'obsstore')
1588 repoview.filterrevs(repo, name)
1590 repoview.filterrevs(repo, name)
1589 return d
1591 return d
1590
1592
1591 allfilter = sorted(repoview.filtertable)
1593 allfilter = sorted(repoview.filtertable)
1592 if names:
1594 if names:
1593 allfilter = [n for n in allfilter if n in names]
1595 allfilter = [n for n in allfilter if n in names]
1594
1596
1595 for name in allfilter:
1597 for name in allfilter:
1596 timer(getfiltered(name), title=name)
1598 timer(getfiltered(name), title=name)
1597 fm.end()
1599 fm.end()
1598
1600
1599 @command('perfbranchmap',
1601 @command('perfbranchmap',
1600 [('f', 'full', False,
1602 [('f', 'full', False,
1601 'Includes build time of subset'),
1603 'Includes build time of subset'),
1602 ('', 'clear-revbranch', False,
1604 ('', 'clear-revbranch', False,
1603 'purge the revbranch cache between computation'),
1605 'purge the revbranch cache between computation'),
1604 ] + formatteropts)
1606 ] + formatteropts)
1605 def perfbranchmap(ui, repo, *filternames, **opts):
1607 def perfbranchmap(ui, repo, *filternames, **opts):
1606 """benchmark the update of a branchmap
1608 """benchmark the update of a branchmap
1607
1609
1608 This benchmarks the full repo.branchmap() call with read and write disabled
1610 This benchmarks the full repo.branchmap() call with read and write disabled
1609 """
1611 """
1610 full = opts.get("full", False)
1612 full = opts.get("full", False)
1611 clear_revbranch = opts.get("clear_revbranch", False)
1613 clear_revbranch = opts.get("clear_revbranch", False)
1612 timer, fm = gettimer(ui, opts)
1614 timer, fm = gettimer(ui, opts)
1613 def getbranchmap(filtername):
1615 def getbranchmap(filtername):
1614 """generate a benchmark function for the filtername"""
1616 """generate a benchmark function for the filtername"""
1615 if filtername is None:
1617 if filtername is None:
1616 view = repo
1618 view = repo
1617 else:
1619 else:
1618 view = repo.filtered(filtername)
1620 view = repo.filtered(filtername)
1619 def d():
1621 def d():
1620 if clear_revbranch:
1622 if clear_revbranch:
1621 repo.revbranchcache()._clear()
1623 repo.revbranchcache()._clear()
1622 if full:
1624 if full:
1623 view._branchcaches.clear()
1625 view._branchcaches.clear()
1624 else:
1626 else:
1625 view._branchcaches.pop(filtername, None)
1627 view._branchcaches.pop(filtername, None)
1626 view.branchmap()
1628 view.branchmap()
1627 return d
1629 return d
1628 # add filter in smaller subset to bigger subset
1630 # add filter in smaller subset to bigger subset
1629 possiblefilters = set(repoview.filtertable)
1631 possiblefilters = set(repoview.filtertable)
1630 if filternames:
1632 if filternames:
1631 possiblefilters &= set(filternames)
1633 possiblefilters &= set(filternames)
1632 subsettable = getbranchmapsubsettable()
1634 subsettable = getbranchmapsubsettable()
1633 allfilters = []
1635 allfilters = []
1634 while possiblefilters:
1636 while possiblefilters:
1635 for name in possiblefilters:
1637 for name in possiblefilters:
1636 subset = subsettable.get(name)
1638 subset = subsettable.get(name)
1637 if subset not in possiblefilters:
1639 if subset not in possiblefilters:
1638 break
1640 break
1639 else:
1641 else:
1640 assert False, 'subset cycle %s!' % possiblefilters
1642 assert False, 'subset cycle %s!' % possiblefilters
1641 allfilters.append(name)
1643 allfilters.append(name)
1642 possiblefilters.remove(name)
1644 possiblefilters.remove(name)
1643
1645
1644 # warm the cache
1646 # warm the cache
1645 if not full:
1647 if not full:
1646 for name in allfilters:
1648 for name in allfilters:
1647 repo.filtered(name).branchmap()
1649 repo.filtered(name).branchmap()
1648 if not filternames or 'unfiltered' in filternames:
1650 if not filternames or 'unfiltered' in filternames:
1649 # add unfiltered
1651 # add unfiltered
1650 allfilters.append(None)
1652 allfilters.append(None)
1651
1653
1652 branchcacheread = safeattrsetter(branchmap, 'read')
1654 branchcacheread = safeattrsetter(branchmap, 'read')
1653 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1655 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1654 branchcacheread.set(lambda repo: None)
1656 branchcacheread.set(lambda repo: None)
1655 branchcachewrite.set(lambda bc, repo: None)
1657 branchcachewrite.set(lambda bc, repo: None)
1656 try:
1658 try:
1657 for name in allfilters:
1659 for name in allfilters:
1658 printname = name
1660 printname = name
1659 if name is None:
1661 if name is None:
1660 printname = 'unfiltered'
1662 printname = 'unfiltered'
1661 timer(getbranchmap(name), title=str(printname))
1663 timer(getbranchmap(name), title=str(printname))
1662 finally:
1664 finally:
1663 branchcacheread.restore()
1665 branchcacheread.restore()
1664 branchcachewrite.restore()
1666 branchcachewrite.restore()
1665 fm.end()
1667 fm.end()
1666
1668
1667 @command('perfloadmarkers')
1669 @command('perfloadmarkers')
1668 def perfloadmarkers(ui, repo):
1670 def perfloadmarkers(ui, repo):
1669 """benchmark the time to parse the on-disk markers for a repo
1671 """benchmark the time to parse the on-disk markers for a repo
1670
1672
1671 Result is the number of markers in the repo."""
1673 Result is the number of markers in the repo."""
1672 timer, fm = gettimer(ui)
1674 timer, fm = gettimer(ui)
1673 svfs = getsvfs(repo)
1675 svfs = getsvfs(repo)
1674 timer(lambda: len(obsolete.obsstore(svfs)))
1676 timer(lambda: len(obsolete.obsstore(svfs)))
1675 fm.end()
1677 fm.end()
1676
1678
1677 @command('perflrucachedict', formatteropts +
1679 @command('perflrucachedict', formatteropts +
1678 [('', 'size', 4, 'size of cache'),
1680 [('', 'size', 4, 'size of cache'),
1679 ('', 'gets', 10000, 'number of key lookups'),
1681 ('', 'gets', 10000, 'number of key lookups'),
1680 ('', 'sets', 10000, 'number of key sets'),
1682 ('', 'sets', 10000, 'number of key sets'),
1681 ('', 'mixed', 10000, 'number of mixed mode operations'),
1683 ('', 'mixed', 10000, 'number of mixed mode operations'),
1682 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1684 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1683 norepo=True)
1685 norepo=True)
1684 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1686 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1685 mixedgetfreq=50, **opts):
1687 mixedgetfreq=50, **opts):
1686 def doinit():
1688 def doinit():
1687 for i in xrange(10000):
1689 for i in xrange(10000):
1688 util.lrucachedict(size)
1690 util.lrucachedict(size)
1689
1691
1690 values = []
1692 values = []
1691 for i in xrange(size):
1693 for i in xrange(size):
1692 values.append(random.randint(0, sys.maxint))
1694 values.append(random.randint(0, sys.maxint))
1693
1695
1694 # Get mode fills the cache and tests raw lookup performance with no
1696 # Get mode fills the cache and tests raw lookup performance with no
1695 # eviction.
1697 # eviction.
1696 getseq = []
1698 getseq = []
1697 for i in xrange(gets):
1699 for i in xrange(gets):
1698 getseq.append(random.choice(values))
1700 getseq.append(random.choice(values))
1699
1701
1700 def dogets():
1702 def dogets():
1701 d = util.lrucachedict(size)
1703 d = util.lrucachedict(size)
1702 for v in values:
1704 for v in values:
1703 d[v] = v
1705 d[v] = v
1704 for key in getseq:
1706 for key in getseq:
1705 value = d[key]
1707 value = d[key]
1706 value # silence pyflakes warning
1708 value # silence pyflakes warning
1707
1709
1708 # Set mode tests insertion speed with cache eviction.
1710 # Set mode tests insertion speed with cache eviction.
1709 setseq = []
1711 setseq = []
1710 for i in xrange(sets):
1712 for i in xrange(sets):
1711 setseq.append(random.randint(0, sys.maxint))
1713 setseq.append(random.randint(0, sys.maxint))
1712
1714
1713 def dosets():
1715 def dosets():
1714 d = util.lrucachedict(size)
1716 d = util.lrucachedict(size)
1715 for v in setseq:
1717 for v in setseq:
1716 d[v] = v
1718 d[v] = v
1717
1719
1718 # Mixed mode randomly performs gets and sets with eviction.
1720 # Mixed mode randomly performs gets and sets with eviction.
1719 mixedops = []
1721 mixedops = []
1720 for i in xrange(mixed):
1722 for i in xrange(mixed):
1721 r = random.randint(0, 100)
1723 r = random.randint(0, 100)
1722 if r < mixedgetfreq:
1724 if r < mixedgetfreq:
1723 op = 0
1725 op = 0
1724 else:
1726 else:
1725 op = 1
1727 op = 1
1726
1728
1727 mixedops.append((op, random.randint(0, size * 2)))
1729 mixedops.append((op, random.randint(0, size * 2)))
1728
1730
1729 def domixed():
1731 def domixed():
1730 d = util.lrucachedict(size)
1732 d = util.lrucachedict(size)
1731
1733
1732 for op, v in mixedops:
1734 for op, v in mixedops:
1733 if op == 0:
1735 if op == 0:
1734 try:
1736 try:
1735 d[v]
1737 d[v]
1736 except KeyError:
1738 except KeyError:
1737 pass
1739 pass
1738 else:
1740 else:
1739 d[v] = v
1741 d[v] = v
1740
1742
1741 benches = [
1743 benches = [
1742 (doinit, 'init'),
1744 (doinit, 'init'),
1743 (dogets, 'gets'),
1745 (dogets, 'gets'),
1744 (dosets, 'sets'),
1746 (dosets, 'sets'),
1745 (domixed, 'mixed')
1747 (domixed, 'mixed')
1746 ]
1748 ]
1747
1749
1748 for fn, title in benches:
1750 for fn, title in benches:
1749 timer, fm = gettimer(ui, opts)
1751 timer, fm = gettimer(ui, opts)
1750 timer(fn, title=title)
1752 timer(fn, title=title)
1751 fm.end()
1753 fm.end()
1752
1754
1753 @command('perfwrite', formatteropts)
1755 @command('perfwrite', formatteropts)
1754 def perfwrite(ui, repo, **opts):
1756 def perfwrite(ui, repo, **opts):
1755 """microbenchmark ui.write
1757 """microbenchmark ui.write
1756 """
1758 """
1757 timer, fm = gettimer(ui, opts)
1759 timer, fm = gettimer(ui, opts)
1758 def write():
1760 def write():
1759 for i in range(100000):
1761 for i in range(100000):
1760 ui.write(('Testing write performance\n'))
1762 ui.write(('Testing write performance\n'))
1761 timer(write)
1763 timer(write)
1762 fm.end()
1764 fm.end()
1763
1765
1764 def uisetup(ui):
1766 def uisetup(ui):
1765 if (util.safehasattr(cmdutil, 'openrevlog') and
1767 if (util.safehasattr(cmdutil, 'openrevlog') and
1766 not util.safehasattr(commands, 'debugrevlogopts')):
1768 not util.safehasattr(commands, 'debugrevlogopts')):
1767 # for "historical portability":
1769 # for "historical portability":
1768 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1770 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1769 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1771 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1770 # openrevlog() should cause failure, because it has been
1772 # openrevlog() should cause failure, because it has been
1771 # available since 3.5 (or 49c583ca48c4).
1773 # available since 3.5 (or 49c583ca48c4).
1772 def openrevlog(orig, repo, cmd, file_, opts):
1774 def openrevlog(orig, repo, cmd, file_, opts):
1773 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1775 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1774 raise error.Abort("This version doesn't support --dir option",
1776 raise error.Abort("This version doesn't support --dir option",
1775 hint="use 3.5 or later")
1777 hint="use 3.5 or later")
1776 return orig(repo, cmd, file_, opts)
1778 return orig(repo, cmd, file_, opts)
1777 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
1779 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
General Comments 0
You need to be logged in to leave comments. Login now