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