diff --git a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.cpp b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.cpp index cc0851e53d..3a3ab06d02 100644 --- a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.cpp @@ -22,6 +22,7 @@ namespace AzNetworking const AZ::TimeMs deltaTimeMs = currentTimeMs - m_lastLoggedTimeMs; m_atoms[m_activeAtom].m_bytesTransmitted += byteCount; + m_atoms[m_activeAtom].m_packetsSent++; m_atoms[m_activeAtom].m_timeAccumulatorMs += deltaTimeMs; if (m_atoms[m_activeAtom].m_timeAccumulatorMs >= m_maxSampleTimeMs) @@ -32,6 +33,11 @@ namespace AzNetworking m_lastLoggedTimeMs = currentTimeMs; } + void DatarateMetrics::LogPacketLost() + { + m_atoms[m_activeAtom].m_packetsLost++; + } + float DatarateMetrics::GetBytesPerSecond() const { const uint32_t sampleAtom = 1 - m_activeAtom; @@ -47,6 +53,18 @@ namespace AzNetworking return (bytesLogged * 1000.0f) / sampleTime; // (* 1000) to convert from bytes per millisecond to bytes per second } + float DatarateMetrics::GetLossRatePercent() const + { + const uint32_t sampleAtom = 1 - m_activeAtom; + + if (m_atoms[sampleAtom].m_packetsSent == 0) + { + return 0.0f; + } + + return float(m_atoms[sampleAtom].m_packetsLost) / float(m_atoms[sampleAtom].m_packetsSent); + } + void ConnectionComputeRtt::LogPacketSent(PacketId packetId, AZ::TimeMs currentTimeMs) { for (uint32_t i = 0; i < MaxTrackableEntries; i++) diff --git a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.h b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.h index c2227b07b6..b576c64e86 100644 --- a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.h +++ b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.h @@ -19,8 +19,10 @@ namespace AzNetworking { DatarateAtom() = default; + AZ::TimeMs m_timeAccumulatorMs = AZ::TimeMs{ 0 }; uint32_t m_bytesTransmitted = 0; - AZ::TimeMs m_timeAccumulatorMs = AZ::TimeMs{0}; + uint32_t m_packetsSent = 0; + uint32_t m_packetsLost = 0; }; //! @class DatarateMetrics @@ -40,19 +42,26 @@ namespace AzNetworking //! @param currentTimeMs current process time in milliseconds void LogPacket(uint32_t byteCount, AZ::TimeMs currentTimeMs); + //! Invoked whenever a packet has determined to be lost. + void LogPacketLost(); + //! Retrieve a sample of the datarate being incurred by this connection in bytes per second. //! @return datarate for traffic sent to or from the connection in bytes per second float GetBytesPerSecond() const; + //! Returns the estimated packet loss rate as a percentage of packets. + //! @return the estimated percentage loss rate + float GetLossRatePercent() const; + private: //! Used internally to swap buffers used for metric gathering. void SwapBuffers(); - static constexpr AZ::TimeMs MaxSampleTimeMs = AZ::TimeMs{500}; + static constexpr AZ::TimeMs MaxSampleTimeMs = AZ::TimeMs{ 2000 }; - AZ::TimeMs m_maxSampleTimeMs = MaxSampleTimeMs; - AZ::TimeMs m_lastLoggedTimeMs = MaxSampleTimeMs; + AZ::TimeMs m_maxSampleTimeMs = MaxSampleTimeMs; + AZ::TimeMs m_lastLoggedTimeMs = MaxSampleTimeMs; uint32_t m_activeAtom = 0; DatarateAtom m_atoms[2]; }; @@ -69,7 +78,7 @@ namespace AzNetworking ConnectionPacketEntry(PacketId packetId, AZ::TimeMs sendTimeMs); PacketId m_packetId = InvalidPacketId; - AZ::TimeMs m_sendTimeMs = AZ::TimeMs{0}; + AZ::TimeMs m_sendTimeMs = AZ::TimeMs{0}; }; //! @class ConnectionComputeRtt @@ -100,8 +109,8 @@ namespace AzNetworking private: - static constexpr uint32_t MaxTrackableEntries = 4; - static constexpr float InitialRoundTripTime = 0.1f; //< Start off with a 100 millisecond estimate for Rtt + static constexpr uint32_t MaxTrackableEntries = 8; + static constexpr float InitialRoundTripTime = 0.1f; //< Start off with a 100 millisecond estimate for Rtt float m_roundTripTime = InitialRoundTripTime; ConnectionPacketEntry m_entries[MaxTrackableEntries]; @@ -117,6 +126,11 @@ namespace AzNetworking //! Resets all internal metrics to defaults. void Reset(); + void LogPacketSent(uint32_t byteCount, AZ::TimeMs currentTimeMs); + void LogPacketRecv(uint32_t byteCount, AZ::TimeMs currentTimeMs); + void LogPacketLost(); + void LogPacketAcked(); + uint32_t m_packetsSent = 0; uint32_t m_packetsRecv = 0; uint32_t m_packetsLost = 0; diff --git a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.inl b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.inl index 5d7d18f709..5f196c4ed1 100644 --- a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.inl +++ b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.inl @@ -40,4 +40,33 @@ namespace AzNetworking { *this = ConnectionMetrics(); } + + inline void ConnectionMetrics::LogPacketSent(uint32_t byteCount, AZ::TimeMs currentTimeMs) + { + if (byteCount > 0) + { + m_packetsSent++; + } + m_sendDatarate.LogPacket(byteCount, currentTimeMs); + } + + inline void ConnectionMetrics::LogPacketRecv(uint32_t byteCount, AZ::TimeMs currentTimeMs) + { + if (byteCount > 0) + { + m_packetsRecv++; + } + m_recvDatarate.LogPacket(byteCount, currentTimeMs); + } + + inline void ConnectionMetrics::LogPacketLost() + { + m_packetsLost++; + m_sendDatarate.LogPacketLost(); + } + + inline void ConnectionMetrics::LogPacketAcked() + { + m_packetsAcked++; + } } diff --git a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/IConnection.h b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/IConnection.h index 1034f17585..363fd1d37b 100644 --- a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/IConnection.h +++ b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/IConnection.h @@ -95,11 +95,6 @@ namespace AzNetworking //! @return the max transmission unit for this connection virtual uint32_t GetConnectionMtu() const = 0; - //! Sets connection quality values for testing poor connection conditions. - //! Currently unsupported on TcpConnections - //! @param connectionQuality simulated connection quality values to use - virtual void SetConnectionQuality(const ConnectionQuality& connectionQuality) = 0; - //! Returns the connection identifier for this connection instance. //! @return the connection identifier for this connection instance ConnectionId GetConnectionId() const; @@ -128,12 +123,23 @@ namespace AzNetworking //! @return reference to the connection metric info ConnectionMetrics& GetMetrics(); + //! Retrieves debug connection quality settings. + //! Currently unsupported on TcpConnections + //! @return connection quality structure for this connection + const ConnectionQuality& GetConnectionQuality() const; + + //! Retrieves debug connection quality settings, non-const. + //! Currently unsupported on TcpConnections + //! @return connection quality structure for this connection + ConnectionQuality& GetConnectionQuality(); + private: // The following data members are here in the interface for performance reasons ConnectionId m_connectionId = InvalidConnectionId; IpAddress m_remoteAddress; ConnectionMetrics m_connectionMetrics; + ConnectionQuality m_connectionQuality; void* m_userData = nullptr; }; } diff --git a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/IConnection.inl b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/IConnection.inl index 646afac8f0..61e92010c7 100644 --- a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/IConnection.inl +++ b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/IConnection.inl @@ -59,4 +59,14 @@ namespace AzNetworking { return m_connectionMetrics; } + + inline const ConnectionQuality& IConnection::GetConnectionQuality() const + { + return m_connectionQuality; + } + + inline ConnectionQuality& IConnection::GetConnectionQuality() + { + return m_connectionQuality; + } } diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.cpp index ea746b9d00..1beb83e19f 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.cpp @@ -122,7 +122,7 @@ namespace AzNetworking bool TcpConnection::UpdateRecv() { const AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs(); - GetMetrics().m_recvDatarate.LogPacket(0, startTimeMs); + GetMetrics().LogPacketRecv(0, startTimeMs); // Read new data off the input socket { @@ -261,11 +261,6 @@ namespace AzNetworking return 0; // do nothing, unsupported on TCP connections } - void TcpConnection::SetConnectionQuality([[maybe_unused]] const ConnectionQuality& connectionQuality) - { - ; // do nothing, unsupported on TCP connections - } - bool TcpConnection::SendPacketInternal(PacketType packetType, TcpPacketEncodingBuffer& payloadBuffer, AZ::TimeMs currentTimeMs) { AZ_Assert(payloadBuffer.GetCapacity() < AZStd::numeric_limits::max(), "Buffer capacity should be representable using 2 bytes or less"); @@ -333,8 +328,7 @@ namespace AzNetworking } m_sendRingbuffer.AdvanceWriteBuffer(headerSize + payloadSize); - GetMetrics().m_packetsSent++; - GetMetrics().m_sendDatarate.LogPacket(headerSize + payloadSize, currentTimeMs); + GetMetrics().LogPacketSent(headerSize + payloadSize, currentTimeMs); m_networkInterface.GetMetrics().m_sendPackets++; UpdateSend(); return true; @@ -379,8 +373,7 @@ namespace AzNetworking memcpy(dstData, srcData, packetSize); m_recvRingbuffer.AdvanceReadBuffer(serializer.GetReadSize() + packetSize); - GetMetrics().m_packetsRecv++; - GetMetrics().m_recvDatarate.LogPacket(packetSize, currentTimeMs); + GetMetrics().LogPacketRecv(packetSize, currentTimeMs); m_networkInterface.GetMetrics().m_recvPackets++; return true; } diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.h b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.h index af2c69292c..b769aea086 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.h +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.h @@ -102,7 +102,6 @@ namespace AzNetworking bool Disconnect(DisconnectReason reason, TerminationEndpoint endpoint) override; void SetConnectionMtu(uint32_t connectionMtu) override; uint32_t GetConnectionMtu() const override; - void SetConnectionQuality(const ConnectionQuality& connectionQuality) override; // @} //! Sets the registered socket file descriptor for this TcpConnection in the associated ConnectionSet instance. diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp index 798116d180..3efc6a51a8 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp @@ -152,7 +152,7 @@ namespace AzNetworking void UdpConnection::ProcessAcked(PacketId packetId, AZ::TimeMs currentTimeMs) { - GetMetrics().m_packetsAcked++; + GetMetrics().LogPacketAcked(); m_reliableQueue.OnPacketAcked(m_networkInterface, *this, packetId); // Compute Rtt adjustments @@ -172,8 +172,7 @@ namespace AzNetworking GetMetrics().m_connectionRtt.LogPacketSent(packetId, currentTimeMs); } - GetMetrics().m_packetsSent++; - GetMetrics().m_sendDatarate.LogPacket(packetSize, currentTimeMs); + GetMetrics().LogPacketSent(packetSize, currentTimeMs); m_lastSentPacketMs = currentTimeMs; m_unackedPacketCount = 0; } @@ -193,7 +192,7 @@ namespace AzNetworking return PacketTimeoutResult::Acked; case PacketAckState::Nacked: - GetMetrics().m_packetsLost++; + GetMetrics().LogPacketLost(); if (reliability == ReliabilityType::Reliable) { m_reliableQueue.OnPacketLost(m_networkInterface, *this, packetId); @@ -224,8 +223,7 @@ namespace AzNetworking return false; } - GetMetrics().m_packetsRecv++; - GetMetrics().m_recvDatarate.LogPacket(packetSize, currentTimeMs); + GetMetrics().LogPacketRecv(packetSize, currentTimeMs); if (header.GetIsReliable() && !m_reliableQueue.OnPacketReceived(header)) { diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h index c8e0c2cdc9..67d626d6cb 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h @@ -66,13 +66,8 @@ namespace AzNetworking bool Disconnect(DisconnectReason reason, TerminationEndpoint endpoint) override; void SetConnectionMtu(uint32_t connectionMtu) override; uint32_t GetConnectionMtu() const override; - void SetConnectionQuality(const ConnectionQuality& connectionQuality) override; // @} - //! Gets connection quality values for testing poor connection conditions. - //! @return connection quality values for this IConnection instance - const ConnectionQuality& GetConnectionQuality() const; - //! Returns a suitable encryption endpoint for this connection type. //! @return reference to the connections encryption endpoint DtlsEndpoint& GetDtlsEndpoint(); @@ -146,8 +141,6 @@ namespace AzNetworking UdpFragmentQueue m_fragmentQueue; ConnectionState m_state = ConnectionState::Disconnected; ConnectionRole m_connectionRole = ConnectionRole::Connector; - - ConnectionQuality m_connectionQuality; DtlsEndpoint m_dtlsEndpoint; AZ::TimeMs m_lastSentPacketMs; @@ -160,4 +153,3 @@ namespace AzNetworking } #include - diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.inl b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.inl index 1ab273d53d..b31595263d 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.inl +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.inl @@ -10,16 +10,6 @@ namespace AzNetworking { - inline void UdpConnection::SetConnectionQuality(const ConnectionQuality& connectionQuality) - { - m_connectionQuality = connectionQuality; - } - - inline const ConnectionQuality& UdpConnection::GetConnectionQuality() const - { - return m_connectionQuality; - } - inline DtlsEndpoint& UdpConnection::GetDtlsEndpoint() { return m_dtlsEndpoint; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index 1a29dd4cae..a3ddb856d2 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -224,8 +224,7 @@ namespace AzNetworking continue; } - connection->GetMetrics().m_recvDatarate.LogPacket(packet.m_receivedBytes + UdpPacketHeaderSize, currentTimeMs); - connection->GetMetrics().m_packetsRecv++; + connection->GetMetrics().LogPacketRecv(packet.m_receivedBytes + UdpPacketHeaderSize, currentTimeMs); // Decode the packet flag bitset first since it's always uncompressed UdpPacketHeader header; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp index bb0b6d0fff..29a99f96a1 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp @@ -126,7 +126,7 @@ namespace AzNetworking #ifdef ENABLE_LATENCY_DEBUG if (connectionQuality.m_lossPercentage > 0) { - if (int32_t(m_random.GetRandom() % 100) < (connectionQuality.m_lossPercentage / 2)) + if (int32_t(m_random.GetRandom() % 100) < (connectionQuality.m_lossPercentage)) { // Pretend we sent, but don't actually send return true; @@ -157,9 +157,11 @@ namespace AzNetworking #ifdef ENABLE_LATENCY_DEBUG else if ((connectionQuality.m_latencyMs > AZ::TimeMs{ 0 }) || (connectionQuality.m_varianceMs > AZ::TimeMs{ 0 })) { - const AZ::TimeMs jitterMs = aznumeric_cast(m_random.GetRandom()) % (connectionQuality.m_varianceMs / aznumeric_cast(2)); + const AZ::TimeMs jitterMs = aznumeric_cast(m_random.GetRandom()) % (connectionQuality.m_varianceMs > AZ::TimeMs{ 0 } + ? connectionQuality.m_varianceMs + : AZ::TimeMs{ 1 }); const AZ::TimeMs currTimeMs = AZ::GetElapsedTimeMs(); - const AZ::TimeMs deferTimeMs = (connectionQuality.m_latencyMs / aznumeric_cast(2)) + jitterMs; + const AZ::TimeMs deferTimeMs = (connectionQuality.m_latencyMs) + jitterMs; DeferredData deferred = DeferredData(address, data, size, encrypt, dtlsEndpoint); AZ::Interface::Get()->AddCallback([&, deferredData = deferred] diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index 1611a3213d..4d2d47444c 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -23,36 +23,30 @@ namespace Multiplayer ->Version(1); } } - void MultiplayerDebugSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC_CE("MultiplayerDebugSystemComponent")); } - void MultiplayerDebugSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) { ; } - void MultiplayerDebugSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatbile) { incompatbile.push_back(AZ_CRC_CE("MultiplayerDebugSystemComponent")); } - void MultiplayerDebugSystemComponent::Activate() { #ifdef IMGUI_ENABLED ImGui::ImGuiUpdateListenerBus::Handler::BusConnect(); #endif } - void MultiplayerDebugSystemComponent::Deactivate() { #ifdef IMGUI_ENABLED ImGui::ImGuiUpdateListenerBus::Handler::BusDisconnect(); #endif } - #ifdef IMGUI_ENABLED void MultiplayerDebugSystemComponent::OnImGuiMainMenuUpdate() { @@ -63,7 +57,6 @@ namespace Multiplayer ImGui::EndMenu(); } } - void AccumulatePerSecondValues(const MultiplayerStats& stats, const MultiplayerStats::Metric& metric, float& outCallsPerSecond, float& outBytesPerSecond) { uint64_t summedCalls = 0; @@ -80,8 +73,9 @@ namespace Multiplayer bool DrawMetricsRow(const char* name, bool expandable, uint64_t totalCalls, uint64_t totalBytes, float callsPerSecond, float bytesPerSecond) { - const ImGuiTreeNodeFlags flags = expandable ? ImGuiTreeNodeFlags_SpanFullWidth - : (ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth); + const ImGuiTreeNodeFlags flags = expandable + ? ImGuiTreeNodeFlags_SpanFullWidth + : (ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth); ImGui::TableNextRow(); ImGui::TableNextColumn(); const bool open = ImGui::TreeNodeEx(name, flags); @@ -95,14 +89,12 @@ namespace Multiplayer ImGui::Text("%11.2f", bytesPerSecond); return open; } - bool DrawSummaryRow(const char* name, const MultiplayerStats& stats) { const MultiplayerStats::Metric propertyUpdatesSent = stats.CalculateTotalPropertyUpdateSentMetrics(); const MultiplayerStats::Metric propertyUpdatesRecv = stats.CalculateTotalPropertyUpdateRecvMetrics(); const MultiplayerStats::Metric rpcsSent = stats.CalculateTotalRpcsSentMetrics(); const MultiplayerStats::Metric rpcsRecv = stats.CalculateTotalRpcsRecvMetrics(); - const uint64_t totalCalls = propertyUpdatesSent.m_totalCalls + propertyUpdatesRecv.m_totalCalls + rpcsSent.m_totalCalls + rpcsRecv.m_totalCalls; const uint64_t totalBytes = propertyUpdatesSent.m_totalBytes + propertyUpdatesRecv.m_totalBytes + rpcsSent.m_totalBytes + rpcsRecv.m_totalBytes; float callsPerSecond = 0.0f; @@ -111,17 +103,14 @@ namespace Multiplayer AccumulatePerSecondValues(stats, propertyUpdatesRecv, callsPerSecond, bytesPerSecond); AccumulatePerSecondValues(stats, rpcsSent, callsPerSecond, bytesPerSecond); AccumulatePerSecondValues(stats, rpcsRecv, callsPerSecond, bytesPerSecond); - return DrawMetricsRow(name, true, totalCalls, totalBytes, callsPerSecond, bytesPerSecond); } - bool DrawComponentRow(const char* name, const MultiplayerStats& stats, NetComponentId netComponentId) { const MultiplayerStats::Metric propertyUpdatesSent = stats.CalculateComponentPropertyUpdateSentMetrics(netComponentId); const MultiplayerStats::Metric propertyUpdatesRecv = stats.CalculateComponentPropertyUpdateRecvMetrics(netComponentId); const MultiplayerStats::Metric rpcsSent = stats.CalculateComponentRpcsSentMetrics(netComponentId); const MultiplayerStats::Metric rpcsRecv = stats.CalculateComponentRpcsRecvMetrics(netComponentId); - const uint64_t totalCalls = propertyUpdatesSent.m_totalCalls + propertyUpdatesRecv.m_totalCalls + rpcsSent.m_totalCalls + rpcsRecv.m_totalCalls; const uint64_t totalBytes = propertyUpdatesSent.m_totalBytes + propertyUpdatesRecv.m_totalBytes + rpcsSent.m_totalBytes + rpcsRecv.m_totalBytes; float callsPerSecond = 0.0f; @@ -130,10 +119,8 @@ namespace Multiplayer AccumulatePerSecondValues(stats, propertyUpdatesRecv, callsPerSecond, bytesPerSecond); AccumulatePerSecondValues(stats, rpcsSent, callsPerSecond, bytesPerSecond); AccumulatePerSecondValues(stats, rpcsRecv, callsPerSecond, bytesPerSecond); - return DrawMetricsRow(name, true, totalCalls, totalBytes, callsPerSecond, bytesPerSecond); } - void DrawComponentDetails(const MultiplayerStats& stats, NetComponentId netComponentId) { MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry(); @@ -158,7 +145,6 @@ namespace Multiplayer ImGui::TreePop(); } } - { const MultiplayerStats::Metric metric = stats.CalculateComponentPropertyUpdateRecvMetrics(netComponentId); float callsPerSecond = 0.0f; @@ -180,7 +166,6 @@ namespace Multiplayer ImGui::TreePop(); } } - { const MultiplayerStats::Metric metric = stats.CalculateComponentRpcsSentMetrics(netComponentId); float callsPerSecond = 0.0f; @@ -202,7 +187,6 @@ namespace Multiplayer ImGui::TreePop(); } } - { const MultiplayerStats::Metric metric = stats.CalculateComponentRpcsRecvMetrics(netComponentId); float callsPerSecond = 0.0f; @@ -226,45 +210,218 @@ namespace Multiplayer } } - void MultiplayerDebugSystemComponent::OnImGuiUpdate() + void DrawNetworkingStats() { const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); + const ImGuiTableFlags flags = ImGuiTableFlags_BordersV + | ImGuiTableFlags_BordersOuterH + | ImGuiTableFlags_Resizable + | ImGuiTableFlags_RowBg + | ImGuiTableFlags_NoBordersInBody; + + const ImGuiTreeNodeFlags nodeFlags = (ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth); + + AzNetworking::INetworking* networking = AZ::Interface::Get(); + + ImGui::Text("Total sockets monitored by TcpListenThread: %u", networking->GetTcpListenThreadSocketCount()); + ImGui::Text("Total time spent updating TcpListenThread: %lld", aznumeric_cast(networking->GetTcpListenThreadUpdateTime())); + ImGui::Text("Total sockets monitored by UdpReaderThread: %u", networking->GetUdpReaderThreadSocketCount()); + ImGui::Text("Total time spent updating UdpReaderThread: %lld", aznumeric_cast(networking->GetUdpReaderThreadUpdateTime())); + ImGui::NewLine(); + + for (auto& networkInterface : networking->GetNetworkInterfaces()) + { + if (ImGui::CollapsingHeader(networkInterface.second->GetName().GetCStr())) + { + const char* protocol = networkInterface.second->GetType() == AzNetworking::ProtocolType::Tcp ? "Tcp" : "Udp"; + const char* trustZone = networkInterface.second->GetTrustZone() == AzNetworking::TrustZone::ExternalClientToServer ? "ExternalClientToServer" : "InternalServerToServer"; + const uint32_t port = aznumeric_cast(networkInterface.second->GetPort()); + ImGui::Text("%sNetworkInterface open to %s on port %u", protocol, trustZone, port); + + if (ImGui::BeginTable("", 2, flags)) + { + const AzNetworking::NetworkInterfaceMetrics& metrics = networkInterface.second->GetMetrics(); + ImGui::TableSetupColumn("Stat", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f); + ImGui::TableHeadersRow(); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total time spent updating (ms)"); + ImGui::TableNextColumn(); + ImGui::Text("%lld", aznumeric_cast(metrics.m_updateTimeMs)); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total number of connections"); + ImGui::TableNextColumn(); + ImGui::Text("%llu", aznumeric_cast(metrics.m_connectionCount)); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total send time (ms)"); + ImGui::TableNextColumn(); + ImGui::Text("%lld", aznumeric_cast(metrics.m_sendTimeMs)); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total sent packets"); + ImGui::TableNextColumn(); + ImGui::Text("%llu", aznumeric_cast(metrics.m_sendPackets)); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total sent bytes after compression"); + ImGui::TableNextColumn(); + ImGui::Text("%llu", aznumeric_cast(metrics.m_sendBytes)); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total sent bytes before compression"); + ImGui::TableNextColumn(); + ImGui::Text("%llu", aznumeric_cast(metrics.m_sendBytesUncompressed)); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total sent compressed packets without benefit"); + ImGui::TableNextColumn(); + ImGui::Text("%llu", aznumeric_cast(metrics.m_sendCompressedPacketsNoGain)); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total gain from packet compression"); + ImGui::TableNextColumn(); + ImGui::Text("%lld", aznumeric_cast(metrics.m_sendBytesCompressedDelta)); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total packets resent"); + ImGui::TableNextColumn(); + ImGui::Text("%llu", aznumeric_cast(metrics.m_resentPackets)); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total receive time (ms)"); + ImGui::TableNextColumn(); + ImGui::Text("%lld", aznumeric_cast(metrics.m_recvTimeMs)); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total received packets"); + ImGui::TableNextColumn(); + ImGui::Text("%llu", aznumeric_cast(metrics.m_recvPackets)); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total received bytes after compression"); + ImGui::TableNextColumn(); + ImGui::Text("%llu", aznumeric_cast(metrics.m_recvBytes)); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total received bytes before compression"); + ImGui::TableNextColumn(); + ImGui::Text("%llu", aznumeric_cast(metrics.m_recvBytesUncompressed)); + ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::Text("Total packets discarded due to load"); + ImGui::TableNextColumn(); + ImGui::Text("%llu", aznumeric_cast(metrics.m_discardedPackets)); + ImGui::EndTable(); + } + + if (ImGui::BeginTable("", 7, flags)) + { + // The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On + ImGui::TableSetupColumn("RemoteAddr", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Conn. Id", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 6.0f); + ImGui::TableSetupColumn("Send (Bps)", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 10.0f); + ImGui::TableSetupColumn("Recv (Bps)", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 10.0f); + ImGui::TableSetupColumn("RTT (ms)", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 8.0f); + ImGui::TableSetupColumn("% Lost", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 8.0f); + ImGui::TableSetupColumn("Debug Settings", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 32.0f); + ImGui::TableHeadersRow(); + + auto displayConnectionRow = [](AzNetworking::IConnection& connection) + { + ImGui::PushID(&connection); + + const AzNetworking::ConnectionMetrics& metrics = connection.GetMetrics(); + const AzNetworking::IpAddress::IpString remoteAddr = connection.GetRemoteAddress().GetString(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TreeNodeEx(remoteAddr.c_str(), nodeFlags); + ImGui::TableNextColumn(); + ImGui::Text("%5llu", aznumeric_cast(connection.GetConnectionId())); + ImGui::TableNextColumn(); + ImGui::Text("%9.2f", metrics.m_sendDatarate.GetBytesPerSecond()); + ImGui::TableNextColumn(); + ImGui::Text("%9.2f", metrics.m_recvDatarate.GetBytesPerSecond()); + ImGui::TableNextColumn(); + ImGui::Text("%7.2f", metrics.m_connectionRtt.GetRoundTripTimeSeconds() * 1000.0f); + ImGui::TableNextColumn(); + ImGui::Text("%7.2f", metrics.m_sendDatarate.GetLossRatePercent()); + ImGui::TableNextColumn(); + + { + AzNetworking::ConnectionQuality& quality = connection.GetConnectionQuality(); + int32_t latency = aznumeric_cast(quality.m_latencyMs); + int32_t variance = aznumeric_cast(quality.m_varianceMs); + ImGui::SliderInt("Loss %", &quality.m_lossPercentage, 0, 100); + if (ImGui::SliderInt("Latency(ms)", &latency, 0, 3000)) + { + quality.m_latencyMs = AZ::TimeMs{ latency }; + } + if (ImGui::SliderInt("Jitter(ms)", &variance, 0, 1000)) + { + quality.m_varianceMs = AZ::TimeMs{ variance }; + } + } + ImGui::PopID(); + }; + networkInterface.second->GetConnectionSet().VisitConnections(displayConnectionRow); + ImGui::EndTable(); + } + } + ImGui::NewLine(); + } + ImGui::End(); + } + + void DrawMultiplayerStats() + { + const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; + const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); + + IMultiplayer* multiplayer = AZ::Interface::Get(); + MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry(); + const Multiplayer::MultiplayerStats& stats = multiplayer->GetStats(); + ImGui::Text("Multiplayer operating in %s mode", GetEnumString(multiplayer->GetAgentType())); + ImGui::Text("Total networked entities: %llu", aznumeric_cast(stats.m_entityCount)); + ImGui::Text("Total client connections: %llu", aznumeric_cast(stats.m_clientConnectionCount)); + ImGui::Text("Total server connections: %llu", aznumeric_cast(stats.m_serverConnectionCount)); + ImGui::NewLine(); + + static ImGuiTableFlags flags = ImGuiTableFlags_BordersV + | ImGuiTableFlags_BordersOuterH + | ImGuiTableFlags_Resizable + | ImGuiTableFlags_RowBg + | ImGuiTableFlags_NoBordersInBody; + + if (ImGui::BeginTable("", 5, flags)) + { + // The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Total Calls", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f); + ImGui::TableSetupColumn("Total Bytes", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f); + ImGui::TableSetupColumn("Calls/Sec", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f); + ImGui::TableSetupColumn("Bytes/Sec", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f); + ImGui::TableHeadersRow(); + + if (DrawSummaryRow("Totals", stats)) + { + for (AZStd::size_t index = 0; index < stats.m_componentStats.size(); ++index) + { + const NetComponentId netComponentId = aznumeric_cast(index); + using StringLabel = AZStd::fixed_string<128>; + const StringLabel gemName = componentRegistry->GetComponentGemName(netComponentId); + const StringLabel componentName = componentRegistry->GetComponentName(netComponentId); + const StringLabel label = gemName + "::" + componentName; + if (DrawComponentRow(label.c_str(), stats, netComponentId)) + { + DrawComponentDetails(stats, netComponentId); + ImGui::TreePop(); + } + } + } + ImGui::EndTable(); + ImGui::NewLine(); + } + ImGui::End(); + } + + void MultiplayerDebugSystemComponent::OnImGuiUpdate() + { if (m_displayNetworkingStats) { if (ImGui::Begin("Networking Stats", &m_displayNetworkingStats, ImGuiWindowFlags_None)) { - AzNetworking::INetworking* networking = AZ::Interface::Get(); - - ImGui::Text("Total sockets monitored by TcpListenThread: %u", networking->GetTcpListenThreadSocketCount()); - ImGui::Text("Total time spent updating TcpListenThread: %lld", aznumeric_cast(networking->GetTcpListenThreadUpdateTime())); - ImGui::Text("Total sockets monitored by UdpReaderThread: %u", networking->GetUdpReaderThreadSocketCount()); - ImGui::Text("Total time spent updating UdpReaderThread: %lld", aznumeric_cast(networking->GetUdpReaderThreadUpdateTime())); - - for (auto& networkInterface : networking->GetNetworkInterfaces()) - { - const char* protocol = networkInterface.second->GetType() == AzNetworking::ProtocolType::Tcp ? "Tcp" : "Udp"; - const char* trustZone = networkInterface.second->GetTrustZone() == AzNetworking::TrustZone::ExternalClientToServer ? "ExternalClientToServer" : "InternalServerToServer"; - const uint32_t port = aznumeric_cast(networkInterface.second->GetPort()); - ImGui::Text("%sNetworkInterface: %s - open to %s on port %u", protocol, networkInterface.second->GetName().GetCStr(), trustZone, port); - - const AzNetworking::NetworkInterfaceMetrics& metrics = networkInterface.second->GetMetrics(); - ImGui::Text(" - Total time spent updating in milliseconds: %lld", aznumeric_cast(metrics.m_updateTimeMs)); - ImGui::Text(" - Total number of connections: %llu", aznumeric_cast(metrics.m_connectionCount)); - ImGui::Text(" - Total send time in milliseconds: %lld", aznumeric_cast(metrics.m_sendTimeMs)); - ImGui::Text(" - Total sent packets: %llu", aznumeric_cast(metrics.m_sendPackets)); - ImGui::Text(" - Total sent bytes after compression: %llu", aznumeric_cast(metrics.m_sendBytes)); - ImGui::Text(" - Total sent bytes before compression: %llu", aznumeric_cast(metrics.m_sendBytesUncompressed)); - ImGui::Text(" - Total sent compressed packets without benefit: %llu", aznumeric_cast(metrics.m_sendCompressedPacketsNoGain)); - ImGui::Text(" - Total gain from packet compression: %lld", aznumeric_cast(metrics.m_sendBytesCompressedDelta)); - ImGui::Text(" - Total packets resent: %llu", aznumeric_cast(metrics.m_resentPackets)); - ImGui::Text(" - Total receive time in milliseconds: %lld", aznumeric_cast(metrics.m_recvTimeMs)); - ImGui::Text(" - Total received packets: %llu", aznumeric_cast(metrics.m_recvPackets)); - ImGui::Text(" - Total received bytes after compression: %llu", aznumeric_cast(metrics.m_recvBytes)); - ImGui::Text(" - Total received bytes before compression: %llu", aznumeric_cast(metrics.m_recvBytesUncompressed)); - ImGui::Text(" - Total packets discarded due to load: %llu", aznumeric_cast(metrics.m_discardedPackets)); - } + DrawNetworkingStats(); } } @@ -272,50 +429,7 @@ namespace Multiplayer { if (ImGui::Begin("Multiplayer Stats", &m_displayMultiplayerStats, ImGuiWindowFlags_None)) { - IMultiplayer* multiplayer = AZ::Interface::Get(); - MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry(); - const Multiplayer::MultiplayerStats& stats = multiplayer->GetStats(); - ImGui::Text("Multiplayer operating in %s mode", GetEnumString(multiplayer->GetAgentType())); - ImGui::Text("Total networked entities: %llu", aznumeric_cast(stats.m_entityCount)); - ImGui::Text("Total client connections: %llu", aznumeric_cast(stats.m_clientConnectionCount)); - ImGui::Text("Total server connections: %llu", aznumeric_cast(stats.m_serverConnectionCount)); - ImGui::NewLine(); - - static ImGuiTableFlags flags = ImGuiTableFlags_BordersV - | ImGuiTableFlags_BordersOuterH - | ImGuiTableFlags_Resizable - | ImGuiTableFlags_RowBg - | ImGuiTableFlags_NoBordersInBody; - - if (ImGui::BeginTable("", 5, flags)) - { - // The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Total Calls", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f); - ImGui::TableSetupColumn("Total Bytes", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f); - ImGui::TableSetupColumn("Calls/Sec", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f); - ImGui::TableSetupColumn("Bytes/Sec", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f); - ImGui::TableHeadersRow(); - - if (DrawSummaryRow("Totals", stats)) - { - for (AZStd::size_t index = 0; index < stats.m_componentStats.size(); ++index) - { - const NetComponentId netComponentId = aznumeric_cast(index); - using StringLabel = AZStd::fixed_string<128>; - const StringLabel gemName = componentRegistry->GetComponentGemName(netComponentId); - const StringLabel componentName = componentRegistry->GetComponentName(netComponentId); - const StringLabel label = gemName + "::" + componentName; - if (DrawComponentRow(label.c_str(), stats, netComponentId)) - { - DrawComponentDetails(stats, netComponentId); - ImGui::TreePop(); - } - } - } - ImGui::EndTable(); - } - ImGui::End(); + DrawMultiplayerStats(); } } }