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