~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/groupcompress.py

  • Committer: Richard Wilbur
  • Date: 2016-02-04 19:07:28 UTC
  • mto: This revision was merged to the branch mainline in revision 6618.
  • Revision ID: richard.wilbur@gmail.com-20160204190728-p0zvfii6zase0fw7
Update COPYING.txt from the original http://www.gnu.org/licenses/gpl-2.0.txt  (Only differences were in whitespace.)  Thanks to Petr Stodulka for pointing out the discrepancy.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Core compression logic for compressing streams of related files."""
18
18
 
 
19
from __future__ import absolute_import
 
20
 
19
21
import time
20
22
import zlib
21
 
try:
22
 
    import pylzma
23
 
except ImportError:
24
 
    pylzma = None
25
23
 
26
24
from bzrlib.lazy_import import lazy_import
27
25
lazy_import(globals(), """
28
26
from bzrlib import (
29
27
    annotate,
 
28
    config,
30
29
    debug,
31
30
    errors,
32
31
    graph as _mod_graph,
38
37
    )
39
38
 
40
39
from bzrlib.repofmt import pack_repo
 
40
from bzrlib.i18n import gettext
41
41
""")
42
42
 
43
43
from bzrlib.btree_index import BTreeBuilder
48
48
    AbsentContentFactory,
49
49
    ChunkedContentFactory,
50
50
    FulltextContentFactory,
51
 
    VersionedFiles,
 
51
    VersionedFilesWithFallbacks,
52
52
    )
53
53
 
54
54
# Minimum number of uncompressed bytes to try fetch at once when retrieving
55
55
# groupcompress blocks.
56
56
BATCH_SIZE = 2**16
57
57
 
58
 
_USE_LZMA = False and (pylzma is not None)
59
 
 
60
58
# osutils.sha_string('')
61
59
_null_sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
62
60
 
151
149
                self._content = ''
152
150
            elif self._compressor_name == 'lzma':
153
151
                # We don't do partial lzma decomp yet
 
152
                import pylzma
154
153
                self._content = pylzma.decompress(z_content)
155
154
            elif self._compressor_name == 'zlib':
156
155
                # Start a zlib decompressor
297
296
        self._content = content
298
297
        self._z_content_chunks = None
299
298
 
300
 
    def _create_z_content_using_lzma(self):
301
 
        if self._content_chunks is not None:
302
 
            self._content = ''.join(self._content_chunks)
303
 
            self._content_chunks = None
304
 
        if self._content is None:
305
 
            raise AssertionError('Nothing to compress')
306
 
        z_content = pylzma.compress(self._content)
307
 
        self._z_content_chunks = (z_content,)
308
 
        self._z_content_length = len(z_content)
309
 
 
310
299
    def _create_z_content_from_chunks(self, chunks):
311
300
        compressor = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION)
312
301
        # Peak in this point is 1 fulltext, 1 compressed text, + zlib overhead
320
309
    def _create_z_content(self):
321
310
        if self._z_content_chunks is not None:
322
311
            return
323
 
        if _USE_LZMA:
324
 
            self._create_z_content_using_lzma()
325
 
            return
326
312
        if self._content_chunks is not None:
327
313
            chunks = self._content_chunks
328
314
        else:
332
318
    def to_chunks(self):
333
319
        """Create the byte stream as a series of 'chunks'"""
334
320
        self._create_z_content()
335
 
        if _USE_LZMA:
336
 
            header = self.GCB_LZ_HEADER
337
 
        else:
338
 
            header = self.GCB_HEADER
 
321
        header = self.GCB_HEADER
339
322
        chunks = ['%s%d\n%d\n'
340
323
                  % (header, self._z_content_length, self._content_length),
341
324
                 ]
465
448
                # Grab and cache the raw bytes for this entry
466
449
                # and break the ref-cycle with _manager since we don't need it
467
450
                # anymore
468
 
                self._manager._prepare_for_extract()
 
451
                try:
 
452
                    self._manager._prepare_for_extract()
 
453
                except zlib.error as value:
 
454
                    raise errors.DecompressCorruption("zlib: " + str(value))
469
455
                block = self._manager._block
470
456
                self._bytes = block.extract(self.key, self._start, self._end)
471
457
                # There are code paths that first extract as fulltext, and then
490
476
    _full_enough_block_size = 3*1024*1024 # size at which we won't repack
491
477
    _full_enough_mixed_block_size = 2*768*1024 # 1.5MB
492
478
 
493
 
    def __init__(self, block):
 
479
    def __init__(self, block, get_compressor_settings=None):
494
480
        self._block = block
495
481
        # We need to preserve the ordering
496
482
        self._factories = []
497
483
        self._last_byte = 0
 
484
        self._get_settings = get_compressor_settings
 
485
        self._compressor_settings = None
 
486
 
 
487
    def _get_compressor_settings(self):
 
488
        if self._compressor_settings is not None:
 
489
            return self._compressor_settings
 
490
        settings = None
 
491
        if self._get_settings is not None:
 
492
            settings = self._get_settings()
 
493
        if settings is None:
 
494
            vf = GroupCompressVersionedFiles
 
495
            settings = vf._DEFAULT_COMPRESSOR_SETTINGS
 
496
        self._compressor_settings = settings
 
497
        return self._compressor_settings
498
498
 
499
499
    def add_factory(self, key, parents, start, end):
500
500
        if not self._factories:
533
533
        new_block.set_content(self._block._content[:last_byte])
534
534
        self._block = new_block
535
535
 
 
536
    def _make_group_compressor(self):
 
537
        return GroupCompressor(self._get_compressor_settings())
 
538
 
536
539
    def _rebuild_block(self):
537
540
        """Create a new GroupCompressBlock with only the referenced texts."""
538
 
        compressor = GroupCompressor()
 
541
        compressor = self._make_group_compressor()
539
542
        tstart = time.time()
540
543
        old_length = self._block._content_length
541
544
        end_point = 0
553
556
        #       block? It seems hard to come up with a method that it would
554
557
        #       expand, since we do full compression again. Perhaps based on a
555
558
        #       request that ends up poorly ordered?
 
559
        # TODO: If the content would have expanded, then we would want to
 
560
        #       handle a case where we need to split the block.
 
561
        #       Now that we have a user-tweakable option
 
562
        #       (max_bytes_to_index), it is possible that one person set it
 
563
        #       to a very low value, causing poor compression.
556
564
        delta = time.time() - tstart
557
565
        self._block = new_block
558
566
        trace.mutter('creating new compressed block on-the-fly in %.3fs'
781
789
 
782
790
class _CommonGroupCompressor(object):
783
791
 
784
 
    def __init__(self):
 
792
    def __init__(self, settings=None):
785
793
        """Create a GroupCompressor."""
786
794
        self.chunks = []
787
795
        self._last = None
790
798
        self.labels_deltas = {}
791
799
        self._delta_index = None # Set by the children
792
800
        self._block = GroupCompressBlock()
 
801
        if settings is None:
 
802
            self._settings = {}
 
803
        else:
 
804
            self._settings = settings
793
805
 
794
806
    def compress(self, key, bytes, expected_sha, nostore_sha=None, soft=False):
795
807
        """Compress lines with label key.
910
922
 
911
923
class PythonGroupCompressor(_CommonGroupCompressor):
912
924
 
913
 
    def __init__(self):
 
925
    def __init__(self, settings=None):
914
926
        """Create a GroupCompressor.
915
927
 
916
928
        Used only if the pyrex version is not available.
917
929
        """
918
 
        super(PythonGroupCompressor, self).__init__()
 
930
        super(PythonGroupCompressor, self).__init__(settings)
919
931
        self._delta_index = LinesDeltaIndex([])
920
932
        # The actual content is managed by LinesDeltaIndex
921
933
        self.chunks = self._delta_index.lines
958
970
 
959
971
    It contains code very similar to SequenceMatcher because of having a similar
960
972
    task. However some key differences apply:
961
 
     - there is no junk, we want a minimal edit not a human readable diff.
962
 
     - we don't filter very common lines (because we don't know where a good
963
 
       range will start, and after the first text we want to be emitting minmal
964
 
       edits only.
965
 
     - we chain the left side, not the right side
966
 
     - we incrementally update the adjacency matrix as new lines are provided.
967
 
     - we look for matches in all of the left side, so the routine which does
968
 
       the analagous task of find_longest_match does not need to filter on the
969
 
       left side.
 
973
 
 
974
    * there is no junk, we want a minimal edit not a human readable diff.
 
975
    * we don't filter very common lines (because we don't know where a good
 
976
      range will start, and after the first text we want to be emitting minmal
 
977
      edits only.
 
978
    * we chain the left side, not the right side
 
979
    * we incrementally update the adjacency matrix as new lines are provided.
 
980
    * we look for matches in all of the left side, so the routine which does
 
981
      the analagous task of find_longest_match does not need to filter on the
 
982
      left side.
970
983
    """
971
984
 
972
 
    def __init__(self):
973
 
        super(PyrexGroupCompressor, self).__init__()
974
 
        self._delta_index = DeltaIndex()
 
985
    def __init__(self, settings=None):
 
986
        super(PyrexGroupCompressor, self).__init__(settings)
 
987
        max_bytes_to_index = self._settings.get('max_bytes_to_index', 0)
 
988
        self._delta_index = DeltaIndex(max_bytes_to_index=max_bytes_to_index)
975
989
 
976
990
    def _compress(self, key, bytes, max_delta_size, soft=False):
977
991
        """see _CommonGroupCompressor._compress"""
1068
1082
 
1069
1083
class _BatchingBlockFetcher(object):
1070
1084
    """Fetch group compress blocks in batches.
1071
 
    
 
1085
 
1072
1086
    :ivar total_bytes: int of expected number of bytes needed to fetch the
1073
1087
        currently pending batch.
1074
1088
    """
1075
1089
 
1076
 
    def __init__(self, gcvf, locations):
 
1090
    def __init__(self, gcvf, locations, get_compressor_settings=None):
1077
1091
        self.gcvf = gcvf
1078
1092
        self.locations = locations
1079
1093
        self.keys = []
1082
1096
        self.total_bytes = 0
1083
1097
        self.last_read_memo = None
1084
1098
        self.manager = None
 
1099
        self._get_compressor_settings = get_compressor_settings
1085
1100
 
1086
1101
    def add_key(self, key):
1087
1102
        """Add another to key to fetch.
1088
 
        
 
1103
 
1089
1104
        :return: The estimated number of bytes needed to fetch the batch so
1090
1105
            far.
1091
1106
        """
1116
1131
            # and then.
1117
1132
            self.batch_memos[read_memo] = cached_block
1118
1133
        return self.total_bytes
1119
 
        
 
1134
 
1120
1135
    def _flush_manager(self):
1121
1136
        if self.manager is not None:
1122
1137
            for factory in self.manager.get_record_stream():
1127
1142
    def yield_factories(self, full_flush=False):
1128
1143
        """Yield factories for keys added since the last yield.  They will be
1129
1144
        returned in the order they were added via add_key.
1130
 
        
 
1145
 
1131
1146
        :param full_flush: by default, some results may not be returned in case
1132
1147
            they can be part of the next batch.  If full_flush is True, then
1133
1148
            all results are returned.
1161
1176
                    memos_to_get_stack.pop()
1162
1177
                else:
1163
1178
                    block = self.batch_memos[read_memo]
1164
 
                self.manager = _LazyGroupContentManager(block)
 
1179
                self.manager = _LazyGroupContentManager(block,
 
1180
                    get_compressor_settings=self._get_compressor_settings)
1165
1181
                self.last_read_memo = read_memo
1166
1182
            start, end = index_memo[3:5]
1167
1183
            self.manager.add_factory(key, parents, start, end)
1174
1190
        self.total_bytes = 0
1175
1191
 
1176
1192
 
1177
 
class GroupCompressVersionedFiles(VersionedFiles):
 
1193
class GroupCompressVersionedFiles(VersionedFilesWithFallbacks):
1178
1194
    """A group-compress based VersionedFiles implementation."""
1179
1195
 
1180
 
    def __init__(self, index, access, delta=True, _unadded_refs=None):
 
1196
    # This controls how the GroupCompress DeltaIndex works. Basically, we
 
1197
    # compute hash pointers into the source blocks (so hash(text) => text).
 
1198
    # However each of these references costs some memory in trade against a
 
1199
    # more accurate match result. For very large files, they either are
 
1200
    # pre-compressed and change in bulk whenever they change, or change in just
 
1201
    # local blocks. Either way, 'improved resolution' is not very helpful,
 
1202
    # versus running out of memory trying to track everything. The default max
 
1203
    # gives 100% sampling of a 1MB file.
 
1204
    _DEFAULT_MAX_BYTES_TO_INDEX = 1024 * 1024
 
1205
    _DEFAULT_COMPRESSOR_SETTINGS = {'max_bytes_to_index':
 
1206
                                     _DEFAULT_MAX_BYTES_TO_INDEX}
 
1207
 
 
1208
    def __init__(self, index, access, delta=True, _unadded_refs=None,
 
1209
                 _group_cache=None):
1181
1210
        """Create a GroupCompressVersionedFiles object.
1182
1211
 
1183
1212
        :param index: The index object storing access and graph data.
1184
1213
        :param access: The access object storing raw data.
1185
1214
        :param delta: Whether to delta compress or just entropy compress.
1186
1215
        :param _unadded_refs: private parameter, don't use.
 
1216
        :param _group_cache: private parameter, don't use.
1187
1217
        """
1188
1218
        self._index = index
1189
1219
        self._access = access
1191
1221
        if _unadded_refs is None:
1192
1222
            _unadded_refs = {}
1193
1223
        self._unadded_refs = _unadded_refs
1194
 
        self._group_cache = LRUSizeCache(max_size=50*1024*1024)
 
1224
        if _group_cache is None:
 
1225
            _group_cache = LRUSizeCache(max_size=50*1024*1024)
 
1226
        self._group_cache = _group_cache
1195
1227
        self._immediate_fallback_vfs = []
 
1228
        self._max_bytes_to_index = None
1196
1229
 
1197
1230
    def without_fallbacks(self):
1198
1231
        """Return a clone of this object without any fallbacks configured."""
1199
1232
        return GroupCompressVersionedFiles(self._index, self._access,
1200
 
            self._delta, _unadded_refs=dict(self._unadded_refs))
 
1233
            self._delta, _unadded_refs=dict(self._unadded_refs),
 
1234
            _group_cache=self._group_cache)
1201
1235
 
1202
1236
    def add_lines(self, key, parents, lines, parent_texts=None,
1203
1237
        left_matching_blocks=None, nostore_sha=None, random_id=False,
1207
1241
        :param key: The key tuple of the text to add.
1208
1242
        :param parents: The parents key tuples of the text to add.
1209
1243
        :param lines: A list of lines. Each line must be a bytestring. And all
1210
 
            of them except the last must be terminated with \n and contain no
1211
 
            other \n's. The last line may either contain no \n's or a single
1212
 
            terminating \n. If the lines list does meet this constraint the add
1213
 
            routine may error or may succeed - but you will be unable to read
1214
 
            the data back accurately. (Checking the lines have been split
 
1244
            of them except the last must be terminated with \\n and contain no
 
1245
            other \\n's. The last line may either contain no \\n's or a single
 
1246
            terminating \\n. If the lines list does meet this constraint the
 
1247
            add routine may error or may succeed - but you will be unable to
 
1248
            read the data back accurately. (Checking the lines have been split
1215
1249
            correctly is expensive and extremely unlikely to catch bugs so it
1216
1250
            is not done at runtime unless check_content is True.)
1217
1251
        :param parent_texts: An optional dictionary containing the opaque
1312
1346
            self._check_lines_not_unicode(lines)
1313
1347
            self._check_lines_are_lines(lines)
1314
1348
 
1315
 
    def get_known_graph_ancestry(self, keys):
1316
 
        """Get a KnownGraph instance with the ancestry of keys."""
1317
 
        # Note that this is identical to
1318
 
        # KnitVersionedFiles.get_known_graph_ancestry, but they don't share
1319
 
        # ancestry.
1320
 
        parent_map, missing_keys = self._index.find_ancestry(keys)
1321
 
        for fallback in self._transitive_fallbacks():
1322
 
            if not missing_keys:
1323
 
                break
1324
 
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
1325
 
                                                missing_keys)
1326
 
            parent_map.update(f_parent_map)
1327
 
            missing_keys = f_missing_keys
1328
 
        kg = _mod_graph.KnownGraph(parent_map)
1329
 
        return kg
1330
 
 
1331
1349
    def get_parent_map(self, keys):
1332
1350
        """Get a map of the graph parents of keys.
1333
1351
 
1471
1489
 
1472
1490
        The returned objects should be in the order defined by 'ordering',
1473
1491
        which can weave between different sources.
 
1492
 
1474
1493
        :param ordering: Must be one of 'topological' or 'groupcompress'
1475
1494
        :return: List of [(source, [keys])] tuples, such that all keys are in
1476
1495
            the defined order, regardless of source.
1571
1590
        #  - we encounter an unadded ref, or
1572
1591
        #  - we run out of keys, or
1573
1592
        #  - the total bytes to retrieve for this batch > BATCH_SIZE
1574
 
        batcher = _BatchingBlockFetcher(self, locations)
 
1593
        batcher = _BatchingBlockFetcher(self, locations,
 
1594
            get_compressor_settings=self._get_compressor_settings)
1575
1595
        for source, keys in source_keys:
1576
1596
            if source is self:
1577
1597
                for key in keys:
1623
1643
        for _ in self._insert_record_stream(stream, random_id=False):
1624
1644
            pass
1625
1645
 
 
1646
    def _get_compressor_settings(self):
 
1647
        if self._max_bytes_to_index is None:
 
1648
            # TODO: VersionedFiles don't know about their containing
 
1649
            #       repository, so they don't have much of an idea about their
 
1650
            #       location. So for now, this is only a global option.
 
1651
            c = config.GlobalConfig()
 
1652
            val = c.get_user_option('bzr.groupcompress.max_bytes_to_index')
 
1653
            if val is not None:
 
1654
                try:
 
1655
                    val = int(val)
 
1656
                except ValueError, e:
 
1657
                    trace.warning('Value for '
 
1658
                                  '"bzr.groupcompress.max_bytes_to_index"'
 
1659
                                  ' %r is not an integer'
 
1660
                                  % (val,))
 
1661
                    val = None
 
1662
            if val is None:
 
1663
                val = self._DEFAULT_MAX_BYTES_TO_INDEX
 
1664
            self._max_bytes_to_index = val
 
1665
        return {'max_bytes_to_index': self._max_bytes_to_index}
 
1666
 
 
1667
    def _make_group_compressor(self):
 
1668
        return GroupCompressor(self._get_compressor_settings())
 
1669
 
1626
1670
    def _insert_record_stream(self, stream, random_id=False, nostore_sha=None,
1627
1671
                              reuse_blocks=True):
1628
1672
        """Internal core to insert a record stream into this container.
1651
1695
                return adapter
1652
1696
        # This will go up to fulltexts for gc to gc fetching, which isn't
1653
1697
        # ideal.
1654
 
        self._compressor = GroupCompressor()
 
1698
        self._compressor = self._make_group_compressor()
1655
1699
        self._unadded_refs = {}
1656
1700
        keys_to_add = []
1657
1701
        def flush():
1658
1702
            bytes_len, chunks = self._compressor.flush().to_chunks()
1659
 
            self._compressor = GroupCompressor()
 
1703
            self._compressor = self._make_group_compressor()
1660
1704
            # Note: At this point we still have 1 copy of the fulltext (in
1661
1705
            #       record and the var 'bytes'), and this generates 2 copies of
1662
1706
            #       the compressed text (one for bytes, one in chunks)
1692
1736
                raise errors.RevisionNotPresent(record.key, self)
1693
1737
            if random_id:
1694
1738
                if record.key in inserted_keys:
1695
 
                    trace.note('Insert claimed random_id=True,'
1696
 
                               ' but then inserted %r two times', record.key)
 
1739
                    trace.note(gettext('Insert claimed random_id=True,'
 
1740
                               ' but then inserted %r two times'), record.key)
1697
1741
                    continue
1698
1742
                inserted_keys.add(record.key)
1699
1743
            if reuse_blocks:
2073
2117
        :param keys: An iterable of keys.
2074
2118
        :return: A dict of key:
2075
2119
            (index_memo, compression_parent, parents, record_details).
2076
 
            index_memo
2077
 
                opaque structure to pass to read_records to extract the raw
2078
 
                data
2079
 
            compression_parent
2080
 
                Content that this record is built upon, may be None
2081
 
            parents
2082
 
                Logical parents of this node
2083
 
            record_details
2084
 
                extra information about the content which needs to be passed to
2085
 
                Factory.parse_record
 
2120
 
 
2121
            * index_memo: opaque structure to pass to read_records to extract
 
2122
              the raw data
 
2123
            * compression_parent: Content that this record is built upon, may
 
2124
              be None
 
2125
            * parents: Logical parents of this node
 
2126
            * record_details: extra information about the content which needs
 
2127
              to be passed to Factory.parse_record
2086
2128
        """
2087
2129
        self._check_read()
2088
2130
        result = {}