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