Skip to content
Snippets Groups Projects
Commit 7e86c1f9 authored by Jens Koenen's avatar Jens Koenen
Browse files

Added creation of statistics for performance samples

parent 5d863037
Branches
No related tags found
No related merge requests found
......@@ -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)
{
if (!statistic->is_log_only())
{
graph_names.push_back(graph->get_name().c_str());
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()
......
......@@ -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;
......
......@@ -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;
......
......@@ -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!");
......
......@@ -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.
......
......@@ -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
......@@ -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
......@@ -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()
......
......@@ -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);
......
#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
{
if (this->frame_number == key.frame_number)
{
this->samples.resize(sample_count, 0.0f);
return this->transform_id < key.transform_id;
}
void Statistic::interface()
{
std::string plot_name = this->name;
return this->frame_number < key.frame_number;
}
for (uint32_t index = 0; index < plot_name.size(); index++)
{
if (index == 0)
Statistic::Statistic(const std::string& name, Unit unit, bool log_only) : name(name), unit(unit), log_only(log_only)
{
plot_name[index] = std::toupper(plot_name[index]);
}
else if (plot_name[index - 1] == '_')
void Statistic::interface(uint32_t sample_count)
{
plot_name[index - 1] = ' ';
plot_name[index] = std::toupper(plot_name[index]);
}
std::string plot_name = this->get_label_name();
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 sum / (double)count;
}
return sample_sum / (float)this->valid_count;
double Statistic::get_min() const
{
return this->get_min(this->samples.size());
}
float Statistic::get_min() const
double Statistic::get_min(uint32_t sample_count) const
{
if (this->valid_count <= 0)
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 min;
}
return sample_min;
double Statistic::get_max() const
{
return this->get_max(this->samples.size());
}
float Statistic::get_max() const
double Statistic::get_max(uint32_t sample_count) const
{
if (this->valid_count <= 0)
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);
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_values.push_back(this->samples[index].value);
}
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;
}
for (uint32_t index = 0; index < this->valid_count; index++)
std::string Statistic::get_unit_name() const
{
switch (this->unit)
{
sample_max = std::max(sample_max, this->samples[index]);
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 sample_max;
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;
}
bool Statistic::is_log_only() const
{
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() + "]";
}
std::vector<float> Statistic::get_samples_ordered() const
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 << "; ";
}
for (const std::string& label : unordered_sample_labels)
{
file << "; " << label;
}
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)
{
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++)
{
std::vector<float> samples_ordered;
samples_ordered.resize(this->valid_count, 0.0f);
file << "; ";
if (this->valid_count < this->samples.size())
if (row[index].has_value())
{
memcpy(samples_ordered.data(), this->samples.data(), this->valid_count * sizeof(float));
file << row[index].value();
}
}
for (uint32_t index = 0; index < (ordered_sample_labels.size() - row.size()); index++)
{
file << "; ";
}
row_added = true;
row_iter++;
}
else
{
uint32_t count1 = this->samples.size() - this->current_index;
uint32_t count2 = this->current_index;
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;
}
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);
memcpy(samples_ordered.data(), this->samples.data() + this->current_index, count1 * sizeof(float));
memcpy(samples_ordered.data() + count1, this->samples.data(), count2 * sizeof(float));
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;
}
return samples_ordered;
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
......@@ -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
......@@ -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);
......
......@@ -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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment