##// END OF EJS Templates
perf: teach perfbdiff to call blocks() and to use xdiff...
Gregory Szorc -
r36784:d382344c default
parent child Browse files
Show More
@@ -1,1743 +1,1763
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, blocks, xdiff, 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 if xdiff:
947 mdiff.bdiff.xdiffblocks(*pair)
948 elif blocks:
949 mdiff.bdiff.blocks(*pair)
950 else:
946 mdiff.textdiff(*pair)
951 mdiff.textdiff(*pair)
947 q.task_done()
952 q.task_done()
948 pair = q.get()
953 pair = q.get()
949 q.task_done() # for the None one
954 q.task_done() # for the None one
950 with ready:
955 with ready:
951 ready.wait()
956 ready.wait()
952
957
953 @command('perfbdiff', revlogopts + formatteropts + [
958 @command('perfbdiff', revlogopts + formatteropts + [
954 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
959 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
955 ('', 'alldata', False, 'test bdiffs for all associated revisions'),
960 ('', 'alldata', False, 'test bdiffs for all associated revisions'),
956 ('', 'threads', 0, 'number of thread to use (disable with 0)'),
961 ('', 'threads', 0, 'number of thread to use (disable with 0)'),
962 ('', 'blocks', False, 'test computing diffs into blocks'),
963 ('', 'xdiff', False, 'use xdiff algorithm'),
957 ],
964 ],
958
965
959 '-c|-m|FILE REV')
966 '-c|-m|FILE REV')
960 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
967 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
961 """benchmark a bdiff between revisions
968 """benchmark a bdiff between revisions
962
969
963 By default, benchmark a bdiff between its delta parent and itself.
970 By default, benchmark a bdiff between its delta parent and itself.
964
971
965 With ``--count``, benchmark bdiffs between delta parents and self for N
972 With ``--count``, benchmark bdiffs between delta parents and self for N
966 revisions starting at the specified revision.
973 revisions starting at the specified revision.
967
974
968 With ``--alldata``, assume the requested revision is a changeset and
975 With ``--alldata``, assume the requested revision is a changeset and
969 measure bdiffs for all changes related to that changeset (manifest
976 measure bdiffs for all changes related to that changeset (manifest
970 and filelogs).
977 and filelogs).
971 """
978 """
979 opts = pycompat.byteskwargs(opts)
980
981 if opts['xdiff'] and not opts['blocks']:
982 raise error.CommandError('perfbdiff', '--xdiff requires --blocks')
983
972 if opts['alldata']:
984 if opts['alldata']:
973 opts['changelog'] = True
985 opts['changelog'] = True
974
986
975 if opts.get('changelog') or opts.get('manifest'):
987 if opts.get('changelog') or opts.get('manifest'):
976 file_, rev = None, file_
988 file_, rev = None, file_
977 elif rev is None:
989 elif rev is None:
978 raise error.CommandError('perfbdiff', 'invalid arguments')
990 raise error.CommandError('perfbdiff', 'invalid arguments')
979
991
992 blocks = opts['blocks']
993 xdiff = opts['xdiff']
980 textpairs = []
994 textpairs = []
981
995
982 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
996 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
983
997
984 startrev = r.rev(r.lookup(rev))
998 startrev = r.rev(r.lookup(rev))
985 for rev in range(startrev, min(startrev + count, len(r) - 1)):
999 for rev in range(startrev, min(startrev + count, len(r) - 1)):
986 if opts['alldata']:
1000 if opts['alldata']:
987 # Load revisions associated with changeset.
1001 # Load revisions associated with changeset.
988 ctx = repo[rev]
1002 ctx = repo[rev]
989 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
1003 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
990 for pctx in ctx.parents():
1004 for pctx in ctx.parents():
991 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
1005 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
992 textpairs.append((pman, mtext))
1006 textpairs.append((pman, mtext))
993
1007
994 # Load filelog revisions by iterating manifest delta.
1008 # Load filelog revisions by iterating manifest delta.
995 man = ctx.manifest()
1009 man = ctx.manifest()
996 pman = ctx.p1().manifest()
1010 pman = ctx.p1().manifest()
997 for filename, change in pman.diff(man).items():
1011 for filename, change in pman.diff(man).items():
998 fctx = repo.file(filename)
1012 fctx = repo.file(filename)
999 f1 = fctx.revision(change[0][0] or -1)
1013 f1 = fctx.revision(change[0][0] or -1)
1000 f2 = fctx.revision(change[1][0] or -1)
1014 f2 = fctx.revision(change[1][0] or -1)
1001 textpairs.append((f1, f2))
1015 textpairs.append((f1, f2))
1002 else:
1016 else:
1003 dp = r.deltaparent(rev)
1017 dp = r.deltaparent(rev)
1004 textpairs.append((r.revision(dp), r.revision(rev)))
1018 textpairs.append((r.revision(dp), r.revision(rev)))
1005
1019
1006 withthreads = threads > 0
1020 withthreads = threads > 0
1007 if not withthreads:
1021 if not withthreads:
1008 def d():
1022 def d():
1009 for pair in textpairs:
1023 for pair in textpairs:
1024 if xdiff:
1025 mdiff.bdiff.xdiffblocks(*pair)
1026 elif blocks:
1027 mdiff.bdiff.blocks(*pair)
1028 else:
1010 mdiff.textdiff(*pair)
1029 mdiff.textdiff(*pair)
1011 else:
1030 else:
1012 q = util.queue()
1031 q = util.queue()
1013 for i in xrange(threads):
1032 for i in xrange(threads):
1014 q.put(None)
1033 q.put(None)
1015 ready = threading.Condition()
1034 ready = threading.Condition()
1016 done = threading.Event()
1035 done = threading.Event()
1017 for i in xrange(threads):
1036 for i in xrange(threads):
1018 threading.Thread(target=_bdiffworker, args=(q, ready, done)).start()
1037 threading.Thread(target=_bdiffworker,
1038 args=(q, blocks, xdiff, ready, done)).start()
1019 q.join()
1039 q.join()
1020 def d():
1040 def d():
1021 for pair in textpairs:
1041 for pair in textpairs:
1022 q.put(pair)
1042 q.put(pair)
1023 for i in xrange(threads):
1043 for i in xrange(threads):
1024 q.put(None)
1044 q.put(None)
1025 with ready:
1045 with ready:
1026 ready.notify_all()
1046 ready.notify_all()
1027 q.join()
1047 q.join()
1028 timer, fm = gettimer(ui, opts)
1048 timer, fm = gettimer(ui, opts)
1029 timer(d)
1049 timer(d)
1030 fm.end()
1050 fm.end()
1031
1051
1032 if withthreads:
1052 if withthreads:
1033 done.set()
1053 done.set()
1034 for i in xrange(threads):
1054 for i in xrange(threads):
1035 q.put(None)
1055 q.put(None)
1036 with ready:
1056 with ready:
1037 ready.notify_all()
1057 ready.notify_all()
1038
1058
1039 @command('perfunidiff', revlogopts + formatteropts + [
1059 @command('perfunidiff', revlogopts + formatteropts + [
1040 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
1060 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
1041 ('', 'alldata', False, 'test unidiffs for all associated revisions'),
1061 ('', 'alldata', False, 'test unidiffs for all associated revisions'),
1042 ], '-c|-m|FILE REV')
1062 ], '-c|-m|FILE REV')
1043 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
1063 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
1044 """benchmark a unified diff between revisions
1064 """benchmark a unified diff between revisions
1045
1065
1046 This doesn't include any copy tracing - it's just a unified diff
1066 This doesn't include any copy tracing - it's just a unified diff
1047 of the texts.
1067 of the texts.
1048
1068
1049 By default, benchmark a diff between its delta parent and itself.
1069 By default, benchmark a diff between its delta parent and itself.
1050
1070
1051 With ``--count``, benchmark diffs between delta parents and self for N
1071 With ``--count``, benchmark diffs between delta parents and self for N
1052 revisions starting at the specified revision.
1072 revisions starting at the specified revision.
1053
1073
1054 With ``--alldata``, assume the requested revision is a changeset and
1074 With ``--alldata``, assume the requested revision is a changeset and
1055 measure diffs for all changes related to that changeset (manifest
1075 measure diffs for all changes related to that changeset (manifest
1056 and filelogs).
1076 and filelogs).
1057 """
1077 """
1058 if opts['alldata']:
1078 if opts['alldata']:
1059 opts['changelog'] = True
1079 opts['changelog'] = True
1060
1080
1061 if opts.get('changelog') or opts.get('manifest'):
1081 if opts.get('changelog') or opts.get('manifest'):
1062 file_, rev = None, file_
1082 file_, rev = None, file_
1063 elif rev is None:
1083 elif rev is None:
1064 raise error.CommandError('perfunidiff', 'invalid arguments')
1084 raise error.CommandError('perfunidiff', 'invalid arguments')
1065
1085
1066 textpairs = []
1086 textpairs = []
1067
1087
1068 r = cmdutil.openrevlog(repo, 'perfunidiff', file_, opts)
1088 r = cmdutil.openrevlog(repo, 'perfunidiff', file_, opts)
1069
1089
1070 startrev = r.rev(r.lookup(rev))
1090 startrev = r.rev(r.lookup(rev))
1071 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1091 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1072 if opts['alldata']:
1092 if opts['alldata']:
1073 # Load revisions associated with changeset.
1093 # Load revisions associated with changeset.
1074 ctx = repo[rev]
1094 ctx = repo[rev]
1075 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
1095 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
1076 for pctx in ctx.parents():
1096 for pctx in ctx.parents():
1077 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
1097 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
1078 textpairs.append((pman, mtext))
1098 textpairs.append((pman, mtext))
1079
1099
1080 # Load filelog revisions by iterating manifest delta.
1100 # Load filelog revisions by iterating manifest delta.
1081 man = ctx.manifest()
1101 man = ctx.manifest()
1082 pman = ctx.p1().manifest()
1102 pman = ctx.p1().manifest()
1083 for filename, change in pman.diff(man).items():
1103 for filename, change in pman.diff(man).items():
1084 fctx = repo.file(filename)
1104 fctx = repo.file(filename)
1085 f1 = fctx.revision(change[0][0] or -1)
1105 f1 = fctx.revision(change[0][0] or -1)
1086 f2 = fctx.revision(change[1][0] or -1)
1106 f2 = fctx.revision(change[1][0] or -1)
1087 textpairs.append((f1, f2))
1107 textpairs.append((f1, f2))
1088 else:
1108 else:
1089 dp = r.deltaparent(rev)
1109 dp = r.deltaparent(rev)
1090 textpairs.append((r.revision(dp), r.revision(rev)))
1110 textpairs.append((r.revision(dp), r.revision(rev)))
1091
1111
1092 def d():
1112 def d():
1093 for left, right in textpairs:
1113 for left, right in textpairs:
1094 # The date strings don't matter, so we pass empty strings.
1114 # The date strings don't matter, so we pass empty strings.
1095 headerlines, hunks = mdiff.unidiff(
1115 headerlines, hunks = mdiff.unidiff(
1096 left, '', right, '', 'left', 'right', binary=False)
1116 left, '', right, '', 'left', 'right', binary=False)
1097 # consume iterators in roughly the way patch.py does
1117 # consume iterators in roughly the way patch.py does
1098 b'\n'.join(headerlines)
1118 b'\n'.join(headerlines)
1099 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
1119 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
1100 timer, fm = gettimer(ui, opts)
1120 timer, fm = gettimer(ui, opts)
1101 timer(d)
1121 timer(d)
1102 fm.end()
1122 fm.end()
1103
1123
1104 @command('perfdiffwd', formatteropts)
1124 @command('perfdiffwd', formatteropts)
1105 def perfdiffwd(ui, repo, **opts):
1125 def perfdiffwd(ui, repo, **opts):
1106 """Profile diff of working directory changes"""
1126 """Profile diff of working directory changes"""
1107 timer, fm = gettimer(ui, opts)
1127 timer, fm = gettimer(ui, opts)
1108 options = {
1128 options = {
1109 'w': 'ignore_all_space',
1129 'w': 'ignore_all_space',
1110 'b': 'ignore_space_change',
1130 'b': 'ignore_space_change',
1111 'B': 'ignore_blank_lines',
1131 'B': 'ignore_blank_lines',
1112 }
1132 }
1113
1133
1114 for diffopt in ('', 'w', 'b', 'B', 'wB'):
1134 for diffopt in ('', 'w', 'b', 'B', 'wB'):
1115 opts = dict((options[c], '1') for c in diffopt)
1135 opts = dict((options[c], '1') for c in diffopt)
1116 def d():
1136 def d():
1117 ui.pushbuffer()
1137 ui.pushbuffer()
1118 commands.diff(ui, repo, **opts)
1138 commands.diff(ui, repo, **opts)
1119 ui.popbuffer()
1139 ui.popbuffer()
1120 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
1140 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
1121 timer(d, title)
1141 timer(d, title)
1122 fm.end()
1142 fm.end()
1123
1143
1124 @command('perfrevlogindex', revlogopts + formatteropts,
1144 @command('perfrevlogindex', revlogopts + formatteropts,
1125 '-c|-m|FILE')
1145 '-c|-m|FILE')
1126 def perfrevlogindex(ui, repo, file_=None, **opts):
1146 def perfrevlogindex(ui, repo, file_=None, **opts):
1127 """Benchmark operations against a revlog index.
1147 """Benchmark operations against a revlog index.
1128
1148
1129 This tests constructing a revlog instance, reading index data,
1149 This tests constructing a revlog instance, reading index data,
1130 parsing index data, and performing various operations related to
1150 parsing index data, and performing various operations related to
1131 index data.
1151 index data.
1132 """
1152 """
1133
1153
1134 rl = cmdutil.openrevlog(repo, 'perfrevlogindex', file_, opts)
1154 rl = cmdutil.openrevlog(repo, 'perfrevlogindex', file_, opts)
1135
1155
1136 opener = getattr(rl, 'opener') # trick linter
1156 opener = getattr(rl, 'opener') # trick linter
1137 indexfile = rl.indexfile
1157 indexfile = rl.indexfile
1138 data = opener.read(indexfile)
1158 data = opener.read(indexfile)
1139
1159
1140 header = struct.unpack('>I', data[0:4])[0]
1160 header = struct.unpack('>I', data[0:4])[0]
1141 version = header & 0xFFFF
1161 version = header & 0xFFFF
1142 if version == 1:
1162 if version == 1:
1143 revlogio = revlog.revlogio()
1163 revlogio = revlog.revlogio()
1144 inline = header & (1 << 16)
1164 inline = header & (1 << 16)
1145 else:
1165 else:
1146 raise error.Abort(('unsupported revlog version: %d') % version)
1166 raise error.Abort(('unsupported revlog version: %d') % version)
1147
1167
1148 rllen = len(rl)
1168 rllen = len(rl)
1149
1169
1150 node0 = rl.node(0)
1170 node0 = rl.node(0)
1151 node25 = rl.node(rllen // 4)
1171 node25 = rl.node(rllen // 4)
1152 node50 = rl.node(rllen // 2)
1172 node50 = rl.node(rllen // 2)
1153 node75 = rl.node(rllen // 4 * 3)
1173 node75 = rl.node(rllen // 4 * 3)
1154 node100 = rl.node(rllen - 1)
1174 node100 = rl.node(rllen - 1)
1155
1175
1156 allrevs = range(rllen)
1176 allrevs = range(rllen)
1157 allrevsrev = list(reversed(allrevs))
1177 allrevsrev = list(reversed(allrevs))
1158 allnodes = [rl.node(rev) for rev in range(rllen)]
1178 allnodes = [rl.node(rev) for rev in range(rllen)]
1159 allnodesrev = list(reversed(allnodes))
1179 allnodesrev = list(reversed(allnodes))
1160
1180
1161 def constructor():
1181 def constructor():
1162 revlog.revlog(opener, indexfile)
1182 revlog.revlog(opener, indexfile)
1163
1183
1164 def read():
1184 def read():
1165 with opener(indexfile) as fh:
1185 with opener(indexfile) as fh:
1166 fh.read()
1186 fh.read()
1167
1187
1168 def parseindex():
1188 def parseindex():
1169 revlogio.parseindex(data, inline)
1189 revlogio.parseindex(data, inline)
1170
1190
1171 def getentry(revornode):
1191 def getentry(revornode):
1172 index = revlogio.parseindex(data, inline)[0]
1192 index = revlogio.parseindex(data, inline)[0]
1173 index[revornode]
1193 index[revornode]
1174
1194
1175 def getentries(revs, count=1):
1195 def getentries(revs, count=1):
1176 index = revlogio.parseindex(data, inline)[0]
1196 index = revlogio.parseindex(data, inline)[0]
1177
1197
1178 for i in range(count):
1198 for i in range(count):
1179 for rev in revs:
1199 for rev in revs:
1180 index[rev]
1200 index[rev]
1181
1201
1182 def resolvenode(node):
1202 def resolvenode(node):
1183 nodemap = revlogio.parseindex(data, inline)[1]
1203 nodemap = revlogio.parseindex(data, inline)[1]
1184 # This only works for the C code.
1204 # This only works for the C code.
1185 if nodemap is None:
1205 if nodemap is None:
1186 return
1206 return
1187
1207
1188 try:
1208 try:
1189 nodemap[node]
1209 nodemap[node]
1190 except error.RevlogError:
1210 except error.RevlogError:
1191 pass
1211 pass
1192
1212
1193 def resolvenodes(nodes, count=1):
1213 def resolvenodes(nodes, count=1):
1194 nodemap = revlogio.parseindex(data, inline)[1]
1214 nodemap = revlogio.parseindex(data, inline)[1]
1195 if nodemap is None:
1215 if nodemap is None:
1196 return
1216 return
1197
1217
1198 for i in range(count):
1218 for i in range(count):
1199 for node in nodes:
1219 for node in nodes:
1200 try:
1220 try:
1201 nodemap[node]
1221 nodemap[node]
1202 except error.RevlogError:
1222 except error.RevlogError:
1203 pass
1223 pass
1204
1224
1205 benches = [
1225 benches = [
1206 (constructor, 'revlog constructor'),
1226 (constructor, 'revlog constructor'),
1207 (read, 'read'),
1227 (read, 'read'),
1208 (parseindex, 'create index object'),
1228 (parseindex, 'create index object'),
1209 (lambda: getentry(0), 'retrieve index entry for rev 0'),
1229 (lambda: getentry(0), 'retrieve index entry for rev 0'),
1210 (lambda: resolvenode('a' * 20), 'look up missing node'),
1230 (lambda: resolvenode('a' * 20), 'look up missing node'),
1211 (lambda: resolvenode(node0), 'look up node at rev 0'),
1231 (lambda: resolvenode(node0), 'look up node at rev 0'),
1212 (lambda: resolvenode(node25), 'look up node at 1/4 len'),
1232 (lambda: resolvenode(node25), 'look up node at 1/4 len'),
1213 (lambda: resolvenode(node50), 'look up node at 1/2 len'),
1233 (lambda: resolvenode(node50), 'look up node at 1/2 len'),
1214 (lambda: resolvenode(node75), 'look up node at 3/4 len'),
1234 (lambda: resolvenode(node75), 'look up node at 3/4 len'),
1215 (lambda: resolvenode(node100), 'look up node at tip'),
1235 (lambda: resolvenode(node100), 'look up node at tip'),
1216 # 2x variation is to measure caching impact.
1236 # 2x variation is to measure caching impact.
1217 (lambda: resolvenodes(allnodes),
1237 (lambda: resolvenodes(allnodes),
1218 'look up all nodes (forward)'),
1238 'look up all nodes (forward)'),
1219 (lambda: resolvenodes(allnodes, 2),
1239 (lambda: resolvenodes(allnodes, 2),
1220 'look up all nodes 2x (forward)'),
1240 'look up all nodes 2x (forward)'),
1221 (lambda: resolvenodes(allnodesrev),
1241 (lambda: resolvenodes(allnodesrev),
1222 'look up all nodes (reverse)'),
1242 'look up all nodes (reverse)'),
1223 (lambda: resolvenodes(allnodesrev, 2),
1243 (lambda: resolvenodes(allnodesrev, 2),
1224 'look up all nodes 2x (reverse)'),
1244 'look up all nodes 2x (reverse)'),
1225 (lambda: getentries(allrevs),
1245 (lambda: getentries(allrevs),
1226 'retrieve all index entries (forward)'),
1246 'retrieve all index entries (forward)'),
1227 (lambda: getentries(allrevs, 2),
1247 (lambda: getentries(allrevs, 2),
1228 'retrieve all index entries 2x (forward)'),
1248 'retrieve all index entries 2x (forward)'),
1229 (lambda: getentries(allrevsrev),
1249 (lambda: getentries(allrevsrev),
1230 'retrieve all index entries (reverse)'),
1250 'retrieve all index entries (reverse)'),
1231 (lambda: getentries(allrevsrev, 2),
1251 (lambda: getentries(allrevsrev, 2),
1232 'retrieve all index entries 2x (reverse)'),
1252 'retrieve all index entries 2x (reverse)'),
1233 ]
1253 ]
1234
1254
1235 for fn, title in benches:
1255 for fn, title in benches:
1236 timer, fm = gettimer(ui, opts)
1256 timer, fm = gettimer(ui, opts)
1237 timer(fn, title=title)
1257 timer(fn, title=title)
1238 fm.end()
1258 fm.end()
1239
1259
1240 @command('perfrevlogrevisions', revlogopts + formatteropts +
1260 @command('perfrevlogrevisions', revlogopts + formatteropts +
1241 [('d', 'dist', 100, 'distance between the revisions'),
1261 [('d', 'dist', 100, 'distance between the revisions'),
1242 ('s', 'startrev', 0, 'revision to start reading at'),
1262 ('s', 'startrev', 0, 'revision to start reading at'),
1243 ('', 'reverse', False, 'read in reverse')],
1263 ('', 'reverse', False, 'read in reverse')],
1244 '-c|-m|FILE')
1264 '-c|-m|FILE')
1245 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
1265 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
1246 **opts):
1266 **opts):
1247 """Benchmark reading a series of revisions from a revlog.
1267 """Benchmark reading a series of revisions from a revlog.
1248
1268
1249 By default, we read every ``-d/--dist`` revision from 0 to tip of
1269 By default, we read every ``-d/--dist`` revision from 0 to tip of
1250 the specified revlog.
1270 the specified revlog.
1251
1271
1252 The start revision can be defined via ``-s/--startrev``.
1272 The start revision can be defined via ``-s/--startrev``.
1253 """
1273 """
1254 rl = cmdutil.openrevlog(repo, 'perfrevlogrevisions', file_, opts)
1274 rl = cmdutil.openrevlog(repo, 'perfrevlogrevisions', file_, opts)
1255 rllen = getlen(ui)(rl)
1275 rllen = getlen(ui)(rl)
1256
1276
1257 def d():
1277 def d():
1258 rl.clearcaches()
1278 rl.clearcaches()
1259
1279
1260 beginrev = startrev
1280 beginrev = startrev
1261 endrev = rllen
1281 endrev = rllen
1262 dist = opts['dist']
1282 dist = opts['dist']
1263
1283
1264 if reverse:
1284 if reverse:
1265 beginrev, endrev = endrev, beginrev
1285 beginrev, endrev = endrev, beginrev
1266 dist = -1 * dist
1286 dist = -1 * dist
1267
1287
1268 for x in xrange(beginrev, endrev, dist):
1288 for x in xrange(beginrev, endrev, dist):
1269 # Old revisions don't support passing int.
1289 # Old revisions don't support passing int.
1270 n = rl.node(x)
1290 n = rl.node(x)
1271 rl.revision(n)
1291 rl.revision(n)
1272
1292
1273 timer, fm = gettimer(ui, opts)
1293 timer, fm = gettimer(ui, opts)
1274 timer(d)
1294 timer(d)
1275 fm.end()
1295 fm.end()
1276
1296
1277 @command('perfrevlogchunks', revlogopts + formatteropts +
1297 @command('perfrevlogchunks', revlogopts + formatteropts +
1278 [('e', 'engines', '', 'compression engines to use'),
1298 [('e', 'engines', '', 'compression engines to use'),
1279 ('s', 'startrev', 0, 'revision to start at')],
1299 ('s', 'startrev', 0, 'revision to start at')],
1280 '-c|-m|FILE')
1300 '-c|-m|FILE')
1281 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1301 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1282 """Benchmark operations on revlog chunks.
1302 """Benchmark operations on revlog chunks.
1283
1303
1284 Logically, each revlog is a collection of fulltext revisions. However,
1304 Logically, each revlog is a collection of fulltext revisions. However,
1285 stored within each revlog are "chunks" of possibly compressed data. This
1305 stored within each revlog are "chunks" of possibly compressed data. This
1286 data needs to be read and decompressed or compressed and written.
1306 data needs to be read and decompressed or compressed and written.
1287
1307
1288 This command measures the time it takes to read+decompress and recompress
1308 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.
1309 chunks in a revlog. It effectively isolates I/O and compression performance.
1290 For measurements of higher-level operations like resolving revisions,
1310 For measurements of higher-level operations like resolving revisions,
1291 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1311 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1292 """
1312 """
1293 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
1313 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
1294
1314
1295 # _chunkraw was renamed to _getsegmentforrevs.
1315 # _chunkraw was renamed to _getsegmentforrevs.
1296 try:
1316 try:
1297 segmentforrevs = rl._getsegmentforrevs
1317 segmentforrevs = rl._getsegmentforrevs
1298 except AttributeError:
1318 except AttributeError:
1299 segmentforrevs = rl._chunkraw
1319 segmentforrevs = rl._chunkraw
1300
1320
1301 # Verify engines argument.
1321 # Verify engines argument.
1302 if engines:
1322 if engines:
1303 engines = set(e.strip() for e in engines.split(','))
1323 engines = set(e.strip() for e in engines.split(','))
1304 for engine in engines:
1324 for engine in engines:
1305 try:
1325 try:
1306 util.compressionengines[engine]
1326 util.compressionengines[engine]
1307 except KeyError:
1327 except KeyError:
1308 raise error.Abort('unknown compression engine: %s' % engine)
1328 raise error.Abort('unknown compression engine: %s' % engine)
1309 else:
1329 else:
1310 engines = []
1330 engines = []
1311 for e in util.compengines:
1331 for e in util.compengines:
1312 engine = util.compengines[e]
1332 engine = util.compengines[e]
1313 try:
1333 try:
1314 if engine.available():
1334 if engine.available():
1315 engine.revlogcompressor().compress('dummy')
1335 engine.revlogcompressor().compress('dummy')
1316 engines.append(e)
1336 engines.append(e)
1317 except NotImplementedError:
1337 except NotImplementedError:
1318 pass
1338 pass
1319
1339
1320 revs = list(rl.revs(startrev, len(rl) - 1))
1340 revs = list(rl.revs(startrev, len(rl) - 1))
1321
1341
1322 def rlfh(rl):
1342 def rlfh(rl):
1323 if rl._inline:
1343 if rl._inline:
1324 return getsvfs(repo)(rl.indexfile)
1344 return getsvfs(repo)(rl.indexfile)
1325 else:
1345 else:
1326 return getsvfs(repo)(rl.datafile)
1346 return getsvfs(repo)(rl.datafile)
1327
1347
1328 def doread():
1348 def doread():
1329 rl.clearcaches()
1349 rl.clearcaches()
1330 for rev in revs:
1350 for rev in revs:
1331 segmentforrevs(rev, rev)
1351 segmentforrevs(rev, rev)
1332
1352
1333 def doreadcachedfh():
1353 def doreadcachedfh():
1334 rl.clearcaches()
1354 rl.clearcaches()
1335 fh = rlfh(rl)
1355 fh = rlfh(rl)
1336 for rev in revs:
1356 for rev in revs:
1337 segmentforrevs(rev, rev, df=fh)
1357 segmentforrevs(rev, rev, df=fh)
1338
1358
1339 def doreadbatch():
1359 def doreadbatch():
1340 rl.clearcaches()
1360 rl.clearcaches()
1341 segmentforrevs(revs[0], revs[-1])
1361 segmentforrevs(revs[0], revs[-1])
1342
1362
1343 def doreadbatchcachedfh():
1363 def doreadbatchcachedfh():
1344 rl.clearcaches()
1364 rl.clearcaches()
1345 fh = rlfh(rl)
1365 fh = rlfh(rl)
1346 segmentforrevs(revs[0], revs[-1], df=fh)
1366 segmentforrevs(revs[0], revs[-1], df=fh)
1347
1367
1348 def dochunk():
1368 def dochunk():
1349 rl.clearcaches()
1369 rl.clearcaches()
1350 fh = rlfh(rl)
1370 fh = rlfh(rl)
1351 for rev in revs:
1371 for rev in revs:
1352 rl._chunk(rev, df=fh)
1372 rl._chunk(rev, df=fh)
1353
1373
1354 chunks = [None]
1374 chunks = [None]
1355
1375
1356 def dochunkbatch():
1376 def dochunkbatch():
1357 rl.clearcaches()
1377 rl.clearcaches()
1358 fh = rlfh(rl)
1378 fh = rlfh(rl)
1359 # Save chunks as a side-effect.
1379 # Save chunks as a side-effect.
1360 chunks[0] = rl._chunks(revs, df=fh)
1380 chunks[0] = rl._chunks(revs, df=fh)
1361
1381
1362 def docompress(compressor):
1382 def docompress(compressor):
1363 rl.clearcaches()
1383 rl.clearcaches()
1364
1384
1365 try:
1385 try:
1366 # Swap in the requested compression engine.
1386 # Swap in the requested compression engine.
1367 oldcompressor = rl._compressor
1387 oldcompressor = rl._compressor
1368 rl._compressor = compressor
1388 rl._compressor = compressor
1369 for chunk in chunks[0]:
1389 for chunk in chunks[0]:
1370 rl.compress(chunk)
1390 rl.compress(chunk)
1371 finally:
1391 finally:
1372 rl._compressor = oldcompressor
1392 rl._compressor = oldcompressor
1373
1393
1374 benches = [
1394 benches = [
1375 (lambda: doread(), 'read'),
1395 (lambda: doread(), 'read'),
1376 (lambda: doreadcachedfh(), 'read w/ reused fd'),
1396 (lambda: doreadcachedfh(), 'read w/ reused fd'),
1377 (lambda: doreadbatch(), 'read batch'),
1397 (lambda: doreadbatch(), 'read batch'),
1378 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
1398 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
1379 (lambda: dochunk(), 'chunk'),
1399 (lambda: dochunk(), 'chunk'),
1380 (lambda: dochunkbatch(), 'chunk batch'),
1400 (lambda: dochunkbatch(), 'chunk batch'),
1381 ]
1401 ]
1382
1402
1383 for engine in sorted(engines):
1403 for engine in sorted(engines):
1384 compressor = util.compengines[engine].revlogcompressor()
1404 compressor = util.compengines[engine].revlogcompressor()
1385 benches.append((functools.partial(docompress, compressor),
1405 benches.append((functools.partial(docompress, compressor),
1386 'compress w/ %s' % engine))
1406 'compress w/ %s' % engine))
1387
1407
1388 for fn, title in benches:
1408 for fn, title in benches:
1389 timer, fm = gettimer(ui, opts)
1409 timer, fm = gettimer(ui, opts)
1390 timer(fn, title=title)
1410 timer(fn, title=title)
1391 fm.end()
1411 fm.end()
1392
1412
1393 @command('perfrevlogrevision', revlogopts + formatteropts +
1413 @command('perfrevlogrevision', revlogopts + formatteropts +
1394 [('', 'cache', False, 'use caches instead of clearing')],
1414 [('', 'cache', False, 'use caches instead of clearing')],
1395 '-c|-m|FILE REV')
1415 '-c|-m|FILE REV')
1396 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1416 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1397 """Benchmark obtaining a revlog revision.
1417 """Benchmark obtaining a revlog revision.
1398
1418
1399 Obtaining a revlog revision consists of roughly the following steps:
1419 Obtaining a revlog revision consists of roughly the following steps:
1400
1420
1401 1. Compute the delta chain
1421 1. Compute the delta chain
1402 2. Obtain the raw chunks for that delta chain
1422 2. Obtain the raw chunks for that delta chain
1403 3. Decompress each raw chunk
1423 3. Decompress each raw chunk
1404 4. Apply binary patches to obtain fulltext
1424 4. Apply binary patches to obtain fulltext
1405 5. Verify hash of fulltext
1425 5. Verify hash of fulltext
1406
1426
1407 This command measures the time spent in each of these phases.
1427 This command measures the time spent in each of these phases.
1408 """
1428 """
1409 if opts.get('changelog') or opts.get('manifest'):
1429 if opts.get('changelog') or opts.get('manifest'):
1410 file_, rev = None, file_
1430 file_, rev = None, file_
1411 elif rev is None:
1431 elif rev is None:
1412 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
1432 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
1413
1433
1414 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
1434 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
1415
1435
1416 # _chunkraw was renamed to _getsegmentforrevs.
1436 # _chunkraw was renamed to _getsegmentforrevs.
1417 try:
1437 try:
1418 segmentforrevs = r._getsegmentforrevs
1438 segmentforrevs = r._getsegmentforrevs
1419 except AttributeError:
1439 except AttributeError:
1420 segmentforrevs = r._chunkraw
1440 segmentforrevs = r._chunkraw
1421
1441
1422 node = r.lookup(rev)
1442 node = r.lookup(rev)
1423 rev = r.rev(node)
1443 rev = r.rev(node)
1424
1444
1425 def getrawchunks(data, chain):
1445 def getrawchunks(data, chain):
1426 start = r.start
1446 start = r.start
1427 length = r.length
1447 length = r.length
1428 inline = r._inline
1448 inline = r._inline
1429 iosize = r._io.size
1449 iosize = r._io.size
1430 buffer = util.buffer
1450 buffer = util.buffer
1431 offset = start(chain[0])
1451 offset = start(chain[0])
1432
1452
1433 chunks = []
1453 chunks = []
1434 ladd = chunks.append
1454 ladd = chunks.append
1435
1455
1436 for rev in chain:
1456 for rev in chain:
1437 chunkstart = start(rev)
1457 chunkstart = start(rev)
1438 if inline:
1458 if inline:
1439 chunkstart += (rev + 1) * iosize
1459 chunkstart += (rev + 1) * iosize
1440 chunklength = length(rev)
1460 chunklength = length(rev)
1441 ladd(buffer(data, chunkstart - offset, chunklength))
1461 ladd(buffer(data, chunkstart - offset, chunklength))
1442
1462
1443 return chunks
1463 return chunks
1444
1464
1445 def dodeltachain(rev):
1465 def dodeltachain(rev):
1446 if not cache:
1466 if not cache:
1447 r.clearcaches()
1467 r.clearcaches()
1448 r._deltachain(rev)
1468 r._deltachain(rev)
1449
1469
1450 def doread(chain):
1470 def doread(chain):
1451 if not cache:
1471 if not cache:
1452 r.clearcaches()
1472 r.clearcaches()
1453 segmentforrevs(chain[0], chain[-1])
1473 segmentforrevs(chain[0], chain[-1])
1454
1474
1455 def dorawchunks(data, chain):
1475 def dorawchunks(data, chain):
1456 if not cache:
1476 if not cache:
1457 r.clearcaches()
1477 r.clearcaches()
1458 getrawchunks(data, chain)
1478 getrawchunks(data, chain)
1459
1479
1460 def dodecompress(chunks):
1480 def dodecompress(chunks):
1461 decomp = r.decompress
1481 decomp = r.decompress
1462 for chunk in chunks:
1482 for chunk in chunks:
1463 decomp(chunk)
1483 decomp(chunk)
1464
1484
1465 def dopatch(text, bins):
1485 def dopatch(text, bins):
1466 if not cache:
1486 if not cache:
1467 r.clearcaches()
1487 r.clearcaches()
1468 mdiff.patches(text, bins)
1488 mdiff.patches(text, bins)
1469
1489
1470 def dohash(text):
1490 def dohash(text):
1471 if not cache:
1491 if not cache:
1472 r.clearcaches()
1492 r.clearcaches()
1473 r.checkhash(text, node, rev=rev)
1493 r.checkhash(text, node, rev=rev)
1474
1494
1475 def dorevision():
1495 def dorevision():
1476 if not cache:
1496 if not cache:
1477 r.clearcaches()
1497 r.clearcaches()
1478 r.revision(node)
1498 r.revision(node)
1479
1499
1480 chain = r._deltachain(rev)[0]
1500 chain = r._deltachain(rev)[0]
1481 data = segmentforrevs(chain[0], chain[-1])[1]
1501 data = segmentforrevs(chain[0], chain[-1])[1]
1482 rawchunks = getrawchunks(data, chain)
1502 rawchunks = getrawchunks(data, chain)
1483 bins = r._chunks(chain)
1503 bins = r._chunks(chain)
1484 text = str(bins[0])
1504 text = str(bins[0])
1485 bins = bins[1:]
1505 bins = bins[1:]
1486 text = mdiff.patches(text, bins)
1506 text = mdiff.patches(text, bins)
1487
1507
1488 benches = [
1508 benches = [
1489 (lambda: dorevision(), 'full'),
1509 (lambda: dorevision(), 'full'),
1490 (lambda: dodeltachain(rev), 'deltachain'),
1510 (lambda: dodeltachain(rev), 'deltachain'),
1491 (lambda: doread(chain), 'read'),
1511 (lambda: doread(chain), 'read'),
1492 (lambda: dorawchunks(data, chain), 'rawchunks'),
1512 (lambda: dorawchunks(data, chain), 'rawchunks'),
1493 (lambda: dodecompress(rawchunks), 'decompress'),
1513 (lambda: dodecompress(rawchunks), 'decompress'),
1494 (lambda: dopatch(text, bins), 'patch'),
1514 (lambda: dopatch(text, bins), 'patch'),
1495 (lambda: dohash(text), 'hash'),
1515 (lambda: dohash(text), 'hash'),
1496 ]
1516 ]
1497
1517
1498 for fn, title in benches:
1518 for fn, title in benches:
1499 timer, fm = gettimer(ui, opts)
1519 timer, fm = gettimer(ui, opts)
1500 timer(fn, title=title)
1520 timer(fn, title=title)
1501 fm.end()
1521 fm.end()
1502
1522
1503 @command('perfrevset',
1523 @command('perfrevset',
1504 [('C', 'clear', False, 'clear volatile cache between each call.'),
1524 [('C', 'clear', False, 'clear volatile cache between each call.'),
1505 ('', 'contexts', False, 'obtain changectx for each revision')]
1525 ('', 'contexts', False, 'obtain changectx for each revision')]
1506 + formatteropts, "REVSET")
1526 + formatteropts, "REVSET")
1507 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1527 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1508 """benchmark the execution time of a revset
1528 """benchmark the execution time of a revset
1509
1529
1510 Use the --clean option if need to evaluate the impact of build volatile
1530 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
1531 revisions set cache on the revset execution. Volatile cache hold filtered
1512 and obsolete related cache."""
1532 and obsolete related cache."""
1513 timer, fm = gettimer(ui, opts)
1533 timer, fm = gettimer(ui, opts)
1514 def d():
1534 def d():
1515 if clear:
1535 if clear:
1516 repo.invalidatevolatilesets()
1536 repo.invalidatevolatilesets()
1517 if contexts:
1537 if contexts:
1518 for ctx in repo.set(expr): pass
1538 for ctx in repo.set(expr): pass
1519 else:
1539 else:
1520 for r in repo.revs(expr): pass
1540 for r in repo.revs(expr): pass
1521 timer(d)
1541 timer(d)
1522 fm.end()
1542 fm.end()
1523
1543
1524 @command('perfvolatilesets',
1544 @command('perfvolatilesets',
1525 [('', 'clear-obsstore', False, 'drop obsstore between each call.'),
1545 [('', 'clear-obsstore', False, 'drop obsstore between each call.'),
1526 ] + formatteropts)
1546 ] + formatteropts)
1527 def perfvolatilesets(ui, repo, *names, **opts):
1547 def perfvolatilesets(ui, repo, *names, **opts):
1528 """benchmark the computation of various volatile set
1548 """benchmark the computation of various volatile set
1529
1549
1530 Volatile set computes element related to filtering and obsolescence."""
1550 Volatile set computes element related to filtering and obsolescence."""
1531 timer, fm = gettimer(ui, opts)
1551 timer, fm = gettimer(ui, opts)
1532 repo = repo.unfiltered()
1552 repo = repo.unfiltered()
1533
1553
1534 def getobs(name):
1554 def getobs(name):
1535 def d():
1555 def d():
1536 repo.invalidatevolatilesets()
1556 repo.invalidatevolatilesets()
1537 if opts['clear_obsstore']:
1557 if opts['clear_obsstore']:
1538 clearfilecache(repo, 'obsstore')
1558 clearfilecache(repo, 'obsstore')
1539 obsolete.getrevs(repo, name)
1559 obsolete.getrevs(repo, name)
1540 return d
1560 return d
1541
1561
1542 allobs = sorted(obsolete.cachefuncs)
1562 allobs = sorted(obsolete.cachefuncs)
1543 if names:
1563 if names:
1544 allobs = [n for n in allobs if n in names]
1564 allobs = [n for n in allobs if n in names]
1545
1565
1546 for name in allobs:
1566 for name in allobs:
1547 timer(getobs(name), title=name)
1567 timer(getobs(name), title=name)
1548
1568
1549 def getfiltered(name):
1569 def getfiltered(name):
1550 def d():
1570 def d():
1551 repo.invalidatevolatilesets()
1571 repo.invalidatevolatilesets()
1552 if opts['clear_obsstore']:
1572 if opts['clear_obsstore']:
1553 clearfilecache(repo, 'obsstore')
1573 clearfilecache(repo, 'obsstore')
1554 repoview.filterrevs(repo, name)
1574 repoview.filterrevs(repo, name)
1555 return d
1575 return d
1556
1576
1557 allfilter = sorted(repoview.filtertable)
1577 allfilter = sorted(repoview.filtertable)
1558 if names:
1578 if names:
1559 allfilter = [n for n in allfilter if n in names]
1579 allfilter = [n for n in allfilter if n in names]
1560
1580
1561 for name in allfilter:
1581 for name in allfilter:
1562 timer(getfiltered(name), title=name)
1582 timer(getfiltered(name), title=name)
1563 fm.end()
1583 fm.end()
1564
1584
1565 @command('perfbranchmap',
1585 @command('perfbranchmap',
1566 [('f', 'full', False,
1586 [('f', 'full', False,
1567 'Includes build time of subset'),
1587 'Includes build time of subset'),
1568 ('', 'clear-revbranch', False,
1588 ('', 'clear-revbranch', False,
1569 'purge the revbranch cache between computation'),
1589 'purge the revbranch cache between computation'),
1570 ] + formatteropts)
1590 ] + formatteropts)
1571 def perfbranchmap(ui, repo, *filternames, **opts):
1591 def perfbranchmap(ui, repo, *filternames, **opts):
1572 """benchmark the update of a branchmap
1592 """benchmark the update of a branchmap
1573
1593
1574 This benchmarks the full repo.branchmap() call with read and write disabled
1594 This benchmarks the full repo.branchmap() call with read and write disabled
1575 """
1595 """
1576 full = opts.get("full", False)
1596 full = opts.get("full", False)
1577 clear_revbranch = opts.get("clear_revbranch", False)
1597 clear_revbranch = opts.get("clear_revbranch", False)
1578 timer, fm = gettimer(ui, opts)
1598 timer, fm = gettimer(ui, opts)
1579 def getbranchmap(filtername):
1599 def getbranchmap(filtername):
1580 """generate a benchmark function for the filtername"""
1600 """generate a benchmark function for the filtername"""
1581 if filtername is None:
1601 if filtername is None:
1582 view = repo
1602 view = repo
1583 else:
1603 else:
1584 view = repo.filtered(filtername)
1604 view = repo.filtered(filtername)
1585 def d():
1605 def d():
1586 if clear_revbranch:
1606 if clear_revbranch:
1587 repo.revbranchcache()._clear()
1607 repo.revbranchcache()._clear()
1588 if full:
1608 if full:
1589 view._branchcaches.clear()
1609 view._branchcaches.clear()
1590 else:
1610 else:
1591 view._branchcaches.pop(filtername, None)
1611 view._branchcaches.pop(filtername, None)
1592 view.branchmap()
1612 view.branchmap()
1593 return d
1613 return d
1594 # add filter in smaller subset to bigger subset
1614 # add filter in smaller subset to bigger subset
1595 possiblefilters = set(repoview.filtertable)
1615 possiblefilters = set(repoview.filtertable)
1596 if filternames:
1616 if filternames:
1597 possiblefilters &= set(filternames)
1617 possiblefilters &= set(filternames)
1598 subsettable = getbranchmapsubsettable()
1618 subsettable = getbranchmapsubsettable()
1599 allfilters = []
1619 allfilters = []
1600 while possiblefilters:
1620 while possiblefilters:
1601 for name in possiblefilters:
1621 for name in possiblefilters:
1602 subset = subsettable.get(name)
1622 subset = subsettable.get(name)
1603 if subset not in possiblefilters:
1623 if subset not in possiblefilters:
1604 break
1624 break
1605 else:
1625 else:
1606 assert False, 'subset cycle %s!' % possiblefilters
1626 assert False, 'subset cycle %s!' % possiblefilters
1607 allfilters.append(name)
1627 allfilters.append(name)
1608 possiblefilters.remove(name)
1628 possiblefilters.remove(name)
1609
1629
1610 # warm the cache
1630 # warm the cache
1611 if not full:
1631 if not full:
1612 for name in allfilters:
1632 for name in allfilters:
1613 repo.filtered(name).branchmap()
1633 repo.filtered(name).branchmap()
1614 if not filternames or 'unfiltered' in filternames:
1634 if not filternames or 'unfiltered' in filternames:
1615 # add unfiltered
1635 # add unfiltered
1616 allfilters.append(None)
1636 allfilters.append(None)
1617
1637
1618 branchcacheread = safeattrsetter(branchmap, 'read')
1638 branchcacheread = safeattrsetter(branchmap, 'read')
1619 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1639 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1620 branchcacheread.set(lambda repo: None)
1640 branchcacheread.set(lambda repo: None)
1621 branchcachewrite.set(lambda bc, repo: None)
1641 branchcachewrite.set(lambda bc, repo: None)
1622 try:
1642 try:
1623 for name in allfilters:
1643 for name in allfilters:
1624 printname = name
1644 printname = name
1625 if name is None:
1645 if name is None:
1626 printname = 'unfiltered'
1646 printname = 'unfiltered'
1627 timer(getbranchmap(name), title=str(printname))
1647 timer(getbranchmap(name), title=str(printname))
1628 finally:
1648 finally:
1629 branchcacheread.restore()
1649 branchcacheread.restore()
1630 branchcachewrite.restore()
1650 branchcachewrite.restore()
1631 fm.end()
1651 fm.end()
1632
1652
1633 @command('perfloadmarkers')
1653 @command('perfloadmarkers')
1634 def perfloadmarkers(ui, repo):
1654 def perfloadmarkers(ui, repo):
1635 """benchmark the time to parse the on-disk markers for a repo
1655 """benchmark the time to parse the on-disk markers for a repo
1636
1656
1637 Result is the number of markers in the repo."""
1657 Result is the number of markers in the repo."""
1638 timer, fm = gettimer(ui)
1658 timer, fm = gettimer(ui)
1639 svfs = getsvfs(repo)
1659 svfs = getsvfs(repo)
1640 timer(lambda: len(obsolete.obsstore(svfs)))
1660 timer(lambda: len(obsolete.obsstore(svfs)))
1641 fm.end()
1661 fm.end()
1642
1662
1643 @command('perflrucachedict', formatteropts +
1663 @command('perflrucachedict', formatteropts +
1644 [('', 'size', 4, 'size of cache'),
1664 [('', 'size', 4, 'size of cache'),
1645 ('', 'gets', 10000, 'number of key lookups'),
1665 ('', 'gets', 10000, 'number of key lookups'),
1646 ('', 'sets', 10000, 'number of key sets'),
1666 ('', 'sets', 10000, 'number of key sets'),
1647 ('', 'mixed', 10000, 'number of mixed mode operations'),
1667 ('', 'mixed', 10000, 'number of mixed mode operations'),
1648 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1668 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1649 norepo=True)
1669 norepo=True)
1650 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1670 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1651 mixedgetfreq=50, **opts):
1671 mixedgetfreq=50, **opts):
1652 def doinit():
1672 def doinit():
1653 for i in xrange(10000):
1673 for i in xrange(10000):
1654 util.lrucachedict(size)
1674 util.lrucachedict(size)
1655
1675
1656 values = []
1676 values = []
1657 for i in xrange(size):
1677 for i in xrange(size):
1658 values.append(random.randint(0, sys.maxint))
1678 values.append(random.randint(0, sys.maxint))
1659
1679
1660 # Get mode fills the cache and tests raw lookup performance with no
1680 # Get mode fills the cache and tests raw lookup performance with no
1661 # eviction.
1681 # eviction.
1662 getseq = []
1682 getseq = []
1663 for i in xrange(gets):
1683 for i in xrange(gets):
1664 getseq.append(random.choice(values))
1684 getseq.append(random.choice(values))
1665
1685
1666 def dogets():
1686 def dogets():
1667 d = util.lrucachedict(size)
1687 d = util.lrucachedict(size)
1668 for v in values:
1688 for v in values:
1669 d[v] = v
1689 d[v] = v
1670 for key in getseq:
1690 for key in getseq:
1671 value = d[key]
1691 value = d[key]
1672 value # silence pyflakes warning
1692 value # silence pyflakes warning
1673
1693
1674 # Set mode tests insertion speed with cache eviction.
1694 # Set mode tests insertion speed with cache eviction.
1675 setseq = []
1695 setseq = []
1676 for i in xrange(sets):
1696 for i in xrange(sets):
1677 setseq.append(random.randint(0, sys.maxint))
1697 setseq.append(random.randint(0, sys.maxint))
1678
1698
1679 def dosets():
1699 def dosets():
1680 d = util.lrucachedict(size)
1700 d = util.lrucachedict(size)
1681 for v in setseq:
1701 for v in setseq:
1682 d[v] = v
1702 d[v] = v
1683
1703
1684 # Mixed mode randomly performs gets and sets with eviction.
1704 # Mixed mode randomly performs gets and sets with eviction.
1685 mixedops = []
1705 mixedops = []
1686 for i in xrange(mixed):
1706 for i in xrange(mixed):
1687 r = random.randint(0, 100)
1707 r = random.randint(0, 100)
1688 if r < mixedgetfreq:
1708 if r < mixedgetfreq:
1689 op = 0
1709 op = 0
1690 else:
1710 else:
1691 op = 1
1711 op = 1
1692
1712
1693 mixedops.append((op, random.randint(0, size * 2)))
1713 mixedops.append((op, random.randint(0, size * 2)))
1694
1714
1695 def domixed():
1715 def domixed():
1696 d = util.lrucachedict(size)
1716 d = util.lrucachedict(size)
1697
1717
1698 for op, v in mixedops:
1718 for op, v in mixedops:
1699 if op == 0:
1719 if op == 0:
1700 try:
1720 try:
1701 d[v]
1721 d[v]
1702 except KeyError:
1722 except KeyError:
1703 pass
1723 pass
1704 else:
1724 else:
1705 d[v] = v
1725 d[v] = v
1706
1726
1707 benches = [
1727 benches = [
1708 (doinit, 'init'),
1728 (doinit, 'init'),
1709 (dogets, 'gets'),
1729 (dogets, 'gets'),
1710 (dosets, 'sets'),
1730 (dosets, 'sets'),
1711 (domixed, 'mixed')
1731 (domixed, 'mixed')
1712 ]
1732 ]
1713
1733
1714 for fn, title in benches:
1734 for fn, title in benches:
1715 timer, fm = gettimer(ui, opts)
1735 timer, fm = gettimer(ui, opts)
1716 timer(fn, title=title)
1736 timer(fn, title=title)
1717 fm.end()
1737 fm.end()
1718
1738
1719 @command('perfwrite', formatteropts)
1739 @command('perfwrite', formatteropts)
1720 def perfwrite(ui, repo, **opts):
1740 def perfwrite(ui, repo, **opts):
1721 """microbenchmark ui.write
1741 """microbenchmark ui.write
1722 """
1742 """
1723 timer, fm = gettimer(ui, opts)
1743 timer, fm = gettimer(ui, opts)
1724 def write():
1744 def write():
1725 for i in range(100000):
1745 for i in range(100000):
1726 ui.write(('Testing write performance\n'))
1746 ui.write(('Testing write performance\n'))
1727 timer(write)
1747 timer(write)
1728 fm.end()
1748 fm.end()
1729
1749
1730 def uisetup(ui):
1750 def uisetup(ui):
1731 if (util.safehasattr(cmdutil, 'openrevlog') and
1751 if (util.safehasattr(cmdutil, 'openrevlog') and
1732 not util.safehasattr(commands, 'debugrevlogopts')):
1752 not util.safehasattr(commands, 'debugrevlogopts')):
1733 # for "historical portability":
1753 # for "historical portability":
1734 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1754 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1735 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1755 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1736 # openrevlog() should cause failure, because it has been
1756 # openrevlog() should cause failure, because it has been
1737 # available since 3.5 (or 49c583ca48c4).
1757 # available since 3.5 (or 49c583ca48c4).
1738 def openrevlog(orig, repo, cmd, file_, opts):
1758 def openrevlog(orig, repo, cmd, file_, opts):
1739 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1759 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1740 raise error.Abort("This version doesn't support --dir option",
1760 raise error.Abort("This version doesn't support --dir option",
1741 hint="use 3.5 or later")
1761 hint="use 3.5 or later")
1742 return orig(repo, cmd, file_, opts)
1762 return orig(repo, cmd, file_, opts)
1743 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
1763 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
General Comments 0
You need to be logged in to leave comments. Login now