##// END OF EJS Templates
perf: add a `setup` argument to run code outside of the timed section...
Boris Feld -
r40716:9d88ae5c default
parent child Browse files
Show More
@@ -1,2365 +1,2367
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 contextlib
22 import contextlib
23 import functools
23 import functools
24 import gc
24 import gc
25 import os
25 import os
26 import random
26 import random
27 import shutil
27 import shutil
28 import struct
28 import struct
29 import sys
29 import sys
30 import tempfile
30 import tempfile
31 import threading
31 import threading
32 import time
32 import time
33 from mercurial import (
33 from mercurial import (
34 changegroup,
34 changegroup,
35 cmdutil,
35 cmdutil,
36 commands,
36 commands,
37 copies,
37 copies,
38 error,
38 error,
39 extensions,
39 extensions,
40 mdiff,
40 mdiff,
41 merge,
41 merge,
42 revlog,
42 revlog,
43 util,
43 util,
44 )
44 )
45
45
46 # for "historical portability":
46 # for "historical portability":
47 # try to import modules separately (in dict order), and ignore
47 # try to import modules separately (in dict order), and ignore
48 # failure, because these aren't available with early Mercurial
48 # failure, because these aren't available with early Mercurial
49 try:
49 try:
50 from mercurial import branchmap # since 2.5 (or bcee63733aad)
50 from mercurial import branchmap # since 2.5 (or bcee63733aad)
51 except ImportError:
51 except ImportError:
52 pass
52 pass
53 try:
53 try:
54 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
54 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
55 except ImportError:
55 except ImportError:
56 pass
56 pass
57 try:
57 try:
58 from mercurial import registrar # since 3.7 (or 37d50250b696)
58 from mercurial import registrar # since 3.7 (or 37d50250b696)
59 dir(registrar) # forcibly load it
59 dir(registrar) # forcibly load it
60 except ImportError:
60 except ImportError:
61 registrar = None
61 registrar = None
62 try:
62 try:
63 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
63 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
64 except ImportError:
64 except ImportError:
65 pass
65 pass
66 try:
66 try:
67 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
67 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
68 except ImportError:
68 except ImportError:
69 pass
69 pass
70
70
71 def identity(a):
71 def identity(a):
72 return a
72 return a
73
73
74 try:
74 try:
75 from mercurial import pycompat
75 from mercurial import pycompat
76 getargspec = pycompat.getargspec # added to module after 4.5
76 getargspec = pycompat.getargspec # added to module after 4.5
77 _byteskwargs = pycompat.byteskwargs # since 4.1 (or fbc3f73dc802)
77 _byteskwargs = pycompat.byteskwargs # since 4.1 (or fbc3f73dc802)
78 _sysstr = pycompat.sysstr # since 4.0 (or 2219f4f82ede)
78 _sysstr = pycompat.sysstr # since 4.0 (or 2219f4f82ede)
79 _xrange = pycompat.xrange # since 4.8 (or 7eba8f83129b)
79 _xrange = pycompat.xrange # since 4.8 (or 7eba8f83129b)
80 fsencode = pycompat.fsencode # since 3.9 (or f4a5e0e86a7e)
80 fsencode = pycompat.fsencode # since 3.9 (or f4a5e0e86a7e)
81 if pycompat.ispy3:
81 if pycompat.ispy3:
82 _maxint = sys.maxsize # per py3 docs for replacing maxint
82 _maxint = sys.maxsize # per py3 docs for replacing maxint
83 else:
83 else:
84 _maxint = sys.maxint
84 _maxint = sys.maxint
85 except (ImportError, AttributeError):
85 except (ImportError, AttributeError):
86 import inspect
86 import inspect
87 getargspec = inspect.getargspec
87 getargspec = inspect.getargspec
88 _byteskwargs = identity
88 _byteskwargs = identity
89 fsencode = identity # no py3 support
89 fsencode = identity # no py3 support
90 _maxint = sys.maxint # no py3 support
90 _maxint = sys.maxint # no py3 support
91 _sysstr = lambda x: x # no py3 support
91 _sysstr = lambda x: x # no py3 support
92 _xrange = xrange
92 _xrange = xrange
93
93
94 try:
94 try:
95 # 4.7+
95 # 4.7+
96 queue = pycompat.queue.Queue
96 queue = pycompat.queue.Queue
97 except (AttributeError, ImportError):
97 except (AttributeError, ImportError):
98 # <4.7.
98 # <4.7.
99 try:
99 try:
100 queue = pycompat.queue
100 queue = pycompat.queue
101 except (AttributeError, ImportError):
101 except (AttributeError, ImportError):
102 queue = util.queue
102 queue = util.queue
103
103
104 try:
104 try:
105 from mercurial import logcmdutil
105 from mercurial import logcmdutil
106 makelogtemplater = logcmdutil.maketemplater
106 makelogtemplater = logcmdutil.maketemplater
107 except (AttributeError, ImportError):
107 except (AttributeError, ImportError):
108 try:
108 try:
109 makelogtemplater = cmdutil.makelogtemplater
109 makelogtemplater = cmdutil.makelogtemplater
110 except (AttributeError, ImportError):
110 except (AttributeError, ImportError):
111 makelogtemplater = None
111 makelogtemplater = None
112
112
113 # for "historical portability":
113 # for "historical portability":
114 # define util.safehasattr forcibly, because util.safehasattr has been
114 # define util.safehasattr forcibly, because util.safehasattr has been
115 # available since 1.9.3 (or 94b200a11cf7)
115 # available since 1.9.3 (or 94b200a11cf7)
116 _undefined = object()
116 _undefined = object()
117 def safehasattr(thing, attr):
117 def safehasattr(thing, attr):
118 return getattr(thing, _sysstr(attr), _undefined) is not _undefined
118 return getattr(thing, _sysstr(attr), _undefined) is not _undefined
119 setattr(util, 'safehasattr', safehasattr)
119 setattr(util, 'safehasattr', safehasattr)
120
120
121 # for "historical portability":
121 # for "historical portability":
122 # define util.timer forcibly, because util.timer has been available
122 # define util.timer forcibly, because util.timer has been available
123 # since ae5d60bb70c9
123 # since ae5d60bb70c9
124 if safehasattr(time, 'perf_counter'):
124 if safehasattr(time, 'perf_counter'):
125 util.timer = time.perf_counter
125 util.timer = time.perf_counter
126 elif os.name == b'nt':
126 elif os.name == b'nt':
127 util.timer = time.clock
127 util.timer = time.clock
128 else:
128 else:
129 util.timer = time.time
129 util.timer = time.time
130
130
131 # for "historical portability":
131 # for "historical portability":
132 # use locally defined empty option list, if formatteropts isn't
132 # use locally defined empty option list, if formatteropts isn't
133 # available, because commands.formatteropts has been available since
133 # available, because commands.formatteropts has been available since
134 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
134 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
135 # available since 2.2 (or ae5f92e154d3)
135 # available since 2.2 (or ae5f92e154d3)
136 formatteropts = getattr(cmdutil, "formatteropts",
136 formatteropts = getattr(cmdutil, "formatteropts",
137 getattr(commands, "formatteropts", []))
137 getattr(commands, "formatteropts", []))
138
138
139 # for "historical portability":
139 # for "historical portability":
140 # use locally defined option list, if debugrevlogopts isn't available,
140 # use locally defined option list, if debugrevlogopts isn't available,
141 # because commands.debugrevlogopts has been available since 3.7 (or
141 # because commands.debugrevlogopts has been available since 3.7 (or
142 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
142 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
143 # since 1.9 (or a79fea6b3e77).
143 # since 1.9 (or a79fea6b3e77).
144 revlogopts = getattr(cmdutil, "debugrevlogopts",
144 revlogopts = getattr(cmdutil, "debugrevlogopts",
145 getattr(commands, "debugrevlogopts", [
145 getattr(commands, "debugrevlogopts", [
146 (b'c', b'changelog', False, (b'open changelog')),
146 (b'c', b'changelog', False, (b'open changelog')),
147 (b'm', b'manifest', False, (b'open manifest')),
147 (b'm', b'manifest', False, (b'open manifest')),
148 (b'', b'dir', False, (b'open directory manifest')),
148 (b'', b'dir', False, (b'open directory manifest')),
149 ]))
149 ]))
150
150
151 cmdtable = {}
151 cmdtable = {}
152
152
153 # for "historical portability":
153 # for "historical portability":
154 # define parsealiases locally, because cmdutil.parsealiases has been
154 # define parsealiases locally, because cmdutil.parsealiases has been
155 # available since 1.5 (or 6252852b4332)
155 # available since 1.5 (or 6252852b4332)
156 def parsealiases(cmd):
156 def parsealiases(cmd):
157 return cmd.split(b"|")
157 return cmd.split(b"|")
158
158
159 if safehasattr(registrar, 'command'):
159 if safehasattr(registrar, 'command'):
160 command = registrar.command(cmdtable)
160 command = registrar.command(cmdtable)
161 elif safehasattr(cmdutil, 'command'):
161 elif safehasattr(cmdutil, 'command'):
162 command = cmdutil.command(cmdtable)
162 command = cmdutil.command(cmdtable)
163 if b'norepo' not in getargspec(command).args:
163 if b'norepo' not in getargspec(command).args:
164 # for "historical portability":
164 # for "historical portability":
165 # wrap original cmdutil.command, because "norepo" option has
165 # wrap original cmdutil.command, because "norepo" option has
166 # been available since 3.1 (or 75a96326cecb)
166 # been available since 3.1 (or 75a96326cecb)
167 _command = command
167 _command = command
168 def command(name, options=(), synopsis=None, norepo=False):
168 def command(name, options=(), synopsis=None, norepo=False):
169 if norepo:
169 if norepo:
170 commands.norepo += b' %s' % b' '.join(parsealiases(name))
170 commands.norepo += b' %s' % b' '.join(parsealiases(name))
171 return _command(name, list(options), synopsis)
171 return _command(name, list(options), synopsis)
172 else:
172 else:
173 # for "historical portability":
173 # for "historical portability":
174 # define "@command" annotation locally, because cmdutil.command
174 # define "@command" annotation locally, because cmdutil.command
175 # has been available since 1.9 (or 2daa5179e73f)
175 # has been available since 1.9 (or 2daa5179e73f)
176 def command(name, options=(), synopsis=None, norepo=False):
176 def command(name, options=(), synopsis=None, norepo=False):
177 def decorator(func):
177 def decorator(func):
178 if synopsis:
178 if synopsis:
179 cmdtable[name] = func, list(options), synopsis
179 cmdtable[name] = func, list(options), synopsis
180 else:
180 else:
181 cmdtable[name] = func, list(options)
181 cmdtable[name] = func, list(options)
182 if norepo:
182 if norepo:
183 commands.norepo += b' %s' % b' '.join(parsealiases(name))
183 commands.norepo += b' %s' % b' '.join(parsealiases(name))
184 return func
184 return func
185 return decorator
185 return decorator
186
186
187 try:
187 try:
188 import mercurial.registrar
188 import mercurial.registrar
189 import mercurial.configitems
189 import mercurial.configitems
190 configtable = {}
190 configtable = {}
191 configitem = mercurial.registrar.configitem(configtable)
191 configitem = mercurial.registrar.configitem(configtable)
192 configitem(b'perf', b'presleep',
192 configitem(b'perf', b'presleep',
193 default=mercurial.configitems.dynamicdefault,
193 default=mercurial.configitems.dynamicdefault,
194 )
194 )
195 configitem(b'perf', b'stub',
195 configitem(b'perf', b'stub',
196 default=mercurial.configitems.dynamicdefault,
196 default=mercurial.configitems.dynamicdefault,
197 )
197 )
198 configitem(b'perf', b'parentscount',
198 configitem(b'perf', b'parentscount',
199 default=mercurial.configitems.dynamicdefault,
199 default=mercurial.configitems.dynamicdefault,
200 )
200 )
201 configitem(b'perf', b'all-timing',
201 configitem(b'perf', b'all-timing',
202 default=mercurial.configitems.dynamicdefault,
202 default=mercurial.configitems.dynamicdefault,
203 )
203 )
204 except (ImportError, AttributeError):
204 except (ImportError, AttributeError):
205 pass
205 pass
206
206
207 def getlen(ui):
207 def getlen(ui):
208 if ui.configbool(b"perf", b"stub", False):
208 if ui.configbool(b"perf", b"stub", False):
209 return lambda x: 1
209 return lambda x: 1
210 return len
210 return len
211
211
212 def gettimer(ui, opts=None):
212 def gettimer(ui, opts=None):
213 """return a timer function and formatter: (timer, formatter)
213 """return a timer function and formatter: (timer, formatter)
214
214
215 This function exists to gather the creation of formatter in a single
215 This function exists to gather the creation of formatter in a single
216 place instead of duplicating it in all performance commands."""
216 place instead of duplicating it in all performance commands."""
217
217
218 # enforce an idle period before execution to counteract power management
218 # enforce an idle period before execution to counteract power management
219 # experimental config: perf.presleep
219 # experimental config: perf.presleep
220 time.sleep(getint(ui, b"perf", b"presleep", 1))
220 time.sleep(getint(ui, b"perf", b"presleep", 1))
221
221
222 if opts is None:
222 if opts is None:
223 opts = {}
223 opts = {}
224 # redirect all to stderr unless buffer api is in use
224 # redirect all to stderr unless buffer api is in use
225 if not ui._buffers:
225 if not ui._buffers:
226 ui = ui.copy()
226 ui = ui.copy()
227 uifout = safeattrsetter(ui, b'fout', ignoremissing=True)
227 uifout = safeattrsetter(ui, b'fout', ignoremissing=True)
228 if uifout:
228 if uifout:
229 # for "historical portability":
229 # for "historical portability":
230 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
230 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
231 uifout.set(ui.ferr)
231 uifout.set(ui.ferr)
232
232
233 # get a formatter
233 # get a formatter
234 uiformatter = getattr(ui, 'formatter', None)
234 uiformatter = getattr(ui, 'formatter', None)
235 if uiformatter:
235 if uiformatter:
236 fm = uiformatter(b'perf', opts)
236 fm = uiformatter(b'perf', opts)
237 else:
237 else:
238 # for "historical portability":
238 # for "historical portability":
239 # define formatter locally, because ui.formatter has been
239 # define formatter locally, because ui.formatter has been
240 # available since 2.2 (or ae5f92e154d3)
240 # available since 2.2 (or ae5f92e154d3)
241 from mercurial import node
241 from mercurial import node
242 class defaultformatter(object):
242 class defaultformatter(object):
243 """Minimized composition of baseformatter and plainformatter
243 """Minimized composition of baseformatter and plainformatter
244 """
244 """
245 def __init__(self, ui, topic, opts):
245 def __init__(self, ui, topic, opts):
246 self._ui = ui
246 self._ui = ui
247 if ui.debugflag:
247 if ui.debugflag:
248 self.hexfunc = node.hex
248 self.hexfunc = node.hex
249 else:
249 else:
250 self.hexfunc = node.short
250 self.hexfunc = node.short
251 def __nonzero__(self):
251 def __nonzero__(self):
252 return False
252 return False
253 __bool__ = __nonzero__
253 __bool__ = __nonzero__
254 def startitem(self):
254 def startitem(self):
255 pass
255 pass
256 def data(self, **data):
256 def data(self, **data):
257 pass
257 pass
258 def write(self, fields, deftext, *fielddata, **opts):
258 def write(self, fields, deftext, *fielddata, **opts):
259 self._ui.write(deftext % fielddata, **opts)
259 self._ui.write(deftext % fielddata, **opts)
260 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
260 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
261 if cond:
261 if cond:
262 self._ui.write(deftext % fielddata, **opts)
262 self._ui.write(deftext % fielddata, **opts)
263 def plain(self, text, **opts):
263 def plain(self, text, **opts):
264 self._ui.write(text, **opts)
264 self._ui.write(text, **opts)
265 def end(self):
265 def end(self):
266 pass
266 pass
267 fm = defaultformatter(ui, b'perf', opts)
267 fm = defaultformatter(ui, b'perf', opts)
268
268
269 # stub function, runs code only once instead of in a loop
269 # stub function, runs code only once instead of in a loop
270 # experimental config: perf.stub
270 # experimental config: perf.stub
271 if ui.configbool(b"perf", b"stub", False):
271 if ui.configbool(b"perf", b"stub", False):
272 return functools.partial(stub_timer, fm), fm
272 return functools.partial(stub_timer, fm), fm
273
273
274 # experimental config: perf.all-timing
274 # experimental config: perf.all-timing
275 displayall = ui.configbool(b"perf", b"all-timing", False)
275 displayall = ui.configbool(b"perf", b"all-timing", False)
276 return functools.partial(_timer, fm, displayall=displayall), fm
276 return functools.partial(_timer, fm, displayall=displayall), fm
277
277
278 def stub_timer(fm, func, title=None):
278 def stub_timer(fm, func, setup=None, title=None):
279 func()
279 func()
280
280
281 @contextlib.contextmanager
281 @contextlib.contextmanager
282 def timeone():
282 def timeone():
283 r = []
283 r = []
284 ostart = os.times()
284 ostart = os.times()
285 cstart = util.timer()
285 cstart = util.timer()
286 yield r
286 yield r
287 cstop = util.timer()
287 cstop = util.timer()
288 ostop = os.times()
288 ostop = os.times()
289 a, b = ostart, ostop
289 a, b = ostart, ostop
290 r.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
290 r.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
291
291
292 def _timer(fm, func, title=None, displayall=False):
292 def _timer(fm, func, setup=None, title=None, displayall=False):
293 gc.collect()
293 gc.collect()
294 results = []
294 results = []
295 begin = util.timer()
295 begin = util.timer()
296 count = 0
296 count = 0
297 while True:
297 while True:
298 if setup is not None:
299 setup()
298 with timeone() as item:
300 with timeone() as item:
299 r = func()
301 r = func()
300 count += 1
302 count += 1
301 results.append(item[0])
303 results.append(item[0])
302 cstop = util.timer()
304 cstop = util.timer()
303 if cstop - begin > 3 and count >= 100:
305 if cstop - begin > 3 and count >= 100:
304 break
306 break
305 if cstop - begin > 10 and count >= 3:
307 if cstop - begin > 10 and count >= 3:
306 break
308 break
307
309
308 formatone(fm, results, title=title, result=r,
310 formatone(fm, results, title=title, result=r,
309 displayall=displayall)
311 displayall=displayall)
310
312
311 def formatone(fm, timings, title=None, result=None, displayall=False):
313 def formatone(fm, timings, title=None, result=None, displayall=False):
312
314
313 count = len(timings)
315 count = len(timings)
314
316
315 fm.startitem()
317 fm.startitem()
316
318
317 if title:
319 if title:
318 fm.write(b'title', b'! %s\n', title)
320 fm.write(b'title', b'! %s\n', title)
319 if result:
321 if result:
320 fm.write(b'result', b'! result: %s\n', result)
322 fm.write(b'result', b'! result: %s\n', result)
321 def display(role, entry):
323 def display(role, entry):
322 prefix = b''
324 prefix = b''
323 if role != b'best':
325 if role != b'best':
324 prefix = b'%s.' % role
326 prefix = b'%s.' % role
325 fm.plain(b'!')
327 fm.plain(b'!')
326 fm.write(prefix + b'wall', b' wall %f', entry[0])
328 fm.write(prefix + b'wall', b' wall %f', entry[0])
327 fm.write(prefix + b'comb', b' comb %f', entry[1] + entry[2])
329 fm.write(prefix + b'comb', b' comb %f', entry[1] + entry[2])
328 fm.write(prefix + b'user', b' user %f', entry[1])
330 fm.write(prefix + b'user', b' user %f', entry[1])
329 fm.write(prefix + b'sys', b' sys %f', entry[2])
331 fm.write(prefix + b'sys', b' sys %f', entry[2])
330 fm.write(prefix + b'count', b' (%s of %%d)' % role, count)
332 fm.write(prefix + b'count', b' (%s of %%d)' % role, count)
331 fm.plain(b'\n')
333 fm.plain(b'\n')
332 timings.sort()
334 timings.sort()
333 min_val = timings[0]
335 min_val = timings[0]
334 display(b'best', min_val)
336 display(b'best', min_val)
335 if displayall:
337 if displayall:
336 max_val = timings[-1]
338 max_val = timings[-1]
337 display(b'max', max_val)
339 display(b'max', max_val)
338 avg = tuple([sum(x) / count for x in zip(*timings)])
340 avg = tuple([sum(x) / count for x in zip(*timings)])
339 display(b'avg', avg)
341 display(b'avg', avg)
340 median = timings[len(timings) // 2]
342 median = timings[len(timings) // 2]
341 display(b'median', median)
343 display(b'median', median)
342
344
343 # utilities for historical portability
345 # utilities for historical portability
344
346
345 def getint(ui, section, name, default):
347 def getint(ui, section, name, default):
346 # for "historical portability":
348 # for "historical portability":
347 # ui.configint has been available since 1.9 (or fa2b596db182)
349 # ui.configint has been available since 1.9 (or fa2b596db182)
348 v = ui.config(section, name, None)
350 v = ui.config(section, name, None)
349 if v is None:
351 if v is None:
350 return default
352 return default
351 try:
353 try:
352 return int(v)
354 return int(v)
353 except ValueError:
355 except ValueError:
354 raise error.ConfigError((b"%s.%s is not an integer ('%s')")
356 raise error.ConfigError((b"%s.%s is not an integer ('%s')")
355 % (section, name, v))
357 % (section, name, v))
356
358
357 def safeattrsetter(obj, name, ignoremissing=False):
359 def safeattrsetter(obj, name, ignoremissing=False):
358 """Ensure that 'obj' has 'name' attribute before subsequent setattr
360 """Ensure that 'obj' has 'name' attribute before subsequent setattr
359
361
360 This function is aborted, if 'obj' doesn't have 'name' attribute
362 This function is aborted, if 'obj' doesn't have 'name' attribute
361 at runtime. This avoids overlooking removal of an attribute, which
363 at runtime. This avoids overlooking removal of an attribute, which
362 breaks assumption of performance measurement, in the future.
364 breaks assumption of performance measurement, in the future.
363
365
364 This function returns the object to (1) assign a new value, and
366 This function returns the object to (1) assign a new value, and
365 (2) restore an original value to the attribute.
367 (2) restore an original value to the attribute.
366
368
367 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
369 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
368 abortion, and this function returns None. This is useful to
370 abortion, and this function returns None. This is useful to
369 examine an attribute, which isn't ensured in all Mercurial
371 examine an attribute, which isn't ensured in all Mercurial
370 versions.
372 versions.
371 """
373 """
372 if not util.safehasattr(obj, name):
374 if not util.safehasattr(obj, name):
373 if ignoremissing:
375 if ignoremissing:
374 return None
376 return None
375 raise error.Abort((b"missing attribute %s of %s might break assumption"
377 raise error.Abort((b"missing attribute %s of %s might break assumption"
376 b" of performance measurement") % (name, obj))
378 b" of performance measurement") % (name, obj))
377
379
378 origvalue = getattr(obj, _sysstr(name))
380 origvalue = getattr(obj, _sysstr(name))
379 class attrutil(object):
381 class attrutil(object):
380 def set(self, newvalue):
382 def set(self, newvalue):
381 setattr(obj, _sysstr(name), newvalue)
383 setattr(obj, _sysstr(name), newvalue)
382 def restore(self):
384 def restore(self):
383 setattr(obj, _sysstr(name), origvalue)
385 setattr(obj, _sysstr(name), origvalue)
384
386
385 return attrutil()
387 return attrutil()
386
388
387 # utilities to examine each internal API changes
389 # utilities to examine each internal API changes
388
390
389 def getbranchmapsubsettable():
391 def getbranchmapsubsettable():
390 # for "historical portability":
392 # for "historical portability":
391 # subsettable is defined in:
393 # subsettable is defined in:
392 # - branchmap since 2.9 (or 175c6fd8cacc)
394 # - branchmap since 2.9 (or 175c6fd8cacc)
393 # - repoview since 2.5 (or 59a9f18d4587)
395 # - repoview since 2.5 (or 59a9f18d4587)
394 for mod in (branchmap, repoview):
396 for mod in (branchmap, repoview):
395 subsettable = getattr(mod, 'subsettable', None)
397 subsettable = getattr(mod, 'subsettable', None)
396 if subsettable:
398 if subsettable:
397 return subsettable
399 return subsettable
398
400
399 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
401 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
400 # branchmap and repoview modules exist, but subsettable attribute
402 # branchmap and repoview modules exist, but subsettable attribute
401 # doesn't)
403 # doesn't)
402 raise error.Abort((b"perfbranchmap not available with this Mercurial"),
404 raise error.Abort((b"perfbranchmap not available with this Mercurial"),
403 hint=b"use 2.5 or later")
405 hint=b"use 2.5 or later")
404
406
405 def getsvfs(repo):
407 def getsvfs(repo):
406 """Return appropriate object to access files under .hg/store
408 """Return appropriate object to access files under .hg/store
407 """
409 """
408 # for "historical portability":
410 # for "historical portability":
409 # repo.svfs has been available since 2.3 (or 7034365089bf)
411 # repo.svfs has been available since 2.3 (or 7034365089bf)
410 svfs = getattr(repo, 'svfs', None)
412 svfs = getattr(repo, 'svfs', None)
411 if svfs:
413 if svfs:
412 return svfs
414 return svfs
413 else:
415 else:
414 return getattr(repo, 'sopener')
416 return getattr(repo, 'sopener')
415
417
416 def getvfs(repo):
418 def getvfs(repo):
417 """Return appropriate object to access files under .hg
419 """Return appropriate object to access files under .hg
418 """
420 """
419 # for "historical portability":
421 # for "historical portability":
420 # repo.vfs has been available since 2.3 (or 7034365089bf)
422 # repo.vfs has been available since 2.3 (or 7034365089bf)
421 vfs = getattr(repo, 'vfs', None)
423 vfs = getattr(repo, 'vfs', None)
422 if vfs:
424 if vfs:
423 return vfs
425 return vfs
424 else:
426 else:
425 return getattr(repo, 'opener')
427 return getattr(repo, 'opener')
426
428
427 def repocleartagscachefunc(repo):
429 def repocleartagscachefunc(repo):
428 """Return the function to clear tags cache according to repo internal API
430 """Return the function to clear tags cache according to repo internal API
429 """
431 """
430 if util.safehasattr(repo, b'_tagscache'): # since 2.0 (or 9dca7653b525)
432 if util.safehasattr(repo, b'_tagscache'): # since 2.0 (or 9dca7653b525)
431 # in this case, setattr(repo, '_tagscache', None) or so isn't
433 # in this case, setattr(repo, '_tagscache', None) or so isn't
432 # correct way to clear tags cache, because existing code paths
434 # correct way to clear tags cache, because existing code paths
433 # expect _tagscache to be a structured object.
435 # expect _tagscache to be a structured object.
434 def clearcache():
436 def clearcache():
435 # _tagscache has been filteredpropertycache since 2.5 (or
437 # _tagscache has been filteredpropertycache since 2.5 (or
436 # 98c867ac1330), and delattr() can't work in such case
438 # 98c867ac1330), and delattr() can't work in such case
437 if b'_tagscache' in vars(repo):
439 if b'_tagscache' in vars(repo):
438 del repo.__dict__[b'_tagscache']
440 del repo.__dict__[b'_tagscache']
439 return clearcache
441 return clearcache
440
442
441 repotags = safeattrsetter(repo, b'_tags', ignoremissing=True)
443 repotags = safeattrsetter(repo, b'_tags', ignoremissing=True)
442 if repotags: # since 1.4 (or 5614a628d173)
444 if repotags: # since 1.4 (or 5614a628d173)
443 return lambda : repotags.set(None)
445 return lambda : repotags.set(None)
444
446
445 repotagscache = safeattrsetter(repo, b'tagscache', ignoremissing=True)
447 repotagscache = safeattrsetter(repo, b'tagscache', ignoremissing=True)
446 if repotagscache: # since 0.6 (or d7df759d0e97)
448 if repotagscache: # since 0.6 (or d7df759d0e97)
447 return lambda : repotagscache.set(None)
449 return lambda : repotagscache.set(None)
448
450
449 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
451 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
450 # this point, but it isn't so problematic, because:
452 # this point, but it isn't so problematic, because:
451 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
453 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
452 # in perftags() causes failure soon
454 # in perftags() causes failure soon
453 # - perf.py itself has been available since 1.1 (or eb240755386d)
455 # - perf.py itself has been available since 1.1 (or eb240755386d)
454 raise error.Abort((b"tags API of this hg command is unknown"))
456 raise error.Abort((b"tags API of this hg command is unknown"))
455
457
456 # utilities to clear cache
458 # utilities to clear cache
457
459
458 def clearfilecache(repo, attrname):
460 def clearfilecache(repo, attrname):
459 unfi = repo.unfiltered()
461 unfi = repo.unfiltered()
460 if attrname in vars(unfi):
462 if attrname in vars(unfi):
461 delattr(unfi, attrname)
463 delattr(unfi, attrname)
462 unfi._filecache.pop(attrname, None)
464 unfi._filecache.pop(attrname, None)
463
465
464 # perf commands
466 # perf commands
465
467
466 @command(b'perfwalk', formatteropts)
468 @command(b'perfwalk', formatteropts)
467 def perfwalk(ui, repo, *pats, **opts):
469 def perfwalk(ui, repo, *pats, **opts):
468 opts = _byteskwargs(opts)
470 opts = _byteskwargs(opts)
469 timer, fm = gettimer(ui, opts)
471 timer, fm = gettimer(ui, opts)
470 m = scmutil.match(repo[None], pats, {})
472 m = scmutil.match(repo[None], pats, {})
471 timer(lambda: len(list(repo.dirstate.walk(m, subrepos=[], unknown=True,
473 timer(lambda: len(list(repo.dirstate.walk(m, subrepos=[], unknown=True,
472 ignored=False))))
474 ignored=False))))
473 fm.end()
475 fm.end()
474
476
475 @command(b'perfannotate', formatteropts)
477 @command(b'perfannotate', formatteropts)
476 def perfannotate(ui, repo, f, **opts):
478 def perfannotate(ui, repo, f, **opts):
477 opts = _byteskwargs(opts)
479 opts = _byteskwargs(opts)
478 timer, fm = gettimer(ui, opts)
480 timer, fm = gettimer(ui, opts)
479 fc = repo[b'.'][f]
481 fc = repo[b'.'][f]
480 timer(lambda: len(fc.annotate(True)))
482 timer(lambda: len(fc.annotate(True)))
481 fm.end()
483 fm.end()
482
484
483 @command(b'perfstatus',
485 @command(b'perfstatus',
484 [(b'u', b'unknown', False,
486 [(b'u', b'unknown', False,
485 b'ask status to look for unknown files')] + formatteropts)
487 b'ask status to look for unknown files')] + formatteropts)
486 def perfstatus(ui, repo, **opts):
488 def perfstatus(ui, repo, **opts):
487 opts = _byteskwargs(opts)
489 opts = _byteskwargs(opts)
488 #m = match.always(repo.root, repo.getcwd())
490 #m = match.always(repo.root, repo.getcwd())
489 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
491 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
490 # False))))
492 # False))))
491 timer, fm = gettimer(ui, opts)
493 timer, fm = gettimer(ui, opts)
492 timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown']))))
494 timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown']))))
493 fm.end()
495 fm.end()
494
496
495 @command(b'perfaddremove', formatteropts)
497 @command(b'perfaddremove', formatteropts)
496 def perfaddremove(ui, repo, **opts):
498 def perfaddremove(ui, repo, **opts):
497 opts = _byteskwargs(opts)
499 opts = _byteskwargs(opts)
498 timer, fm = gettimer(ui, opts)
500 timer, fm = gettimer(ui, opts)
499 try:
501 try:
500 oldquiet = repo.ui.quiet
502 oldquiet = repo.ui.quiet
501 repo.ui.quiet = True
503 repo.ui.quiet = True
502 matcher = scmutil.match(repo[None])
504 matcher = scmutil.match(repo[None])
503 opts[b'dry_run'] = True
505 opts[b'dry_run'] = True
504 timer(lambda: scmutil.addremove(repo, matcher, b"", opts))
506 timer(lambda: scmutil.addremove(repo, matcher, b"", opts))
505 finally:
507 finally:
506 repo.ui.quiet = oldquiet
508 repo.ui.quiet = oldquiet
507 fm.end()
509 fm.end()
508
510
509 def clearcaches(cl):
511 def clearcaches(cl):
510 # behave somewhat consistently across internal API changes
512 # behave somewhat consistently across internal API changes
511 if util.safehasattr(cl, b'clearcaches'):
513 if util.safehasattr(cl, b'clearcaches'):
512 cl.clearcaches()
514 cl.clearcaches()
513 elif util.safehasattr(cl, b'_nodecache'):
515 elif util.safehasattr(cl, b'_nodecache'):
514 from mercurial.node import nullid, nullrev
516 from mercurial.node import nullid, nullrev
515 cl._nodecache = {nullid: nullrev}
517 cl._nodecache = {nullid: nullrev}
516 cl._nodepos = None
518 cl._nodepos = None
517
519
518 @command(b'perfheads', formatteropts)
520 @command(b'perfheads', formatteropts)
519 def perfheads(ui, repo, **opts):
521 def perfheads(ui, repo, **opts):
520 opts = _byteskwargs(opts)
522 opts = _byteskwargs(opts)
521 timer, fm = gettimer(ui, opts)
523 timer, fm = gettimer(ui, opts)
522 cl = repo.changelog
524 cl = repo.changelog
523 def d():
525 def d():
524 len(cl.headrevs())
526 len(cl.headrevs())
525 clearcaches(cl)
527 clearcaches(cl)
526 timer(d)
528 timer(d)
527 fm.end()
529 fm.end()
528
530
529 @command(b'perftags', formatteropts)
531 @command(b'perftags', formatteropts)
530 def perftags(ui, repo, **opts):
532 def perftags(ui, repo, **opts):
531 import mercurial.changelog
533 import mercurial.changelog
532 import mercurial.manifest
534 import mercurial.manifest
533
535
534 opts = _byteskwargs(opts)
536 opts = _byteskwargs(opts)
535 timer, fm = gettimer(ui, opts)
537 timer, fm = gettimer(ui, opts)
536 svfs = getsvfs(repo)
538 svfs = getsvfs(repo)
537 repocleartagscache = repocleartagscachefunc(repo)
539 repocleartagscache = repocleartagscachefunc(repo)
538 def t():
540 def t():
539 repo.changelog = mercurial.changelog.changelog(svfs)
541 repo.changelog = mercurial.changelog.changelog(svfs)
540 rootmanifest = mercurial.manifest.manifestrevlog(svfs)
542 rootmanifest = mercurial.manifest.manifestrevlog(svfs)
541 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo,
543 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo,
542 rootmanifest)
544 rootmanifest)
543 repocleartagscache()
545 repocleartagscache()
544 return len(repo.tags())
546 return len(repo.tags())
545 timer(t)
547 timer(t)
546 fm.end()
548 fm.end()
547
549
548 @command(b'perfancestors', formatteropts)
550 @command(b'perfancestors', formatteropts)
549 def perfancestors(ui, repo, **opts):
551 def perfancestors(ui, repo, **opts):
550 opts = _byteskwargs(opts)
552 opts = _byteskwargs(opts)
551 timer, fm = gettimer(ui, opts)
553 timer, fm = gettimer(ui, opts)
552 heads = repo.changelog.headrevs()
554 heads = repo.changelog.headrevs()
553 def d():
555 def d():
554 for a in repo.changelog.ancestors(heads):
556 for a in repo.changelog.ancestors(heads):
555 pass
557 pass
556 timer(d)
558 timer(d)
557 fm.end()
559 fm.end()
558
560
559 @command(b'perfancestorset', formatteropts)
561 @command(b'perfancestorset', formatteropts)
560 def perfancestorset(ui, repo, revset, **opts):
562 def perfancestorset(ui, repo, revset, **opts):
561 opts = _byteskwargs(opts)
563 opts = _byteskwargs(opts)
562 timer, fm = gettimer(ui, opts)
564 timer, fm = gettimer(ui, opts)
563 revs = repo.revs(revset)
565 revs = repo.revs(revset)
564 heads = repo.changelog.headrevs()
566 heads = repo.changelog.headrevs()
565 def d():
567 def d():
566 s = repo.changelog.ancestors(heads)
568 s = repo.changelog.ancestors(heads)
567 for rev in revs:
569 for rev in revs:
568 rev in s
570 rev in s
569 timer(d)
571 timer(d)
570 fm.end()
572 fm.end()
571
573
572 @command(b'perfbookmarks', formatteropts)
574 @command(b'perfbookmarks', formatteropts)
573 def perfbookmarks(ui, repo, **opts):
575 def perfbookmarks(ui, repo, **opts):
574 """benchmark parsing bookmarks from disk to memory"""
576 """benchmark parsing bookmarks from disk to memory"""
575 opts = _byteskwargs(opts)
577 opts = _byteskwargs(opts)
576 timer, fm = gettimer(ui, opts)
578 timer, fm = gettimer(ui, opts)
577 def d():
579 def d():
578 clearfilecache(repo, b'_bookmarks')
580 clearfilecache(repo, b'_bookmarks')
579 repo._bookmarks
581 repo._bookmarks
580 timer(d)
582 timer(d)
581 fm.end()
583 fm.end()
582
584
583 @command(b'perfbundleread', formatteropts, b'BUNDLE')
585 @command(b'perfbundleread', formatteropts, b'BUNDLE')
584 def perfbundleread(ui, repo, bundlepath, **opts):
586 def perfbundleread(ui, repo, bundlepath, **opts):
585 """Benchmark reading of bundle files.
587 """Benchmark reading of bundle files.
586
588
587 This command is meant to isolate the I/O part of bundle reading as
589 This command is meant to isolate the I/O part of bundle reading as
588 much as possible.
590 much as possible.
589 """
591 """
590 from mercurial import (
592 from mercurial import (
591 bundle2,
593 bundle2,
592 exchange,
594 exchange,
593 streamclone,
595 streamclone,
594 )
596 )
595
597
596 opts = _byteskwargs(opts)
598 opts = _byteskwargs(opts)
597
599
598 def makebench(fn):
600 def makebench(fn):
599 def run():
601 def run():
600 with open(bundlepath, b'rb') as fh:
602 with open(bundlepath, b'rb') as fh:
601 bundle = exchange.readbundle(ui, fh, bundlepath)
603 bundle = exchange.readbundle(ui, fh, bundlepath)
602 fn(bundle)
604 fn(bundle)
603
605
604 return run
606 return run
605
607
606 def makereadnbytes(size):
608 def makereadnbytes(size):
607 def run():
609 def run():
608 with open(bundlepath, b'rb') as fh:
610 with open(bundlepath, b'rb') as fh:
609 bundle = exchange.readbundle(ui, fh, bundlepath)
611 bundle = exchange.readbundle(ui, fh, bundlepath)
610 while bundle.read(size):
612 while bundle.read(size):
611 pass
613 pass
612
614
613 return run
615 return run
614
616
615 def makestdioread(size):
617 def makestdioread(size):
616 def run():
618 def run():
617 with open(bundlepath, b'rb') as fh:
619 with open(bundlepath, b'rb') as fh:
618 while fh.read(size):
620 while fh.read(size):
619 pass
621 pass
620
622
621 return run
623 return run
622
624
623 # bundle1
625 # bundle1
624
626
625 def deltaiter(bundle):
627 def deltaiter(bundle):
626 for delta in bundle.deltaiter():
628 for delta in bundle.deltaiter():
627 pass
629 pass
628
630
629 def iterchunks(bundle):
631 def iterchunks(bundle):
630 for chunk in bundle.getchunks():
632 for chunk in bundle.getchunks():
631 pass
633 pass
632
634
633 # bundle2
635 # bundle2
634
636
635 def forwardchunks(bundle):
637 def forwardchunks(bundle):
636 for chunk in bundle._forwardchunks():
638 for chunk in bundle._forwardchunks():
637 pass
639 pass
638
640
639 def iterparts(bundle):
641 def iterparts(bundle):
640 for part in bundle.iterparts():
642 for part in bundle.iterparts():
641 pass
643 pass
642
644
643 def iterpartsseekable(bundle):
645 def iterpartsseekable(bundle):
644 for part in bundle.iterparts(seekable=True):
646 for part in bundle.iterparts(seekable=True):
645 pass
647 pass
646
648
647 def seek(bundle):
649 def seek(bundle):
648 for part in bundle.iterparts(seekable=True):
650 for part in bundle.iterparts(seekable=True):
649 part.seek(0, os.SEEK_END)
651 part.seek(0, os.SEEK_END)
650
652
651 def makepartreadnbytes(size):
653 def makepartreadnbytes(size):
652 def run():
654 def run():
653 with open(bundlepath, b'rb') as fh:
655 with open(bundlepath, b'rb') as fh:
654 bundle = exchange.readbundle(ui, fh, bundlepath)
656 bundle = exchange.readbundle(ui, fh, bundlepath)
655 for part in bundle.iterparts():
657 for part in bundle.iterparts():
656 while part.read(size):
658 while part.read(size):
657 pass
659 pass
658
660
659 return run
661 return run
660
662
661 benches = [
663 benches = [
662 (makestdioread(8192), b'read(8k)'),
664 (makestdioread(8192), b'read(8k)'),
663 (makestdioread(16384), b'read(16k)'),
665 (makestdioread(16384), b'read(16k)'),
664 (makestdioread(32768), b'read(32k)'),
666 (makestdioread(32768), b'read(32k)'),
665 (makestdioread(131072), b'read(128k)'),
667 (makestdioread(131072), b'read(128k)'),
666 ]
668 ]
667
669
668 with open(bundlepath, b'rb') as fh:
670 with open(bundlepath, b'rb') as fh:
669 bundle = exchange.readbundle(ui, fh, bundlepath)
671 bundle = exchange.readbundle(ui, fh, bundlepath)
670
672
671 if isinstance(bundle, changegroup.cg1unpacker):
673 if isinstance(bundle, changegroup.cg1unpacker):
672 benches.extend([
674 benches.extend([
673 (makebench(deltaiter), b'cg1 deltaiter()'),
675 (makebench(deltaiter), b'cg1 deltaiter()'),
674 (makebench(iterchunks), b'cg1 getchunks()'),
676 (makebench(iterchunks), b'cg1 getchunks()'),
675 (makereadnbytes(8192), b'cg1 read(8k)'),
677 (makereadnbytes(8192), b'cg1 read(8k)'),
676 (makereadnbytes(16384), b'cg1 read(16k)'),
678 (makereadnbytes(16384), b'cg1 read(16k)'),
677 (makereadnbytes(32768), b'cg1 read(32k)'),
679 (makereadnbytes(32768), b'cg1 read(32k)'),
678 (makereadnbytes(131072), b'cg1 read(128k)'),
680 (makereadnbytes(131072), b'cg1 read(128k)'),
679 ])
681 ])
680 elif isinstance(bundle, bundle2.unbundle20):
682 elif isinstance(bundle, bundle2.unbundle20):
681 benches.extend([
683 benches.extend([
682 (makebench(forwardchunks), b'bundle2 forwardchunks()'),
684 (makebench(forwardchunks), b'bundle2 forwardchunks()'),
683 (makebench(iterparts), b'bundle2 iterparts()'),
685 (makebench(iterparts), b'bundle2 iterparts()'),
684 (makebench(iterpartsseekable), b'bundle2 iterparts() seekable'),
686 (makebench(iterpartsseekable), b'bundle2 iterparts() seekable'),
685 (makebench(seek), b'bundle2 part seek()'),
687 (makebench(seek), b'bundle2 part seek()'),
686 (makepartreadnbytes(8192), b'bundle2 part read(8k)'),
688 (makepartreadnbytes(8192), b'bundle2 part read(8k)'),
687 (makepartreadnbytes(16384), b'bundle2 part read(16k)'),
689 (makepartreadnbytes(16384), b'bundle2 part read(16k)'),
688 (makepartreadnbytes(32768), b'bundle2 part read(32k)'),
690 (makepartreadnbytes(32768), b'bundle2 part read(32k)'),
689 (makepartreadnbytes(131072), b'bundle2 part read(128k)'),
691 (makepartreadnbytes(131072), b'bundle2 part read(128k)'),
690 ])
692 ])
691 elif isinstance(bundle, streamclone.streamcloneapplier):
693 elif isinstance(bundle, streamclone.streamcloneapplier):
692 raise error.Abort(b'stream clone bundles not supported')
694 raise error.Abort(b'stream clone bundles not supported')
693 else:
695 else:
694 raise error.Abort(b'unhandled bundle type: %s' % type(bundle))
696 raise error.Abort(b'unhandled bundle type: %s' % type(bundle))
695
697
696 for fn, title in benches:
698 for fn, title in benches:
697 timer, fm = gettimer(ui, opts)
699 timer, fm = gettimer(ui, opts)
698 timer(fn, title=title)
700 timer(fn, title=title)
699 fm.end()
701 fm.end()
700
702
701 @command(b'perfchangegroupchangelog', formatteropts +
703 @command(b'perfchangegroupchangelog', formatteropts +
702 [(b'', b'version', b'02', b'changegroup version'),
704 [(b'', b'version', b'02', b'changegroup version'),
703 (b'r', b'rev', b'', b'revisions to add to changegroup')])
705 (b'r', b'rev', b'', b'revisions to add to changegroup')])
704 def perfchangegroupchangelog(ui, repo, version=b'02', rev=None, **opts):
706 def perfchangegroupchangelog(ui, repo, version=b'02', rev=None, **opts):
705 """Benchmark producing a changelog group for a changegroup.
707 """Benchmark producing a changelog group for a changegroup.
706
708
707 This measures the time spent processing the changelog during a
709 This measures the time spent processing the changelog during a
708 bundle operation. This occurs during `hg bundle` and on a server
710 bundle operation. This occurs during `hg bundle` and on a server
709 processing a `getbundle` wire protocol request (handles clones
711 processing a `getbundle` wire protocol request (handles clones
710 and pull requests).
712 and pull requests).
711
713
712 By default, all revisions are added to the changegroup.
714 By default, all revisions are added to the changegroup.
713 """
715 """
714 opts = _byteskwargs(opts)
716 opts = _byteskwargs(opts)
715 cl = repo.changelog
717 cl = repo.changelog
716 nodes = [cl.lookup(r) for r in repo.revs(rev or b'all()')]
718 nodes = [cl.lookup(r) for r in repo.revs(rev or b'all()')]
717 bundler = changegroup.getbundler(version, repo)
719 bundler = changegroup.getbundler(version, repo)
718
720
719 def d():
721 def d():
720 state, chunks = bundler._generatechangelog(cl, nodes)
722 state, chunks = bundler._generatechangelog(cl, nodes)
721 for chunk in chunks:
723 for chunk in chunks:
722 pass
724 pass
723
725
724 timer, fm = gettimer(ui, opts)
726 timer, fm = gettimer(ui, opts)
725
727
726 # Terminal printing can interfere with timing. So disable it.
728 # Terminal printing can interfere with timing. So disable it.
727 with ui.configoverride({(b'progress', b'disable'): True}):
729 with ui.configoverride({(b'progress', b'disable'): True}):
728 timer(d)
730 timer(d)
729
731
730 fm.end()
732 fm.end()
731
733
732 @command(b'perfdirs', formatteropts)
734 @command(b'perfdirs', formatteropts)
733 def perfdirs(ui, repo, **opts):
735 def perfdirs(ui, repo, **opts):
734 opts = _byteskwargs(opts)
736 opts = _byteskwargs(opts)
735 timer, fm = gettimer(ui, opts)
737 timer, fm = gettimer(ui, opts)
736 dirstate = repo.dirstate
738 dirstate = repo.dirstate
737 b'a' in dirstate
739 b'a' in dirstate
738 def d():
740 def d():
739 dirstate.hasdir(b'a')
741 dirstate.hasdir(b'a')
740 del dirstate._map._dirs
742 del dirstate._map._dirs
741 timer(d)
743 timer(d)
742 fm.end()
744 fm.end()
743
745
744 @command(b'perfdirstate', formatteropts)
746 @command(b'perfdirstate', formatteropts)
745 def perfdirstate(ui, repo, **opts):
747 def perfdirstate(ui, repo, **opts):
746 opts = _byteskwargs(opts)
748 opts = _byteskwargs(opts)
747 timer, fm = gettimer(ui, opts)
749 timer, fm = gettimer(ui, opts)
748 b"a" in repo.dirstate
750 b"a" in repo.dirstate
749 def d():
751 def d():
750 repo.dirstate.invalidate()
752 repo.dirstate.invalidate()
751 b"a" in repo.dirstate
753 b"a" in repo.dirstate
752 timer(d)
754 timer(d)
753 fm.end()
755 fm.end()
754
756
755 @command(b'perfdirstatedirs', formatteropts)
757 @command(b'perfdirstatedirs', formatteropts)
756 def perfdirstatedirs(ui, repo, **opts):
758 def perfdirstatedirs(ui, repo, **opts):
757 opts = _byteskwargs(opts)
759 opts = _byteskwargs(opts)
758 timer, fm = gettimer(ui, opts)
760 timer, fm = gettimer(ui, opts)
759 b"a" in repo.dirstate
761 b"a" in repo.dirstate
760 def d():
762 def d():
761 repo.dirstate.hasdir(b"a")
763 repo.dirstate.hasdir(b"a")
762 del repo.dirstate._map._dirs
764 del repo.dirstate._map._dirs
763 timer(d)
765 timer(d)
764 fm.end()
766 fm.end()
765
767
766 @command(b'perfdirstatefoldmap', formatteropts)
768 @command(b'perfdirstatefoldmap', formatteropts)
767 def perfdirstatefoldmap(ui, repo, **opts):
769 def perfdirstatefoldmap(ui, repo, **opts):
768 opts = _byteskwargs(opts)
770 opts = _byteskwargs(opts)
769 timer, fm = gettimer(ui, opts)
771 timer, fm = gettimer(ui, opts)
770 dirstate = repo.dirstate
772 dirstate = repo.dirstate
771 b'a' in dirstate
773 b'a' in dirstate
772 def d():
774 def d():
773 dirstate._map.filefoldmap.get(b'a')
775 dirstate._map.filefoldmap.get(b'a')
774 del dirstate._map.filefoldmap
776 del dirstate._map.filefoldmap
775 timer(d)
777 timer(d)
776 fm.end()
778 fm.end()
777
779
778 @command(b'perfdirfoldmap', formatteropts)
780 @command(b'perfdirfoldmap', formatteropts)
779 def perfdirfoldmap(ui, repo, **opts):
781 def perfdirfoldmap(ui, repo, **opts):
780 opts = _byteskwargs(opts)
782 opts = _byteskwargs(opts)
781 timer, fm = gettimer(ui, opts)
783 timer, fm = gettimer(ui, opts)
782 dirstate = repo.dirstate
784 dirstate = repo.dirstate
783 b'a' in dirstate
785 b'a' in dirstate
784 def d():
786 def d():
785 dirstate._map.dirfoldmap.get(b'a')
787 dirstate._map.dirfoldmap.get(b'a')
786 del dirstate._map.dirfoldmap
788 del dirstate._map.dirfoldmap
787 del dirstate._map._dirs
789 del dirstate._map._dirs
788 timer(d)
790 timer(d)
789 fm.end()
791 fm.end()
790
792
791 @command(b'perfdirstatewrite', formatteropts)
793 @command(b'perfdirstatewrite', formatteropts)
792 def perfdirstatewrite(ui, repo, **opts):
794 def perfdirstatewrite(ui, repo, **opts):
793 opts = _byteskwargs(opts)
795 opts = _byteskwargs(opts)
794 timer, fm = gettimer(ui, opts)
796 timer, fm = gettimer(ui, opts)
795 ds = repo.dirstate
797 ds = repo.dirstate
796 b"a" in ds
798 b"a" in ds
797 def d():
799 def d():
798 ds._dirty = True
800 ds._dirty = True
799 ds.write(repo.currenttransaction())
801 ds.write(repo.currenttransaction())
800 timer(d)
802 timer(d)
801 fm.end()
803 fm.end()
802
804
803 @command(b'perfmergecalculate',
805 @command(b'perfmergecalculate',
804 [(b'r', b'rev', b'.', b'rev to merge against')] + formatteropts)
806 [(b'r', b'rev', b'.', b'rev to merge against')] + formatteropts)
805 def perfmergecalculate(ui, repo, rev, **opts):
807 def perfmergecalculate(ui, repo, rev, **opts):
806 opts = _byteskwargs(opts)
808 opts = _byteskwargs(opts)
807 timer, fm = gettimer(ui, opts)
809 timer, fm = gettimer(ui, opts)
808 wctx = repo[None]
810 wctx = repo[None]
809 rctx = scmutil.revsingle(repo, rev, rev)
811 rctx = scmutil.revsingle(repo, rev, rev)
810 ancestor = wctx.ancestor(rctx)
812 ancestor = wctx.ancestor(rctx)
811 # we don't want working dir files to be stat'd in the benchmark, so prime
813 # we don't want working dir files to be stat'd in the benchmark, so prime
812 # that cache
814 # that cache
813 wctx.dirty()
815 wctx.dirty()
814 def d():
816 def d():
815 # acceptremote is True because we don't want prompts in the middle of
817 # acceptremote is True because we don't want prompts in the middle of
816 # our benchmark
818 # our benchmark
817 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
819 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
818 acceptremote=True, followcopies=True)
820 acceptremote=True, followcopies=True)
819 timer(d)
821 timer(d)
820 fm.end()
822 fm.end()
821
823
822 @command(b'perfpathcopies', [], b"REV REV")
824 @command(b'perfpathcopies', [], b"REV REV")
823 def perfpathcopies(ui, repo, rev1, rev2, **opts):
825 def perfpathcopies(ui, repo, rev1, rev2, **opts):
824 opts = _byteskwargs(opts)
826 opts = _byteskwargs(opts)
825 timer, fm = gettimer(ui, opts)
827 timer, fm = gettimer(ui, opts)
826 ctx1 = scmutil.revsingle(repo, rev1, rev1)
828 ctx1 = scmutil.revsingle(repo, rev1, rev1)
827 ctx2 = scmutil.revsingle(repo, rev2, rev2)
829 ctx2 = scmutil.revsingle(repo, rev2, rev2)
828 def d():
830 def d():
829 copies.pathcopies(ctx1, ctx2)
831 copies.pathcopies(ctx1, ctx2)
830 timer(d)
832 timer(d)
831 fm.end()
833 fm.end()
832
834
833 @command(b'perfphases',
835 @command(b'perfphases',
834 [(b'', b'full', False, b'include file reading time too'),
836 [(b'', b'full', False, b'include file reading time too'),
835 ], b"")
837 ], b"")
836 def perfphases(ui, repo, **opts):
838 def perfphases(ui, repo, **opts):
837 """benchmark phasesets computation"""
839 """benchmark phasesets computation"""
838 opts = _byteskwargs(opts)
840 opts = _byteskwargs(opts)
839 timer, fm = gettimer(ui, opts)
841 timer, fm = gettimer(ui, opts)
840 _phases = repo._phasecache
842 _phases = repo._phasecache
841 full = opts.get(b'full')
843 full = opts.get(b'full')
842 def d():
844 def d():
843 phases = _phases
845 phases = _phases
844 if full:
846 if full:
845 clearfilecache(repo, b'_phasecache')
847 clearfilecache(repo, b'_phasecache')
846 phases = repo._phasecache
848 phases = repo._phasecache
847 phases.invalidate()
849 phases.invalidate()
848 phases.loadphaserevs(repo)
850 phases.loadphaserevs(repo)
849 timer(d)
851 timer(d)
850 fm.end()
852 fm.end()
851
853
852 @command(b'perfphasesremote',
854 @command(b'perfphasesremote',
853 [], b"[DEST]")
855 [], b"[DEST]")
854 def perfphasesremote(ui, repo, dest=None, **opts):
856 def perfphasesremote(ui, repo, dest=None, **opts):
855 """benchmark time needed to analyse phases of the remote server"""
857 """benchmark time needed to analyse phases of the remote server"""
856 from mercurial.node import (
858 from mercurial.node import (
857 bin,
859 bin,
858 )
860 )
859 from mercurial import (
861 from mercurial import (
860 exchange,
862 exchange,
861 hg,
863 hg,
862 phases,
864 phases,
863 )
865 )
864 opts = _byteskwargs(opts)
866 opts = _byteskwargs(opts)
865 timer, fm = gettimer(ui, opts)
867 timer, fm = gettimer(ui, opts)
866
868
867 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
869 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
868 if not path:
870 if not path:
869 raise error.Abort((b'default repository not configured!'),
871 raise error.Abort((b'default repository not configured!'),
870 hint=(b"see 'hg help config.paths'"))
872 hint=(b"see 'hg help config.paths'"))
871 dest = path.pushloc or path.loc
873 dest = path.pushloc or path.loc
872 branches = (path.branch, opts.get(b'branch') or [])
874 branches = (path.branch, opts.get(b'branch') or [])
873 ui.status((b'analysing phase of %s\n') % util.hidepassword(dest))
875 ui.status((b'analysing phase of %s\n') % util.hidepassword(dest))
874 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
876 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
875 other = hg.peer(repo, opts, dest)
877 other = hg.peer(repo, opts, dest)
876
878
877 # easier to perform discovery through the operation
879 # easier to perform discovery through the operation
878 op = exchange.pushoperation(repo, other)
880 op = exchange.pushoperation(repo, other)
879 exchange._pushdiscoverychangeset(op)
881 exchange._pushdiscoverychangeset(op)
880
882
881 remotesubset = op.fallbackheads
883 remotesubset = op.fallbackheads
882
884
883 with other.commandexecutor() as e:
885 with other.commandexecutor() as e:
884 remotephases = e.callcommand(b'listkeys',
886 remotephases = e.callcommand(b'listkeys',
885 {b'namespace': b'phases'}).result()
887 {b'namespace': b'phases'}).result()
886 del other
888 del other
887 publishing = remotephases.get(b'publishing', False)
889 publishing = remotephases.get(b'publishing', False)
888 if publishing:
890 if publishing:
889 ui.status((b'publishing: yes\n'))
891 ui.status((b'publishing: yes\n'))
890 else:
892 else:
891 ui.status((b'publishing: no\n'))
893 ui.status((b'publishing: no\n'))
892
894
893 nodemap = repo.changelog.nodemap
895 nodemap = repo.changelog.nodemap
894 nonpublishroots = 0
896 nonpublishroots = 0
895 for nhex, phase in remotephases.iteritems():
897 for nhex, phase in remotephases.iteritems():
896 if nhex == b'publishing': # ignore data related to publish option
898 if nhex == b'publishing': # ignore data related to publish option
897 continue
899 continue
898 node = bin(nhex)
900 node = bin(nhex)
899 if node in nodemap and int(phase):
901 if node in nodemap and int(phase):
900 nonpublishroots += 1
902 nonpublishroots += 1
901 ui.status((b'number of roots: %d\n') % len(remotephases))
903 ui.status((b'number of roots: %d\n') % len(remotephases))
902 ui.status((b'number of known non public roots: %d\n') % nonpublishroots)
904 ui.status((b'number of known non public roots: %d\n') % nonpublishroots)
903 def d():
905 def d():
904 phases.remotephasessummary(repo,
906 phases.remotephasessummary(repo,
905 remotesubset,
907 remotesubset,
906 remotephases)
908 remotephases)
907 timer(d)
909 timer(d)
908 fm.end()
910 fm.end()
909
911
910 @command(b'perfmanifest',[
912 @command(b'perfmanifest',[
911 (b'm', b'manifest-rev', False, b'Look up a manifest node revision'),
913 (b'm', b'manifest-rev', False, b'Look up a manifest node revision'),
912 (b'', b'clear-disk', False, b'clear on-disk caches too'),
914 (b'', b'clear-disk', False, b'clear on-disk caches too'),
913 ] + formatteropts, b'REV|NODE')
915 ] + formatteropts, b'REV|NODE')
914 def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts):
916 def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts):
915 """benchmark the time to read a manifest from disk and return a usable
917 """benchmark the time to read a manifest from disk and return a usable
916 dict-like object
918 dict-like object
917
919
918 Manifest caches are cleared before retrieval."""
920 Manifest caches are cleared before retrieval."""
919 opts = _byteskwargs(opts)
921 opts = _byteskwargs(opts)
920 timer, fm = gettimer(ui, opts)
922 timer, fm = gettimer(ui, opts)
921 if not manifest_rev:
923 if not manifest_rev:
922 ctx = scmutil.revsingle(repo, rev, rev)
924 ctx = scmutil.revsingle(repo, rev, rev)
923 t = ctx.manifestnode()
925 t = ctx.manifestnode()
924 else:
926 else:
925 from mercurial.node import bin
927 from mercurial.node import bin
926
928
927 if len(rev) == 40:
929 if len(rev) == 40:
928 t = bin(rev)
930 t = bin(rev)
929 else:
931 else:
930 try:
932 try:
931 rev = int(rev)
933 rev = int(rev)
932
934
933 if util.safehasattr(repo.manifestlog, b'getstorage'):
935 if util.safehasattr(repo.manifestlog, b'getstorage'):
934 t = repo.manifestlog.getstorage(b'').node(rev)
936 t = repo.manifestlog.getstorage(b'').node(rev)
935 else:
937 else:
936 t = repo.manifestlog._revlog.lookup(rev)
938 t = repo.manifestlog._revlog.lookup(rev)
937 except ValueError:
939 except ValueError:
938 raise error.Abort(b'manifest revision must be integer or full '
940 raise error.Abort(b'manifest revision must be integer or full '
939 b'node')
941 b'node')
940 def d():
942 def d():
941 repo.manifestlog.clearcaches(clear_persisted_data=clear_disk)
943 repo.manifestlog.clearcaches(clear_persisted_data=clear_disk)
942 repo.manifestlog[t].read()
944 repo.manifestlog[t].read()
943 timer(d)
945 timer(d)
944 fm.end()
946 fm.end()
945
947
946 @command(b'perfchangeset', formatteropts)
948 @command(b'perfchangeset', formatteropts)
947 def perfchangeset(ui, repo, rev, **opts):
949 def perfchangeset(ui, repo, rev, **opts):
948 opts = _byteskwargs(opts)
950 opts = _byteskwargs(opts)
949 timer, fm = gettimer(ui, opts)
951 timer, fm = gettimer(ui, opts)
950 n = scmutil.revsingle(repo, rev).node()
952 n = scmutil.revsingle(repo, rev).node()
951 def d():
953 def d():
952 repo.changelog.read(n)
954 repo.changelog.read(n)
953 #repo.changelog._cache = None
955 #repo.changelog._cache = None
954 timer(d)
956 timer(d)
955 fm.end()
957 fm.end()
956
958
957 @command(b'perfindex', formatteropts)
959 @command(b'perfindex', formatteropts)
958 def perfindex(ui, repo, **opts):
960 def perfindex(ui, repo, **opts):
959 import mercurial.revlog
961 import mercurial.revlog
960 opts = _byteskwargs(opts)
962 opts = _byteskwargs(opts)
961 timer, fm = gettimer(ui, opts)
963 timer, fm = gettimer(ui, opts)
962 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
964 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
963 n = repo[b"tip"].node()
965 n = repo[b"tip"].node()
964 svfs = getsvfs(repo)
966 svfs = getsvfs(repo)
965 def d():
967 def d():
966 cl = mercurial.revlog.revlog(svfs, b"00changelog.i")
968 cl = mercurial.revlog.revlog(svfs, b"00changelog.i")
967 cl.rev(n)
969 cl.rev(n)
968 timer(d)
970 timer(d)
969 fm.end()
971 fm.end()
970
972
971 @command(b'perfstartup', formatteropts)
973 @command(b'perfstartup', formatteropts)
972 def perfstartup(ui, repo, **opts):
974 def perfstartup(ui, repo, **opts):
973 opts = _byteskwargs(opts)
975 opts = _byteskwargs(opts)
974 timer, fm = gettimer(ui, opts)
976 timer, fm = gettimer(ui, opts)
975 def d():
977 def d():
976 if os.name != r'nt':
978 if os.name != r'nt':
977 os.system(b"HGRCPATH= %s version -q > /dev/null" %
979 os.system(b"HGRCPATH= %s version -q > /dev/null" %
978 fsencode(sys.argv[0]))
980 fsencode(sys.argv[0]))
979 else:
981 else:
980 os.environ[r'HGRCPATH'] = r' '
982 os.environ[r'HGRCPATH'] = r' '
981 os.system(r"%s version -q > NUL" % sys.argv[0])
983 os.system(r"%s version -q > NUL" % sys.argv[0])
982 timer(d)
984 timer(d)
983 fm.end()
985 fm.end()
984
986
985 @command(b'perfparents', formatteropts)
987 @command(b'perfparents', formatteropts)
986 def perfparents(ui, repo, **opts):
988 def perfparents(ui, repo, **opts):
987 opts = _byteskwargs(opts)
989 opts = _byteskwargs(opts)
988 timer, fm = gettimer(ui, opts)
990 timer, fm = gettimer(ui, opts)
989 # control the number of commits perfparents iterates over
991 # control the number of commits perfparents iterates over
990 # experimental config: perf.parentscount
992 # experimental config: perf.parentscount
991 count = getint(ui, b"perf", b"parentscount", 1000)
993 count = getint(ui, b"perf", b"parentscount", 1000)
992 if len(repo.changelog) < count:
994 if len(repo.changelog) < count:
993 raise error.Abort(b"repo needs %d commits for this test" % count)
995 raise error.Abort(b"repo needs %d commits for this test" % count)
994 repo = repo.unfiltered()
996 repo = repo.unfiltered()
995 nl = [repo.changelog.node(i) for i in _xrange(count)]
997 nl = [repo.changelog.node(i) for i in _xrange(count)]
996 def d():
998 def d():
997 for n in nl:
999 for n in nl:
998 repo.changelog.parents(n)
1000 repo.changelog.parents(n)
999 timer(d)
1001 timer(d)
1000 fm.end()
1002 fm.end()
1001
1003
1002 @command(b'perfctxfiles', formatteropts)
1004 @command(b'perfctxfiles', formatteropts)
1003 def perfctxfiles(ui, repo, x, **opts):
1005 def perfctxfiles(ui, repo, x, **opts):
1004 opts = _byteskwargs(opts)
1006 opts = _byteskwargs(opts)
1005 x = int(x)
1007 x = int(x)
1006 timer, fm = gettimer(ui, opts)
1008 timer, fm = gettimer(ui, opts)
1007 def d():
1009 def d():
1008 len(repo[x].files())
1010 len(repo[x].files())
1009 timer(d)
1011 timer(d)
1010 fm.end()
1012 fm.end()
1011
1013
1012 @command(b'perfrawfiles', formatteropts)
1014 @command(b'perfrawfiles', formatteropts)
1013 def perfrawfiles(ui, repo, x, **opts):
1015 def perfrawfiles(ui, repo, x, **opts):
1014 opts = _byteskwargs(opts)
1016 opts = _byteskwargs(opts)
1015 x = int(x)
1017 x = int(x)
1016 timer, fm = gettimer(ui, opts)
1018 timer, fm = gettimer(ui, opts)
1017 cl = repo.changelog
1019 cl = repo.changelog
1018 def d():
1020 def d():
1019 len(cl.read(x)[3])
1021 len(cl.read(x)[3])
1020 timer(d)
1022 timer(d)
1021 fm.end()
1023 fm.end()
1022
1024
1023 @command(b'perflookup', formatteropts)
1025 @command(b'perflookup', formatteropts)
1024 def perflookup(ui, repo, rev, **opts):
1026 def perflookup(ui, repo, rev, **opts):
1025 opts = _byteskwargs(opts)
1027 opts = _byteskwargs(opts)
1026 timer, fm = gettimer(ui, opts)
1028 timer, fm = gettimer(ui, opts)
1027 timer(lambda: len(repo.lookup(rev)))
1029 timer(lambda: len(repo.lookup(rev)))
1028 fm.end()
1030 fm.end()
1029
1031
1030 @command(b'perflinelogedits',
1032 @command(b'perflinelogedits',
1031 [(b'n', b'edits', 10000, b'number of edits'),
1033 [(b'n', b'edits', 10000, b'number of edits'),
1032 (b'', b'max-hunk-lines', 10, b'max lines in a hunk'),
1034 (b'', b'max-hunk-lines', 10, b'max lines in a hunk'),
1033 ], norepo=True)
1035 ], norepo=True)
1034 def perflinelogedits(ui, **opts):
1036 def perflinelogedits(ui, **opts):
1035 from mercurial import linelog
1037 from mercurial import linelog
1036
1038
1037 opts = _byteskwargs(opts)
1039 opts = _byteskwargs(opts)
1038
1040
1039 edits = opts[b'edits']
1041 edits = opts[b'edits']
1040 maxhunklines = opts[b'max_hunk_lines']
1042 maxhunklines = opts[b'max_hunk_lines']
1041
1043
1042 maxb1 = 100000
1044 maxb1 = 100000
1043 random.seed(0)
1045 random.seed(0)
1044 randint = random.randint
1046 randint = random.randint
1045 currentlines = 0
1047 currentlines = 0
1046 arglist = []
1048 arglist = []
1047 for rev in _xrange(edits):
1049 for rev in _xrange(edits):
1048 a1 = randint(0, currentlines)
1050 a1 = randint(0, currentlines)
1049 a2 = randint(a1, min(currentlines, a1 + maxhunklines))
1051 a2 = randint(a1, min(currentlines, a1 + maxhunklines))
1050 b1 = randint(0, maxb1)
1052 b1 = randint(0, maxb1)
1051 b2 = randint(b1, b1 + maxhunklines)
1053 b2 = randint(b1, b1 + maxhunklines)
1052 currentlines += (b2 - b1) - (a2 - a1)
1054 currentlines += (b2 - b1) - (a2 - a1)
1053 arglist.append((rev, a1, a2, b1, b2))
1055 arglist.append((rev, a1, a2, b1, b2))
1054
1056
1055 def d():
1057 def d():
1056 ll = linelog.linelog()
1058 ll = linelog.linelog()
1057 for args in arglist:
1059 for args in arglist:
1058 ll.replacelines(*args)
1060 ll.replacelines(*args)
1059
1061
1060 timer, fm = gettimer(ui, opts)
1062 timer, fm = gettimer(ui, opts)
1061 timer(d)
1063 timer(d)
1062 fm.end()
1064 fm.end()
1063
1065
1064 @command(b'perfrevrange', formatteropts)
1066 @command(b'perfrevrange', formatteropts)
1065 def perfrevrange(ui, repo, *specs, **opts):
1067 def perfrevrange(ui, repo, *specs, **opts):
1066 opts = _byteskwargs(opts)
1068 opts = _byteskwargs(opts)
1067 timer, fm = gettimer(ui, opts)
1069 timer, fm = gettimer(ui, opts)
1068 revrange = scmutil.revrange
1070 revrange = scmutil.revrange
1069 timer(lambda: len(revrange(repo, specs)))
1071 timer(lambda: len(revrange(repo, specs)))
1070 fm.end()
1072 fm.end()
1071
1073
1072 @command(b'perfnodelookup', formatteropts)
1074 @command(b'perfnodelookup', formatteropts)
1073 def perfnodelookup(ui, repo, rev, **opts):
1075 def perfnodelookup(ui, repo, rev, **opts):
1074 opts = _byteskwargs(opts)
1076 opts = _byteskwargs(opts)
1075 timer, fm = gettimer(ui, opts)
1077 timer, fm = gettimer(ui, opts)
1076 import mercurial.revlog
1078 import mercurial.revlog
1077 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
1079 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
1078 n = scmutil.revsingle(repo, rev).node()
1080 n = scmutil.revsingle(repo, rev).node()
1079 cl = mercurial.revlog.revlog(getsvfs(repo), b"00changelog.i")
1081 cl = mercurial.revlog.revlog(getsvfs(repo), b"00changelog.i")
1080 def d():
1082 def d():
1081 cl.rev(n)
1083 cl.rev(n)
1082 clearcaches(cl)
1084 clearcaches(cl)
1083 timer(d)
1085 timer(d)
1084 fm.end()
1086 fm.end()
1085
1087
1086 @command(b'perflog',
1088 @command(b'perflog',
1087 [(b'', b'rename', False, b'ask log to follow renames')
1089 [(b'', b'rename', False, b'ask log to follow renames')
1088 ] + formatteropts)
1090 ] + formatteropts)
1089 def perflog(ui, repo, rev=None, **opts):
1091 def perflog(ui, repo, rev=None, **opts):
1090 opts = _byteskwargs(opts)
1092 opts = _byteskwargs(opts)
1091 if rev is None:
1093 if rev is None:
1092 rev=[]
1094 rev=[]
1093 timer, fm = gettimer(ui, opts)
1095 timer, fm = gettimer(ui, opts)
1094 ui.pushbuffer()
1096 ui.pushbuffer()
1095 timer(lambda: commands.log(ui, repo, rev=rev, date=b'', user=b'',
1097 timer(lambda: commands.log(ui, repo, rev=rev, date=b'', user=b'',
1096 copies=opts.get(b'rename')))
1098 copies=opts.get(b'rename')))
1097 ui.popbuffer()
1099 ui.popbuffer()
1098 fm.end()
1100 fm.end()
1099
1101
1100 @command(b'perfmoonwalk', formatteropts)
1102 @command(b'perfmoonwalk', formatteropts)
1101 def perfmoonwalk(ui, repo, **opts):
1103 def perfmoonwalk(ui, repo, **opts):
1102 """benchmark walking the changelog backwards
1104 """benchmark walking the changelog backwards
1103
1105
1104 This also loads the changelog data for each revision in the changelog.
1106 This also loads the changelog data for each revision in the changelog.
1105 """
1107 """
1106 opts = _byteskwargs(opts)
1108 opts = _byteskwargs(opts)
1107 timer, fm = gettimer(ui, opts)
1109 timer, fm = gettimer(ui, opts)
1108 def moonwalk():
1110 def moonwalk():
1109 for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1):
1111 for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1):
1110 ctx = repo[i]
1112 ctx = repo[i]
1111 ctx.branch() # read changelog data (in addition to the index)
1113 ctx.branch() # read changelog data (in addition to the index)
1112 timer(moonwalk)
1114 timer(moonwalk)
1113 fm.end()
1115 fm.end()
1114
1116
1115 @command(b'perftemplating',
1117 @command(b'perftemplating',
1116 [(b'r', b'rev', [], b'revisions to run the template on'),
1118 [(b'r', b'rev', [], b'revisions to run the template on'),
1117 ] + formatteropts)
1119 ] + formatteropts)
1118 def perftemplating(ui, repo, testedtemplate=None, **opts):
1120 def perftemplating(ui, repo, testedtemplate=None, **opts):
1119 """test the rendering time of a given template"""
1121 """test the rendering time of a given template"""
1120 if makelogtemplater is None:
1122 if makelogtemplater is None:
1121 raise error.Abort((b"perftemplating not available with this Mercurial"),
1123 raise error.Abort((b"perftemplating not available with this Mercurial"),
1122 hint=b"use 4.3 or later")
1124 hint=b"use 4.3 or later")
1123
1125
1124 opts = _byteskwargs(opts)
1126 opts = _byteskwargs(opts)
1125
1127
1126 nullui = ui.copy()
1128 nullui = ui.copy()
1127 nullui.fout = open(os.devnull, r'wb')
1129 nullui.fout = open(os.devnull, r'wb')
1128 nullui.disablepager()
1130 nullui.disablepager()
1129 revs = opts.get(b'rev')
1131 revs = opts.get(b'rev')
1130 if not revs:
1132 if not revs:
1131 revs = [b'all()']
1133 revs = [b'all()']
1132 revs = list(scmutil.revrange(repo, revs))
1134 revs = list(scmutil.revrange(repo, revs))
1133
1135
1134 defaulttemplate = (b'{date|shortdate} [{rev}:{node|short}]'
1136 defaulttemplate = (b'{date|shortdate} [{rev}:{node|short}]'
1135 b' {author|person}: {desc|firstline}\n')
1137 b' {author|person}: {desc|firstline}\n')
1136 if testedtemplate is None:
1138 if testedtemplate is None:
1137 testedtemplate = defaulttemplate
1139 testedtemplate = defaulttemplate
1138 displayer = makelogtemplater(nullui, repo, testedtemplate)
1140 displayer = makelogtemplater(nullui, repo, testedtemplate)
1139 def format():
1141 def format():
1140 for r in revs:
1142 for r in revs:
1141 ctx = repo[r]
1143 ctx = repo[r]
1142 displayer.show(ctx)
1144 displayer.show(ctx)
1143 displayer.flush(ctx)
1145 displayer.flush(ctx)
1144
1146
1145 timer, fm = gettimer(ui, opts)
1147 timer, fm = gettimer(ui, opts)
1146 timer(format)
1148 timer(format)
1147 fm.end()
1149 fm.end()
1148
1150
1149 @command(b'perfcca', formatteropts)
1151 @command(b'perfcca', formatteropts)
1150 def perfcca(ui, repo, **opts):
1152 def perfcca(ui, repo, **opts):
1151 opts = _byteskwargs(opts)
1153 opts = _byteskwargs(opts)
1152 timer, fm = gettimer(ui, opts)
1154 timer, fm = gettimer(ui, opts)
1153 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
1155 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
1154 fm.end()
1156 fm.end()
1155
1157
1156 @command(b'perffncacheload', formatteropts)
1158 @command(b'perffncacheload', formatteropts)
1157 def perffncacheload(ui, repo, **opts):
1159 def perffncacheload(ui, repo, **opts):
1158 opts = _byteskwargs(opts)
1160 opts = _byteskwargs(opts)
1159 timer, fm = gettimer(ui, opts)
1161 timer, fm = gettimer(ui, opts)
1160 s = repo.store
1162 s = repo.store
1161 def d():
1163 def d():
1162 s.fncache._load()
1164 s.fncache._load()
1163 timer(d)
1165 timer(d)
1164 fm.end()
1166 fm.end()
1165
1167
1166 @command(b'perffncachewrite', formatteropts)
1168 @command(b'perffncachewrite', formatteropts)
1167 def perffncachewrite(ui, repo, **opts):
1169 def perffncachewrite(ui, repo, **opts):
1168 opts = _byteskwargs(opts)
1170 opts = _byteskwargs(opts)
1169 timer, fm = gettimer(ui, opts)
1171 timer, fm = gettimer(ui, opts)
1170 s = repo.store
1172 s = repo.store
1171 lock = repo.lock()
1173 lock = repo.lock()
1172 s.fncache._load()
1174 s.fncache._load()
1173 tr = repo.transaction(b'perffncachewrite')
1175 tr = repo.transaction(b'perffncachewrite')
1174 tr.addbackup(b'fncache')
1176 tr.addbackup(b'fncache')
1175 def d():
1177 def d():
1176 s.fncache._dirty = True
1178 s.fncache._dirty = True
1177 s.fncache.write(tr)
1179 s.fncache.write(tr)
1178 timer(d)
1180 timer(d)
1179 tr.close()
1181 tr.close()
1180 lock.release()
1182 lock.release()
1181 fm.end()
1183 fm.end()
1182
1184
1183 @command(b'perffncacheencode', formatteropts)
1185 @command(b'perffncacheencode', formatteropts)
1184 def perffncacheencode(ui, repo, **opts):
1186 def perffncacheencode(ui, repo, **opts):
1185 opts = _byteskwargs(opts)
1187 opts = _byteskwargs(opts)
1186 timer, fm = gettimer(ui, opts)
1188 timer, fm = gettimer(ui, opts)
1187 s = repo.store
1189 s = repo.store
1188 s.fncache._load()
1190 s.fncache._load()
1189 def d():
1191 def d():
1190 for p in s.fncache.entries:
1192 for p in s.fncache.entries:
1191 s.encode(p)
1193 s.encode(p)
1192 timer(d)
1194 timer(d)
1193 fm.end()
1195 fm.end()
1194
1196
1195 def _bdiffworker(q, blocks, xdiff, ready, done):
1197 def _bdiffworker(q, blocks, xdiff, ready, done):
1196 while not done.is_set():
1198 while not done.is_set():
1197 pair = q.get()
1199 pair = q.get()
1198 while pair is not None:
1200 while pair is not None:
1199 if xdiff:
1201 if xdiff:
1200 mdiff.bdiff.xdiffblocks(*pair)
1202 mdiff.bdiff.xdiffblocks(*pair)
1201 elif blocks:
1203 elif blocks:
1202 mdiff.bdiff.blocks(*pair)
1204 mdiff.bdiff.blocks(*pair)
1203 else:
1205 else:
1204 mdiff.textdiff(*pair)
1206 mdiff.textdiff(*pair)
1205 q.task_done()
1207 q.task_done()
1206 pair = q.get()
1208 pair = q.get()
1207 q.task_done() # for the None one
1209 q.task_done() # for the None one
1208 with ready:
1210 with ready:
1209 ready.wait()
1211 ready.wait()
1210
1212
1211 def _manifestrevision(repo, mnode):
1213 def _manifestrevision(repo, mnode):
1212 ml = repo.manifestlog
1214 ml = repo.manifestlog
1213
1215
1214 if util.safehasattr(ml, b'getstorage'):
1216 if util.safehasattr(ml, b'getstorage'):
1215 store = ml.getstorage(b'')
1217 store = ml.getstorage(b'')
1216 else:
1218 else:
1217 store = ml._revlog
1219 store = ml._revlog
1218
1220
1219 return store.revision(mnode)
1221 return store.revision(mnode)
1220
1222
1221 @command(b'perfbdiff', revlogopts + formatteropts + [
1223 @command(b'perfbdiff', revlogopts + formatteropts + [
1222 (b'', b'count', 1, b'number of revisions to test (when using --startrev)'),
1224 (b'', b'count', 1, b'number of revisions to test (when using --startrev)'),
1223 (b'', b'alldata', False, b'test bdiffs for all associated revisions'),
1225 (b'', b'alldata', False, b'test bdiffs for all associated revisions'),
1224 (b'', b'threads', 0, b'number of thread to use (disable with 0)'),
1226 (b'', b'threads', 0, b'number of thread to use (disable with 0)'),
1225 (b'', b'blocks', False, b'test computing diffs into blocks'),
1227 (b'', b'blocks', False, b'test computing diffs into blocks'),
1226 (b'', b'xdiff', False, b'use xdiff algorithm'),
1228 (b'', b'xdiff', False, b'use xdiff algorithm'),
1227 ],
1229 ],
1228
1230
1229 b'-c|-m|FILE REV')
1231 b'-c|-m|FILE REV')
1230 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
1232 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
1231 """benchmark a bdiff between revisions
1233 """benchmark a bdiff between revisions
1232
1234
1233 By default, benchmark a bdiff between its delta parent and itself.
1235 By default, benchmark a bdiff between its delta parent and itself.
1234
1236
1235 With ``--count``, benchmark bdiffs between delta parents and self for N
1237 With ``--count``, benchmark bdiffs between delta parents and self for N
1236 revisions starting at the specified revision.
1238 revisions starting at the specified revision.
1237
1239
1238 With ``--alldata``, assume the requested revision is a changeset and
1240 With ``--alldata``, assume the requested revision is a changeset and
1239 measure bdiffs for all changes related to that changeset (manifest
1241 measure bdiffs for all changes related to that changeset (manifest
1240 and filelogs).
1242 and filelogs).
1241 """
1243 """
1242 opts = _byteskwargs(opts)
1244 opts = _byteskwargs(opts)
1243
1245
1244 if opts[b'xdiff'] and not opts[b'blocks']:
1246 if opts[b'xdiff'] and not opts[b'blocks']:
1245 raise error.CommandError(b'perfbdiff', b'--xdiff requires --blocks')
1247 raise error.CommandError(b'perfbdiff', b'--xdiff requires --blocks')
1246
1248
1247 if opts[b'alldata']:
1249 if opts[b'alldata']:
1248 opts[b'changelog'] = True
1250 opts[b'changelog'] = True
1249
1251
1250 if opts.get(b'changelog') or opts.get(b'manifest'):
1252 if opts.get(b'changelog') or opts.get(b'manifest'):
1251 file_, rev = None, file_
1253 file_, rev = None, file_
1252 elif rev is None:
1254 elif rev is None:
1253 raise error.CommandError(b'perfbdiff', b'invalid arguments')
1255 raise error.CommandError(b'perfbdiff', b'invalid arguments')
1254
1256
1255 blocks = opts[b'blocks']
1257 blocks = opts[b'blocks']
1256 xdiff = opts[b'xdiff']
1258 xdiff = opts[b'xdiff']
1257 textpairs = []
1259 textpairs = []
1258
1260
1259 r = cmdutil.openrevlog(repo, b'perfbdiff', file_, opts)
1261 r = cmdutil.openrevlog(repo, b'perfbdiff', file_, opts)
1260
1262
1261 startrev = r.rev(r.lookup(rev))
1263 startrev = r.rev(r.lookup(rev))
1262 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1264 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1263 if opts[b'alldata']:
1265 if opts[b'alldata']:
1264 # Load revisions associated with changeset.
1266 # Load revisions associated with changeset.
1265 ctx = repo[rev]
1267 ctx = repo[rev]
1266 mtext = _manifestrevision(repo, ctx.manifestnode())
1268 mtext = _manifestrevision(repo, ctx.manifestnode())
1267 for pctx in ctx.parents():
1269 for pctx in ctx.parents():
1268 pman = _manifestrevision(repo, pctx.manifestnode())
1270 pman = _manifestrevision(repo, pctx.manifestnode())
1269 textpairs.append((pman, mtext))
1271 textpairs.append((pman, mtext))
1270
1272
1271 # Load filelog revisions by iterating manifest delta.
1273 # Load filelog revisions by iterating manifest delta.
1272 man = ctx.manifest()
1274 man = ctx.manifest()
1273 pman = ctx.p1().manifest()
1275 pman = ctx.p1().manifest()
1274 for filename, change in pman.diff(man).items():
1276 for filename, change in pman.diff(man).items():
1275 fctx = repo.file(filename)
1277 fctx = repo.file(filename)
1276 f1 = fctx.revision(change[0][0] or -1)
1278 f1 = fctx.revision(change[0][0] or -1)
1277 f2 = fctx.revision(change[1][0] or -1)
1279 f2 = fctx.revision(change[1][0] or -1)
1278 textpairs.append((f1, f2))
1280 textpairs.append((f1, f2))
1279 else:
1281 else:
1280 dp = r.deltaparent(rev)
1282 dp = r.deltaparent(rev)
1281 textpairs.append((r.revision(dp), r.revision(rev)))
1283 textpairs.append((r.revision(dp), r.revision(rev)))
1282
1284
1283 withthreads = threads > 0
1285 withthreads = threads > 0
1284 if not withthreads:
1286 if not withthreads:
1285 def d():
1287 def d():
1286 for pair in textpairs:
1288 for pair in textpairs:
1287 if xdiff:
1289 if xdiff:
1288 mdiff.bdiff.xdiffblocks(*pair)
1290 mdiff.bdiff.xdiffblocks(*pair)
1289 elif blocks:
1291 elif blocks:
1290 mdiff.bdiff.blocks(*pair)
1292 mdiff.bdiff.blocks(*pair)
1291 else:
1293 else:
1292 mdiff.textdiff(*pair)
1294 mdiff.textdiff(*pair)
1293 else:
1295 else:
1294 q = queue()
1296 q = queue()
1295 for i in _xrange(threads):
1297 for i in _xrange(threads):
1296 q.put(None)
1298 q.put(None)
1297 ready = threading.Condition()
1299 ready = threading.Condition()
1298 done = threading.Event()
1300 done = threading.Event()
1299 for i in _xrange(threads):
1301 for i in _xrange(threads):
1300 threading.Thread(target=_bdiffworker,
1302 threading.Thread(target=_bdiffworker,
1301 args=(q, blocks, xdiff, ready, done)).start()
1303 args=(q, blocks, xdiff, ready, done)).start()
1302 q.join()
1304 q.join()
1303 def d():
1305 def d():
1304 for pair in textpairs:
1306 for pair in textpairs:
1305 q.put(pair)
1307 q.put(pair)
1306 for i in _xrange(threads):
1308 for i in _xrange(threads):
1307 q.put(None)
1309 q.put(None)
1308 with ready:
1310 with ready:
1309 ready.notify_all()
1311 ready.notify_all()
1310 q.join()
1312 q.join()
1311 timer, fm = gettimer(ui, opts)
1313 timer, fm = gettimer(ui, opts)
1312 timer(d)
1314 timer(d)
1313 fm.end()
1315 fm.end()
1314
1316
1315 if withthreads:
1317 if withthreads:
1316 done.set()
1318 done.set()
1317 for i in _xrange(threads):
1319 for i in _xrange(threads):
1318 q.put(None)
1320 q.put(None)
1319 with ready:
1321 with ready:
1320 ready.notify_all()
1322 ready.notify_all()
1321
1323
1322 @command(b'perfunidiff', revlogopts + formatteropts + [
1324 @command(b'perfunidiff', revlogopts + formatteropts + [
1323 (b'', b'count', 1, b'number of revisions to test (when using --startrev)'),
1325 (b'', b'count', 1, b'number of revisions to test (when using --startrev)'),
1324 (b'', b'alldata', False, b'test unidiffs for all associated revisions'),
1326 (b'', b'alldata', False, b'test unidiffs for all associated revisions'),
1325 ], b'-c|-m|FILE REV')
1327 ], b'-c|-m|FILE REV')
1326 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
1328 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
1327 """benchmark a unified diff between revisions
1329 """benchmark a unified diff between revisions
1328
1330
1329 This doesn't include any copy tracing - it's just a unified diff
1331 This doesn't include any copy tracing - it's just a unified diff
1330 of the texts.
1332 of the texts.
1331
1333
1332 By default, benchmark a diff between its delta parent and itself.
1334 By default, benchmark a diff between its delta parent and itself.
1333
1335
1334 With ``--count``, benchmark diffs between delta parents and self for N
1336 With ``--count``, benchmark diffs between delta parents and self for N
1335 revisions starting at the specified revision.
1337 revisions starting at the specified revision.
1336
1338
1337 With ``--alldata``, assume the requested revision is a changeset and
1339 With ``--alldata``, assume the requested revision is a changeset and
1338 measure diffs for all changes related to that changeset (manifest
1340 measure diffs for all changes related to that changeset (manifest
1339 and filelogs).
1341 and filelogs).
1340 """
1342 """
1341 opts = _byteskwargs(opts)
1343 opts = _byteskwargs(opts)
1342 if opts[b'alldata']:
1344 if opts[b'alldata']:
1343 opts[b'changelog'] = True
1345 opts[b'changelog'] = True
1344
1346
1345 if opts.get(b'changelog') or opts.get(b'manifest'):
1347 if opts.get(b'changelog') or opts.get(b'manifest'):
1346 file_, rev = None, file_
1348 file_, rev = None, file_
1347 elif rev is None:
1349 elif rev is None:
1348 raise error.CommandError(b'perfunidiff', b'invalid arguments')
1350 raise error.CommandError(b'perfunidiff', b'invalid arguments')
1349
1351
1350 textpairs = []
1352 textpairs = []
1351
1353
1352 r = cmdutil.openrevlog(repo, b'perfunidiff', file_, opts)
1354 r = cmdutil.openrevlog(repo, b'perfunidiff', file_, opts)
1353
1355
1354 startrev = r.rev(r.lookup(rev))
1356 startrev = r.rev(r.lookup(rev))
1355 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1357 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1356 if opts[b'alldata']:
1358 if opts[b'alldata']:
1357 # Load revisions associated with changeset.
1359 # Load revisions associated with changeset.
1358 ctx = repo[rev]
1360 ctx = repo[rev]
1359 mtext = _manifestrevision(repo, ctx.manifestnode())
1361 mtext = _manifestrevision(repo, ctx.manifestnode())
1360 for pctx in ctx.parents():
1362 for pctx in ctx.parents():
1361 pman = _manifestrevision(repo, pctx.manifestnode())
1363 pman = _manifestrevision(repo, pctx.manifestnode())
1362 textpairs.append((pman, mtext))
1364 textpairs.append((pman, mtext))
1363
1365
1364 # Load filelog revisions by iterating manifest delta.
1366 # Load filelog revisions by iterating manifest delta.
1365 man = ctx.manifest()
1367 man = ctx.manifest()
1366 pman = ctx.p1().manifest()
1368 pman = ctx.p1().manifest()
1367 for filename, change in pman.diff(man).items():
1369 for filename, change in pman.diff(man).items():
1368 fctx = repo.file(filename)
1370 fctx = repo.file(filename)
1369 f1 = fctx.revision(change[0][0] or -1)
1371 f1 = fctx.revision(change[0][0] or -1)
1370 f2 = fctx.revision(change[1][0] or -1)
1372 f2 = fctx.revision(change[1][0] or -1)
1371 textpairs.append((f1, f2))
1373 textpairs.append((f1, f2))
1372 else:
1374 else:
1373 dp = r.deltaparent(rev)
1375 dp = r.deltaparent(rev)
1374 textpairs.append((r.revision(dp), r.revision(rev)))
1376 textpairs.append((r.revision(dp), r.revision(rev)))
1375
1377
1376 def d():
1378 def d():
1377 for left, right in textpairs:
1379 for left, right in textpairs:
1378 # The date strings don't matter, so we pass empty strings.
1380 # The date strings don't matter, so we pass empty strings.
1379 headerlines, hunks = mdiff.unidiff(
1381 headerlines, hunks = mdiff.unidiff(
1380 left, b'', right, b'', b'left', b'right', binary=False)
1382 left, b'', right, b'', b'left', b'right', binary=False)
1381 # consume iterators in roughly the way patch.py does
1383 # consume iterators in roughly the way patch.py does
1382 b'\n'.join(headerlines)
1384 b'\n'.join(headerlines)
1383 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
1385 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
1384 timer, fm = gettimer(ui, opts)
1386 timer, fm = gettimer(ui, opts)
1385 timer(d)
1387 timer(d)
1386 fm.end()
1388 fm.end()
1387
1389
1388 @command(b'perfdiffwd', formatteropts)
1390 @command(b'perfdiffwd', formatteropts)
1389 def perfdiffwd(ui, repo, **opts):
1391 def perfdiffwd(ui, repo, **opts):
1390 """Profile diff of working directory changes"""
1392 """Profile diff of working directory changes"""
1391 opts = _byteskwargs(opts)
1393 opts = _byteskwargs(opts)
1392 timer, fm = gettimer(ui, opts)
1394 timer, fm = gettimer(ui, opts)
1393 options = {
1395 options = {
1394 'w': 'ignore_all_space',
1396 'w': 'ignore_all_space',
1395 'b': 'ignore_space_change',
1397 'b': 'ignore_space_change',
1396 'B': 'ignore_blank_lines',
1398 'B': 'ignore_blank_lines',
1397 }
1399 }
1398
1400
1399 for diffopt in ('', 'w', 'b', 'B', 'wB'):
1401 for diffopt in ('', 'w', 'b', 'B', 'wB'):
1400 opts = dict((options[c], b'1') for c in diffopt)
1402 opts = dict((options[c], b'1') for c in diffopt)
1401 def d():
1403 def d():
1402 ui.pushbuffer()
1404 ui.pushbuffer()
1403 commands.diff(ui, repo, **opts)
1405 commands.diff(ui, repo, **opts)
1404 ui.popbuffer()
1406 ui.popbuffer()
1405 diffopt = diffopt.encode('ascii')
1407 diffopt = diffopt.encode('ascii')
1406 title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none')
1408 title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none')
1407 timer(d, title=title)
1409 timer(d, title=title)
1408 fm.end()
1410 fm.end()
1409
1411
1410 @command(b'perfrevlogindex', revlogopts + formatteropts,
1412 @command(b'perfrevlogindex', revlogopts + formatteropts,
1411 b'-c|-m|FILE')
1413 b'-c|-m|FILE')
1412 def perfrevlogindex(ui, repo, file_=None, **opts):
1414 def perfrevlogindex(ui, repo, file_=None, **opts):
1413 """Benchmark operations against a revlog index.
1415 """Benchmark operations against a revlog index.
1414
1416
1415 This tests constructing a revlog instance, reading index data,
1417 This tests constructing a revlog instance, reading index data,
1416 parsing index data, and performing various operations related to
1418 parsing index data, and performing various operations related to
1417 index data.
1419 index data.
1418 """
1420 """
1419
1421
1420 opts = _byteskwargs(opts)
1422 opts = _byteskwargs(opts)
1421
1423
1422 rl = cmdutil.openrevlog(repo, b'perfrevlogindex', file_, opts)
1424 rl = cmdutil.openrevlog(repo, b'perfrevlogindex', file_, opts)
1423
1425
1424 opener = getattr(rl, 'opener') # trick linter
1426 opener = getattr(rl, 'opener') # trick linter
1425 indexfile = rl.indexfile
1427 indexfile = rl.indexfile
1426 data = opener.read(indexfile)
1428 data = opener.read(indexfile)
1427
1429
1428 header = struct.unpack(b'>I', data[0:4])[0]
1430 header = struct.unpack(b'>I', data[0:4])[0]
1429 version = header & 0xFFFF
1431 version = header & 0xFFFF
1430 if version == 1:
1432 if version == 1:
1431 revlogio = revlog.revlogio()
1433 revlogio = revlog.revlogio()
1432 inline = header & (1 << 16)
1434 inline = header & (1 << 16)
1433 else:
1435 else:
1434 raise error.Abort((b'unsupported revlog version: %d') % version)
1436 raise error.Abort((b'unsupported revlog version: %d') % version)
1435
1437
1436 rllen = len(rl)
1438 rllen = len(rl)
1437
1439
1438 node0 = rl.node(0)
1440 node0 = rl.node(0)
1439 node25 = rl.node(rllen // 4)
1441 node25 = rl.node(rllen // 4)
1440 node50 = rl.node(rllen // 2)
1442 node50 = rl.node(rllen // 2)
1441 node75 = rl.node(rllen // 4 * 3)
1443 node75 = rl.node(rllen // 4 * 3)
1442 node100 = rl.node(rllen - 1)
1444 node100 = rl.node(rllen - 1)
1443
1445
1444 allrevs = range(rllen)
1446 allrevs = range(rllen)
1445 allrevsrev = list(reversed(allrevs))
1447 allrevsrev = list(reversed(allrevs))
1446 allnodes = [rl.node(rev) for rev in range(rllen)]
1448 allnodes = [rl.node(rev) for rev in range(rllen)]
1447 allnodesrev = list(reversed(allnodes))
1449 allnodesrev = list(reversed(allnodes))
1448
1450
1449 def constructor():
1451 def constructor():
1450 revlog.revlog(opener, indexfile)
1452 revlog.revlog(opener, indexfile)
1451
1453
1452 def read():
1454 def read():
1453 with opener(indexfile) as fh:
1455 with opener(indexfile) as fh:
1454 fh.read()
1456 fh.read()
1455
1457
1456 def parseindex():
1458 def parseindex():
1457 revlogio.parseindex(data, inline)
1459 revlogio.parseindex(data, inline)
1458
1460
1459 def getentry(revornode):
1461 def getentry(revornode):
1460 index = revlogio.parseindex(data, inline)[0]
1462 index = revlogio.parseindex(data, inline)[0]
1461 index[revornode]
1463 index[revornode]
1462
1464
1463 def getentries(revs, count=1):
1465 def getentries(revs, count=1):
1464 index = revlogio.parseindex(data, inline)[0]
1466 index = revlogio.parseindex(data, inline)[0]
1465
1467
1466 for i in range(count):
1468 for i in range(count):
1467 for rev in revs:
1469 for rev in revs:
1468 index[rev]
1470 index[rev]
1469
1471
1470 def resolvenode(node):
1472 def resolvenode(node):
1471 nodemap = revlogio.parseindex(data, inline)[1]
1473 nodemap = revlogio.parseindex(data, inline)[1]
1472 # This only works for the C code.
1474 # This only works for the C code.
1473 if nodemap is None:
1475 if nodemap is None:
1474 return
1476 return
1475
1477
1476 try:
1478 try:
1477 nodemap[node]
1479 nodemap[node]
1478 except error.RevlogError:
1480 except error.RevlogError:
1479 pass
1481 pass
1480
1482
1481 def resolvenodes(nodes, count=1):
1483 def resolvenodes(nodes, count=1):
1482 nodemap = revlogio.parseindex(data, inline)[1]
1484 nodemap = revlogio.parseindex(data, inline)[1]
1483 if nodemap is None:
1485 if nodemap is None:
1484 return
1486 return
1485
1487
1486 for i in range(count):
1488 for i in range(count):
1487 for node in nodes:
1489 for node in nodes:
1488 try:
1490 try:
1489 nodemap[node]
1491 nodemap[node]
1490 except error.RevlogError:
1492 except error.RevlogError:
1491 pass
1493 pass
1492
1494
1493 benches = [
1495 benches = [
1494 (constructor, b'revlog constructor'),
1496 (constructor, b'revlog constructor'),
1495 (read, b'read'),
1497 (read, b'read'),
1496 (parseindex, b'create index object'),
1498 (parseindex, b'create index object'),
1497 (lambda: getentry(0), b'retrieve index entry for rev 0'),
1499 (lambda: getentry(0), b'retrieve index entry for rev 0'),
1498 (lambda: resolvenode(b'a' * 20), b'look up missing node'),
1500 (lambda: resolvenode(b'a' * 20), b'look up missing node'),
1499 (lambda: resolvenode(node0), b'look up node at rev 0'),
1501 (lambda: resolvenode(node0), b'look up node at rev 0'),
1500 (lambda: resolvenode(node25), b'look up node at 1/4 len'),
1502 (lambda: resolvenode(node25), b'look up node at 1/4 len'),
1501 (lambda: resolvenode(node50), b'look up node at 1/2 len'),
1503 (lambda: resolvenode(node50), b'look up node at 1/2 len'),
1502 (lambda: resolvenode(node75), b'look up node at 3/4 len'),
1504 (lambda: resolvenode(node75), b'look up node at 3/4 len'),
1503 (lambda: resolvenode(node100), b'look up node at tip'),
1505 (lambda: resolvenode(node100), b'look up node at tip'),
1504 # 2x variation is to measure caching impact.
1506 # 2x variation is to measure caching impact.
1505 (lambda: resolvenodes(allnodes),
1507 (lambda: resolvenodes(allnodes),
1506 b'look up all nodes (forward)'),
1508 b'look up all nodes (forward)'),
1507 (lambda: resolvenodes(allnodes, 2),
1509 (lambda: resolvenodes(allnodes, 2),
1508 b'look up all nodes 2x (forward)'),
1510 b'look up all nodes 2x (forward)'),
1509 (lambda: resolvenodes(allnodesrev),
1511 (lambda: resolvenodes(allnodesrev),
1510 b'look up all nodes (reverse)'),
1512 b'look up all nodes (reverse)'),
1511 (lambda: resolvenodes(allnodesrev, 2),
1513 (lambda: resolvenodes(allnodesrev, 2),
1512 b'look up all nodes 2x (reverse)'),
1514 b'look up all nodes 2x (reverse)'),
1513 (lambda: getentries(allrevs),
1515 (lambda: getentries(allrevs),
1514 b'retrieve all index entries (forward)'),
1516 b'retrieve all index entries (forward)'),
1515 (lambda: getentries(allrevs, 2),
1517 (lambda: getentries(allrevs, 2),
1516 b'retrieve all index entries 2x (forward)'),
1518 b'retrieve all index entries 2x (forward)'),
1517 (lambda: getentries(allrevsrev),
1519 (lambda: getentries(allrevsrev),
1518 b'retrieve all index entries (reverse)'),
1520 b'retrieve all index entries (reverse)'),
1519 (lambda: getentries(allrevsrev, 2),
1521 (lambda: getentries(allrevsrev, 2),
1520 b'retrieve all index entries 2x (reverse)'),
1522 b'retrieve all index entries 2x (reverse)'),
1521 ]
1523 ]
1522
1524
1523 for fn, title in benches:
1525 for fn, title in benches:
1524 timer, fm = gettimer(ui, opts)
1526 timer, fm = gettimer(ui, opts)
1525 timer(fn, title=title)
1527 timer(fn, title=title)
1526 fm.end()
1528 fm.end()
1527
1529
1528 @command(b'perfrevlogrevisions', revlogopts + formatteropts +
1530 @command(b'perfrevlogrevisions', revlogopts + formatteropts +
1529 [(b'd', b'dist', 100, b'distance between the revisions'),
1531 [(b'd', b'dist', 100, b'distance between the revisions'),
1530 (b's', b'startrev', 0, b'revision to start reading at'),
1532 (b's', b'startrev', 0, b'revision to start reading at'),
1531 (b'', b'reverse', False, b'read in reverse')],
1533 (b'', b'reverse', False, b'read in reverse')],
1532 b'-c|-m|FILE')
1534 b'-c|-m|FILE')
1533 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
1535 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
1534 **opts):
1536 **opts):
1535 """Benchmark reading a series of revisions from a revlog.
1537 """Benchmark reading a series of revisions from a revlog.
1536
1538
1537 By default, we read every ``-d/--dist`` revision from 0 to tip of
1539 By default, we read every ``-d/--dist`` revision from 0 to tip of
1538 the specified revlog.
1540 the specified revlog.
1539
1541
1540 The start revision can be defined via ``-s/--startrev``.
1542 The start revision can be defined via ``-s/--startrev``.
1541 """
1543 """
1542 opts = _byteskwargs(opts)
1544 opts = _byteskwargs(opts)
1543
1545
1544 rl = cmdutil.openrevlog(repo, b'perfrevlogrevisions', file_, opts)
1546 rl = cmdutil.openrevlog(repo, b'perfrevlogrevisions', file_, opts)
1545 rllen = getlen(ui)(rl)
1547 rllen = getlen(ui)(rl)
1546
1548
1547 if startrev < 0:
1549 if startrev < 0:
1548 startrev = rllen + startrev
1550 startrev = rllen + startrev
1549
1551
1550 def d():
1552 def d():
1551 rl.clearcaches()
1553 rl.clearcaches()
1552
1554
1553 beginrev = startrev
1555 beginrev = startrev
1554 endrev = rllen
1556 endrev = rllen
1555 dist = opts[b'dist']
1557 dist = opts[b'dist']
1556
1558
1557 if reverse:
1559 if reverse:
1558 beginrev, endrev = endrev - 1, beginrev - 1
1560 beginrev, endrev = endrev - 1, beginrev - 1
1559 dist = -1 * dist
1561 dist = -1 * dist
1560
1562
1561 for x in _xrange(beginrev, endrev, dist):
1563 for x in _xrange(beginrev, endrev, dist):
1562 # Old revisions don't support passing int.
1564 # Old revisions don't support passing int.
1563 n = rl.node(x)
1565 n = rl.node(x)
1564 rl.revision(n)
1566 rl.revision(n)
1565
1567
1566 timer, fm = gettimer(ui, opts)
1568 timer, fm = gettimer(ui, opts)
1567 timer(d)
1569 timer(d)
1568 fm.end()
1570 fm.end()
1569
1571
1570 @command(b'perfrevlogwrite', revlogopts + formatteropts +
1572 @command(b'perfrevlogwrite', revlogopts + formatteropts +
1571 [(b's', b'startrev', 1000, b'revision to start writing at'),
1573 [(b's', b'startrev', 1000, b'revision to start writing at'),
1572 (b'', b'stoprev', -1, b'last revision to write'),
1574 (b'', b'stoprev', -1, b'last revision to write'),
1573 (b'', b'count', 3, b'last revision to write'),
1575 (b'', b'count', 3, b'last revision to write'),
1574 (b'', b'details', False, b'print timing for every revisions tested'),
1576 (b'', b'details', False, b'print timing for every revisions tested'),
1575 (b'', b'source', b'full', b'the kind of data feed in the revlog'),
1577 (b'', b'source', b'full', b'the kind of data feed in the revlog'),
1576 (b'', b'lazydeltabase', True, b'try the provided delta first'),
1578 (b'', b'lazydeltabase', True, b'try the provided delta first'),
1577 ],
1579 ],
1578 b'-c|-m|FILE')
1580 b'-c|-m|FILE')
1579 def perfrevlogwrite(ui, repo, file_=None, startrev=1000, stoprev=-1, **opts):
1581 def perfrevlogwrite(ui, repo, file_=None, startrev=1000, stoprev=-1, **opts):
1580 """Benchmark writing a series of revisions to a revlog.
1582 """Benchmark writing a series of revisions to a revlog.
1581
1583
1582 Possible source values are:
1584 Possible source values are:
1583 * `full`: add from a full text (default).
1585 * `full`: add from a full text (default).
1584 * `parent-1`: add from a delta to the first parent
1586 * `parent-1`: add from a delta to the first parent
1585 * `parent-2`: add from a delta to the second parent if it exists
1587 * `parent-2`: add from a delta to the second parent if it exists
1586 (use a delta from the first parent otherwise)
1588 (use a delta from the first parent otherwise)
1587 * `parent-smallest`: add from the smallest delta (either p1 or p2)
1589 * `parent-smallest`: add from the smallest delta (either p1 or p2)
1588 * `storage`: add from the existing precomputed deltas
1590 * `storage`: add from the existing precomputed deltas
1589 """
1591 """
1590 opts = _byteskwargs(opts)
1592 opts = _byteskwargs(opts)
1591
1593
1592 rl = cmdutil.openrevlog(repo, b'perfrevlogwrite', file_, opts)
1594 rl = cmdutil.openrevlog(repo, b'perfrevlogwrite', file_, opts)
1593 rllen = getlen(ui)(rl)
1595 rllen = getlen(ui)(rl)
1594 if startrev < 0:
1596 if startrev < 0:
1595 startrev = rllen + startrev
1597 startrev = rllen + startrev
1596 if stoprev < 0:
1598 if stoprev < 0:
1597 stoprev = rllen + stoprev
1599 stoprev = rllen + stoprev
1598
1600
1599 lazydeltabase = opts['lazydeltabase']
1601 lazydeltabase = opts['lazydeltabase']
1600 source = opts['source']
1602 source = opts['source']
1601 validsource = (b'full', b'parent-1', b'parent-2', b'parent-smallest',
1603 validsource = (b'full', b'parent-1', b'parent-2', b'parent-smallest',
1602 b'storage')
1604 b'storage')
1603 if source not in validsource:
1605 if source not in validsource:
1604 raise error.Abort('invalid source type: %s' % source)
1606 raise error.Abort('invalid source type: %s' % source)
1605
1607
1606 ### actually gather results
1608 ### actually gather results
1607 count = opts['count']
1609 count = opts['count']
1608 if count <= 0:
1610 if count <= 0:
1609 raise error.Abort('invalide run count: %d' % count)
1611 raise error.Abort('invalide run count: %d' % count)
1610 allresults = []
1612 allresults = []
1611 for c in range(count):
1613 for c in range(count):
1612 timing = _timeonewrite(ui, rl, source, startrev, stoprev, c + 1,
1614 timing = _timeonewrite(ui, rl, source, startrev, stoprev, c + 1,
1613 lazydeltabase=lazydeltabase)
1615 lazydeltabase=lazydeltabase)
1614 allresults.append(timing)
1616 allresults.append(timing)
1615
1617
1616 ### consolidate the results in a single list
1618 ### consolidate the results in a single list
1617 results = []
1619 results = []
1618 for idx, (rev, t) in enumerate(allresults[0]):
1620 for idx, (rev, t) in enumerate(allresults[0]):
1619 ts = [t]
1621 ts = [t]
1620 for other in allresults[1:]:
1622 for other in allresults[1:]:
1621 orev, ot = other[idx]
1623 orev, ot = other[idx]
1622 assert orev == rev
1624 assert orev == rev
1623 ts.append(ot)
1625 ts.append(ot)
1624 results.append((rev, ts))
1626 results.append((rev, ts))
1625 resultcount = len(results)
1627 resultcount = len(results)
1626
1628
1627 ### Compute and display relevant statistics
1629 ### Compute and display relevant statistics
1628
1630
1629 # get a formatter
1631 # get a formatter
1630 fm = ui.formatter(b'perf', opts)
1632 fm = ui.formatter(b'perf', opts)
1631 displayall = ui.configbool(b"perf", b"all-timing", False)
1633 displayall = ui.configbool(b"perf", b"all-timing", False)
1632
1634
1633 # print individual details if requested
1635 # print individual details if requested
1634 if opts['details']:
1636 if opts['details']:
1635 for idx, item in enumerate(results, 1):
1637 for idx, item in enumerate(results, 1):
1636 rev, data = item
1638 rev, data = item
1637 title = 'revisions #%d of %d, rev %d' % (idx, resultcount, rev)
1639 title = 'revisions #%d of %d, rev %d' % (idx, resultcount, rev)
1638 formatone(fm, data, title=title, displayall=displayall)
1640 formatone(fm, data, title=title, displayall=displayall)
1639
1641
1640 # sorts results by median time
1642 # sorts results by median time
1641 results.sort(key=lambda x: sorted(x[1])[len(x[1]) // 2])
1643 results.sort(key=lambda x: sorted(x[1])[len(x[1]) // 2])
1642 # list of (name, index) to display)
1644 # list of (name, index) to display)
1643 relevants = [
1645 relevants = [
1644 ("min", 0),
1646 ("min", 0),
1645 ("10%", resultcount * 10 // 100),
1647 ("10%", resultcount * 10 // 100),
1646 ("25%", resultcount * 25 // 100),
1648 ("25%", resultcount * 25 // 100),
1647 ("50%", resultcount * 70 // 100),
1649 ("50%", resultcount * 70 // 100),
1648 ("75%", resultcount * 75 // 100),
1650 ("75%", resultcount * 75 // 100),
1649 ("90%", resultcount * 90 // 100),
1651 ("90%", resultcount * 90 // 100),
1650 ("95%", resultcount * 95 // 100),
1652 ("95%", resultcount * 95 // 100),
1651 ("99%", resultcount * 99 // 100),
1653 ("99%", resultcount * 99 // 100),
1652 ("max", -1),
1654 ("max", -1),
1653 ]
1655 ]
1654 if not ui.quiet:
1656 if not ui.quiet:
1655 for name, idx in relevants:
1657 for name, idx in relevants:
1656 data = results[idx]
1658 data = results[idx]
1657 title = '%s of %d, rev %d' % (name, resultcount, data[0])
1659 title = '%s of %d, rev %d' % (name, resultcount, data[0])
1658 formatone(fm, data[1], title=title, displayall=displayall)
1660 formatone(fm, data[1], title=title, displayall=displayall)
1659
1661
1660 # XXX summing that many float will not be very precise, we ignore this fact
1662 # XXX summing that many float will not be very precise, we ignore this fact
1661 # for now
1663 # for now
1662 totaltime = []
1664 totaltime = []
1663 for item in allresults:
1665 for item in allresults:
1664 totaltime.append((sum(x[1][0] for x in item),
1666 totaltime.append((sum(x[1][0] for x in item),
1665 sum(x[1][1] for x in item),
1667 sum(x[1][1] for x in item),
1666 sum(x[1][2] for x in item),)
1668 sum(x[1][2] for x in item),)
1667 )
1669 )
1668 formatone(fm, totaltime, title="total time (%d revs)" % resultcount,
1670 formatone(fm, totaltime, title="total time (%d revs)" % resultcount,
1669 displayall=displayall)
1671 displayall=displayall)
1670 fm.end()
1672 fm.end()
1671
1673
1672 class _faketr(object):
1674 class _faketr(object):
1673 def add(s, x, y, z=None):
1675 def add(s, x, y, z=None):
1674 return None
1676 return None
1675
1677
1676 def _timeonewrite(ui, orig, source, startrev, stoprev, runidx=None,
1678 def _timeonewrite(ui, orig, source, startrev, stoprev, runidx=None,
1677 lazydeltabase=True):
1679 lazydeltabase=True):
1678 timings = []
1680 timings = []
1679 tr = _faketr()
1681 tr = _faketr()
1680 with _temprevlog(ui, orig, startrev) as dest:
1682 with _temprevlog(ui, orig, startrev) as dest:
1681 dest._lazydeltabase = lazydeltabase
1683 dest._lazydeltabase = lazydeltabase
1682 revs = list(orig.revs(startrev, stoprev))
1684 revs = list(orig.revs(startrev, stoprev))
1683 total = len(revs)
1685 total = len(revs)
1684 topic = 'adding'
1686 topic = 'adding'
1685 if runidx is not None:
1687 if runidx is not None:
1686 topic += ' (run #%d)' % runidx
1688 topic += ' (run #%d)' % runidx
1687 for idx, rev in enumerate(revs):
1689 for idx, rev in enumerate(revs):
1688 ui.progress(topic, idx, unit='revs', total=total)
1690 ui.progress(topic, idx, unit='revs', total=total)
1689 addargs, addkwargs = _getrevisionseed(orig, rev, tr, source)
1691 addargs, addkwargs = _getrevisionseed(orig, rev, tr, source)
1690 with timeone() as r:
1692 with timeone() as r:
1691 dest.addrawrevision(*addargs, **addkwargs)
1693 dest.addrawrevision(*addargs, **addkwargs)
1692 timings.append((rev, r[0]))
1694 timings.append((rev, r[0]))
1693 ui.progress(topic, total, unit='revs', total=total)
1695 ui.progress(topic, total, unit='revs', total=total)
1694 ui.progress(topic, None, unit='revs', total=total)
1696 ui.progress(topic, None, unit='revs', total=total)
1695 return timings
1697 return timings
1696
1698
1697 def _getrevisionseed(orig, rev, tr, source):
1699 def _getrevisionseed(orig, rev, tr, source):
1698 from mercurial.node import nullid
1700 from mercurial.node import nullid
1699
1701
1700 linkrev = orig.linkrev(rev)
1702 linkrev = orig.linkrev(rev)
1701 node = orig.node(rev)
1703 node = orig.node(rev)
1702 p1, p2 = orig.parents(node)
1704 p1, p2 = orig.parents(node)
1703 flags = orig.flags(rev)
1705 flags = orig.flags(rev)
1704 cachedelta = None
1706 cachedelta = None
1705 text = None
1707 text = None
1706
1708
1707 if source == b'full':
1709 if source == b'full':
1708 text = orig.revision(rev)
1710 text = orig.revision(rev)
1709 elif source == b'parent-1':
1711 elif source == b'parent-1':
1710 baserev = orig.rev(p1)
1712 baserev = orig.rev(p1)
1711 cachedelta = (baserev, orig.revdiff(p1, rev))
1713 cachedelta = (baserev, orig.revdiff(p1, rev))
1712 elif source == b'parent-2':
1714 elif source == b'parent-2':
1713 parent = p2
1715 parent = p2
1714 if p2 == nullid:
1716 if p2 == nullid:
1715 parent = p1
1717 parent = p1
1716 baserev = orig.rev(parent)
1718 baserev = orig.rev(parent)
1717 cachedelta = (baserev, orig.revdiff(parent, rev))
1719 cachedelta = (baserev, orig.revdiff(parent, rev))
1718 elif source == b'parent-smallest':
1720 elif source == b'parent-smallest':
1719 p1diff = orig.revdiff(p1, rev)
1721 p1diff = orig.revdiff(p1, rev)
1720 parent = p1
1722 parent = p1
1721 diff = p1diff
1723 diff = p1diff
1722 if p2 != nullid:
1724 if p2 != nullid:
1723 p2diff = orig.revdiff(p2, rev)
1725 p2diff = orig.revdiff(p2, rev)
1724 if len(p1diff) > len(p2diff):
1726 if len(p1diff) > len(p2diff):
1725 parent = p2
1727 parent = p2
1726 diff = p2diff
1728 diff = p2diff
1727 baserev = orig.rev(parent)
1729 baserev = orig.rev(parent)
1728 cachedelta = (baserev, diff)
1730 cachedelta = (baserev, diff)
1729 elif source == b'storage':
1731 elif source == b'storage':
1730 baserev = orig.deltaparent(rev)
1732 baserev = orig.deltaparent(rev)
1731 cachedelta = (baserev, orig.revdiff(orig.node(baserev), rev))
1733 cachedelta = (baserev, orig.revdiff(orig.node(baserev), rev))
1732
1734
1733 return ((text, tr, linkrev, p1, p2),
1735 return ((text, tr, linkrev, p1, p2),
1734 {'node': node, 'flags': flags, 'cachedelta': cachedelta})
1736 {'node': node, 'flags': flags, 'cachedelta': cachedelta})
1735
1737
1736 @contextlib.contextmanager
1738 @contextlib.contextmanager
1737 def _temprevlog(ui, orig, truncaterev):
1739 def _temprevlog(ui, orig, truncaterev):
1738 from mercurial import vfs as vfsmod
1740 from mercurial import vfs as vfsmod
1739
1741
1740 if orig._inline:
1742 if orig._inline:
1741 raise error.Abort('not supporting inline revlog (yet)')
1743 raise error.Abort('not supporting inline revlog (yet)')
1742
1744
1743 origindexpath = orig.opener.join(orig.indexfile)
1745 origindexpath = orig.opener.join(orig.indexfile)
1744 origdatapath = orig.opener.join(orig.datafile)
1746 origdatapath = orig.opener.join(orig.datafile)
1745 indexname = 'revlog.i'
1747 indexname = 'revlog.i'
1746 dataname = 'revlog.d'
1748 dataname = 'revlog.d'
1747
1749
1748 tmpdir = tempfile.mkdtemp(prefix='tmp-hgperf-')
1750 tmpdir = tempfile.mkdtemp(prefix='tmp-hgperf-')
1749 try:
1751 try:
1750 # copy the data file in a temporary directory
1752 # copy the data file in a temporary directory
1751 ui.debug('copying data in %s\n' % tmpdir)
1753 ui.debug('copying data in %s\n' % tmpdir)
1752 destindexpath = os.path.join(tmpdir, 'revlog.i')
1754 destindexpath = os.path.join(tmpdir, 'revlog.i')
1753 destdatapath = os.path.join(tmpdir, 'revlog.d')
1755 destdatapath = os.path.join(tmpdir, 'revlog.d')
1754 shutil.copyfile(origindexpath, destindexpath)
1756 shutil.copyfile(origindexpath, destindexpath)
1755 shutil.copyfile(origdatapath, destdatapath)
1757 shutil.copyfile(origdatapath, destdatapath)
1756
1758
1757 # remove the data we want to add again
1759 # remove the data we want to add again
1758 ui.debug('truncating data to be rewritten\n')
1760 ui.debug('truncating data to be rewritten\n')
1759 with open(destindexpath, 'ab') as index:
1761 with open(destindexpath, 'ab') as index:
1760 index.seek(0)
1762 index.seek(0)
1761 index.truncate(truncaterev * orig._io.size)
1763 index.truncate(truncaterev * orig._io.size)
1762 with open(destdatapath, 'ab') as data:
1764 with open(destdatapath, 'ab') as data:
1763 data.seek(0)
1765 data.seek(0)
1764 data.truncate(orig.start(truncaterev))
1766 data.truncate(orig.start(truncaterev))
1765
1767
1766 # instantiate a new revlog from the temporary copy
1768 # instantiate a new revlog from the temporary copy
1767 ui.debug('truncating adding to be rewritten\n')
1769 ui.debug('truncating adding to be rewritten\n')
1768 vfs = vfsmod.vfs(tmpdir)
1770 vfs = vfsmod.vfs(tmpdir)
1769 vfs.options = getattr(orig.opener, 'options', None)
1771 vfs.options = getattr(orig.opener, 'options', None)
1770
1772
1771 dest = revlog.revlog(vfs,
1773 dest = revlog.revlog(vfs,
1772 indexfile=indexname,
1774 indexfile=indexname,
1773 datafile=dataname)
1775 datafile=dataname)
1774 if dest._inline:
1776 if dest._inline:
1775 raise error.Abort('not supporting inline revlog (yet)')
1777 raise error.Abort('not supporting inline revlog (yet)')
1776 # make sure internals are initialized
1778 # make sure internals are initialized
1777 dest.revision(len(dest) - 1)
1779 dest.revision(len(dest) - 1)
1778 yield dest
1780 yield dest
1779 del dest, vfs
1781 del dest, vfs
1780 finally:
1782 finally:
1781 shutil.rmtree(tmpdir, True)
1783 shutil.rmtree(tmpdir, True)
1782
1784
1783 @command(b'perfrevlogchunks', revlogopts + formatteropts +
1785 @command(b'perfrevlogchunks', revlogopts + formatteropts +
1784 [(b'e', b'engines', b'', b'compression engines to use'),
1786 [(b'e', b'engines', b'', b'compression engines to use'),
1785 (b's', b'startrev', 0, b'revision to start at')],
1787 (b's', b'startrev', 0, b'revision to start at')],
1786 b'-c|-m|FILE')
1788 b'-c|-m|FILE')
1787 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1789 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1788 """Benchmark operations on revlog chunks.
1790 """Benchmark operations on revlog chunks.
1789
1791
1790 Logically, each revlog is a collection of fulltext revisions. However,
1792 Logically, each revlog is a collection of fulltext revisions. However,
1791 stored within each revlog are "chunks" of possibly compressed data. This
1793 stored within each revlog are "chunks" of possibly compressed data. This
1792 data needs to be read and decompressed or compressed and written.
1794 data needs to be read and decompressed or compressed and written.
1793
1795
1794 This command measures the time it takes to read+decompress and recompress
1796 This command measures the time it takes to read+decompress and recompress
1795 chunks in a revlog. It effectively isolates I/O and compression performance.
1797 chunks in a revlog. It effectively isolates I/O and compression performance.
1796 For measurements of higher-level operations like resolving revisions,
1798 For measurements of higher-level operations like resolving revisions,
1797 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1799 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1798 """
1800 """
1799 opts = _byteskwargs(opts)
1801 opts = _byteskwargs(opts)
1800
1802
1801 rl = cmdutil.openrevlog(repo, b'perfrevlogchunks', file_, opts)
1803 rl = cmdutil.openrevlog(repo, b'perfrevlogchunks', file_, opts)
1802
1804
1803 # _chunkraw was renamed to _getsegmentforrevs.
1805 # _chunkraw was renamed to _getsegmentforrevs.
1804 try:
1806 try:
1805 segmentforrevs = rl._getsegmentforrevs
1807 segmentforrevs = rl._getsegmentforrevs
1806 except AttributeError:
1808 except AttributeError:
1807 segmentforrevs = rl._chunkraw
1809 segmentforrevs = rl._chunkraw
1808
1810
1809 # Verify engines argument.
1811 # Verify engines argument.
1810 if engines:
1812 if engines:
1811 engines = set(e.strip() for e in engines.split(b','))
1813 engines = set(e.strip() for e in engines.split(b','))
1812 for engine in engines:
1814 for engine in engines:
1813 try:
1815 try:
1814 util.compressionengines[engine]
1816 util.compressionengines[engine]
1815 except KeyError:
1817 except KeyError:
1816 raise error.Abort(b'unknown compression engine: %s' % engine)
1818 raise error.Abort(b'unknown compression engine: %s' % engine)
1817 else:
1819 else:
1818 engines = []
1820 engines = []
1819 for e in util.compengines:
1821 for e in util.compengines:
1820 engine = util.compengines[e]
1822 engine = util.compengines[e]
1821 try:
1823 try:
1822 if engine.available():
1824 if engine.available():
1823 engine.revlogcompressor().compress(b'dummy')
1825 engine.revlogcompressor().compress(b'dummy')
1824 engines.append(e)
1826 engines.append(e)
1825 except NotImplementedError:
1827 except NotImplementedError:
1826 pass
1828 pass
1827
1829
1828 revs = list(rl.revs(startrev, len(rl) - 1))
1830 revs = list(rl.revs(startrev, len(rl) - 1))
1829
1831
1830 def rlfh(rl):
1832 def rlfh(rl):
1831 if rl._inline:
1833 if rl._inline:
1832 return getsvfs(repo)(rl.indexfile)
1834 return getsvfs(repo)(rl.indexfile)
1833 else:
1835 else:
1834 return getsvfs(repo)(rl.datafile)
1836 return getsvfs(repo)(rl.datafile)
1835
1837
1836 def doread():
1838 def doread():
1837 rl.clearcaches()
1839 rl.clearcaches()
1838 for rev in revs:
1840 for rev in revs:
1839 segmentforrevs(rev, rev)
1841 segmentforrevs(rev, rev)
1840
1842
1841 def doreadcachedfh():
1843 def doreadcachedfh():
1842 rl.clearcaches()
1844 rl.clearcaches()
1843 fh = rlfh(rl)
1845 fh = rlfh(rl)
1844 for rev in revs:
1846 for rev in revs:
1845 segmentforrevs(rev, rev, df=fh)
1847 segmentforrevs(rev, rev, df=fh)
1846
1848
1847 def doreadbatch():
1849 def doreadbatch():
1848 rl.clearcaches()
1850 rl.clearcaches()
1849 segmentforrevs(revs[0], revs[-1])
1851 segmentforrevs(revs[0], revs[-1])
1850
1852
1851 def doreadbatchcachedfh():
1853 def doreadbatchcachedfh():
1852 rl.clearcaches()
1854 rl.clearcaches()
1853 fh = rlfh(rl)
1855 fh = rlfh(rl)
1854 segmentforrevs(revs[0], revs[-1], df=fh)
1856 segmentforrevs(revs[0], revs[-1], df=fh)
1855
1857
1856 def dochunk():
1858 def dochunk():
1857 rl.clearcaches()
1859 rl.clearcaches()
1858 fh = rlfh(rl)
1860 fh = rlfh(rl)
1859 for rev in revs:
1861 for rev in revs:
1860 rl._chunk(rev, df=fh)
1862 rl._chunk(rev, df=fh)
1861
1863
1862 chunks = [None]
1864 chunks = [None]
1863
1865
1864 def dochunkbatch():
1866 def dochunkbatch():
1865 rl.clearcaches()
1867 rl.clearcaches()
1866 fh = rlfh(rl)
1868 fh = rlfh(rl)
1867 # Save chunks as a side-effect.
1869 # Save chunks as a side-effect.
1868 chunks[0] = rl._chunks(revs, df=fh)
1870 chunks[0] = rl._chunks(revs, df=fh)
1869
1871
1870 def docompress(compressor):
1872 def docompress(compressor):
1871 rl.clearcaches()
1873 rl.clearcaches()
1872
1874
1873 try:
1875 try:
1874 # Swap in the requested compression engine.
1876 # Swap in the requested compression engine.
1875 oldcompressor = rl._compressor
1877 oldcompressor = rl._compressor
1876 rl._compressor = compressor
1878 rl._compressor = compressor
1877 for chunk in chunks[0]:
1879 for chunk in chunks[0]:
1878 rl.compress(chunk)
1880 rl.compress(chunk)
1879 finally:
1881 finally:
1880 rl._compressor = oldcompressor
1882 rl._compressor = oldcompressor
1881
1883
1882 benches = [
1884 benches = [
1883 (lambda: doread(), b'read'),
1885 (lambda: doread(), b'read'),
1884 (lambda: doreadcachedfh(), b'read w/ reused fd'),
1886 (lambda: doreadcachedfh(), b'read w/ reused fd'),
1885 (lambda: doreadbatch(), b'read batch'),
1887 (lambda: doreadbatch(), b'read batch'),
1886 (lambda: doreadbatchcachedfh(), b'read batch w/ reused fd'),
1888 (lambda: doreadbatchcachedfh(), b'read batch w/ reused fd'),
1887 (lambda: dochunk(), b'chunk'),
1889 (lambda: dochunk(), b'chunk'),
1888 (lambda: dochunkbatch(), b'chunk batch'),
1890 (lambda: dochunkbatch(), b'chunk batch'),
1889 ]
1891 ]
1890
1892
1891 for engine in sorted(engines):
1893 for engine in sorted(engines):
1892 compressor = util.compengines[engine].revlogcompressor()
1894 compressor = util.compengines[engine].revlogcompressor()
1893 benches.append((functools.partial(docompress, compressor),
1895 benches.append((functools.partial(docompress, compressor),
1894 b'compress w/ %s' % engine))
1896 b'compress w/ %s' % engine))
1895
1897
1896 for fn, title in benches:
1898 for fn, title in benches:
1897 timer, fm = gettimer(ui, opts)
1899 timer, fm = gettimer(ui, opts)
1898 timer(fn, title=title)
1900 timer(fn, title=title)
1899 fm.end()
1901 fm.end()
1900
1902
1901 @command(b'perfrevlogrevision', revlogopts + formatteropts +
1903 @command(b'perfrevlogrevision', revlogopts + formatteropts +
1902 [(b'', b'cache', False, b'use caches instead of clearing')],
1904 [(b'', b'cache', False, b'use caches instead of clearing')],
1903 b'-c|-m|FILE REV')
1905 b'-c|-m|FILE REV')
1904 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1906 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1905 """Benchmark obtaining a revlog revision.
1907 """Benchmark obtaining a revlog revision.
1906
1908
1907 Obtaining a revlog revision consists of roughly the following steps:
1909 Obtaining a revlog revision consists of roughly the following steps:
1908
1910
1909 1. Compute the delta chain
1911 1. Compute the delta chain
1910 2. Slice the delta chain if applicable
1912 2. Slice the delta chain if applicable
1911 3. Obtain the raw chunks for that delta chain
1913 3. Obtain the raw chunks for that delta chain
1912 4. Decompress each raw chunk
1914 4. Decompress each raw chunk
1913 5. Apply binary patches to obtain fulltext
1915 5. Apply binary patches to obtain fulltext
1914 6. Verify hash of fulltext
1916 6. Verify hash of fulltext
1915
1917
1916 This command measures the time spent in each of these phases.
1918 This command measures the time spent in each of these phases.
1917 """
1919 """
1918 opts = _byteskwargs(opts)
1920 opts = _byteskwargs(opts)
1919
1921
1920 if opts.get(b'changelog') or opts.get(b'manifest'):
1922 if opts.get(b'changelog') or opts.get(b'manifest'):
1921 file_, rev = None, file_
1923 file_, rev = None, file_
1922 elif rev is None:
1924 elif rev is None:
1923 raise error.CommandError(b'perfrevlogrevision', b'invalid arguments')
1925 raise error.CommandError(b'perfrevlogrevision', b'invalid arguments')
1924
1926
1925 r = cmdutil.openrevlog(repo, b'perfrevlogrevision', file_, opts)
1927 r = cmdutil.openrevlog(repo, b'perfrevlogrevision', file_, opts)
1926
1928
1927 # _chunkraw was renamed to _getsegmentforrevs.
1929 # _chunkraw was renamed to _getsegmentforrevs.
1928 try:
1930 try:
1929 segmentforrevs = r._getsegmentforrevs
1931 segmentforrevs = r._getsegmentforrevs
1930 except AttributeError:
1932 except AttributeError:
1931 segmentforrevs = r._chunkraw
1933 segmentforrevs = r._chunkraw
1932
1934
1933 node = r.lookup(rev)
1935 node = r.lookup(rev)
1934 rev = r.rev(node)
1936 rev = r.rev(node)
1935
1937
1936 def getrawchunks(data, chain):
1938 def getrawchunks(data, chain):
1937 start = r.start
1939 start = r.start
1938 length = r.length
1940 length = r.length
1939 inline = r._inline
1941 inline = r._inline
1940 iosize = r._io.size
1942 iosize = r._io.size
1941 buffer = util.buffer
1943 buffer = util.buffer
1942
1944
1943 chunks = []
1945 chunks = []
1944 ladd = chunks.append
1946 ladd = chunks.append
1945 for idx, item in enumerate(chain):
1947 for idx, item in enumerate(chain):
1946 offset = start(item[0])
1948 offset = start(item[0])
1947 bits = data[idx]
1949 bits = data[idx]
1948 for rev in item:
1950 for rev in item:
1949 chunkstart = start(rev)
1951 chunkstart = start(rev)
1950 if inline:
1952 if inline:
1951 chunkstart += (rev + 1) * iosize
1953 chunkstart += (rev + 1) * iosize
1952 chunklength = length(rev)
1954 chunklength = length(rev)
1953 ladd(buffer(bits, chunkstart - offset, chunklength))
1955 ladd(buffer(bits, chunkstart - offset, chunklength))
1954
1956
1955 return chunks
1957 return chunks
1956
1958
1957 def dodeltachain(rev):
1959 def dodeltachain(rev):
1958 if not cache:
1960 if not cache:
1959 r.clearcaches()
1961 r.clearcaches()
1960 r._deltachain(rev)
1962 r._deltachain(rev)
1961
1963
1962 def doread(chain):
1964 def doread(chain):
1963 if not cache:
1965 if not cache:
1964 r.clearcaches()
1966 r.clearcaches()
1965 for item in slicedchain:
1967 for item in slicedchain:
1966 segmentforrevs(item[0], item[-1])
1968 segmentforrevs(item[0], item[-1])
1967
1969
1968 def doslice(r, chain, size):
1970 def doslice(r, chain, size):
1969 for s in slicechunk(r, chain, targetsize=size):
1971 for s in slicechunk(r, chain, targetsize=size):
1970 pass
1972 pass
1971
1973
1972 def dorawchunks(data, chain):
1974 def dorawchunks(data, chain):
1973 if not cache:
1975 if not cache:
1974 r.clearcaches()
1976 r.clearcaches()
1975 getrawchunks(data, chain)
1977 getrawchunks(data, chain)
1976
1978
1977 def dodecompress(chunks):
1979 def dodecompress(chunks):
1978 decomp = r.decompress
1980 decomp = r.decompress
1979 for chunk in chunks:
1981 for chunk in chunks:
1980 decomp(chunk)
1982 decomp(chunk)
1981
1983
1982 def dopatch(text, bins):
1984 def dopatch(text, bins):
1983 if not cache:
1985 if not cache:
1984 r.clearcaches()
1986 r.clearcaches()
1985 mdiff.patches(text, bins)
1987 mdiff.patches(text, bins)
1986
1988
1987 def dohash(text):
1989 def dohash(text):
1988 if not cache:
1990 if not cache:
1989 r.clearcaches()
1991 r.clearcaches()
1990 r.checkhash(text, node, rev=rev)
1992 r.checkhash(text, node, rev=rev)
1991
1993
1992 def dorevision():
1994 def dorevision():
1993 if not cache:
1995 if not cache:
1994 r.clearcaches()
1996 r.clearcaches()
1995 r.revision(node)
1997 r.revision(node)
1996
1998
1997 try:
1999 try:
1998 from mercurial.revlogutils.deltas import slicechunk
2000 from mercurial.revlogutils.deltas import slicechunk
1999 except ImportError:
2001 except ImportError:
2000 slicechunk = getattr(revlog, '_slicechunk', None)
2002 slicechunk = getattr(revlog, '_slicechunk', None)
2001
2003
2002 size = r.length(rev)
2004 size = r.length(rev)
2003 chain = r._deltachain(rev)[0]
2005 chain = r._deltachain(rev)[0]
2004 if not getattr(r, '_withsparseread', False):
2006 if not getattr(r, '_withsparseread', False):
2005 slicedchain = (chain,)
2007 slicedchain = (chain,)
2006 else:
2008 else:
2007 slicedchain = tuple(slicechunk(r, chain, targetsize=size))
2009 slicedchain = tuple(slicechunk(r, chain, targetsize=size))
2008 data = [segmentforrevs(seg[0], seg[-1])[1] for seg in slicedchain]
2010 data = [segmentforrevs(seg[0], seg[-1])[1] for seg in slicedchain]
2009 rawchunks = getrawchunks(data, slicedchain)
2011 rawchunks = getrawchunks(data, slicedchain)
2010 bins = r._chunks(chain)
2012 bins = r._chunks(chain)
2011 text = bytes(bins[0])
2013 text = bytes(bins[0])
2012 bins = bins[1:]
2014 bins = bins[1:]
2013 text = mdiff.patches(text, bins)
2015 text = mdiff.patches(text, bins)
2014
2016
2015 benches = [
2017 benches = [
2016 (lambda: dorevision(), b'full'),
2018 (lambda: dorevision(), b'full'),
2017 (lambda: dodeltachain(rev), b'deltachain'),
2019 (lambda: dodeltachain(rev), b'deltachain'),
2018 (lambda: doread(chain), b'read'),
2020 (lambda: doread(chain), b'read'),
2019 ]
2021 ]
2020
2022
2021 if getattr(r, '_withsparseread', False):
2023 if getattr(r, '_withsparseread', False):
2022 slicing = (lambda: doslice(r, chain, size), b'slice-sparse-chain')
2024 slicing = (lambda: doslice(r, chain, size), b'slice-sparse-chain')
2023 benches.append(slicing)
2025 benches.append(slicing)
2024
2026
2025 benches.extend([
2027 benches.extend([
2026 (lambda: dorawchunks(data, slicedchain), b'rawchunks'),
2028 (lambda: dorawchunks(data, slicedchain), b'rawchunks'),
2027 (lambda: dodecompress(rawchunks), b'decompress'),
2029 (lambda: dodecompress(rawchunks), b'decompress'),
2028 (lambda: dopatch(text, bins), b'patch'),
2030 (lambda: dopatch(text, bins), b'patch'),
2029 (lambda: dohash(text), b'hash'),
2031 (lambda: dohash(text), b'hash'),
2030 ])
2032 ])
2031
2033
2032 timer, fm = gettimer(ui, opts)
2034 timer, fm = gettimer(ui, opts)
2033 for fn, title in benches:
2035 for fn, title in benches:
2034 timer(fn, title=title)
2036 timer(fn, title=title)
2035 fm.end()
2037 fm.end()
2036
2038
2037 @command(b'perfrevset',
2039 @command(b'perfrevset',
2038 [(b'C', b'clear', False, b'clear volatile cache between each call.'),
2040 [(b'C', b'clear', False, b'clear volatile cache between each call.'),
2039 (b'', b'contexts', False, b'obtain changectx for each revision')]
2041 (b'', b'contexts', False, b'obtain changectx for each revision')]
2040 + formatteropts, b"REVSET")
2042 + formatteropts, b"REVSET")
2041 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
2043 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
2042 """benchmark the execution time of a revset
2044 """benchmark the execution time of a revset
2043
2045
2044 Use the --clean option if need to evaluate the impact of build volatile
2046 Use the --clean option if need to evaluate the impact of build volatile
2045 revisions set cache on the revset execution. Volatile cache hold filtered
2047 revisions set cache on the revset execution. Volatile cache hold filtered
2046 and obsolete related cache."""
2048 and obsolete related cache."""
2047 opts = _byteskwargs(opts)
2049 opts = _byteskwargs(opts)
2048
2050
2049 timer, fm = gettimer(ui, opts)
2051 timer, fm = gettimer(ui, opts)
2050 def d():
2052 def d():
2051 if clear:
2053 if clear:
2052 repo.invalidatevolatilesets()
2054 repo.invalidatevolatilesets()
2053 if contexts:
2055 if contexts:
2054 for ctx in repo.set(expr): pass
2056 for ctx in repo.set(expr): pass
2055 else:
2057 else:
2056 for r in repo.revs(expr): pass
2058 for r in repo.revs(expr): pass
2057 timer(d)
2059 timer(d)
2058 fm.end()
2060 fm.end()
2059
2061
2060 @command(b'perfvolatilesets',
2062 @command(b'perfvolatilesets',
2061 [(b'', b'clear-obsstore', False, b'drop obsstore between each call.'),
2063 [(b'', b'clear-obsstore', False, b'drop obsstore between each call.'),
2062 ] + formatteropts)
2064 ] + formatteropts)
2063 def perfvolatilesets(ui, repo, *names, **opts):
2065 def perfvolatilesets(ui, repo, *names, **opts):
2064 """benchmark the computation of various volatile set
2066 """benchmark the computation of various volatile set
2065
2067
2066 Volatile set computes element related to filtering and obsolescence."""
2068 Volatile set computes element related to filtering and obsolescence."""
2067 opts = _byteskwargs(opts)
2069 opts = _byteskwargs(opts)
2068 timer, fm = gettimer(ui, opts)
2070 timer, fm = gettimer(ui, opts)
2069 repo = repo.unfiltered()
2071 repo = repo.unfiltered()
2070
2072
2071 def getobs(name):
2073 def getobs(name):
2072 def d():
2074 def d():
2073 repo.invalidatevolatilesets()
2075 repo.invalidatevolatilesets()
2074 if opts[b'clear_obsstore']:
2076 if opts[b'clear_obsstore']:
2075 clearfilecache(repo, b'obsstore')
2077 clearfilecache(repo, b'obsstore')
2076 obsolete.getrevs(repo, name)
2078 obsolete.getrevs(repo, name)
2077 return d
2079 return d
2078
2080
2079 allobs = sorted(obsolete.cachefuncs)
2081 allobs = sorted(obsolete.cachefuncs)
2080 if names:
2082 if names:
2081 allobs = [n for n in allobs if n in names]
2083 allobs = [n for n in allobs if n in names]
2082
2084
2083 for name in allobs:
2085 for name in allobs:
2084 timer(getobs(name), title=name)
2086 timer(getobs(name), title=name)
2085
2087
2086 def getfiltered(name):
2088 def getfiltered(name):
2087 def d():
2089 def d():
2088 repo.invalidatevolatilesets()
2090 repo.invalidatevolatilesets()
2089 if opts[b'clear_obsstore']:
2091 if opts[b'clear_obsstore']:
2090 clearfilecache(repo, b'obsstore')
2092 clearfilecache(repo, b'obsstore')
2091 repoview.filterrevs(repo, name)
2093 repoview.filterrevs(repo, name)
2092 return d
2094 return d
2093
2095
2094 allfilter = sorted(repoview.filtertable)
2096 allfilter = sorted(repoview.filtertable)
2095 if names:
2097 if names:
2096 allfilter = [n for n in allfilter if n in names]
2098 allfilter = [n for n in allfilter if n in names]
2097
2099
2098 for name in allfilter:
2100 for name in allfilter:
2099 timer(getfiltered(name), title=name)
2101 timer(getfiltered(name), title=name)
2100 fm.end()
2102 fm.end()
2101
2103
2102 @command(b'perfbranchmap',
2104 @command(b'perfbranchmap',
2103 [(b'f', b'full', False,
2105 [(b'f', b'full', False,
2104 b'Includes build time of subset'),
2106 b'Includes build time of subset'),
2105 (b'', b'clear-revbranch', False,
2107 (b'', b'clear-revbranch', False,
2106 b'purge the revbranch cache between computation'),
2108 b'purge the revbranch cache between computation'),
2107 ] + formatteropts)
2109 ] + formatteropts)
2108 def perfbranchmap(ui, repo, *filternames, **opts):
2110 def perfbranchmap(ui, repo, *filternames, **opts):
2109 """benchmark the update of a branchmap
2111 """benchmark the update of a branchmap
2110
2112
2111 This benchmarks the full repo.branchmap() call with read and write disabled
2113 This benchmarks the full repo.branchmap() call with read and write disabled
2112 """
2114 """
2113 opts = _byteskwargs(opts)
2115 opts = _byteskwargs(opts)
2114 full = opts.get(b"full", False)
2116 full = opts.get(b"full", False)
2115 clear_revbranch = opts.get(b"clear_revbranch", False)
2117 clear_revbranch = opts.get(b"clear_revbranch", False)
2116 timer, fm = gettimer(ui, opts)
2118 timer, fm = gettimer(ui, opts)
2117 def getbranchmap(filtername):
2119 def getbranchmap(filtername):
2118 """generate a benchmark function for the filtername"""
2120 """generate a benchmark function for the filtername"""
2119 if filtername is None:
2121 if filtername is None:
2120 view = repo
2122 view = repo
2121 else:
2123 else:
2122 view = repo.filtered(filtername)
2124 view = repo.filtered(filtername)
2123 def d():
2125 def d():
2124 if clear_revbranch:
2126 if clear_revbranch:
2125 repo.revbranchcache()._clear()
2127 repo.revbranchcache()._clear()
2126 if full:
2128 if full:
2127 view._branchcaches.clear()
2129 view._branchcaches.clear()
2128 else:
2130 else:
2129 view._branchcaches.pop(filtername, None)
2131 view._branchcaches.pop(filtername, None)
2130 view.branchmap()
2132 view.branchmap()
2131 return d
2133 return d
2132 # add filter in smaller subset to bigger subset
2134 # add filter in smaller subset to bigger subset
2133 possiblefilters = set(repoview.filtertable)
2135 possiblefilters = set(repoview.filtertable)
2134 if filternames:
2136 if filternames:
2135 possiblefilters &= set(filternames)
2137 possiblefilters &= set(filternames)
2136 subsettable = getbranchmapsubsettable()
2138 subsettable = getbranchmapsubsettable()
2137 allfilters = []
2139 allfilters = []
2138 while possiblefilters:
2140 while possiblefilters:
2139 for name in possiblefilters:
2141 for name in possiblefilters:
2140 subset = subsettable.get(name)
2142 subset = subsettable.get(name)
2141 if subset not in possiblefilters:
2143 if subset not in possiblefilters:
2142 break
2144 break
2143 else:
2145 else:
2144 assert False, b'subset cycle %s!' % possiblefilters
2146 assert False, b'subset cycle %s!' % possiblefilters
2145 allfilters.append(name)
2147 allfilters.append(name)
2146 possiblefilters.remove(name)
2148 possiblefilters.remove(name)
2147
2149
2148 # warm the cache
2150 # warm the cache
2149 if not full:
2151 if not full:
2150 for name in allfilters:
2152 for name in allfilters:
2151 repo.filtered(name).branchmap()
2153 repo.filtered(name).branchmap()
2152 if not filternames or b'unfiltered' in filternames:
2154 if not filternames or b'unfiltered' in filternames:
2153 # add unfiltered
2155 # add unfiltered
2154 allfilters.append(None)
2156 allfilters.append(None)
2155
2157
2156 branchcacheread = safeattrsetter(branchmap, b'read')
2158 branchcacheread = safeattrsetter(branchmap, b'read')
2157 branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
2159 branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
2158 branchcacheread.set(lambda repo: None)
2160 branchcacheread.set(lambda repo: None)
2159 branchcachewrite.set(lambda bc, repo: None)
2161 branchcachewrite.set(lambda bc, repo: None)
2160 try:
2162 try:
2161 for name in allfilters:
2163 for name in allfilters:
2162 printname = name
2164 printname = name
2163 if name is None:
2165 if name is None:
2164 printname = b'unfiltered'
2166 printname = b'unfiltered'
2165 timer(getbranchmap(name), title=str(printname))
2167 timer(getbranchmap(name), title=str(printname))
2166 finally:
2168 finally:
2167 branchcacheread.restore()
2169 branchcacheread.restore()
2168 branchcachewrite.restore()
2170 branchcachewrite.restore()
2169 fm.end()
2171 fm.end()
2170
2172
2171 @command(b'perfbranchmapload', [
2173 @command(b'perfbranchmapload', [
2172 (b'f', b'filter', b'', b'Specify repoview filter'),
2174 (b'f', b'filter', b'', b'Specify repoview filter'),
2173 (b'', b'list', False, b'List brachmap filter caches'),
2175 (b'', b'list', False, b'List brachmap filter caches'),
2174 ] + formatteropts)
2176 ] + formatteropts)
2175 def perfbranchmapread(ui, repo, filter=b'', list=False, **opts):
2177 def perfbranchmapread(ui, repo, filter=b'', list=False, **opts):
2176 """benchmark reading the branchmap"""
2178 """benchmark reading the branchmap"""
2177 opts = _byteskwargs(opts)
2179 opts = _byteskwargs(opts)
2178
2180
2179 if list:
2181 if list:
2180 for name, kind, st in repo.cachevfs.readdir(stat=True):
2182 for name, kind, st in repo.cachevfs.readdir(stat=True):
2181 if name.startswith(b'branch2'):
2183 if name.startswith(b'branch2'):
2182 filtername = name.partition(b'-')[2] or b'unfiltered'
2184 filtername = name.partition(b'-')[2] or b'unfiltered'
2183 ui.status(b'%s - %s\n'
2185 ui.status(b'%s - %s\n'
2184 % (filtername, util.bytecount(st.st_size)))
2186 % (filtername, util.bytecount(st.st_size)))
2185 return
2187 return
2186 if filter:
2188 if filter:
2187 repo = repoview.repoview(repo, filter)
2189 repo = repoview.repoview(repo, filter)
2188 else:
2190 else:
2189 repo = repo.unfiltered()
2191 repo = repo.unfiltered()
2190 # try once without timer, the filter may not be cached
2192 # try once without timer, the filter may not be cached
2191 if branchmap.read(repo) is None:
2193 if branchmap.read(repo) is None:
2192 raise error.Abort(b'No brachmap cached for %s repo'
2194 raise error.Abort(b'No brachmap cached for %s repo'
2193 % (filter or b'unfiltered'))
2195 % (filter or b'unfiltered'))
2194 timer, fm = gettimer(ui, opts)
2196 timer, fm = gettimer(ui, opts)
2195 timer(lambda: branchmap.read(repo) and None)
2197 timer(lambda: branchmap.read(repo) and None)
2196 fm.end()
2198 fm.end()
2197
2199
2198 @command(b'perfloadmarkers')
2200 @command(b'perfloadmarkers')
2199 def perfloadmarkers(ui, repo):
2201 def perfloadmarkers(ui, repo):
2200 """benchmark the time to parse the on-disk markers for a repo
2202 """benchmark the time to parse the on-disk markers for a repo
2201
2203
2202 Result is the number of markers in the repo."""
2204 Result is the number of markers in the repo."""
2203 timer, fm = gettimer(ui)
2205 timer, fm = gettimer(ui)
2204 svfs = getsvfs(repo)
2206 svfs = getsvfs(repo)
2205 timer(lambda: len(obsolete.obsstore(svfs)))
2207 timer(lambda: len(obsolete.obsstore(svfs)))
2206 fm.end()
2208 fm.end()
2207
2209
2208 @command(b'perflrucachedict', formatteropts +
2210 @command(b'perflrucachedict', formatteropts +
2209 [(b'', b'costlimit', 0, b'maximum total cost of items in cache'),
2211 [(b'', b'costlimit', 0, b'maximum total cost of items in cache'),
2210 (b'', b'mincost', 0, b'smallest cost of items in cache'),
2212 (b'', b'mincost', 0, b'smallest cost of items in cache'),
2211 (b'', b'maxcost', 100, b'maximum cost of items in cache'),
2213 (b'', b'maxcost', 100, b'maximum cost of items in cache'),
2212 (b'', b'size', 4, b'size of cache'),
2214 (b'', b'size', 4, b'size of cache'),
2213 (b'', b'gets', 10000, b'number of key lookups'),
2215 (b'', b'gets', 10000, b'number of key lookups'),
2214 (b'', b'sets', 10000, b'number of key sets'),
2216 (b'', b'sets', 10000, b'number of key sets'),
2215 (b'', b'mixed', 10000, b'number of mixed mode operations'),
2217 (b'', b'mixed', 10000, b'number of mixed mode operations'),
2216 (b'', b'mixedgetfreq', 50, b'frequency of get vs set ops in mixed mode')],
2218 (b'', b'mixedgetfreq', 50, b'frequency of get vs set ops in mixed mode')],
2217 norepo=True)
2219 norepo=True)
2218 def perflrucache(ui, mincost=0, maxcost=100, costlimit=0, size=4,
2220 def perflrucache(ui, mincost=0, maxcost=100, costlimit=0, size=4,
2219 gets=10000, sets=10000, mixed=10000, mixedgetfreq=50, **opts):
2221 gets=10000, sets=10000, mixed=10000, mixedgetfreq=50, **opts):
2220 opts = _byteskwargs(opts)
2222 opts = _byteskwargs(opts)
2221
2223
2222 def doinit():
2224 def doinit():
2223 for i in _xrange(10000):
2225 for i in _xrange(10000):
2224 util.lrucachedict(size)
2226 util.lrucachedict(size)
2225
2227
2226 costrange = list(range(mincost, maxcost + 1))
2228 costrange = list(range(mincost, maxcost + 1))
2227
2229
2228 values = []
2230 values = []
2229 for i in _xrange(size):
2231 for i in _xrange(size):
2230 values.append(random.randint(0, _maxint))
2232 values.append(random.randint(0, _maxint))
2231
2233
2232 # Get mode fills the cache and tests raw lookup performance with no
2234 # Get mode fills the cache and tests raw lookup performance with no
2233 # eviction.
2235 # eviction.
2234 getseq = []
2236 getseq = []
2235 for i in _xrange(gets):
2237 for i in _xrange(gets):
2236 getseq.append(random.choice(values))
2238 getseq.append(random.choice(values))
2237
2239
2238 def dogets():
2240 def dogets():
2239 d = util.lrucachedict(size)
2241 d = util.lrucachedict(size)
2240 for v in values:
2242 for v in values:
2241 d[v] = v
2243 d[v] = v
2242 for key in getseq:
2244 for key in getseq:
2243 value = d[key]
2245 value = d[key]
2244 value # silence pyflakes warning
2246 value # silence pyflakes warning
2245
2247
2246 def dogetscost():
2248 def dogetscost():
2247 d = util.lrucachedict(size, maxcost=costlimit)
2249 d = util.lrucachedict(size, maxcost=costlimit)
2248 for i, v in enumerate(values):
2250 for i, v in enumerate(values):
2249 d.insert(v, v, cost=costs[i])
2251 d.insert(v, v, cost=costs[i])
2250 for key in getseq:
2252 for key in getseq:
2251 try:
2253 try:
2252 value = d[key]
2254 value = d[key]
2253 value # silence pyflakes warning
2255 value # silence pyflakes warning
2254 except KeyError:
2256 except KeyError:
2255 pass
2257 pass
2256
2258
2257 # Set mode tests insertion speed with cache eviction.
2259 # Set mode tests insertion speed with cache eviction.
2258 setseq = []
2260 setseq = []
2259 costs = []
2261 costs = []
2260 for i in _xrange(sets):
2262 for i in _xrange(sets):
2261 setseq.append(random.randint(0, _maxint))
2263 setseq.append(random.randint(0, _maxint))
2262 costs.append(random.choice(costrange))
2264 costs.append(random.choice(costrange))
2263
2265
2264 def doinserts():
2266 def doinserts():
2265 d = util.lrucachedict(size)
2267 d = util.lrucachedict(size)
2266 for v in setseq:
2268 for v in setseq:
2267 d.insert(v, v)
2269 d.insert(v, v)
2268
2270
2269 def doinsertscost():
2271 def doinsertscost():
2270 d = util.lrucachedict(size, maxcost=costlimit)
2272 d = util.lrucachedict(size, maxcost=costlimit)
2271 for i, v in enumerate(setseq):
2273 for i, v in enumerate(setseq):
2272 d.insert(v, v, cost=costs[i])
2274 d.insert(v, v, cost=costs[i])
2273
2275
2274 def dosets():
2276 def dosets():
2275 d = util.lrucachedict(size)
2277 d = util.lrucachedict(size)
2276 for v in setseq:
2278 for v in setseq:
2277 d[v] = v
2279 d[v] = v
2278
2280
2279 # Mixed mode randomly performs gets and sets with eviction.
2281 # Mixed mode randomly performs gets and sets with eviction.
2280 mixedops = []
2282 mixedops = []
2281 for i in _xrange(mixed):
2283 for i in _xrange(mixed):
2282 r = random.randint(0, 100)
2284 r = random.randint(0, 100)
2283 if r < mixedgetfreq:
2285 if r < mixedgetfreq:
2284 op = 0
2286 op = 0
2285 else:
2287 else:
2286 op = 1
2288 op = 1
2287
2289
2288 mixedops.append((op,
2290 mixedops.append((op,
2289 random.randint(0, size * 2),
2291 random.randint(0, size * 2),
2290 random.choice(costrange)))
2292 random.choice(costrange)))
2291
2293
2292 def domixed():
2294 def domixed():
2293 d = util.lrucachedict(size)
2295 d = util.lrucachedict(size)
2294
2296
2295 for op, v, cost in mixedops:
2297 for op, v, cost in mixedops:
2296 if op == 0:
2298 if op == 0:
2297 try:
2299 try:
2298 d[v]
2300 d[v]
2299 except KeyError:
2301 except KeyError:
2300 pass
2302 pass
2301 else:
2303 else:
2302 d[v] = v
2304 d[v] = v
2303
2305
2304 def domixedcost():
2306 def domixedcost():
2305 d = util.lrucachedict(size, maxcost=costlimit)
2307 d = util.lrucachedict(size, maxcost=costlimit)
2306
2308
2307 for op, v, cost in mixedops:
2309 for op, v, cost in mixedops:
2308 if op == 0:
2310 if op == 0:
2309 try:
2311 try:
2310 d[v]
2312 d[v]
2311 except KeyError:
2313 except KeyError:
2312 pass
2314 pass
2313 else:
2315 else:
2314 d.insert(v, v, cost=cost)
2316 d.insert(v, v, cost=cost)
2315
2317
2316 benches = [
2318 benches = [
2317 (doinit, b'init'),
2319 (doinit, b'init'),
2318 ]
2320 ]
2319
2321
2320 if costlimit:
2322 if costlimit:
2321 benches.extend([
2323 benches.extend([
2322 (dogetscost, b'gets w/ cost limit'),
2324 (dogetscost, b'gets w/ cost limit'),
2323 (doinsertscost, b'inserts w/ cost limit'),
2325 (doinsertscost, b'inserts w/ cost limit'),
2324 (domixedcost, b'mixed w/ cost limit'),
2326 (domixedcost, b'mixed w/ cost limit'),
2325 ])
2327 ])
2326 else:
2328 else:
2327 benches.extend([
2329 benches.extend([
2328 (dogets, b'gets'),
2330 (dogets, b'gets'),
2329 (doinserts, b'inserts'),
2331 (doinserts, b'inserts'),
2330 (dosets, b'sets'),
2332 (dosets, b'sets'),
2331 (domixed, b'mixed')
2333 (domixed, b'mixed')
2332 ])
2334 ])
2333
2335
2334 for fn, title in benches:
2336 for fn, title in benches:
2335 timer, fm = gettimer(ui, opts)
2337 timer, fm = gettimer(ui, opts)
2336 timer(fn, title=title)
2338 timer(fn, title=title)
2337 fm.end()
2339 fm.end()
2338
2340
2339 @command(b'perfwrite', formatteropts)
2341 @command(b'perfwrite', formatteropts)
2340 def perfwrite(ui, repo, **opts):
2342 def perfwrite(ui, repo, **opts):
2341 """microbenchmark ui.write
2343 """microbenchmark ui.write
2342 """
2344 """
2343 opts = _byteskwargs(opts)
2345 opts = _byteskwargs(opts)
2344
2346
2345 timer, fm = gettimer(ui, opts)
2347 timer, fm = gettimer(ui, opts)
2346 def write():
2348 def write():
2347 for i in range(100000):
2349 for i in range(100000):
2348 ui.write((b'Testing write performance\n'))
2350 ui.write((b'Testing write performance\n'))
2349 timer(write)
2351 timer(write)
2350 fm.end()
2352 fm.end()
2351
2353
2352 def uisetup(ui):
2354 def uisetup(ui):
2353 if (util.safehasattr(cmdutil, b'openrevlog') and
2355 if (util.safehasattr(cmdutil, b'openrevlog') and
2354 not util.safehasattr(commands, b'debugrevlogopts')):
2356 not util.safehasattr(commands, b'debugrevlogopts')):
2355 # for "historical portability":
2357 # for "historical portability":
2356 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
2358 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
2357 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
2359 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
2358 # openrevlog() should cause failure, because it has been
2360 # openrevlog() should cause failure, because it has been
2359 # available since 3.5 (or 49c583ca48c4).
2361 # available since 3.5 (or 49c583ca48c4).
2360 def openrevlog(orig, repo, cmd, file_, opts):
2362 def openrevlog(orig, repo, cmd, file_, opts):
2361 if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'):
2363 if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'):
2362 raise error.Abort(b"This version doesn't support --dir option",
2364 raise error.Abort(b"This version doesn't support --dir option",
2363 hint=b"use 3.5 or later")
2365 hint=b"use 3.5 or later")
2364 return orig(repo, cmd, file_, opts)
2366 return orig(repo, cmd, file_, opts)
2365 extensions.wrapfunction(cmdutil, b'openrevlog', openrevlog)
2367 extensions.wrapfunction(cmdutil, b'openrevlog', openrevlog)
General Comments 0
You need to be logged in to leave comments. Login now