/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/benchmarks/__init__.py

  • Committer: John Arbash Meinel
  • Date: 2006-08-07 22:00:11 UTC
  • mto: (1908.4.6 commit-perf)
  • mto: This revision was merged to the branch mainline in revision 1923.
  • Revision ID: john@arbash-meinel.com-20060807220011-b98533a8b1815b31
Update the benchmarks to actually use the cached trees

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006 by Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
 
 
18
"""Benchmark test suite for bzr."""
 
19
 
 
20
import os
 
21
import shutil
 
22
 
 
23
from bzrlib import (
 
24
    add,
 
25
    bzrdir,
 
26
    osutils,
 
27
    plugin,
 
28
    workingtree,
 
29
    )
 
30
from bzrlib.tests.TestUtil import TestLoader
 
31
from bzrlib.tests.blackbox import ExternalBase
 
32
 
 
33
 
 
34
class Benchmark(ExternalBase):
 
35
 
 
36
    CACHE_ROOT = None
 
37
 
 
38
    def get_cache_dir(self, extra):
 
39
        """Get the directory to use for caching the given object."""
 
40
 
 
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)
 
47
 
 
48
    def make_kernel_like_tree(self, url=None, root='.',
 
49
                              hardlink_working=False):
 
50
        """Setup a temporary tree roughly like a kernel tree.
 
51
        
 
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
 
57
        """
 
58
        if url is not None:
 
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()
 
63
        else:
 
64
            tree = bzrdir.BzrDir.create_standalone_workingtree(root)
 
65
 
 
66
        self._link_or_copy_kernel_files(root=root, do_link=hardlink_working)
 
67
        return tree
 
68
 
 
69
    def _make_kernel_files(self, root='.'):
 
70
        # a kernel tree has ~10000 and 500 directory, with most files around 
 
71
        # 3-4 levels deep. 
 
72
        # we simulate this by three levels of dirs named 0-7, givin 512 dirs,
 
73
        # and 20 files each.
 
74
        files = []
 
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)
 
81
                    files.append(prefix)
 
82
                    files.extend([prefix + str(foo) for foo in range(20)])
 
83
        cwd = osutils.getcwd()
 
84
        os.chdir(root)
 
85
        self.build_tree(files)
 
86
        os.chdir(cwd)
 
87
 
 
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')
 
91
        if is_cached:
 
92
            return cache_dir
 
93
        os.mkdir(cache_dir)
 
94
        self._make_kernel_files(root=cache_dir)
 
95
        self._protect_files(cache_dir)
 
96
        return cache_dir
 
97
 
 
98
    def _link_or_copy_kernel_files(self, root, do_link=True):
 
99
        """Hardlink the kernel files from the cached location.
 
100
 
 
101
        If the platform doesn't correctly support hardlinking files, it
 
102
        reverts to just creating new ones.
 
103
        """
 
104
 
 
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
 
109
            # cp -a takes 3s
 
110
            # using hardlinks takes < 1s.
 
111
            self._make_kernel_files(root=root)
 
112
            return
 
113
 
 
114
        cache_dir = self._cache_kernel_like_tree()
 
115
 
 
116
        # Hardlinking the target directory is *much* faster (7s => <1s).
 
117
        osutils.copy_tree(cache_dir, root,
 
118
                          handlers={'file':os.link})
 
119
 
 
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.
 
123
 
 
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?
 
128
        """
 
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():
 
134
            if link_working:
 
135
                if link_bzr:
 
136
                    handlers = {'file':os.link}
 
137
                else:
 
138
                    # Don't hardlink files inside bzr
 
139
                    def file_handler(source, dest):
 
140
                        if '.bzr/' in source:
 
141
                            shutil.copyfile(source, dest)
 
142
                        else:
 
143
                            os.link(source, dest)
 
144
                    handlers = {'file':file_handler}
 
145
            elif link_bzr:
 
146
                # Only link files inside .bzr/
 
147
                def file_handler(source, dest):
 
148
                    if '.bzr/' in source:
 
149
                        os.link(source, dest)
 
150
                    else:
 
151
                        shutil.copyfile(source, dest)
 
152
                handlers = {'file':file_handler}
 
153
        osutils.copy_tree(source, dest, handlers=handlers)
 
154
 
 
155
    def _protect_files(self, root):
 
156
        """Chmod all files underneath 'root' to prevent writing
 
157
 
 
158
        :param root: The base directory to modify
 
159
        """
 
160
        for dirinfo, entries in osutils.walkdirs(root):
 
161
            for relpath, name, kind, st, abspath in entries:
 
162
                if kind == 'file':
 
163
                    os.chmod(abspath, 0440)
 
164
 
 
165
    def _cache_kernel_like_added_tree(self):
 
166
        cache_dir, is_cached = self.get_cache_dir('kernel_like_added_tree')
 
167
        if is_cached:
 
168
            return cache_dir
 
169
 
 
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)
 
175
 
 
176
        self._protect_files(cache_dir+'/.bzr')
 
177
        return cache_dir
 
178
 
 
179
    def make_kernel_like_added_tree(self, root='.',
 
180
                                    hardlink_working=True):
 
181
        """Make a kernel like tree, with all files added
 
182
 
 
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.
 
187
        """
 
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()
 
192
 
 
193
        self._clone_tree(cache_dir, root,
 
194
                         link_working=hardlink_working)
 
195
        return workingtree.WorkingTree.open(root)
 
196
 
 
197
    def _cache_kernel_like_committed_tree(self):
 
198
        cache_dir, is_cached = self.get_cache_dir('kernel_like_committed_tree')
 
199
        if is_cached:
 
200
            return cache_dir
 
201
 
 
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')
 
206
 
 
207
        self._protect_files(cache_dir+'/.bzr')
 
208
        return cache_dir
 
209
 
 
210
    def make_kernel_like_committed_tree(self, root='.',
 
211
                                    hardlink_working=True,
 
212
                                    hardlink_bzr=False):
 
213
        """Make a kernel like tree, with all files added and committed
 
214
 
 
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
 
221
        """
 
222
        cache_dir = self._cache_kernel_like_committed_tree()
 
223
 
 
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)
 
229
 
 
230
    def _cache_many_commit_tree(self):
 
231
        cache_dir, is_cached = self.get_cache_dir('many_commit_tree')
 
232
        if is_cached:
 
233
            return cache_dir
 
234
 
 
235
        tree = bzrdir.BzrDir.create_standalone_workingtree(cache_dir)
 
236
        tree.lock_write()
 
237
        tree.branch.lock_write()
 
238
        tree.branch.repository.lock_write()
 
239
        try:
 
240
            for i in xrange(1000):
 
241
                tree.commit('no-changes commit %d' % i)
 
242
        finally:
 
243
            try:
 
244
                try:
 
245
                    tree.branch.repository.unlock()
 
246
                finally:
 
247
                    tree.branch.unlock()
 
248
            finally:
 
249
                tree.unlock()
 
250
 
 
251
        return cache_dir
 
252
 
 
253
    def make_many_commit_tree(self, directory_name='.',
 
254
                              hardlink=False):
 
255
        """Create a tree with many commits.
 
256
        
 
257
        No file changes are included. Not hardlinking the working tree, 
 
258
        because there are no working tree files.
 
259
        """
 
260
        cache_dir = self._cache_many_commit_tree()
 
261
        self._clone_tree(cache_dir, directory_name,
 
262
                         link_bzr=hardlink)
 
263
        return workingtree.WorkingTree.open(directory_name)
 
264
 
 
265
    def _cache_heavily_merged_tree(self):
 
266
        cache_dir, is_cached = self.get_cache_dir('heavily_merged_tree')
 
267
        if is_cached:
 
268
            return cache_dir
 
269
 
 
270
        os.mkdir(cache_dir)
 
271
        tree = bzrdir.BzrDir.create_standalone_workingtree(
 
272
                cache_dir + '/tree1')
 
273
        tree.lock_write()
 
274
        try:
 
275
            tree2 = tree.bzrdir.sprout(cache_dir + '/tree2').open_workingtree()
 
276
            tree2.lock_write()
 
277
            try:
 
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([])
 
286
            finally:
 
287
                tree2.unlock()
 
288
        finally:
 
289
            tree.unlock()
 
290
        return cache_dir
 
291
 
 
292
    def make_heavily_merged_tree(self, directory_name='.',
 
293
                                 hardlink=False):
 
294
        """Create a tree in which almost every commit is a merge.
 
295
       
 
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 
 
300
        tree files.
 
301
        """
 
302
        cache_dir = self._cache_heavily_merged_tree()
 
303
        tree_dir = cache_dir + '/tree1'
 
304
        self._clone_tree(tree_dir, directory_name,
 
305
                         link_bzr=hardlink)
 
306
        return workingtree.WorkingTree.open(directory_name)
 
307
 
 
308
 
 
309
def test_suite():
 
310
    """Build and return a TestSuite which contains benchmark tests only."""
 
311
    testmod_names = [ \
 
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',
 
323
                   ]
 
324
    suite = TestLoader().loadTestsFromModuleNames(testmod_names) 
 
325
 
 
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())
 
330
 
 
331
    return suite