1
# Copyright (C) 2006 by Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
"""Benchmark test suite for bzr."""
30
from bzrlib.tests.TestUtil import TestLoader
31
from bzrlib.tests.blackbox import ExternalBase
34
class Benchmark(ExternalBase):
38
def get_cache_dir(self, extra):
39
"""Get the directory to use for caching the given object."""
41
if Benchmark.CACHE_ROOT is None:
42
Benchmark.CACHE_ROOT = osutils.pathjoin(self.TEST_ROOT, 'CACHE')
43
if not os.path.isdir(Benchmark.CACHE_ROOT):
44
os.mkdir(Benchmark.CACHE_ROOT)
45
cache_dir = osutils.pathjoin(self.CACHE_ROOT, extra)
46
return cache_dir, os.path.exists(cache_dir)
48
def make_kernel_like_tree(self, url=None, root='.',
49
hardlink_working=False):
50
"""Setup a temporary tree roughly like a kernel tree.
52
:param url: Creat the kernel like tree as a lightweight checkout
53
of a new branch created at url.
54
:param hardlink_working: instead of creating a new copy of all files
55
just hardlink the working tree. Tests must request this, because
56
they must break links if they want to change the files
59
b = bzrdir.BzrDir.create_branch_convenience(url)
60
d = bzrdir.BzrDir.create(root)
61
bzrlib.branch.BranchReferenceFormat().initialize(d, b)
62
tree = d.create_workingtree()
64
tree = bzrdir.BzrDir.create_standalone_workingtree(root)
66
self._link_or_copy_kernel_files(root=root, do_link=hardlink_working)
69
def _make_kernel_files(self, root='.'):
70
# a kernel tree has ~10000 and 500 directory, with most files around
72
# we simulate this by three levels of dirs named 0-7, givin 512 dirs,
75
for outer in range(8):
76
files.append("%s/" % outer)
77
for middle in range(8):
78
files.append("%s/%s/" % (outer, middle))
79
for inner in range(8):
80
prefix = "%s/%s/%s/" % (outer, middle, inner)
82
files.extend([prefix + str(foo) for foo in range(20)])
83
cwd = osutils.getcwd()
85
self.build_tree(files)
88
def _cache_kernel_like_tree(self):
89
"""Create the kernel_like_tree cache dir if it doesn't exist"""
90
cache_dir, is_cached = self.get_cache_dir('kernel_like_tree')
94
self._make_kernel_files(root=cache_dir)
95
self._protect_files(cache_dir)
98
def _link_or_copy_kernel_files(self, root, do_link=True):
99
"""Hardlink the kernel files from the cached location.
101
If the platform doesn't correctly support hardlinking files, it
102
reverts to just creating new ones.
105
if not osutils.hardlinks_good() or not do_link:
106
# Turns out that 'shutil.copytree()' is no faster than
107
# just creating them. Probably the python overhead.
108
# Plain _make_kernel_files takes 5s
110
# using hardlinks takes < 1s.
111
self._make_kernel_files(root=root)
114
cache_dir = self._cache_kernel_like_tree()
116
# Hardlinking the target directory is *much* faster (7s => <1s).
117
osutils.copy_tree(cache_dir, root,
118
handlers={'file':os.link})
120
def _clone_tree(self, source, dest, link_bzr=False, link_working=True):
121
"""Copy the contents from a given location to another location.
122
Optionally hardlink certain pieces of the tree.
124
:param source: The directory to copy
125
:param dest: The destination
126
:param link_bzr: Should the .bzr/ files be hardlinked?
127
:param link_working: Should the working tree be hardlinked?
129
# We use shutil.copyfile so that we don't copy permissions
130
# because most of our source trees are marked readonly to
131
# prevent modifying in the case of hardlinks
132
handlers = {'file':shutil.copyfile}
133
if osutils.hardlinks_good():
136
handlers = {'file':os.link}
138
# Don't hardlink files inside bzr
139
def file_handler(source, dest):
140
if '.bzr/' in source:
141
shutil.copyfile(source, dest)
143
os.link(source, dest)
144
handlers = {'file':file_handler}
146
# Only link files inside .bzr/
147
def file_handler(source, dest):
148
if '.bzr/' in source:
149
os.link(source, dest)
151
shutil.copyfile(source, dest)
152
handlers = {'file':file_handler}
153
osutils.copy_tree(source, dest, handlers=handlers)
155
def _protect_files(self, root):
156
"""Chmod all files underneath 'root' to prevent writing
158
:param root: The base directory to modify
160
for dirinfo, entries in osutils.walkdirs(root):
161
for relpath, name, kind, st, abspath in entries:
163
os.chmod(abspath, 0440)
165
def _cache_kernel_like_added_tree(self):
166
cache_dir, is_cached = self.get_cache_dir('kernel_like_added_tree')
170
# Get a basic tree with working files
171
tree = self.make_kernel_like_tree(root=cache_dir,
172
hardlink_working=True)
173
# Add everything to it
174
add.smart_add_tree(tree, [cache_dir], recurse=True, save=True)
176
self._protect_files(cache_dir+'/.bzr')
179
def make_kernel_like_added_tree(self, root='.',
180
hardlink_working=True):
181
"""Make a kernel like tree, with all files added
183
:param root: Where to create the files
184
:param hardlink_working: Instead of copying all of the working tree
185
files, just hardlink them to the cached files. Tests can unlink
186
files that they will change.
188
# There isn't much underneath .bzr, so we don't support hardlinking
189
# it. Testing showed there wasn't much gain, and there is potentially
190
# a problem if someone modifies something underneath us.
191
cache_dir = self._cache_kernel_like_added_tree()
193
self._clone_tree(cache_dir, root,
194
link_working=hardlink_working)
195
return workingtree.WorkingTree.open(root)
197
def _cache_kernel_like_committed_tree(self):
198
cache_dir, is_cached = self.get_cache_dir('kernel_like_committed_tree')
202
# Get a basic tree with working files
203
tree = self.make_kernel_like_added_tree(root=cache_dir,
204
hardlink_working=True)
205
tree.commit('first post', rev_id='r1')
207
self._protect_files(cache_dir+'/.bzr')
210
def make_kernel_like_committed_tree(self, root='.',
211
hardlink_working=True,
213
"""Make a kernel like tree, with all files added and committed
215
:param root: Where to create the files
216
:param hardlink_working: Instead of copying all of the working tree
217
files, just hardlink them to the cached files. Tests can unlink
218
files that they will change.
219
:param hardlink_bzr: Hardlink the .bzr directory. For readonly
220
operations this is safe, and shaves off a lot of setup time
222
cache_dir = self._cache_kernel_like_committed_tree()
224
# Now we have a cached tree, just copy it
225
self._clone_tree(cache_dir, root,
226
link_bzr=hardlink_bzr,
227
link_working=hardlink_working)
228
return workingtree.WorkingTree.open(root)
230
def _cache_many_commit_tree(self):
231
cache_dir, is_cached = self.get_cache_dir('many_commit_tree')
235
tree = bzrdir.BzrDir.create_standalone_workingtree(cache_dir)
237
tree.branch.lock_write()
238
tree.branch.repository.lock_write()
240
for i in xrange(1000):
241
tree.commit('no-changes commit %d' % i)
245
tree.branch.repository.unlock()
253
def make_many_commit_tree(self, directory_name='.',
255
"""Create a tree with many commits.
257
No file changes are included. Not hardlinking the working tree,
258
because there are no working tree files.
260
cache_dir = self._cache_many_commit_tree()
261
self._clone_tree(cache_dir, directory_name,
263
return workingtree.WorkingTree.open(directory_name)
265
def _cache_heavily_merged_tree(self):
266
cache_dir, is_cached = self.get_cache_dir('heavily_merged_tree')
271
tree = bzrdir.BzrDir.create_standalone_workingtree(
272
cache_dir + '/tree1')
275
tree2 = tree.bzrdir.sprout(cache_dir + '/tree2').open_workingtree()
278
for i in xrange(250):
279
revision_id = tree.commit('no-changes commit %d-a' % i)
280
tree2.branch.fetch(tree.branch, revision_id)
281
tree2.set_pending_merges([revision_id])
282
revision_id = tree2.commit('no-changes commit %d-b' % i)
283
tree.branch.fetch(tree2.branch, revision_id)
284
tree.set_pending_merges([revision_id])
285
tree.set_pending_merges([])
292
def make_heavily_merged_tree(self, directory_name='.',
294
"""Create a tree in which almost every commit is a merge.
296
No file changes are included. This produces two trees,
297
one of which is returned. Except for the first commit, every
298
commit in its revision-history is a merge another commit in the other
299
tree. Not hardlinking the working tree, because there are no working
302
cache_dir = self._cache_heavily_merged_tree()
303
tree_dir = cache_dir + '/tree1'
304
self._clone_tree(tree_dir, directory_name,
306
return workingtree.WorkingTree.open(directory_name)
310
"""Build and return a TestSuite which contains benchmark tests only."""
312
'bzrlib.benchmarks.bench_add',
313
'bzrlib.benchmarks.bench_bench',
314
'bzrlib.benchmarks.bench_checkout',
315
'bzrlib.benchmarks.bench_commit',
316
'bzrlib.benchmarks.bench_inventory',
317
'bzrlib.benchmarks.bench_log',
318
'bzrlib.benchmarks.bench_osutils',
319
'bzrlib.benchmarks.bench_rocks',
320
'bzrlib.benchmarks.bench_status',
321
'bzrlib.benchmarks.bench_transform',
322
'bzrlib.benchmarks.bench_workingtree',
324
suite = TestLoader().loadTestsFromModuleNames(testmod_names)
326
# Load any benchmarks from plugins
327
for name, module in plugin.all_plugins().items():
328
if getattr(module, 'bench_suite', None) is not None:
329
suite.addTest(module.bench_suite())