Index: net/disk_cache/disk_cache_perftest.cc |
diff --git a/net/disk_cache/disk_cache_perftest.cc b/net/disk_cache/disk_cache_perftest.cc |
index b13a6909f2b2c8d3dea9bab7ec1e9b9fd3d75348..4c745c68af610e6e006bd982119643c7cb4f497a 100644 |
--- a/net/disk_cache/disk_cache_perftest.cc |
+++ b/net/disk_cache/disk_cache_perftest.cc |
@@ -1,354 +1,561 @@ |
-// 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/rand_util.h" |
-#include "base/run_loop.h" |
-#include "base/strings/string_util.h" |
-#include "base/test/perf_time_logger.h" |
-#include "base/test/scoped_task_environment.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/backend_cleanup_tracker.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 "net/disk_cache/simple/simple_index.h" |
-#include "net/disk_cache/simple/simple_index_file.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(saved_fd_limit_); |
- } |
- |
- 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_; |
- base::test::ScopedTaskEnvironment scoped_task_environment_; |
-}; |
- |
-// 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 = base::RandInt(0, 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) { |
- 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) || defined(OS_ANDROID) |
- // And, cache directories, on platforms where the eviction utility supports |
- // this (currently Linux and Android 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(); |
-} |
- |
-// 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)); |
- |
- 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++) { |
- int block_size = base::RandInt(1, 4); |
- EXPECT_TRUE( |
- files.CreateBlock(disk_cache::RANKINGS, block_size, &address[i])); |
- } |
- |
- timer1.Done(); |
- base::PerfTimeLogger timer2("Create and delete blocks"); |
- |
- for (int i = 0; i < 200000; i++) { |
- int block_size = base::RandInt(1, 4); |
- int entry = base::RandInt(0, kNumBlocks - 1); |
- |
- files.DeleteBlock(address[entry], false); |
- EXPECT_TRUE( |
- files.CreateBlock(disk_cache::RANKINGS, block_size, &address[entry])); |
- } |
- |
- timer2.Done(); |
- base::RunLoop().RunUntilIdle(); |
-} |
- |
-// Measures how quickly SimpleIndex can compute which entries to evict. |
-TEST(SimpleIndexPerfTest, EvictionPerformance) { |
- const int kEntries = 10000; |
- |
- class NoOpDelegate : public disk_cache::SimpleIndexDelegate { |
- void DoomEntries(std::vector<uint64_t>* entry_hashes, |
- const net::CompletionCallback& callback) override {} |
- }; |
- |
- NoOpDelegate delegate; |
- base::Time start(base::Time::Now()); |
- |
- double evict_elapsed_ms = 0; |
- int iterations = 0; |
- while (iterations < 61000) { |
- ++iterations; |
- disk_cache::SimpleIndex index(/* io_thread = */ nullptr, |
- /* cleanup_tracker = */ nullptr, &delegate, |
- net::DISK_CACHE, |
- /* simple_index_file = */ nullptr); |
- |
- // Make sure large enough to not evict on insertion. |
- index.SetMaxSize(kEntries * 2); |
- |
- for (int i = 0; i < kEntries; ++i) { |
- index.InsertEntryForTesting( |
- i, disk_cache::EntryMetadata(start + base::TimeDelta::FromSeconds(i), |
- 1u)); |
- } |
- |
- // Trigger an eviction. |
- base::ElapsedTimer timer; |
- index.SetMaxSize(kEntries); |
- index.UpdateEntrySize(0, 1u); |
- evict_elapsed_ms += timer.Elapsed().InMillisecondsF(); |
- } |
- |
- LOG(ERROR) << "Average time to evict:" << (evict_elapsed_ms / iterations) |
- << "ms"; |
-} |
- |
-} // namespace |
+// 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 <memory> |
+#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/rand_util.h" |
+#include "base/run_loop.h" |
+#include "base/strings/string_util.h" |
+#include "base/test/perf_time_logger.h" |
+#include "base/test/scoped_task_environment.h" |
+#include "base/test/test_file_util.h" |
+#include "base/threading/thread.h" |
+#include "build/build_config.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/backend_cleanup_tracker.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 "net/disk_cache/simple/simple_index.h" |
+#include "net/disk_cache/simple/simple_index_file.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "testing/platform_test.h" |
+ |
+using base::Time; |
+ |
+namespace { |
+ |
+const size_t kNumEntries = 1000; |
+const int kHeadersSize = 800; |
+ |
+const int kBodySize = 256 * 1024 - 1; |
+ |
+// As of 2017-01-12, this is the typical IPC size used for |
+const int kChunkSize = 64 * 1024; |
+ |
+// As of 2017-01-12, this is a typical per-tab limit on HTTP connections. |
+const int kMaxParallelOperations = 10; |
+ |
+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; |
+}; |
+ |
+enum class WhatToRead { |
+ HEADERS_ONLY, |
+ HEADERS_AND_BODY, |
+}; |
+ |
+class DiskCachePerfTest : public DiskCacheTestWithCache { |
+ public: |
+ DiskCachePerfTest() { |
+ if (saved_fd_limit_ < kFdLimitForCacheTests) |
+ MaybeSetFdLimit(kFdLimitForCacheTests); |
+ } |
+ |
+ ~DiskCachePerfTest() override { |
+ if (saved_fd_limit_ < kFdLimitForCacheTests) |
+ MaybeSetFdLimit(saved_fd_limit_); |
+ } |
+ |
+ const std::vector<TestEntry>& entries() const { return entries_; } |
+ |
+ protected: |
+ |
+ // Helper methods for constructing tests. |
+ bool TimeWrites(); |
+ bool TimeReads(WhatToRead what_to_read, const char* timer_message); |
+ void ResetAndEvictSystemDiskCache(); |
+ |
+ // Callbacks used within tests for intermediate operations. |
+ void WriteCallback(const net::CompletionCallback& final_callback, |
+ scoped_refptr<net::IOBuffer> headers_buffer, |
+ scoped_refptr<net::IOBuffer> body_buffer, |
+ disk_cache::Entry* cache_entry, |
+ int entry_index, |
+ size_t write_offset, |
+ int result); |
+ |
+ // Complete perf tests. |
+ void CacheBackendPerformance(); |
+ |
+ const size_t kFdLimitForCacheTests = 8192; |
+ |
+ std::vector<TestEntry> entries_; |
+ |
+ size_t next_entry_ = 0; // Which entry will be the next entry to read/write. |
+ size_t pending_operations_count_ = 0; |
+ int pending_result_; |
+ |
+ private: |
+ base::test::ScopedTaskEnvironment scoped_task_environment_; |
+ const size_t saved_fd_limit_ = MaybeGetMaxFds(); |
+}; |
+ |
+class WriteHandler { |
+ public: |
+ WriteHandler(const DiskCachePerfTest* test, |
+ disk_cache::Backend* cache, |
+ net::CompletionCallback final_callback) |
+ : test_(test), cache_(cache), final_callback_(final_callback) { |
+ CacheTestFillBuffer(headers_buffer_->data(), kHeadersSize, false); |
+ CacheTestFillBuffer(body_buffer_->data(), kChunkSize, false); |
+ } |
+ |
+ void Run(); |
+ |
+ protected: |
+ void CreateNextEntry(); |
+ |
+ void CreateCallback(std::unique_ptr<disk_cache::Entry*> unique_entry_ptr, |
+ int data_len, |
+ int result); |
+ void WriteDataCallback(disk_cache::Entry* entry, |
+ int next_offset, |
+ int data_len, |
+ int expected_result, |
+ int result); |
+ |
+ private: |
+ bool CheckForErrorAndCancel(int result); |
+ |
+ const DiskCachePerfTest* test_; |
+ disk_cache::Backend* cache_; |
+ net::CompletionCallback final_callback_; |
+ |
+ size_t next_entry_index_ = 0; |
+ size_t pending_operations_count_ = 0; |
+ |
+ int pending_result_ = net::OK; |
+ |
+ scoped_refptr<net::IOBuffer> headers_buffer_ = |
+ new net::IOBuffer(kHeadersSize); |
+ scoped_refptr<net::IOBuffer> body_buffer_ = new net::IOBuffer(kChunkSize); |
+}; |
+ |
+void WriteHandler::Run() { |
+ for (int i = 0; i < kMaxParallelOperations; ++i) { |
+ ++pending_operations_count_; |
+ CreateNextEntry(); |
+ } |
+} |
+ |
+void WriteHandler::CreateNextEntry() { |
+ EXPECT_GT(kNumEntries, next_entry_index_); |
+ TestEntry test_entry = test_->entries()[next_entry_index_++]; |
+ disk_cache::Entry** entry_ptr = new disk_cache::Entry*(); |
+ std::unique_ptr<disk_cache::Entry*> unique_entry_ptr(entry_ptr); |
+ net::CompletionCallback callback = |
+ base::Bind(&WriteHandler::CreateCallback, base::Unretained(this), |
+ base::Passed(&unique_entry_ptr), test_entry.data_len); |
+ int result = cache_->CreateEntry(test_entry.key, entry_ptr, callback); |
+ if (result != net::ERR_IO_PENDING) |
+ callback.Run(result); |
+} |
+ |
+void WriteHandler::CreateCallback(std::unique_ptr<disk_cache::Entry*> entry_ptr, |
+ int data_len, |
+ int result) { |
+ if (CheckForErrorAndCancel(result)) |
+ return; |
+ |
+ disk_cache::Entry* entry = *entry_ptr; |
+ |
+ net::CompletionCallback callback = |
+ base::Bind(&WriteHandler::WriteDataCallback, base::Unretained(this), |
+ entry, 0, data_len, kHeadersSize); |
+ int new_result = entry->WriteData(0, 0, headers_buffer_.get(), kHeadersSize, |
+ callback, false); |
+ if (new_result != net::ERR_IO_PENDING) |
+ callback.Run(new_result); |
+} |
+ |
+void WriteHandler::WriteDataCallback(disk_cache::Entry* entry, |
+ int next_offset, |
+ int data_len, |
+ int expected_result, |
+ int result) { |
+ if (CheckForErrorAndCancel(result)) { |
+ entry->Close(); |
+ return; |
+ } |
+ if (next_offset >= data_len) { |
+ entry->Close(); |
+ if (next_entry_index_ < kNumEntries) { |
+ CreateNextEntry(); |
+ } else { |
+ --pending_operations_count_; |
+ if (pending_operations_count_ == 0) |
+ final_callback_.Run(net::OK); |
+ } |
+ return; |
+ } |
+ |
+ int write_size = std::min(kChunkSize, data_len - next_offset); |
+ net::CompletionCallback callback = |
+ base::Bind(&WriteHandler::WriteDataCallback, base::Unretained(this), |
+ entry, next_offset + write_size, data_len, write_size); |
+ int new_result = entry->WriteData(1, next_offset, body_buffer_.get(), |
+ write_size, callback, false); |
+ if (new_result != net::ERR_IO_PENDING) |
+ callback.Run(new_result); |
+} |
+ |
+bool WriteHandler::CheckForErrorAndCancel(int result) { |
+ DCHECK_NE(net::ERR_IO_PENDING, result); |
+ if (result != net::OK && !(result > 0)) |
+ pending_result_ = result; |
+ if (pending_result_ != net::OK) { |
+ --pending_operations_count_; |
+ if (pending_operations_count_ == 0) |
+ final_callback_.Run(pending_result_); |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+class ReadHandler { |
+ public: |
+ ReadHandler(const DiskCachePerfTest* test, |
+ WhatToRead what_to_read, |
+ disk_cache::Backend* cache, |
+ net::CompletionCallback final_callback) |
+ : test_(test), |
+ what_to_read_(what_to_read), |
+ cache_(cache), |
+ final_callback_(final_callback) { |
+ for (int i = 0; i < kMaxParallelOperations; ++i) |
+ read_buffers_[i] = new net::IOBuffer(std::max(kHeadersSize, kChunkSize)); |
+ } |
+ |
+ void Run(); |
+ |
+ protected: |
+ void OpenNextEntry(int parallel_operation_index); |
+ |
+ void OpenCallback(int parallel_operation_index, |
+ std::unique_ptr<disk_cache::Entry*> unique_entry_ptr, |
+ int data_len, |
+ int result); |
+ void ReadDataCallback(int parallel_operation_index, |
+ disk_cache::Entry* entry, |
+ int next_offset, |
+ int data_len, |
+ int expected_result, |
+ int result); |
+ |
+ private: |
+ bool CheckForErrorAndCancel(int result); |
+ |
+ const DiskCachePerfTest* test_; |
+ const WhatToRead what_to_read_; |
+ |
+ disk_cache::Backend* cache_; |
+ net::CompletionCallback final_callback_; |
+ |
+ size_t next_entry_index_ = 0; |
+ size_t pending_operations_count_ = 0; |
+ |
+ int pending_result_ = net::OK; |
+ |
+ scoped_refptr<net::IOBuffer> read_buffers_[kMaxParallelOperations]; |
+}; |
+ |
+void ReadHandler::Run() { |
+ for (int i = 0; i < kMaxParallelOperations; ++i) { |
+ OpenNextEntry(pending_operations_count_); |
+ ++pending_operations_count_; |
+ } |
+} |
+ |
+void ReadHandler::OpenNextEntry(int parallel_operation_index) { |
+ EXPECT_GT(kNumEntries, next_entry_index_); |
+ TestEntry test_entry = test_->entries()[next_entry_index_++]; |
+ disk_cache::Entry** entry_ptr = new disk_cache::Entry*(); |
+ std::unique_ptr<disk_cache::Entry*> unique_entry_ptr(entry_ptr); |
+ net::CompletionCallback callback = |
+ base::Bind(&ReadHandler::OpenCallback, base::Unretained(this), |
+ parallel_operation_index, base::Passed(&unique_entry_ptr), |
+ test_entry.data_len); |
+ int result = cache_->OpenEntry(test_entry.key, entry_ptr, callback); |
+ if (result != net::ERR_IO_PENDING) |
+ callback.Run(result); |
+} |
+ |
+void ReadHandler::OpenCallback(int parallel_operation_index, |
+ std::unique_ptr<disk_cache::Entry*> entry_ptr, |
+ int data_len, |
+ int result) { |
+ if (CheckForErrorAndCancel(result)) |
+ return; |
+ |
+ disk_cache::Entry* entry = *(entry_ptr.get()); |
+ |
+ EXPECT_EQ(data_len, entry->GetDataSize(1)); |
+ |
+ net::CompletionCallback callback = |
+ base::Bind(&ReadHandler::ReadDataCallback, base::Unretained(this), |
+ parallel_operation_index, entry, 0, data_len, kHeadersSize); |
+ int new_result = |
+ entry->ReadData(0, 0, read_buffers_[parallel_operation_index].get(), |
+ kChunkSize, callback); |
+ if (new_result != net::ERR_IO_PENDING) |
+ callback.Run(new_result); |
+} |
+ |
+void ReadHandler::ReadDataCallback(int parallel_operation_index, |
+ disk_cache::Entry* entry, |
+ int next_offset, |
+ int data_len, |
+ int expected_result, |
+ int result) { |
+ if (CheckForErrorAndCancel(result)) { |
+ entry->Close(); |
+ return; |
+ } |
+ if (what_to_read_ == WhatToRead::HEADERS_ONLY || next_offset >= data_len) { |
+ entry->Close(); |
+ if (next_entry_index_ < kNumEntries) { |
+ OpenNextEntry(parallel_operation_index); |
+ } else { |
+ --pending_operations_count_; |
+ if (pending_operations_count_ == 0) |
+ final_callback_.Run(net::OK); |
+ } |
+ return; |
+ } |
+ |
+ int expected_read_size = std::min(kChunkSize, data_len - next_offset); |
+ net::CompletionCallback callback = |
+ base::Bind(&ReadHandler::ReadDataCallback, base::Unretained(this), |
+ parallel_operation_index, entry, next_offset + kChunkSize, |
+ data_len, expected_read_size); |
+ int new_result = entry->ReadData( |
+ 1, next_offset, read_buffers_[parallel_operation_index].get(), kChunkSize, |
+ callback); |
+ if (new_result != net::ERR_IO_PENDING) |
+ callback.Run(new_result); |
+} |
+ |
+bool ReadHandler::CheckForErrorAndCancel(int result) { |
+ DCHECK_NE(net::ERR_IO_PENDING, result); |
+ if (result != net::OK && !(result > 0)) |
+ pending_result_ = result; |
+ if (pending_result_ != net::OK) { |
+ --pending_operations_count_; |
+ if (pending_operations_count_ == 0) |
+ final_callback_.Run(pending_result_); |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+bool DiskCachePerfTest::TimeWrites() { |
+ for (size_t i = 0; i < kNumEntries; i++) { |
+ TestEntry entry; |
+ entry.key = GenerateKey(true); |
+ entry.data_len = base::RandInt(0, kBodySize); |
+ entries_.push_back(entry); |
+ } |
+ |
+ net::TestCompletionCallback cb; |
+ |
+ base::PerfTimeLogger timer("Write disk cache entries"); |
+ |
+ std::unique_ptr<WriteHandler> write_handler( |
+ new WriteHandler(this, cache_.get(), cb.callback())); |
+ write_handler->Run(); |
+ return cb.WaitForResult() == net::OK; |
+} |
+ |
+bool DiskCachePerfTest::TimeReads(WhatToRead what_to_read, |
+ const char* timer_message) { |
+ base::PerfTimeLogger timer(timer_message); |
+ |
+ net::TestCompletionCallback cb; |
+ std::unique_ptr<ReadHandler> read_handler( |
+ new ReadHandler(this, what_to_read, cache_.get(), cb.callback())); |
+ read_handler->Run(); |
+ return cb.WaitForResult() == net::OK; |
+} |
+ |
+TEST_F(DiskCachePerfTest, BlockfileHashes) { |
+ 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) || defined(OS_ANDROID) |
+ // And, cache directories, on platforms where the eviction utility supports |
+ // this (currently Linux and Android 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(TimeWrites()); |
+ |
+ disk_cache::SimpleBackendImpl::FlushWorkerPoolForTesting(); |
+ base::RunLoop().RunUntilIdle(); |
+ |
+ ResetAndEvictSystemDiskCache(); |
+ EXPECT_TRUE(TimeReads(WhatToRead::HEADERS_ONLY, |
+ "Read disk cache headers only (cold)")); |
+ EXPECT_TRUE(TimeReads(WhatToRead::HEADERS_ONLY, |
+ "Read disk cache headers only (warm)")); |
+ |
+ disk_cache::SimpleBackendImpl::FlushWorkerPoolForTesting(); |
+ base::RunLoop().RunUntilIdle(); |
+ |
+ ResetAndEvictSystemDiskCache(); |
+ EXPECT_TRUE(TimeReads(WhatToRead::HEADERS_AND_BODY, |
+ "Read disk cache entries (cold)")); |
+ EXPECT_TRUE(TimeReads(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(); |
+} |
+ |
+// 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)); |
+ |
+ 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++) { |
+ int block_size = base::RandInt(1, 4); |
+ EXPECT_TRUE( |
+ files.CreateBlock(disk_cache::RANKINGS, block_size, &address[i])); |
+ } |
+ |
+ timer1.Done(); |
+ base::PerfTimeLogger timer2("Create and delete blocks"); |
+ |
+ for (int i = 0; i < 200000; i++) { |
+ int block_size = base::RandInt(1, 4); |
+ int entry = base::RandInt(0, kNumBlocks - 1); |
+ |
+ files.DeleteBlock(address[entry], false); |
+ EXPECT_TRUE( |
+ files.CreateBlock(disk_cache::RANKINGS, block_size, &address[entry])); |
+ } |
+ |
+ timer2.Done(); |
+ base::RunLoop().RunUntilIdle(); |
+} |
+ |
+// Measures how quickly SimpleIndex can compute which entries to evict. |
+TEST(SimpleIndexPerfTest, EvictionPerformance) { |
+ const int kEntries = 10000; |
+ |
+ class NoOpDelegate : public disk_cache::SimpleIndexDelegate { |
+ void DoomEntries(std::vector<uint64_t>* entry_hashes, |
+ const net::CompletionCallback& callback) override {} |
+ }; |
+ |
+ NoOpDelegate delegate; |
+ base::Time start(base::Time::Now()); |
+ |
+ double evict_elapsed_ms = 0; |
+ int iterations = 0; |
+ while (iterations < 61000) { |
+ ++iterations; |
+ disk_cache::SimpleIndex index(/* io_thread = */ nullptr, |
+ /* cleanup_tracker = */ nullptr, &delegate, |
+ net::DISK_CACHE, |
+ /* simple_index_file = */ nullptr); |
+ |
+ // Make sure large enough to not evict on insertion. |
+ index.SetMaxSize(kEntries * 2); |
+ |
+ for (int i = 0; i < kEntries; ++i) { |
+ index.InsertEntryForTesting( |
+ i, disk_cache::EntryMetadata(start + base::TimeDelta::FromSeconds(i), |
+ 1u)); |
+ } |
+ |
+ // Trigger an eviction. |
+ base::ElapsedTimer timer; |
+ index.SetMaxSize(kEntries); |
+ index.UpdateEntrySize(0, 1u); |
+ evict_elapsed_ms += timer.Elapsed().InMillisecondsF(); |
+ } |
+ |
+ LOG(ERROR) << "Average time to evict:" << (evict_elapsed_ms / iterations) |
+ << "ms"; |
+} |
+ |
+} // namespace |