blob: b55d54a9b5ec56d20dca59e124c713f2050fac76 [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 <iostream>
#include <memory>
#include <set>
#include <lib/async-loop/cpp/loop.h>
#include <lib/component/cpp/startup_context.h>
#include <lib/fit/function.h>
#include <lib/fsl/vmo/strings.h>
#include <lib/fxl/command_line.h>
#include <lib/fxl/files/scoped_temp_dir.h>
#include <lib/fxl/logging.h>
#include <lib/fxl/memory/ref_ptr.h>
#include <lib/fxl/strings/string_number_conversions.h>
#include <lib/zx/time.h>
#include <trace/event.h>
#include "peridot/bin/ledger/fidl/include/types.h"
#include "peridot/bin/ledger/testing/data_generator.h"
#include "peridot/bin/ledger/testing/get_ledger.h"
#include "peridot/bin/ledger/testing/get_page_ensure_initialized.h"
#include "peridot/bin/ledger/testing/page_data_generator.h"
#include "peridot/bin/ledger/testing/quit_on_error.h"
#include "peridot/bin/ledger/testing/run_with_tracing.h"
#include "peridot/lib/convert/convert.h"
#include "peridot/lib/rng/test_random.h"
namespace ledger {
namespace {
// Benchmark that measures performance of the Put() operation.
//
// Parameters:
// --entry-count=<int> the number of entries to be put
// --transaction-size=<int> the size of a single transaction in number of put
// operations. If equal to 0, no explicit transactions will be made.
// --key-size=<int> the size of a single key in bytes
// --value-size=<int> the size of a single value in bytes
// --refs=(on|off) the reference strategy: on if every value is inserted
// as a reference, off if every value is inserted as a FIDL array.
// --update whether operations will update existing entries (put with existing
// keys and new values)
// --seed=<int> (optional) the seed for key and value generation
class PutBenchmark : public PageWatcher {
public:
PutBenchmark(async::Loop* loop,
std::unique_ptr<component::StartupContext> startup_context,
int entry_count, int transaction_size, int key_size,
int value_size, bool update,
PageDataGenerator::ReferenceStrategy reference_strategy,
uint64_t seed);
void Run();
// PageWatcher:
void OnChange(PageChange page_change, ResultState result_state,
OnChangeCallback callback) override;
private:
// Initilizes the keys to be used in the benchmark. In case the benchmark is
// on updating entries, it also adds these keys in the ledger with some
// initial values.
void InitializeKeys(
fit::function<void(std::vector<std::vector<uint8_t>>)> on_done);
void BindWatcher(std::vector<std::vector<uint8_t>> keys);
void RunSingle(int i, std::vector<std::vector<uint8_t>> keys);
void CommitAndRunNext(int i, size_t key_number,
std::vector<std::vector<uint8_t>> keys);
void PutEntry(std::vector<uint8_t> key, std::vector<uint8_t> value,
fit::function<void()> on_done);
void ShutDown();
fit::closure QuitLoopClosure();
async::Loop* const loop_;
rng::TestRandom random_;
DataGenerator generator_;
PageDataGenerator page_data_generator_;
files::ScopedTempDir tmp_dir_;
std::unique_ptr<component::StartupContext> startup_context_;
const int entry_count_;
const int transaction_size_;
const int key_size_;
const int value_size_;
const bool update_;
fidl::Binding<PageWatcher> page_watcher_binding_;
const PageDataGenerator::ReferenceStrategy reference_strategy_;
fuchsia::sys::ComponentControllerPtr component_controller_;
LedgerPtr ledger_;
PagePtr page_;
// Keys that we use to identify a change event. For transaction_size = 1 it
// contains all the keys, otherwise only the last changed key for each
// transaction.
std::set<size_t> keys_to_receive_;
FXL_DISALLOW_COPY_AND_ASSIGN(PutBenchmark);
};
} // namespace
} // namespace ledger
namespace ledger {
namespace {
constexpr fxl::StringView kStoragePath = "/data/benchmark/ledger/put";
PutBenchmark::PutBenchmark(
async::Loop* loop,
std::unique_ptr<component::StartupContext> startup_context, int entry_count,
int transaction_size, int key_size, int value_size, bool update,
PageDataGenerator::ReferenceStrategy reference_strategy, uint64_t seed)
: loop_(loop),
random_(seed),
generator_(&random_),
page_data_generator_(&random_),
tmp_dir_(kStoragePath),
startup_context_(std::move(startup_context)),
entry_count_(entry_count),
transaction_size_(transaction_size),
key_size_(key_size),
value_size_(value_size),
update_(update),
page_watcher_binding_(this),
reference_strategy_(reference_strategy) {
FXL_DCHECK(loop_);
FXL_DCHECK(entry_count > 0);
FXL_DCHECK(transaction_size >= 0);
FXL_DCHECK(key_size > 0);
FXL_DCHECK(value_size > 0);
}
void PutBenchmark::Run() {
FXL_LOG(INFO) << "--entry-count=" << entry_count_
<< " --transaction-size=" << transaction_size_
<< " --key-size=" << key_size_
<< " --value-size=" << value_size_ << " --refs="
<< (reference_strategy_ ==
PageDataGenerator::ReferenceStrategy::INLINE
? "off"
: "on")
<< (update_ ? " --update" : "");
Status status = GetLedger(
startup_context_.get(), component_controller_.NewRequest(), nullptr, "",
"put", DetachedPath(tmp_dir_.path()), QuitLoopClosure(), &ledger_);
if (QuitOnError(QuitLoopClosure(), status, "GetLedger")) {
return;
}
GetPageEnsureInitialized(
&ledger_, nullptr, DelayCallback::YES, QuitLoopClosure(),
[this](Status status, PagePtr page, PageId id) mutable {
if (QuitOnError(QuitLoopClosure(), status,
"GetPageEnsureInitialized")) {
return;
}
page_ = std::move(page);
InitializeKeys(
[this](std::vector<std::vector<uint8_t>> keys) mutable {
if (transaction_size_ > 0) {
page_->StartTransaction(
[this, keys = std::move(keys)](Status status) mutable {
if (QuitOnError(QuitLoopClosure(), status,
"Page::StartTransaction")) {
return;
}
TRACE_ASYNC_BEGIN("benchmark", "transaction", 0);
BindWatcher(std::move(keys));
});
} else {
BindWatcher(std::move(keys));
}
});
});
}
void PutBenchmark::OnChange(PageChange page_change,
ResultState /*result_state*/,
OnChangeCallback callback) {
for (auto const& change : page_change.changed_entries) {
size_t key_number = std::stoul(convert::ToString(change.key));
if (keys_to_receive_.find(key_number) != keys_to_receive_.end()) {
TRACE_ASYNC_END("benchmark", "local_change_notification", key_number);
keys_to_receive_.erase(key_number);
}
}
if (keys_to_receive_.empty()) {
ShutDown();
}
callback(nullptr);
}
void PutBenchmark::InitializeKeys(
fit::function<void(std::vector<std::vector<uint8_t>>)> on_done) {
std::vector<std::vector<uint8_t>> keys =
generator_.MakeKeys(entry_count_, key_size_, entry_count_);
std::vector<std::vector<uint8_t>> keys_cloned;
for (int i = 0; i < entry_count_; ++i) {
keys_cloned.push_back(keys[i]);
if (transaction_size_ == 0 ||
i % transaction_size_ == transaction_size_ - 1) {
keys_to_receive_.insert(std::stoul(convert::ToString(keys[i])));
}
}
// Last key should always be recorded so the last transaction is not lost.
size_t last_key_number = std::stoul(convert::ToString(keys.back()));
keys_to_receive_.insert(last_key_number);
if (!update_) {
on_done(std::move(keys));
return;
}
page_data_generator_.Populate(
&page_, std::move(keys_cloned), value_size_, keys_cloned.size(),
reference_strategy_, Priority::EAGER,
[this, keys = std::move(keys),
on_done = std::move(on_done)](Status status) mutable {
if (QuitOnError(QuitLoopClosure(), status,
"PageDataGenerator::Populate")) {
return;
}
on_done(std::move(keys));
});
}
void PutBenchmark::BindWatcher(std::vector<std::vector<uint8_t>> keys) {
PageSnapshotPtr snapshot;
page_->GetSnapshot(
snapshot.NewRequest(), fidl::VectorPtr<uint8_t>::New(0),
page_watcher_binding_.NewBinding(),
[this, keys = std::move(keys)](Status status) mutable {
if (QuitOnError(QuitLoopClosure(), status, "GetSnapshot")) {
return;
}
RunSingle(0, std::move(keys));
});
}
void PutBenchmark::RunSingle(int i,
std::vector<std::vector<uint8_t>> keys) {
if (i == entry_count_) {
// All sent, waiting for watcher notification before shutting down.
return;
}
fidl::VectorPtr<uint8_t> value = generator_.MakeValue(value_size_);
size_t key_number = std::stoul(convert::ToString(keys[i]));
if (transaction_size_ == 0) {
TRACE_ASYNC_BEGIN("benchmark", "local_change_notification", key_number);
}
PutEntry(std::move(keys[i]), std::move(value),
[this, i, key_number, keys = std::move(keys)]() mutable {
if (transaction_size_ > 0 &&
(i % transaction_size_ == transaction_size_ - 1 ||
i + 1 == entry_count_)) {
CommitAndRunNext(i, key_number, std::move(keys));
} else {
RunSingle(i + 1, std::move(keys));
}
});
}
void PutBenchmark::PutEntry(std::vector<uint8_t> key,
std::vector<uint8_t> value,
fit::function<void()> on_done) {
auto trace_event_id = TRACE_NONCE();
TRACE_ASYNC_BEGIN("benchmark", "put", trace_event_id);
if (reference_strategy_ == PageDataGenerator::ReferenceStrategy::INLINE) {
page_->Put(
std::move(key), std::move(value),
[this, trace_event_id, on_done = std::move(on_done)](Status status) {
if (QuitOnError(QuitLoopClosure(), status, "Page::Put")) {
return;
}
TRACE_ASYNC_END("benchmark", "put", trace_event_id);
on_done();
});
return;
}
fsl::SizedVmo vmo;
FXL_CHECK(fsl::VmoFromString(convert::ToStringView(value), &vmo));
TRACE_ASYNC_BEGIN("benchmark", "create_reference", trace_event_id);
page_->CreateReferenceFromBuffer(
std::move(vmo).ToTransport(),
[this, trace_event_id, key = std::move(key),
on_done = std::move(on_done)](Status status,
ReferencePtr reference) mutable {
if (QuitOnError(QuitLoopClosure(), status,
"Page::CreateReferenceFromBuffer")) {
return;
}
TRACE_ASYNC_END("benchmark", "create_reference", trace_event_id);
TRACE_ASYNC_BEGIN("benchmark", "put_reference", trace_event_id);
page_->PutReference(
std::move(key), std::move(*reference), Priority::EAGER,
[this, trace_event_id,
on_done = std::move(on_done)](Status status) {
if (QuitOnError(QuitLoopClosure(), status,
"Page::PutReference")) {
return;
}
TRACE_ASYNC_END("benchmark", "put_reference", trace_event_id);
TRACE_ASYNC_END("benchmark", "put", trace_event_id);
on_done();
});
});
}
void PutBenchmark::CommitAndRunNext(
int i, size_t key_number, std::vector<std::vector<uint8_t>> keys) {
TRACE_ASYNC_BEGIN("benchmark", "local_change_notification", key_number);
TRACE_ASYNC_BEGIN("benchmark", "commit", i / transaction_size_);
page_->Commit([this, i, keys = std::move(keys)](Status status) mutable {
if (QuitOnError(QuitLoopClosure(), status, "Page::Commit")) {
return;
}
TRACE_ASYNC_END("benchmark", "commit", i / transaction_size_);
TRACE_ASYNC_END("benchmark", "transaction", i / transaction_size_);
if (i == entry_count_ - 1) {
RunSingle(i + 1, std::move(keys));
return;
}
page_->StartTransaction([this, i = i + 1,
keys = std::move(keys)](Status status) mutable {
if (QuitOnError(QuitLoopClosure(), status, "Page::StartTransaction")) {
return;
}
TRACE_ASYNC_BEGIN("benchmark", "transaction", i / transaction_size_);
RunSingle(i, std::move(keys));
});
});
}
void PutBenchmark::ShutDown() {
// Shut down the Ledger process first as it relies on |tmp_dir_| storage.
KillLedgerProcess(&component_controller_);
loop_->Quit();
}
fit::closure PutBenchmark::QuitLoopClosure() {
return [this] { loop_->Quit(); };
}
} // namespace
} // namespace ledger
namespace ledger {
namespace {
constexpr fxl::StringView kBinaryPath =
"fuchsia-pkg://fuchsia.com/ledger_benchmarks#meta/put.cmx";
constexpr fxl::StringView kEntryCountFlag = "entry-count";
constexpr fxl::StringView kTransactionSizeFlag = "transaction-size";
constexpr fxl::StringView kKeySizeFlag = "key-size";
constexpr fxl::StringView kValueSizeFlag = "value-size";
constexpr fxl::StringView kRefsFlag = "refs";
constexpr fxl::StringView kUpdateFlag = "update";
constexpr fxl::StringView kSeedFlag = "seed";
constexpr fxl::StringView kRefsOnFlag = "on";
constexpr fxl::StringView kRefsOffFlag = "off";
void PrintUsage() {
std::cout << "Usage: trace record "
<< kBinaryPath
// Comment to make clang format not break formatting.
<< " --" << kEntryCountFlag << "=<int>"
<< " --" << kTransactionSizeFlag << "=<int>"
<< " --" << kKeySizeFlag << "=<int>"
<< " --" << kValueSizeFlag << "=<int>"
<< " --" << kRefsFlag << "=(" << kRefsOnFlag << "|" << kRefsOffFlag
<< ")"
<< " [--" << kSeedFlag << "=<int>]"
<< " [--" << kUpdateFlag << "]" << std::endl;
}
bool GetPositiveIntValue(const fxl::CommandLine& command_line,
fxl::StringView flag, int* value) {
std::string value_str;
int found_value;
if (!command_line.GetOptionValue(flag.ToString(), &value_str) ||
!fxl::StringToNumberWithError(value_str, &found_value) ||
found_value <= 0) {
return false;
}
*value = found_value;
return true;
}
int Main(int argc, const char** argv) {
fxl::CommandLine command_line = fxl::CommandLineFromArgcArgv(argc, argv);
async::Loop loop(&kAsyncLoopConfigAttachToThread);
auto startup_context = component::StartupContext::CreateFromStartupInfo();
int entry_count;
std::string transaction_size_str;
int transaction_size;
int key_size;
int value_size;
bool update = command_line.HasOption(kUpdateFlag.ToString());
if (!GetPositiveIntValue(command_line, kEntryCountFlag, &entry_count) ||
!command_line.GetOptionValue(kTransactionSizeFlag.ToString(),
&transaction_size_str) ||
!fxl::StringToNumberWithError(transaction_size_str, &transaction_size) ||
transaction_size < 0 ||
!GetPositiveIntValue(command_line, kKeySizeFlag, &key_size) ||
!GetPositiveIntValue(command_line, kValueSizeFlag, &value_size)) {
PrintUsage();
return -1;
}
std::string ref_strategy_str;
if (!command_line.GetOptionValue(kRefsFlag.ToString(), &ref_strategy_str)) {
PrintUsage();
return -1;
}
PageDataGenerator::ReferenceStrategy ref_strategy;
if (ref_strategy_str == kRefsOnFlag) {
ref_strategy = PageDataGenerator::ReferenceStrategy::REFERENCE;
} else if (ref_strategy_str == kRefsOffFlag) {
ref_strategy = PageDataGenerator::ReferenceStrategy::INLINE;
} else {
std::cerr << "Unknown option " << ref_strategy_str << " for "
<< kRefsFlag.ToString() << std::endl;
PrintUsage();
return -1;
}
int seed;
std::string seed_str;
if (command_line.GetOptionValue(kSeedFlag.ToString(), &seed_str)) {
if (!fxl::StringToNumberWithError(seed_str, &seed)) {
PrintUsage();
return -1;
}
} else {
seed = 0;
}
PutBenchmark app(&loop, std::move(startup_context), entry_count,
transaction_size, key_size, value_size, update, ref_strategy,
seed);
return RunWithTracing(&loop, [&app] { app.Run(); });
}
} // namespace
} // namespace ledger
int main(int argc, const char** argv) { return ledger::Main(argc, argv); }