Commit 262c744e authored by Matthieu Dorier's avatar Matthieu Dorier
Browse files

started converting configuration file to use 5 databases instead of 1

parent 11f84b91
...@@ -25,19 +25,22 @@ namespace hepnos { ...@@ -25,19 +25,22 @@ namespace hepnos {
// DataStoreImpl implementation // DataStoreImpl implementation
//////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////
struct DistributedDBInfo {
std::vector<sdskv::database> dbs;
struct ch_placement_instance* chi = nullptr;
};
class DataStoreImpl { class DataStoreImpl {
public: public:
margo_instance_id m_mid; // Margo instance margo_instance_id m_mid; // Margo instance
std::unordered_map<std::string,hg_addr_t> m_addrs; // Addresses used by the service std::unordered_map<std::string,hg_addr_t> m_addrs; // Addresses used by the service
sdskv::client m_sdskv_client; // SDSKV client sdskv::client m_sdskv_client; // SDSKV client
std::vector<sdskv::database> m_databases; // list of SDSKV databases DistributedDBInfo m_databases; // list of SDSKV databases
struct ch_placement_instance* m_chi_sdskv; // ch-placement instance for SDSKV
const DataStore::iterator m_end; // iterator for the end() of the DataStore const DataStore::iterator m_end; // iterator for the end() of the DataStore
DataStoreImpl() DataStoreImpl()
: m_mid(MARGO_INSTANCE_NULL) : m_mid(MARGO_INSTANCE_NULL)
, m_chi_sdskv(nullptr)
, m_end() {} , m_end() {}
void init(const std::string& configFile) { void init(const std::string& configFile) {
...@@ -61,9 +64,12 @@ class DataStoreImpl { ...@@ -61,9 +64,12 @@ class DataStoreImpl {
throw Exception("Could not create SDSKV client (SDSKV error="+std::to_string(ex.error())+")"); throw Exception("Could not create SDSKV client (SDSKV error="+std::to_string(ex.error())+")");
} }
// create list of sdskv provider handles // create list of sdskv provider handles
YAML::Node sdskv = config["hepnos"]["providers"]["sdskv"]; YAML::Node databases = config["hepnos"]["databases"];
for(YAML::const_iterator it = sdskv.begin(); it != sdskv.end(); it++) { YAML::Node dataset_db = databases["datasets"];
std::string str_addr = it->first.as<std::string>(); for(YAML::const_iterator address_it = dataset_db.begin(); address_it != dataset_db.end(); address_it++) {
std::string str_addr = address_it->first.as<std::string>();
YAML::Node providers = address_it->second;
// lookup the address
hg_addr_t addr; hg_addr_t addr;
if(m_addrs.count(str_addr) != 0) { if(m_addrs.count(str_addr) != 0) {
addr = m_addrs[str_addr]; addr = m_addrs[str_addr];
...@@ -76,33 +82,29 @@ class DataStoreImpl { ...@@ -76,33 +82,29 @@ class DataStoreImpl {
} }
m_addrs[str_addr] = addr; m_addrs[str_addr] = addr;
} }
// get the number of providers // iterate over providers for this address
uint16_t num_providers = it->second.as<uint16_t>(); for(YAML::const_iterator provider_it = providers.begin(); provider_it != providers.end(); provider_it++) {
for(uint16_t provider_id = 0 ; provider_id < num_providers; provider_id++) { // get the provider id
std::vector<sdskv::database> dbs; uint16_t provider_id = provider_it->first.as<uint16_t>();
try { // create provider handle
sdskv::provider_handle ph(m_sdskv_client, addr, provider_id); sdskv::provider_handle ph(m_sdskv_client, addr, provider_id);
dbs = m_sdskv_client.open(ph); // get the database ids
} catch(sdskv::exception& ex) { YAML::Node databases = provider_it->second;
cleanup(); // iterate over databases for this provider
throw Exception("Could not open databases (SDSKV error="+std::to_string(ex.error())+")"); for(unsigned i=0; i < databases.size(); i++) {
m_databases.dbs.push_back(sdskv::database(ph, databases[i].as<uint64_t>()));
} }
if(dbs.size() == 0) { } // for each provider
continue; } // for each address
}
for(auto& db : dbs)
m_databases.push_back(db);
}
}
// initialize ch-placement for the SDSKV providers // initialize ch-placement for the SDSKV providers
m_chi_sdskv = ch_placement_initialize("hash_lookup3", m_databases.size(), 4, 0); m_databases.chi = ch_placement_initialize("hash_lookup3", m_databases.dbs.size(), 4, 0);
} }
void cleanup() { void cleanup() {
m_databases.clear(); m_databases.dbs.clear();
m_sdskv_client = sdskv::client(); m_sdskv_client = sdskv::client();
if(m_chi_sdskv) if(m_databases.chi)
ch_placement_finalize(m_chi_sdskv); ch_placement_finalize(m_databases.chi);
for(auto& addr : m_addrs) { for(auto& addr : m_addrs) {
margo_addr_free(m_mid, addr.second); margo_addr_free(m_mid, addr.second);
} }
...@@ -127,27 +129,26 @@ class DataStoreImpl { ...@@ -127,27 +129,26 @@ class DataStoreImpl {
if(!protoNode) { if(!protoNode) {
throw Exception("\"protocol\" entry not found in \"client\" section"); throw Exception("\"protocol\" entry not found in \"client\" section");
} }
// hepnos entry has providers entry // hepnos entry has databases entry
auto providersNode = hepnosNode["providers"]; auto databasesNode = hepnosNode["databases"];
if(!providersNode) { if(!databasesNode) {
throw Exception("\"providers\" entry not found in \"hepnos\" section"); throw Exception("\"databasess\" entry not found in \"hepnos\" section");
}
// provider entry has sdskv entry
auto sdskvNode = providersNode["sdskv"];
if(!sdskvNode) {
throw Exception("\"sdskv\" entry not found in \"providers\" section");
}
// sdskv entry is not empty
if(sdskvNode.size() == 0) {
throw Exception("No provider found in \"sdskv\" section");
} }
// for each sdskv entry if(!databasesNode.IsMap()) {
for(auto it = sdskvNode.begin(); it != sdskvNode.end(); it++) { throw Exception("\"databases\" entry should be a map");
if(it->second.IsScalar()) continue; // one provider id given } /*
else { for(auto provider_it = databasesNode.begin(); provider_it != databasesNode.end(); provider_it++) {
throw Exception("Invalid value type for provider in \"sdskv\" section"); // provider entry should be a sequence
if(!provider.IsSequence()) {
throw Exception("provider entry should be a sequence");
}
for(auto db : provider) {
if(!db.IsScalar()) {
throw Exception("database id should be a scalar");
}
} }
} }
*/
} }
public: public:
...@@ -181,7 +182,7 @@ class DataStoreImpl { ...@@ -181,7 +182,7 @@ class DataStoreImpl {
// use the complete name for final objects (level 0) // use the complete name for final objects (level 0)
name_hash = hashString(key); name_hash = hashString(key);
} }
ch_placement_find_closest(m_chi_sdskv, name_hash, 1, &sdskv_db_idx); ch_placement_find_closest(m_databases.chi, name_hash, 1, &sdskv_db_idx);
return sdskv_db_idx; return sdskv_db_idx;
} }
...@@ -193,7 +194,7 @@ class DataStoreImpl { ...@@ -193,7 +194,7 @@ class DataStoreImpl {
// find out which DB to access // find out which DB to access
long unsigned sdskv_db_idx = computeDbIndex(level, containerName, key); long unsigned sdskv_db_idx = computeDbIndex(level, containerName, key);
// make corresponding datastore entry // make corresponding datastore entry
auto& db = m_databases[sdskv_db_idx]; auto& db = m_databases.dbs[sdskv_db_idx];
// read the value // read the value
if(data.size() == 0) if(data.size() == 0)
data.resize(2048); // eagerly allocate 2KB data.resize(2048); // eagerly allocate 2KB
...@@ -216,7 +217,7 @@ class DataStoreImpl { ...@@ -216,7 +217,7 @@ class DataStoreImpl {
// find out which DB to access // find out which DB to access
long unsigned sdskv_db_idx = computeDbIndex(level, containerName, key); long unsigned sdskv_db_idx = computeDbIndex(level, containerName, key);
// make corresponding datastore entry // make corresponding datastore entry
auto& db = m_databases[sdskv_db_idx]; auto& db = m_databases.dbs[sdskv_db_idx];
// read the value // read the value
try { try {
db.get(key.data(), key.size(), value, vsize); db.get(key.data(), key.size(), value, vsize);
...@@ -239,7 +240,7 @@ class DataStoreImpl { ...@@ -239,7 +240,7 @@ class DataStoreImpl {
// find out which DB to access // find out which DB to access
long unsigned sdskv_db_idx = computeDbIndex(level, containerName, key); long unsigned sdskv_db_idx = computeDbIndex(level, containerName, key);
// make corresponding datastore entry // make corresponding datastore entry
auto& db = m_databases[sdskv_db_idx]; auto& db = m_databases.dbs[sdskv_db_idx];
try { try {
return db.exists(key); return db.exists(key);
} catch(sdskv::exception& ex) { } catch(sdskv::exception& ex) {
...@@ -255,7 +256,7 @@ class DataStoreImpl { ...@@ -255,7 +256,7 @@ class DataStoreImpl {
// find out which DB to access // find out which DB to access
long unsigned sdskv_db_idx = computeDbIndex(level, containerName, key); long unsigned sdskv_db_idx = computeDbIndex(level, containerName, key);
// Create the product id // Create the product id
const auto& db = m_databases[sdskv_db_idx]; const auto& db = m_databases.dbs[sdskv_db_idx];
try { try {
db.put(key.data(), key.size(), data, data_size); db.put(key.data(), key.size(), data, data_size);
} catch(sdskv::exception& ex) { } catch(sdskv::exception& ex) {
...@@ -272,7 +273,7 @@ class DataStoreImpl { ...@@ -272,7 +273,7 @@ class DataStoreImpl {
const std::vector<std::string>& keys, const std::vector<std::string>& keys,
const std::vector<std::string>& values) { const std::vector<std::string>& values) {
// Create the product id // Create the product id
const auto& db = m_databases[db_index]; const auto& db = m_databases.dbs[db_index];
try { try {
db.put_multi(keys, values); db.put_multi(keys, values);
} catch(sdskv::exception& ex) { } catch(sdskv::exception& ex) {
...@@ -288,11 +289,11 @@ class DataStoreImpl { ...@@ -288,11 +289,11 @@ class DataStoreImpl {
// hash the name to get the provider id // hash the name to get the provider id
long unsigned db_idx = 0; long unsigned db_idx = 0;
uint64_t h = hashString(containerName); uint64_t h = hashString(containerName);
ch_placement_find_closest(m_chi_sdskv, h, 1, &db_idx); ch_placement_find_closest(m_databases.chi, h, 1, &db_idx);
// make an entry for the lower bound // make an entry for the lower bound
auto lb_entry = buildKey(level, containerName, lower); auto lb_entry = buildKey(level, containerName, lower);
// get provider and database // get provider and database
const auto& db = m_databases[db_idx]; const auto& db = m_databases.dbs[db_idx];
// ignore keys that don't have the same level or the same prefix // ignore keys that don't have the same level or the same prefix
std::string prefix(2+containerName.size(), '\0'); std::string prefix(2+containerName.size(), '\0');
prefix[0] = level; prefix[0] = level;
......
...@@ -4,27 +4,16 @@ ...@@ -4,27 +4,16 @@
namespace hepnos { namespace hepnos {
struct ConnectionInfoGenerator::Impl {
std::string m_addr; // address of this process
uint16_t m_num_bake_providers; // number of BAKE provider ids
uint16_t m_num_sdskv_providers; // number of SDSKV provider ids
};
ConnectionInfoGenerator::ConnectionInfoGenerator( ConnectionInfoGenerator::ConnectionInfoGenerator(
const std::string& address, const std::string& address,
uint16_t sdskv_providers, const ServiceConfig& config)
uint16_t bake_providers) : address(address), serviceConfig(config) {}
: m_impl(std::make_unique<Impl>()) {
m_impl->m_addr = address;
m_impl->m_num_bake_providers = bake_providers;
m_impl->m_num_sdskv_providers = sdskv_providers;
}
ConnectionInfoGenerator::~ConnectionInfoGenerator() {} ConnectionInfoGenerator::~ConnectionInfoGenerator() {}
void ConnectionInfoGenerator::generateFile(MPI_Comm comm, const std::string& filename) const { void ConnectionInfoGenerator::generateFile(MPI_Comm comm, const std::string& filename) const {
int rank, size; int rank, size;
auto addr_cpy = m_impl->m_addr; auto addr_cpy = address;
addr_cpy.resize(1024,'\0'); addr_cpy.resize(1024,'\0');
const char* addr = addr_cpy.c_str(); const char* addr = addr_cpy.c_str();
...@@ -40,21 +29,28 @@ void ConnectionInfoGenerator::generateFile(MPI_Comm comm, const std::string& fil ...@@ -40,21 +29,28 @@ void ConnectionInfoGenerator::generateFile(MPI_Comm comm, const std::string& fil
std::vector<char> addresses_buf(1024*size); std::vector<char> addresses_buf(1024*size);
MPI_Gather(addr, 1024, MPI_BYTE, addresses_buf.data(), 1024, MPI_BYTE, 0, comm); MPI_Gather(addr, 1024, MPI_BYTE, addresses_buf.data(), 1024, MPI_BYTE, 0, comm);
// Exchange bake providers info std::vector<sdskv_database_id_t> local_ids;
std::vector<uint16_t> bake_pr_ids_buf(size); std::vector<sdskv_database_id_t> all_db_ids;
MPI_Gather(&(m_impl->m_num_bake_providers), for(auto& p : serviceConfig.datasetProviders)
1, MPI_UNSIGNED_SHORT, for(auto& db : p.databases)
bake_pr_ids_buf.data(), local_ids.push_back(db.id);
1, MPI_UNSIGNED_SHORT, for(auto& p : serviceConfig.runProviders)
0, comm); for(auto& db : p.databases)
local_ids.push_back(db.id);
for(auto& p : serviceConfig.subrunProviders)
for(auto& db : p.databases)
local_ids.push_back(db.id);
for(auto& p : serviceConfig.eventProviders)
for(auto& db : p.databases)
local_ids.push_back(db.id);
for(auto& p : serviceConfig.productProviders)
for(auto& db : p.databases)
local_ids.push_back(db.id);
// Exchange database ids
all_db_ids.resize(local_ids.size()*size);
MPI_Gather(local_ids.data(), sizeof(sdskv_database_id_t)*local_ids.size(), MPI_BYTE,
all_db_ids.data(), sizeof(sdskv_database_id_t)*local_ids.size(), MPI_BYTE, 0, comm);
// Exchange sdskv providers info
std::vector<uint16_t> sdskv_pr_ids_buf(size);
MPI_Gather(&(m_impl->m_num_sdskv_providers),
1, MPI_UNSIGNED_SHORT,
sdskv_pr_ids_buf.data(),
1, MPI_UNSIGNED_SHORT,
0, comm);
// After this line, the rest is executed only by rank 0 // After this line, the rest is executed only by rank 0
if(rank != 0) return; if(rank != 0) return;
...@@ -66,14 +62,61 @@ void ConnectionInfoGenerator::generateFile(MPI_Comm comm, const std::string& fil ...@@ -66,14 +62,61 @@ void ConnectionInfoGenerator::generateFile(MPI_Comm comm, const std::string& fil
YAML::Node config; YAML::Node config;
config["hepnos"]["client"]["protocol"] = proto; config["hepnos"]["client"]["protocol"] = proto;
YAML::Node providers = config["hepnos"]["providers"]; YAML::Node databases = config["hepnos"]["databases"];
for(unsigned int i=0; i < size; i++) { YAML::Node datasets = databases["datasets"];
const auto& provider_addr = addresses[i]; YAML::Node runs = databases["runs"];
if(sdskv_pr_ids_buf[i]) { YAML::Node subruns = databases["subruns"];
providers["sdskv"][provider_addr] = sdskv_pr_ids_buf[i]; YAML::Node events = databases["events"];
YAML::Node products = databases["products"];
// for all the server nodes...
for(unsigned i=0; i < size; i++) {
YAML::Node datasetProviders = datasets[addresses[i]];
YAML::Node runProviders = runs[addresses[i]];
YAML::Node subrunProviders = subruns[addresses[i]];
YAML::Node eventProviders = events[addresses[i]];
YAML::Node productProviders = products[addresses[i]];
int db_per_node = local_ids.size();
int j=0;
for(auto& p : serviceConfig.datasetProviders) {
auto provider = datasetProviders[std::to_string(p.provider_id)];
for(auto& db : p.databases) {
auto id = all_db_ids[i*db_per_node+j];
provider.push_back(id);
j += 1;
}
}
for(auto& p : serviceConfig.runProviders) {
auto provider = runProviders[std::to_string(p.provider_id)];
for(auto& db : p.databases) {
auto id = all_db_ids[i*db_per_node+j];
provider.push_back(id);
j += 1;
}
}
for(auto& p : serviceConfig.subrunProviders) {
auto provider = subrunProviders[std::to_string(p.provider_id)];
for(auto& db : p.databases) {
auto id = all_db_ids[i*db_per_node+j];
provider.push_back(id);
j += 1;
}
}
for(auto& p : serviceConfig.eventProviders) {
auto provider = eventProviders[std::to_string(p.provider_id)];
for(auto& db : p.databases) {
auto id = all_db_ids[i*db_per_node+j];
provider.push_back(id);
j += 1;
}
} }
if(bake_pr_ids_buf[i]) { for(auto& p : serviceConfig.productProviders) {
providers["bake"][provider_addr] = bake_pr_ids_buf[i]; auto provider = productProviders[std::to_string(p.provider_id)];
for(auto& db : p.databases) {
auto id = all_db_ids[i*db_per_node+j];
provider.push_back(id);
j += 1;
}
} }
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include <string> #include <string>
#include <memory> #include <memory>
#include <mpi.h> #include <mpi.h>
#include "ServiceConfig.hpp"
namespace hepnos { namespace hepnos {
...@@ -11,13 +12,14 @@ class ConnectionInfoGenerator { ...@@ -11,13 +12,14 @@ class ConnectionInfoGenerator {
private: private:
class Impl; const std::string& address;
std::unique_ptr<Impl> m_impl; const ServiceConfig& serviceConfig;
public: public:
ConnectionInfoGenerator(const std::string& address, ConnectionInfoGenerator(const std::string& address,
uint16_t num_sdskv_providers, uint16_t num_bake_providers); const ServiceConfig& config);
ConnectionInfoGenerator(const ConnectionInfoGenerator&) = delete; ConnectionInfoGenerator(const ConnectionInfoGenerator&) = delete;
ConnectionInfoGenerator(ConnectionInfoGenerator&&) = delete; ConnectionInfoGenerator(ConnectionInfoGenerator&&) = delete;
ConnectionInfoGenerator& operator=(const ConnectionInfoGenerator&) = delete; ConnectionInfoGenerator& operator=(const ConnectionInfoGenerator&) = delete;
......
...@@ -20,17 +20,21 @@ namespace tl = thallium; ...@@ -20,17 +20,21 @@ namespace tl = thallium;
#define ASSERT(__cond, __msg, ...) { if(!(__cond)) { fprintf(stderr, "[%s:%d] " __msg, __FILE__, __LINE__, __VA_ARGS__); exit(-1); } } #define ASSERT(__cond, __msg, ...) { if(!(__cond)) { fprintf(stderr, "[%s:%d] " __msg, __FILE__, __LINE__, __VA_ARGS__); exit(-1); } }
static void createProviderAndDatabases(tl::engine& engine, hepnos::ProviderConfig& provider_config);
void hepnos_run_service(MPI_Comm comm, const char* config_file, const char* connection_file) void hepnos_run_service(MPI_Comm comm, const char* config_file, const char* connection_file)
{ {
int ret; int ret;
int rank; int rank;
int size;
MPI_Comm_rank(comm, &rank); MPI_Comm_rank(comm, &rank);
MPI_Comm_size(comm, &size);
/* load configuration */ /* load configuration */
std::unique_ptr<hepnos::ServiceConfig> config; std::unique_ptr<hepnos::ServiceConfig> config;
try { try {
config = std::make_unique<hepnos::ServiceConfig>(config_file, rank); config = std::make_unique<hepnos::ServiceConfig>(config_file, rank, size);
} catch(const std::exception& e) { } catch(const std::exception& e) {
std::cerr << "Error: when reading configuration:" << std::endl; std::cerr << "Error: when reading configuration:" << std::endl;
std::cerr << " " << e.what() << std::endl; std::cerr << " " << e.what() << std::endl;
...@@ -42,9 +46,9 @@ void hepnos_run_service(MPI_Comm comm, const char* config_file, const char* conn ...@@ -42,9 +46,9 @@ void hepnos_run_service(MPI_Comm comm, const char* config_file, const char* conn
std::unique_ptr<tl::engine> engine; std::unique_ptr<tl::engine> engine;
try { try {
engine = std::make_unique<tl::engine>( engine = std::make_unique<tl::engine>(
config->getAddress(), config->address,
THALLIUM_SERVER_MODE, THALLIUM_SERVER_MODE,
false, config->getNumThreads()-1); false, config->numThreads-1);
} catch(std::exception& ex) { } catch(std::exception& ex) {
std::cerr << "Error: unable to initialize thallium" << std::endl; std::cerr << "Error: unable to initialize thallium" << std::endl;
...@@ -56,37 +60,37 @@ void hepnos_run_service(MPI_Comm comm, const char* config_file, const char* conn ...@@ -56,37 +60,37 @@ void hepnos_run_service(MPI_Comm comm, const char* config_file, const char* conn
engine->enable_remote_shutdown(); engine->enable_remote_shutdown();
auto self_addr_str = static_cast<std::string>(engine->self()); auto self_addr_str = static_cast<std::string>(engine->self());
if(config->hasDatabase()) { /* SDSKV providers initialization */
/* SDSKV provider initialization */ for(auto& provider_config : config->datasetProviders)
for(auto sdskv_provider_id = 0; sdskv_provider_id < config->getNumDatabaseProviders(); sdskv_provider_id++) { createProviderAndDatabases(*engine, provider_config);
for(auto& provider_config : config->runProviders)
sdskv::provider* provider = sdskv::provider::create( createProviderAndDatabases(*engine, provider_config);
engine->get_margo_instance(), sdskv_provider_id, SDSKV_ABT_POOL_DEFAULT); for(auto& provider_config : config->subrunProviders)
createProviderAndDatabases(*engine, provider_config);
for(unsigned i=0 ; i < config->getNumDatabaseTargets(); i++) { for(auto& provider_config : config->eventProviders)
auto db_path = config->getDatabasePath(rank, sdskv_provider_id, i); createProviderAndDatabases(*engine, provider_config);
auto db_name = config->getDatabaseName(rank, sdskv_provider_id, i); for(auto& provider_config : config->productProviders)
sdskv_db_type_t db_type; createProviderAndDatabases(*engine, provider_config);
if(config->getDatabaseType() == "null") db_type = KVDB_NULL;
if(config->getDatabaseType() == "map") db_type = KVDB_MAP;
if(config->getDatabaseType() == "ldb") db_type = KVDB_LEVELDB;
if(config->getDatabaseType() == "bdb") db_type = KVDB_BERKELEYDB;
sdskv_database_id_t db_id;
sdskv_config_t config;
std::memset(&config, 0, sizeof(config));
config.db_name = db_name.c_str();
config.db_path = db_path.c_str();
config.db_type = db_type;
config.db_no_overwrite = 1;
db_id = provider->attach_database(config);
}
}
}
hepnos::ConnectionInfoGenerator fileGen(self_addr_str, hepnos::ConnectionInfoGenerator fileGen(self_addr_str, *config);
config->getNumDatabaseProviders(),
config->getNumStorageProviders());
fileGen.generateFile(MPI_COMM_WORLD, connection_file); fileGen.generateFile(MPI_COMM_WORLD, connection_file);
engine->wait_for_finalize(); engine->wait_for_finalize();
} }
static void createProviderAndDatabases(tl::engine& engine, hepnos::ProviderConfig& provider_config) {
sdskv::provider* provider = sdskv::provider::create(
engine.get_margo_instance(),
provider_config.provider_id,
SDSKV_ABT_POOL_DEFAULT);
for(auto& db_config : provider_config.databases) {
sdskv_config_t config;
std::memset(&config, 0, sizeof(config));
config.db_name = db_config.name.c_str();
config.db_path = db_config.path.c_str();
config.db_type = db_config.type;
config.db_no_overwrite = 1;
db_config.id = provider->attach_database(config);
}
}
#include <cmath> #include <cmath>
#include <iomanip> #include <iomanip>
#include <iostream>
#include <unordered_map>
#include "ServiceConfig.hpp" #include "ServiceConfig.hpp"