blob: daeb6770b91d5fe02c89141c183a8db13aa4aad6 [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <limits>
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/hash.h"
#include "base/process/process_metrics.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/test/perf_time_logger.h"
#include "base/test/test_file_util.h"
#include "base/threading/thread.h"
#include "net/base/cache_type.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/disk_cache/blockfile/backend_impl.h"
#include "net/disk_cache/blockfile/block_files.h"
#include "net/disk_cache/disk_cache.h"
#include "net/disk_cache/disk_cache_test_base.h"
#include "net/disk_cache/disk_cache_test_util.h"
#include "net/disk_cache/simple/simple_backend_impl.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
using base::Time;
namespace {
size_t MaybeGetMaxFds() {
#if defined(OS_POSIX)
return base::GetMaxFds();
#else
return std::numeric_limits<size_t>::max();
#endif
}
void MaybeSetFdLimit(unsigned int max_descriptors) {
#if defined(OS_POSIX)
base::SetFdLimit(max_descriptors);
#endif
}
struct TestEntry {
std::string key;
int data_len;
};
class DiskCachePerfTest : public DiskCacheTestWithCache {
public:
DiskCachePerfTest() : saved_fd_limit_(MaybeGetMaxFds()) {
if (saved_fd_limit_ < kFdLimitForCacheTests)
MaybeSetFdLimit(kFdLimitForCacheTests);
}
~DiskCachePerfTest() override {
if (saved_fd_limit_ < kFdLimitForCacheTests)
MaybeSetFdLimit(kFdLimitForCacheTests);
}
protected:
enum class WhatToRead {
HEADERS_ONLY,
HEADERS_AND_BODY,
};
// Helper methods for constructing tests.
bool TimeWrite();
bool TimeRead(WhatToRead what_to_read, const char* timer_message);
void ResetAndEvictSystemDiskCache();
// Complete perf tests.
void CacheBackendPerformance();
const size_t kFdLimitForCacheTests = 8192;
const int kNumEntries = 1000;
const int kHeadersSize = 800;
const int kBodySize = 256 * 1024 - 1;
std::vector<TestEntry> entries_;
private:
const size_t saved_fd_limit_;
};
// Creates num_entries on the cache, and writes kHeaderSize bytes of metadata
// and up to kBodySize of data to each entry.
bool DiskCachePerfTest::TimeWrite() {
// TODO(gavinp): This test would be significantly more realistic if it didn't
// do single reads and writes. Perhaps entries should be written 64kb at a
// time. As well, not all entries should be created and written essentially
// simultaneously; some number of entries in flight at a time would be a
// likely better testing load.
scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kHeadersSize));
scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kBodySize));
CacheTestFillBuffer(buffer1->data(), kHeadersSize, false);
CacheTestFillBuffer(buffer2->data(), kBodySize, false);
int expected = 0;
MessageLoopHelper helper;
CallbackTest callback(&helper, true);
base::PerfTimeLogger timer("Write disk cache entries");
for (int i = 0; i < kNumEntries; i++) {
TestEntry entry;
entry.key = GenerateKey(true);
entry.data_len = rand() % kBodySize;
entries_.push_back(entry);
disk_cache::Entry* cache_entry;
net::TestCompletionCallback cb;
int rv = cache_->CreateEntry(entry.key, &cache_entry, cb.callback());
if (net::OK != cb.GetResult(rv))
break;
int ret = cache_entry->WriteData(
0, 0, buffer1.get(), kHeadersSize,
base::Bind(&CallbackTest::Run, base::Unretained(&callback)), false);
if (net::ERR_IO_PENDING == ret)
expected++;
else if (kHeadersSize != ret)
break;
ret = cache_entry->WriteData(
1, 0, buffer2.get(), entry.data_len,
base::Bind(&CallbackTest::Run, base::Unretained(&callback)), false);
if (net::ERR_IO_PENDING == ret)
expected++;
else if (entry.data_len != ret)
break;
cache_entry->Close();
}
helper.WaitUntilCacheIoFinished(expected);
timer.Done();
return expected == helper.callbacks_called();
}
// Reads the data and metadata from each entry listed on |entries|.
bool DiskCachePerfTest::TimeRead(WhatToRead what_to_read,
const char* timer_message) {
scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kHeadersSize));
scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kBodySize));
CacheTestFillBuffer(buffer1->data(), kHeadersSize, false);
CacheTestFillBuffer(buffer2->data(), kBodySize, false);
int expected = 0;
MessageLoopHelper helper;
CallbackTest callback(&helper, true);
base::PerfTimeLogger timer(timer_message);
for (int i = 0; i < kNumEntries; i++) {
disk_cache::Entry* cache_entry;
net::TestCompletionCallback cb;
int rv = cache_->OpenEntry(entries_[i].key, &cache_entry, cb.callback());
if (net::OK != cb.GetResult(rv))
break;
int ret = cache_entry->ReadData(
0, 0, buffer1.get(), kHeadersSize,
base::Bind(&CallbackTest::Run, base::Unretained(&callback)));
if (net::ERR_IO_PENDING == ret)
expected++;
else if (kHeadersSize != ret)
break;
if (what_to_read == WhatToRead::HEADERS_AND_BODY) {
ret = cache_entry->ReadData(
1, 0, buffer2.get(), entries_[i].data_len,
base::Bind(&CallbackTest::Run, base::Unretained(&callback)));
if (net::ERR_IO_PENDING == ret)
expected++;
else if (entries_[i].data_len != ret)
break;
}
cache_entry->Close();
}
helper.WaitUntilCacheIoFinished(expected);
timer.Done();
return (expected == helper.callbacks_called());
}
TEST_F(DiskCachePerfTest, BlockfileHashes) {
int seed = static_cast<int>(Time::Now().ToInternalValue());
srand(seed);
base::PerfTimeLogger timer("Hash disk cache keys");
for (int i = 0; i < 300000; i++) {
std::string key = GenerateKey(true);
base::Hash(key);
}
timer.Done();
}
void DiskCachePerfTest::ResetAndEvictSystemDiskCache() {
base::RunLoop().RunUntilIdle();
cache_.reset();
// Flush all files in the cache out of system memory.
const base::FilePath::StringType file_pattern = FILE_PATH_LITERAL("*");
base::FileEnumerator enumerator(cache_path_, true /* recursive */,
base::FileEnumerator::FILES, file_pattern);
for (base::FilePath file_path = enumerator.Next(); !file_path.empty();
file_path = enumerator.Next()) {
ASSERT_TRUE(base::EvictFileFromSystemCache(file_path));
}
#if defined(OS_LINUX)
// And, cache directories, on platforms where the eviction utility supports
// this (currently Linux only).
if (simple_cache_mode_) {
ASSERT_TRUE(
base::EvictFileFromSystemCache(cache_path_.AppendASCII("index-dir")));
}
ASSERT_TRUE(base::EvictFileFromSystemCache(cache_path_));
#endif
DisableFirstCleanup();
InitCache();
}
void DiskCachePerfTest::CacheBackendPerformance() {
InitCache();
EXPECT_TRUE(TimeWrite());
disk_cache::SimpleBackendImpl::FlushWorkerPoolForTesting();
base::RunLoop().RunUntilIdle();
ResetAndEvictSystemDiskCache();
EXPECT_TRUE(TimeRead(WhatToRead::HEADERS_ONLY,
"Read disk cache headers only (cold)"));
EXPECT_TRUE(TimeRead(WhatToRead::HEADERS_ONLY,
"Read disk cache headers only (warm)"));
disk_cache::SimpleBackendImpl::FlushWorkerPoolForTesting();
base::RunLoop().RunUntilIdle();
ResetAndEvictSystemDiskCache();
EXPECT_TRUE(
TimeRead(WhatToRead::HEADERS_AND_BODY, "Read disk cache entries (cold)"));
EXPECT_TRUE(
TimeRead(WhatToRead::HEADERS_AND_BODY, "Read disk cache entries (warm)"));
disk_cache::SimpleBackendImpl::FlushWorkerPoolForTesting();
base::RunLoop().RunUntilIdle();
}
TEST_F(DiskCachePerfTest, CacheBackendPerformance) {
CacheBackendPerformance();
}
TEST_F(DiskCachePerfTest, SimpleCacheBackendPerformance) {
SetSimpleCacheMode();
CacheBackendPerformance();
}
int BlockSize() {
// We can use form 1 to 4 blocks.
return (rand() & 0x3) + 1;
}
// Creating and deleting "entries" on a block-file is something quite frequent
// (after all, almost everything is stored on block files). The operation is
// almost free when the file is empty, but can be expensive if the file gets
// fragmented, or if we have multiple files. This test measures that scenario,
// by using multiple, highly fragmented files.
TEST_F(DiskCachePerfTest, BlockFilesPerformance) {
ASSERT_TRUE(CleanupCacheDir());
disk_cache::BlockFiles files(cache_path_);
ASSERT_TRUE(files.Init(true));
int seed = static_cast<int>(Time::Now().ToInternalValue());
srand(seed);
const int kNumBlocks = 60000;
disk_cache::Addr address[kNumBlocks];
base::PerfTimeLogger timer1("Fill three block-files");
// Fill up the 32-byte block file (use three files).
for (int i = 0; i < kNumBlocks; i++) {
EXPECT_TRUE(
files.CreateBlock(disk_cache::RANKINGS, BlockSize(), &address[i]));
}
timer1.Done();
base::PerfTimeLogger timer2("Create and delete blocks");
for (int i = 0; i < 200000; i++) {
int entry = rand() * (kNumBlocks / RAND_MAX + 1);
if (entry >= kNumBlocks)
entry = 0;
files.DeleteBlock(address[entry], false);
EXPECT_TRUE(
files.CreateBlock(disk_cache::RANKINGS, BlockSize(), &address[entry]));
}
timer2.Done();
base::RunLoop().RunUntilIdle();
}
} // namespace