1 # Copyright (C) 2009 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
17 """A manager of caches."""
20 from bzrlib
import lru_cache
, trace
21 from bzrlib
.plugins
.fastimport
import helpers
23 class CacheManager(object):
25 def __init__(self
, info
=None, verbose
=False, inventory_cache_size
=10):
26 """Create a manager of caches.
28 :param info: a ConfigObj holding the output from
29 the --info processor, or None if no hints are available
31 self
.verbose
= verbose
33 # dataref -> data. datref is either :mark or the sha-1.
34 # Sticky blobs aren't removed after being referenced.
36 self
._sticky
_blobs
= {}
38 # revision-id -> Inventory cache
39 # these are large and we probably don't need too many as
40 # most parents are recent in history
41 self
.inventories
= lru_cache
.LRUCache(inventory_cache_size
)
43 # import commmit-ids -> revision-id lookup table
44 # we need to keep all of these but they are small
45 self
.revision_ids
= {}
47 # (path, branch_ref) -> file-ids - as generated.
48 # (Use store_file_id/fetch_fileid methods rather than direct access.)
50 # Head tracking: last ref, last id per ref & map of commit ids to ref*s*
55 # Work out the blobs to make sticky - None means all
56 self
._blob
_ref
_counts
= {}
59 blobs_by_counts
= info
['Blob reference counts']
60 # The parser hands values back as lists, already parsed
61 for count
, blob_list
in blobs_by_counts
.items():
64 self
._blob
_ref
_counts
[b
] = n
66 # info not in file - possible when no blobs used
69 def dump_stats(self
, note
=trace
.note
):
70 """Dump some statistics about what we cached."""
71 # TODO: add in inventory stastistics
72 note("Cache statistics:")
73 self
._show
_stats
_for
(self
._sticky
_blobs
, "sticky blobs", note
=note
)
74 self
._show
_stats
_for
(self
.revision_ids
, "revision-ids", note
=note
)
75 # These aren't interesting so omit from the output, at least for now
76 #self._show_stats_for(self._blobs, "other blobs", note=note)
77 #self._show_stats_for(self.last_ids, "last-ids", note=note)
78 #self._show_stats_for(self.heads, "heads", note=note)
80 def _show_stats_for(self
, dict, label
, note
=trace
.note
, tuple_key
=False):
81 """Dump statistics about a given dictionary.
83 By the key and value need to support len().
87 size
= sum(map(len, (''.join(k
) for k
in dict.keys())))
89 size
= sum(map(len, dict.keys()))
90 size
+= sum(map(len, dict.values()))
91 size
= size
* 1.0 / 1024
99 note(" %-12s: %8.1f %s (%d %s)" % (label
, size
, unit
, count
,
100 helpers
.single_plural(count
, "item", "items")))
103 """Free up any memory used by the caches."""
105 self
._sticky
_blobs
.clear()
106 self
.revision_ids
.clear()
107 self
.last_ids
.clear()
109 self
.inventories
.clear()
111 def store_blob(self
, id, data
):
112 """Store a blob of data."""
113 # Note: If we're not reference counting, everything has to be sticky
114 if not self
._blob
_ref
_counts
or id in self
._blob
_ref
_counts
:
115 self
._sticky
_blobs
[id] = data
117 # Empty data is always sticky
118 self
._sticky
_blobs
[id] = data
120 self
._blobs
[id] = data
122 def fetch_blob(self
, id):
123 """Fetch a blob of data."""
125 b
= self
._sticky
_blobs
[id]
126 if self
._blob
_ref
_counts
and b
!= '':
127 self
._blob
_ref
_counts
[id] -= 1
128 if self
._blob
_ref
_counts
[id] == 0:
129 del self
._sticky
_blobs
[id]
132 return self
._blobs
.pop(id)
134 def track_heads(self
, cmd
):
135 """Track the repository heads given a CommitCommand.
137 :param cmd: the CommitCommand
138 :return: the list of parents in terms of commit-ids
140 # Get the true set of parents
141 if cmd
.from_
is not None:
142 parents
= [cmd
.from_
]
144 last_id
= self
.last_ids
.get(cmd
.ref
)
145 if last_id
is not None:
149 parents
.extend(cmd
.merges
)
152 self
.track_heads_for_ref(cmd
.ref
, cmd
.id, parents
)
155 def track_heads_for_ref(self
, cmd_ref
, cmd_id
, parents
=None):
156 if parents
is not None:
157 for parent
in parents
:
158 if parent
in self
.heads
:
159 del self
.heads
[parent
]
160 self
.heads
.setdefault(cmd_id
, set()).add(cmd_ref
)
161 self
.last_ids
[cmd_ref
] = cmd_id
162 self
.last_ref
= cmd_ref