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