2022-02-22 20:33:45 +08:00
|
|
|
// Copyright 2022 Memgraph Ltd.
|
2021-10-26 14:53:56 +08:00
|
|
|
//
|
|
|
|
// Use of this software is governed by the Business Source License
|
|
|
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
|
|
// License, and you may not use this file except in compliance with the Business Source License.
|
|
|
|
//
|
|
|
|
// As of the Change Date specified in that file, in accordance with
|
|
|
|
// the Business Source License, use of this software will be governed
|
|
|
|
// by the Apache License, Version 2.0, included in the file
|
|
|
|
// licenses/APL.txt.
|
|
|
|
|
2018-02-08 00:52:01 +08:00
|
|
|
#pragma once
|
|
|
|
|
2018-07-02 21:34:33 +08:00
|
|
|
#include <atomic>
|
|
|
|
#include <chrono>
|
|
|
|
#include <fstream>
|
|
|
|
#include <iostream>
|
|
|
|
#include <map>
|
2019-04-23 17:00:49 +08:00
|
|
|
#include <optional>
|
2018-07-02 21:34:33 +08:00
|
|
|
#include <string>
|
|
|
|
#include <thread>
|
|
|
|
#include <unordered_map>
|
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
|
|
|
|
2018-01-27 01:50:16 +08:00
|
|
|
#include "json/json.hpp"
|
|
|
|
|
2021-01-21 22:47:56 +08:00
|
|
|
#include "utils/logging.hpp"
|
2018-01-27 01:50:16 +08:00
|
|
|
#include "utils/timer.hpp"
|
|
|
|
|
2018-03-28 20:49:28 +08:00
|
|
|
#include "common.hpp"
|
|
|
|
|
2018-01-27 01:50:16 +08:00
|
|
|
const int MAX_RETRIES = 30;
|
|
|
|
|
2018-08-10 20:35:43 +08:00
|
|
|
DEFINE_string(db, "", "Database queries are executed on.");
|
2018-01-27 01:50:16 +08:00
|
|
|
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");
|
2018-06-20 23:44:47 +08:00
|
|
|
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
|
2018-01-27 01:50:16 +08:00
|
|
|
DEFINE_int32(duration, 30, "Number of seconds to execute benchmark");
|
|
|
|
|
2018-02-08 00:52:01 +08:00
|
|
|
DEFINE_string(group, "unknown", "Test group name");
|
|
|
|
DEFINE_string(scenario, "unknown", "Test scenario name");
|
|
|
|
|
2019-12-06 21:41:03 +08:00
|
|
|
std::atomic<uint64_t> executed_queries{0};
|
|
|
|
std::atomic<uint64_t> executed_steps{0};
|
|
|
|
std::atomic<uint64_t> serialization_errors{0};
|
2018-02-15 23:28:38 +08:00
|
|
|
|
2018-01-27 01:50:16 +08:00
|
|
|
class TestClient {
|
|
|
|
public:
|
2018-03-28 20:49:28 +08:00
|
|
|
TestClient() {
|
|
|
|
Endpoint endpoint(FLAGS_address, FLAGS_port);
|
2018-10-19 18:28:38 +08:00
|
|
|
client_.Connect(endpoint, FLAGS_username, FLAGS_password);
|
2018-03-28 20:49:28 +08:00
|
|
|
}
|
2018-01-27 01:50:16 +08:00
|
|
|
|
|
|
|
virtual ~TestClient() {}
|
|
|
|
|
|
|
|
auto ConsumeStats() {
|
2022-02-22 20:33:45 +08:00
|
|
|
std::unique_lock<memgraph::utils::SpinLock> guard(lock_);
|
2018-01-27 01:50:16 +08:00
|
|
|
auto stats = stats_;
|
|
|
|
stats_.clear();
|
|
|
|
return stats;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Run() {
|
|
|
|
runner_thread_ = std::thread([&] {
|
|
|
|
while (keep_running_) {
|
|
|
|
Step();
|
2019-12-06 21:41:03 +08:00
|
|
|
++executed_steps;
|
2018-01-27 01:50:16 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void Stop() {
|
|
|
|
keep_running_ = false;
|
|
|
|
runner_thread_.join();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
virtual void Step() = 0;
|
|
|
|
|
2022-02-22 20:33:45 +08:00
|
|
|
std::optional<memgraph::communication::bolt::QueryData> Execute(const std::string &query,
|
|
|
|
const std::map<std::string, Value> ¶ms,
|
|
|
|
const std::string &query_name = "") {
|
|
|
|
memgraph::communication::bolt::QueryData result;
|
2018-03-06 02:11:52 +08:00
|
|
|
int retries;
|
2022-02-22 20:33:45 +08:00
|
|
|
memgraph::utils::Timer timer;
|
2018-03-06 02:11:52 +08:00
|
|
|
try {
|
2021-02-18 22:32:43 +08:00
|
|
|
std::tie(result, retries) = ExecuteNTimesTillSuccess(client_, query, params, MAX_RETRIES);
|
2022-02-22 20:33:45 +08:00
|
|
|
} catch (const memgraph::utils::BasicException &e) {
|
2019-12-06 21:41:03 +08:00
|
|
|
serialization_errors += MAX_RETRIES;
|
2019-04-23 17:00:49 +08:00
|
|
|
return std::nullopt;
|
2018-03-06 02:11:52 +08:00
|
|
|
}
|
2018-01-27 01:50:16 +08:00
|
|
|
auto wall_time = timer.Elapsed();
|
|
|
|
auto metadata = result.metadata;
|
|
|
|
metadata["wall_time"] = wall_time.count();
|
|
|
|
{
|
2022-02-22 20:33:45 +08:00
|
|
|
std::unique_lock<memgraph::utils::SpinLock> guard(lock_);
|
2018-01-27 01:50:16 +08:00
|
|
|
if (query_name != "") {
|
|
|
|
stats_[query_name].push_back(std::move(metadata));
|
|
|
|
} else {
|
|
|
|
stats_[query].push_back(std::move(metadata));
|
|
|
|
}
|
|
|
|
}
|
2019-12-06 21:41:03 +08:00
|
|
|
++executed_queries;
|
|
|
|
serialization_errors += retries;
|
2018-01-27 01:50:16 +08:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-02-22 20:33:45 +08:00
|
|
|
memgraph::utils::SpinLock lock_;
|
2021-02-18 22:32:43 +08:00
|
|
|
std::unordered_map<std::string, std::vector<std::map<std::string, Value>>> stats_;
|
2018-01-27 01:50:16 +08:00
|
|
|
|
|
|
|
std::atomic<bool> keep_running_{true};
|
|
|
|
std::thread runner_thread_;
|
|
|
|
|
|
|
|
private:
|
2022-02-22 20:33:45 +08:00
|
|
|
memgraph::communication::ClientContext context_{FLAGS_use_ssl};
|
2022-08-26 19:19:27 +08:00
|
|
|
Client client_{context_};
|
2018-01-27 01:50:16 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
void RunMultithreadedTest(std::vector<std::unique_ptr<TestClient>> &clients) {
|
2021-01-21 22:47:56 +08:00
|
|
|
MG_ASSERT((int)clients.size() == FLAGS_num_workers);
|
2018-01-27 01:50:16 +08:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2022-02-22 20:33:45 +08:00
|
|
|
memgraph::utils::Timer timer;
|
2018-01-27 01:50:16 +08:00
|
|
|
for (auto &client : clients) {
|
|
|
|
client->Run();
|
|
|
|
}
|
2021-01-21 22:47:56 +08:00
|
|
|
spdlog::info("Starting test with {} workers", clients.size());
|
2018-01-27 01:50:16 +08:00
|
|
|
while (timer.Elapsed().count() < FLAGS_duration) {
|
2021-02-18 22:32:43 +08:00
|
|
|
std::unordered_map<std::string, std::map<std::string, Value>> aggregated_stats;
|
2018-01-27 01:50:16 +08:00
|
|
|
|
|
|
|
using namespace std::chrono_literals;
|
2021-02-18 22:32:43 +08:00
|
|
|
std::unordered_map<std::string, std::vector<std::map<std::string, Value>>> stats;
|
2018-01-27 01:50:16 +08:00
|
|
|
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];
|
2021-02-18 22:32:43 +08:00
|
|
|
query_stats.insert(query_stats.end(), client_query_stats.second.begin(), client_query_stats.second.end());
|
2018-01-27 01:50:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-24 21:11:18 +08:00
|
|
|
// TODO: Here we combine pure values, json and Value which is a
|
2018-01-27 01:50:16 +08:00
|
|
|
// little bit chaotic. Think about refactoring this part to only use json
|
2018-07-24 21:11:18 +08:00
|
|
|
// and write Value to json converter.
|
2018-01-27 01:50:16 +08:00
|
|
|
const std::vector<std::string> fields = {
|
2018-03-06 02:11:52 +08:00
|
|
|
"wall_time",
|
|
|
|
"parsing_time",
|
|
|
|
"planning_time",
|
|
|
|
"plan_execution_time",
|
2018-01-27 01:50:16 +08:00
|
|
|
};
|
|
|
|
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];
|
2018-07-24 21:11:18 +08:00
|
|
|
aggregated_query_stats.insert({"count", Value(0)});
|
2018-01-27 01:50:16 +08:00
|
|
|
auto old_count = aggregated_query_stats["count"].ValueInt();
|
|
|
|
aggregated_query_stats["count"].ValueInt() += new_count;
|
|
|
|
for (const auto &stat : new_aggregated_query_stats) {
|
2018-07-24 21:11:18 +08:00
|
|
|
auto it = aggregated_query_stats.insert({stat.first, Value(0.0)}).first;
|
2021-02-18 22:32:43 +08:00
|
|
|
it->second = (it->second.ValueDouble() * old_count + stat.second) / (old_count + new_count);
|
2018-01-27 01:50:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-06 21:41:03 +08:00
|
|
|
out << "{\"num_executed_queries\": " << executed_queries << ", "
|
|
|
|
<< "\"num_executed_steps\": " << executed_steps << ", "
|
2021-02-18 22:32:43 +08:00
|
|
|
<< "\"elapsed_time\": " << timer.Elapsed().count() << ", \"queries\": [";
|
2022-02-22 20:33:45 +08:00
|
|
|
memgraph::utils::PrintIterable(out, aggregated_stats, ", ", [](auto &stream, const auto &x) {
|
2021-02-18 22:32:43 +08:00
|
|
|
stream << "{\"query\": " << nlohmann::json(x.first) << ", \"stats\": ";
|
|
|
|
PrintJsonValue(stream, Value(x.second));
|
|
|
|
stream << "}";
|
|
|
|
});
|
2018-01-27 01:50:16 +08:00
|
|
|
out << "]}" << std::endl;
|
|
|
|
out.flush();
|
|
|
|
std::this_thread::sleep_for(1s);
|
|
|
|
}
|
2021-01-21 22:47:56 +08:00
|
|
|
spdlog::info("Stopping workers...");
|
2018-01-27 01:50:16 +08:00
|
|
|
for (auto &client : clients) {
|
|
|
|
client->Stop();
|
|
|
|
}
|
2021-01-21 22:47:56 +08:00
|
|
|
spdlog::info("Stopped workers...");
|
2018-01-27 01:50:16 +08:00
|
|
|
}
|