ce29517998
Summary: Utils source files are now moved to a standalone mg-utils library. Unit and manual tests are no longer collected using glob recursion in cmake, but are explicitly listed. This allows us to set only required dependencies of those tests. Both of these changes should improve compilation and link times, as well as lower the disk usage. Additional improvement would be to cleanup utils header files to be split in .hpp and .cpp as well as merging threading into utils. Other potential library extractions that shouldn't be difficult are: * data_structures * io/network * communication Reviewers: buda, mferencevic, dgleich, ipaljak, mculinovic, mtomic, msantl Reviewed By: buda Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1408
197 lines
6.0 KiB
C++
197 lines
6.0 KiB
C++
#pragma once
|
|
|
|
#include "json/json.hpp"
|
|
|
|
#include "stats/metrics.hpp"
|
|
#include "stats/stats.hpp"
|
|
#include "utils/timer.hpp"
|
|
|
|
#include "common.hpp"
|
|
|
|
const int MAX_RETRIES = 30;
|
|
|
|
DEFINE_string(address, "127.0.0.1", "Server address");
|
|
DEFINE_int32(port, 7687, "Server port");
|
|
DEFINE_int32(num_workers, 1, "Number of workers");
|
|
DEFINE_string(output, "", "Output file");
|
|
DEFINE_string(username, "", "Username for the database");
|
|
DEFINE_string(password, "", "Password for the database");
|
|
DEFINE_int32(duration, 30, "Number of seconds to execute benchmark");
|
|
|
|
DEFINE_string(group, "unknown", "Test group name");
|
|
DEFINE_string(scenario, "unknown", "Test scenario name");
|
|
|
|
auto &executed_queries = stats::GetCounter("executed_queries");
|
|
auto &serialization_errors = stats::GetCounter("serialization_errors");
|
|
|
|
class TestClient {
|
|
public:
|
|
TestClient() {
|
|
Endpoint endpoint(FLAGS_address, FLAGS_port);
|
|
if (!client_.Connect(endpoint, FLAGS_username, FLAGS_password)) {
|
|
LOG(FATAL) << "Couldn't connect to " << endpoint;
|
|
}
|
|
}
|
|
|
|
virtual ~TestClient() {}
|
|
|
|
auto ConsumeStats() {
|
|
std::unique_lock<SpinLock> guard(lock_);
|
|
auto stats = stats_;
|
|
stats_.clear();
|
|
return stats;
|
|
}
|
|
|
|
void Run() {
|
|
runner_thread_ = std::thread([&] {
|
|
while (keep_running_) {
|
|
Step();
|
|
}
|
|
});
|
|
}
|
|
|
|
void Stop() {
|
|
keep_running_ = false;
|
|
runner_thread_.join();
|
|
}
|
|
|
|
protected:
|
|
virtual void Step() = 0;
|
|
|
|
std::experimental::optional<communication::bolt::QueryData> Execute(
|
|
const std::string &query,
|
|
const std::map<std::string, DecodedValue> ¶ms,
|
|
const std::string &query_name = "") {
|
|
communication::bolt::QueryData result;
|
|
int retries;
|
|
utils::Timer timer;
|
|
try {
|
|
std::tie(result, retries) =
|
|
ExecuteNTimesTillSuccess(client_, query, params, MAX_RETRIES);
|
|
} catch (const utils::BasicException &e) {
|
|
serialization_errors.Bump(MAX_RETRIES);
|
|
return std::experimental::nullopt;
|
|
}
|
|
auto wall_time = timer.Elapsed();
|
|
auto metadata = result.metadata;
|
|
metadata["wall_time"] = wall_time.count();
|
|
{
|
|
std::unique_lock<SpinLock> guard(lock_);
|
|
if (query_name != "") {
|
|
stats_[query_name].push_back(std::move(metadata));
|
|
} else {
|
|
stats_[query].push_back(std::move(metadata));
|
|
}
|
|
}
|
|
executed_queries.Bump();
|
|
serialization_errors.Bump(retries);
|
|
return result;
|
|
}
|
|
|
|
SpinLock lock_;
|
|
std::unordered_map<std::string,
|
|
std::vector<std::map<std::string, DecodedValue>>>
|
|
stats_;
|
|
|
|
std::atomic<bool> keep_running_{true};
|
|
std::thread runner_thread_;
|
|
|
|
private:
|
|
Client client_;
|
|
};
|
|
|
|
void RunMultithreadedTest(std::vector<std::unique_ptr<TestClient>> &clients) {
|
|
CHECK((int)clients.size() == FLAGS_num_workers);
|
|
|
|
// Open stream for writing stats.
|
|
std::streambuf *buf;
|
|
std::ofstream f;
|
|
if (FLAGS_output != "") {
|
|
f.open(FLAGS_output);
|
|
buf = f.rdbuf();
|
|
} else {
|
|
buf = std::cout.rdbuf();
|
|
}
|
|
std::ostream out(buf);
|
|
|
|
utils::Timer timer;
|
|
for (auto &client : clients) {
|
|
client->Run();
|
|
}
|
|
LOG(INFO) << "Starting test with " << clients.size() << " workers";
|
|
while (timer.Elapsed().count() < FLAGS_duration) {
|
|
std::unordered_map<std::string, std::map<std::string, DecodedValue>>
|
|
aggregated_stats;
|
|
|
|
using namespace std::chrono_literals;
|
|
std::unordered_map<std::string,
|
|
std::vector<std::map<std::string, DecodedValue>>>
|
|
stats;
|
|
for (const auto &client : clients) {
|
|
auto client_stats = client->ConsumeStats();
|
|
for (const auto &client_query_stats : client_stats) {
|
|
auto &query_stats = stats[client_query_stats.first];
|
|
query_stats.insert(query_stats.end(), client_query_stats.second.begin(),
|
|
client_query_stats.second.end());
|
|
}
|
|
}
|
|
|
|
// TODO: Here we combine pure values, json and DecodedValue which is a
|
|
// little bit chaotic. Think about refactoring this part to only use json
|
|
// and write DecodedValue to json converter.
|
|
const std::vector<std::string> fields = {
|
|
"wall_time",
|
|
"parsing_time",
|
|
"planning_time",
|
|
"plan_execution_time",
|
|
};
|
|
for (const auto &query_stats : stats) {
|
|
std::map<std::string, double> new_aggregated_query_stats;
|
|
for (const auto &stats : query_stats.second) {
|
|
for (const auto &field : fields) {
|
|
auto it = stats.find(field);
|
|
if (it != stats.end()) {
|
|
new_aggregated_query_stats[field] += it->second.ValueDouble();
|
|
}
|
|
}
|
|
}
|
|
int64_t new_count = query_stats.second.size();
|
|
|
|
auto &aggregated_query_stats = aggregated_stats[query_stats.first];
|
|
aggregated_query_stats.insert({"count", DecodedValue(0)});
|
|
auto old_count = aggregated_query_stats["count"].ValueInt();
|
|
aggregated_query_stats["count"].ValueInt() += new_count;
|
|
for (const auto &stat : new_aggregated_query_stats) {
|
|
auto it = aggregated_query_stats.insert({stat.first, DecodedValue(0.0)})
|
|
.first;
|
|
it->second = (it->second.ValueDouble() * old_count + stat.second) /
|
|
(old_count + new_count);
|
|
stats::LogStat(
|
|
fmt::format("queries.{}.{}", query_stats.first, stat.first),
|
|
(stat.second / new_count));
|
|
}
|
|
stats::LogStat(fmt::format("queries.{}.count", query_stats.first),
|
|
new_count);
|
|
}
|
|
|
|
out << "{\"num_executed_queries\": " << executed_queries.Value() << ", "
|
|
<< "\"elapsed_time\": " << timer.Elapsed().count()
|
|
<< ", \"queries\": [";
|
|
utils::PrintIterable(
|
|
out, aggregated_stats, ", ", [](auto &stream, const auto &x) {
|
|
stream << "{\"query\": " << nlohmann::json(x.first)
|
|
<< ", \"stats\": ";
|
|
PrintJsonDecodedValue(stream, DecodedValue(x.second));
|
|
stream << "}";
|
|
});
|
|
out << "]}" << std::endl;
|
|
out.flush();
|
|
std::this_thread::sleep_for(1s);
|
|
}
|
|
LOG(INFO) << "Stopping workers...";
|
|
for (auto &client : clients) {
|
|
client->Stop();
|
|
}
|
|
LOG(INFO) << "Stopped workers...";
|
|
}
|