diff --git a/src/Psc_Cpp_Core/Base/RingBuffer.hpp b/src/Psc_Cpp_Core/Base/RingBuffer.hpp index 3014323..e5ee665 100644 --- a/src/Psc_Cpp_Core/Base/RingBuffer.hpp +++ b/src/Psc_Cpp_Core/Base/RingBuffer.hpp @@ -6,6 +6,12 @@ #include #include namespace Psc { +template +struct Ring_Buffer_State { + Size_T capacity{}; + Size_T used{}; + Size_T free{}; +}; // -------------------- utils -------------------- template bool is_pow2(Size_T x) { @@ -53,10 +59,14 @@ public: [[nodiscard]] bool full() const { return ((tail + 1) & mask_) == head; } + [[nodiscard]] Ring_Buffer_State state() { + std::lock_guard lock(mutex_); + return {capacity, used_space_unsafe(), free_space_unsafe()}; + } [[nodiscard]] std::string state_str() { std::lock_guard lock(mutex_); - auto free_space = free_space_unsafe(); - auto used_space = used_space_unsafe(); + const auto free_space = free_space_unsafe(); + const auto used_space = used_space_unsafe(); return VAR_STR_5(free_space, used_space, head, tail, capacity); } protected: diff --git a/src/Psc_Cpp_Core/Serial/Serial_Coro.h b/src/Psc_Cpp_Core/Serial/Serial_Coro.h index 7db6def..b78ea49 100644 --- a/src/Psc_Cpp_Core/Serial/Serial_Coro.h +++ b/src/Psc_Cpp_Core/Serial/Serial_Coro.h @@ -8,37 +8,47 @@ #include #include #include +#include "Psc_Cpp_Core/Statistics/Statistics.h" #include "Serial.h" namespace Psc::serial { class Serial_Coro : public Serial { public: + Psc::Transport_Statistics statistics; bool open() { if (port_ && port_->is_open()) { return true; } + statistics.record_start_attempt(); port_ = std::make_unique(io_context_); asio::error_code ec; port_->open(normalize_port_name(serial_name), ec); if (ec) { + statistics.record_error(error_text(ec)); return false; } apply_options(ec); if (ec) { + statistics.record_error(error_text(ec)); close(); return false; } serial::set_block(port_->native_handle(), false); + statistics.record_started(); + statistics.set_receive_buffer(static_cast(std::max(buffer_byte_size, 0)), 0); return true; } void close() { if (!port_) { return; } + const auto was_open = port_->is_open(); asio::error_code ec; port_->cancel(ec); port_->close(ec); port_.reset(); io_context_.restart(); + if (was_open) + statistics.record_stopped(); } [[nodiscard]] asio::awaitable tick_coro() { tick(); @@ -75,13 +85,17 @@ private: return ""; } auto available = serial::get_available_bytes(port_->native_handle()); + statistics.set_receive_buffer(static_cast(std::max(buffer_byte_size, 0)), static_cast(std::max(available, 0))); if (available <= 0) { return ""; } auto data = serial::read_all(port_->native_handle()); if (data.size() > max_size) { + statistics.record_dropped(data.size() - max_size); data.resize(max_size); } + if (!data.empty()) + statistics.record_received(data.size()); return data; } std::size_t write_impl(std::string_view data) { @@ -91,7 +105,12 @@ private: } asio::error_code ec; auto size = asio::write(*port_, asio::buffer(data), ec); - return ec ? 0 : size; + if (ec) { + statistics.record_error(error_text(ec)); + return 0; + } + statistics.record_sent(size); + return size; } static std::string normalize_port_name(std::string_view port_name) { std::string ret(port_name); @@ -102,6 +121,9 @@ private: #endif return ret; } + static std::string error_text(const asio::error_code& ec) { + return std::string(ec.category().name()) + ":" + std::to_string(ec.value()) + " " + Psc::platform_2_utf8(ec.message()); + } void apply_options(asio::error_code& ec) { if (baud_rate != static_cast(-1)) { port_->set_option(asio::serial_port_base::baud_rate( diff --git a/src/Psc_Cpp_Core/Statistics/Statistics.h b/src/Psc_Cpp_Core/Statistics/Statistics.h index ca8556e..eb21fb8 100644 --- a/src/Psc_Cpp_Core/Statistics/Statistics.h +++ b/src/Psc_Cpp_Core/Statistics/Statistics.h @@ -1,7 +1,12 @@ #pragma once #include #include +#include #include +#include +#include +#include +#include #include "../Base/global_include.h" #include "global.h" namespace Psc { @@ -230,4 +235,190 @@ public: total = 0; } }; +class Throughput_Statistics : public Base_Statistics { +public: + Throughput_Statistics() : last_sample_ms_(get_current_ms()) { + } + void clear() { + std::lock_guard lock(mtx_); + bytes_total_ = 0; + events_total_ = 0; + window_bytes_ = 0; + window_events_ = 0; + bytes_per_second_.clear(); + events_per_second_.clear(); + bytes_per_event_.clear(); + last_sample_ms_ = get_current_ms(); + start_time = last_sample_ms_; + } + void update(std::size_t bytes) { + std::lock_guard lock(mtx_); + bytes_total_ += bytes; + ++events_total_; + window_bytes_ += bytes; + ++window_events_; + bytes_per_event_.update(static_cast(bytes)); + sample_locked(get_current_ms()); + } + [[nodiscard]] JSON to_json() const override { + std::lock_guard lock(mtx_); + sample_locked(get_current_ms()); + JSON ret = JSON::object(); + ret.append({"bytes_total", bytes_total_}); + ret.append({"events_total", events_total_}); + ret.append({"bytes_per_second", bytes_per_second_.to_json()}); + ret.append({"events_per_second", events_per_second_.to_json()}); + ret.append({"bytes_per_event", bytes_per_event_.to_json()}); + return ret; + } +private: + void sample_locked(std::uint64_t now) const { + constexpr std::uint64_t sample_period_ms = 250; + const auto elapsed_ms = now - last_sample_ms_; + if (elapsed_ms < sample_period_ms) + return; + const auto elapsed_seconds = static_cast(elapsed_ms) / 1000.0; + bytes_per_second_.update(static_cast(window_bytes_) / elapsed_seconds); + events_per_second_.update(static_cast(window_events_) / elapsed_seconds); + window_bytes_ = 0; + window_events_ = 0; + last_sample_ms_ = now; + } + mutable std::mutex mtx_; + mutable std::uint64_t bytes_total_{}; + mutable std::uint64_t events_total_{}; + mutable std::uint64_t window_bytes_{}; + mutable std::uint64_t window_events_{}; + mutable std::uint64_t last_sample_ms_{}; + mutable Value_Statistics bytes_per_second_; + mutable Value_Statistics events_per_second_; + mutable Value_Statistics bytes_per_event_; +}; +class Transport_Statistics : public Base_Statistics { +public: + void record_received(std::size_t bytes) { + received_.update(bytes); + last_activity_unix_ms_.store(get_unix_ms(), std::memory_order_relaxed); + } + void record_sent(std::size_t bytes) { + sent_.update(bytes); + last_activity_unix_ms_.store(get_unix_ms(), std::memory_order_relaxed); + } + void record_dropped(std::size_t bytes) { + dropped_.update(bytes); + last_activity_unix_ms_.store(get_unix_ms(), std::memory_order_relaxed); + } + void record_start_attempt() { + std::lock_guard lock(mtx_); + ++start_attempt_total_; + } + void record_started() { + std::lock_guard lock(mtx_); + ++start_success_total_; + ++active_sessions_; + max_active_sessions_ = std::max(max_active_sessions_, active_sessions_); + last_started_unix_ms_ = get_unix_ms(); + if (active_sessions_ == 1) + active_since_ms_ = get_current_ms(); + } + void record_stopped() { + std::lock_guard lock(mtx_); + if (active_sessions_ == 0) + return; + --active_sessions_; + ++stop_total_; + last_stopped_unix_ms_ = get_unix_ms(); + if (active_sessions_ == 0) + active_since_ms_ = 0; + } + void record_error(std::string error) { + std::lock_guard lock(mtx_); + ++error_total_; + last_error_unix_ms_ = get_unix_ms(); + last_error_ = std::move(error); + } + void set_send_buffer(std::size_t capacity, std::size_t used) { + std::lock_guard lock(mtx_); + send_buffer_capacity_ = capacity; + send_buffer_used_ = used; + send_buffer_high_water_ = std::max(send_buffer_high_water_, used); + } + void set_receive_buffer(std::size_t capacity, std::size_t used) { + std::lock_guard lock(mtx_); + receive_buffer_capacity_ = capacity; + receive_buffer_used_ = used; + receive_buffer_high_water_ = std::max(receive_buffer_high_water_, used); + } + [[nodiscard]] std::uint64_t last_activity_unix_ms() const { + return last_activity_unix_ms_.load(std::memory_order_relaxed); + } + [[nodiscard]] std::uint64_t active_sessions() const { + std::lock_guard lock(mtx_); + return active_sessions_; + } + [[nodiscard]] JSON to_json() const override { + JSON traffic = JSON::object(); + traffic.append({"received", received_.to_json()}); + traffic.append({"sent", sent_.to_json()}); + traffic.append({"dropped", dropped_.to_json()}); + std::lock_guard lock(mtx_); + JSON lifecycle = JSON::object(); + lifecycle.append({"start_attempt_total", start_attempt_total_}); + lifecycle.append({"start_success_total", start_success_total_}); + lifecycle.append({"stop_total", stop_total_}); + lifecycle.append({"error_total", error_total_}); + lifecycle.append({"active_sessions", active_sessions_}); + lifecycle.append({"max_active_sessions", max_active_sessions_}); + lifecycle.append({"active_duration_ms", active_since_ms_ == 0 ? 0 : get_current_ms() - active_since_ms_}); + lifecycle.append({"last_started_unix_ms", last_started_unix_ms_}); + lifecycle.append({"last_stopped_unix_ms", last_stopped_unix_ms_}); + lifecycle.append({"last_error_unix_ms", last_error_unix_ms_}); + lifecycle.append({"last_activity_unix_ms", last_activity_unix_ms_.load(std::memory_order_relaxed)}); + lifecycle.append({"last_error", last_error_}); + JSON buffers = JSON::object(); + buffers.append({"send", buffer_json(send_buffer_capacity_, send_buffer_used_, send_buffer_high_water_)}); + buffers.append({"receive", buffer_json(receive_buffer_capacity_, receive_buffer_used_, receive_buffer_high_water_)}); + JSON ret = JSON::object(); + ret.append({"traffic", traffic}); + ret.append({"lifecycle", lifecycle}); + ret.append({"buffers", buffers}); + return ret; + } +private: + [[nodiscard]] static std::uint64_t get_unix_ms() { + using namespace std::chrono; + return static_cast(duration_cast(system_clock::now().time_since_epoch()).count()); + } + [[nodiscard]] static JSON buffer_json(std::size_t capacity, std::size_t used, std::size_t high_water) { + JSON ret = JSON::object(); + ret.append({"capacity_bytes", capacity}); + ret.append({"used_bytes", used}); + ret.append({"free_bytes", capacity > used ? capacity - used : 0}); + ret.append({"utilization_percent", capacity == 0 ? 0.0 : static_cast(used) * 100.0 / static_cast(capacity)}); + ret.append({"high_water_bytes", high_water}); + return ret; + } + mutable std::mutex mtx_; + Throughput_Statistics received_; + Throughput_Statistics sent_; + Throughput_Statistics dropped_; + std::atomic last_activity_unix_ms_{}; + std::uint64_t start_attempt_total_{}; + std::uint64_t start_success_total_{}; + std::uint64_t stop_total_{}; + std::uint64_t error_total_{}; + std::uint64_t active_sessions_{}; + std::uint64_t max_active_sessions_{}; + std::uint64_t active_since_ms_{}; + std::uint64_t last_started_unix_ms_{}; + std::uint64_t last_stopped_unix_ms_{}; + std::uint64_t last_error_unix_ms_{}; + std::size_t send_buffer_capacity_{}; + std::size_t send_buffer_used_{}; + std::size_t send_buffer_high_water_{}; + std::size_t receive_buffer_capacity_{}; + std::size_t receive_buffer_used_{}; + std::size_t receive_buffer_high_water_{}; + std::string last_error_; +}; } // namespace Psc diff --git a/src/Psc_Cpp_Core/socket/ASIO_Utils.h b/src/Psc_Cpp_Core/socket/ASIO_Utils.h index b6eb266..8f14c52 100644 --- a/src/Psc_Cpp_Core/socket/ASIO_Utils.h +++ b/src/Psc_Cpp_Core/socket/ASIO_Utils.h @@ -1,14 +1,15 @@ #pragma once +#include #include #include "Psc_Cpp_Core/Base/RingBuffer.hpp" #include "Socket.h" #include "global.h" namespace Psc::asio_socket { #ifndef PSC_ASIO_STREAM_RING_BUFFER -#define PSC_ASIO_STREAM_RING_BUFFER StreamRingBuffer_ST +#define PSC_ASIO_STREAM_RING_BUFFER StreamRingBuffer_MT #endif #ifndef PSC_ASIO_PACKET_RING_BUFFER -#define PSC_ASIO_PACKET_RING_BUFFER RingBuffer_ST +#define PSC_ASIO_PACKET_RING_BUFFER RingBuffer_MT #endif using ASIO_StreamRingBuffer = PSC_ASIO_STREAM_RING_BUFFER; using ASIO_PacketRingBuffer = PSC_ASIO_PACKET_RING_BUFFER; @@ -26,4 +27,7 @@ endpoint_to_sockaddr(const asio::ip::udp::endpoint& endpoint) { inline bool would_block(const asio::error_code& ec) { return ec == asio::error::would_block || ec == asio::error::try_again; } +inline std::string error_text(const asio::error_code& ec) { + return std::string(ec.category().name()) + ":" + std::to_string(ec.value()) + " " + Psc::platform_2_utf8(ec.message()); +} } // namespace Psc::asio_socket diff --git a/src/Psc_Cpp_Core/socket/TCP_Client.cpp b/src/Psc_Cpp_Core/socket/TCP_Client.cpp index ccaf1d4..80ddad2 100644 --- a/src/Psc_Cpp_Core/socket/TCP_Client.cpp +++ b/src/Psc_Cpp_Core/socket/TCP_Client.cpp @@ -2,6 +2,14 @@ #include #include namespace Psc::asio_socket { +namespace { +void update_buffer_statistics(TCP_Client& client, ASIO_StreamRingBuffer& send_buffer, ASIO_StreamRingBuffer& receive_buffer) { + const auto send = send_buffer.state(); + const auto receive = receive_buffer.state(); + client.statistics.set_send_buffer(send.capacity, send.used); + client.statistics.set_receive_buffer(receive.capacity, receive.used); +} +} TCP_Client::~TCP_Client() { close(); } @@ -20,9 +28,11 @@ void TCP_Client::create() { resolver = std::make_unique(io_context); socket = std::make_unique(io_context); socket_fd = socket_id(socket.get()); + update_buffer_statistics(*this, send_buffer, recv_buffer); set_state(Reconnecting); } void TCP_Client::close() { + const bool was_connected = state == Connected; connect_pending = false; read_pending = false; write_pending = false; @@ -37,9 +47,12 @@ void TCP_Client::close() { socket->close(ec); } socket_fd = static_cast(-1); + if (was_connected) + statistics.record_stopped(); set_state(Not_Created); } void TCP_Client::schedule_recreate() { + const bool was_connected = state == Connected; connect_pending = false; read_pending = false; write_pending = false; @@ -52,6 +65,8 @@ void TCP_Client::schedule_recreate() { } next_reconnect_tp = std::chrono::steady_clock::now() + std::chrono::milliseconds(reconnect_backoff_ms); + if (was_connected) + statistics.record_stopped(); set_state(Reconnecting); } bool TCP_Client::start_connect() { @@ -65,6 +80,7 @@ bool TCP_Client::start_connect() { resolver = std::make_unique(io_context); socket = std::make_unique(io_context); connect_pending = true; + statistics.record_start_attempt(); set_state(Connecting); resolver->async_resolve( dest_address.ip, std::to_string(dest_address.port), @@ -73,6 +89,7 @@ bool TCP_Client::start_connect() { if (state != Connecting || !socket) return; if (ec) { + statistics.record_error(error_text(ec)); schedule_recreate(); return; } @@ -83,11 +100,13 @@ bool TCP_Client::start_connect() { if (state != Connecting || !socket) return; if (connect_ec) { + statistics.record_error(error_text(connect_ec)); schedule_recreate(); return; } socket_fd = socket_id(socket.get()); address = dest_address; + statistics.record_started(); set_state(Connected); start_read(); start_write(); @@ -124,11 +143,15 @@ void TCP_Client::start_read() { return; if (ec == asio::error::eof || ec == asio::error::connection_reset || ec) { + statistics.record_error(error_text(ec)); schedule_recreate(); return; } - recv_buffer.write_best_effort(recv_storage.data(), - n); + const auto written = recv_buffer.write_best_effort(recv_storage.data(), n); + statistics.record_received(n); + if (written < n) + statistics.record_dropped(n - written); + update_buffer_statistics(*this, send_buffer, recv_buffer); start_read(); }); } @@ -151,10 +174,13 @@ void TCP_Client::start_write() { if (ec == asio::error::operation_aborted) return; if (ec) { + statistics.record_error(error_text(ec)); schedule_recreate(); return; } send_buffer.skip(sent); + statistics.record_sent(sent); + update_buffer_statistics(*this, send_buffer, recv_buffer); if (sent != 0) start_write(); }); @@ -168,12 +194,16 @@ std::string TCP_Client::read() { break; ret.append(buffer.data(), n); } + update_buffer_statistics(*this, send_buffer, recv_buffer); return ret; } void TCP_Client::send(std::string_view data) { if (data.empty()) return; - send_buffer.write_best_effort(data.data(), data.size()); + const auto written = send_buffer.write_best_effort(data.data(), data.size()); + if (written < data.size()) + statistics.record_dropped(data.size() - written); + update_buffer_statistics(*this, send_buffer, recv_buffer); if (state == Connected) start_write(); } diff --git a/src/Psc_Cpp_Core/socket/TCP_Client.h b/src/Psc_Cpp_Core/socket/TCP_Client.h index 65f05d8..47c0dbb 100644 --- a/src/Psc_Cpp_Core/socket/TCP_Client.h +++ b/src/Psc_Cpp_Core/socket/TCP_Client.h @@ -7,6 +7,7 @@ #include #include #include "ASIO_Utils.h" +#include "Psc_Cpp_Core/Statistics/Statistics.h" namespace Psc::asio_socket { class TCP_Client : public Socket_Base { public: @@ -32,6 +33,7 @@ public: }; void close() override; std::atomic state{Not_Created}; + Transport_Statistics statistics; void set_state(State s); std::function on_state_change = nullptr; diff --git a/src/Psc_Cpp_Core/socket/TCP_Server.cpp b/src/Psc_Cpp_Core/socket/TCP_Server.cpp index b869e9f..27ada02 100644 --- a/src/Psc_Cpp_Core/socket/TCP_Server.cpp +++ b/src/Psc_Cpp_Core/socket/TCP_Server.cpp @@ -2,6 +2,14 @@ #include #include namespace Psc::asio_socket { +namespace { +void update_buffer_statistics(const std::shared_ptr& conn) { + const auto send = conn->send_buffer.state(); + const auto receive = conn->recv_buffer.state(); + conn->statistics.set_send_buffer(send.capacity, send.used); + conn->statistics.set_receive_buffer(receive.capacity, receive.used); +} +} TCP_Server::~TCP_Server() { close(); } @@ -47,28 +55,34 @@ bool TCP_Server::listen(const Sockaddr_In& addr) { create(); if (addr.ip.empty() || addr.port == 0) { state = Not_Set_Field; + statistics.record_error("listen address is empty"); return false; } asio::error_code ec; const auto address_value = asio::ip::make_address(addr.ip, ec); if (ec) { state = Not_bind; + statistics.record_error(error_text(ec)); return false; } asio::ip::tcp::endpoint endpoint(address_value, static_cast(addr.port)); acceptor->open(endpoint.protocol(), ec); - if (ec) + if (ec) { + statistics.record_error(error_text(ec)); return false; + } acceptor->set_option(asio::socket_base::reuse_address(true), ec); acceptor->bind(endpoint, ec); if (ec) { state = Not_bind; + statistics.record_error(error_text(ec)); return false; } acceptor->listen(asio::socket_base::max_listen_connections, ec); if (ec) { state = Not_Listen; + statistics.record_error(error_text(ec)); return false; } address = addr; @@ -84,11 +98,14 @@ void TCP_Server::close() { pending_accept_socket->close(ec); } pending_accept_socket.reset(); - for (auto& [_, conn] : tcp_clients) { + for (const auto& conn : get_all_clients()) { close_client(conn); } - tcp_clients.clear(); - accepted_clients.clear(); + { + std::lock_guard lock(clients_mutex); + tcp_clients.clear(); + accepted_clients.clear(); + } if (acceptor) { asio::error_code ec; acceptor->cancel(ec); @@ -98,6 +115,7 @@ void TCP_Server::close() { state = Not_Created; } std::shared_ptr TCP_Server::accept() const { + std::lock_guard lock(clients_mutex); while (!accepted_clients.empty()) { auto conn = accepted_clients.front().lock(); accepted_clients.erase(accepted_clients.begin()); @@ -111,6 +129,7 @@ void TCP_Server::start_accept() { return; pending_accept_socket = std::make_shared(io_context); accept_pending = true; + statistics.record_start_attempt(); acceptor->async_accept( *pending_accept_socket, [this](const asio::error_code& ec) { accept_pending = false; @@ -137,11 +156,20 @@ void TCP_Server::start_accept() { conn->info.fd = socket_id(conn->socket.get()); conn->info.sockaddr = endpoint_to_sockaddr(conn->socket->remote_endpoint(option_ec)); - tcp_clients[conn->info.fd] = conn; - accepted_clients.push_back(conn); + update_buffer_statistics(conn); + conn->statistics.record_started(); + statistics.record_started(); + { + std::lock_guard lock(clients_mutex); + tcp_clients[conn->info.fd] = conn; + accepted_clients.push_back(conn); + } start_read(conn); start_write(conn); } + else if (ec) { + statistics.record_error(error_text(ec)); + } pending_accept_socket.reset(); start_accept(); }); @@ -159,12 +187,24 @@ void TCP_Server::start_read(const std::shared_ptr& conn) { return; if (ec == asio::error::operation_aborted) return; - if (ec == asio::error::eof || ec == asio::error::connection_reset || - ec) { + if (ec == asio::error::eof) { close_client(conn); return; } - conn->recv_buffer.write_best_effort(conn->recv_storage.data(), n); + if (ec) { + conn->statistics.record_error(error_text(ec)); + statistics.record_error(error_text(ec)); + close_client(conn); + return; + } + const auto written = conn->recv_buffer.write_best_effort(conn->recv_storage.data(), n); + conn->statistics.record_received(n); + statistics.record_received(n); + if (written < n) { + conn->statistics.record_dropped(n - written); + statistics.record_dropped(n - written); + } + update_buffer_statistics(conn); start_read(conn); }); } @@ -188,20 +228,24 @@ void TCP_Server::start_write(const std::shared_ptr& conn) { if (ec == asio::error::operation_aborted) return; if (ec) { + conn->statistics.record_error(error_text(ec)); + statistics.record_error(error_text(ec)); close_client(conn); return; } conn->send_buffer.skip(sent); - conn->push_speed.update(static_cast(sent)); - conn->send_num.update(static_cast(sent)); + conn->statistics.record_sent(sent); + statistics.record_sent(sent); + update_buffer_statistics(conn); if (sent != 0) start_write(conn); }); } void TCP_Server::close_client(const std::shared_ptr& conn) { - if (!conn || conn->closing) + if (!conn || conn->closing.exchange(true)) return; - conn->closing = true; + conn->statistics.record_stopped(); + statistics.record_stopped(); if (conn->socket) { asio::error_code ec; conn->socket->cancel(ec); @@ -210,6 +254,7 @@ void TCP_Server::close_client(const std::shared_ptr& conn) { } } void TCP_Server::cleanup_closed_clients() { + std::lock_guard lock(clients_mutex); for (auto it = tcp_clients.begin(); it != tcp_clients.end();) { if (!it->second || it->second->closing || !it->second->socket || !it->second->socket->is_open()) { @@ -231,7 +276,7 @@ void TCP_Server::cleanup_closed_clients() { } void TCP_Server::flush_clients() { start_accept(); - for (auto& [_, conn] : tcp_clients) { + for (const auto& conn : get_all_clients()) { start_read(conn); start_write(conn); } @@ -239,20 +284,23 @@ void TCP_Server::flush_clients() { void TCP_Server::write_to_all_clients(std::string_view data) { if (data.empty()) return; - for (auto& [_, conn] : tcp_clients) { + for (const auto& conn : get_all_clients()) { if (!conn || conn->closing) continue; auto written = conn->send_buffer.write_best_effort(data.data(), data.size()); - if (written < data.size()) - conn->lose_speed.update(static_cast(data.size() - written)); + if (written < data.size()) { + conn->statistics.record_dropped(data.size() - written); + statistics.record_dropped(data.size() - written); + } + update_buffer_statistics(conn); start_write(conn); } } std::vector TCP_Server::read_from_all_clients() { std::vector ret; std::array buffer{}; - for (auto& [_, conn] : tcp_clients) { + for (const auto& conn : get_all_clients()) { if (!conn || conn->closing) continue; std::string data; @@ -264,12 +312,14 @@ std::vector TCP_Server::read_from_all_clients() { } if (!data.empty()) ret.push_back({conn->info, std::move(data)}); + update_buffer_statistics(conn); } cleanup_closed_clients(); return ret; } std::vector TCP_Server::client_fds() { std::vector ret; + std::lock_guard lock(clients_mutex); for (const auto& [fd, conn] : tcp_clients) { if (conn && !conn->closing) ret.push_back(fd); @@ -278,12 +328,35 @@ std::vector TCP_Server::client_fds() { } std::vector> TCP_Server::get_all_clients() { std::vector> ret; + std::lock_guard lock(clients_mutex); for (const auto& [_, conn] : tcp_clients) { if (conn && !conn->closing) ret.push_back(conn); } return ret; } +std::shared_ptr TCP_Server::get_client(Socket_FD fd) { + std::lock_guard lock(clients_mutex); + const auto iterator = tcp_clients.find(fd); + return iterator == tcp_clients.end() || !iterator->second || iterator->second->closing ? nullptr : iterator->second; +} +Psc::JSON TCP_Server::get_statistics_json() { + std::size_t send_capacity = 0; + std::size_t send_used = 0; + std::size_t receive_capacity = 0; + std::size_t receive_used = 0; + for (const auto& conn : get_all_clients()) { + const auto send = conn->send_buffer.state(); + const auto receive = conn->recv_buffer.state(); + send_capacity += send.capacity; + send_used += send.used; + receive_capacity += receive.capacity; + receive_used += receive.used; + } + statistics.set_send_buffer(send_capacity, send_used); + statistics.set_receive_buffer(receive_capacity, receive_used); + return statistics.to_json(); +} std::string TCP_Server::to_string() { return "TCP_Server:[" + (address ? address->to_string() : std::string{}) + "]"; diff --git a/src/Psc_Cpp_Core/socket/TCP_Server.h b/src/Psc_Cpp_Core/socket/TCP_Server.h index 40fd8b9..7ea5601 100644 --- a/src/Psc_Cpp_Core/socket/TCP_Server.h +++ b/src/Psc_Cpp_Core/socket/TCP_Server.h @@ -1,7 +1,9 @@ #pragma once #include +#include #include #include +#include #include #include #include "ASIO_Utils.h" @@ -13,14 +15,12 @@ public: std::shared_ptr socket; ASIO_StreamRingBuffer send_buffer; ASIO_StreamRingBuffer recv_buffer; - Speed_Statistics push_speed; - Speed_Statistics lose_speed; - Value_Statistics send_num; + Transport_Statistics statistics; std::array recv_storage{}; std::vector send_storage; - bool read_pending = false; - bool write_pending = false; - bool closing = false; + std::atomic_bool read_pending = false; + std::atomic_bool write_pending = false; + std::atomic_bool closing = false; [[nodiscard]] std::string to_string() const { return info.to_string(); } @@ -50,10 +50,13 @@ public: Not_Listen, Working }; - State state = Not_Created; + std::atomic state{Not_Created}; + Transport_Statistics statistics; std::vector read_from_all_clients(); std::vector client_fds(); std::vector> get_all_clients(); + std::shared_ptr get_client(Socket_FD fd); + Psc::JSON get_statistics_json(); void flush_clients(); [[nodiscard]] std::shared_ptr accept() const; protected: @@ -68,6 +71,7 @@ protected: bool accept_pending = false; std::shared_ptr pending_accept_socket; mutable std::vector> accepted_clients; + mutable std::mutex clients_mutex; void start_accept(); void start_read(const std::shared_ptr& conn); void start_write(const std::shared_ptr& conn); diff --git a/src/Psc_Cpp_Core/socket/UDP_Client.cpp b/src/Psc_Cpp_Core/socket/UDP_Client.cpp index 862973a..043d244 100644 --- a/src/Psc_Cpp_Core/socket/UDP_Client.cpp +++ b/src/Psc_Cpp_Core/socket/UDP_Client.cpp @@ -1,6 +1,14 @@ #include "UDP_Client.h" #include namespace Psc::asio_socket { +namespace { +void update_buffer_statistics(Transport_Statistics& statistics, ASIO_PacketRingBuffer& send_buffer, ASIO_PacketRingBuffer& receive_buffer) { + const auto send = send_buffer.state(); + const auto receive = receive_buffer.state(); + statistics.set_send_buffer(send.capacity, send.used); + statistics.set_receive_buffer(receive.capacity, receive.used); +} +} void UDP_Client::create() { socket = std::make_unique(io_context); asio::error_code ec; @@ -13,6 +21,7 @@ void UDP_Client::create() { send_storage.assign(static_cast(read_chunk_size), 0); send_buffer.init(user_buffer_size); recv_buffer.init(user_buffer_size); + update_buffer_statistics(statistics, send_buffer, recv_buffer); state = Not_Set_Field; } bool UDP_Client::connect() { @@ -22,14 +31,17 @@ bool UDP_Client::connect() { create(); if (dest_address.ip.empty() || dest_address.port == 0) { state = Not_Set_Field; + statistics.record_error("destination address is empty"); return false; } + statistics.record_start_attempt(); asio::error_code ec; endpoint = asio::ip::udp::endpoint(asio::ip::make_address(dest_address.ip, ec), static_cast(dest_address.port)); if (ec) { state = Not_Set_Field; + statistics.record_error(error_text(ec)); return false; } io_context.restart(); @@ -42,10 +54,12 @@ bool UDP_Client::connect() { if (connect_ec) { connected = false; state = Not_Set_Field; + statistics.record_error(error_text(connect_ec)); return; } connected = true; state = Working; + statistics.record_started(); start_read(); start_write(); }); @@ -57,6 +71,7 @@ std::string UDP_Client::read() { if (!recv_buffer.read(ret.data(), out_len)) return {}; ret.resize(out_len); + update_buffer_statistics(statistics, send_buffer, recv_buffer); return ret; } void UDP_Client::tick() { @@ -78,7 +93,9 @@ void UDP_Client::tick() { void UDP_Client::send(std::string_view data) { if (data.empty()) return; - send_buffer.write(data.data(), data.size()); + if (!send_buffer.write(data.data(), data.size())) + statistics.record_dropped(data.size()); + update_buffer_statistics(statistics, send_buffer, recv_buffer); if (!connected) connect(); if (connected) @@ -100,9 +117,14 @@ void UDP_Client::start_read() { if (ec) { connected = false; state = Not_Set_Field; + statistics.record_error(error_text(ec)); + statistics.record_stopped(); return; } - recv_buffer.write(recv_storage.data(), n); + statistics.record_received(n); + if (!recv_buffer.write(recv_storage.data(), n)) + statistics.record_dropped(n); + update_buffer_statistics(statistics, send_buffer, recv_buffer); start_read(); }); } @@ -117,7 +139,7 @@ void UDP_Client::start_write() { send_storage.resize(out_len); write_pending = true; socket->async_send(asio::buffer(send_storage.data(), send_storage.size()), - [this](const asio::error_code& ec, std::size_t) { + [this](const asio::error_code& ec, std::size_t sent) { write_pending = false; if (!connected) return; @@ -126,15 +148,20 @@ void UDP_Client::start_write() { if (ec) { connected = false; state = Not_Set_Field; + statistics.record_error(error_text(ec)); + statistics.record_stopped(); return; } send_buffer.skip_one(); + statistics.record_sent(sent); + update_buffer_statistics(statistics, send_buffer, recv_buffer); send_storage.assign(static_cast(read_chunk_size), 0); start_write(); }); } void UDP_Client::close() { + const bool was_connected = connected; connected = false; connect_pending = false; read_pending = false; @@ -145,6 +172,8 @@ void UDP_Client::close() { socket->close(ec); } state = Not_Created; + if (was_connected) + statistics.record_stopped(); } std::string UDP_Client::to_string() { return "UDP_Client:[" + dest_address.to_string() + "]"; diff --git a/src/Psc_Cpp_Core/socket/UDP_Client.h b/src/Psc_Cpp_Core/socket/UDP_Client.h index be03b66..76db3e7 100644 --- a/src/Psc_Cpp_Core/socket/UDP_Client.h +++ b/src/Psc_Cpp_Core/socket/UDP_Client.h @@ -1,9 +1,11 @@ #pragma once #include +#include #include #include #include #include "ASIO_Utils.h" +#include "Psc_Cpp_Core/Statistics/Statistics.h" namespace Psc::asio_socket { class UDP_Client { public: @@ -27,7 +29,8 @@ public: Connecting, Working, }; - State state = Not_Created; + std::atomic state{Not_Created}; + Transport_Statistics statistics; private: asio::io_context io_context; std::unique_ptr socket; diff --git a/src/Psc_Cpp_Core/socket/UDP_Server.cpp b/src/Psc_Cpp_Core/socket/UDP_Server.cpp index 4daea2c..8fc5998 100644 --- a/src/Psc_Cpp_Core/socket/UDP_Server.cpp +++ b/src/Psc_Cpp_Core/socket/UDP_Server.cpp @@ -2,6 +2,14 @@ #include #include namespace Psc::asio_socket { +namespace { +void update_buffer_statistics(Transport_Statistics& statistics, ASIO_PacketRingBuffer& send_buffer, ASIO_PacketRingBuffer& receive_buffer) { + const auto send = send_buffer.state(); + const auto receive = receive_buffer.state(); + statistics.set_send_buffer(send.capacity, send.used); + statistics.set_receive_buffer(receive.capacity, receive.used); +} +} void UDP_Server::create() { socket = std::make_unique(io_context); socket_fd = socket_id(socket.get()); @@ -11,6 +19,7 @@ void UDP_Server::create() { send_storage.assign(static_cast(read_chunk_size), 0); read_pending = false; write_pending = false; + update_buffer_statistics(statistics, send_buffer, recv_buffer); state = Not_Bind; } bool UDP_Server::bind() { @@ -18,19 +27,24 @@ bool UDP_Server::bind() { create(); if (bind_address.ip.empty() || bind_address.port == 0) { state = Not_Set_Field; + statistics.record_error("bind address is empty"); return false; } + statistics.record_start_attempt(); asio::error_code ec; auto address_value = asio::ip::make_address(bind_address.ip, ec); if (ec) { state = Not_Bind; + statistics.record_error(error_text(ec)); return false; } asio::ip::udp::endpoint endpoint( address_value, static_cast(bind_address.port)); socket->open(endpoint.protocol(), ec); - if (ec) + if (ec) { + statistics.record_error(error_text(ec)); return false; + } socket->set_option(asio::socket_base::reuse_address(true), ec); socket->set_option( asio::socket_base::receive_buffer_size(static_cast(buffer_size)), @@ -38,10 +52,12 @@ bool UDP_Server::bind() { socket->bind(endpoint, ec); if (ec) { state = Not_Bind; + statistics.record_error(error_text(ec)); return false; } bound = true; state = Working; + statistics.record_started(); start_read(); return true; } @@ -69,17 +85,35 @@ void UDP_Server::start_read() { return; if (ec == asio::error::operation_aborted) return; - if (ec) + if (ec) { + statistics.record_error(error_text(ec)); return; + } last_peer = endpoint_to_sockaddr(recv_endpoint); has_last_peer = true; - if (std::find(clients.begin(), clients.end(), last_peer) == - clients.end()) { - clients.push_back(last_peer); + std::shared_ptr peer_statistics; + { + std::lock_guard lock(clients_mutex); + if (std::find(clients.begin(), clients.end(), last_peer) == clients.end()) { + clients.push_back(last_peer); + peer_statistics = std::make_shared(); + peer_statistics->record_started(); + client_statistics.emplace(last_peer, peer_statistics); + } + else { + peer_statistics = client_statistics.at(last_peer); + } } + statistics.record_received(n); + peer_statistics->record_received(n); if (recv_buffer.write(recv_storage.data(), n)) { recv_peers.push_back(last_peer); } + else { + statistics.record_dropped(n); + peer_statistics->record_dropped(n); + } + update_buffer_statistics(statistics, send_buffer, recv_buffer); start_read(); }); } @@ -95,10 +129,18 @@ void UDP_Server::start_write() { return; send_storage.resize(out_len); auto endpoint = send_endpoints.front(); + const auto peer = endpoint_to_sockaddr(endpoint); + std::shared_ptr peer_statistics; + { + std::lock_guard lock(clients_mutex); + const auto iterator = client_statistics.find(peer); + if (iterator != client_statistics.end()) + peer_statistics = iterator->second; + } write_pending = true; socket->async_send_to( asio::buffer(send_storage.data(), send_storage.size()), endpoint, - [this](const asio::error_code& ec, std::size_t) { + [this, peer_statistics](const asio::error_code& ec, std::size_t sent) { write_pending = false; if (!bound) return; @@ -106,9 +148,18 @@ void UDP_Server::start_write() { return; if (!ec) { send_buffer.skip_one(); + statistics.record_sent(sent); + if (peer_statistics) + peer_statistics->record_sent(sent); if (!send_endpoints.empty()) send_endpoints.erase(send_endpoints.begin()); } + else { + statistics.record_error(error_text(ec)); + if (peer_statistics) + peer_statistics->record_error(error_text(ec)); + } + update_buffer_statistics(statistics, send_buffer, recv_buffer); send_storage.assign(static_cast(read_chunk_size), 0); start_write(); }); @@ -121,13 +172,20 @@ void UDP_Server::close() { socket->cancel(ec); socket->close(ec); } - clients.clear(); + { + std::lock_guard lock(clients_mutex); + for (const auto& [_, peer_statistics] : client_statistics) + peer_statistics->record_stopped(); + clients.clear(); + client_statistics.clear(); + } recv_peers.clear(); send_endpoints.clear(); bound = false; has_last_peer = false; socket_fd = static_cast(-1); state = Not_Created; + statistics.record_stopped(); } std::vector UDP_Server::read() { std::vector ret; @@ -139,6 +197,7 @@ std::vector UDP_Server::read() { ret.push_back({recv_peers.front(), std::string(data.data(), out_len)}); recv_peers.erase(recv_peers.begin()); } + update_buffer_statistics(statistics, send_buffer, recv_buffer); return ret; } void UDP_Server::reply_last_peer(std::string_view data) { @@ -154,16 +213,49 @@ void UDP_Server::send_to(std::string_view ip, uint16_t port, return; asio::error_code ec; auto endpoint = asio::ip::udp::endpoint(asio::ip::make_address(ip, ec), port); - if (ec) + if (ec) { + statistics.record_error(error_text(ec)); return; + } + const auto peer = Sockaddr_In(ip, port); + std::shared_ptr peer_statistics; + { + std::lock_guard lock(clients_mutex); + const auto [iterator, inserted] = client_statistics.try_emplace(peer, std::make_shared()); + peer_statistics = iterator->second; + if (inserted) { + clients.push_back(peer); + peer_statistics->record_started(); + } + } if (send_buffer.write(data.data(), data.size())) { send_endpoints.push_back(endpoint); } + else { + statistics.record_dropped(data.size()); + peer_statistics->record_dropped(data.size()); + } + update_buffer_statistics(statistics, send_buffer, recv_buffer); start_write(); } void UDP_Server::write_to_all_clients(std::string_view msg) { - for (const auto& client : clients) { - send_to(client.ip, static_cast(client.port), msg); + for (const auto& client : get_all_clients()) { + send_to(client.address.ip, static_cast(client.address.port), msg); } } +std::vector UDP_Server::get_all_clients() const { + std::vector ret; + std::lock_guard lock(clients_mutex); + ret.reserve(clients.size()); + for (const auto& client : clients) + ret.push_back({client, client_statistics.at(client)}); + return ret; +} +std::optional UDP_Server::get_client(std::size_t index) const { + std::lock_guard lock(clients_mutex); + if (index >= clients.size()) + return std::nullopt; + const auto& client = clients[index]; + return Client_Info{client, client_statistics.at(client)}; +} } // namespace Psc::asio_socket diff --git a/src/Psc_Cpp_Core/socket/UDP_Server.h b/src/Psc_Cpp_Core/socket/UDP_Server.h index 2c1d699..dca6c8a 100644 --- a/src/Psc_Cpp_Core/socket/UDP_Server.h +++ b/src/Psc_Cpp_Core/socket/UDP_Server.h @@ -1,9 +1,13 @@ #pragma once +#include #include +#include #include +#include #include #include #include "ASIO_Utils.h" +#include "Psc_Cpp_Core/Statistics/Statistics.h" namespace Psc::asio_socket { class UDP_Server { public: @@ -34,9 +38,15 @@ public: Not_Bind, Working, }; - State state = Not_Created; + std::atomic state{Not_Created}; + struct Client_Info { + Sockaddr_In address; + std::shared_ptr statistics; + }; + [[nodiscard]] std::vector get_all_clients() const; + [[nodiscard]] std::optional get_client(std::size_t index) const; + Transport_Statistics statistics; Sockaddr_In bind_address; - std::vector clients; Socket_FD socket_fd = static_cast(-1); bool bound = false; int read_chunk_size = 1024 * 1024; @@ -55,6 +65,9 @@ private: std::vector send_storage; bool read_pending = false; bool write_pending = false; + mutable std::mutex clients_mutex; + std::vector clients; + std::map> client_statistics; void start_read(); void start_write(); };