diff --git a/src/headset/remote_headset.cpp b/src/headset/remote_headset.cpp index 0697bf91b8edc932c1bf5170274aa0231eb027a6..07dc88bc4e8220df7dfb2b29b9a9f185a0a7c7b0 100644 --- a/src/headset/remote_headset.cpp +++ b/src/headset/remote_headset.cpp @@ -37,21 +37,28 @@ bool RemoteHeadset::on_setup_device(lava::device::create_param& parameters) bool RemoteHeadset::on_create() { - this->statistic_send_bitrate = make_statistic("Send-Bitrate (Mbps)", 128); - this->statistic_send_queue_size = make_statistic("Send-Queue (KBytes)", 128); - this->statistic_receive_bitrate = make_statistic("Receive-Bitrate (Mbps)", 128); - this->statistic_receive_queue_size = make_statistic("Receive-Queue (KBytes)", 128); + StatisticLog::Ptr statistic_log = this->get_application()->get_statistic_log(); + + this->statistic_send_bitrate = make_statistic("host_send_bitrate", UNIT_MBITS_PER_SECOND); + this->statistic_send_queue_size = make_statistic("host_send_queue", UNIT_KBITS); + this->statistic_receive_bitrate = make_statistic("host_receive_bitrate", UNIT_MBITS_PER_SECOND); + this->statistic_receive_queue_size = make_statistic("host_receive_queue", UNIT_KBITS); this->statistic_latency = make_latency_statistic("latency"); - this->graph_list.push_back(this->statistic_latency->get_total_latency()); - this->graph_list.push_back(this->statistic_latency->get_server_response()); - this->graph_list.push_back(this->statistic_latency->get_frame_decoded()); - this->graph_list.push_back(this->statistic_latency->get_command_construct()); - this->graph_list.push_back(this->statistic_latency->get_command_executed()); - this->graph_list.push_back(this->statistic_send_bitrate); - this->graph_list.push_back(this->statistic_send_queue_size); - this->graph_list.push_back(this->statistic_receive_bitrate); - this->graph_list.push_back(this->statistic_receive_queue_size); + statistic_log->add_statistic(this->statistic_send_bitrate); + statistic_log->add_statistic(this->statistic_send_queue_size); + statistic_log->add_statistic(this->statistic_receive_bitrate); + statistic_log->add_statistic(this->statistic_receive_queue_size); + + this->statistic_list.push_back(this->statistic_latency->get_total_latency()); + this->statistic_list.push_back(this->statistic_latency->get_server_response()); + this->statistic_list.push_back(this->statistic_latency->get_frame_decoded()); + this->statistic_list.push_back(this->statistic_latency->get_command_construct()); + this->statistic_list.push_back(this->statistic_latency->get_command_executed()); + this->statistic_list.push_back(this->statistic_send_bitrate); + this->statistic_list.push_back(this->statistic_send_queue_size); + this->statistic_list.push_back(this->statistic_receive_bitrate); + this->statistic_list.push_back(this->statistic_receive_queue_size); StereoStrategy::Ptr strategy = this->get_application()->get_stereo_strategy(); this->frame_id_count = strategy->get_max_remote_frame_ids(); @@ -128,14 +135,25 @@ bool RemoteHeadset::on_interface() this->transport->send_overlay_config(this->overlay_enable); } - std::vector<const char*> graph_names; + std::vector<std::string> graph_names; + std::vector<const char*> graph_name_pointers; - for (const Statistic::Ptr& graph : this->graph_list) + std::unique_lock<std::mutex> lock(this->transport_mutex); + for (const Statistic::Ptr& statistic : this->statistic_list) { - graph_names.push_back(graph->get_name().c_str()); + if (!statistic->is_log_only()) + { + graph_names.push_back(statistic->get_display_name()); + } } + lock.unlock(); - ImGui::Combo("Overlay Graph", (int32_t*) &this->overlay_graph, graph_names.data(), graph_names.size()); + for (const std::string& graph_name : graph_names) + { + graph_name_pointers.push_back(graph_name.c_str()); + } + + ImGui::Combo("Overlay Graph", (int32_t*) &this->overlay_graph, graph_name_pointers.data(), graph_name_pointers.size()); ImGui::Separator(); @@ -222,14 +240,14 @@ bool RemoteHeadset::on_interface() ImGui::Separator(); - this->statistic_send_bitrate->interface(); - this->statistic_receive_bitrate->interface(); - - this->statistic_send_queue_size->interface(); - this->statistic_receive_queue_size->interface(); - - std::unique_lock<std::mutex> lock(this->transport_mutex); - this->statistic_latency->interface(); + lock.lock(); + for (Statistic::Ptr statistic : this->statistic_list) + { + if (!statistic->is_log_only()) + { + statistic->interface(128); + } + } lock.unlock(); return true; @@ -399,7 +417,7 @@ bool RemoteHeadset::create_transport() { this->on_latency(frame_number, frame_id, transform_id, timestamp_transfrom_query, timestamp_server_response, timestamp_frame_decoded, timestamp_command_construct, timestamp_command_executed); }); - this->transport->set_on_performance_sample([this](FrameNumber frame_number, FrameId frame_id, TransformId transform_id, PerformanceSampleUnit unit, bool log_only, double sample, const std::string& name) + this->transport->set_on_performance_sample([this](std::optional<FrameNumber> frame_number, std::optional<FrameId> frame_id, std::optional<TransformId> transform_id, Unit unit, bool log_only, double sample, const std::string& name) { this->on_performance_sample(frame_number, frame_id, transform_id, unit, log_only, sample, name); }); @@ -587,9 +605,19 @@ void RemoteHeadset::update_overlay() if (this->overlay_enable) { + std::vector<Statistic::Ptr> graph_list; + + for (Statistic::Ptr statistic : this->statistic_list) + { + if (!statistic->is_log_only()) + { + graph_list.push_back(statistic); + } + } + if (buttons[BUTTON_B] == BUTTON_STATE_PRESSED && !this->overlay_graph_pressed) { - this->overlay_graph = (this->overlay_graph + 1) % this->graph_list.size(); + this->overlay_graph = (this->overlay_graph + 1) % graph_list.size(); this->overlay_graph_pressed = true; } @@ -598,10 +626,11 @@ void RemoteHeadset::update_overlay() this->overlay_graph_pressed = false; } - Statistic::Ptr active_graph = this->graph_list[this->overlay_graph]; - std::vector<float> samples = active_graph->get_samples_ordered(); + Statistic::Ptr active_graph = graph_list[this->overlay_graph]; + std::vector<float> samples = active_graph->get_sample_values(128); + std::string label = active_graph->get_label_name(); - this->transport->send_overlay_graph(this->frame_number, 0, active_graph->get_name(), std::span(samples)); + this->transport->send_overlay_graph(this->frame_number, 0, label, std::span(samples)); this->transport->send_overlay_text(this->frame_number, 1, "Headset", this->get_name()); this->transport->send_overlay_text(this->frame_number, 2, "Strategy", this->get_application()->get_stereo_strategy()->get_name()); } @@ -679,9 +708,37 @@ void RemoteHeadset::on_latency(FrameNumber frame_number, FrameId frame_id, Trans lock.unlock(); } -void RemoteHeadset::on_performance_sample(FrameNumber frame_number, FrameId frame_id, TransformId transform_id, PerformanceSampleUnit unit, bool log_only, double sample, const std::string& name) +void RemoteHeadset::on_performance_sample(std::optional<FrameNumber> frame_number, std::optional<FrameId> frame_id, std::optional<TransformId> transform_id, Unit unit, bool log_only, double sample, const std::string& name) { - + std::unique_lock<std::mutex> lock(this->transport_mutex); + Statistic::Ptr statistic; + + for (uint32_t index = 0; index < this->statistic_list.size(); index++) + { + if (this->statistic_list[index]->get_name() == name) + { + statistic = this->statistic_list[index]; + + break; + } + } + + if (statistic == nullptr) + { + statistic = make_statistic(name, unit, log_only); + this->statistic_list.push_back(statistic); + + StatisticLog::Ptr statistic_log = this->get_application()->get_statistic_log(); + statistic_log->add_statistic(statistic); + } + + StatisticSample statistic_sample; + statistic_sample.value = sample; + statistic_sample.frame_number = frame_number; + statistic_sample.frame_id = frame_id; + statistic_sample.transform_id = transform_id; + + statistic->add_sample(statistic_sample); } void RemoteHeadset::on_transport_error() diff --git a/src/headset/remote_headset.hpp b/src/headset/remote_headset.hpp index d65f190073d448ec32f13943fe9653976bc9d135..aa34cb9f68eb4d616b963592c6ee42c792f7308d 100644 --- a/src/headset/remote_headset.hpp +++ b/src/headset/remote_headset.hpp @@ -71,7 +71,7 @@ private: void on_controller_button(Controller controller, Button button, ButtonState button_state); void on_controller_thumbstick(Controller controller, const glm::vec2& thumbstick); void on_latency(FrameNumber frame_number, FrameId frame_id, TransformId transform_id, double timestamp_transform_query, double timestamp_server_response, double timestamp_frame_decoded, double timestamp_command_construct, double timestamp_command_executed); - void on_performance_sample(FrameNumber frame_number, FrameId frame_id, TransformId transform_id, PerformanceSampleUnit unit, bool log_only, double sample, const std::string& name); + void on_performance_sample(std::optional<FrameNumber> frame_number, std::optional<FrameId> frame_id, std::optional<TransformId> transform_id, Unit unit, bool log_only, double sample, const std::string& name); void on_transport_error(); void on_encode_error(); @@ -128,8 +128,8 @@ private: bool overlay_enable_pressed = false; uint32_t overlay_graph = 0; bool overlay_graph_pressed = false; - std::vector<Statistic::Ptr> graph_list; + std::vector<Statistic::Ptr> statistic_list; //NOTE: Protected by transport_mutex Statistic::Ptr statistic_send_bitrate; Statistic::Ptr statistic_send_queue_size; Statistic::Ptr statistic_receive_bitrate; diff --git a/src/transport/transport.hpp b/src/transport/transport.hpp index a2022a24b3aaef1355cced63240da2ebc3ece797..4363aafe6d3a6b1c7b22b4358933a48f71ae2bd0 100644 --- a/src/transport/transport.hpp +++ b/src/transport/transport.hpp @@ -26,6 +26,7 @@ #include <glm/glm.hpp> #include <functional> #include <memory> +#include <optional> #include <span> #include "types.hpp" @@ -100,7 +101,7 @@ public: - typedef std::function<void(FrameNumber frame_number, FrameId frame_id, TransformId transform_id, PerformanceSampleUnit unit, bool log_only, double sample, const std::string& name)> OnPerformanceSample; + typedef std::function<void(std::optional<FrameNumber> frame_number, std::optional<FrameId> frame_id, std::optional<TransformId> transform_id, Unit unit, bool log_only, double sample, const std::string& name)> OnPerformanceSample; // OnTransportError: Called in case of error or disconnect. After that the transport method transitions to the error state typedef std::function<void()> OnTransportError; diff --git a/src/transport/udp_packet.cpp b/src/transport/udp_packet.cpp index b7bb3764e26b590a5b718120a0b78c53acf9b092..e47e4272aabffc6bce83fe19628ac83dfe2c92ba 100644 --- a/src/transport/udp_packet.cpp +++ b/src/transport/udp_packet.cpp @@ -100,30 +100,30 @@ bool convert_udp_button_state(UDPButtonState udp_button_state, ButtonState& butt return true; } -bool convert_udp_performance_sample_unit(UDPPerformanceSampleUnit udp_unit, PerformanceSampleUnit& unit) +bool convert_udp_performance_sample_unit(UDPPerformanceSampleUnit udp_unit, Unit& unit) { switch (udp_unit) { case UDP_PERFORMANCE_SAMPLE_UNIT_UNDEFINED: - unit = PERFORMANCE_SAMPLE_UNIT_UNDEFINED; + unit = UNIT_UNDEFINED; break; case UDP_PERFORMANCE_SAMPLE_UNIT_BITS: - unit = PERFORMANCE_SAMPLE_UNIT_BITS; + unit = UNIT_BITS; break; case UDP_PERFORMANCE_SAMPLE_UNIT_BYTES: - unit = PERFORMANCE_SAMPLE_UNIT_BYTES; + unit = UNIT_BYTES; break; case UDP_PERFORMANCE_SAMPLE_UNIT_SECONDS: - unit = PERFORMANCE_SAMPLE_UNIT_SECONDS; + unit = UNIT_SECONDS; break; case UDP_PERFORMANCE_SAMPLE_UNIT_MILLISECONDS: - unit = PERFORMANCE_SAMPLE_UNIT_MILLISECONDS; + unit = UNIT_MILLISECONDS; break; case UDP_PERFORMANCE_SAMPLE_UNIT_MBITS_PER_SECOND: - unit = PERFORMANCE_SAMPLE_UNIT_MBITS_PER_SECOND; + unit = UNIT_MBITS_PER_SECOND; break; case UDP_PERFORMANCE_SAMPLE_UNIT_UNDEFINED_PER_SECOND: - unit = PERFORMANCE_SAMPLE_UNIT_UNDEFINED_PER_SECOND; + unit = UNIT_UNDEFINED_PER_SECOND; break; default: lava::log()->error("UDP-Transport: Unkown performance sample unit!"); diff --git a/src/transport/udp_packet.hpp b/src/transport/udp_packet.hpp index 083a7e3c7ffc7afe278827d39dbdb65fef91a8e0..39e8b6b51e0c62bfe5d175e7b85c27830eefa82e 100644 --- a/src/transport/udp_packet.hpp +++ b/src/transport/udp_packet.hpp @@ -10,6 +10,10 @@ #define UDP_PACKET_OVERLAY_GRAPH_LABEL_MAX_LENGTH 128 #define UDP_PACKET_OVERLAY_GRAPH_SAMPLE_MAX_COUNT 64 +#define UDP_UNDEFINED_FRAME_NUMBER 0xFFFFFFFF //NOTE: Only allowed for PacketPerformanceSample +#define UDP_UNDEFINED_FRAME_ID 0xFFFFFFFF //NOTE: Only allowed for PacketPerformanceSample +#define UDP_UNDEFINED_TRANSFORM_ID 0xFFFFFFFF //NOTE: Only allowed for PacketPerformanceSample + enum UDPPacketId : uint32_t { UDP_PACKET_ID_SETUP = 0x00, @@ -88,7 +92,7 @@ bool convert_udp_controller(UDPControllerId udp_controller, Controller& controll bool convert_udp_controller_state(UDPControllerState udp_controller_state, ControllerState& controller_state); bool convert_udp_button(UDPButtonId udp_button, Button& button); bool convert_udp_button_state(UDPButtonState udp_button_state, ButtonState& button_state); -bool convert_udp_performance_sample_unit(UDPPerformanceSampleUnit udp_unit, PerformanceSampleUnit& unit); +bool convert_udp_performance_sample_unit(UDPPerformanceSampleUnit udp_unit, Unit& unit); //All packages need to be smaller than 512, otherwise the package needs to be fragmented. //In case of an fragmented package, all parts of it are gathered and sorted based on the information provide in the packet. diff --git a/src/transport/udp_transport.cpp b/src/transport/udp_transport.cpp index 32e7ba2a88b1fee0755309fb389c4a5d5a60b797..f71f7969cb1c8a43626aea1888f44319c352306b 100644 --- a/src/transport/udp_transport.cpp +++ b/src/transport/udp_transport.cpp @@ -993,7 +993,26 @@ void UDPTransport::parse_performance_sample(const Datagram& datagram) return; } - PerformanceSampleUnit unit; + std::optional<FrameNumber> frame_number; + std::optional<FrameId> frame_id; + std::optional<TransformId> transform_id; + + if (packet->frame_number != UDP_UNDEFINED_FRAME_NUMBER) + { + frame_number = packet->frame_number; + } + + if (packet->frame_id != UDP_UNDEFINED_FRAME_ID) + { + frame_id = packet->frame_id; + } + + if (packet->transform_id != UDP_UNDEFINED_TRANSFORM_ID) + { + transform_id = packet->transform_id; + } + + Unit unit; if (!convert_udp_performance_sample_unit(packet->unit, unit)) { @@ -1006,5 +1025,5 @@ void UDPTransport::parse_performance_sample(const Datagram& datagram) uint32_t name_length = strnlen((const char*)packet->name, sizeof(packet->name)); std::string name((const char*)packet->name, name_length); - this->on_performance_sample(packet->frame_number, packet->frame_id, packet->transform_id, unit, packet->log_only, packet->sample, name); + this->on_performance_sample(frame_number, frame_id, transform_id, unit, packet->log_only, packet->sample, name); } \ No newline at end of file diff --git a/src/types.hpp b/src/types.hpp index a56459342b19eaa1239c4843aa77e1b8b298fcc7..ff03377e3259608af048cf337961abc0d8bb1e6c 100644 --- a/src/types.hpp +++ b/src/types.hpp @@ -49,13 +49,26 @@ enum RemoteStrategy REMOTE_STRATEGY_NATIVE }; -enum PerformanceSampleUnit +enum Unit { - PERFORMANCE_SAMPLE_UNIT_UNDEFINED, - PERFORMANCE_SAMPLE_UNIT_BITS, - PERFORMANCE_SAMPLE_UNIT_BYTES, - PERFORMANCE_SAMPLE_UNIT_SECONDS, - PERFORMANCE_SAMPLE_UNIT_MILLISECONDS, - PERFORMANCE_SAMPLE_UNIT_MBITS_PER_SECOND, - PERFORMANCE_SAMPLE_UNIT_UNDEFINED_PER_SECOND + UNIT_UNDEFINED, + UNIT_HOURS, + UNIT_MINUTES, + UNIT_SECONDS, + UNIT_MILLISECONDS, + UNIT_MIRCOSECONDS, + UNIT_BITS, + UNIT_KBITS, //NOTE: Base 1000 + UNIT_MBITS, //NOTE: Base 1000 + UNIT_KIBITS, //NOTE: Base 1024 + UNIT_MIBITS, //NOTE: Base 1024 + UNIT_BYTES, + UNIT_KBYTES, //NOTE: Base 1000 + UNIT_MBYTES, //NOTE: Base 1000 + UNIT_KIBYTES, //NOTE: Base 1024 + UNIT_MIBYTES, //NOTE: Base 1024 + UNIT_UNDEFINED_PER_SECOND, + UNIT_BITS_PER_SECOND, + UNIT_KBITS_PER_SECOND, //NOTE: Base 1000 + UNIT_MBITS_PER_SECOND, //NOTE: Base 1000 }; \ No newline at end of file diff --git a/src/utility/latency.cpp b/src/utility/latency.cpp index 83b7a2deb766fe0fafb348323ed3e208e84b2702..87d9f3a0e2586912eeb5a24e32091d025c87e2aa 100644 --- a/src/utility/latency.cpp +++ b/src/utility/latency.cpp @@ -6,20 +6,20 @@ LatencyStatistic::LatencyStatistic(const std::string& base_directory) : base_directory(base_directory) { - this->statistic_total_latency = make_statistic("Total Latency", 128); - this->statistic_server_response = make_statistic("Server Response", 128); - this->statistic_frame_decoded = make_statistic("Frame Decoded", 128); - this->statistic_command_construct = make_statistic("Command Construct", 128); - this->statistic_command_executed = make_statistic("Command Executed", 128); + this->statistic_total_latency = make_statistic("Total Latency", UNIT_MILLISECONDS); + this->statistic_server_response = make_statistic("Server Response", UNIT_MILLISECONDS); + this->statistic_frame_decoded = make_statistic("Frame Decoded", UNIT_MILLISECONDS); + this->statistic_command_construct = make_statistic("Command Construct", UNIT_MILLISECONDS); + this->statistic_command_executed = make_statistic("Command Executed", UNIT_MILLISECONDS); } void LatencyStatistic::interface() { - this->statistic_total_latency->interface(); - this->statistic_server_response->interface(); - this->statistic_frame_decoded->interface(); - this->statistic_command_construct->interface(); - this->statistic_command_executed->interface(); + this->statistic_total_latency->interface(128); + this->statistic_server_response->interface(128); + this->statistic_frame_decoded->interface(128); + this->statistic_command_construct->interface(128); + this->statistic_command_executed->interface(128); } bool LatencyStatistic::write_to_file() diff --git a/src/utility/pass_timer.cpp b/src/utility/pass_timer.cpp index c13b0f3ca5c2bfeb74e108c632c499bbded4766d..4f9fe8bc50196e9bce66f589fd16bc6fb2d2b82a 100644 --- a/src/utility/pass_timer.cpp +++ b/src/utility/pass_timer.cpp @@ -72,7 +72,7 @@ void PassTimer::interface() { for (Statistic::Ptr statistic : this->pass_statistcs) { - statistic->interface(); + statistic->interface(128); } } @@ -287,7 +287,7 @@ bool PassTimer::evaluate_wating_passes(lava::device_ptr device, lava::index fram if (!found) { - Statistic::Ptr statistic = make_statistic(pass_statistic.name, 128); + Statistic::Ptr statistic = make_statistic(pass_statistic.name, UNIT_MILLISECONDS); statistic->add_sample(pass_statistic.delta_time); this->pass_statistcs.push_back(statistic); diff --git a/src/utility/statistic.cpp b/src/utility/statistic.cpp index 5a272aaec758e86d2952d00d23e57f5f6f338447..be57146bc3c8092c1d1fcabdec26cef07ac95e03 100644 --- a/src/utility/statistic.cpp +++ b/src/utility/statistic.cpp @@ -1,105 +1,246 @@ #include "statistic.hpp" -#include <array> +#include <liblava/lava.hpp> +#include <glm/glm.hpp> #include <imgui.h> -Statistic::Statistic(const std::string& name, uint32_t sample_count) : name(name) +#include <filesystem> +#include <fstream> + +bool StatisticKey::operator<(const StatisticKey& key) const { - this->samples.resize(sample_count, 0.0f); + if (this->frame_number == key.frame_number) + { + return this->transform_id < key.transform_id; + } + + return this->frame_number < key.frame_number; } -void Statistic::interface() +Statistic::Statistic(const std::string& name, Unit unit, bool log_only) : name(name), unit(unit), log_only(log_only) { - std::string plot_name = this->name; + +} - for (uint32_t index = 0; index < plot_name.size(); index++) - { - if (index == 0) - { - plot_name[index] = std::toupper(plot_name[index]); - } +void Statistic::interface(uint32_t sample_count) +{ + std::string plot_name = this->get_label_name(); - else if (plot_name[index - 1] == '_') - { - plot_name[index - 1] = ' '; - plot_name[index] = std::toupper(plot_name[index]); - } + uint32_t plot_length = glm::min(sample_count, (uint32_t)this->samples.size()); + uint32_t plot_src_offset = this->samples.size() - plot_length; + uint32_t plot_dst_offset = sample_count - plot_length; + + std::vector<float> plot_samples; + plot_samples.resize(sample_count, 0.0f); + + for (uint32_t index = 0; index < plot_length; index++) + { + plot_samples[plot_dst_offset + index] = this->samples[plot_src_offset + index].value; } - ImGui::PlotLines(plot_name.c_str(), this->samples.data(), this->samples.size(), this->current_index, nullptr, 0.0f, this->get_max(), ImVec2(0, 64)); + ImGui::PlotLines(plot_name.c_str(), plot_samples.data(), plot_samples.size(), 0, nullptr, 0.0f, this->get_max(sample_count), ImVec2(0, 64)); ImVec2 cursor = ImGui::GetCursorPos(); ImGui::SetWindowFontScale(0.7f); ImGui::SetCursorPosX(cursor.x + 5); ImGui::SetCursorPosY(cursor.y - 22.0f); - ImGui::Text("Avg: %8.4f", this->get_average()); + ImGui::Text("Avg: %8.4f", this->get_average(sample_count)); ImGui::SetCursorPosX(cursor.x + 90); ImGui::SetCursorPosY(cursor.y - 22.0f); - ImGui::Text("Min: %8.4f", this->get_min()); + ImGui::Text("Min: %8.4f", this->get_min(sample_count)); ImGui::SetCursorPosX(cursor.x + 175); ImGui::SetCursorPosY(cursor.y - 22.0f); - ImGui::Text("Max: %8.4f", this->get_max()); + ImGui::Text("Max: %8.4f", this->get_max(sample_count)); ImGui::SetCursorPos(cursor); ImGui::SetWindowFontScale(1.0); } -void Statistic::add_sample(float sample) +void Statistic::add_sample(float value) { - this->samples[this->current_index] = sample; - this->valid_count = std::max(this->valid_count, this->current_index + 1); - this->current_index = (this->current_index + 1) % this->samples.size(); + StatisticSample sample; + sample.value = value; + + this->samples.push_back(sample); } -float Statistic::get_average() const +void Statistic::add_sample(const StatisticSample& sample) { - if (this->valid_count <= 0) + this->samples.push_back(sample); +} + +double Statistic::get_average() const +{ + return this->get_average(this->samples.size()); +} + +double Statistic::get_average(uint32_t sample_count) const +{ + uint32_t count = glm::min(sample_count, (uint32_t)this->samples.size()); + + if (count <= 0) { return 0.0f; } - float sample_sum = 0.0f; + uint32_t offset = this->samples.size() - count; + double sum = 0.0f; - for (uint32_t index = 0; index < this->valid_count; index++) + for (uint32_t index = offset; index < this->samples.size(); index++) { - sample_sum += this->samples[index]; + sum += this->samples[index].value; } - return sample_sum / (float)this->valid_count; + return sum / (double)count; } -float Statistic::get_min() const +double Statistic::get_min() const { - if (this->valid_count <= 0) + return this->get_min(this->samples.size()); +} + +double Statistic::get_min(uint32_t sample_count) const +{ + uint32_t count = glm::min(sample_count, (uint32_t)this->samples.size()); + + if (count <= 0) { return 0.0f; } - float sample_min = this->samples[0]; + uint32_t offset = this->samples.size() - count; + double min = this->samples[offset].value; - for (uint32_t index = 0; index < this->valid_count; index++) + for (uint32_t index = offset + 1; index < this->samples.size(); index++) { - sample_min = std::min(sample_min, this->samples[index]); + min = glm::min(min, this->samples[index].value); } - return sample_min; + return min; } -float Statistic::get_max() const +double Statistic::get_max() const { - if (this->valid_count <= 0) + return this->get_max(this->samples.size()); +} + +double Statistic::get_max(uint32_t sample_count) const +{ + uint32_t count = glm::min(sample_count, (uint32_t)this->samples.size()); + + if (count <= 0) { return 0.0f; } - float sample_max = this->samples[0]; + uint32_t offset = this->samples.size() - count; + double max = this->samples[offset].value; + + for (uint32_t index = offset + 1; index < this->samples.size(); index++) + { + max = glm::max(max, this->samples[index].value); + } + + return max; +} + +std::vector<float> Statistic::get_sample_values(uint32_t sample_count) const +{ + std::vector<float> sample_values; + sample_values.reserve(sample_count); - for (uint32_t index = 0; index < this->valid_count; index++) + uint32_t count = glm::min(sample_count, (uint32_t) this->samples.size()); + uint32_t offset = this->samples.size() - count; + + for (uint32_t index = offset; index < this->samples.size(); index++) { - sample_max = std::max(sample_max, this->samples[index]); + sample_values.push_back(this->samples[index].value); } - return sample_max; + return sample_values; +} + +std::string Statistic::get_label_name() const +{ + return this->get_display_name() + " [" + this->get_unit_name() + "]"; +} + +std::string Statistic::get_display_name() const +{ + std::string display_name = this->name; + + for (uint32_t index = 0; index < display_name.size(); index++) + { + if (index == 0) + { + display_name[index] = std::toupper(display_name[index]); + } + + else if (display_name[index - 1] == '_') + { + display_name[index - 1] = ' '; + display_name[index] = std::toupper(display_name[index]); + } + } + + return display_name; +} + +std::string Statistic::get_unit_name() const +{ + switch (this->unit) + { + case UNIT_UNDEFINED: + return "undef"; + case UNIT_HOURS: + return "h"; + case UNIT_MINUTES: + return "min"; + case UNIT_SECONDS: + return "s"; + case UNIT_MILLISECONDS: + return "ms"; + case UNIT_MIRCOSECONDS: + return "us"; + case UNIT_BITS: + return "bit"; + case UNIT_KBITS: + return "kbit"; + case UNIT_MBITS: + return "Mbit"; + case UNIT_KIBITS: + return "Kibit"; + case UNIT_MIBITS: + return "Mibit"; + case UNIT_BYTES: + return "B"; + case UNIT_KBYTES: + return "kB"; + case UNIT_MBYTES: + return "MB"; + case UNIT_KIBYTES: + return "KiB"; + case UNIT_MIBYTES: + return "MiB"; + case UNIT_UNDEFINED_PER_SECOND: + return "undef/s"; + case UNIT_BITS_PER_SECOND: + return "bit/s"; + case UNIT_KBITS_PER_SECOND: + return "kbit/s"; + case UNIT_MBITS_PER_SECOND: + return "Mbit/s"; + default: + lava::log()->error("Unkown unit!"); + break; + } + + return "undef"; +} + +const std::vector<StatisticSample>& Statistic::get_samples() const +{ + return this->samples; } const std::string& Statistic::get_name() const @@ -107,34 +248,247 @@ const std::string& Statistic::get_name() const return this->name; } -const std::vector<float>& Statistic::get_samples() const +Unit Statistic::get_unit() const { - return this->samples; + return this->unit; } -std::vector<float> Statistic::get_samples_ordered() const +bool Statistic::is_log_only() const { - std::vector<float> samples_ordered; - samples_ordered.resize(this->valid_count, 0.0f); + return this->log_only; +} + +bool StatisticLog::write(const std::string& directory) +{ + if (!std::filesystem::exists(directory)) + { + std::filesystem::create_directory(directory); + } + + std::string file_name = this->build_file_name(); + + std::fstream file; + file.open(directory + "/" + file_name, std::ios::out); + + if (!file.good()) + { + lava::log()->error("Can't write to file '" + file_name + "' for statistic!"); + + return false; + } + + std::vector<std::string> ordered_sample_labels; + std::map<StatisticKey, std::vector<std::optional<double>>> ordered_samples; + + std::vector<std::string> unordered_sample_labels; + std::vector<std::vector<double>> unordered_samples; + + for (const Statistic::Ptr& statistic : this->statistic_list) + { + for (const StatisticSample& sample : statistic->get_samples()) + { + std::string label; + + if (sample.frame_id.has_value()) + { + label = statistic->get_name() + "_" + std::to_string(sample.frame_id.value()) + " [" + statistic->get_unit_name() + "]"; + } + + else + { + label = statistic->get_name() + " [" + statistic->get_unit_name() + "]"; + } + + bool ordered = sample.frame_number.has_value() || sample.transform_id.has_value(); + + if (ordered) + { + StatisticKey key; + key.frame_number = sample.frame_number.value_or(0); + key.transform_id = sample.transform_id.value_or(0); + + uint32_t column_index = 0; + bool column_found = false; + + for (uint32_t index = 0; index < ordered_sample_labels.size(); index++) + { + if (ordered_sample_labels[index] == label) + { + column_index = index; + column_found = true; + + break; + } + } + + if (!column_found) + { + column_index = ordered_sample_labels.size(); + ordered_sample_labels.push_back(label); + } + + std::vector<std::optional<double>>& row = ordered_samples[key]; + + if (row.size() <= column_index) + { + row.resize(column_index + 1); + } + + row[column_index] = sample.value; + } + + else + { + uint32_t column_index = 0; + bool column_found = false; + + for (uint32_t index = 0; index < unordered_sample_labels.size(); index++) + { + if (unordered_sample_labels[index] == label) + { + column_index = index; + column_found = true; + + break; + } + } + + if (!column_found) + { + column_index = unordered_sample_labels.size(); + unordered_sample_labels.push_back(label); + } + + if (unordered_samples.size() <= column_index) + { + unordered_samples.resize(column_index + 1); + } + + unordered_samples[column_index].push_back(sample.value); + } + } + } + + file << "frame_number"; + file << "; transform_id"; + + for (const std::string& label : ordered_sample_labels) + { + file << "; " << label; + } + + if (!ordered_sample_labels.empty() && !unordered_sample_labels.empty()) + { + file << "; "; + } - if (this->valid_count < this->samples.size()) + for (const std::string& label : unordered_sample_labels) { - memcpy(samples_ordered.data(), this->samples.data(), this->valid_count * sizeof(float)); + file << "; " << label; } - else + file << std::endl; + file << std::fixed << std::setprecision(3); + + std::map<StatisticKey, std::vector<std::optional<double>>>::iterator row_iter = ordered_samples.begin(); + uint32_t row_index = 0; + bool row_added = true; + + while (row_added) { - uint32_t count1 = this->samples.size() - this->current_index; - uint32_t count2 = this->current_index; + row_added = false; + + if (row_iter != ordered_samples.end()) + { + const StatisticKey& key = row_iter->first; + const std::vector<std::optional<double>>& row = row_iter->second; + + file << key.frame_number << "; " << key.transform_id; + + for (uint32_t index = 0; index < row.size(); index++) + { + file << "; "; + + if (row[index].has_value()) + { + file << row[index].value(); + } + } + + for (uint32_t index = 0; index < (ordered_sample_labels.size() - row.size()); index++) + { + file << "; "; + } - memcpy(samples_ordered.data(), this->samples.data() + this->current_index, count1 * sizeof(float)); - memcpy(samples_ordered.data() + count1, this->samples.data(), count2 * sizeof(float)); + row_added = true; + row_iter++; + } + + else + { + file << " ; "; + + for (uint32_t index = 0; index < ordered_sample_labels.size(); index++) + { + file << "; "; + } + } + + if (!ordered_sample_labels.empty() && !unordered_sample_labels.empty()) + { + file << "; "; + } + + for (const std::vector<double>& column : unordered_samples) + { + file << "; "; + + if (row_index < column.size()) + { + file << column[row_index]; + + row_added = true; + } + } + + row_index++; + + file << std::endl; } - return samples_ordered; + file.close(); + + return true; +} + +void StatisticLog::add_statistic(Statistic::Ptr statistic) +{ + this->statistic_list.push_back(statistic); +} + +std::string StatisticLog::build_file_name() +{ + time_t system_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + tm local_time = *localtime(&system_time); + + std::string name = "statistic"; + name += "_" + std::to_string(local_time.tm_mday); + name += "-" + std::to_string(local_time.tm_mon + 1); + name += "-" + std::to_string(local_time.tm_year + 1900); + name += "_" + std::to_string(local_time.tm_hour); + name += "-" + std::to_string(local_time.tm_min); + name += "-" + std::to_string(local_time.tm_sec); + name += ".csv"; + + return name; +} + +Statistic::Ptr make_statistic(const std::string& name, Unit unit, bool log_only) +{ + return std::make_shared<Statistic>(name, unit, log_only); } -Statistic::Ptr make_statistic(const std::string& name, uint32_t sample_count) +StatisticLog::Ptr make_statistic_log() { - return std::make_shared<Statistic>(name, sample_count); + return std::make_shared<StatisticLog>(); } \ No newline at end of file diff --git a/src/utility/statistic.hpp b/src/utility/statistic.hpp index 23b57be95703c8fa2e5d5d6295eea8e299fbf762..27dfde52dfa8329a324e1cb80a7083e1901a5e1d 100644 --- a/src/utility/statistic.hpp +++ b/src/utility/statistic.hpp @@ -12,8 +12,32 @@ #include <memory> #include <string> #include <vector> +#include <optional> #include <cstdint> +#include "types.hpp" + +struct StatisticSample +{ + double value; + + std::optional<FrameNumber> frame_number; + std::optional<FrameId> frame_id; + std::optional<TransformId> transform_id; +}; + +struct StatisticKey +{ +public: + FrameNumber frame_number; + TransformId transform_id; + +public: + StatisticKey() = default; + + bool operator<(const StatisticKey& key) const; +}; + class Statistic { public: @@ -21,25 +45,57 @@ public: private: std::string name; - std::vector<float> samples; + Unit unit; + bool log_only; - uint32_t current_index = 0; - uint32_t valid_count = 0; + std::vector<StatisticSample> samples; public: - Statistic(const std::string& name, uint32_t sample_count); + Statistic(const std::string& name, Unit unit, bool log_only); + + void interface(uint32_t sample_count); + + void add_sample(float value); + void add_sample(const StatisticSample& sample); - void interface(); + double get_average() const; + double get_average(uint32_t sample_count) const; + double get_min() const; + double get_min(uint32_t sample_count) const; + double get_max() const; + double get_max(uint32_t sample_count) const; - void add_sample(float sample); + std::vector<float> get_sample_values(uint32_t sample_count) const; - float get_average() const; - float get_min() const; - float get_max() const; + std::string get_label_name() const; + std::string get_display_name() const; + std::string get_unit_name() const; + const std::vector<StatisticSample>& get_samples() const; const std::string& get_name() const; - const std::vector<float>& get_samples() const; - std::vector<float> get_samples_ordered() const; + Unit get_unit() const; + + bool is_log_only() const; +}; + +class StatisticLog +{ +public: + typedef std::shared_ptr<StatisticLog> Ptr; + +public: + StatisticLog() = default; + + bool write(const std::string& directory); + + void add_statistic(Statistic::Ptr statistic); + +private: + std::string build_file_name(); + +private: + std::vector<Statistic::Ptr> statistic_list; }; -Statistic::Ptr make_statistic(const std::string& name, uint32_t sample_count); \ No newline at end of file +Statistic::Ptr make_statistic(const std::string& name, Unit unit, bool log_only = false); +StatisticLog::Ptr make_statistic_log(); \ No newline at end of file diff --git a/src/vr_application.cpp b/src/vr_application.cpp index e8390490848dfdca4ed66889b22dc5c530e938fc..af896acb7da712a2ae975c72779835a70d91eb42 100644 --- a/src/vr_application.cpp +++ b/src/vr_application.cpp @@ -178,6 +178,11 @@ IndirectCache::Ptr VRApplication::get_indirect_cache() const return this->indirect_cache; } +StatisticLog::Ptr VRApplication::get_statistic_log() const +{ + return this->statistic_log; +} + const CommandParser& VRApplication::get_command_parser() const { return this->command_parser; @@ -453,13 +458,20 @@ bool VRApplication::on_create() return false; } - this->frame_time = make_statistic("Frame Time", 128); + this->statistic_log = make_statistic_log(); + this->frame_time = make_statistic("frame_time", UNIT_MILLISECONDS); + this->statistic_log->add_statistic(this->frame_time); return this->create_config(); } void VRApplication::on_destroy() { + if(!this->statistic_log->write("statistic")) + { + lava::log()->error("Can't write statistic!"); + } + this->destroy_config(); if (this->headset != nullptr) @@ -587,7 +599,7 @@ bool VRApplication::on_interface() ImGui::Checkbox("Frame Capture", &capture_enabled); this->frame_capture->set_enabled(capture_enabled); - this->frame_time->interface(); + this->frame_time->interface(128); this->pass_timer->interface(); this->app->draw_about(true); diff --git a/src/vr_application.hpp b/src/vr_application.hpp index d1cd77da3371449f666df7e401c68e84cdcaeb4a..63cccf9163b9cb41c4dc0877d6c632297bd146da 100644 --- a/src/vr_application.hpp +++ b/src/vr_application.hpp @@ -46,6 +46,8 @@ public: ShadowCache::Ptr get_shadow_cache() const; IndirectCache::Ptr get_indirect_cache() const; + StatisticLog::Ptr get_statistic_log() const; + const CommandParser& get_command_parser() const; lava::device_ptr get_device() const; @@ -97,5 +99,6 @@ private: CommandParser command_parser; bool created = false; + StatisticLog::Ptr statistic_log; Statistic::Ptr frame_time; }; \ No newline at end of file