1// Copyright (c) 2011 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. 4 5#include <string> 6 7#include "base/basictypes.h" 8#include "base/bind.h" 9#include "base/bind_helpers.h" 10#include "base/hash.h" 11#include "base/strings/string_util.h" 12#include "base/test/perf_time_logger.h" 13#include "base/test/test_file_util.h" 14#include "base/threading/thread.h" 15#include "base/timer/timer.h" 16#include "net/base/cache_type.h" 17#include "net/base/io_buffer.h" 18#include "net/base/net_errors.h" 19#include "net/base/test_completion_callback.h" 20#include "net/disk_cache/blockfile/backend_impl.h" 21#include "net/disk_cache/blockfile/block_files.h" 22#include "net/disk_cache/disk_cache.h" 23#include "net/disk_cache/disk_cache_test_base.h" 24#include "net/disk_cache/disk_cache_test_util.h" 25#include "testing/gtest/include/gtest/gtest.h" 26#include "testing/platform_test.h" 27 28using base::Time; 29 30namespace { 31 32struct TestEntry { 33 std::string key; 34 int data_len; 35}; 36typedef std::vector<TestEntry> TestEntries; 37 38const int kMaxSize = 16 * 1024 - 1; 39 40// Creates num_entries on the cache, and writes 200 bytes of metadata and up 41// to kMaxSize of data to each entry. 42bool TimeWrite(int num_entries, disk_cache::Backend* cache, 43 TestEntries* entries) { 44 const int kSize1 = 200; 45 scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1)); 46 scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kMaxSize)); 47 48 CacheTestFillBuffer(buffer1->data(), kSize1, false); 49 CacheTestFillBuffer(buffer2->data(), kMaxSize, false); 50 51 int expected = 0; 52 53 MessageLoopHelper helper; 54 CallbackTest callback(&helper, true); 55 56 base::PerfTimeLogger timer("Write disk cache entries"); 57 58 for (int i = 0; i < num_entries; i++) { 59 TestEntry entry; 60 entry.key = GenerateKey(true); 61 entry.data_len = rand() % kMaxSize; 62 entries->push_back(entry); 63 64 disk_cache::Entry* cache_entry; 65 net::TestCompletionCallback cb; 66 int rv = cache->CreateEntry(entry.key, &cache_entry, cb.callback()); 67 if (net::OK != cb.GetResult(rv)) 68 break; 69 int ret = cache_entry->WriteData( 70 0, 0, buffer1.get(), kSize1, 71 base::Bind(&CallbackTest::Run, base::Unretained(&callback)), false); 72 if (net::ERR_IO_PENDING == ret) 73 expected++; 74 else if (kSize1 != ret) 75 break; 76 77 ret = cache_entry->WriteData( 78 1, 0, buffer2.get(), entry.data_len, 79 base::Bind(&CallbackTest::Run, base::Unretained(&callback)), false); 80 if (net::ERR_IO_PENDING == ret) 81 expected++; 82 else if (entry.data_len != ret) 83 break; 84 cache_entry->Close(); 85 } 86 87 helper.WaitUntilCacheIoFinished(expected); 88 timer.Done(); 89 90 return (expected == helper.callbacks_called()); 91} 92 93// Reads the data and metadata from each entry listed on |entries|. 94bool TimeRead(int num_entries, disk_cache::Backend* cache, 95 const TestEntries& entries, bool cold) { 96 const int kSize1 = 200; 97 scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1)); 98 scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kMaxSize)); 99 100 CacheTestFillBuffer(buffer1->data(), kSize1, false); 101 CacheTestFillBuffer(buffer2->data(), kMaxSize, false); 102 103 int expected = 0; 104 105 MessageLoopHelper helper; 106 CallbackTest callback(&helper, true); 107 108 const char* message = cold ? "Read disk cache entries (cold)" : 109 "Read disk cache entries (warm)"; 110 base::PerfTimeLogger timer(message); 111 112 for (int i = 0; i < num_entries; i++) { 113 disk_cache::Entry* cache_entry; 114 net::TestCompletionCallback cb; 115 int rv = cache->OpenEntry(entries[i].key, &cache_entry, cb.callback()); 116 if (net::OK != cb.GetResult(rv)) 117 break; 118 int ret = cache_entry->ReadData( 119 0, 0, buffer1.get(), kSize1, 120 base::Bind(&CallbackTest::Run, base::Unretained(&callback))); 121 if (net::ERR_IO_PENDING == ret) 122 expected++; 123 else if (kSize1 != ret) 124 break; 125 126 ret = cache_entry->ReadData( 127 1, 0, buffer2.get(), entries[i].data_len, 128 base::Bind(&CallbackTest::Run, base::Unretained(&callback))); 129 if (net::ERR_IO_PENDING == ret) 130 expected++; 131 else if (entries[i].data_len != ret) 132 break; 133 cache_entry->Close(); 134 } 135 136 helper.WaitUntilCacheIoFinished(expected); 137 timer.Done(); 138 139 return (expected == helper.callbacks_called()); 140} 141 142int BlockSize() { 143 // We can use form 1 to 4 blocks. 144 return (rand() & 0x3) + 1; 145} 146 147} // namespace 148 149TEST_F(DiskCacheTest, Hash) { 150 int seed = static_cast<int>(Time::Now().ToInternalValue()); 151 srand(seed); 152 153 base::PerfTimeLogger timer("Hash disk cache keys"); 154 for (int i = 0; i < 300000; i++) { 155 std::string key = GenerateKey(true); 156 base::Hash(key); 157 } 158 timer.Done(); 159} 160 161TEST_F(DiskCacheTest, CacheBackendPerformance) { 162 base::Thread cache_thread("CacheThread"); 163 ASSERT_TRUE(cache_thread.StartWithOptions( 164 base::Thread::Options(base::MessageLoop::TYPE_IO, 0))); 165 166 ASSERT_TRUE(CleanupCacheDir()); 167 net::TestCompletionCallback cb; 168 scoped_ptr<disk_cache::Backend> cache; 169 int rv = disk_cache::CreateCacheBackend(net::DISK_CACHE, 170 net::CACHE_BACKEND_BLOCKFILE, 171 cache_path_, 172 0, 173 false, 174 cache_thread.task_runner(), 175 NULL, 176 &cache, 177 cb.callback()); 178 179 ASSERT_EQ(net::OK, cb.GetResult(rv)); 180 181 int seed = static_cast<int>(Time::Now().ToInternalValue()); 182 srand(seed); 183 184 TestEntries entries; 185 int num_entries = 1000; 186 187 EXPECT_TRUE(TimeWrite(num_entries, cache.get(), &entries)); 188 189 base::MessageLoop::current()->RunUntilIdle(); 190 cache.reset(); 191 192 ASSERT_TRUE(base::EvictFileFromSystemCache( 193 cache_path_.AppendASCII("index"))); 194 ASSERT_TRUE(base::EvictFileFromSystemCache( 195 cache_path_.AppendASCII("data_0"))); 196 ASSERT_TRUE(base::EvictFileFromSystemCache( 197 cache_path_.AppendASCII("data_1"))); 198 ASSERT_TRUE(base::EvictFileFromSystemCache( 199 cache_path_.AppendASCII("data_2"))); 200 ASSERT_TRUE(base::EvictFileFromSystemCache( 201 cache_path_.AppendASCII("data_3"))); 202 203 rv = disk_cache::CreateCacheBackend(net::DISK_CACHE, 204 net::CACHE_BACKEND_BLOCKFILE, 205 cache_path_, 206 0, 207 false, 208 cache_thread.task_runner(), 209 NULL, 210 &cache, 211 cb.callback()); 212 ASSERT_EQ(net::OK, cb.GetResult(rv)); 213 214 EXPECT_TRUE(TimeRead(num_entries, cache.get(), entries, true)); 215 216 EXPECT_TRUE(TimeRead(num_entries, cache.get(), entries, false)); 217 218 base::MessageLoop::current()->RunUntilIdle(); 219} 220 221// Creating and deleting "entries" on a block-file is something quite frequent 222// (after all, almost everything is stored on block files). The operation is 223// almost free when the file is empty, but can be expensive if the file gets 224// fragmented, or if we have multiple files. This test measures that scenario, 225// by using multiple, highly fragmented files. 226TEST_F(DiskCacheTest, BlockFilesPerformance) { 227 ASSERT_TRUE(CleanupCacheDir()); 228 229 disk_cache::BlockFiles files(cache_path_); 230 ASSERT_TRUE(files.Init(true)); 231 232 int seed = static_cast<int>(Time::Now().ToInternalValue()); 233 srand(seed); 234 235 const int kNumEntries = 60000; 236 disk_cache::Addr* address = new disk_cache::Addr[kNumEntries]; 237 238 base::PerfTimeLogger timer1("Fill three block-files"); 239 240 // Fill up the 32-byte block file (use three files). 241 for (int i = 0; i < kNumEntries; i++) { 242 EXPECT_TRUE(files.CreateBlock(disk_cache::RANKINGS, BlockSize(), 243 &address[i])); 244 } 245 246 timer1.Done(); 247 base::PerfTimeLogger timer2("Create and delete blocks"); 248 249 for (int i = 0; i < 200000; i++) { 250 int entry = rand() * (kNumEntries / RAND_MAX + 1); 251 if (entry >= kNumEntries) 252 entry = 0; 253 254 files.DeleteBlock(address[entry], false); 255 EXPECT_TRUE(files.CreateBlock(disk_cache::RANKINGS, BlockSize(), 256 &address[entry])); 257 } 258 259 timer2.Done(); 260 base::MessageLoop::current()->RunUntilIdle(); 261 delete[] address; 262} 263