##// END OF EJS Templates
perf: make perfmanifest and perfnodelookup work with revsets...
Martin von Zweigbergk -
r37373:5bcd5859 default
parent child Browse files
Show More
@@ -1,1764 +1,1764
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 opts['dry_run'] = True
426 opts['dry_run'] = True
427 timer(lambda: scmutil.addremove(repo, matcher, "", opts))
427 timer(lambda: scmutil.addremove(repo, matcher, "", opts))
428 finally:
428 finally:
429 repo.ui.quiet = oldquiet
429 repo.ui.quiet = oldquiet
430 fm.end()
430 fm.end()
431
431
432 def clearcaches(cl):
432 def clearcaches(cl):
433 # behave somewhat consistently across internal API changes
433 # behave somewhat consistently across internal API changes
434 if util.safehasattr(cl, 'clearcaches'):
434 if util.safehasattr(cl, 'clearcaches'):
435 cl.clearcaches()
435 cl.clearcaches()
436 elif util.safehasattr(cl, '_nodecache'):
436 elif util.safehasattr(cl, '_nodecache'):
437 from mercurial.node import nullid, nullrev
437 from mercurial.node import nullid, nullrev
438 cl._nodecache = {nullid: nullrev}
438 cl._nodecache = {nullid: nullrev}
439 cl._nodepos = None
439 cl._nodepos = None
440
440
441 @command('perfheads', formatteropts)
441 @command('perfheads', formatteropts)
442 def perfheads(ui, repo, **opts):
442 def perfheads(ui, repo, **opts):
443 timer, fm = gettimer(ui, opts)
443 timer, fm = gettimer(ui, opts)
444 cl = repo.changelog
444 cl = repo.changelog
445 def d():
445 def d():
446 len(cl.headrevs())
446 len(cl.headrevs())
447 clearcaches(cl)
447 clearcaches(cl)
448 timer(d)
448 timer(d)
449 fm.end()
449 fm.end()
450
450
451 @command('perftags', formatteropts)
451 @command('perftags', formatteropts)
452 def perftags(ui, repo, **opts):
452 def perftags(ui, repo, **opts):
453 import mercurial.changelog
453 import mercurial.changelog
454 import mercurial.manifest
454 import mercurial.manifest
455 timer, fm = gettimer(ui, opts)
455 timer, fm = gettimer(ui, opts)
456 svfs = getsvfs(repo)
456 svfs = getsvfs(repo)
457 repocleartagscache = repocleartagscachefunc(repo)
457 repocleartagscache = repocleartagscachefunc(repo)
458 def t():
458 def t():
459 repo.changelog = mercurial.changelog.changelog(svfs)
459 repo.changelog = mercurial.changelog.changelog(svfs)
460 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
460 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
461 repocleartagscache()
461 repocleartagscache()
462 return len(repo.tags())
462 return len(repo.tags())
463 timer(t)
463 timer(t)
464 fm.end()
464 fm.end()
465
465
466 @command('perfancestors', formatteropts)
466 @command('perfancestors', formatteropts)
467 def perfancestors(ui, repo, **opts):
467 def perfancestors(ui, repo, **opts):
468 timer, fm = gettimer(ui, opts)
468 timer, fm = gettimer(ui, opts)
469 heads = repo.changelog.headrevs()
469 heads = repo.changelog.headrevs()
470 def d():
470 def d():
471 for a in repo.changelog.ancestors(heads):
471 for a in repo.changelog.ancestors(heads):
472 pass
472 pass
473 timer(d)
473 timer(d)
474 fm.end()
474 fm.end()
475
475
476 @command('perfancestorset', formatteropts)
476 @command('perfancestorset', formatteropts)
477 def perfancestorset(ui, repo, revset, **opts):
477 def perfancestorset(ui, repo, revset, **opts):
478 timer, fm = gettimer(ui, opts)
478 timer, fm = gettimer(ui, opts)
479 revs = repo.revs(revset)
479 revs = repo.revs(revset)
480 heads = repo.changelog.headrevs()
480 heads = repo.changelog.headrevs()
481 def d():
481 def d():
482 s = repo.changelog.ancestors(heads)
482 s = repo.changelog.ancestors(heads)
483 for rev in revs:
483 for rev in revs:
484 rev in s
484 rev in s
485 timer(d)
485 timer(d)
486 fm.end()
486 fm.end()
487
487
488 @command('perfbookmarks', formatteropts)
488 @command('perfbookmarks', formatteropts)
489 def perfbookmarks(ui, repo, **opts):
489 def perfbookmarks(ui, repo, **opts):
490 """benchmark parsing bookmarks from disk to memory"""
490 """benchmark parsing bookmarks from disk to memory"""
491 timer, fm = gettimer(ui, opts)
491 timer, fm = gettimer(ui, opts)
492 def d():
492 def d():
493 clearfilecache(repo, '_bookmarks')
493 clearfilecache(repo, '_bookmarks')
494 repo._bookmarks
494 repo._bookmarks
495 timer(d)
495 timer(d)
496 fm.end()
496 fm.end()
497
497
498 @command('perfbundleread', formatteropts, 'BUNDLE')
498 @command('perfbundleread', formatteropts, 'BUNDLE')
499 def perfbundleread(ui, repo, bundlepath, **opts):
499 def perfbundleread(ui, repo, bundlepath, **opts):
500 """Benchmark reading of bundle files.
500 """Benchmark reading of bundle files.
501
501
502 This command is meant to isolate the I/O part of bundle reading as
502 This command is meant to isolate the I/O part of bundle reading as
503 much as possible.
503 much as possible.
504 """
504 """
505 from mercurial import (
505 from mercurial import (
506 bundle2,
506 bundle2,
507 exchange,
507 exchange,
508 streamclone,
508 streamclone,
509 )
509 )
510
510
511 def makebench(fn):
511 def makebench(fn):
512 def run():
512 def run():
513 with open(bundlepath, 'rb') as fh:
513 with open(bundlepath, 'rb') as fh:
514 bundle = exchange.readbundle(ui, fh, bundlepath)
514 bundle = exchange.readbundle(ui, fh, bundlepath)
515 fn(bundle)
515 fn(bundle)
516
516
517 return run
517 return run
518
518
519 def makereadnbytes(size):
519 def makereadnbytes(size):
520 def run():
520 def run():
521 with open(bundlepath, 'rb') as fh:
521 with open(bundlepath, 'rb') as fh:
522 bundle = exchange.readbundle(ui, fh, bundlepath)
522 bundle = exchange.readbundle(ui, fh, bundlepath)
523 while bundle.read(size):
523 while bundle.read(size):
524 pass
524 pass
525
525
526 return run
526 return run
527
527
528 def makestdioread(size):
528 def makestdioread(size):
529 def run():
529 def run():
530 with open(bundlepath, 'rb') as fh:
530 with open(bundlepath, 'rb') as fh:
531 while fh.read(size):
531 while fh.read(size):
532 pass
532 pass
533
533
534 return run
534 return run
535
535
536 # bundle1
536 # bundle1
537
537
538 def deltaiter(bundle):
538 def deltaiter(bundle):
539 for delta in bundle.deltaiter():
539 for delta in bundle.deltaiter():
540 pass
540 pass
541
541
542 def iterchunks(bundle):
542 def iterchunks(bundle):
543 for chunk in bundle.getchunks():
543 for chunk in bundle.getchunks():
544 pass
544 pass
545
545
546 # bundle2
546 # bundle2
547
547
548 def forwardchunks(bundle):
548 def forwardchunks(bundle):
549 for chunk in bundle._forwardchunks():
549 for chunk in bundle._forwardchunks():
550 pass
550 pass
551
551
552 def iterparts(bundle):
552 def iterparts(bundle):
553 for part in bundle.iterparts():
553 for part in bundle.iterparts():
554 pass
554 pass
555
555
556 def iterpartsseekable(bundle):
556 def iterpartsseekable(bundle):
557 for part in bundle.iterparts(seekable=True):
557 for part in bundle.iterparts(seekable=True):
558 pass
558 pass
559
559
560 def seek(bundle):
560 def seek(bundle):
561 for part in bundle.iterparts(seekable=True):
561 for part in bundle.iterparts(seekable=True):
562 part.seek(0, os.SEEK_END)
562 part.seek(0, os.SEEK_END)
563
563
564 def makepartreadnbytes(size):
564 def makepartreadnbytes(size):
565 def run():
565 def run():
566 with open(bundlepath, 'rb') as fh:
566 with open(bundlepath, 'rb') as fh:
567 bundle = exchange.readbundle(ui, fh, bundlepath)
567 bundle = exchange.readbundle(ui, fh, bundlepath)
568 for part in bundle.iterparts():
568 for part in bundle.iterparts():
569 while part.read(size):
569 while part.read(size):
570 pass
570 pass
571
571
572 return run
572 return run
573
573
574 benches = [
574 benches = [
575 (makestdioread(8192), 'read(8k)'),
575 (makestdioread(8192), 'read(8k)'),
576 (makestdioread(16384), 'read(16k)'),
576 (makestdioread(16384), 'read(16k)'),
577 (makestdioread(32768), 'read(32k)'),
577 (makestdioread(32768), 'read(32k)'),
578 (makestdioread(131072), 'read(128k)'),
578 (makestdioread(131072), 'read(128k)'),
579 ]
579 ]
580
580
581 with open(bundlepath, 'rb') as fh:
581 with open(bundlepath, 'rb') as fh:
582 bundle = exchange.readbundle(ui, fh, bundlepath)
582 bundle = exchange.readbundle(ui, fh, bundlepath)
583
583
584 if isinstance(bundle, changegroup.cg1unpacker):
584 if isinstance(bundle, changegroup.cg1unpacker):
585 benches.extend([
585 benches.extend([
586 (makebench(deltaiter), 'cg1 deltaiter()'),
586 (makebench(deltaiter), 'cg1 deltaiter()'),
587 (makebench(iterchunks), 'cg1 getchunks()'),
587 (makebench(iterchunks), 'cg1 getchunks()'),
588 (makereadnbytes(8192), 'cg1 read(8k)'),
588 (makereadnbytes(8192), 'cg1 read(8k)'),
589 (makereadnbytes(16384), 'cg1 read(16k)'),
589 (makereadnbytes(16384), 'cg1 read(16k)'),
590 (makereadnbytes(32768), 'cg1 read(32k)'),
590 (makereadnbytes(32768), 'cg1 read(32k)'),
591 (makereadnbytes(131072), 'cg1 read(128k)'),
591 (makereadnbytes(131072), 'cg1 read(128k)'),
592 ])
592 ])
593 elif isinstance(bundle, bundle2.unbundle20):
593 elif isinstance(bundle, bundle2.unbundle20):
594 benches.extend([
594 benches.extend([
595 (makebench(forwardchunks), 'bundle2 forwardchunks()'),
595 (makebench(forwardchunks), 'bundle2 forwardchunks()'),
596 (makebench(iterparts), 'bundle2 iterparts()'),
596 (makebench(iterparts), 'bundle2 iterparts()'),
597 (makebench(iterpartsseekable), 'bundle2 iterparts() seekable'),
597 (makebench(iterpartsseekable), 'bundle2 iterparts() seekable'),
598 (makebench(seek), 'bundle2 part seek()'),
598 (makebench(seek), 'bundle2 part seek()'),
599 (makepartreadnbytes(8192), 'bundle2 part read(8k)'),
599 (makepartreadnbytes(8192), 'bundle2 part read(8k)'),
600 (makepartreadnbytes(16384), 'bundle2 part read(16k)'),
600 (makepartreadnbytes(16384), 'bundle2 part read(16k)'),
601 (makepartreadnbytes(32768), 'bundle2 part read(32k)'),
601 (makepartreadnbytes(32768), 'bundle2 part read(32k)'),
602 (makepartreadnbytes(131072), 'bundle2 part read(128k)'),
602 (makepartreadnbytes(131072), 'bundle2 part read(128k)'),
603 ])
603 ])
604 elif isinstance(bundle, streamclone.streamcloneapplier):
604 elif isinstance(bundle, streamclone.streamcloneapplier):
605 raise error.Abort('stream clone bundles not supported')
605 raise error.Abort('stream clone bundles not supported')
606 else:
606 else:
607 raise error.Abort('unhandled bundle type: %s' % type(bundle))
607 raise error.Abort('unhandled bundle type: %s' % type(bundle))
608
608
609 for fn, title in benches:
609 for fn, title in benches:
610 timer, fm = gettimer(ui, opts)
610 timer, fm = gettimer(ui, opts)
611 timer(fn, title=title)
611 timer(fn, title=title)
612 fm.end()
612 fm.end()
613
613
614 @command('perfchangegroupchangelog', formatteropts +
614 @command('perfchangegroupchangelog', formatteropts +
615 [('', 'version', '02', 'changegroup version'),
615 [('', 'version', '02', 'changegroup version'),
616 ('r', 'rev', '', 'revisions to add to changegroup')])
616 ('r', 'rev', '', 'revisions to add to changegroup')])
617 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
617 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
618 """Benchmark producing a changelog group for a changegroup.
618 """Benchmark producing a changelog group for a changegroup.
619
619
620 This measures the time spent processing the changelog during a
620 This measures the time spent processing the changelog during a
621 bundle operation. This occurs during `hg bundle` and on a server
621 bundle operation. This occurs during `hg bundle` and on a server
622 processing a `getbundle` wire protocol request (handles clones
622 processing a `getbundle` wire protocol request (handles clones
623 and pull requests).
623 and pull requests).
624
624
625 By default, all revisions are added to the changegroup.
625 By default, all revisions are added to the changegroup.
626 """
626 """
627 cl = repo.changelog
627 cl = repo.changelog
628 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
628 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
629 bundler = changegroup.getbundler(version, repo)
629 bundler = changegroup.getbundler(version, repo)
630
630
631 def lookup(node):
631 def lookup(node):
632 # The real bundler reads the revision in order to access the
632 # The real bundler reads the revision in order to access the
633 # manifest node and files list. Do that here.
633 # manifest node and files list. Do that here.
634 cl.read(node)
634 cl.read(node)
635 return node
635 return node
636
636
637 def d():
637 def d():
638 for chunk in bundler.group(revs, cl, lookup):
638 for chunk in bundler.group(revs, cl, lookup):
639 pass
639 pass
640
640
641 timer, fm = gettimer(ui, opts)
641 timer, fm = gettimer(ui, opts)
642 timer(d)
642 timer(d)
643 fm.end()
643 fm.end()
644
644
645 @command('perfdirs', formatteropts)
645 @command('perfdirs', formatteropts)
646 def perfdirs(ui, repo, **opts):
646 def perfdirs(ui, repo, **opts):
647 timer, fm = gettimer(ui, opts)
647 timer, fm = gettimer(ui, opts)
648 dirstate = repo.dirstate
648 dirstate = repo.dirstate
649 'a' in dirstate
649 'a' in dirstate
650 def d():
650 def d():
651 dirstate.hasdir('a')
651 dirstate.hasdir('a')
652 del dirstate._map._dirs
652 del dirstate._map._dirs
653 timer(d)
653 timer(d)
654 fm.end()
654 fm.end()
655
655
656 @command('perfdirstate', formatteropts)
656 @command('perfdirstate', formatteropts)
657 def perfdirstate(ui, repo, **opts):
657 def perfdirstate(ui, repo, **opts):
658 timer, fm = gettimer(ui, opts)
658 timer, fm = gettimer(ui, opts)
659 "a" in repo.dirstate
659 "a" in repo.dirstate
660 def d():
660 def d():
661 repo.dirstate.invalidate()
661 repo.dirstate.invalidate()
662 "a" in repo.dirstate
662 "a" in repo.dirstate
663 timer(d)
663 timer(d)
664 fm.end()
664 fm.end()
665
665
666 @command('perfdirstatedirs', formatteropts)
666 @command('perfdirstatedirs', formatteropts)
667 def perfdirstatedirs(ui, repo, **opts):
667 def perfdirstatedirs(ui, repo, **opts):
668 timer, fm = gettimer(ui, opts)
668 timer, fm = gettimer(ui, opts)
669 "a" in repo.dirstate
669 "a" in repo.dirstate
670 def d():
670 def d():
671 repo.dirstate.hasdir("a")
671 repo.dirstate.hasdir("a")
672 del repo.dirstate._map._dirs
672 del repo.dirstate._map._dirs
673 timer(d)
673 timer(d)
674 fm.end()
674 fm.end()
675
675
676 @command('perfdirstatefoldmap', formatteropts)
676 @command('perfdirstatefoldmap', formatteropts)
677 def perfdirstatefoldmap(ui, repo, **opts):
677 def perfdirstatefoldmap(ui, repo, **opts):
678 timer, fm = gettimer(ui, opts)
678 timer, fm = gettimer(ui, opts)
679 dirstate = repo.dirstate
679 dirstate = repo.dirstate
680 'a' in dirstate
680 'a' in dirstate
681 def d():
681 def d():
682 dirstate._map.filefoldmap.get('a')
682 dirstate._map.filefoldmap.get('a')
683 del dirstate._map.filefoldmap
683 del dirstate._map.filefoldmap
684 timer(d)
684 timer(d)
685 fm.end()
685 fm.end()
686
686
687 @command('perfdirfoldmap', formatteropts)
687 @command('perfdirfoldmap', formatteropts)
688 def perfdirfoldmap(ui, repo, **opts):
688 def perfdirfoldmap(ui, repo, **opts):
689 timer, fm = gettimer(ui, opts)
689 timer, fm = gettimer(ui, opts)
690 dirstate = repo.dirstate
690 dirstate = repo.dirstate
691 'a' in dirstate
691 'a' in dirstate
692 def d():
692 def d():
693 dirstate._map.dirfoldmap.get('a')
693 dirstate._map.dirfoldmap.get('a')
694 del dirstate._map.dirfoldmap
694 del dirstate._map.dirfoldmap
695 del dirstate._map._dirs
695 del dirstate._map._dirs
696 timer(d)
696 timer(d)
697 fm.end()
697 fm.end()
698
698
699 @command('perfdirstatewrite', formatteropts)
699 @command('perfdirstatewrite', formatteropts)
700 def perfdirstatewrite(ui, repo, **opts):
700 def perfdirstatewrite(ui, repo, **opts):
701 timer, fm = gettimer(ui, opts)
701 timer, fm = gettimer(ui, opts)
702 ds = repo.dirstate
702 ds = repo.dirstate
703 "a" in ds
703 "a" in ds
704 def d():
704 def d():
705 ds._dirty = True
705 ds._dirty = True
706 ds.write(repo.currenttransaction())
706 ds.write(repo.currenttransaction())
707 timer(d)
707 timer(d)
708 fm.end()
708 fm.end()
709
709
710 @command('perfmergecalculate',
710 @command('perfmergecalculate',
711 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
711 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
712 def perfmergecalculate(ui, repo, rev, **opts):
712 def perfmergecalculate(ui, repo, rev, **opts):
713 timer, fm = gettimer(ui, opts)
713 timer, fm = gettimer(ui, opts)
714 wctx = repo[None]
714 wctx = repo[None]
715 rctx = scmutil.revsingle(repo, rev, rev)
715 rctx = scmutil.revsingle(repo, rev, rev)
716 ancestor = wctx.ancestor(rctx)
716 ancestor = wctx.ancestor(rctx)
717 # we don't want working dir files to be stat'd in the benchmark, so prime
717 # we don't want working dir files to be stat'd in the benchmark, so prime
718 # that cache
718 # that cache
719 wctx.dirty()
719 wctx.dirty()
720 def d():
720 def d():
721 # acceptremote is True because we don't want prompts in the middle of
721 # acceptremote is True because we don't want prompts in the middle of
722 # our benchmark
722 # our benchmark
723 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
723 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
724 acceptremote=True, followcopies=True)
724 acceptremote=True, followcopies=True)
725 timer(d)
725 timer(d)
726 fm.end()
726 fm.end()
727
727
728 @command('perfpathcopies', [], "REV REV")
728 @command('perfpathcopies', [], "REV REV")
729 def perfpathcopies(ui, repo, rev1, rev2, **opts):
729 def perfpathcopies(ui, repo, rev1, rev2, **opts):
730 timer, fm = gettimer(ui, opts)
730 timer, fm = gettimer(ui, opts)
731 ctx1 = scmutil.revsingle(repo, rev1, rev1)
731 ctx1 = scmutil.revsingle(repo, rev1, rev1)
732 ctx2 = scmutil.revsingle(repo, rev2, rev2)
732 ctx2 = scmutil.revsingle(repo, rev2, rev2)
733 def d():
733 def d():
734 copies.pathcopies(ctx1, ctx2)
734 copies.pathcopies(ctx1, ctx2)
735 timer(d)
735 timer(d)
736 fm.end()
736 fm.end()
737
737
738 @command('perfphases',
738 @command('perfphases',
739 [('', 'full', False, 'include file reading time too'),
739 [('', 'full', False, 'include file reading time too'),
740 ], "")
740 ], "")
741 def perfphases(ui, repo, **opts):
741 def perfphases(ui, repo, **opts):
742 """benchmark phasesets computation"""
742 """benchmark phasesets computation"""
743 timer, fm = gettimer(ui, opts)
743 timer, fm = gettimer(ui, opts)
744 _phases = repo._phasecache
744 _phases = repo._phasecache
745 full = opts.get('full')
745 full = opts.get('full')
746 def d():
746 def d():
747 phases = _phases
747 phases = _phases
748 if full:
748 if full:
749 clearfilecache(repo, '_phasecache')
749 clearfilecache(repo, '_phasecache')
750 phases = repo._phasecache
750 phases = repo._phasecache
751 phases.invalidate()
751 phases.invalidate()
752 phases.loadphaserevs(repo)
752 phases.loadphaserevs(repo)
753 timer(d)
753 timer(d)
754 fm.end()
754 fm.end()
755
755
756 @command('perfmanifest', [], 'REV')
756 @command('perfmanifest', [], 'REV')
757 def perfmanifest(ui, repo, rev, **opts):
757 def perfmanifest(ui, repo, rev, **opts):
758 timer, fm = gettimer(ui, opts)
758 timer, fm = gettimer(ui, opts)
759 ctx = scmutil.revsingle(repo, rev, rev)
759 ctx = scmutil.revsingle(repo, rev, rev)
760 t = ctx.manifestnode()
760 t = ctx.manifestnode()
761 def d():
761 def d():
762 repo.manifestlog.clearcaches()
762 repo.manifestlog.clearcaches()
763 repo.manifestlog[t].read()
763 repo.manifestlog[t].read()
764 timer(d)
764 timer(d)
765 fm.end()
765 fm.end()
766
766
767 @command('perfchangeset', formatteropts)
767 @command('perfchangeset', formatteropts)
768 def perfchangeset(ui, repo, rev, **opts):
768 def perfchangeset(ui, repo, rev, **opts):
769 timer, fm = gettimer(ui, opts)
769 timer, fm = gettimer(ui, opts)
770 n = repo[rev].node()
770 n = scmutil.revsingle(repo, rev).node()
771 def d():
771 def d():
772 repo.changelog.read(n)
772 repo.changelog.read(n)
773 #repo.changelog._cache = None
773 #repo.changelog._cache = None
774 timer(d)
774 timer(d)
775 fm.end()
775 fm.end()
776
776
777 @command('perfindex', formatteropts)
777 @command('perfindex', formatteropts)
778 def perfindex(ui, repo, **opts):
778 def perfindex(ui, repo, **opts):
779 import mercurial.revlog
779 import mercurial.revlog
780 timer, fm = gettimer(ui, opts)
780 timer, fm = gettimer(ui, opts)
781 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
781 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
782 n = repo["tip"].node()
782 n = repo["tip"].node()
783 svfs = getsvfs(repo)
783 svfs = getsvfs(repo)
784 def d():
784 def d():
785 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
785 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
786 cl.rev(n)
786 cl.rev(n)
787 timer(d)
787 timer(d)
788 fm.end()
788 fm.end()
789
789
790 @command('perfstartup', formatteropts)
790 @command('perfstartup', formatteropts)
791 def perfstartup(ui, repo, **opts):
791 def perfstartup(ui, repo, **opts):
792 timer, fm = gettimer(ui, opts)
792 timer, fm = gettimer(ui, opts)
793 cmd = sys.argv[0]
793 cmd = sys.argv[0]
794 def d():
794 def d():
795 if os.name != 'nt':
795 if os.name != 'nt':
796 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
796 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
797 else:
797 else:
798 os.environ['HGRCPATH'] = ' '
798 os.environ['HGRCPATH'] = ' '
799 os.system("%s version -q > NUL" % cmd)
799 os.system("%s version -q > NUL" % cmd)
800 timer(d)
800 timer(d)
801 fm.end()
801 fm.end()
802
802
803 @command('perfparents', formatteropts)
803 @command('perfparents', formatteropts)
804 def perfparents(ui, repo, **opts):
804 def perfparents(ui, repo, **opts):
805 timer, fm = gettimer(ui, opts)
805 timer, fm = gettimer(ui, opts)
806 # control the number of commits perfparents iterates over
806 # control the number of commits perfparents iterates over
807 # experimental config: perf.parentscount
807 # experimental config: perf.parentscount
808 count = getint(ui, "perf", "parentscount", 1000)
808 count = getint(ui, "perf", "parentscount", 1000)
809 if len(repo.changelog) < count:
809 if len(repo.changelog) < count:
810 raise error.Abort("repo needs %d commits for this test" % count)
810 raise error.Abort("repo needs %d commits for this test" % count)
811 repo = repo.unfiltered()
811 repo = repo.unfiltered()
812 nl = [repo.changelog.node(i) for i in xrange(count)]
812 nl = [repo.changelog.node(i) for i in xrange(count)]
813 def d():
813 def d():
814 for n in nl:
814 for n in nl:
815 repo.changelog.parents(n)
815 repo.changelog.parents(n)
816 timer(d)
816 timer(d)
817 fm.end()
817 fm.end()
818
818
819 @command('perfctxfiles', formatteropts)
819 @command('perfctxfiles', formatteropts)
820 def perfctxfiles(ui, repo, x, **opts):
820 def perfctxfiles(ui, repo, x, **opts):
821 x = int(x)
821 x = int(x)
822 timer, fm = gettimer(ui, opts)
822 timer, fm = gettimer(ui, opts)
823 def d():
823 def d():
824 len(repo[x].files())
824 len(repo[x].files())
825 timer(d)
825 timer(d)
826 fm.end()
826 fm.end()
827
827
828 @command('perfrawfiles', formatteropts)
828 @command('perfrawfiles', formatteropts)
829 def perfrawfiles(ui, repo, x, **opts):
829 def perfrawfiles(ui, repo, x, **opts):
830 x = int(x)
830 x = int(x)
831 timer, fm = gettimer(ui, opts)
831 timer, fm = gettimer(ui, opts)
832 cl = repo.changelog
832 cl = repo.changelog
833 def d():
833 def d():
834 len(cl.read(x)[3])
834 len(cl.read(x)[3])
835 timer(d)
835 timer(d)
836 fm.end()
836 fm.end()
837
837
838 @command('perflookup', formatteropts)
838 @command('perflookup', formatteropts)
839 def perflookup(ui, repo, rev, **opts):
839 def perflookup(ui, repo, rev, **opts):
840 timer, fm = gettimer(ui, opts)
840 timer, fm = gettimer(ui, opts)
841 timer(lambda: len(repo.lookup(rev)))
841 timer(lambda: len(repo.lookup(rev)))
842 fm.end()
842 fm.end()
843
843
844 @command('perfrevrange', formatteropts)
844 @command('perfrevrange', formatteropts)
845 def perfrevrange(ui, repo, *specs, **opts):
845 def perfrevrange(ui, repo, *specs, **opts):
846 timer, fm = gettimer(ui, opts)
846 timer, fm = gettimer(ui, opts)
847 revrange = scmutil.revrange
847 revrange = scmutil.revrange
848 timer(lambda: len(revrange(repo, specs)))
848 timer(lambda: len(revrange(repo, specs)))
849 fm.end()
849 fm.end()
850
850
851 @command('perfnodelookup', formatteropts)
851 @command('perfnodelookup', formatteropts)
852 def perfnodelookup(ui, repo, rev, **opts):
852 def perfnodelookup(ui, repo, rev, **opts):
853 timer, fm = gettimer(ui, opts)
853 timer, fm = gettimer(ui, opts)
854 import mercurial.revlog
854 import mercurial.revlog
855 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
855 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
856 n = repo[rev].node()
856 n = scmutil.revsingle(repo, rev).node()
857 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
857 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
858 def d():
858 def d():
859 cl.rev(n)
859 cl.rev(n)
860 clearcaches(cl)
860 clearcaches(cl)
861 timer(d)
861 timer(d)
862 fm.end()
862 fm.end()
863
863
864 @command('perflog',
864 @command('perflog',
865 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
865 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
866 def perflog(ui, repo, rev=None, **opts):
866 def perflog(ui, repo, rev=None, **opts):
867 if rev is None:
867 if rev is None:
868 rev=[]
868 rev=[]
869 timer, fm = gettimer(ui, opts)
869 timer, fm = gettimer(ui, opts)
870 ui.pushbuffer()
870 ui.pushbuffer()
871 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
871 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
872 copies=opts.get('rename')))
872 copies=opts.get('rename')))
873 ui.popbuffer()
873 ui.popbuffer()
874 fm.end()
874 fm.end()
875
875
876 @command('perfmoonwalk', formatteropts)
876 @command('perfmoonwalk', formatteropts)
877 def perfmoonwalk(ui, repo, **opts):
877 def perfmoonwalk(ui, repo, **opts):
878 """benchmark walking the changelog backwards
878 """benchmark walking the changelog backwards
879
879
880 This also loads the changelog data for each revision in the changelog.
880 This also loads the changelog data for each revision in the changelog.
881 """
881 """
882 timer, fm = gettimer(ui, opts)
882 timer, fm = gettimer(ui, opts)
883 def moonwalk():
883 def moonwalk():
884 for i in xrange(len(repo), -1, -1):
884 for i in xrange(len(repo), -1, -1):
885 ctx = repo[i]
885 ctx = repo[i]
886 ctx.branch() # read changelog data (in addition to the index)
886 ctx.branch() # read changelog data (in addition to the index)
887 timer(moonwalk)
887 timer(moonwalk)
888 fm.end()
888 fm.end()
889
889
890 @command('perftemplating', formatteropts)
890 @command('perftemplating', formatteropts)
891 def perftemplating(ui, repo, rev=None, **opts):
891 def perftemplating(ui, repo, rev=None, **opts):
892 if rev is None:
892 if rev is None:
893 rev=[]
893 rev=[]
894 timer, fm = gettimer(ui, opts)
894 timer, fm = gettimer(ui, opts)
895 ui.pushbuffer()
895 ui.pushbuffer()
896 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
896 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
897 template='{date|shortdate} [{rev}:{node|short}]'
897 template='{date|shortdate} [{rev}:{node|short}]'
898 ' {author|person}: {desc|firstline}\n'))
898 ' {author|person}: {desc|firstline}\n'))
899 ui.popbuffer()
899 ui.popbuffer()
900 fm.end()
900 fm.end()
901
901
902 @command('perfcca', formatteropts)
902 @command('perfcca', formatteropts)
903 def perfcca(ui, repo, **opts):
903 def perfcca(ui, repo, **opts):
904 timer, fm = gettimer(ui, opts)
904 timer, fm = gettimer(ui, opts)
905 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
905 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
906 fm.end()
906 fm.end()
907
907
908 @command('perffncacheload', formatteropts)
908 @command('perffncacheload', formatteropts)
909 def perffncacheload(ui, repo, **opts):
909 def perffncacheload(ui, repo, **opts):
910 timer, fm = gettimer(ui, opts)
910 timer, fm = gettimer(ui, opts)
911 s = repo.store
911 s = repo.store
912 def d():
912 def d():
913 s.fncache._load()
913 s.fncache._load()
914 timer(d)
914 timer(d)
915 fm.end()
915 fm.end()
916
916
917 @command('perffncachewrite', formatteropts)
917 @command('perffncachewrite', formatteropts)
918 def perffncachewrite(ui, repo, **opts):
918 def perffncachewrite(ui, repo, **opts):
919 timer, fm = gettimer(ui, opts)
919 timer, fm = gettimer(ui, opts)
920 s = repo.store
920 s = repo.store
921 s.fncache._load()
921 s.fncache._load()
922 lock = repo.lock()
922 lock = repo.lock()
923 tr = repo.transaction('perffncachewrite')
923 tr = repo.transaction('perffncachewrite')
924 def d():
924 def d():
925 s.fncache._dirty = True
925 s.fncache._dirty = True
926 s.fncache.write(tr)
926 s.fncache.write(tr)
927 timer(d)
927 timer(d)
928 tr.close()
928 tr.close()
929 lock.release()
929 lock.release()
930 fm.end()
930 fm.end()
931
931
932 @command('perffncacheencode', formatteropts)
932 @command('perffncacheencode', formatteropts)
933 def perffncacheencode(ui, repo, **opts):
933 def perffncacheencode(ui, repo, **opts):
934 timer, fm = gettimer(ui, opts)
934 timer, fm = gettimer(ui, opts)
935 s = repo.store
935 s = repo.store
936 s.fncache._load()
936 s.fncache._load()
937 def d():
937 def d():
938 for p in s.fncache.entries:
938 for p in s.fncache.entries:
939 s.encode(p)
939 s.encode(p)
940 timer(d)
940 timer(d)
941 fm.end()
941 fm.end()
942
942
943 def _bdiffworker(q, blocks, xdiff, ready, done):
943 def _bdiffworker(q, blocks, xdiff, ready, done):
944 while not done.is_set():
944 while not done.is_set():
945 pair = q.get()
945 pair = q.get()
946 while pair is not None:
946 while pair is not None:
947 if xdiff:
947 if xdiff:
948 mdiff.bdiff.xdiffblocks(*pair)
948 mdiff.bdiff.xdiffblocks(*pair)
949 elif blocks:
949 elif blocks:
950 mdiff.bdiff.blocks(*pair)
950 mdiff.bdiff.blocks(*pair)
951 else:
951 else:
952 mdiff.textdiff(*pair)
952 mdiff.textdiff(*pair)
953 q.task_done()
953 q.task_done()
954 pair = q.get()
954 pair = q.get()
955 q.task_done() # for the None one
955 q.task_done() # for the None one
956 with ready:
956 with ready:
957 ready.wait()
957 ready.wait()
958
958
959 @command('perfbdiff', revlogopts + formatteropts + [
959 @command('perfbdiff', revlogopts + formatteropts + [
960 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
960 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
961 ('', 'alldata', False, 'test bdiffs for all associated revisions'),
961 ('', 'alldata', False, 'test bdiffs for all associated revisions'),
962 ('', 'threads', 0, 'number of thread to use (disable with 0)'),
962 ('', 'threads', 0, 'number of thread to use (disable with 0)'),
963 ('', 'blocks', False, 'test computing diffs into blocks'),
963 ('', 'blocks', False, 'test computing diffs into blocks'),
964 ('', 'xdiff', False, 'use xdiff algorithm'),
964 ('', 'xdiff', False, 'use xdiff algorithm'),
965 ],
965 ],
966
966
967 '-c|-m|FILE REV')
967 '-c|-m|FILE REV')
968 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
968 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
969 """benchmark a bdiff between revisions
969 """benchmark a bdiff between revisions
970
970
971 By default, benchmark a bdiff between its delta parent and itself.
971 By default, benchmark a bdiff between its delta parent and itself.
972
972
973 With ``--count``, benchmark bdiffs between delta parents and self for N
973 With ``--count``, benchmark bdiffs between delta parents and self for N
974 revisions starting at the specified revision.
974 revisions starting at the specified revision.
975
975
976 With ``--alldata``, assume the requested revision is a changeset and
976 With ``--alldata``, assume the requested revision is a changeset and
977 measure bdiffs for all changes related to that changeset (manifest
977 measure bdiffs for all changes related to that changeset (manifest
978 and filelogs).
978 and filelogs).
979 """
979 """
980 opts = pycompat.byteskwargs(opts)
980 opts = pycompat.byteskwargs(opts)
981
981
982 if opts['xdiff'] and not opts['blocks']:
982 if opts['xdiff'] and not opts['blocks']:
983 raise error.CommandError('perfbdiff', '--xdiff requires --blocks')
983 raise error.CommandError('perfbdiff', '--xdiff requires --blocks')
984
984
985 if opts['alldata']:
985 if opts['alldata']:
986 opts['changelog'] = True
986 opts['changelog'] = True
987
987
988 if opts.get('changelog') or opts.get('manifest'):
988 if opts.get('changelog') or opts.get('manifest'):
989 file_, rev = None, file_
989 file_, rev = None, file_
990 elif rev is None:
990 elif rev is None:
991 raise error.CommandError('perfbdiff', 'invalid arguments')
991 raise error.CommandError('perfbdiff', 'invalid arguments')
992
992
993 blocks = opts['blocks']
993 blocks = opts['blocks']
994 xdiff = opts['xdiff']
994 xdiff = opts['xdiff']
995 textpairs = []
995 textpairs = []
996
996
997 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
997 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
998
998
999 startrev = r.rev(r.lookup(rev))
999 startrev = r.rev(r.lookup(rev))
1000 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1000 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1001 if opts['alldata']:
1001 if opts['alldata']:
1002 # Load revisions associated with changeset.
1002 # Load revisions associated with changeset.
1003 ctx = repo[rev]
1003 ctx = repo[rev]
1004 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
1004 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
1005 for pctx in ctx.parents():
1005 for pctx in ctx.parents():
1006 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
1006 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
1007 textpairs.append((pman, mtext))
1007 textpairs.append((pman, mtext))
1008
1008
1009 # Load filelog revisions by iterating manifest delta.
1009 # Load filelog revisions by iterating manifest delta.
1010 man = ctx.manifest()
1010 man = ctx.manifest()
1011 pman = ctx.p1().manifest()
1011 pman = ctx.p1().manifest()
1012 for filename, change in pman.diff(man).items():
1012 for filename, change in pman.diff(man).items():
1013 fctx = repo.file(filename)
1013 fctx = repo.file(filename)
1014 f1 = fctx.revision(change[0][0] or -1)
1014 f1 = fctx.revision(change[0][0] or -1)
1015 f2 = fctx.revision(change[1][0] or -1)
1015 f2 = fctx.revision(change[1][0] or -1)
1016 textpairs.append((f1, f2))
1016 textpairs.append((f1, f2))
1017 else:
1017 else:
1018 dp = r.deltaparent(rev)
1018 dp = r.deltaparent(rev)
1019 textpairs.append((r.revision(dp), r.revision(rev)))
1019 textpairs.append((r.revision(dp), r.revision(rev)))
1020
1020
1021 withthreads = threads > 0
1021 withthreads = threads > 0
1022 if not withthreads:
1022 if not withthreads:
1023 def d():
1023 def d():
1024 for pair in textpairs:
1024 for pair in textpairs:
1025 if xdiff:
1025 if xdiff:
1026 mdiff.bdiff.xdiffblocks(*pair)
1026 mdiff.bdiff.xdiffblocks(*pair)
1027 elif blocks:
1027 elif blocks:
1028 mdiff.bdiff.blocks(*pair)
1028 mdiff.bdiff.blocks(*pair)
1029 else:
1029 else:
1030 mdiff.textdiff(*pair)
1030 mdiff.textdiff(*pair)
1031 else:
1031 else:
1032 q = util.queue()
1032 q = util.queue()
1033 for i in xrange(threads):
1033 for i in xrange(threads):
1034 q.put(None)
1034 q.put(None)
1035 ready = threading.Condition()
1035 ready = threading.Condition()
1036 done = threading.Event()
1036 done = threading.Event()
1037 for i in xrange(threads):
1037 for i in xrange(threads):
1038 threading.Thread(target=_bdiffworker,
1038 threading.Thread(target=_bdiffworker,
1039 args=(q, blocks, xdiff, ready, done)).start()
1039 args=(q, blocks, xdiff, ready, done)).start()
1040 q.join()
1040 q.join()
1041 def d():
1041 def d():
1042 for pair in textpairs:
1042 for pair in textpairs:
1043 q.put(pair)
1043 q.put(pair)
1044 for i in xrange(threads):
1044 for i in xrange(threads):
1045 q.put(None)
1045 q.put(None)
1046 with ready:
1046 with ready:
1047 ready.notify_all()
1047 ready.notify_all()
1048 q.join()
1048 q.join()
1049 timer, fm = gettimer(ui, opts)
1049 timer, fm = gettimer(ui, opts)
1050 timer(d)
1050 timer(d)
1051 fm.end()
1051 fm.end()
1052
1052
1053 if withthreads:
1053 if withthreads:
1054 done.set()
1054 done.set()
1055 for i in xrange(threads):
1055 for i in xrange(threads):
1056 q.put(None)
1056 q.put(None)
1057 with ready:
1057 with ready:
1058 ready.notify_all()
1058 ready.notify_all()
1059
1059
1060 @command('perfunidiff', revlogopts + formatteropts + [
1060 @command('perfunidiff', revlogopts + formatteropts + [
1061 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
1061 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
1062 ('', 'alldata', False, 'test unidiffs for all associated revisions'),
1062 ('', 'alldata', False, 'test unidiffs for all associated revisions'),
1063 ], '-c|-m|FILE REV')
1063 ], '-c|-m|FILE REV')
1064 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
1064 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
1065 """benchmark a unified diff between revisions
1065 """benchmark a unified diff between revisions
1066
1066
1067 This doesn't include any copy tracing - it's just a unified diff
1067 This doesn't include any copy tracing - it's just a unified diff
1068 of the texts.
1068 of the texts.
1069
1069
1070 By default, benchmark a diff between its delta parent and itself.
1070 By default, benchmark a diff between its delta parent and itself.
1071
1071
1072 With ``--count``, benchmark diffs between delta parents and self for N
1072 With ``--count``, benchmark diffs between delta parents and self for N
1073 revisions starting at the specified revision.
1073 revisions starting at the specified revision.
1074
1074
1075 With ``--alldata``, assume the requested revision is a changeset and
1075 With ``--alldata``, assume the requested revision is a changeset and
1076 measure diffs for all changes related to that changeset (manifest
1076 measure diffs for all changes related to that changeset (manifest
1077 and filelogs).
1077 and filelogs).
1078 """
1078 """
1079 if opts['alldata']:
1079 if opts['alldata']:
1080 opts['changelog'] = True
1080 opts['changelog'] = True
1081
1081
1082 if opts.get('changelog') or opts.get('manifest'):
1082 if opts.get('changelog') or opts.get('manifest'):
1083 file_, rev = None, file_
1083 file_, rev = None, file_
1084 elif rev is None:
1084 elif rev is None:
1085 raise error.CommandError('perfunidiff', 'invalid arguments')
1085 raise error.CommandError('perfunidiff', 'invalid arguments')
1086
1086
1087 textpairs = []
1087 textpairs = []
1088
1088
1089 r = cmdutil.openrevlog(repo, 'perfunidiff', file_, opts)
1089 r = cmdutil.openrevlog(repo, 'perfunidiff', file_, opts)
1090
1090
1091 startrev = r.rev(r.lookup(rev))
1091 startrev = r.rev(r.lookup(rev))
1092 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1092 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1093 if opts['alldata']:
1093 if opts['alldata']:
1094 # Load revisions associated with changeset.
1094 # Load revisions associated with changeset.
1095 ctx = repo[rev]
1095 ctx = repo[rev]
1096 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
1096 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
1097 for pctx in ctx.parents():
1097 for pctx in ctx.parents():
1098 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
1098 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
1099 textpairs.append((pman, mtext))
1099 textpairs.append((pman, mtext))
1100
1100
1101 # Load filelog revisions by iterating manifest delta.
1101 # Load filelog revisions by iterating manifest delta.
1102 man = ctx.manifest()
1102 man = ctx.manifest()
1103 pman = ctx.p1().manifest()
1103 pman = ctx.p1().manifest()
1104 for filename, change in pman.diff(man).items():
1104 for filename, change in pman.diff(man).items():
1105 fctx = repo.file(filename)
1105 fctx = repo.file(filename)
1106 f1 = fctx.revision(change[0][0] or -1)
1106 f1 = fctx.revision(change[0][0] or -1)
1107 f2 = fctx.revision(change[1][0] or -1)
1107 f2 = fctx.revision(change[1][0] or -1)
1108 textpairs.append((f1, f2))
1108 textpairs.append((f1, f2))
1109 else:
1109 else:
1110 dp = r.deltaparent(rev)
1110 dp = r.deltaparent(rev)
1111 textpairs.append((r.revision(dp), r.revision(rev)))
1111 textpairs.append((r.revision(dp), r.revision(rev)))
1112
1112
1113 def d():
1113 def d():
1114 for left, right in textpairs:
1114 for left, right in textpairs:
1115 # The date strings don't matter, so we pass empty strings.
1115 # The date strings don't matter, so we pass empty strings.
1116 headerlines, hunks = mdiff.unidiff(
1116 headerlines, hunks = mdiff.unidiff(
1117 left, '', right, '', 'left', 'right', binary=False)
1117 left, '', right, '', 'left', 'right', binary=False)
1118 # consume iterators in roughly the way patch.py does
1118 # consume iterators in roughly the way patch.py does
1119 b'\n'.join(headerlines)
1119 b'\n'.join(headerlines)
1120 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
1120 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
1121 timer, fm = gettimer(ui, opts)
1121 timer, fm = gettimer(ui, opts)
1122 timer(d)
1122 timer(d)
1123 fm.end()
1123 fm.end()
1124
1124
1125 @command('perfdiffwd', formatteropts)
1125 @command('perfdiffwd', formatteropts)
1126 def perfdiffwd(ui, repo, **opts):
1126 def perfdiffwd(ui, repo, **opts):
1127 """Profile diff of working directory changes"""
1127 """Profile diff of working directory changes"""
1128 timer, fm = gettimer(ui, opts)
1128 timer, fm = gettimer(ui, opts)
1129 options = {
1129 options = {
1130 'w': 'ignore_all_space',
1130 'w': 'ignore_all_space',
1131 'b': 'ignore_space_change',
1131 'b': 'ignore_space_change',
1132 'B': 'ignore_blank_lines',
1132 'B': 'ignore_blank_lines',
1133 }
1133 }
1134
1134
1135 for diffopt in ('', 'w', 'b', 'B', 'wB'):
1135 for diffopt in ('', 'w', 'b', 'B', 'wB'):
1136 opts = dict((options[c], '1') for c in diffopt)
1136 opts = dict((options[c], '1') for c in diffopt)
1137 def d():
1137 def d():
1138 ui.pushbuffer()
1138 ui.pushbuffer()
1139 commands.diff(ui, repo, **opts)
1139 commands.diff(ui, repo, **opts)
1140 ui.popbuffer()
1140 ui.popbuffer()
1141 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
1141 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
1142 timer(d, title)
1142 timer(d, title)
1143 fm.end()
1143 fm.end()
1144
1144
1145 @command('perfrevlogindex', revlogopts + formatteropts,
1145 @command('perfrevlogindex', revlogopts + formatteropts,
1146 '-c|-m|FILE')
1146 '-c|-m|FILE')
1147 def perfrevlogindex(ui, repo, file_=None, **opts):
1147 def perfrevlogindex(ui, repo, file_=None, **opts):
1148 """Benchmark operations against a revlog index.
1148 """Benchmark operations against a revlog index.
1149
1149
1150 This tests constructing a revlog instance, reading index data,
1150 This tests constructing a revlog instance, reading index data,
1151 parsing index data, and performing various operations related to
1151 parsing index data, and performing various operations related to
1152 index data.
1152 index data.
1153 """
1153 """
1154
1154
1155 rl = cmdutil.openrevlog(repo, 'perfrevlogindex', file_, opts)
1155 rl = cmdutil.openrevlog(repo, 'perfrevlogindex', file_, opts)
1156
1156
1157 opener = getattr(rl, 'opener') # trick linter
1157 opener = getattr(rl, 'opener') # trick linter
1158 indexfile = rl.indexfile
1158 indexfile = rl.indexfile
1159 data = opener.read(indexfile)
1159 data = opener.read(indexfile)
1160
1160
1161 header = struct.unpack('>I', data[0:4])[0]
1161 header = struct.unpack('>I', data[0:4])[0]
1162 version = header & 0xFFFF
1162 version = header & 0xFFFF
1163 if version == 1:
1163 if version == 1:
1164 revlogio = revlog.revlogio()
1164 revlogio = revlog.revlogio()
1165 inline = header & (1 << 16)
1165 inline = header & (1 << 16)
1166 else:
1166 else:
1167 raise error.Abort(('unsupported revlog version: %d') % version)
1167 raise error.Abort(('unsupported revlog version: %d') % version)
1168
1168
1169 rllen = len(rl)
1169 rllen = len(rl)
1170
1170
1171 node0 = rl.node(0)
1171 node0 = rl.node(0)
1172 node25 = rl.node(rllen // 4)
1172 node25 = rl.node(rllen // 4)
1173 node50 = rl.node(rllen // 2)
1173 node50 = rl.node(rllen // 2)
1174 node75 = rl.node(rllen // 4 * 3)
1174 node75 = rl.node(rllen // 4 * 3)
1175 node100 = rl.node(rllen - 1)
1175 node100 = rl.node(rllen - 1)
1176
1176
1177 allrevs = range(rllen)
1177 allrevs = range(rllen)
1178 allrevsrev = list(reversed(allrevs))
1178 allrevsrev = list(reversed(allrevs))
1179 allnodes = [rl.node(rev) for rev in range(rllen)]
1179 allnodes = [rl.node(rev) for rev in range(rllen)]
1180 allnodesrev = list(reversed(allnodes))
1180 allnodesrev = list(reversed(allnodes))
1181
1181
1182 def constructor():
1182 def constructor():
1183 revlog.revlog(opener, indexfile)
1183 revlog.revlog(opener, indexfile)
1184
1184
1185 def read():
1185 def read():
1186 with opener(indexfile) as fh:
1186 with opener(indexfile) as fh:
1187 fh.read()
1187 fh.read()
1188
1188
1189 def parseindex():
1189 def parseindex():
1190 revlogio.parseindex(data, inline)
1190 revlogio.parseindex(data, inline)
1191
1191
1192 def getentry(revornode):
1192 def getentry(revornode):
1193 index = revlogio.parseindex(data, inline)[0]
1193 index = revlogio.parseindex(data, inline)[0]
1194 index[revornode]
1194 index[revornode]
1195
1195
1196 def getentries(revs, count=1):
1196 def getentries(revs, count=1):
1197 index = revlogio.parseindex(data, inline)[0]
1197 index = revlogio.parseindex(data, inline)[0]
1198
1198
1199 for i in range(count):
1199 for i in range(count):
1200 for rev in revs:
1200 for rev in revs:
1201 index[rev]
1201 index[rev]
1202
1202
1203 def resolvenode(node):
1203 def resolvenode(node):
1204 nodemap = revlogio.parseindex(data, inline)[1]
1204 nodemap = revlogio.parseindex(data, inline)[1]
1205 # This only works for the C code.
1205 # This only works for the C code.
1206 if nodemap is None:
1206 if nodemap is None:
1207 return
1207 return
1208
1208
1209 try:
1209 try:
1210 nodemap[node]
1210 nodemap[node]
1211 except error.RevlogError:
1211 except error.RevlogError:
1212 pass
1212 pass
1213
1213
1214 def resolvenodes(nodes, count=1):
1214 def resolvenodes(nodes, count=1):
1215 nodemap = revlogio.parseindex(data, inline)[1]
1215 nodemap = revlogio.parseindex(data, inline)[1]
1216 if nodemap is None:
1216 if nodemap is None:
1217 return
1217 return
1218
1218
1219 for i in range(count):
1219 for i in range(count):
1220 for node in nodes:
1220 for node in nodes:
1221 try:
1221 try:
1222 nodemap[node]
1222 nodemap[node]
1223 except error.RevlogError:
1223 except error.RevlogError:
1224 pass
1224 pass
1225
1225
1226 benches = [
1226 benches = [
1227 (constructor, 'revlog constructor'),
1227 (constructor, 'revlog constructor'),
1228 (read, 'read'),
1228 (read, 'read'),
1229 (parseindex, 'create index object'),
1229 (parseindex, 'create index object'),
1230 (lambda: getentry(0), 'retrieve index entry for rev 0'),
1230 (lambda: getentry(0), 'retrieve index entry for rev 0'),
1231 (lambda: resolvenode('a' * 20), 'look up missing node'),
1231 (lambda: resolvenode('a' * 20), 'look up missing node'),
1232 (lambda: resolvenode(node0), 'look up node at rev 0'),
1232 (lambda: resolvenode(node0), 'look up node at rev 0'),
1233 (lambda: resolvenode(node25), 'look up node at 1/4 len'),
1233 (lambda: resolvenode(node25), 'look up node at 1/4 len'),
1234 (lambda: resolvenode(node50), 'look up node at 1/2 len'),
1234 (lambda: resolvenode(node50), 'look up node at 1/2 len'),
1235 (lambda: resolvenode(node75), 'look up node at 3/4 len'),
1235 (lambda: resolvenode(node75), 'look up node at 3/4 len'),
1236 (lambda: resolvenode(node100), 'look up node at tip'),
1236 (lambda: resolvenode(node100), 'look up node at tip'),
1237 # 2x variation is to measure caching impact.
1237 # 2x variation is to measure caching impact.
1238 (lambda: resolvenodes(allnodes),
1238 (lambda: resolvenodes(allnodes),
1239 'look up all nodes (forward)'),
1239 'look up all nodes (forward)'),
1240 (lambda: resolvenodes(allnodes, 2),
1240 (lambda: resolvenodes(allnodes, 2),
1241 'look up all nodes 2x (forward)'),
1241 'look up all nodes 2x (forward)'),
1242 (lambda: resolvenodes(allnodesrev),
1242 (lambda: resolvenodes(allnodesrev),
1243 'look up all nodes (reverse)'),
1243 'look up all nodes (reverse)'),
1244 (lambda: resolvenodes(allnodesrev, 2),
1244 (lambda: resolvenodes(allnodesrev, 2),
1245 'look up all nodes 2x (reverse)'),
1245 'look up all nodes 2x (reverse)'),
1246 (lambda: getentries(allrevs),
1246 (lambda: getentries(allrevs),
1247 'retrieve all index entries (forward)'),
1247 'retrieve all index entries (forward)'),
1248 (lambda: getentries(allrevs, 2),
1248 (lambda: getentries(allrevs, 2),
1249 'retrieve all index entries 2x (forward)'),
1249 'retrieve all index entries 2x (forward)'),
1250 (lambda: getentries(allrevsrev),
1250 (lambda: getentries(allrevsrev),
1251 'retrieve all index entries (reverse)'),
1251 'retrieve all index entries (reverse)'),
1252 (lambda: getentries(allrevsrev, 2),
1252 (lambda: getentries(allrevsrev, 2),
1253 'retrieve all index entries 2x (reverse)'),
1253 'retrieve all index entries 2x (reverse)'),
1254 ]
1254 ]
1255
1255
1256 for fn, title in benches:
1256 for fn, title in benches:
1257 timer, fm = gettimer(ui, opts)
1257 timer, fm = gettimer(ui, opts)
1258 timer(fn, title=title)
1258 timer(fn, title=title)
1259 fm.end()
1259 fm.end()
1260
1260
1261 @command('perfrevlogrevisions', revlogopts + formatteropts +
1261 @command('perfrevlogrevisions', revlogopts + formatteropts +
1262 [('d', 'dist', 100, 'distance between the revisions'),
1262 [('d', 'dist', 100, 'distance between the revisions'),
1263 ('s', 'startrev', 0, 'revision to start reading at'),
1263 ('s', 'startrev', 0, 'revision to start reading at'),
1264 ('', 'reverse', False, 'read in reverse')],
1264 ('', 'reverse', False, 'read in reverse')],
1265 '-c|-m|FILE')
1265 '-c|-m|FILE')
1266 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
1266 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
1267 **opts):
1267 **opts):
1268 """Benchmark reading a series of revisions from a revlog.
1268 """Benchmark reading a series of revisions from a revlog.
1269
1269
1270 By default, we read every ``-d/--dist`` revision from 0 to tip of
1270 By default, we read every ``-d/--dist`` revision from 0 to tip of
1271 the specified revlog.
1271 the specified revlog.
1272
1272
1273 The start revision can be defined via ``-s/--startrev``.
1273 The start revision can be defined via ``-s/--startrev``.
1274 """
1274 """
1275 rl = cmdutil.openrevlog(repo, 'perfrevlogrevisions', file_, opts)
1275 rl = cmdutil.openrevlog(repo, 'perfrevlogrevisions', file_, opts)
1276 rllen = getlen(ui)(rl)
1276 rllen = getlen(ui)(rl)
1277
1277
1278 def d():
1278 def d():
1279 rl.clearcaches()
1279 rl.clearcaches()
1280
1280
1281 beginrev = startrev
1281 beginrev = startrev
1282 endrev = rllen
1282 endrev = rllen
1283 dist = opts['dist']
1283 dist = opts['dist']
1284
1284
1285 if reverse:
1285 if reverse:
1286 beginrev, endrev = endrev, beginrev
1286 beginrev, endrev = endrev, beginrev
1287 dist = -1 * dist
1287 dist = -1 * dist
1288
1288
1289 for x in xrange(beginrev, endrev, dist):
1289 for x in xrange(beginrev, endrev, dist):
1290 # Old revisions don't support passing int.
1290 # Old revisions don't support passing int.
1291 n = rl.node(x)
1291 n = rl.node(x)
1292 rl.revision(n)
1292 rl.revision(n)
1293
1293
1294 timer, fm = gettimer(ui, opts)
1294 timer, fm = gettimer(ui, opts)
1295 timer(d)
1295 timer(d)
1296 fm.end()
1296 fm.end()
1297
1297
1298 @command('perfrevlogchunks', revlogopts + formatteropts +
1298 @command('perfrevlogchunks', revlogopts + formatteropts +
1299 [('e', 'engines', '', 'compression engines to use'),
1299 [('e', 'engines', '', 'compression engines to use'),
1300 ('s', 'startrev', 0, 'revision to start at')],
1300 ('s', 'startrev', 0, 'revision to start at')],
1301 '-c|-m|FILE')
1301 '-c|-m|FILE')
1302 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1302 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1303 """Benchmark operations on revlog chunks.
1303 """Benchmark operations on revlog chunks.
1304
1304
1305 Logically, each revlog is a collection of fulltext revisions. However,
1305 Logically, each revlog is a collection of fulltext revisions. However,
1306 stored within each revlog are "chunks" of possibly compressed data. This
1306 stored within each revlog are "chunks" of possibly compressed data. This
1307 data needs to be read and decompressed or compressed and written.
1307 data needs to be read and decompressed or compressed and written.
1308
1308
1309 This command measures the time it takes to read+decompress and recompress
1309 This command measures the time it takes to read+decompress and recompress
1310 chunks in a revlog. It effectively isolates I/O and compression performance.
1310 chunks in a revlog. It effectively isolates I/O and compression performance.
1311 For measurements of higher-level operations like resolving revisions,
1311 For measurements of higher-level operations like resolving revisions,
1312 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1312 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1313 """
1313 """
1314 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
1314 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
1315
1315
1316 # _chunkraw was renamed to _getsegmentforrevs.
1316 # _chunkraw was renamed to _getsegmentforrevs.
1317 try:
1317 try:
1318 segmentforrevs = rl._getsegmentforrevs
1318 segmentforrevs = rl._getsegmentforrevs
1319 except AttributeError:
1319 except AttributeError:
1320 segmentforrevs = rl._chunkraw
1320 segmentforrevs = rl._chunkraw
1321
1321
1322 # Verify engines argument.
1322 # Verify engines argument.
1323 if engines:
1323 if engines:
1324 engines = set(e.strip() for e in engines.split(','))
1324 engines = set(e.strip() for e in engines.split(','))
1325 for engine in engines:
1325 for engine in engines:
1326 try:
1326 try:
1327 util.compressionengines[engine]
1327 util.compressionengines[engine]
1328 except KeyError:
1328 except KeyError:
1329 raise error.Abort('unknown compression engine: %s' % engine)
1329 raise error.Abort('unknown compression engine: %s' % engine)
1330 else:
1330 else:
1331 engines = []
1331 engines = []
1332 for e in util.compengines:
1332 for e in util.compengines:
1333 engine = util.compengines[e]
1333 engine = util.compengines[e]
1334 try:
1334 try:
1335 if engine.available():
1335 if engine.available():
1336 engine.revlogcompressor().compress('dummy')
1336 engine.revlogcompressor().compress('dummy')
1337 engines.append(e)
1337 engines.append(e)
1338 except NotImplementedError:
1338 except NotImplementedError:
1339 pass
1339 pass
1340
1340
1341 revs = list(rl.revs(startrev, len(rl) - 1))
1341 revs = list(rl.revs(startrev, len(rl) - 1))
1342
1342
1343 def rlfh(rl):
1343 def rlfh(rl):
1344 if rl._inline:
1344 if rl._inline:
1345 return getsvfs(repo)(rl.indexfile)
1345 return getsvfs(repo)(rl.indexfile)
1346 else:
1346 else:
1347 return getsvfs(repo)(rl.datafile)
1347 return getsvfs(repo)(rl.datafile)
1348
1348
1349 def doread():
1349 def doread():
1350 rl.clearcaches()
1350 rl.clearcaches()
1351 for rev in revs:
1351 for rev in revs:
1352 segmentforrevs(rev, rev)
1352 segmentforrevs(rev, rev)
1353
1353
1354 def doreadcachedfh():
1354 def doreadcachedfh():
1355 rl.clearcaches()
1355 rl.clearcaches()
1356 fh = rlfh(rl)
1356 fh = rlfh(rl)
1357 for rev in revs:
1357 for rev in revs:
1358 segmentforrevs(rev, rev, df=fh)
1358 segmentforrevs(rev, rev, df=fh)
1359
1359
1360 def doreadbatch():
1360 def doreadbatch():
1361 rl.clearcaches()
1361 rl.clearcaches()
1362 segmentforrevs(revs[0], revs[-1])
1362 segmentforrevs(revs[0], revs[-1])
1363
1363
1364 def doreadbatchcachedfh():
1364 def doreadbatchcachedfh():
1365 rl.clearcaches()
1365 rl.clearcaches()
1366 fh = rlfh(rl)
1366 fh = rlfh(rl)
1367 segmentforrevs(revs[0], revs[-1], df=fh)
1367 segmentforrevs(revs[0], revs[-1], df=fh)
1368
1368
1369 def dochunk():
1369 def dochunk():
1370 rl.clearcaches()
1370 rl.clearcaches()
1371 fh = rlfh(rl)
1371 fh = rlfh(rl)
1372 for rev in revs:
1372 for rev in revs:
1373 rl._chunk(rev, df=fh)
1373 rl._chunk(rev, df=fh)
1374
1374
1375 chunks = [None]
1375 chunks = [None]
1376
1376
1377 def dochunkbatch():
1377 def dochunkbatch():
1378 rl.clearcaches()
1378 rl.clearcaches()
1379 fh = rlfh(rl)
1379 fh = rlfh(rl)
1380 # Save chunks as a side-effect.
1380 # Save chunks as a side-effect.
1381 chunks[0] = rl._chunks(revs, df=fh)
1381 chunks[0] = rl._chunks(revs, df=fh)
1382
1382
1383 def docompress(compressor):
1383 def docompress(compressor):
1384 rl.clearcaches()
1384 rl.clearcaches()
1385
1385
1386 try:
1386 try:
1387 # Swap in the requested compression engine.
1387 # Swap in the requested compression engine.
1388 oldcompressor = rl._compressor
1388 oldcompressor = rl._compressor
1389 rl._compressor = compressor
1389 rl._compressor = compressor
1390 for chunk in chunks[0]:
1390 for chunk in chunks[0]:
1391 rl.compress(chunk)
1391 rl.compress(chunk)
1392 finally:
1392 finally:
1393 rl._compressor = oldcompressor
1393 rl._compressor = oldcompressor
1394
1394
1395 benches = [
1395 benches = [
1396 (lambda: doread(), 'read'),
1396 (lambda: doread(), 'read'),
1397 (lambda: doreadcachedfh(), 'read w/ reused fd'),
1397 (lambda: doreadcachedfh(), 'read w/ reused fd'),
1398 (lambda: doreadbatch(), 'read batch'),
1398 (lambda: doreadbatch(), 'read batch'),
1399 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
1399 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
1400 (lambda: dochunk(), 'chunk'),
1400 (lambda: dochunk(), 'chunk'),
1401 (lambda: dochunkbatch(), 'chunk batch'),
1401 (lambda: dochunkbatch(), 'chunk batch'),
1402 ]
1402 ]
1403
1403
1404 for engine in sorted(engines):
1404 for engine in sorted(engines):
1405 compressor = util.compengines[engine].revlogcompressor()
1405 compressor = util.compengines[engine].revlogcompressor()
1406 benches.append((functools.partial(docompress, compressor),
1406 benches.append((functools.partial(docompress, compressor),
1407 'compress w/ %s' % engine))
1407 'compress w/ %s' % engine))
1408
1408
1409 for fn, title in benches:
1409 for fn, title in benches:
1410 timer, fm = gettimer(ui, opts)
1410 timer, fm = gettimer(ui, opts)
1411 timer(fn, title=title)
1411 timer(fn, title=title)
1412 fm.end()
1412 fm.end()
1413
1413
1414 @command('perfrevlogrevision', revlogopts + formatteropts +
1414 @command('perfrevlogrevision', revlogopts + formatteropts +
1415 [('', 'cache', False, 'use caches instead of clearing')],
1415 [('', 'cache', False, 'use caches instead of clearing')],
1416 '-c|-m|FILE REV')
1416 '-c|-m|FILE REV')
1417 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1417 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1418 """Benchmark obtaining a revlog revision.
1418 """Benchmark obtaining a revlog revision.
1419
1419
1420 Obtaining a revlog revision consists of roughly the following steps:
1420 Obtaining a revlog revision consists of roughly the following steps:
1421
1421
1422 1. Compute the delta chain
1422 1. Compute the delta chain
1423 2. Obtain the raw chunks for that delta chain
1423 2. Obtain the raw chunks for that delta chain
1424 3. Decompress each raw chunk
1424 3. Decompress each raw chunk
1425 4. Apply binary patches to obtain fulltext
1425 4. Apply binary patches to obtain fulltext
1426 5. Verify hash of fulltext
1426 5. Verify hash of fulltext
1427
1427
1428 This command measures the time spent in each of these phases.
1428 This command measures the time spent in each of these phases.
1429 """
1429 """
1430 if opts.get('changelog') or opts.get('manifest'):
1430 if opts.get('changelog') or opts.get('manifest'):
1431 file_, rev = None, file_
1431 file_, rev = None, file_
1432 elif rev is None:
1432 elif rev is None:
1433 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
1433 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
1434
1434
1435 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
1435 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
1436
1436
1437 # _chunkraw was renamed to _getsegmentforrevs.
1437 # _chunkraw was renamed to _getsegmentforrevs.
1438 try:
1438 try:
1439 segmentforrevs = r._getsegmentforrevs
1439 segmentforrevs = r._getsegmentforrevs
1440 except AttributeError:
1440 except AttributeError:
1441 segmentforrevs = r._chunkraw
1441 segmentforrevs = r._chunkraw
1442
1442
1443 node = r.lookup(rev)
1443 node = r.lookup(rev)
1444 rev = r.rev(node)
1444 rev = r.rev(node)
1445
1445
1446 def getrawchunks(data, chain):
1446 def getrawchunks(data, chain):
1447 start = r.start
1447 start = r.start
1448 length = r.length
1448 length = r.length
1449 inline = r._inline
1449 inline = r._inline
1450 iosize = r._io.size
1450 iosize = r._io.size
1451 buffer = util.buffer
1451 buffer = util.buffer
1452 offset = start(chain[0])
1452 offset = start(chain[0])
1453
1453
1454 chunks = []
1454 chunks = []
1455 ladd = chunks.append
1455 ladd = chunks.append
1456
1456
1457 for rev in chain:
1457 for rev in chain:
1458 chunkstart = start(rev)
1458 chunkstart = start(rev)
1459 if inline:
1459 if inline:
1460 chunkstart += (rev + 1) * iosize
1460 chunkstart += (rev + 1) * iosize
1461 chunklength = length(rev)
1461 chunklength = length(rev)
1462 ladd(buffer(data, chunkstart - offset, chunklength))
1462 ladd(buffer(data, chunkstart - offset, chunklength))
1463
1463
1464 return chunks
1464 return chunks
1465
1465
1466 def dodeltachain(rev):
1466 def dodeltachain(rev):
1467 if not cache:
1467 if not cache:
1468 r.clearcaches()
1468 r.clearcaches()
1469 r._deltachain(rev)
1469 r._deltachain(rev)
1470
1470
1471 def doread(chain):
1471 def doread(chain):
1472 if not cache:
1472 if not cache:
1473 r.clearcaches()
1473 r.clearcaches()
1474 segmentforrevs(chain[0], chain[-1])
1474 segmentforrevs(chain[0], chain[-1])
1475
1475
1476 def dorawchunks(data, chain):
1476 def dorawchunks(data, chain):
1477 if not cache:
1477 if not cache:
1478 r.clearcaches()
1478 r.clearcaches()
1479 getrawchunks(data, chain)
1479 getrawchunks(data, chain)
1480
1480
1481 def dodecompress(chunks):
1481 def dodecompress(chunks):
1482 decomp = r.decompress
1482 decomp = r.decompress
1483 for chunk in chunks:
1483 for chunk in chunks:
1484 decomp(chunk)
1484 decomp(chunk)
1485
1485
1486 def dopatch(text, bins):
1486 def dopatch(text, bins):
1487 if not cache:
1487 if not cache:
1488 r.clearcaches()
1488 r.clearcaches()
1489 mdiff.patches(text, bins)
1489 mdiff.patches(text, bins)
1490
1490
1491 def dohash(text):
1491 def dohash(text):
1492 if not cache:
1492 if not cache:
1493 r.clearcaches()
1493 r.clearcaches()
1494 r.checkhash(text, node, rev=rev)
1494 r.checkhash(text, node, rev=rev)
1495
1495
1496 def dorevision():
1496 def dorevision():
1497 if not cache:
1497 if not cache:
1498 r.clearcaches()
1498 r.clearcaches()
1499 r.revision(node)
1499 r.revision(node)
1500
1500
1501 chain = r._deltachain(rev)[0]
1501 chain = r._deltachain(rev)[0]
1502 data = segmentforrevs(chain[0], chain[-1])[1]
1502 data = segmentforrevs(chain[0], chain[-1])[1]
1503 rawchunks = getrawchunks(data, chain)
1503 rawchunks = getrawchunks(data, chain)
1504 bins = r._chunks(chain)
1504 bins = r._chunks(chain)
1505 text = str(bins[0])
1505 text = str(bins[0])
1506 bins = bins[1:]
1506 bins = bins[1:]
1507 text = mdiff.patches(text, bins)
1507 text = mdiff.patches(text, bins)
1508
1508
1509 benches = [
1509 benches = [
1510 (lambda: dorevision(), 'full'),
1510 (lambda: dorevision(), 'full'),
1511 (lambda: dodeltachain(rev), 'deltachain'),
1511 (lambda: dodeltachain(rev), 'deltachain'),
1512 (lambda: doread(chain), 'read'),
1512 (lambda: doread(chain), 'read'),
1513 (lambda: dorawchunks(data, chain), 'rawchunks'),
1513 (lambda: dorawchunks(data, chain), 'rawchunks'),
1514 (lambda: dodecompress(rawchunks), 'decompress'),
1514 (lambda: dodecompress(rawchunks), 'decompress'),
1515 (lambda: dopatch(text, bins), 'patch'),
1515 (lambda: dopatch(text, bins), 'patch'),
1516 (lambda: dohash(text), 'hash'),
1516 (lambda: dohash(text), 'hash'),
1517 ]
1517 ]
1518
1518
1519 for fn, title in benches:
1519 for fn, title in benches:
1520 timer, fm = gettimer(ui, opts)
1520 timer, fm = gettimer(ui, opts)
1521 timer(fn, title=title)
1521 timer(fn, title=title)
1522 fm.end()
1522 fm.end()
1523
1523
1524 @command('perfrevset',
1524 @command('perfrevset',
1525 [('C', 'clear', False, 'clear volatile cache between each call.'),
1525 [('C', 'clear', False, 'clear volatile cache between each call.'),
1526 ('', 'contexts', False, 'obtain changectx for each revision')]
1526 ('', 'contexts', False, 'obtain changectx for each revision')]
1527 + formatteropts, "REVSET")
1527 + formatteropts, "REVSET")
1528 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1528 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1529 """benchmark the execution time of a revset
1529 """benchmark the execution time of a revset
1530
1530
1531 Use the --clean option if need to evaluate the impact of build volatile
1531 Use the --clean option if need to evaluate the impact of build volatile
1532 revisions set cache on the revset execution. Volatile cache hold filtered
1532 revisions set cache on the revset execution. Volatile cache hold filtered
1533 and obsolete related cache."""
1533 and obsolete related cache."""
1534 timer, fm = gettimer(ui, opts)
1534 timer, fm = gettimer(ui, opts)
1535 def d():
1535 def d():
1536 if clear:
1536 if clear:
1537 repo.invalidatevolatilesets()
1537 repo.invalidatevolatilesets()
1538 if contexts:
1538 if contexts:
1539 for ctx in repo.set(expr): pass
1539 for ctx in repo.set(expr): pass
1540 else:
1540 else:
1541 for r in repo.revs(expr): pass
1541 for r in repo.revs(expr): pass
1542 timer(d)
1542 timer(d)
1543 fm.end()
1543 fm.end()
1544
1544
1545 @command('perfvolatilesets',
1545 @command('perfvolatilesets',
1546 [('', 'clear-obsstore', False, 'drop obsstore between each call.'),
1546 [('', 'clear-obsstore', False, 'drop obsstore between each call.'),
1547 ] + formatteropts)
1547 ] + formatteropts)
1548 def perfvolatilesets(ui, repo, *names, **opts):
1548 def perfvolatilesets(ui, repo, *names, **opts):
1549 """benchmark the computation of various volatile set
1549 """benchmark the computation of various volatile set
1550
1550
1551 Volatile set computes element related to filtering and obsolescence."""
1551 Volatile set computes element related to filtering and obsolescence."""
1552 timer, fm = gettimer(ui, opts)
1552 timer, fm = gettimer(ui, opts)
1553 repo = repo.unfiltered()
1553 repo = repo.unfiltered()
1554
1554
1555 def getobs(name):
1555 def getobs(name):
1556 def d():
1556 def d():
1557 repo.invalidatevolatilesets()
1557 repo.invalidatevolatilesets()
1558 if opts['clear_obsstore']:
1558 if opts['clear_obsstore']:
1559 clearfilecache(repo, 'obsstore')
1559 clearfilecache(repo, 'obsstore')
1560 obsolete.getrevs(repo, name)
1560 obsolete.getrevs(repo, name)
1561 return d
1561 return d
1562
1562
1563 allobs = sorted(obsolete.cachefuncs)
1563 allobs = sorted(obsolete.cachefuncs)
1564 if names:
1564 if names:
1565 allobs = [n for n in allobs if n in names]
1565 allobs = [n for n in allobs if n in names]
1566
1566
1567 for name in allobs:
1567 for name in allobs:
1568 timer(getobs(name), title=name)
1568 timer(getobs(name), title=name)
1569
1569
1570 def getfiltered(name):
1570 def getfiltered(name):
1571 def d():
1571 def d():
1572 repo.invalidatevolatilesets()
1572 repo.invalidatevolatilesets()
1573 if opts['clear_obsstore']:
1573 if opts['clear_obsstore']:
1574 clearfilecache(repo, 'obsstore')
1574 clearfilecache(repo, 'obsstore')
1575 repoview.filterrevs(repo, name)
1575 repoview.filterrevs(repo, name)
1576 return d
1576 return d
1577
1577
1578 allfilter = sorted(repoview.filtertable)
1578 allfilter = sorted(repoview.filtertable)
1579 if names:
1579 if names:
1580 allfilter = [n for n in allfilter if n in names]
1580 allfilter = [n for n in allfilter if n in names]
1581
1581
1582 for name in allfilter:
1582 for name in allfilter:
1583 timer(getfiltered(name), title=name)
1583 timer(getfiltered(name), title=name)
1584 fm.end()
1584 fm.end()
1585
1585
1586 @command('perfbranchmap',
1586 @command('perfbranchmap',
1587 [('f', 'full', False,
1587 [('f', 'full', False,
1588 'Includes build time of subset'),
1588 'Includes build time of subset'),
1589 ('', 'clear-revbranch', False,
1589 ('', 'clear-revbranch', False,
1590 'purge the revbranch cache between computation'),
1590 'purge the revbranch cache between computation'),
1591 ] + formatteropts)
1591 ] + formatteropts)
1592 def perfbranchmap(ui, repo, *filternames, **opts):
1592 def perfbranchmap(ui, repo, *filternames, **opts):
1593 """benchmark the update of a branchmap
1593 """benchmark the update of a branchmap
1594
1594
1595 This benchmarks the full repo.branchmap() call with read and write disabled
1595 This benchmarks the full repo.branchmap() call with read and write disabled
1596 """
1596 """
1597 full = opts.get("full", False)
1597 full = opts.get("full", False)
1598 clear_revbranch = opts.get("clear_revbranch", False)
1598 clear_revbranch = opts.get("clear_revbranch", False)
1599 timer, fm = gettimer(ui, opts)
1599 timer, fm = gettimer(ui, opts)
1600 def getbranchmap(filtername):
1600 def getbranchmap(filtername):
1601 """generate a benchmark function for the filtername"""
1601 """generate a benchmark function for the filtername"""
1602 if filtername is None:
1602 if filtername is None:
1603 view = repo
1603 view = repo
1604 else:
1604 else:
1605 view = repo.filtered(filtername)
1605 view = repo.filtered(filtername)
1606 def d():
1606 def d():
1607 if clear_revbranch:
1607 if clear_revbranch:
1608 repo.revbranchcache()._clear()
1608 repo.revbranchcache()._clear()
1609 if full:
1609 if full:
1610 view._branchcaches.clear()
1610 view._branchcaches.clear()
1611 else:
1611 else:
1612 view._branchcaches.pop(filtername, None)
1612 view._branchcaches.pop(filtername, None)
1613 view.branchmap()
1613 view.branchmap()
1614 return d
1614 return d
1615 # add filter in smaller subset to bigger subset
1615 # add filter in smaller subset to bigger subset
1616 possiblefilters = set(repoview.filtertable)
1616 possiblefilters = set(repoview.filtertable)
1617 if filternames:
1617 if filternames:
1618 possiblefilters &= set(filternames)
1618 possiblefilters &= set(filternames)
1619 subsettable = getbranchmapsubsettable()
1619 subsettable = getbranchmapsubsettable()
1620 allfilters = []
1620 allfilters = []
1621 while possiblefilters:
1621 while possiblefilters:
1622 for name in possiblefilters:
1622 for name in possiblefilters:
1623 subset = subsettable.get(name)
1623 subset = subsettable.get(name)
1624 if subset not in possiblefilters:
1624 if subset not in possiblefilters:
1625 break
1625 break
1626 else:
1626 else:
1627 assert False, 'subset cycle %s!' % possiblefilters
1627 assert False, 'subset cycle %s!' % possiblefilters
1628 allfilters.append(name)
1628 allfilters.append(name)
1629 possiblefilters.remove(name)
1629 possiblefilters.remove(name)
1630
1630
1631 # warm the cache
1631 # warm the cache
1632 if not full:
1632 if not full:
1633 for name in allfilters:
1633 for name in allfilters:
1634 repo.filtered(name).branchmap()
1634 repo.filtered(name).branchmap()
1635 if not filternames or 'unfiltered' in filternames:
1635 if not filternames or 'unfiltered' in filternames:
1636 # add unfiltered
1636 # add unfiltered
1637 allfilters.append(None)
1637 allfilters.append(None)
1638
1638
1639 branchcacheread = safeattrsetter(branchmap, 'read')
1639 branchcacheread = safeattrsetter(branchmap, 'read')
1640 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1640 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1641 branchcacheread.set(lambda repo: None)
1641 branchcacheread.set(lambda repo: None)
1642 branchcachewrite.set(lambda bc, repo: None)
1642 branchcachewrite.set(lambda bc, repo: None)
1643 try:
1643 try:
1644 for name in allfilters:
1644 for name in allfilters:
1645 printname = name
1645 printname = name
1646 if name is None:
1646 if name is None:
1647 printname = 'unfiltered'
1647 printname = 'unfiltered'
1648 timer(getbranchmap(name), title=str(printname))
1648 timer(getbranchmap(name), title=str(printname))
1649 finally:
1649 finally:
1650 branchcacheread.restore()
1650 branchcacheread.restore()
1651 branchcachewrite.restore()
1651 branchcachewrite.restore()
1652 fm.end()
1652 fm.end()
1653
1653
1654 @command('perfloadmarkers')
1654 @command('perfloadmarkers')
1655 def perfloadmarkers(ui, repo):
1655 def perfloadmarkers(ui, repo):
1656 """benchmark the time to parse the on-disk markers for a repo
1656 """benchmark the time to parse the on-disk markers for a repo
1657
1657
1658 Result is the number of markers in the repo."""
1658 Result is the number of markers in the repo."""
1659 timer, fm = gettimer(ui)
1659 timer, fm = gettimer(ui)
1660 svfs = getsvfs(repo)
1660 svfs = getsvfs(repo)
1661 timer(lambda: len(obsolete.obsstore(svfs)))
1661 timer(lambda: len(obsolete.obsstore(svfs)))
1662 fm.end()
1662 fm.end()
1663
1663
1664 @command('perflrucachedict', formatteropts +
1664 @command('perflrucachedict', formatteropts +
1665 [('', 'size', 4, 'size of cache'),
1665 [('', 'size', 4, 'size of cache'),
1666 ('', 'gets', 10000, 'number of key lookups'),
1666 ('', 'gets', 10000, 'number of key lookups'),
1667 ('', 'sets', 10000, 'number of key sets'),
1667 ('', 'sets', 10000, 'number of key sets'),
1668 ('', 'mixed', 10000, 'number of mixed mode operations'),
1668 ('', 'mixed', 10000, 'number of mixed mode operations'),
1669 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1669 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1670 norepo=True)
1670 norepo=True)
1671 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1671 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1672 mixedgetfreq=50, **opts):
1672 mixedgetfreq=50, **opts):
1673 def doinit():
1673 def doinit():
1674 for i in xrange(10000):
1674 for i in xrange(10000):
1675 util.lrucachedict(size)
1675 util.lrucachedict(size)
1676
1676
1677 values = []
1677 values = []
1678 for i in xrange(size):
1678 for i in xrange(size):
1679 values.append(random.randint(0, sys.maxint))
1679 values.append(random.randint(0, sys.maxint))
1680
1680
1681 # Get mode fills the cache and tests raw lookup performance with no
1681 # Get mode fills the cache and tests raw lookup performance with no
1682 # eviction.
1682 # eviction.
1683 getseq = []
1683 getseq = []
1684 for i in xrange(gets):
1684 for i in xrange(gets):
1685 getseq.append(random.choice(values))
1685 getseq.append(random.choice(values))
1686
1686
1687 def dogets():
1687 def dogets():
1688 d = util.lrucachedict(size)
1688 d = util.lrucachedict(size)
1689 for v in values:
1689 for v in values:
1690 d[v] = v
1690 d[v] = v
1691 for key in getseq:
1691 for key in getseq:
1692 value = d[key]
1692 value = d[key]
1693 value # silence pyflakes warning
1693 value # silence pyflakes warning
1694
1694
1695 # Set mode tests insertion speed with cache eviction.
1695 # Set mode tests insertion speed with cache eviction.
1696 setseq = []
1696 setseq = []
1697 for i in xrange(sets):
1697 for i in xrange(sets):
1698 setseq.append(random.randint(0, sys.maxint))
1698 setseq.append(random.randint(0, sys.maxint))
1699
1699
1700 def dosets():
1700 def dosets():
1701 d = util.lrucachedict(size)
1701 d = util.lrucachedict(size)
1702 for v in setseq:
1702 for v in setseq:
1703 d[v] = v
1703 d[v] = v
1704
1704
1705 # Mixed mode randomly performs gets and sets with eviction.
1705 # Mixed mode randomly performs gets and sets with eviction.
1706 mixedops = []
1706 mixedops = []
1707 for i in xrange(mixed):
1707 for i in xrange(mixed):
1708 r = random.randint(0, 100)
1708 r = random.randint(0, 100)
1709 if r < mixedgetfreq:
1709 if r < mixedgetfreq:
1710 op = 0
1710 op = 0
1711 else:
1711 else:
1712 op = 1
1712 op = 1
1713
1713
1714 mixedops.append((op, random.randint(0, size * 2)))
1714 mixedops.append((op, random.randint(0, size * 2)))
1715
1715
1716 def domixed():
1716 def domixed():
1717 d = util.lrucachedict(size)
1717 d = util.lrucachedict(size)
1718
1718
1719 for op, v in mixedops:
1719 for op, v in mixedops:
1720 if op == 0:
1720 if op == 0:
1721 try:
1721 try:
1722 d[v]
1722 d[v]
1723 except KeyError:
1723 except KeyError:
1724 pass
1724 pass
1725 else:
1725 else:
1726 d[v] = v
1726 d[v] = v
1727
1727
1728 benches = [
1728 benches = [
1729 (doinit, 'init'),
1729 (doinit, 'init'),
1730 (dogets, 'gets'),
1730 (dogets, 'gets'),
1731 (dosets, 'sets'),
1731 (dosets, 'sets'),
1732 (domixed, 'mixed')
1732 (domixed, 'mixed')
1733 ]
1733 ]
1734
1734
1735 for fn, title in benches:
1735 for fn, title in benches:
1736 timer, fm = gettimer(ui, opts)
1736 timer, fm = gettimer(ui, opts)
1737 timer(fn, title=title)
1737 timer(fn, title=title)
1738 fm.end()
1738 fm.end()
1739
1739
1740 @command('perfwrite', formatteropts)
1740 @command('perfwrite', formatteropts)
1741 def perfwrite(ui, repo, **opts):
1741 def perfwrite(ui, repo, **opts):
1742 """microbenchmark ui.write
1742 """microbenchmark ui.write
1743 """
1743 """
1744 timer, fm = gettimer(ui, opts)
1744 timer, fm = gettimer(ui, opts)
1745 def write():
1745 def write():
1746 for i in range(100000):
1746 for i in range(100000):
1747 ui.write(('Testing write performance\n'))
1747 ui.write(('Testing write performance\n'))
1748 timer(write)
1748 timer(write)
1749 fm.end()
1749 fm.end()
1750
1750
1751 def uisetup(ui):
1751 def uisetup(ui):
1752 if (util.safehasattr(cmdutil, 'openrevlog') and
1752 if (util.safehasattr(cmdutil, 'openrevlog') and
1753 not util.safehasattr(commands, 'debugrevlogopts')):
1753 not util.safehasattr(commands, 'debugrevlogopts')):
1754 # for "historical portability":
1754 # for "historical portability":
1755 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1755 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1756 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1756 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1757 # openrevlog() should cause failure, because it has been
1757 # openrevlog() should cause failure, because it has been
1758 # available since 3.5 (or 49c583ca48c4).
1758 # available since 3.5 (or 49c583ca48c4).
1759 def openrevlog(orig, repo, cmd, file_, opts):
1759 def openrevlog(orig, repo, cmd, file_, opts):
1760 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1760 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1761 raise error.Abort("This version doesn't support --dir option",
1761 raise error.Abort("This version doesn't support --dir option",
1762 hint="use 3.5 or later")
1762 hint="use 3.5 or later")
1763 return orig(repo, cmd, file_, opts)
1763 return orig(repo, cmd, file_, opts)
1764 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
1764 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
General Comments 0
You need to be logged in to leave comments. Login now