1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/disk_cache/block_files.h"
7 #include "base/atomicops.h"
8 #include "base/file_util.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/threading/thread_checker.h"
13 #include "base/time/time.h"
14 #include "net/disk_cache/cache_util.h"
15 #include "net/disk_cache/file_lock.h"
16 #include "net/disk_cache/trace.h"
18 using base::TimeTicks
;
20 namespace disk_cache
{
22 BlockFiles::BlockFiles(const base::FilePath
& path
)
23 : init_(false), zero_buffer_(NULL
), path_(path
) {
26 BlockFiles::~BlockFiles() {
28 delete[] zero_buffer_
;
32 bool BlockFiles::Init(bool create_files
) {
37 thread_checker_
.reset(new base::ThreadChecker
);
39 block_files_
.resize(kFirstAdditionalBlockFile
);
40 for (int i
= 0; i
< kFirstAdditionalBlockFile
; i
++) {
42 if (!CreateBlockFile(i
, static_cast<FileType
>(i
+ 1), true))
45 if (!OpenBlockFile(i
))
48 // Walk this chain of files removing empty ones.
49 if (!RemoveEmptyFile(static_cast<FileType
>(i
+ 1)))
57 bool BlockFiles::CreateBlock(FileType block_type
, int block_count
,
58 Addr
* block_address
) {
59 DCHECK(thread_checker_
->CalledOnValidThread());
60 if (block_type
< RANKINGS
|| block_type
> BLOCK_4K
||
61 block_count
< 1 || block_count
> 4)
66 MappedFile
* file
= FileForNewBlock(block_type
, block_count
);
70 ScopedFlush
flush(file
);
71 BlockFileHeader
* header
= reinterpret_cast<BlockFileHeader
*>(file
->buffer());
74 for (int i
= block_count
; i
<= 4; i
++) {
75 if (header
->empty
[i
- 1]) {
83 if (!CreateMapBlock(target_size
, block_count
, header
, &index
))
86 Addr
address(block_type
, block_count
, header
->this_file
, index
);
87 block_address
->set_value(address
.value());
88 Trace("CreateBlock 0x%x", address
.value());
92 void BlockFiles::DeleteBlock(Addr address
, bool deep
) {
93 DCHECK(thread_checker_
->CalledOnValidThread());
94 if (!address
.is_initialized() || address
.is_separate_file())
98 zero_buffer_
= new char[Addr::BlockSizeForFileType(BLOCK_4K
) * 4];
99 memset(zero_buffer_
, 0, Addr::BlockSizeForFileType(BLOCK_4K
) * 4);
101 MappedFile
* file
= GetFile(address
);
105 Trace("DeleteBlock 0x%x", address
.value());
107 size_t size
= address
.BlockSize() * address
.num_blocks();
108 size_t offset
= address
.start_block() * address
.BlockSize() +
111 file
->Write(zero_buffer_
, size
, offset
);
113 BlockFileHeader
* header
= reinterpret_cast<BlockFileHeader
*>(file
->buffer());
114 DeleteMapBlock(address
.start_block(), address
.num_blocks(), header
);
117 if (!header
->num_entries
) {
118 // This file is now empty. Let's try to delete it.
119 FileType type
= Addr::RequiredFileType(header
->entry_size
);
120 if (Addr::BlockSizeForFileType(RANKINGS
) == header
->entry_size
)
122 RemoveEmptyFile(type
); // Ignore failures.
126 void BlockFiles::CloseFiles() {
128 DCHECK(thread_checker_
->CalledOnValidThread());
131 for (unsigned int i
= 0; i
< block_files_
.size(); i
++) {
132 if (block_files_
[i
]) {
133 block_files_
[i
]->Release();
134 block_files_
[i
] = NULL
;
137 block_files_
.clear();
140 void BlockFiles::ReportStats() {
141 DCHECK(thread_checker_
->CalledOnValidThread());
142 int used_blocks
[kFirstAdditionalBlockFile
];
143 int load
[kFirstAdditionalBlockFile
];
144 for (int i
= 0; i
< kFirstAdditionalBlockFile
; i
++) {
145 GetFileStats(i
, &used_blocks
[i
], &load
[i
]);
147 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_0", used_blocks
[0]);
148 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_1", used_blocks
[1]);
149 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_2", used_blocks
[2]);
150 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_3", used_blocks
[3]);
152 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_0", load
[0], 101);
153 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_1", load
[1], 101);
154 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_2", load
[2], 101);
155 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_3", load
[3], 101);
158 bool BlockFiles::IsValid(Addr address
) {
162 if (!address
.is_initialized() || address
.is_separate_file())
165 MappedFile
* file
= GetFile(address
);
169 BlockFileHeader
* header
= reinterpret_cast<BlockFileHeader
*>(file
->buffer());
170 bool rv
= UsedMapBlock(address
.start_block(), address
.num_blocks(), header
);
173 static bool read_contents
= false;
175 scoped_ptr
<char[]> buffer
;
176 buffer
.reset(new char[Addr::BlockSizeForFileType(BLOCK_4K
) * 4]);
177 size_t size
= address
.BlockSize() * address
.num_blocks();
178 size_t offset
= address
.start_block() * address
.BlockSize() +
180 bool ok
= file
->Read(buffer
.get(), size
, offset
);
188 MappedFile
* BlockFiles::GetFile(Addr address
) {
189 DCHECK(thread_checker_
->CalledOnValidThread());
190 DCHECK(block_files_
.size() >= 4);
191 DCHECK(address
.is_block_file() || !address
.is_initialized());
192 if (!address
.is_initialized())
195 int file_index
= address
.FileNumber();
196 if (static_cast<unsigned int>(file_index
) >= block_files_
.size() ||
197 !block_files_
[file_index
]) {
198 // We need to open the file
199 if (!OpenBlockFile(file_index
))
202 DCHECK(block_files_
.size() >= static_cast<unsigned int>(file_index
));
203 return block_files_
[file_index
];
206 bool BlockFiles::GrowBlockFile(MappedFile
* file
, BlockFileHeader
* header
) {
207 if (kMaxBlocks
== header
->max_entries
)
210 ScopedFlush
flush(file
);
211 DCHECK(!header
->empty
[3]);
212 int new_size
= header
->max_entries
+ 1024;
213 if (new_size
> kMaxBlocks
)
214 new_size
= kMaxBlocks
;
216 int new_size_bytes
= new_size
* header
->entry_size
+ sizeof(*header
);
218 if (!file
->SetLength(new_size_bytes
)) {
219 // Most likely we are trying to truncate the file, so the header is wrong.
220 if (header
->updating
< 10 && !FixBlockFileHeader(file
)) {
221 // If we can't fix the file increase the lock guard so we'll pick it on
222 // the next start and replace it.
223 header
->updating
= 100;
226 return (header
->max_entries
>= new_size
);
229 FileLock
lock(header
);
230 header
->empty
[3] = (new_size
- header
->max_entries
) / 4; // 4 blocks entries
231 header
->max_entries
= new_size
;
236 MappedFile
* BlockFiles::FileForNewBlock(FileType block_type
, int block_count
) {
237 COMPILE_ASSERT(RANKINGS
== 1, invalid_file_type
);
238 MappedFile
* file
= block_files_
[block_type
- 1];
239 BlockFileHeader
* header
= reinterpret_cast<BlockFileHeader
*>(file
->buffer());
241 TimeTicks start
= TimeTicks::Now();
242 while (NeedToGrowBlockFile(header
, block_count
)) {
243 if (kMaxBlocks
== header
->max_entries
) {
244 file
= NextFile(file
);
247 header
= reinterpret_cast<BlockFileHeader
*>(file
->buffer());
251 if (!GrowBlockFile(file
, header
))
255 HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", TimeTicks::Now() - start
);
259 // Note that we expect to be called outside of a FileLock... however, we cannot
260 // DCHECK on header->updating because we may be fixing a crash.
261 bool BlockFiles::FixBlockFileHeader(MappedFile
* file
) {
262 ScopedFlush
flush(file
);
263 BlockFileHeader
* header
= reinterpret_cast<BlockFileHeader
*>(file
->buffer());
264 int file_size
= static_cast<int>(file
->GetLength());
265 if (file_size
< static_cast<int>(sizeof(*header
)))
266 return false; // file_size > 2GB is also an error.
268 const int kMinBlockSize
= 36;
269 const int kMaxBlockSize
= 4096;
270 if (header
->entry_size
< kMinBlockSize
||
271 header
->entry_size
> kMaxBlockSize
|| header
->num_entries
< 0)
274 // Make sure that we survive crashes.
275 header
->updating
= 1;
276 int expected
= header
->entry_size
* header
->max_entries
+ sizeof(*header
);
277 if (file_size
!= expected
) {
278 int max_expected
= header
->entry_size
* kMaxBlocks
+ sizeof(*header
);
279 if (file_size
< expected
|| header
->empty
[3] || file_size
> max_expected
) {
281 LOG(ERROR
) << "Unexpected file size";
284 // We were in the middle of growing the file.
285 int num_entries
= (file_size
- sizeof(*header
)) / header
->entry_size
;
286 header
->max_entries
= num_entries
;
289 FixAllocationCounters(header
);
290 int empty_blocks
= EmptyBlocks(header
);
291 if (empty_blocks
+ header
->num_entries
> header
->max_entries
)
292 header
->num_entries
= header
->max_entries
- empty_blocks
;
294 if (!ValidateCounters(header
))
297 header
->updating
= 0;
301 // We are interested in the total number of blocks used by this file type, and
302 // the max number of blocks that we can store (reported as the percentage of
303 // used blocks). In order to find out the number of used blocks, we have to
304 // substract the empty blocks from the total blocks for each file in the chain.
305 void BlockFiles::GetFileStats(int index
, int* used_count
, int* load
) {
310 if (!block_files_
[index
] && !OpenBlockFile(index
))
313 BlockFileHeader
* header
=
314 reinterpret_cast<BlockFileHeader
*>(block_files_
[index
]->buffer());
316 max_blocks
+= header
->max_entries
;
317 int used
= header
->max_entries
;
318 for (int i
= 0; i
< 4; i
++) {
319 used
-= header
->empty
[i
] * (i
+ 1);
324 if (!header
->next_file
)
326 index
= header
->next_file
;
329 *load
= *used_count
* 100 / max_blocks
;
332 } // namespace disk_cache