blob: 57947ea66cf351a293a7ee5fc2df8460af831c1f [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 <lib/async-loop/cpp/loop.h>
#include <lib/callback/trace_callback.h>
#include <lib/fidl/cpp/optional.h>
#include <lib/fit/function.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/zx/time.h>
#include <trace/event.h>
#include <iostream>
#include <vector>
#include "garnet/public/lib/callback/waiter.h"
#include "peridot/lib/rng/test_random.h"
#include "src/ledger/bin/fidl/include/types.h"
#include "src/ledger/bin/testing/data_generator.h"
#include "src/ledger/bin/testing/get_ledger.h"
#include "src/ledger/bin/testing/get_page_ensure_initialized.h"
#include "src/ledger/bin/testing/quit_on_error.h"
#include "src/ledger/bin/testing/run_with_tracing.h"
#include "src/lib/files/directory.h"
#include "src/lib/files/scoped_temp_dir.h"
#include "src/lib/fxl/command_line.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/fxl/memory/ref_ptr.h"
#include "src/lib/fxl/strings/string_number_conversions.h"
namespace ledger {
namespace {
constexpr fxl::StringView kBinaryPath =
"fuchsia-pkg://fuchsia.com/ledger_benchmarks#meta/get_page.cmx";
constexpr fxl::StringView kStoragePath = "/data/benchmark/ledger/get_page";
constexpr fxl::StringView kPageCountFlag = "requests-count";
constexpr fxl::StringView kReuseFlag = "reuse";
constexpr fxl::StringView kWaitForCachedPageFlag = "wait-for-cached-page";
constexpr fxl::StringView kClearPagesFlag = "clear-pages";
// The delay to be used when waiting for a ledger background I/O operation to
// finish. This is used when it is not possible to wait for a specific event,
// like in the case of expecting the precached Page to be ready at the time of
// Page request. 500ms is chosen as a sufficiently long delay to guarantee this.
constexpr zx::duration kDelay = zx::msec(500);
constexpr size_t kKeySize = 10;
constexpr size_t kValueSize = 10;
void PrintUsage() {
std::cout << "Usage: trace record "
<< kBinaryPath
// Comment to make clang format not break formatting.
<< " --" << kPageCountFlag << "=<int>"
<< " [--" << kReuseFlag << "]"
<< " [--" << kWaitForCachedPageFlag << "]"
<< " [--" << kClearPagesFlag << "]" << std::endl;
}
// Benchmark that measures the time taken to get a page.
//
// Parameters:
// --requests-count=<int> number of requests made.
// --reuse - if this flag is specified, the same id will be used. Otherwise, a
// new page with a random id is requested every time.
// --wait_for_cached_page - if this flag is specified, the benchmark will wait
// for a sufficient amount of time before each page request, to allow Ledger
// to precache an empty new page.
class GetPageBenchmark {
public:
GetPageBenchmark(async::Loop* loop,
std::unique_ptr<sys::ComponentContext> component_context,
size_t requests_count, bool reuse, bool wait_for_cached_page,
bool clear_pages);
void Run();
private:
void RunSingle(size_t request_number);
void PopulateAndClearPage(size_t page_index, fit::closure callback);
void ShutDown();
fit::closure QuitLoopClosure();
async::Loop* const loop_;
rng::TestRandom random_;
files::ScopedTempDir tmp_dir_;
DataGenerator generator_;
std::unique_ptr<sys::ComponentContext> component_context_;
const size_t requests_count_;
const bool reuse_;
const bool wait_for_cached_page_;
const bool clear_pages_;
fuchsia::sys::ComponentControllerPtr component_controller_;
LedgerPtr ledger_;
PageIdPtr page_id_;
std::vector<PagePtr> pages_;
FXL_DISALLOW_COPY_AND_ASSIGN(GetPageBenchmark);
};
GetPageBenchmark::GetPageBenchmark(
async::Loop* loop, std::unique_ptr<sys::ComponentContext> component_context,
size_t requests_count, bool reuse, bool wait_for_cached_page,
bool clear_pages)
: loop_(loop),
random_(0),
tmp_dir_(kStoragePath),
generator_(&random_),
component_context_(std::move(component_context)),
requests_count_(requests_count),
reuse_(reuse),
wait_for_cached_page_(wait_for_cached_page),
clear_pages_(clear_pages) {
FXL_DCHECK(loop_);
FXL_DCHECK(requests_count_ > 0);
pages_.resize(requests_count_);
}
void GetPageBenchmark::Run() {
Status status = GetLedger(
component_context_.get(), component_controller_.NewRequest(), nullptr, "",
"get_page", DetachedPath(tmp_dir_.path()), QuitLoopClosure(), &ledger_);
if (QuitOnError(QuitLoopClosure(), status, "GetLedger")) {
return;
}
page_id_ = fidl::MakeOptional(generator_.MakePageId());
RunSingle(0);
}
void GetPageBenchmark::RunSingle(size_t request_number) {
if (request_number == requests_count_) {
ShutDown();
return;
}
if (wait_for_cached_page_) {
// Wait before each page request, so that a pre-cached page is ready.
zx_nanosleep(zx_deadline_after(kDelay.get()));
}
auto waiter = fxl::MakeRefCounted<callback::CompletionWaiter>();
TRACE_ASYNC_BEGIN("benchmark", "get_page", request_number);
ledger_->GetPage(reuse_ ? fidl::Clone(page_id_) : nullptr,
pages_[request_number].NewRequest());
ledger_->Sync(
[this, callback = waiter->NewCallback(), request_number]() mutable {
TRACE_ASYNC_END("benchmark", "get_page", request_number);
if (!clear_pages_) {
callback();
return;
}
// Make sure there is something written on disk before clearing the
// page. This will test the behavior of actually clearing a page (vs.
// just closing an always empty page).
PopulateAndClearPage(request_number, std::move(callback));
});
auto get_id_callback =
TRACE_CALLBACK(waiter->NewCallback(), "benchmark", "get_page_id");
// Request the page id without waiting for the GetPage callback to be called.
pages_[request_number]->GetId([callback = std::move(get_id_callback)](
PageId found_page_id) { callback(); });
// Wait for both GetPage and GetId to finish, before starting the next run.
waiter->Finalize([this, request_number]() {
if (clear_pages_) {
// To evict the cleared pages we need to close them.
pages_[request_number].Unbind();
}
RunSingle(request_number + 1);
});
}
void GetPageBenchmark::PopulateAndClearPage(size_t page_index,
fit::closure callback) {
pages_[page_index]->Put(generator_.MakeKey(page_index, kKeySize),
generator_.MakeValue(kValueSize));
pages_[page_index]->Clear();
pages_[page_index]->Sync(std::move(callback));
}
void GetPageBenchmark::ShutDown() {
if (clear_pages_) {
// Wait sufficient amount of time so that all cleared pages are evicted.
zx_nanosleep(zx_deadline_after(kDelay.get()));
}
KillLedgerProcess(&component_controller_);
loop_->Quit();
}
fit::closure GetPageBenchmark::QuitLoopClosure() {
return [this] { loop_->Quit(); };
}
int Main(int argc, const char** argv) {
fxl::CommandLine command_line = fxl::CommandLineFromArgcArgv(argc, argv);
async::Loop loop(&kAsyncLoopConfigAttachToThread);
auto component_context = sys::ComponentContext::Create();
std::string requests_count_str;
size_t requests_count;
if (!command_line.GetOptionValue(kPageCountFlag.ToString(),
&requests_count_str) ||
!fxl::StringToNumberWithError(requests_count_str, &requests_count) ||
requests_count == 0) {
PrintUsage();
return EXIT_FAILURE;
}
bool reuse = command_line.HasOption(kReuseFlag);
bool wait_for_cached_page = command_line.HasOption(kWaitForCachedPageFlag);
bool clear_pages = command_line.HasOption(kClearPagesFlag);
GetPageBenchmark app(&loop, std::move(component_context), requests_count,
reuse, wait_for_cached_page, clear_pages);
return RunWithTracing(&loop, [&app] { app.Run(); });
}
} // namespace
} // namespace ledger
int main(int argc, const char** argv) { return ledger::Main(argc, argv); }