// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2016 Red Hat Inc. */ #pragma once #include #include #include #include #include #include #include #include namespace crimson { namespace qos_simulation { template class Simulation { public: using TimePoint = std::chrono::time_point; protected: using ClientMap = std::map; using ServerMap = std::map; uint server_count = 0; uint client_count = 0; ServerMap servers; ClientMap clients; std::vector server_ids; TimePoint early_time; TimePoint servers_created_time; TimePoint clients_created_time; TimePoint clients_finished_time; TimePoint late_time; std::default_random_engine prng; bool has_run = false; public: double fmt_tp(const TimePoint& t) { auto c = t.time_since_epoch().count(); return uint64_t(c / 1000000.0 + 0.5) % 100000 / 1000.0; } TimePoint now() { return std::chrono::steady_clock::now(); } using ClientBasedServerSelectFunc = std::function; using ClientFilter = std::function; using ServerFilter = std::function; using ServerDataOutF = std::function; using ClientDataOutF = std::function; Simulation() : early_time(now()), prng(std::chrono::system_clock::now().time_since_epoch().count()) { // empty } ~Simulation() { for (auto c : clients) { TC* cp = c.second; delete cp; } for (auto s : servers) { delete s.second; } } uint get_client_count() const { return client_count; } uint get_server_count() const { return server_count; } TC& get_client(ClientId id) { return *clients[id]; } TS& get_server(ServerId id) { return *servers[id]; } const ServerId& get_server_id(uint index) const { return server_ids[index]; } void add_servers(uint count, std::function create_server_f) { uint i = server_count; // increment server_count before creating servers since they // will start running immediately and may use the server_count // value; NB: this could still be an issue if servers are // added with multiple add_servers calls; consider using a // separate start function after all servers (and clients?) // have been added server_count += count; for (; i < server_count; ++i) { server_ids.push_back(i); servers[i] = create_server_f(i); } servers_created_time = now(); } void add_clients(uint count, std::function create_client_f) { uint i = client_count; // increment client_count before creating clients since they // will start running immediately and may use the client_count // value (e.g., in the server selection function); NB: this could // still be an issue if clients are added with multiple // add_clients calls; consider using a separate start function // after all clients have been added client_count += count; for (; i < client_count; ++i) { clients[i] = create_client_f(i); } clients_created_time = now(); } void run() { assert(server_count > 0); assert(client_count > 0); std::cout << "simulation started" << std::endl; // clients are now running; wait for all to finish for (auto const &i : clients) { i.second->wait_until_done(); } late_time = clients_finished_time = now(); std::cout << "simulation completed in " << std::chrono::duration_cast(clients_finished_time - servers_created_time).count() << " millisecs" << std::endl; has_run = true; } // run void display_stats(std::ostream& out, ServerDataOutF server_out_f, ClientDataOutF client_out_f, ServerFilter server_filter = [] (const ServerId&) { return true; }, ClientFilter client_filter = [] (const ClientId&) { return true; }, int head_w = 12, int data_w = 7, int data_prec = 2) { assert(has_run); // skip first 2 secondsd of data const std::chrono::seconds skip_amount(0); // calculate in groups of 5 seconds const std::chrono::seconds measure_unit(2); // unit to output reports in const std::chrono::seconds report_unit(1); // compute and display stats TimePoint earliest_start = late_time; TimePoint latest_start = early_time; TimePoint earliest_finish = late_time; TimePoint latest_finish = early_time; for (auto const &c : clients) { auto start = c.second->get_op_times().front(); auto end = c.second->get_op_times().back(); if (start < earliest_start) { earliest_start = start; } if (start > latest_start) { latest_start = start; } if (end < earliest_finish) { earliest_finish = end; } if (end > latest_finish) { latest_finish = end; } } double ops_factor = std::chrono::duration_cast>(measure_unit) / std::chrono::duration_cast>(report_unit); const auto start_edge = clients_created_time + skip_amount; std::map> ops_data; for (auto const &c : clients) { auto it = c.second->get_op_times().begin(); const auto end = c.second->get_op_times().end(); while (it != end && *it < start_edge) { ++it; } for (auto time_edge = start_edge + measure_unit; time_edge <= latest_finish + measure_unit; time_edge += measure_unit) { int count = 0; for (; it != end && *it < time_edge; ++count, ++it) { /* empty */ } double ops_per_second = double(count) / ops_factor; ops_data[c.first].push_back(ops_per_second); } } out << "==== Client Data ====" << std::endl; out << std::setw(head_w) << "client:"; for (auto const &c : clients) { if (!client_filter(c.first)) continue; out << " " << std::setw(data_w) << c.first; } out << std::setw(data_w) << "total" << std::endl; { bool has_data; size_t i = 0; do { std::string line_header = "t_" + std::to_string(i) + ":"; out << std::setw(head_w) << line_header; has_data = false; double total = 0.0; for (auto const &c : clients) { double data = 0.0; if (i < ops_data[c.first].size()) { data = ops_data[c.first][i]; has_data = true; } total += data; if (!client_filter(c.first)) continue; out << " " << std::setw(data_w) << std::setprecision(data_prec) << std::fixed << data; } out << " " << std::setw(data_w) << std::setprecision(data_prec) << std::fixed << total << std::endl; ++i; } while(has_data); } client_out_f(out, this, client_filter, head_w, data_w, data_prec); display_client_internal_stats(out, "nanoseconds"); out << std::endl << "==== Server Data ====" << std::endl; out << std::setw(head_w) << "server:"; for (auto const &s : servers) { if (!server_filter(s.first)) continue; out << " " << std::setw(data_w) << s.first; } out << " " << std::setw(data_w) << "total" << std::endl; server_out_f(out, this, server_filter, head_w, data_w, data_prec); display_server_internal_stats(out, "nanoseconds"); // clean up clients then servers for (auto i = clients.begin(); i != clients.end(); ++i) { delete i->second; i->second = nullptr; } for (auto i = servers.begin(); i != servers.end(); ++i) { delete i->second; i->second = nullptr; } } // display_stats template void display_server_internal_stats(std::ostream& out, std::string time_unit) { T add_request_time(0); T request_complete_time(0); uint32_t add_request_count = 0; uint32_t request_complete_count = 0; for (uint i = 0; i < get_server_count(); ++i) { const auto& server = get_server(i); const auto& is = server.get_internal_stats(); add_request_time += std::chrono::duration_cast(is.add_request_time); request_complete_time += std::chrono::duration_cast(is.request_complete_time); add_request_count += is.add_request_count; request_complete_count += is.request_complete_count; } double add_request_time_per_unit = double(add_request_time.count()) / add_request_count ; out << "total time to add requests: " << std::fixed << add_request_time.count() << " " << time_unit << ";" << std::endl << " count: " << add_request_count << ";" << std::endl << " average: " << add_request_time_per_unit << " " << time_unit << " per request/response" << std::endl; double request_complete_time_unit = double(request_complete_time.count()) / request_complete_count ; out << "total time to note requests complete: " << std::fixed << request_complete_time.count() << " " << time_unit << ";" << std::endl << " count: " << request_complete_count << ";" << std::endl << " average: " << request_complete_time_unit << " " << time_unit << " per request/response" << std::endl; out << std::endl; assert(add_request_count == request_complete_count); out << "server timing for QOS algorithm: " << add_request_time_per_unit + request_complete_time_unit << " " << time_unit << " per request/response" << std::endl; } template void display_client_internal_stats(std::ostream& out, std::string time_unit) { T track_resp_time(0); T get_req_params_time(0); uint32_t track_resp_count = 0; uint32_t get_req_params_count = 0; for (uint i = 0; i < get_client_count(); ++i) { const auto& client = get_client(i); const auto& is = client.get_internal_stats(); track_resp_time += std::chrono::duration_cast(is.track_resp_time); get_req_params_time += std::chrono::duration_cast(is.get_req_params_time); track_resp_count += is.track_resp_count; get_req_params_count += is.get_req_params_count; } double track_resp_time_unit = double(track_resp_time.count()) / track_resp_count; out << "total time to track responses: " << std::fixed << track_resp_time.count() << " " << time_unit << ";" << std::endl << " count: " << track_resp_count << ";" << std::endl << " average: " << track_resp_time_unit << " " << time_unit << " per request/response" << std::endl; double get_req_params_time_unit = double(get_req_params_time.count()) / get_req_params_count; out << "total time to get request parameters: " << std::fixed << get_req_params_time.count() << " " << time_unit << ";" << std::endl << " count: " << get_req_params_count << ";" << std::endl << " average: " << get_req_params_time_unit << " " << time_unit << " per request/response" << std::endl; out << std::endl; assert(track_resp_count == get_req_params_count); out << "client timing for QOS algorithm: " << track_resp_time_unit + get_req_params_time_unit << " " << time_unit << " per request/response" << std::endl; } // **** server selection functions **** const ServerId& server_select_alternate(uint64_t seed, uint16_t client_idx) { uint index = (client_idx + seed) % server_count; return server_ids[index]; } // returns a lambda using the range specified as servers_per (client) ClientBasedServerSelectFunc make_server_select_alt_range(uint16_t servers_per) { return [servers_per,this](uint64_t seed, uint16_t client_idx) -> const ServerId& { double factor = double(server_count) / client_count; uint offset = seed % servers_per; uint index = (uint(0.5 + client_idx * factor) + offset) % server_count; return server_ids[index]; }; } // function to choose a server randomly const ServerId& server_select_random(uint64_t seed, uint16_t client_idx) { uint index = prng() % server_count; return server_ids[index]; } // function to choose a server randomly ClientBasedServerSelectFunc make_server_select_ran_range(uint16_t servers_per) { return [servers_per,this](uint64_t seed, uint16_t client_idx) -> const ServerId& { double factor = double(server_count) / client_count; uint offset = prng() % servers_per; uint index = (uint(0.5 + client_idx * factor) + offset) % server_count; return server_ids[index]; }; } // function to always choose the first server const ServerId& server_select_0(uint64_t seed, uint16_t client_idx) { return server_ids[0]; } }; // class Simulation }; // namespace qos_simulation }; // namespace crimson