/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 21:12:32 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-20060807211232-96637b7104feace3
Add the ability to specify a benchmark cache directory.

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 _link_or_copy_kernel_files(self, root, do_link=True):
 
89
        """Hardlink the kernel files from the cached location.
 
90
 
 
91
        If the platform doesn't correctly support hardlinking files, it
 
92
        reverts to just creating new ones.
 
93
        """
 
94
 
 
95
        if not osutils.hardlinks_good() or not do_link:
 
96
            # Turns out that 'shutil.copytree()' is no faster than
 
97
            # just creating them. Probably the python overhead.
 
98
            # Plain _make_kernel_files takes 5s
 
99
            # cp -a takes 3s
 
100
            # using hardlinks takes < 1s.
 
101
            self._make_kernel_files(root=root)
 
102
            return
 
103
 
 
104
        cache_dir, is_cached = self.get_cache_dir('kernel_like_tree')
 
105
        if not is_cached:
 
106
            os.mkdir(cache_dir)
 
107
            self._make_kernel_files(root=cache_dir)
 
108
            self._protect_files(cache_dir)
 
109
 
 
110
        # Hardlinking the target directory is *much* faster (7s => <1s).
 
111
        osutils.copy_tree(cache_dir, root,
 
112
                          handlers={'file':os.link})
 
113
 
 
114
    def _clone_tree(self, source, dest, link_bzr=False, link_working=True):
 
115
        """Copy the contents from a given location to another location.
 
116
        Optionally hardlink certain pieces of the tree.
 
117
 
 
118
        :param source: The directory to copy
 
119
        :param dest: The destination
 
120
        :param link_bzr: Should the .bzr/ files be hardlinked?
 
121
        :param link_working: Should the working tree be hardlinked?
 
122
        """
 
123
        # We use shutil.copyfile so that we don't copy permissions
 
124
        # because most of our source trees are marked readonly to
 
125
        # prevent modifying in the case of hardlinks
 
126
        handlers = {'file':shutil.copyfile}
 
127
        if osutils.hardlinks_good():
 
128
            if link_working:
 
129
                if link_bzr:
 
130
                    handlers = {'file':os.link}
 
131
                else:
 
132
                    # Don't hardlink files inside bzr
 
133
                    def file_handler(source, dest):
 
134
                        if '.bzr/' in source:
 
135
                            shutil.copyfile(source, dest)
 
136
                        else:
 
137
                            os.link(source, dest)
 
138
                    handlers = {'file':file_handler}
 
139
            elif link_bzr:
 
140
                # Only link files inside .bzr/
 
141
                def file_handler(source, dest):
 
142
                    if '.bzr/' in source:
 
143
                        os.link(source, dest)
 
144
                    else:
 
145
                        shutil.copyfile(source, dest)
 
146
                handlers = {'file':file_handler}
 
147
        osutils.copy_tree(source, dest, handlers=handlers)
 
148
 
 
149
    def _protect_files(self, root):
 
150
        """Chmod all files underneath 'root' to prevent writing
 
151
 
 
152
        :param root: The base directory to modify
 
153
        """
 
154
        for dirinfo, entries in osutils.walkdirs(root):
 
155
            for relpath, name, kind, st, abspath in entries:
 
156
                if kind == 'file':
 
157
                    os.chmod(abspath, 0440)
 
158
 
 
159
    def make_kernel_like_added_tree(self, root='.',
 
160
                                    hardlink_working=True):
 
161
        """Make a kernel like tree, with all files added
 
162
 
 
163
        :param root: Where to create the files
 
164
        :param hardlink_working: Instead of copying all of the working tree
 
165
            files, just hardlink them to the cached files. Tests can unlink
 
166
            files that they will change.
 
167
        """
 
168
        # There isn't much underneath .bzr, so we don't support hardlinking
 
169
        # it. Testing showed there wasn't much gain, and there is potentially
 
170
        # a problem if someone modifies something underneath us.
 
171
        cache_dir, is_cached = self.get_cache_dir('kernel_like_added_tree')
 
172
        if not is_cached:
 
173
            # Get a basic tree with working files
 
174
            tree = self.make_kernel_like_tree(root=cache_dir,
 
175
                                              hardlink_working=True)
 
176
            # Add everything to it
 
177
            add.smart_add_tree(tree, [cache_dir], recurse=True, save=True)
 
178
 
 
179
            self._protect_files(cache_dir+'/.bzr')
 
180
 
 
181
        self._clone_tree(cache_dir, root,
 
182
                         link_working=hardlink_working)
 
183
        return workingtree.WorkingTree.open(root)
 
184
 
 
185
    def make_kernel_like_committed_tree(self, root='.',
 
186
                                    hardlink_working=True,
 
187
                                    hardlink_bzr=False):
 
188
        """Make a kernel like tree, with all files added and committed
 
189
 
 
190
        :param root: Where to create the files
 
191
        :param hardlink_working: Instead of copying all of the working tree
 
192
            files, just hardlink them to the cached files. Tests can unlink
 
193
            files that they will change.
 
194
        :param hardlink_bzr: Hardlink the .bzr directory. For readonly 
 
195
            operations this is safe, and shaves off a lot of setup time
 
196
        """
 
197
        cache_dir, is_cached = self.get_cache_dir('kernel_like_committed_tree')
 
198
        if not is_cached:
 
199
            # Get a basic tree with working files
 
200
            tree = self.make_kernel_like_added_tree(root=cache_dir,
 
201
                                                    hardlink_working=True)
 
202
            tree.commit('first post', rev_id='r1')
 
203
 
 
204
            self._protect_files(cache_dir+'/.bzr')
 
205
 
 
206
        # Now we have a cached tree, just copy it
 
207
        self._clone_tree(cache_dir, root,
 
208
                         link_bzr=hardlink_bzr,
 
209
                         link_working=hardlink_working)
 
210
        return workingtree.WorkingTree.open(root)
 
211
 
 
212
    def make_many_commit_tree(self, directory_name='.'):
 
213
        """Create a tree with many commits.
 
214
        
 
215
        No files change are included.
 
216
        """
 
217
        tree = bzrdir.BzrDir.create_standalone_workingtree(directory_name)
 
218
        tree.lock_write()
 
219
        tree.branch.lock_write()
 
220
        tree.branch.repository.lock_write()
 
221
        try:
 
222
            for i in xrange(1000):
 
223
                tree.commit('no-changes commit %d' % i)
 
224
        finally:
 
225
            try:
 
226
                try:
 
227
                    tree.branch.repository.unlock()
 
228
                finally:
 
229
                    tree.branch.unlock()
 
230
            finally:
 
231
                tree.unlock()
 
232
        return tree
 
233
 
 
234
    def make_heavily_merged_tree(self, directory_name='.'):
 
235
        """Create a tree in which almost every commit is a merge.
 
236
       
 
237
        No files change are included.  This produces two trees, 
 
238
        one of which is returned.  Except for the first commit, every
 
239
        commit in its revision-history is a merge another commit in the other
 
240
        tree.
 
241
        """
 
242
        tree = bzrdir.BzrDir.create_standalone_workingtree(directory_name)
 
243
        tree.lock_write()
 
244
        try:
 
245
            tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
246
            tree2.lock_write()
 
247
            try:
 
248
                for i in xrange(250):
 
249
                    revision_id = tree.commit('no-changes commit %d-a' % i)
 
250
                    tree2.branch.fetch(tree.branch, revision_id)
 
251
                    tree2.set_pending_merges([revision_id])
 
252
                    revision_id = tree2.commit('no-changes commit %d-b' % i)
 
253
                    tree.branch.fetch(tree2.branch, revision_id)
 
254
                    tree.set_pending_merges([revision_id])
 
255
                tree.set_pending_merges([])
 
256
            finally:
 
257
                tree.unlock()
 
258
        finally:
 
259
            tree2.unlock()
 
260
        return tree
 
261
 
 
262
 
 
263
def test_suite():
 
264
    """Build and return a TestSuite which contains benchmark tests only."""
 
265
    testmod_names = [ \
 
266
                   'bzrlib.benchmarks.bench_add',
 
267
                   'bzrlib.benchmarks.bench_bench',
 
268
                   'bzrlib.benchmarks.bench_checkout',
 
269
                   'bzrlib.benchmarks.bench_commit',
 
270
                   'bzrlib.benchmarks.bench_inventory',
 
271
                   'bzrlib.benchmarks.bench_log',
 
272
                   'bzrlib.benchmarks.bench_osutils',
 
273
                   'bzrlib.benchmarks.bench_rocks',
 
274
                   'bzrlib.benchmarks.bench_status',
 
275
                   'bzrlib.benchmarks.bench_transform',
 
276
                   'bzrlib.benchmarks.bench_workingtree',
 
277
                   ]
 
278
    suite = TestLoader().loadTestsFromModuleNames(testmod_names) 
 
279
 
 
280
    # Load any benchmarks from plugins
 
281
    for name, module in plugin.all_plugins().items():
 
282
        if getattr(module, 'bench_suite', None) is not None:
 
283
            suite.addTest(module.bench_suite())
 
284
 
 
285
    return suite