From e8bba6ade1fa0a6c736859c85fd59ed58ed80027 Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Thu, 29 Aug 2019 13:42:17 -0700 Subject: [PATCH] Added interface for app to provision cert. Also: - STEAMNETWORKINGSOCKETS_STEAM now mens "running on steam", not "running using the steam client". STEAMNETWORKINGSOCKETS_STEAMCLIENT is for that. - Refactored stats stuff, moved it into the namespace. At one point I thought I might expose some stuff in a public interface. For now, keeping it internal. - Removed concept of Steam "universe" from this branch of the code. - Don't use OVERRIDE, override works. --- examples/example_chat.cpp | 3 +- include/steam/isteamnetworkingsockets.h | 17 + include/steam/isteamnetworkingutils.h | 2 +- include/steam/steamnetworkingtypes.h | 2 +- src/CMakeLists.txt | 1 + ...teamnetworkingsockets_messages_certs.proto | 10 + src/meson.build | 1 + .../clientlib/csteamnetworkingsockets.cpp | 136 +- .../clientlib/csteamnetworkingsockets.h | 58 +- .../steamnetworking_stats.h | 4 + .../steamnetworkingsockets_internal.h | 8 +- .../steamnetworkingsockets_shared.cpp | 1529 +---------------- .../steamnetworkingsockets_stats.cpp | 1455 ++++++++++++++++ tests/test_connection.cpp | 3 +- 14 files changed, 1715 insertions(+), 1514 deletions(-) create mode 100644 src/steamnetworkingsockets/steamnetworkingsockets_stats.cpp diff --git a/examples/example_chat.cpp b/examples/example_chat.cpp index 21da3c0..93e9e92 100644 --- a/examples/example_chat.cpp +++ b/examples/example_chat.cpp @@ -96,7 +96,8 @@ static void InitSteamDatagramConnectionSockets() if ( !GameNetworkingSockets_Init( nullptr, errMsg ) ) FatalError( "GameNetworkingSockets_Init failed. %s", errMsg ); #else - SteamDatagramClient_SetAppIDAndUniverse( 570, k_EUniverseDev ); // Just set something, doesn't matter what + SteamDatagramClient_SetAppID( 570 ); // Just set something, doesn't matter what + //SteamDatagramClient_SetUniverse( k_EUniverseDev ); SteamDatagramErrMsg errMsg; if ( !SteamDatagramClient_Init( true, errMsg ) ) diff --git a/include/steam/isteamnetworkingsockets.h b/include/steam/isteamnetworkingsockets.h index ccadbbf..02e4624 100644 --- a/include/steam/isteamnetworkingsockets.h +++ b/include/steam/isteamnetworkingsockets.h @@ -324,6 +324,23 @@ public: /// details, pass non-NULL to receive them. virtual ESteamNetworkingAvailability GetAuthenticationStatus( SteamNetAuthenticationStatus_t *pDetails ) = 0; +/// Certificate provision by the application. (On Steam, Steam will handle all this automatically) +#ifndef STEAMNETWORKINGSOCKETS_STEAM + + /// Get blob that describes a certificate request. You can send this to your game coordinator. + /// Upon entry, *pcbBlob should contain the size of the buffer. On successful exit, it will + /// return the number of bytes that were populated. You can pass pBlob=NULL to query for the required + /// size. (256 bytes is a very conservative estimate.) + /// + /// Pass this blob to your game coordinator and call SteamDatagram_CreateCert. + virtual bool GetCertificateRequest( int *pcbBlob, void *pBlob, SteamNetworkingErrMsg &errMsg ) = 0; + + /// Set the certificate. The certificate blob should be the output of + /// SteamDatagram_CreateCert. + virtual bool SetCertificate( const void *pCertificate, int cbCertificate, SteamNetworkingErrMsg &errMsg ) = 0; + +#endif + #ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR /// Dedicated servers ervers hosted in known data centers #endif // #ifndef STEAMNETWORKINGSOCKETS_ENABLE_SDR diff --git a/include/steam/isteamnetworkingutils.h b/include/steam/isteamnetworkingutils.h index 27b0747..bdb29df 100644 --- a/include/steam/isteamnetworkingutils.h +++ b/include/steam/isteamnetworkingutils.h @@ -149,7 +149,7 @@ inline bool ISteamNetworkingUtils::SetConnectionConfigValueInt32( HSteamNetConne inline bool ISteamNetworkingUtils::SetConnectionConfigValueFloat( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, float val ) { return SetConfigValue( eValue, k_ESteamNetworkingConfig_Connection, hConn, k_ESteamNetworkingConfig_Float, &val ); } inline bool ISteamNetworkingUtils::SetConnectionConfigValueString( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, const char *val ) { return SetConfigValue( eValue, k_ESteamNetworkingConfig_Connection, hConn, k_ESteamNetworkingConfig_String, val ); } -#if !defined( STEAMNETWORKINGSOCKETS_STATIC_LINK ) && defined( STEAMNETWORKINGSOCKETS_STEAM ) +#if !defined( STEAMNETWORKINGSOCKETS_STATIC_LINK ) && defined( STEAMNETWORKINGSOCKETS_STEAMCLIENT ) inline void SteamNetworkingIPAddr::ToString( char *buf, size_t cbBuf, bool bWithPort ) const { SteamNetworkingUtils()->SteamNetworkingIPAddr_ToString( *this, buf, cbBuf, bWithPort ); } inline bool SteamNetworkingIPAddr::ParseString( const char *pszStr ) { return SteamNetworkingUtils()->SteamNetworkingIPAddr_ParseString( this, pszStr ); } inline void SteamNetworkingIdentity::ToString( char *buf, size_t cbBuf ) const { SteamNetworkingUtils()->SteamNetworkingIdentity_ToString( *this, buf, cbBuf ); } diff --git a/include/steam/steamnetworkingtypes.h b/include/steam/steamnetworkingtypes.h index 5f58e9d..7027125 100644 --- a/include/steam/steamnetworkingtypes.h +++ b/include/steam/steamnetworkingtypes.h @@ -1142,7 +1142,7 @@ inline const uint8 *SteamNetworkingIdentity::GetGenericBytes( int &cbLen ) const inline bool SteamNetworkingIdentity::operator==(const SteamNetworkingIdentity &x ) const { return m_eType == x.m_eType && m_cbSize == x.m_cbSize && memcmp( m_genericBytes, x.m_genericBytes, m_cbSize ) == 0; } inline void SteamNetworkingMessage_t::Release() { (*m_pfnRelease)( this ); } -#if defined( STEAMNETWORKINGSOCKETS_STATIC_LINK ) || !defined( STEAMNETWORKINGSOCKETS_STEAM ) +#if defined( STEAMNETWORKINGSOCKETS_STATIC_LINK ) || !defined( STEAMNETWORKINGSOCKETS_STEAMCLIENT ) STEAMNETWORKINGSOCKETS_INTERFACE void SteamAPI_SteamNetworkingIPAddr_ToString( const SteamNetworkingIPAddr *pAddr, char *buf, size_t cbBuf, bool bWithPort ); STEAMNETWORKINGSOCKETS_INTERFACE bool SteamAPI_SteamNetworkingIPAddr_ParseString( SteamNetworkingIPAddr *pAddr, const char *pszStr ); STEAMNETWORKINGSOCKETS_INTERFACE void SteamAPI_SteamNetworkingIdentity_ToString( const SteamNetworkingIdentity &identity, char *buf, size_t cbBuf ); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 68c80f8..61d1554 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ set(GNS_SRCS "steamnetworkingsockets/steamnetworkingsockets_certs.cpp" "steamnetworkingsockets/steamnetworkingsockets_certstore.cpp" "steamnetworkingsockets/steamnetworkingsockets_shared.cpp" + "steamnetworkingsockets/steamnetworkingsockets_stats.cpp" "tier0/dbg.cpp" "tier0/platformtime.cpp" "tier1/bitstring.cpp" diff --git a/src/common/steamnetworkingsockets_messages_certs.proto b/src/common/steamnetworkingsockets_messages_certs.proto index f319db7..e943dda 100644 --- a/src/common/steamnetworkingsockets_messages_certs.proto +++ b/src/common/steamnetworkingsockets_messages_certs.proto @@ -89,5 +89,15 @@ message CMsgSteamDatagramCertificateSigned optional bytes ca_signature = 6; } +// A request by a client to a CA to issue a cert. +message CMsgSteamDatagramCertificateRequest +{ + // An unsigned cert. The requestor will populate the fields + // appropriate to the request. (Who do you thin you are, + // what app(s) would you like access for, etc) Most importantly, + // the caller must fill in the public key they want to use + optional CMsgSteamDatagramCertificate cert = 1; +} + // Do not remove this comment due to a bug on the Mac OS X protobuf compiler diff --git a/src/meson.build b/src/meson.build index e017cb2..c092ead 100644 --- a/src/meson.build +++ b/src/meson.build @@ -72,6 +72,7 @@ sources = [ 'steamnetworkingsockets/steamnetworkingsockets_certs.cpp', 'steamnetworkingsockets/steamnetworkingsockets_certstore.cpp', 'steamnetworkingsockets/steamnetworkingsockets_shared.cpp', + 'steamnetworkingsockets/steamnetworkingsockets_stats.cpp', 'tier0/dbg.cpp', 'tier0/platformtime.cpp', 'tier1/bitstring.cpp', diff --git a/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp b/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp index 48871be..c0cb86c 100644 --- a/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp +++ b/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp @@ -4,6 +4,7 @@ #include "steamnetworkingsockets_lowlevel.h" #include "steamnetworkingsockets_connections.h" #include "steamnetworkingsockets_udp.h" +#include "../steamnetworkingsockets_certstore.h" #include "crypto.h" #ifdef STEAMNETWORKINGSOCKETS_STANDALONELIB @@ -374,6 +375,137 @@ bool CSteamNetworkingSockets::GetIdentity( SteamNetworkingIdentity *pIdentity ) return !m_identity.IsInvalid(); } +/// Certificate provision by the application. (On Steam, Steam will handle all this automatically) +#ifndef STEAMNETWORKINGSOCKETS_STEAM + +bool CSteamNetworkingSockets::GetCertificateRequest( int *pcbBlob, void *pBlob, SteamNetworkingErrMsg &errMsg ) +{ + SteamDatagramTransportLock scopeLock; + + // If we don't have a private key, generate one now. + CECSigningPublicKey pubKey; + if ( m_keyPrivateKey.IsValid() ) + { + DbgVerify( m_keyPrivateKey.GetPublicKey( &pubKey ) ); + } + else + { + CCrypto::GenerateSigningKeyPair( &pubKey, &m_keyPrivateKey ); + } + + // Fill out the request + CMsgSteamDatagramCertificateRequest msgRequest; + CMsgSteamDatagramCertificate &msgCert =*msgRequest.mutable_cert(); + + // Our public key + msgCert.set_key_type( CMsgSteamDatagramCertificate_EKeyType_ED25519 ); + DbgVerify( pubKey.GetRawDataAsStdString( msgCert.mutable_key_data() ) ); + + // Our identity, if we know it + InternalGetIdentity(); + if ( !m_identity.IsInvalid() && !m_identity.IsLocalHost() ) + { + SteamNetworkingIdentityToProtobuf( m_identity, msgCert, identity, legacy_steam_id ); + } + + // Check size + int cb = msgRequest.ByteSize(); + if ( !pBlob ) + { + *pcbBlob = cb; + return true; + } + if ( cb > *pcbBlob ) + { + *pcbBlob = cb; + V_sprintf_safe( errMsg, "%d byte buffer not big enough; %d bytes required", *pcbBlob, cb ); + return false; + } + + *pcbBlob = cb; + uint8 *p = (uint8 *)pBlob; + DbgVerify( msgRequest.SerializeWithCachedSizesToArray( p ) == p + cb ); + return true; +} + +bool CSteamNetworkingSockets::SetCertificate( const void *pCertificate, int cbCertificate, SteamNetworkingErrMsg &errMsg ) +{ + // Crack the blob + CMsgSteamDatagramCertificateSigned msgCertSigned; + if ( !msgCertSigned.ParseFromArray( pCertificate, cbCertificate ) ) + { + V_strcpy_safe( errMsg, "CMsgSteamDatagramCertificateSigned failed protobuf parse" ); + return false; + } + + SteamDatagramTransportLock scopeLock; + + // Crack the cert, and check the signature. If *we* aren't even willing + // to trust it, assume that our peers won't either + CMsgSteamDatagramCertificate msgCert; + time_t authTime = m_pSteamNetworkingUtils->GetTimeSecure(); + const CertAuthScope *pAuthScope = CertStore_CheckCert( msgCertSigned, msgCert, authTime, errMsg ); + if ( !pAuthScope ) + return false; + + // Extract the identity from the cert + SteamNetworkingErrMsg tempErrMsg; + SteamNetworkingIdentity certIdentity; + int r = SteamNetworkingIdentityFromCert( certIdentity, msgCert, tempErrMsg ); + if ( r < 0 ) + { + V_sprintf_safe( errMsg, "Cert has invalid identity. %s", tempErrMsg ); + return false; + } + + // Make sure the cert actually matches our public key. + if ( !m_keyPrivateKey.IsValid() ) + { + // WAT + V_strcpy_safe( errMsg, "Cannot set cert. No private key?" ); + return false; + } + if ( msgCert.key_type() != CMsgSteamDatagramCertificate_EKeyType_ED25519 || msgCert.key_data().size() != 32 ) + { + V_strcpy_safe( errMsg, "Cert has invalid public key" ); + return false; + } + if ( memcmp( msgCert.key_data().c_str(), m_keyPrivateKey.GetPublicKeyRawData(), 32 ) != 0 ) + { + V_strcpy_safe( errMsg, "Cert public key does not match our private key" ); + return false; + } + + // Make sure the cert authorizes us for the App we think we are running + AppId_t nAppID = m_pSteamNetworkingUtils->GetAppID(); + if ( !CheckCertAppID( msgCert, pAuthScope, nAppID, tempErrMsg ) ) + { + V_sprintf_safe( errMsg, "Cert does not authorize us for App %u", nAppID ); + return false; + } + + // If we don't know our identity, then set it now. Otherwise, + // it better match. + if ( m_identity.IsInvalid() || m_identity.IsLocalHost() ) + { + m_identity = certIdentity; + } + else if ( !( m_identity == certIdentity ) ) + { + V_sprintf_safe( errMsg, "Cert is for identity '%s'. We are '%s'", SteamNetworkingIdentityRender( certIdentity ).c_str(), SteamNetworkingIdentityRender( m_identity ).c_str() ); + return false; + } + + // Save it off + m_msgSignedCert = std::move( msgCertSigned ); + m_msgCert = std::move( msgCert ); + + // OK + return true; +} + +#endif + #ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE ESteamNetworkingAvailability CSteamNetworkingSockets::InitAuthentication() { @@ -674,7 +806,7 @@ bool CSteamNetworkingSockets::BCertHasIdentity() const } -bool CSteamNetworkingSockets::SetCertificate( const void *pCert, int cbCert, void *pPrivateKey, int cbPrivateKey, SteamDatagramErrMsg &errMsg ) +bool CSteamNetworkingSockets::SetCertificateAndPrivateKey( const void *pCert, int cbCert, void *pPrivateKey, int cbPrivateKey, SteamDatagramErrMsg &errMsg ) { m_msgCert.Clear(); m_msgSignedCert.Clear(); @@ -1244,7 +1376,7 @@ bool CSteamNetworkingUtils::SteamNetworkingIdentity_ParseString( SteamNetworking AppId_t CSteamNetworkingUtils::GetAppID() { - return 0; + return m_nAppID; } time_t CSteamNetworkingUtils::GetTimeSecure() diff --git a/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.h b/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.h index dcf4065..59166c6 100644 --- a/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.h +++ b/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.h @@ -8,7 +8,7 @@ #include #include -#ifdef STEAMNETWORKINGSOCKETS_STEAM +#ifdef STEAMNETWORKINGSOCKETS_STEAMCLIENT #include #include #else @@ -40,7 +40,7 @@ public: CMsgSteamDatagramCertificate m_msgCert; CECSigningPrivateKey m_keyPrivateKey; bool BCertHasIdentity() const; - virtual bool SetCertificate( const void *pCert, int cbCert, void *pPrivateKey, int cbPrivateKey, SteamDatagramErrMsg &errMsg ); + virtual bool SetCertificateAndPrivateKey( const void *pCert, int cbCert, void *pPrivateKey, int cbPrivateKey, SteamDatagramErrMsg &errMsg ); bool BHasAnyConnections() const; bool BHasAnyListenSockets() const; @@ -75,28 +75,33 @@ public: } // Implements ISteamNetworkingSockets - virtual HSteamListenSocket CreateListenSocketIP( const SteamNetworkingIPAddr &localAddress ) OVERRIDE; - virtual HSteamNetConnection ConnectByIPAddress( const SteamNetworkingIPAddr &adress ) OVERRIDE; - virtual EResult AcceptConnection( HSteamNetConnection hConn ) OVERRIDE; - virtual bool CloseConnection( HSteamNetConnection hConn, int nReason, const char *pszDebug, bool bEnableLinger ) OVERRIDE; - virtual bool CloseListenSocket( HSteamListenSocket hSocket ) OVERRIDE; - virtual bool SetConnectionUserData( HSteamNetConnection hPeer, int64 nUserData ) OVERRIDE; - virtual int64 GetConnectionUserData( HSteamNetConnection hPeer ) OVERRIDE; - virtual void SetConnectionName( HSteamNetConnection hPeer, const char *pszName ) OVERRIDE; - virtual bool GetConnectionName( HSteamNetConnection hPeer, char *pszName, int nMaxLen ) OVERRIDE; - virtual EResult SendMessageToConnection( HSteamNetConnection hConn, const void *pData, uint32 cbData, int nSendFlags ) OVERRIDE; - virtual EResult FlushMessagesOnConnection( HSteamNetConnection hConn ) OVERRIDE; - virtual int ReceiveMessagesOnConnection( HSteamNetConnection hConn, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) OVERRIDE; - virtual int ReceiveMessagesOnListenSocket( HSteamListenSocket hSocket, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) OVERRIDE; - virtual bool GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo_t *pInfo ) OVERRIDE; - virtual bool GetQuickConnectionStatus( HSteamNetConnection hConn, SteamNetworkingQuickConnectionStatus *pStats ) OVERRIDE; - virtual int GetDetailedConnectionStatus( HSteamNetConnection hConn, char *pszBuf, int cbBuf ) OVERRIDE; - virtual bool GetListenSocketAddress( HSteamListenSocket hSocket, SteamNetworkingIPAddr *pAddress ) OVERRIDE; - virtual bool CreateSocketPair( HSteamNetConnection *pOutConnection1, HSteamNetConnection *pOutConnection2, bool bUseNetworkLoopback, const SteamNetworkingIdentity *pIdentity1, const SteamNetworkingIdentity *pIdentity2 ) OVERRIDE; - virtual bool GetIdentity( SteamNetworkingIdentity *pIdentity ) OVERRIDE; + virtual HSteamListenSocket CreateListenSocketIP( const SteamNetworkingIPAddr &localAddress ) override; + virtual HSteamNetConnection ConnectByIPAddress( const SteamNetworkingIPAddr &adress ) override; + virtual EResult AcceptConnection( HSteamNetConnection hConn ) override; + virtual bool CloseConnection( HSteamNetConnection hConn, int nReason, const char *pszDebug, bool bEnableLinger ) override; + virtual bool CloseListenSocket( HSteamListenSocket hSocket ) override; + virtual bool SetConnectionUserData( HSteamNetConnection hPeer, int64 nUserData ) override; + virtual int64 GetConnectionUserData( HSteamNetConnection hPeer ) override; + virtual void SetConnectionName( HSteamNetConnection hPeer, const char *pszName ) override; + virtual bool GetConnectionName( HSteamNetConnection hPeer, char *pszName, int nMaxLen ) override; + virtual EResult SendMessageToConnection( HSteamNetConnection hConn, const void *pData, uint32 cbData, int nSendFlags ) override; + virtual EResult FlushMessagesOnConnection( HSteamNetConnection hConn ) override; + virtual int ReceiveMessagesOnConnection( HSteamNetConnection hConn, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) override; + virtual int ReceiveMessagesOnListenSocket( HSteamListenSocket hSocket, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) override; + virtual bool GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo_t *pInfo ) override; + virtual bool GetQuickConnectionStatus( HSteamNetConnection hConn, SteamNetworkingQuickConnectionStatus *pStats ) override; + virtual int GetDetailedConnectionStatus( HSteamNetConnection hConn, char *pszBuf, int cbBuf ) override; + virtual bool GetListenSocketAddress( HSteamListenSocket hSocket, SteamNetworkingIPAddr *pAddress ) override; + virtual bool CreateSocketPair( HSteamNetConnection *pOutConnection1, HSteamNetConnection *pOutConnection2, bool bUseNetworkLoopback, const SteamNetworkingIdentity *pIdentity1, const SteamNetworkingIdentity *pIdentity2 ) override; + virtual bool GetIdentity( SteamNetworkingIdentity *pIdentity ) override; + +#ifndef STEAMNETWORKINGSOCKETS_STEAM + virtual bool GetCertificateRequest( int *pcbBlob, void *pBlob, SteamNetworkingErrMsg &errMsg ) override; + virtual bool SetCertificate( const void *pCertificate, int cbCertificate, SteamNetworkingErrMsg &errMsg ) override; +#endif #ifdef STEAMNETWORKINGSOCKETS_STANDALONELIB - virtual void RunCallbacks( ISteamNetworkingSocketsCallbacks *pCallbacks ) OVERRIDE; + virtual void RunCallbacks( ISteamNetworkingSocketsCallbacks *pCallbacks ) override; #endif /// Configuration options that will apply to all connections on this interface @@ -156,9 +161,18 @@ public: virtual AppId_t GetAppID(); + void SetAppID( AppId_t nAppID ) + { + Assert( m_nAppID == 0 || m_nAppID == nAppID ); + m_nAppID = nAppID; + } + // Get current time of day, ideally from a source that // doesn't depend on the user setting their local clock properly virtual time_t GetTimeSecure(); + +protected: + AppId_t m_nAppID = 0; }; } // namespace SteamNetworkingSocketsLib diff --git a/src/steamnetworkingsockets/steamnetworking_stats.h b/src/steamnetworkingsockets/steamnetworking_stats.h index a946a9a..c294213 100644 --- a/src/steamnetworkingsockets/steamnetworking_stats.h +++ b/src/steamnetworkingsockets/steamnetworking_stats.h @@ -15,6 +15,8 @@ #pragma pack(push) #pragma pack(8) +namespace SteamNetworkingSocketsLib { + struct SteamDatagramLinkStats; struct SteamDatagramLinkLifetimeStats; struct SteamDatagramLinkInstantaneousStats; @@ -377,4 +379,6 @@ struct SteamNetworkingDetailedConnectionStatus #pragma pack(pop) +} // namespace SteamNetworkingSocketsLib + #endif // STEAMNETWORKING_STATS_H diff --git a/src/steamnetworkingsockets/steamnetworkingsockets_internal.h b/src/steamnetworkingsockets/steamnetworkingsockets_internal.h index 1c798a9..57c90ed 100644 --- a/src/steamnetworkingsockets/steamnetworkingsockets_internal.h +++ b/src/steamnetworkingsockets/steamnetworkingsockets_internal.h @@ -44,7 +44,7 @@ #include #include #include -#ifdef STEAMNETWORKINGSOCKETS_STEAM +#ifdef STEAMNETWORKINGSOCKETS_STEAMCLIENT #include #endif #include @@ -99,14 +99,14 @@ struct iovec struct iovec; #endif +// Internal stuff goes in a private namespace +namespace SteamNetworkingSocketsLib { + struct SteamDatagramLinkStats; struct SteamDatagramLinkLifetimeStats; struct SteamDatagramLinkInstantaneousStats; struct SteamNetworkingDetailedConnectionStatus; -// Internal stuff goes in a private namespace -namespace SteamNetworkingSocketsLib { - // An identity operator that always returns its operand. // NOTE: std::hash is an identity operator on many compilers // for the basic primitives. If you really need actual diff --git a/src/steamnetworkingsockets/steamnetworkingsockets_shared.cpp b/src/steamnetworkingsockets/steamnetworkingsockets_shared.cpp index 89f76b2..ddcb514 100644 --- a/src/steamnetworkingsockets/steamnetworkingsockets_shared.cpp +++ b/src/steamnetworkingsockets/steamnetworkingsockets_shared.cpp @@ -2,1339 +2,15 @@ #include #include -#include "steamnetworking_statsutils.h" +#include "steamnetworkingsockets_internal.h" #include "../tier1/ipv6text.h" // Must be the last include #include -using namespace SteamNetworkingSocketsLib; - -/////////////////////////////////////////////////////////////////////////////// -// -// LinkStatsTracker -// -/////////////////////////////////////////////////////////////////////////////// - - -void SteamDatagramLinkInstantaneousStats::Clear() -{ - memset( this, 0, sizeof(*this) ); - m_nPingMS = -1; - m_flPacketsDroppedPct = -1.0f; - m_flPacketsWeirdSequenceNumberPct = -1.0f; - m_usecMaxJitter = -1; - m_nSendRate = -1; - m_nPendingBytes = 0; -} - -void SteamDatagramLinkLifetimeStats::Clear() -{ - memset( this, 0, sizeof(*this) ); - m_nPingNtile5th = -1; - m_nPingNtile50th = -1; - m_nPingNtile75th = -1; - m_nPingNtile95th = -1; - m_nPingNtile98th = -1; - m_nQualityNtile2nd = -1; - m_nQualityNtile5th = -1; - m_nQualityNtile25th = -1; - m_nQualityNtile50th = -1; - m_nTXSpeedNtile5th = -1; - m_nTXSpeedNtile50th = -1; - m_nTXSpeedNtile75th = -1; - m_nTXSpeedNtile95th = -1; - m_nTXSpeedNtile98th = -1; - m_nRXSpeedNtile5th = -1; - m_nRXSpeedNtile50th = -1; - m_nRXSpeedNtile75th = -1; - m_nRXSpeedNtile95th = -1; - m_nRXSpeedNtile98th = -1; -} - -void SteamDatagramLinkStats::Clear() -{ - m_latest.Clear(); - //m_peak.Clear(); - m_lifetime.Clear(); - m_latestRemote.Clear(); - m_flAgeLatestRemote = -1.0f; - m_lifetimeRemote.Clear(); - m_flAgeLifetimeRemote = -1.0f; -} - - -void PingTracker::Reset() -{ - memset( m_arPing, 0, sizeof(m_arPing) ); - m_nValidPings = 0; - m_nSmoothedPing = -1; - m_usecTimeLastSentPingRequest = 0; -} - -void PingTracker::ReceivedPing( int nPingMS, SteamNetworkingMicroseconds usecNow ) -{ - Assert( nPingMS >= 0 ); - COMPILE_TIME_ASSERT( V_ARRAYSIZE(m_arPing) == 3 ); - - // Discard oldest, insert new sample at head - m_arPing[2] = m_arPing[1]; - m_arPing[1] = m_arPing[0]; - m_arPing[0].m_nPingMS = nPingMS; - m_arPing[0].m_usecTimeRecv = usecNow; - - // Compute smoothed ping and update sample count based on existing sample size - switch ( m_nValidPings ) - { - case 0: - // First sample. Smoothed value is simply the same thing as the sample - m_nValidPings = 1; - m_nSmoothedPing = nPingMS; - break; - - case 1: - // Second sample. Smoothed value is the average - m_nValidPings = 2; - m_nSmoothedPing = ( m_arPing[0].m_nPingMS + m_arPing[1].m_nPingMS ) >> 1; - break; - - default: - AssertMsg1( false, "Unexpected valid ping count %d", m_nValidPings ); - case 2: - // Just received our final sample to complete the sample - m_nValidPings = 3; - case 3: - { - // Full sample. Take the average of the two best. Hopefully this strategy ignores a single - // ping spike, but without being too optimistic and underestimating the sustained latency too - // much. (Another option might be to use the median?) - int nMax = Max( m_arPing[0].m_nPingMS, m_arPing[1].m_nPingMS ); - nMax = Max( nMax, m_arPing[2].m_nPingMS ); - m_nSmoothedPing = ( m_arPing[0].m_nPingMS + m_arPing[1].m_nPingMS + m_arPing[2].m_nPingMS - nMax ) >> 1; - break; - } - } -} - -int PingTracker::PessimisticPingEstimate() const -{ - if ( m_nValidPings < 1 ) - { - AssertMsg( false, "Tried to make a pessimistic ping estimate without any ping data at all!" ); - return 500; - } - int nResult = m_arPing[0].m_nPingMS; - for ( int i = 1 ; i < m_nValidPings ; ++i ) - nResult = Max( nResult, m_arPing[i].m_nPingMS ); - return nResult; -} - -int PingTracker::OptimisticPingEstimate() const -{ - if ( m_nValidPings < 1 ) - { - AssertMsg( false, "Tried to make an optimistic ping estimate without any ping data at all!" ); - return 50; - } - int nResult = m_arPing[0].m_nPingMS; - for ( int i = 1 ; i < m_nValidPings ; ++i ) - nResult = Min( nResult, m_arPing[i].m_nPingMS ); - return nResult; -} - -void LinkStatsTrackerBase::InitInternal( SteamNetworkingMicroseconds usecNow ) -{ - m_nPeerProtocolVersion = 0; - m_bDisconnected = false; - m_sent.Reset(); - m_recv.Reset(); - m_recvExceedRateLimit.Reset(); - m_ping.Reset(); - m_nNextSendSequenceNumber = 1; - m_usecTimeLastSentSeq = 0; - InitMaxRecvPktNum( 0 ); - m_flInPacketsDroppedPct = -1.0f; - m_usecMaxJitterPreviousInterval = -1; - m_flInPacketsWeirdSequencePct = -1.0f; - m_nPktsRecvSequenced = 0; - m_nPktsRecvDropped = 0; - m_nPktsRecvOutOfOrder = 0; - m_nPktsRecvDuplicate = 0; - m_nPktsRecvSequenceNumberLurch = 0; - m_usecTimeLastRecv = 0; - m_usecTimeLastRecvSeq = 0; - memset( &m_latestRemote, 0, sizeof(m_latestRemote) ); - m_usecTimeRecvLatestRemote = 0; - memset( &m_lifetimeRemote, 0, sizeof(m_lifetimeRemote) ); - m_usecTimeRecvLifetimeRemote = 0; - //m_seqnumUnackedSentLifetime = -1; - //m_seqnumPendingAckRecvTimelife = -1; - m_qualityHistogram.Reset(); - m_qualitySample.Clear(); - m_jitterHistogram.Reset(); -} - -void LinkStatsTrackerBase::SetDisconnectedInternal( bool bFlag, SteamNetworkingMicroseconds usecNow ) -{ - m_bDisconnected = bFlag; - - m_pktNumInFlight = 0; - m_bInFlightInstantaneous = false; - m_bInFlightLifetime = false; - PeerAckedInstantaneous( usecNow ); - PeerAckedLifetime( usecNow ); - - // Clear acks we expect, on either state change. - m_usecInFlightReplyTimeout = 0; - m_usecLastSendPacketExpectingImmediateReply = 0; - m_nReplyTimeoutsSinceLastRecv = 0; - m_usecWhenTimeoutStarted = 0; - - if ( !bFlag ) - { - StartNextInterval( usecNow ); - } -} - -void LinkStatsTrackerBase::StartNextInterval( SteamNetworkingMicroseconds usecNow ) -{ - m_nPktsRecvSequencedCurrentInterval = 0; - m_nPktsRecvDroppedCurrentInterval = 0; - m_nPktsRecvWeirdSequenceCurrentInterval = 0; - m_usecMaxJitterCurrentInterval = -1; - m_usecIntervalStart = usecNow; -} - -void LinkStatsTrackerBase::ThinkInternal( SteamNetworkingMicroseconds usecNow ) -{ - // Check for ending the current QoS interval - if ( !m_bDisconnected && m_usecIntervalStart + k_usecSteamDatagramLinkStatsDefaultInterval < usecNow ) - { - UpdateInterval( usecNow ); - } - - // Check for reply timeout that we count. (We intentionally only allow - // one of this type of timeout to be in flight at a time, so that the max - // rate that we accumulate them is based on the ping time, instead of the packet - // rate. - if ( m_usecInFlightReplyTimeout > 0 && m_usecInFlightReplyTimeout < usecNow ) - { - m_usecInFlightReplyTimeout = 0; - if ( m_usecWhenTimeoutStarted == 0 ) - { - Assert( m_nReplyTimeoutsSinceLastRecv == 0 ); - m_usecWhenTimeoutStarted = usecNow; - } - ++m_nReplyTimeoutsSinceLastRecv; - } -} - -void LinkStatsTrackerBase::UpdateInterval( SteamNetworkingMicroseconds usecNow ) -{ - float flElapsed = int64( usecNow - m_usecIntervalStart ) * 1e-6; - flElapsed = Max( flElapsed, .001f ); // make sure math doesn't blow up - - // Check if enough happened in this interval to make a meaningful judgment about connection quality - COMPILE_TIME_ASSERT( k_usecSteamDatagramLinkStatsDefaultInterval >= 5*k_nMillion ); - if ( flElapsed > 4.5f ) - { - if ( m_nPktsRecvSequencedCurrentInterval > 5 ) - { - int nBad = m_nPktsRecvDroppedCurrentInterval + m_nPktsRecvWeirdSequenceCurrentInterval; - if ( nBad == 0 ) - { - // Perfect connection. This will hopefully be relatively common - m_qualitySample.AddSample( 100 ); - ++m_qualityHistogram.m_n100; - } - else - { - - // Less than perfect. Compute quality metric. - int nTotalSent = m_nPktsRecvSequencedCurrentInterval + m_nPktsRecvDroppedCurrentInterval; - int nRecvGood = m_nPktsRecvSequencedCurrentInterval - m_nPktsRecvWeirdSequenceCurrentInterval; - int nQuality = nRecvGood * 100 / nTotalSent; - - // Cap at 99, since 100 is reserved to mean "perfect", - // I don't think it's possible for the calculation above to ever produce 100, but whatever. - if ( nQuality >= 99 ) - { - m_qualitySample.AddSample( 99 ); - ++m_qualityHistogram.m_n99; - } - else if ( nQuality <= 1 ) // in case accounting is hosed or every single packet was out of order, clamp. 0 means "totally dead connection" - { - m_qualitySample.AddSample( 1 ); - ++m_qualityHistogram.m_n1; - } - else - { - m_qualitySample.AddSample( nQuality ); - if ( nQuality >= 97 ) - ++m_qualityHistogram.m_n97; - else if ( nQuality >= 95 ) - ++m_qualityHistogram.m_n95; - else if ( nQuality >= 90 ) - ++m_qualityHistogram.m_n90; - else if ( nQuality >= 75 ) - ++m_qualityHistogram.m_n75; - else if ( nQuality >= 50 ) - ++m_qualityHistogram.m_n50; - else - ++m_qualityHistogram.m_n1; - } - } - } - else if ( m_recv.m_packets.m_nCurrentInterval == 0 && m_sent.m_packets.m_nCurrentInterval > (int64)( flElapsed ) && m_nReplyTimeoutsSinceLastRecv >= 2 ) - { - COMPILE_TIME_ASSERT( k_usecSteamDatagramClientPingTimeout + k_usecSteamDatagramRouterPendClientPing < k_nMillion ); - - // He's dead, Jim. But we've been trying pretty hard to talk to him, so it probably isn't - // because the connection is just idle or shutting down. The connection has probably - // dropped. - m_qualitySample.AddSample(0); - ++m_qualityHistogram.m_nDead; - } - } - - // PacketRate class does most of the work - m_sent.UpdateInterval( flElapsed ); - m_recv.UpdateInterval( flElapsed ); - m_recvExceedRateLimit.UpdateInterval( flElapsed ); - - // Calculate rate of packet flow imperfections - Assert( m_nPktsRecvWeirdSequenceCurrentInterval <= m_nPktsRecvSequencedCurrentInterval ); - if ( m_nPktsRecvSequencedCurrentInterval <= 0 ) - { - // No sequenced packets received during interval, so no data available - m_flInPacketsDroppedPct = -1.0f; - m_flInPacketsWeirdSequencePct = -1.0f; - } - else - { - float flToPct = 1.0f / float( m_nPktsRecvSequencedCurrentInterval + m_nPktsRecvDroppedCurrentInterval ); - m_flInPacketsDroppedPct = m_nPktsRecvDroppedCurrentInterval * flToPct; - m_flInPacketsWeirdSequencePct = m_nPktsRecvWeirdSequenceCurrentInterval * flToPct; - } - - // Peak jitter value - m_usecMaxJitterPreviousInterval = m_usecMaxJitterCurrentInterval; - - // Reset for next time - StartNextInterval( usecNow ); -} - -void LinkStatsTrackerBase::InitMaxRecvPktNum( int64 nPktNum ) -{ - Assert( nPktNum >= 0 ); - m_nMaxRecvPktNum = nPktNum; - - // Set bits, to mark that all values <= this packet number have been - // received. - m_recvPktNumberMask[0] = ~(uint64)0; - unsigned nBitsToSet = (unsigned)( nPktNum & 63 ) + 1; - if ( nBitsToSet == 64 ) - m_recvPktNumberMask[1] = ~(uint64)0; - else - m_recvPktNumberMask[1] = ( (uint64)1 << nBitsToSet ) - 1; -} - -bool LinkStatsTrackerBase::BCheckPacketNumberOldOrDuplicate( int64 nPktNum ) -{ - // We've received a packet with a sequence number. - // Update stats - ++m_nPktsRecvSequencedCurrentInterval; - ++m_nPktsRecvSequenced; - ++m_nPktsRecvSeqSinceSentLifetime; - ++m_nPktsRecvSeqSinceSentInstantaneous; - - // Packet number is increasing? - // (Maybe by a lot -- we don't handle that here.) - if ( nPktNum > m_nMaxRecvPktNum ) - return true; - - // Which block of 64-bit packets is it in? - int64 B = m_nMaxRecvPktNum & ~int64{63}; - int64 idxRecvBitmask = ( ( nPktNum - B ) >> 6 ) + 1; - Assert( idxRecvBitmask < 2 ); - if ( idxRecvBitmask < 0 ) - { - // Too old (at least 64 packets old, maybe up to 128). - // Track stats, both lifetime and current interval - ++m_nPktsRecvSequenceNumberLurch; // Should we track this under a different stat? - ++m_nPktsRecvWeirdSequenceCurrentInterval; - return false; - } - uint64 bit = uint64{1} << ( nPktNum & 63 ); - if ( m_recvPktNumberMask[ idxRecvBitmask ] & bit ) - { - // Duplicate - // Track stats, both lifetime and current interval - ++m_nPktsRecvDuplicate; - ++m_nPktsRecvWeirdSequenceCurrentInterval; - return false; - } - - // We have an out of order packet. We'll update that - // stat in TrackProcessSequencedPacket - Assert( nPktNum > 0 && nPktNum < m_nMaxRecvPktNum ); - return true; -} - -void LinkStatsTrackerBase::TrackProcessSequencedPacket( int64 nPktNum, SteamNetworkingMicroseconds usecNow, int usecSenderTimeSincePrev ) -{ - Assert( nPktNum > 0 ); - - // Update bitfield of received packets - int64 B = m_nMaxRecvPktNum & ~int64{63}; - int64 idxRecvBitmask = ( ( nPktNum - B ) >> 6 ) + 1; - Assert( idxRecvBitmask >= 0 ); // We should have discarded very old packets already - if ( idxRecvBitmask >= 2 ) // Most common case is 0 or 1 - { - if ( idxRecvBitmask == 2 ) - { - // Crossed to the next 64-packet block. Shift bitmasks forward by one. - m_recvPktNumberMask[0] = m_recvPktNumberMask[1]; - } - else - { - // Large packet number jump, we skipped a whole block - m_recvPktNumberMask[0] = 0; - } - m_recvPktNumberMask[1] = 0; - idxRecvBitmask = 1; - } - uint64 bit = uint64{1} << ( nPktNum & 63 ); - Assert( !( m_recvPktNumberMask[ idxRecvBitmask ] & bit ) ); // Should not have already been marked! We should have already discarded duplicates - m_recvPktNumberMask[ idxRecvBitmask ] |= bit; - - // Check for dropped packet. Since we hope that by far the most common - // case will be packets delivered in order, we optimize this logic - // for that case. - int64 nGap = nPktNum - m_nMaxRecvPktNum; - if ( nGap == 1 ) - { - - // We've received two packets, in order. Did the sender supply the time between packets on his side? - if ( usecSenderTimeSincePrev > 0 ) - { - int usecJitter = ( usecNow - m_usecTimeLastRecvSeq ) - usecSenderTimeSincePrev; - usecJitter = abs( usecJitter ); - if ( usecJitter < k_usecTimeSinceLastPacketMaxReasonable ) - { - - // Update max jitter for current interval - m_usecMaxJitterCurrentInterval = Max( m_usecMaxJitterCurrentInterval, usecJitter ); - m_jitterHistogram.AddSample( usecJitter ); - } - else - { - // Something is really, really off. Discard measurement - } - } - - } - else - { - // Classify imperfection based on gap size. - if ( nGap >= 100 ) - { - // Very weird. - ++m_nPktsRecvSequenceNumberLurch; - ++m_nPktsRecvWeirdSequenceCurrentInterval; - - // Continue to code below, reseting the sequence number - // for packets going forward. - } - else if ( nGap > 0 ) - { - // Probably the most common case, we just dropped a packet - int nDropped = nGap-1; - m_nPktsRecvDropped += nDropped; - m_nPktsRecvDroppedCurrentInterval += nDropped; - } - else if ( nGap == 0 ) - { - // We should have already rejected duplicates - Assert( false ); - } - else - { - // Packet number moving in reverse. - // It should be a *small* negative step, e.g. packets delivered out of order. - // If the packet is really old, we should have already discarded it earlier. - Assert( nGap >= -8 * (int64)sizeof(m_recvPktNumberMask) ); - ++m_nPktsRecvOutOfOrder; - ++m_nPktsRecvWeirdSequenceCurrentInterval; - - // We previously counted this packet as dropped. Undo that, it wasn't dropped. - if ( m_nPktsRecvDropped > 0 ) - { - --m_nPktsRecvDropped; - } - else - { - // This is weird. - AssertMsg2( false, "No dropped packets, but pkt num %lld -> %lld and bit is not set?", (long long)m_nMaxRecvPktNum, (long long)nPktNum ); - } - if ( m_nPktsRecvDroppedCurrentInterval > 0 ) // Might have marked it in the previous interval. Our stats will be slightly off in this case. Not worth it tro try to get this exactly right. - --m_nPktsRecvDroppedCurrentInterval; - - } - } - - // Save highest known sequence number for next time. - if ( nGap > 0 ) - { - m_nMaxRecvPktNum += nGap; - m_usecTimeLastRecvSeq = usecNow; - } -} - -bool LinkStatsTrackerBase::BCheckHaveDataToSendInstantaneous( SteamNetworkingMicroseconds usecNow ) -{ - Assert( !m_bDisconnected ); - - // How many packets a second to we expect to send on an "active" connection? - const int64 k_usecActiveConnectionSendInterval = 3*k_nMillion; - COMPILE_TIME_ASSERT( k_usecSteamDatagramClientPingTimeout*2 < k_usecActiveConnectionSendInterval ); - COMPILE_TIME_ASSERT( k_usecSteamDatagramClientBackupRouterKeepaliveInterval > k_usecActiveConnectionSendInterval*5 ); // make sure backup keepalive interval isn't anywhere near close enough to trigger this. - - // Calculate threshold based on how much time has elapsed and a very low packet rate - int64 usecElapsed = usecNow - m_usecPeerAckedInstaneous; - Assert( usecElapsed >= k_usecLinkStatsInstantaneousReportMinInterval ); // don't call this unless you know it's been long enough! - int nThreshold = usecElapsed / k_usecActiveConnectionSendInterval; - - // Have they been trying to talk to us? - if ( m_nPktsRecvSeqSinceSentInstantaneous > nThreshold || m_nPktsSentSinceSentInstantaneous > nThreshold ) - return true; - - // Connection has been idle since the last time we sent instantaneous stats. - // Don't actually send stats, but clear counters and timers and act like we did. - PeerAckedInstantaneous( usecNow ); - - // And don't send anything - return false; -} - -bool LinkStatsTrackerBase::BCheckHaveDataToSendLifetime( SteamNetworkingMicroseconds usecNow ) -{ - Assert( !m_bDisconnected ); - - // Make sure we have something new to report since the last time we sent stats - if ( m_nPktsRecvSeqSinceSentLifetime > 100 || m_nPktsSentSinceSentLifetime > 100 ) - return true; - - // Reset the timer. But do NOT reset the packet counters. So if the connection isn't - // dropped, and we are sending keepalives very slowly, this will just send some stats - // along about every 100 packets or so. Typically we'll drop the session before that - // happens. - m_usecPeerAckedLifetime = usecNow; - - // Don't send anything now - return false; -} - -bool LinkStatsTrackerBase::BNeedToSendStats( SteamNetworkingMicroseconds usecNow ) -{ - // Message already in flight? - if ( m_pktNumInFlight != 0 || m_bDisconnected ) - return false; - bool bNeedToSendInstantaneous = ( m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMaxInterval < usecNow ) && BCheckHaveDataToSendInstantaneous( usecNow ); - bool bNeedToSendLifetime = ( m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportMaxInterval < usecNow ) && BCheckHaveDataToSendLifetime( usecNow ); - return bNeedToSendInstantaneous || bNeedToSendLifetime; -} - -const char *LinkStatsTrackerBase::NeedToSendStats( SteamNetworkingMicroseconds usecNow, const char *const arpszReasonStrings[4] ) -{ - // Message already in flight? - if ( m_pktNumInFlight != 0 || m_bDisconnected ) - return nullptr; - int n = 0; - if ( m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMaxInterval < usecNow && BCheckHaveDataToSendInstantaneous( usecNow ) ) - n |= 1; - if ( m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportMaxInterval < usecNow && BCheckHaveDataToSendLifetime( usecNow ) ) - n |= 2; - return arpszReasonStrings[n]; -} - -SteamNetworkingMicroseconds LinkStatsTrackerBase::GetNextThinkTimeInternal( SteamNetworkingMicroseconds usecNow ) const -{ - SteamNetworkingMicroseconds usecResult = INT64_MAX; - if ( !m_bDisconnected ) - { - - // Expecting a reply? - if ( m_usecInFlightReplyTimeout ) - { - usecResult = std::min( usecResult, m_usecInFlightReplyTimeout ); - } - else if ( m_usecTimeLastRecv ) - { - // Time when BNeedToSendKeepalive will return true - usecResult = std::min( usecResult, m_usecTimeLastRecv + k_usecKeepAliveInterval ); - } - - // Time when BNeedToSendPingImmediate will return true - if ( m_nReplyTimeoutsSinceLastRecv > 0 ) - usecResult = std::min( usecResult, m_usecLastSendPacketExpectingImmediateReply+k_usecAggressivePingInterval ); - - // Time when we need to flush stats - if ( m_pktNumInFlight == 0 ) - { - usecResult = std::min( usecResult, m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMaxInterval ); - usecResult = std::min( usecResult, m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportMaxInterval ); - } - } - - return usecResult; -} - -void LinkStatsTrackerBase::PopulateMessage( CMsgSteamDatagramConnectionQuality &msg, SteamNetworkingMicroseconds usecNow ) -{ - if ( m_pktNumInFlight == 0 && !m_bDisconnected ) - { - - // Ready to send instantaneous stats? - if ( m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMinInterval < usecNow && BCheckHaveDataToSendInstantaneous( usecNow ) ) - { - // !KLUDGE! Go through public struct as intermediary to keep code simple. - SteamDatagramLinkInstantaneousStats sInstant; - GetInstantaneousStats( sInstant ); - LinkStatsInstantaneousStructToMsg( sInstant, *msg.mutable_instantaneous() ); - } - - // Ready to send lifetime stats? - if ( m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportMinInterval < usecNow && BCheckHaveDataToSendLifetime( usecNow ) ) - { - // !KLUDGE! Go through public struct as intermediary to keep code simple. - SteamDatagramLinkLifetimeStats sLifetime; - GetLifetimeStats( sLifetime ); - LinkStatsLifetimeStructToMsg( sLifetime, *msg.mutable_lifetime() ); - } - } -} - -void LinkStatsTrackerBase::TrackSentMessageExpectingReply( SteamNetworkingMicroseconds usecNow, bool bAllowDelayedReply ) -{ - if ( m_usecInFlightReplyTimeout == 0 ) - { - m_usecInFlightReplyTimeout = usecNow + m_ping.CalcConservativeTimeout(); - if ( bAllowDelayedReply ) - m_usecInFlightReplyTimeout += k_usecSteamDatagramRouterPendClientPing; - } - if ( !bAllowDelayedReply ) - m_usecLastSendPacketExpectingImmediateReply = usecNow; -} - -void LinkStatsTrackerBase::ProcessMessage( const CMsgSteamDatagramConnectionQuality &msg, SteamNetworkingMicroseconds usecNow ) -{ - if ( msg.has_instantaneous() ) - { - LinkStatsInstantaneousMsgToStruct( msg.instantaneous(), m_latestRemote ); - m_usecTimeRecvLatestRemote = usecNow; - } - if ( msg.has_lifetime() ) - { - LinkStatsLifetimeMsgToStruct( msg.lifetime(), m_lifetimeRemote ); - m_usecTimeRecvLifetimeRemote = usecNow; - } -} - -void LinkStatsTrackerBase::GetInstantaneousStats( SteamDatagramLinkInstantaneousStats &s ) const -{ - s.m_flOutPacketsPerSec = m_sent.m_packets.m_flRate; - s.m_flOutBytesPerSec = m_sent.m_bytes.m_flRate; - s.m_flInPacketsPerSec = m_recv.m_packets.m_flRate; - s.m_flInBytesPerSec = m_recv.m_bytes.m_flRate; - s.m_nPingMS = m_ping.m_nSmoothedPing; - s.m_flPacketsDroppedPct = m_flInPacketsDroppedPct; - s.m_flPacketsWeirdSequenceNumberPct = m_flInPacketsWeirdSequencePct; - s.m_usecMaxJitter = m_usecMaxJitterPreviousInterval; -} - -void LinkStatsTrackerBase::GetLifetimeStats( SteamDatagramLinkLifetimeStats &s ) const -{ - s.m_nPacketsSent = m_sent.m_packets.m_nTotal; - s.m_nBytesSent = m_sent.m_bytes.m_nTotal; - s.m_nPacketsRecv = m_recv.m_packets.m_nTotal; - s.m_nBytesRecv = m_recv.m_bytes.m_nTotal; - s.m_nPktsRecvSequenced = m_nPktsRecvSequenced; - s.m_nPktsRecvDropped = m_nPktsRecvDropped; - s.m_nPktsRecvOutOfOrder = m_nPktsRecvOutOfOrder; - s.m_nPktsRecvDuplicate = m_nPktsRecvDuplicate; - s.m_nPktsRecvSequenceNumberLurch = m_nPktsRecvSequenceNumberLurch; - - s.m_qualityHistogram = m_qualityHistogram; - - s.m_nQualityNtile50th = m_qualitySample.NumSamples() < 2 ? -1 : m_qualitySample.GetPercentile( .50f ); - s.m_nQualityNtile25th = m_qualitySample.NumSamples() < 4 ? -1 : m_qualitySample.GetPercentile( .25f ); - s.m_nQualityNtile5th = m_qualitySample.NumSamples() < 20 ? -1 : m_qualitySample.GetPercentile( .05f ); - s.m_nQualityNtile2nd = m_qualitySample.NumSamples() < 50 ? -1 : m_qualitySample.GetPercentile( .02f ); - - m_ping.GetLifetimeStats( s ); - - s.m_jitterHistogram = m_jitterHistogram; - - // - // Clear all end-to-end values - // - - s.m_nTXSpeedMax = -1; - - s.m_nTXSpeedHistogram16 = 0; - s.m_nTXSpeedHistogram32 = 0; - s.m_nTXSpeedHistogram64 = 0; - s.m_nTXSpeedHistogram128 = 0; - s.m_nTXSpeedHistogram256 = 0; - s.m_nTXSpeedHistogram512 = 0; - s.m_nTXSpeedHistogram1024 = 0; - s.m_nTXSpeedHistogramMax = 0; - - s.m_nTXSpeedNtile5th = -1; - s.m_nTXSpeedNtile50th = -1; - s.m_nTXSpeedNtile75th = -1; - s.m_nTXSpeedNtile95th = -1; - s.m_nTXSpeedNtile98th = -1; - - s.m_nRXSpeedMax = -1; - - s.m_nRXSpeedHistogram16 = 0; - s.m_nRXSpeedHistogram32 = 0; - s.m_nRXSpeedHistogram64 = 0; - s.m_nRXSpeedHistogram128 = 0; - s.m_nRXSpeedHistogram256 = 0; - s.m_nRXSpeedHistogram512 = 0; - s.m_nRXSpeedHistogram1024 = 0; - s.m_nRXSpeedHistogramMax = 0; - - s.m_nRXSpeedNtile5th = -1; - s.m_nRXSpeedNtile50th = -1; - s.m_nRXSpeedNtile75th = -1; - s.m_nRXSpeedNtile95th = -1; - s.m_nRXSpeedNtile98th = -1; -} - -void LinkStatsTrackerBase::GetLinkStats( SteamDatagramLinkStats &s, SteamNetworkingMicroseconds usecNow ) const -{ - GetInstantaneousStats( s.m_latest ); - GetLifetimeStats( s.m_lifetime ); - - if ( m_usecTimeRecvLatestRemote ) - { - s.m_latestRemote = m_latestRemote; - s.m_flAgeLatestRemote = ( usecNow - m_usecTimeRecvLatestRemote ) * 1e-6; - } - else - { - s.m_latestRemote.Clear(); - s.m_flAgeLatestRemote = -1.0f; - } - - if ( m_usecTimeRecvLifetimeRemote ) - { - s.m_lifetimeRemote = m_lifetimeRemote; - s.m_flAgeLifetimeRemote = ( usecNow - m_usecTimeRecvLifetimeRemote ) * 1e-6; - } - else - { - s.m_lifetimeRemote.Clear(); - s.m_flAgeLifetimeRemote = -1.0f; - } -} - -void LinkStatsTrackerEndToEnd::InitInternal( SteamNetworkingMicroseconds usecNow ) -{ - LinkStatsTrackerBase::InitInternal( usecNow ); - - m_TXSpeedSample.Clear(); - m_nTXSpeed = 0; - m_nTXSpeedHistogram16 = 0; // Speed at kb/s - m_nTXSpeedHistogram32 = 0; - m_nTXSpeedHistogram64 = 0; - m_nTXSpeedHistogram128 = 0; - m_nTXSpeedHistogram256 = 0; - m_nTXSpeedHistogram512 = 0; - m_nTXSpeedHistogram1024 = 0; - m_nTXSpeedHistogramMax = 0; - - m_RXSpeedSample.Clear(); - m_nRXSpeed = 0; - m_nRXSpeedHistogram16 = 0; // Speed at kb/s - m_nRXSpeedHistogram32 = 0; - m_nRXSpeedHistogram64 = 0; - m_nRXSpeedHistogram128 = 0; - m_nRXSpeedHistogram256 = 0; - m_nRXSpeedHistogram512 = 0; - m_nRXSpeedHistogram1024 = 0; - m_nRXSpeedHistogramMax = 0; - - StartNextSpeedInterval( usecNow ); -} - -void LinkStatsTrackerEndToEnd::ThinkInternal( SteamNetworkingMicroseconds usecNow ) -{ - LinkStatsTrackerBase::ThinkInternal( usecNow ); - - if ( m_usecSpeedIntervalStart + k_usecSteamDatagramSpeedStatsDefaultInterval < usecNow ) - { - UpdateSpeedInterval( usecNow ); - } -} - -void LinkStatsTrackerEndToEnd::StartNextSpeedInterval( SteamNetworkingMicroseconds usecNow ) -{ - m_usecSpeedIntervalStart = usecNow; -} - -void LinkStatsTrackerEndToEnd::UpdateSpeedInterval( SteamNetworkingMicroseconds usecNow ) -{ - float flElapsed = int64( usecNow - m_usecIntervalStart ) * 1e-6; - flElapsed = Max( flElapsed, .001f ); // make sure math doesn't blow up - - int nTXKBs = ( m_nTXSpeed + 512 ) / 1024; - m_TXSpeedSample.AddSample( nTXKBs ); - - if ( nTXKBs <= 16 ) ++m_nTXSpeedHistogram16; - else if ( nTXKBs <= 32 ) ++m_nTXSpeedHistogram32; - else if ( nTXKBs <= 64 ) ++m_nTXSpeedHistogram64; - else if ( nTXKBs <= 128 ) ++m_nTXSpeedHistogram128; - else if ( nTXKBs <= 256 ) ++m_nTXSpeedHistogram256; - else if ( nTXKBs <= 512 ) ++m_nTXSpeedHistogram512; - else if ( nTXKBs <= 1024 ) ++m_nTXSpeedHistogram1024; - else ++m_nTXSpeedHistogramMax; - - int nRXKBs = ( m_nRXSpeed + 512 ) / 1024; - m_RXSpeedSample.AddSample( nRXKBs ); - - if ( nRXKBs <= 16 ) ++m_nRXSpeedHistogram16; - else if ( nRXKBs <= 32 ) ++m_nRXSpeedHistogram32; - else if ( nRXKBs <= 64 ) ++m_nRXSpeedHistogram64; - else if ( nRXKBs <= 128 ) ++m_nRXSpeedHistogram128; - else if ( nRXKBs <= 256 ) ++m_nRXSpeedHistogram256; - else if ( nRXKBs <= 512 ) ++m_nRXSpeedHistogram512; - else if ( nRXKBs <= 1024 ) ++m_nRXSpeedHistogram1024; - else ++m_nRXSpeedHistogramMax; - - // Reset for next time - StartNextSpeedInterval( usecNow ); -} - -void LinkStatsTrackerEndToEnd::UpdateSpeeds( int nTXSpeed, int nRXSpeed ) -{ - m_nTXSpeed = nTXSpeed; - m_nRXSpeed = nRXSpeed; - - m_nTXSpeedMax = Max( m_nTXSpeedMax, nTXSpeed ); - m_nRXSpeedMax = Max( m_nRXSpeedMax, nRXSpeed ); -} - -void LinkStatsTrackerEndToEnd::GetLifetimeStats( SteamDatagramLinkLifetimeStats &s ) const -{ - LinkStatsTrackerBase::GetLifetimeStats(s); - - s.m_nTXSpeedMax = m_nTXSpeedMax; - - s.m_nTXSpeedHistogram16 = m_nTXSpeedHistogram16; - s.m_nTXSpeedHistogram32 = m_nTXSpeedHistogram32; - s.m_nTXSpeedHistogram64 = m_nTXSpeedHistogram64; - s.m_nTXSpeedHistogram128 = m_nTXSpeedHistogram128; - s.m_nTXSpeedHistogram256 = m_nTXSpeedHistogram256; - s.m_nTXSpeedHistogram512 = m_nTXSpeedHistogram512; - s.m_nTXSpeedHistogram1024 = m_nTXSpeedHistogram1024; - s.m_nTXSpeedHistogramMax = m_nTXSpeedHistogramMax; - - s.m_nTXSpeedNtile5th = m_TXSpeedSample.NumSamples() < 20 ? -1 : m_TXSpeedSample.GetPercentile( .05f ); - s.m_nTXSpeedNtile50th = m_TXSpeedSample.NumSamples() < 2 ? -1 : m_TXSpeedSample.GetPercentile( .50f ); - s.m_nTXSpeedNtile75th = m_TXSpeedSample.NumSamples() < 4 ? -1 : m_TXSpeedSample.GetPercentile( .75f ); - s.m_nTXSpeedNtile95th = m_TXSpeedSample.NumSamples() < 20 ? -1 : m_TXSpeedSample.GetPercentile( .95f ); - s.m_nTXSpeedNtile98th = m_TXSpeedSample.NumSamples() < 50 ? -1 : m_TXSpeedSample.GetPercentile( .98f ); - - s.m_nRXSpeedMax = m_nRXSpeedMax; - - s.m_nRXSpeedHistogram16 = m_nRXSpeedHistogram16; - s.m_nRXSpeedHistogram32 = m_nRXSpeedHistogram32; - s.m_nRXSpeedHistogram64 = m_nRXSpeedHistogram64; - s.m_nRXSpeedHistogram128 = m_nRXSpeedHistogram128; - s.m_nRXSpeedHistogram256 = m_nRXSpeedHistogram256; - s.m_nRXSpeedHistogram512 = m_nRXSpeedHistogram512; - s.m_nRXSpeedHistogram1024 = m_nRXSpeedHistogram1024; - s.m_nRXSpeedHistogramMax = m_nRXSpeedHistogramMax; - - s.m_nRXSpeedNtile5th = m_RXSpeedSample.NumSamples() < 20 ? -1 : m_RXSpeedSample.GetPercentile( .05f ); - s.m_nRXSpeedNtile50th = m_RXSpeedSample.NumSamples() < 2 ? -1 : m_RXSpeedSample.GetPercentile( .50f ); - s.m_nRXSpeedNtile75th = m_RXSpeedSample.NumSamples() < 4 ? -1 : m_RXSpeedSample.GetPercentile( .75f ); - s.m_nRXSpeedNtile95th = m_RXSpeedSample.NumSamples() < 20 ? -1 : m_RXSpeedSample.GetPercentile( .95f ); - s.m_nRXSpeedNtile98th = m_RXSpeedSample.NumSamples() < 50 ? -1 : m_RXSpeedSample.GetPercentile( .98f ); -} - namespace SteamNetworkingSocketsLib { -void LinkStatsInstantaneousStructToMsg( const SteamDatagramLinkInstantaneousStats &s, CMsgSteamDatagramLinkInstantaneousStats &msg ) -{ - msg.set_out_packets_per_sec_x10( uint32( s.m_flOutPacketsPerSec * 10.0f ) ); - msg.set_out_bytes_per_sec( uint32( s.m_flOutBytesPerSec ) ); - msg.set_in_packets_per_sec_x10( uint32( s.m_flInPacketsPerSec * 10.0f ) ); - msg.set_in_bytes_per_sec( uint32( s.m_flInBytesPerSec ) ); - if ( s.m_nPingMS >= 0 ) - msg.set_ping_ms( uint32( s.m_nPingMS ) ); - if ( s.m_flPacketsDroppedPct >= 0.0f ) - msg.set_packets_dropped_pct( uint32( s.m_flPacketsDroppedPct * 100.0f ) ); - if ( s.m_flPacketsWeirdSequenceNumberPct >= 0.0f ) - msg.set_packets_weird_sequence_pct( uint32( s.m_flPacketsWeirdSequenceNumberPct * 100.0f ) ); - if ( s.m_usecMaxJitter >= 0 ) - msg.set_peak_jitter_usec( s.m_usecMaxJitter ); -} - -void LinkStatsInstantaneousMsgToStruct( const CMsgSteamDatagramLinkInstantaneousStats &msg, SteamDatagramLinkInstantaneousStats &s ) -{ - s.m_flOutPacketsPerSec = msg.out_packets_per_sec_x10() * .1f; - s.m_flOutBytesPerSec = msg.out_bytes_per_sec(); - s.m_flInPacketsPerSec = msg.in_packets_per_sec_x10() * .1f; - s.m_flInBytesPerSec = msg.in_bytes_per_sec(); - if ( msg.has_ping_ms() ) - s.m_nPingMS = msg.ping_ms(); - else - s.m_nPingMS = -1; - - if ( msg.has_packets_dropped_pct() ) - s.m_flPacketsDroppedPct = msg.packets_dropped_pct() * .01f; - else - s.m_flPacketsDroppedPct = -1.0f; - - if ( msg.has_packets_weird_sequence_pct() ) - s.m_flPacketsWeirdSequenceNumberPct = msg.packets_weird_sequence_pct() * .01f; - else - s.m_flPacketsWeirdSequenceNumberPct = -1.0f; - - if ( msg.has_peak_jitter_usec() ) - s.m_usecMaxJitter = msg.peak_jitter_usec(); - else - s.m_usecMaxJitter = -1; - -} - -void LinkStatsLifetimeStructToMsg( const SteamDatagramLinkLifetimeStats &s, CMsgSteamDatagramLinkLifetimeStats &msg ) -{ - msg.set_packets_sent( s.m_nPacketsSent ); - msg.set_kb_sent( ( s.m_nBytesSent + 512 ) / 1024 ); - msg.set_packets_recv( s.m_nPacketsRecv ); - msg.set_kb_recv( ( s.m_nBytesRecv + 512 ) / 1024 ); - msg.set_packets_recv_sequenced( s.m_nPktsRecvSequenced ); - msg.set_packets_recv_dropped( s.m_nPktsRecvDropped ); - msg.set_packets_recv_out_of_order( s.m_nPktsRecvOutOfOrder ); - msg.set_packets_recv_duplicate( s.m_nPktsRecvDuplicate ); - msg.set_packets_recv_lurch( s.m_nPktsRecvSequenceNumberLurch ); - - #define SET_HISTOGRAM( mbr, field ) if ( mbr > 0 ) msg.set_ ## field( mbr ); - #define SET_NTILE( mbr, field ) if ( mbr >= 0 ) msg.set_ ## field( mbr ); - - SET_HISTOGRAM( s.m_qualityHistogram.m_n100 , quality_histogram_100 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n99 , quality_histogram_99 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n97 , quality_histogram_97 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n95 , quality_histogram_95 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n90 , quality_histogram_90 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n75 , quality_histogram_75 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n50 , quality_histogram_50 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n1 , quality_histogram_1 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_nDead, quality_histogram_dead ) - - SET_NTILE( s.m_nQualityNtile50th, quality_ntile_50th ) - SET_NTILE( s.m_nQualityNtile25th, quality_ntile_25th ) - SET_NTILE( s.m_nQualityNtile5th , quality_ntile_5th ) - SET_NTILE( s.m_nQualityNtile2nd , quality_ntile_2nd ) - - SET_HISTOGRAM( s.m_pingHistogram.m_n25 , ping_histogram_25 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n50 , ping_histogram_50 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n75 , ping_histogram_75 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n100, ping_histogram_100 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n125, ping_histogram_125 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n150, ping_histogram_150 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n200, ping_histogram_200 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n300, ping_histogram_300 ) - SET_HISTOGRAM( s.m_pingHistogram.m_nMax, ping_histogram_max ) - - SET_NTILE( s.m_nPingNtile5th , ping_ntile_5th ) - SET_NTILE( s.m_nPingNtile50th, ping_ntile_50th ) - SET_NTILE( s.m_nPingNtile75th, ping_ntile_75th ) - SET_NTILE( s.m_nPingNtile95th, ping_ntile_95th ) - SET_NTILE( s.m_nPingNtile98th, ping_ntile_98th ) - - SET_HISTOGRAM( s.m_jitterHistogram.m_nNegligible, jitter_histogram_negligible ) - SET_HISTOGRAM( s.m_jitterHistogram.m_n1, jitter_histogram_1 ) - SET_HISTOGRAM( s.m_jitterHistogram.m_n2, jitter_histogram_2 ) - SET_HISTOGRAM( s.m_jitterHistogram.m_n5, jitter_histogram_5 ) - SET_HISTOGRAM( s.m_jitterHistogram.m_n10, jitter_histogram_10 ) - SET_HISTOGRAM( s.m_jitterHistogram.m_n20, jitter_histogram_20 ) - - if ( s.m_nTXSpeedMax > 0 ) - msg.set_txspeed_max( s.m_nTXSpeedMax ); - - SET_HISTOGRAM( s.m_nTXSpeedHistogram16, txspeed_histogram_16 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogram32, txspeed_histogram_32 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogram64, txspeed_histogram_64 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogram128, txspeed_histogram_128 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogram256, txspeed_histogram_256 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogram512, txspeed_histogram_512 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogram1024, txspeed_histogram_1024 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogramMax, txspeed_histogram_max ) - - SET_NTILE( s.m_nTXSpeedNtile5th, txspeed_ntile_5th ) - SET_NTILE( s.m_nTXSpeedNtile50th, txspeed_ntile_50th ) - SET_NTILE( s.m_nTXSpeedNtile75th, txspeed_ntile_75th ) - SET_NTILE( s.m_nTXSpeedNtile95th, txspeed_ntile_95th ) - SET_NTILE( s.m_nTXSpeedNtile98th, txspeed_ntile_98th ) - - if ( s.m_nRXSpeedMax > 0 ) - msg.set_rxspeed_max( s.m_nRXSpeedMax ); - - SET_HISTOGRAM( s.m_nRXSpeedHistogram16, rxspeed_histogram_16 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogram32, rxspeed_histogram_32 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogram64, rxspeed_histogram_64 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogram128, rxspeed_histogram_128 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogram256, rxspeed_histogram_256 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogram512, rxspeed_histogram_512 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogram1024, rxspeed_histogram_1024 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogramMax, rxspeed_histogram_max ) - - SET_NTILE( s.m_nRXSpeedNtile5th, rxspeed_ntile_5th ) - SET_NTILE( s.m_nRXSpeedNtile50th, rxspeed_ntile_50th ) - SET_NTILE( s.m_nRXSpeedNtile75th, rxspeed_ntile_75th ) - SET_NTILE( s.m_nRXSpeedNtile95th, rxspeed_ntile_95th ) - SET_NTILE( s.m_nRXSpeedNtile98th, rxspeed_ntile_98th ) - - #undef SET_HISTOGRAM - #undef SET_NTILE -} - -void LinkStatsLifetimeMsgToStruct( const CMsgSteamDatagramLinkLifetimeStats &msg, SteamDatagramLinkLifetimeStats &s ) -{ - s.m_nPacketsSent = msg.packets_sent(); - s.m_nBytesSent = msg.kb_sent() * 1024; - s.m_nPacketsRecv = msg.packets_recv(); - s.m_nBytesRecv = msg.kb_recv() * 1024; - s.m_nPktsRecvSequenced = msg.packets_recv_sequenced(); - s.m_nPktsRecvDropped = msg.packets_recv_dropped(); - s.m_nPktsRecvOutOfOrder = msg.packets_recv_out_of_order(); - s.m_nPktsRecvDuplicate = msg.packets_recv_duplicate(); - s.m_nPktsRecvSequenceNumberLurch = msg.packets_recv_lurch(); - - #define SET_HISTOGRAM( mbr, field ) mbr = msg.field(); - #define SET_NTILE( mbr, field ) mbr = ( msg.has_ ## field() ? msg.field() : -1 ); - - SET_HISTOGRAM( s.m_qualityHistogram.m_n100 , quality_histogram_100 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n99 , quality_histogram_99 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n97 , quality_histogram_97 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n95 , quality_histogram_95 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n90 , quality_histogram_90 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n75 , quality_histogram_75 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n50 , quality_histogram_50 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_n1 , quality_histogram_1 ) - SET_HISTOGRAM( s.m_qualityHistogram.m_nDead, quality_histogram_dead ) - - SET_NTILE( s.m_nQualityNtile50th, quality_ntile_50th ) - SET_NTILE( s.m_nQualityNtile25th, quality_ntile_25th ) - SET_NTILE( s.m_nQualityNtile5th , quality_ntile_5th ) - SET_NTILE( s.m_nQualityNtile2nd , quality_ntile_2nd ) - - SET_HISTOGRAM( s.m_pingHistogram.m_n25 , ping_histogram_25 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n50 , ping_histogram_50 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n75 , ping_histogram_75 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n100, ping_histogram_100 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n125, ping_histogram_125 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n150, ping_histogram_150 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n200, ping_histogram_200 ) - SET_HISTOGRAM( s.m_pingHistogram.m_n300, ping_histogram_300 ) - SET_HISTOGRAM( s.m_pingHistogram.m_nMax, ping_histogram_max ) - - SET_NTILE( s.m_nPingNtile5th , ping_ntile_5th ) - SET_NTILE( s.m_nPingNtile50th, ping_ntile_50th ) - SET_NTILE( s.m_nPingNtile75th, ping_ntile_75th ) - SET_NTILE( s.m_nPingNtile95th, ping_ntile_95th ) - SET_NTILE( s.m_nPingNtile98th, ping_ntile_98th ) - - SET_HISTOGRAM( s.m_jitterHistogram.m_nNegligible, jitter_histogram_negligible ) - SET_HISTOGRAM( s.m_jitterHistogram.m_n1, jitter_histogram_1 ) - SET_HISTOGRAM( s.m_jitterHistogram.m_n2, jitter_histogram_2 ) - SET_HISTOGRAM( s.m_jitterHistogram.m_n5, jitter_histogram_5 ) - SET_HISTOGRAM( s.m_jitterHistogram.m_n10, jitter_histogram_10 ) - SET_HISTOGRAM( s.m_jitterHistogram.m_n20, jitter_histogram_20 ) - - s.m_nTXSpeedMax = msg.txspeed_max(); - - SET_HISTOGRAM( s.m_nTXSpeedHistogram16, txspeed_histogram_16 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogram32, txspeed_histogram_32 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogram64, txspeed_histogram_64 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogram128, txspeed_histogram_128 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogram256, txspeed_histogram_256 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogram512, txspeed_histogram_512 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogram1024, txspeed_histogram_1024 ) - SET_HISTOGRAM( s.m_nTXSpeedHistogramMax, txspeed_histogram_max ) - - SET_NTILE( s.m_nTXSpeedNtile5th, txspeed_ntile_5th ) - SET_NTILE( s.m_nTXSpeedNtile50th, txspeed_ntile_50th ) - SET_NTILE( s.m_nTXSpeedNtile75th, txspeed_ntile_75th ) - SET_NTILE( s.m_nTXSpeedNtile95th, txspeed_ntile_95th ) - SET_NTILE( s.m_nTXSpeedNtile98th, txspeed_ntile_98th ) - - s.m_nRXSpeedMax = msg.rxspeed_max(); - - SET_HISTOGRAM( s.m_nRXSpeedHistogram16, rxspeed_histogram_16 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogram32, rxspeed_histogram_32 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogram64, rxspeed_histogram_64 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogram128, rxspeed_histogram_128 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogram256, rxspeed_histogram_256 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogram512, rxspeed_histogram_512 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogram1024, rxspeed_histogram_1024 ) - SET_HISTOGRAM( s.m_nRXSpeedHistogramMax, rxspeed_histogram_max ) - - SET_NTILE( s.m_nRXSpeedNtile5th, rxspeed_ntile_5th ) - SET_NTILE( s.m_nRXSpeedNtile50th, rxspeed_ntile_50th ) - SET_NTILE( s.m_nRXSpeedNtile75th, rxspeed_ntile_75th ) - SET_NTILE( s.m_nRXSpeedNtile95th, rxspeed_ntile_95th ) - SET_NTILE( s.m_nRXSpeedNtile98th, rxspeed_ntile_98th ) - - #undef SET_HISTOGRAM - #undef SET_NTILE -} - -static void PrintPct( char (&szBuf)[32], float flPct ) -{ - flPct *= 100.0f; - if ( flPct < 0.0f ) - V_strcpy_safe( szBuf, "???" ); - else if ( flPct < 9.5f ) - V_sprintf_safe( szBuf, "%.2f", flPct ); - else if ( flPct < 99.5f ) - V_sprintf_safe( szBuf, "%.1f", flPct ); - else - V_sprintf_safe( szBuf, "%.0f", flPct ); -} - -void LinkStatsPrintInstantaneousToBuf( const char *pszLeader, const SteamDatagramLinkInstantaneousStats &stats, CUtlBuffer &buf ) -{ - buf.Printf( "%sSent:%6.1f pkts/sec%6.1f K/sec\n", pszLeader, stats.m_flOutPacketsPerSec, stats.m_flOutBytesPerSec/1024.0f ); - buf.Printf( "%sRecv:%6.1f pkts/sec%6.1f K/sec\n", pszLeader, stats.m_flInPacketsPerSec, stats.m_flInBytesPerSec/1024.0f ); - - if ( stats.m_nPingMS >= 0 || stats.m_usecMaxJitter >= 0 ) - { - char szPing[ 32 ]; - if ( stats.m_nPingMS < 0 ) - V_strcpy_safe( szPing, "???" ); - else - V_sprintf_safe( szPing, "%d", stats.m_nPingMS ); - - char szPeakJitter[ 32 ]; - if ( stats.m_usecMaxJitter < 0 ) - V_strcpy_safe( szPeakJitter, "???" ); - else - V_sprintf_safe( szPeakJitter, "%.1f", stats.m_usecMaxJitter*1e-3f ); - - buf.Printf( "%sPing:%sms Max latency variance: %sms\n", pszLeader, szPing, szPeakJitter ); - } - - if ( stats.m_flPacketsDroppedPct >= 0.0f && stats.m_flPacketsWeirdSequenceNumberPct >= 0.0f ) - { - char szDropped[ 32 ]; - PrintPct( szDropped, stats.m_flPacketsDroppedPct ); - - char szWeirdSeq[ 32 ]; - PrintPct( szWeirdSeq, stats.m_flPacketsWeirdSequenceNumberPct ); - - char szQuality[32]; - PrintPct( szQuality, 1.0f - stats.m_flPacketsDroppedPct - stats.m_flPacketsWeirdSequenceNumberPct ); - buf.Printf( "%sQuality:%5s%% (Dropped:%4s%% WeirdSeq:%4s%%)\n", pszLeader, szQuality, szDropped, szWeirdSeq); - } - - if ( stats.m_nSendRate > 0 ) - buf.Printf( "%sEst avail bandwidth: %.1fKB/s \n", pszLeader, stats.m_nSendRate/1024.0f ); - if ( stats.m_nPendingBytes >= 0 ) - buf.Printf( "%sBytes buffered: %s\n", pszLeader, NumberPrettyPrinter( stats.m_nPendingBytes ).String() ); -} - -void LinkStatsPrintLifetimeToBuf( const char *pszLeader, const SteamDatagramLinkLifetimeStats &stats, CUtlBuffer &buf ) -{ - buf.Printf( "%sTotals\n", pszLeader ); - buf.Printf( "%s Sent:%11s pkts %15s bytes\n", pszLeader, NumberPrettyPrinter( stats.m_nPacketsSent ).String(), NumberPrettyPrinter( stats.m_nBytesSent ).String() ); - buf.Printf( "%s Recv:%11s pkts %15s bytes\n", pszLeader, NumberPrettyPrinter( stats.m_nPacketsRecv ).String(), NumberPrettyPrinter( stats.m_nBytesRecv ).String() ); - if ( stats.m_nPktsRecvSequenced > 0 ) - { - buf.Printf( "%s Recv w seq:%11s pkts\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvSequenced ).String() ); - float flToPct = 100.0f / ( stats.m_nPktsRecvSequenced + stats.m_nPktsRecvDropped ); - buf.Printf( "%s Dropped :%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvDropped ).String(), stats.m_nPktsRecvDropped * flToPct ); - buf.Printf( "%s OutOfOrder:%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvOutOfOrder ).String(), stats.m_nPktsRecvOutOfOrder * flToPct ); - buf.Printf( "%s Duplicate :%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvDuplicate ).String(), stats.m_nPktsRecvDuplicate * flToPct ); - buf.Printf( "%s SeqLurch :%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvSequenceNumberLurch ).String(), stats.m_nPktsRecvSequenceNumberLurch * flToPct ); - } - - // Do we have enough ping samples such that the distribution might be interesting - { - int nPingSamples = stats.m_pingHistogram.TotalCount(); - if ( nPingSamples >= 5 ) - { - float flToPct = 100.0f / nPingSamples; - buf.Printf( "%sPing histogram: (%d total samples)\n", pszLeader, nPingSamples ); - buf.Printf( "%s 0-25 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n25 , stats.m_pingHistogram.m_n25 *flToPct ); - buf.Printf( "%s 25-50 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n50 , stats.m_pingHistogram.m_n50 *flToPct ); - buf.Printf( "%s 50-75 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n75 , stats.m_pingHistogram.m_n75 *flToPct ); - buf.Printf( "%s 75-100 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n100, stats.m_pingHistogram.m_n100*flToPct ); - buf.Printf( "%s 100-125 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n125, stats.m_pingHistogram.m_n125*flToPct ); - buf.Printf( "%s 125-150 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n150, stats.m_pingHistogram.m_n150*flToPct ); - buf.Printf( "%s 150-200 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n200, stats.m_pingHistogram.m_n200*flToPct ); - buf.Printf( "%s 200-300 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n300, stats.m_pingHistogram.m_n300*flToPct ); - buf.Printf( "%s 300+ :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_nMax, stats.m_pingHistogram.m_nMax*flToPct ); - buf.Printf( "%sPing distribution:\n", pszLeader ); - if ( stats.m_nPingNtile5th >= 0 ) buf.Printf( "%s 5%% of pings <= %4dms\n", pszLeader, stats.m_nPingNtile5th ); - if ( stats.m_nPingNtile50th >= 0 ) buf.Printf( "%s 50%% of pings <= %4dms\n", pszLeader, stats.m_nPingNtile50th ); - if ( stats.m_nPingNtile75th >= 0 ) buf.Printf( "%s 75%% of pings <= %4dms\n", pszLeader, stats.m_nPingNtile75th ); - if ( stats.m_nPingNtile95th >= 0 ) buf.Printf( "%s 95%% of pings <= %4dms\n", pszLeader, stats.m_nPingNtile95th ); - if ( stats.m_nPingNtile98th >= 0 ) buf.Printf( "%s 98%% of pings <= %4dms\n", pszLeader, stats.m_nPingNtile98th ); - } - else - { - buf.Printf( "%sNo ping distribution available. (%d samples)\n", pszLeader, nPingSamples ); - } - } - - // Do we have enough quality samples such that the distribution might be interesting? - { - int nQualitySamples = stats.m_qualityHistogram.TotalCount(); - if ( nQualitySamples >= 5 ) - { - float flToPct = 100.0f / nQualitySamples; - - buf.Printf( "%sConnection quality histogram: (%d measurement intervals)\n", pszLeader, nQualitySamples ); - buf.Printf( "%s 100 :%5d %3.0f%% (All packets received in order)\n", pszLeader, stats.m_qualityHistogram.m_n100, stats.m_qualityHistogram.m_n100*flToPct ); - buf.Printf( "%s 99+ :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n99, stats.m_qualityHistogram.m_n99*flToPct ); - buf.Printf( "%s 97-99 :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n97, stats.m_qualityHistogram.m_n97*flToPct ); - buf.Printf( "%s 95-97 :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n95, stats.m_qualityHistogram.m_n95*flToPct ); - buf.Printf( "%s 90-95 :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n90, stats.m_qualityHistogram.m_n90*flToPct ); - buf.Printf( "%s 75-90 :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n75, stats.m_qualityHistogram.m_n75*flToPct ); - buf.Printf( "%s 50-75 :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n50, stats.m_qualityHistogram.m_n50*flToPct ); - buf.Printf( "%s <50 :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n1, stats.m_qualityHistogram.m_n1*flToPct ); - buf.Printf( "%s dead :%5d %3.0f%% (Expected to receive something but didn't)\n", pszLeader, stats.m_qualityHistogram.m_nDead, stats.m_qualityHistogram.m_nDead*flToPct ); - buf.Printf( "%sConnection quality distribution:\n", pszLeader ); - if ( stats.m_nQualityNtile50th >= 0 ) buf.Printf( "%s 50%% of intervals >= %3d%%\n", pszLeader, stats.m_nQualityNtile50th ); - if ( stats.m_nQualityNtile25th >= 0 ) buf.Printf( "%s 75%% of intervals >= %3d%%\n", pszLeader, stats.m_nQualityNtile25th ); - if ( stats.m_nQualityNtile5th >= 0 ) buf.Printf( "%s 95%% of intervals >= %3d%%\n", pszLeader, stats.m_nQualityNtile5th ); - if ( stats.m_nQualityNtile2nd >= 0 ) buf.Printf( "%s 98%% of intervals >= %3d%%\n", pszLeader, stats.m_nQualityNtile2nd ); - } - else - { - buf.Printf( "%sNo connection quality distribution available. (%d measurement intervals)\n", pszLeader, nQualitySamples ); - } - } - - // Do we have any jitter samples? - { - int nJitterSamples = stats.m_jitterHistogram.TotalCount(); - if ( nJitterSamples >= 1 ) - { - float flToPct = 100.0f / nJitterSamples; - - buf.Printf( "%sLatency variance histogram: (%d total measurements)\n", pszLeader, nJitterSamples ); - buf.Printf( "%s <1 :%7d %3.0f%%\n", pszLeader, stats.m_jitterHistogram.m_nNegligible, stats.m_jitterHistogram.m_nNegligible*flToPct ); - buf.Printf( "%s 1-2 :%7d %3.0f%%\n", pszLeader, stats.m_jitterHistogram.m_n1 , stats.m_jitterHistogram.m_n1 *flToPct ); - buf.Printf( "%s 2-5 :%7d %3.0f%%\n", pszLeader, stats.m_jitterHistogram.m_n2 , stats.m_jitterHistogram.m_n2 *flToPct ); - buf.Printf( "%s 5-10 :%7d %3.0f%%\n", pszLeader, stats.m_jitterHistogram.m_n5 , stats.m_jitterHistogram.m_n5 *flToPct ); - buf.Printf( "%s 10-20 :%7d %3.0f%%\n", pszLeader, stats.m_jitterHistogram.m_n10, stats.m_jitterHistogram.m_n10*flToPct ); - buf.Printf( "%s >20 :%7d %3.0f%%\n", pszLeader, stats.m_jitterHistogram.m_n20, stats.m_jitterHistogram.m_n20*flToPct ); - } - else - { - buf.Printf( "%sLatency variance histogram not available\n", pszLeader ); - } - } - - // Do we have enough tx speed samples such that the distribution might be interesting? - { - int nTXSpeedSamples = stats.TXSpeedHistogramTotalCount(); - if ( nTXSpeedSamples >= 5 ) - { - float flToPct = 100.0f / nTXSpeedSamples; - buf.Printf( "%sTX Speed histogram: (%d total samples)\n", pszLeader, nTXSpeedSamples ); - buf.Printf( "%s 0 - 16 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram16, stats.m_nTXSpeedHistogram16 *flToPct ); - buf.Printf( "%s 16 - 32 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram32, stats.m_nTXSpeedHistogram32 *flToPct ); - buf.Printf( "%s 32 - 64 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram64, stats.m_nTXSpeedHistogram64 *flToPct ); - buf.Printf( "%s 64 - 128 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram128, stats.m_nTXSpeedHistogram128 *flToPct ); - buf.Printf( "%s 128 - 256 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram256, stats.m_nTXSpeedHistogram256 *flToPct ); - buf.Printf( "%s 256 - 512 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram512, stats.m_nTXSpeedHistogram512 *flToPct ); - buf.Printf( "%s 512 - 1024 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram1024, stats.m_nTXSpeedHistogram1024*flToPct ); - buf.Printf( "%s 1024+ KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogramMax, stats.m_nTXSpeedHistogramMax *flToPct ); - buf.Printf( "%sTransmit speed distribution:\n", pszLeader ); - if ( stats.m_nTXSpeedNtile5th >= 0 ) buf.Printf( "%s 5%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile5th ); - if ( stats.m_nTXSpeedNtile50th >= 0 ) buf.Printf( "%s 50%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile50th ); - if ( stats.m_nTXSpeedNtile75th >= 0 ) buf.Printf( "%s 75%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile75th ); - if ( stats.m_nTXSpeedNtile95th >= 0 ) buf.Printf( "%s 95%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile95th ); - if ( stats.m_nTXSpeedNtile98th >= 0 ) buf.Printf( "%s 98%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile98th ); - } - else - { - buf.Printf( "%sNo connection transmit speed distribution available. (%d measurement intervals)\n", pszLeader, nTXSpeedSamples ); - } - } - - // Do we have enough RX speed samples such that the distribution might be interesting? - { - int nRXSpeedSamples = stats.RXSpeedHistogramTotalCount(); - if ( nRXSpeedSamples >= 5 ) - { - float flToPct = 100.0f / nRXSpeedSamples; - buf.Printf( "%sRX Speed histogram: (%d total samples)\n", pszLeader, nRXSpeedSamples ); - buf.Printf( "%s 0 - 16 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram16, stats.m_nRXSpeedHistogram16 *flToPct ); - buf.Printf( "%s 16 - 32 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram32, stats.m_nRXSpeedHistogram32 *flToPct ); - buf.Printf( "%s 32 - 64 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram64, stats.m_nRXSpeedHistogram64 *flToPct ); - buf.Printf( "%s 64 - 128 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram128, stats.m_nRXSpeedHistogram128 *flToPct ); - buf.Printf( "%s 128 - 256 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram256, stats.m_nRXSpeedHistogram256 *flToPct ); - buf.Printf( "%s 256 - 512 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram512, stats.m_nRXSpeedHistogram512 *flToPct ); - buf.Printf( "%s 512 - 1024 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram1024, stats.m_nRXSpeedHistogram1024*flToPct ); - buf.Printf( "%s 1024+ KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogramMax, stats.m_nRXSpeedHistogramMax *flToPct ); - buf.Printf( "%sReceive speed distribution:\n", pszLeader ); - if ( stats.m_nRXSpeedNtile5th >= 0 ) buf.Printf( "%s 5%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile5th ); - if ( stats.m_nRXSpeedNtile50th >= 0 ) buf.Printf( "%s 50%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile50th ); - if ( stats.m_nRXSpeedNtile75th >= 0 ) buf.Printf( "%s 75%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile75th ); - if ( stats.m_nRXSpeedNtile95th >= 0 ) buf.Printf( "%s 95%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile95th ); - if ( stats.m_nRXSpeedNtile98th >= 0 ) buf.Printf( "%s 98%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile98th ); - } - else - { - buf.Printf( "%sNo connection recieve speed distribution available. (%d measurement intervals)\n", pszLeader, nRXSpeedSamples ); - } - } - -} - -void LinkStatsPrintToBuf( const char *pszLeader, const SteamDatagramLinkStats &stats, CUtlBuffer &buf ) -{ - std::string sIndent( pszLeader ); sIndent.append( " " ); - - buf.Printf( "%sCurrent rates:\n", pszLeader ); - LinkStatsPrintInstantaneousToBuf( sIndent.c_str(), stats.m_latest, buf ); - buf.Printf( "%sLifetime stats:\n", pszLeader ); - LinkStatsPrintLifetimeToBuf( sIndent.c_str(), stats.m_lifetime, buf ); - - if ( stats.m_flAgeLatestRemote < 0.0f ) - { - buf.Printf( "%sNo rate stats received from remote host\n", pszLeader ); - } - else - { - buf.Printf( "%sRate stats received from remote host %.1fs ago:\n", pszLeader, stats.m_flAgeLatestRemote ); - LinkStatsPrintInstantaneousToBuf( sIndent.c_str(), stats.m_latestRemote, buf ); - } - - if ( stats.m_flAgeLifetimeRemote < 0.0f ) - { - buf.Printf( "%sNo lifetime stats received from remote host\n", pszLeader ); - } - else - { - buf.Printf( "%sLifetime stats received from remote host %.1fs ago:\n", pszLeader, stats.m_flAgeLifetimeRemote ); - LinkStatsPrintLifetimeToBuf( sIndent.c_str(), stats.m_lifetimeRemote, buf ); - } -} - /////////////////////////////////////////////////////////////////////////////// // // SipHash, used for challenge generation @@ -1477,119 +153,53 @@ const char *GetAvailabilityString( ESteamNetworkingAvailability a ) return "???"; } +static uint32 Murmorhash32( const void *data, size_t len ) +{ + uint32 h = 0; + const uint8 *key = (const uint8 *)data; + if (len > 3) { + const uint32* key_x4 = (const uint32*) key; + size_t i = len >> 2; + do { + uint32 k = *key_x4++; + k *= 0xcc9e2d51; + k = (k << 15) | (k >> 17); + k *= 0x1b873593; + h ^= k; + h = (h << 13) | (h >> 19); + h = (h * 5) + 0xe6546b64; + } while (--i); + key = (const uint8*) key_x4; + } + if (len & 3) { + size_t i = len & 3; + uint32 k = 0; + key = &key[i - 1]; + do { + k <<= 8; + k |= *key--; + } while (--i); + k *= 0xcc9e2d51; + k = (k << 15) | (k >> 17); + k *= 0x1b873593; + h ^= k; + } + h ^= len; + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; +} + +uint32 SteamNetworkingIdentityHash::operator()(struct SteamNetworkingIdentity const &x ) const +{ + return Murmorhash32( &x, sizeof( x.m_eType ) + sizeof( x.m_cbSize ) + x.m_cbSize ); +} + } // namespace SteamNetworkingSocketsLib - -void SteamNetworkingDetailedConnectionStatus::Clear() -{ - V_memset( this, 0, sizeof(*this) ); - COMPILE_TIME_ASSERT( k_ESteamNetworkingAvailability_Unknown == 0 ); - m_statsEndToEnd.Clear(); - m_statsPrimaryRouter.Clear(); - m_nPrimaryRouterBackPing = -1; - m_nBackupRouterFrontPing = -1; - m_nBackupRouterBackPing = -1; -} - -int SteamNetworkingDetailedConnectionStatus::Print( char *pszBuf, int cbBuf ) -{ - CUtlBuffer buf( 0, 8*1024, CUtlBuffer::TEXT_BUFFER ); - - // If we don't have network, there's nothing else we can really do - if ( m_eAvailNetworkConfig != k_ESteamNetworkingAvailability_Current && m_eAvailNetworkConfig != k_ESteamNetworkingAvailability_Unknown ) - { - buf.Printf( "Network configuration: %s\n", GetAvailabilityString( m_eAvailNetworkConfig ) ); - buf.Printf( " Cannot communicate with relays without network config." ); - } - - // Unable to talk to any routers? - if ( m_eAvailAnyRouterCommunication != k_ESteamNetworkingAvailability_Current && m_eAvailAnyRouterCommunication != k_ESteamNetworkingAvailability_Unknown ) - { - buf.Printf( "Router network: %s\n", GetAvailabilityString( m_eAvailAnyRouterCommunication ) ); - } - - switch ( m_info.m_eState ) - { - case k_ESteamNetworkingConnectionState_Connecting: - buf.Printf( "End-to-end connection: connecting\n" ); - break; - - case k_ESteamNetworkingConnectionState_FindingRoute: - buf.Printf( "End-to-end connection: performing rendezvous\n" ); - break; - - case k_ESteamNetworkingConnectionState_Connected: - buf.Printf( "End-to-end connection: connected\n" ); - break; - - case k_ESteamNetworkingConnectionState_ClosedByPeer: - buf.Printf( "End-to-end connection: closed by remote host, reason code %d. (%s)\n", m_info.m_eEndReason, m_info.m_szEndDebug ); - break; - - case k_ESteamNetworkingConnectionState_ProblemDetectedLocally: - buf.Printf( "End-to-end connection: closed due to problem detected locally, reason code %d. (%s)\n", m_info.m_eEndReason, m_info.m_szEndDebug ); - break; - - case k_ESteamNetworkingConnectionState_None: - buf.Printf( "End-to-end connection: closed, reason code %d. (%s)\n", m_info.m_eEndReason, m_info.m_szEndDebug ); - break; - - default: - buf.Printf( "End-to-end connection: BUG: invalid state %d!\n", m_info.m_eState ); - break; - } - - if ( m_info.m_idPOPRemote ) - { - buf.Printf( " Remote host is in data center '%s'\n", SteamNetworkingPOPIDRender( m_info.m_idPOPRemote ).c_str() ); - } - - // If we ever tried to send a packet end-to-end, dump end-to-end stats. - if ( m_statsEndToEnd.m_lifetime.m_nPacketsSent > 0 ) - { - LinkStatsPrintToBuf( " ", m_statsEndToEnd, buf ); - } - - if ( m_unPrimaryRouterIP ) - { - buf.Printf( "Primary router: %s", m_szPrimaryRouterName ); - - int nPrimaryFrontPing = m_statsPrimaryRouter.m_latest.m_nPingMS; - if ( m_nPrimaryRouterBackPing >= 0 ) - buf.Printf( " Ping = %d+%d=%d (front+back=total)\n", nPrimaryFrontPing, m_nPrimaryRouterBackPing,nPrimaryFrontPing+m_nPrimaryRouterBackPing ); - else - buf.Printf( " Ping to relay = %d\n", nPrimaryFrontPing ); - LinkStatsPrintToBuf( " ", m_statsPrimaryRouter, buf ); - - if ( m_unBackupRouterIP == 0 ) - { - // Probably should only print this if we have reason to expect that a backup relay is expected - //buf.Printf( "No backup router selected\n" ); - } - else - { - buf.Printf( "Backup router: %s Ping = %d+%d=%d (front+back=total)\n", - m_szBackupRouterName, - m_nBackupRouterFrontPing, m_nBackupRouterBackPing,m_nBackupRouterFrontPing+m_nBackupRouterBackPing - ); - } - } - else if ( m_info.m_idPOPRelay ) - { - buf.Printf( "Communicating via relay in '%s'\n", SteamNetworkingPOPIDRender( m_info.m_idPOPRelay ).c_str() ); - } - - int sz = buf.TellPut()+1; - if ( pszBuf && cbBuf > 0 ) - { - int l = Min( sz, cbBuf ) - 1; - V_memcpy( pszBuf, buf.Base(), l ); - pszBuf[l] = '\0'; - if ( cbBuf >= sz ) - return 0; - } - - return sz; -} +using namespace SteamNetworkingSocketsLib; /////////////////////////////////////////////////////////////////////////////// // @@ -1784,48 +394,3 @@ STEAMNETWORKINGSOCKETS_INTERFACE bool SteamAPI_SteamNetworkingIdentity_ParseStri // Invalid return false; } - -static uint32 Murmorhash32( const void *data, size_t len ) -{ - uint32 h = 0; - const uint8 *key = (const uint8 *)data; - if (len > 3) { - const uint32* key_x4 = (const uint32*) key; - size_t i = len >> 2; - do { - uint32 k = *key_x4++; - k *= 0xcc9e2d51; - k = (k << 15) | (k >> 17); - k *= 0x1b873593; - h ^= k; - h = (h << 13) | (h >> 19); - h = (h * 5) + 0xe6546b64; - } while (--i); - key = (const uint8*) key_x4; - } - if (len & 3) { - size_t i = len & 3; - uint32 k = 0; - key = &key[i - 1]; - do { - k <<= 8; - k |= *key--; - } while (--i); - k *= 0xcc9e2d51; - k = (k << 15) | (k >> 17); - k *= 0x1b873593; - h ^= k; - } - h ^= len; - h ^= h >> 16; - h *= 0x85ebca6b; - h ^= h >> 13; - h *= 0xc2b2ae35; - h ^= h >> 16; - return h; -} - -uint32 SteamNetworkingIdentityHash::operator()(struct SteamNetworkingIdentity const &x ) const -{ - return Murmorhash32( &x, sizeof( x.m_eType ) + sizeof( x.m_cbSize ) + x.m_cbSize ); -} \ No newline at end of file diff --git a/src/steamnetworkingsockets/steamnetworkingsockets_stats.cpp b/src/steamnetworkingsockets/steamnetworkingsockets_stats.cpp new file mode 100644 index 0000000..5e5d11d --- /dev/null +++ b/src/steamnetworkingsockets/steamnetworkingsockets_stats.cpp @@ -0,0 +1,1455 @@ +//====== Copyright Valve Corporation, All rights reserved. ==================== + +#include +#include +#include "steamnetworking_statsutils.h" + +// Must be the last include +#include + +using namespace SteamNetworkingSocketsLib; + +/////////////////////////////////////////////////////////////////////////////// +// +// LinkStatsTracker +// +/////////////////////////////////////////////////////////////////////////////// + + +void SteamDatagramLinkInstantaneousStats::Clear() +{ + memset( this, 0, sizeof(*this) ); + m_nPingMS = -1; + m_flPacketsDroppedPct = -1.0f; + m_flPacketsWeirdSequenceNumberPct = -1.0f; + m_usecMaxJitter = -1; + m_nSendRate = -1; + m_nPendingBytes = 0; +} + +void SteamDatagramLinkLifetimeStats::Clear() +{ + memset( this, 0, sizeof(*this) ); + m_nPingNtile5th = -1; + m_nPingNtile50th = -1; + m_nPingNtile75th = -1; + m_nPingNtile95th = -1; + m_nPingNtile98th = -1; + m_nQualityNtile2nd = -1; + m_nQualityNtile5th = -1; + m_nQualityNtile25th = -1; + m_nQualityNtile50th = -1; + m_nTXSpeedNtile5th = -1; + m_nTXSpeedNtile50th = -1; + m_nTXSpeedNtile75th = -1; + m_nTXSpeedNtile95th = -1; + m_nTXSpeedNtile98th = -1; + m_nRXSpeedNtile5th = -1; + m_nRXSpeedNtile50th = -1; + m_nRXSpeedNtile75th = -1; + m_nRXSpeedNtile95th = -1; + m_nRXSpeedNtile98th = -1; +} + +void SteamDatagramLinkStats::Clear() +{ + m_latest.Clear(); + //m_peak.Clear(); + m_lifetime.Clear(); + m_latestRemote.Clear(); + m_flAgeLatestRemote = -1.0f; + m_lifetimeRemote.Clear(); + m_flAgeLifetimeRemote = -1.0f; +} + + +void PingTracker::Reset() +{ + memset( m_arPing, 0, sizeof(m_arPing) ); + m_nValidPings = 0; + m_nSmoothedPing = -1; + m_usecTimeLastSentPingRequest = 0; +} + +void PingTracker::ReceivedPing( int nPingMS, SteamNetworkingMicroseconds usecNow ) +{ + Assert( nPingMS >= 0 ); + COMPILE_TIME_ASSERT( V_ARRAYSIZE(m_arPing) == 3 ); + + // Discard oldest, insert new sample at head + m_arPing[2] = m_arPing[1]; + m_arPing[1] = m_arPing[0]; + m_arPing[0].m_nPingMS = nPingMS; + m_arPing[0].m_usecTimeRecv = usecNow; + + // Compute smoothed ping and update sample count based on existing sample size + switch ( m_nValidPings ) + { + case 0: + // First sample. Smoothed value is simply the same thing as the sample + m_nValidPings = 1; + m_nSmoothedPing = nPingMS; + break; + + case 1: + // Second sample. Smoothed value is the average + m_nValidPings = 2; + m_nSmoothedPing = ( m_arPing[0].m_nPingMS + m_arPing[1].m_nPingMS ) >> 1; + break; + + default: + AssertMsg1( false, "Unexpected valid ping count %d", m_nValidPings ); + case 2: + // Just received our final sample to complete the sample + m_nValidPings = 3; + case 3: + { + // Full sample. Take the average of the two best. Hopefully this strategy ignores a single + // ping spike, but without being too optimistic and underestimating the sustained latency too + // much. (Another option might be to use the median?) + int nMax = Max( m_arPing[0].m_nPingMS, m_arPing[1].m_nPingMS ); + nMax = Max( nMax, m_arPing[2].m_nPingMS ); + m_nSmoothedPing = ( m_arPing[0].m_nPingMS + m_arPing[1].m_nPingMS + m_arPing[2].m_nPingMS - nMax ) >> 1; + break; + } + } +} + +int PingTracker::PessimisticPingEstimate() const +{ + if ( m_nValidPings < 1 ) + { + AssertMsg( false, "Tried to make a pessimistic ping estimate without any ping data at all!" ); + return 500; + } + int nResult = m_arPing[0].m_nPingMS; + for ( int i = 1 ; i < m_nValidPings ; ++i ) + nResult = Max( nResult, m_arPing[i].m_nPingMS ); + return nResult; +} + +int PingTracker::OptimisticPingEstimate() const +{ + if ( m_nValidPings < 1 ) + { + AssertMsg( false, "Tried to make an optimistic ping estimate without any ping data at all!" ); + return 50; + } + int nResult = m_arPing[0].m_nPingMS; + for ( int i = 1 ; i < m_nValidPings ; ++i ) + nResult = Min( nResult, m_arPing[i].m_nPingMS ); + return nResult; +} + +void LinkStatsTrackerBase::InitInternal( SteamNetworkingMicroseconds usecNow ) +{ + m_nPeerProtocolVersion = 0; + m_bDisconnected = false; + m_sent.Reset(); + m_recv.Reset(); + m_recvExceedRateLimit.Reset(); + m_ping.Reset(); + m_nNextSendSequenceNumber = 1; + m_usecTimeLastSentSeq = 0; + InitMaxRecvPktNum( 0 ); + m_flInPacketsDroppedPct = -1.0f; + m_usecMaxJitterPreviousInterval = -1; + m_flInPacketsWeirdSequencePct = -1.0f; + m_nPktsRecvSequenced = 0; + m_nPktsRecvDropped = 0; + m_nPktsRecvOutOfOrder = 0; + m_nPktsRecvDuplicate = 0; + m_nPktsRecvSequenceNumberLurch = 0; + m_usecTimeLastRecv = 0; + m_usecTimeLastRecvSeq = 0; + memset( &m_latestRemote, 0, sizeof(m_latestRemote) ); + m_usecTimeRecvLatestRemote = 0; + memset( &m_lifetimeRemote, 0, sizeof(m_lifetimeRemote) ); + m_usecTimeRecvLifetimeRemote = 0; + //m_seqnumUnackedSentLifetime = -1; + //m_seqnumPendingAckRecvTimelife = -1; + m_qualityHistogram.Reset(); + m_qualitySample.Clear(); + m_jitterHistogram.Reset(); +} + +void LinkStatsTrackerBase::SetDisconnectedInternal( bool bFlag, SteamNetworkingMicroseconds usecNow ) +{ + m_bDisconnected = bFlag; + + m_pktNumInFlight = 0; + m_bInFlightInstantaneous = false; + m_bInFlightLifetime = false; + PeerAckedInstantaneous( usecNow ); + PeerAckedLifetime( usecNow ); + + // Clear acks we expect, on either state change. + m_usecInFlightReplyTimeout = 0; + m_usecLastSendPacketExpectingImmediateReply = 0; + m_nReplyTimeoutsSinceLastRecv = 0; + m_usecWhenTimeoutStarted = 0; + + if ( !bFlag ) + { + StartNextInterval( usecNow ); + } +} + +void LinkStatsTrackerBase::StartNextInterval( SteamNetworkingMicroseconds usecNow ) +{ + m_nPktsRecvSequencedCurrentInterval = 0; + m_nPktsRecvDroppedCurrentInterval = 0; + m_nPktsRecvWeirdSequenceCurrentInterval = 0; + m_usecMaxJitterCurrentInterval = -1; + m_usecIntervalStart = usecNow; +} + +void LinkStatsTrackerBase::ThinkInternal( SteamNetworkingMicroseconds usecNow ) +{ + // Check for ending the current QoS interval + if ( !m_bDisconnected && m_usecIntervalStart + k_usecSteamDatagramLinkStatsDefaultInterval < usecNow ) + { + UpdateInterval( usecNow ); + } + + // Check for reply timeout that we count. (We intentionally only allow + // one of this type of timeout to be in flight at a time, so that the max + // rate that we accumulate them is based on the ping time, instead of the packet + // rate. + if ( m_usecInFlightReplyTimeout > 0 && m_usecInFlightReplyTimeout < usecNow ) + { + m_usecInFlightReplyTimeout = 0; + if ( m_usecWhenTimeoutStarted == 0 ) + { + Assert( m_nReplyTimeoutsSinceLastRecv == 0 ); + m_usecWhenTimeoutStarted = usecNow; + } + ++m_nReplyTimeoutsSinceLastRecv; + } +} + +void LinkStatsTrackerBase::UpdateInterval( SteamNetworkingMicroseconds usecNow ) +{ + float flElapsed = int64( usecNow - m_usecIntervalStart ) * 1e-6; + flElapsed = Max( flElapsed, .001f ); // make sure math doesn't blow up + + // Check if enough happened in this interval to make a meaningful judgment about connection quality + COMPILE_TIME_ASSERT( k_usecSteamDatagramLinkStatsDefaultInterval >= 5*k_nMillion ); + if ( flElapsed > 4.5f ) + { + if ( m_nPktsRecvSequencedCurrentInterval > 5 ) + { + int nBad = m_nPktsRecvDroppedCurrentInterval + m_nPktsRecvWeirdSequenceCurrentInterval; + if ( nBad == 0 ) + { + // Perfect connection. This will hopefully be relatively common + m_qualitySample.AddSample( 100 ); + ++m_qualityHistogram.m_n100; + } + else + { + + // Less than perfect. Compute quality metric. + int nTotalSent = m_nPktsRecvSequencedCurrentInterval + m_nPktsRecvDroppedCurrentInterval; + int nRecvGood = m_nPktsRecvSequencedCurrentInterval - m_nPktsRecvWeirdSequenceCurrentInterval; + int nQuality = nRecvGood * 100 / nTotalSent; + + // Cap at 99, since 100 is reserved to mean "perfect", + // I don't think it's possible for the calculation above to ever produce 100, but whatever. + if ( nQuality >= 99 ) + { + m_qualitySample.AddSample( 99 ); + ++m_qualityHistogram.m_n99; + } + else if ( nQuality <= 1 ) // in case accounting is hosed or every single packet was out of order, clamp. 0 means "totally dead connection" + { + m_qualitySample.AddSample( 1 ); + ++m_qualityHistogram.m_n1; + } + else + { + m_qualitySample.AddSample( nQuality ); + if ( nQuality >= 97 ) + ++m_qualityHistogram.m_n97; + else if ( nQuality >= 95 ) + ++m_qualityHistogram.m_n95; + else if ( nQuality >= 90 ) + ++m_qualityHistogram.m_n90; + else if ( nQuality >= 75 ) + ++m_qualityHistogram.m_n75; + else if ( nQuality >= 50 ) + ++m_qualityHistogram.m_n50; + else + ++m_qualityHistogram.m_n1; + } + } + } + else if ( m_recv.m_packets.m_nCurrentInterval == 0 && m_sent.m_packets.m_nCurrentInterval > (int64)( flElapsed ) && m_nReplyTimeoutsSinceLastRecv >= 2 ) + { + COMPILE_TIME_ASSERT( k_usecSteamDatagramClientPingTimeout + k_usecSteamDatagramRouterPendClientPing < k_nMillion ); + + // He's dead, Jim. But we've been trying pretty hard to talk to him, so it probably isn't + // because the connection is just idle or shutting down. The connection has probably + // dropped. + m_qualitySample.AddSample(0); + ++m_qualityHistogram.m_nDead; + } + } + + // PacketRate class does most of the work + m_sent.UpdateInterval( flElapsed ); + m_recv.UpdateInterval( flElapsed ); + m_recvExceedRateLimit.UpdateInterval( flElapsed ); + + // Calculate rate of packet flow imperfections + Assert( m_nPktsRecvWeirdSequenceCurrentInterval <= m_nPktsRecvSequencedCurrentInterval ); + if ( m_nPktsRecvSequencedCurrentInterval <= 0 ) + { + // No sequenced packets received during interval, so no data available + m_flInPacketsDroppedPct = -1.0f; + m_flInPacketsWeirdSequencePct = -1.0f; + } + else + { + float flToPct = 1.0f / float( m_nPktsRecvSequencedCurrentInterval + m_nPktsRecvDroppedCurrentInterval ); + m_flInPacketsDroppedPct = m_nPktsRecvDroppedCurrentInterval * flToPct; + m_flInPacketsWeirdSequencePct = m_nPktsRecvWeirdSequenceCurrentInterval * flToPct; + } + + // Peak jitter value + m_usecMaxJitterPreviousInterval = m_usecMaxJitterCurrentInterval; + + // Reset for next time + StartNextInterval( usecNow ); +} + +void LinkStatsTrackerBase::InitMaxRecvPktNum( int64 nPktNum ) +{ + Assert( nPktNum >= 0 ); + m_nMaxRecvPktNum = nPktNum; + + // Set bits, to mark that all values <= this packet number have been + // received. + m_recvPktNumberMask[0] = ~(uint64)0; + unsigned nBitsToSet = (unsigned)( nPktNum & 63 ) + 1; + if ( nBitsToSet == 64 ) + m_recvPktNumberMask[1] = ~(uint64)0; + else + m_recvPktNumberMask[1] = ( (uint64)1 << nBitsToSet ) - 1; +} + +bool LinkStatsTrackerBase::BCheckPacketNumberOldOrDuplicate( int64 nPktNum ) +{ + // We've received a packet with a sequence number. + // Update stats + ++m_nPktsRecvSequencedCurrentInterval; + ++m_nPktsRecvSequenced; + ++m_nPktsRecvSeqSinceSentLifetime; + ++m_nPktsRecvSeqSinceSentInstantaneous; + + // Packet number is increasing? + // (Maybe by a lot -- we don't handle that here.) + if ( nPktNum > m_nMaxRecvPktNum ) + return true; + + // Which block of 64-bit packets is it in? + int64 B = m_nMaxRecvPktNum & ~int64{63}; + int64 idxRecvBitmask = ( ( nPktNum - B ) >> 6 ) + 1; + Assert( idxRecvBitmask < 2 ); + if ( idxRecvBitmask < 0 ) + { + // Too old (at least 64 packets old, maybe up to 128). + // Track stats, both lifetime and current interval + ++m_nPktsRecvSequenceNumberLurch; // Should we track this under a different stat? + ++m_nPktsRecvWeirdSequenceCurrentInterval; + return false; + } + uint64 bit = uint64{1} << ( nPktNum & 63 ); + if ( m_recvPktNumberMask[ idxRecvBitmask ] & bit ) + { + // Duplicate + // Track stats, both lifetime and current interval + ++m_nPktsRecvDuplicate; + ++m_nPktsRecvWeirdSequenceCurrentInterval; + return false; + } + + // We have an out of order packet. We'll update that + // stat in TrackProcessSequencedPacket + Assert( nPktNum > 0 && nPktNum < m_nMaxRecvPktNum ); + return true; +} + +void LinkStatsTrackerBase::TrackProcessSequencedPacket( int64 nPktNum, SteamNetworkingMicroseconds usecNow, int usecSenderTimeSincePrev ) +{ + Assert( nPktNum > 0 ); + + // Update bitfield of received packets + int64 B = m_nMaxRecvPktNum & ~int64{63}; + int64 idxRecvBitmask = ( ( nPktNum - B ) >> 6 ) + 1; + Assert( idxRecvBitmask >= 0 ); // We should have discarded very old packets already + if ( idxRecvBitmask >= 2 ) // Most common case is 0 or 1 + { + if ( idxRecvBitmask == 2 ) + { + // Crossed to the next 64-packet block. Shift bitmasks forward by one. + m_recvPktNumberMask[0] = m_recvPktNumberMask[1]; + } + else + { + // Large packet number jump, we skipped a whole block + m_recvPktNumberMask[0] = 0; + } + m_recvPktNumberMask[1] = 0; + idxRecvBitmask = 1; + } + uint64 bit = uint64{1} << ( nPktNum & 63 ); + Assert( !( m_recvPktNumberMask[ idxRecvBitmask ] & bit ) ); // Should not have already been marked! We should have already discarded duplicates + m_recvPktNumberMask[ idxRecvBitmask ] |= bit; + + // Check for dropped packet. Since we hope that by far the most common + // case will be packets delivered in order, we optimize this logic + // for that case. + int64 nGap = nPktNum - m_nMaxRecvPktNum; + if ( nGap == 1 ) + { + + // We've received two packets, in order. Did the sender supply the time between packets on his side? + if ( usecSenderTimeSincePrev > 0 ) + { + int usecJitter = ( usecNow - m_usecTimeLastRecvSeq ) - usecSenderTimeSincePrev; + usecJitter = abs( usecJitter ); + if ( usecJitter < k_usecTimeSinceLastPacketMaxReasonable ) + { + + // Update max jitter for current interval + m_usecMaxJitterCurrentInterval = Max( m_usecMaxJitterCurrentInterval, usecJitter ); + m_jitterHistogram.AddSample( usecJitter ); + } + else + { + // Something is really, really off. Discard measurement + } + } + + } + else + { + // Classify imperfection based on gap size. + if ( nGap >= 100 ) + { + // Very weird. + ++m_nPktsRecvSequenceNumberLurch; + ++m_nPktsRecvWeirdSequenceCurrentInterval; + + // Continue to code below, reseting the sequence number + // for packets going forward. + } + else if ( nGap > 0 ) + { + // Probably the most common case, we just dropped a packet + int nDropped = nGap-1; + m_nPktsRecvDropped += nDropped; + m_nPktsRecvDroppedCurrentInterval += nDropped; + } + else if ( nGap == 0 ) + { + // We should have already rejected duplicates + Assert( false ); + } + else + { + // Packet number moving in reverse. + // It should be a *small* negative step, e.g. packets delivered out of order. + // If the packet is really old, we should have already discarded it earlier. + Assert( nGap >= -8 * (int64)sizeof(m_recvPktNumberMask) ); + ++m_nPktsRecvOutOfOrder; + ++m_nPktsRecvWeirdSequenceCurrentInterval; + + // We previously counted this packet as dropped. Undo that, it wasn't dropped. + if ( m_nPktsRecvDropped > 0 ) + { + --m_nPktsRecvDropped; + } + else + { + // This is weird. + AssertMsg2( false, "No dropped packets, but pkt num %lld -> %lld and bit is not set?", (long long)m_nMaxRecvPktNum, (long long)nPktNum ); + } + if ( m_nPktsRecvDroppedCurrentInterval > 0 ) // Might have marked it in the previous interval. Our stats will be slightly off in this case. Not worth it tro try to get this exactly right. + --m_nPktsRecvDroppedCurrentInterval; + + } + } + + // Save highest known sequence number for next time. + if ( nGap > 0 ) + { + m_nMaxRecvPktNum += nGap; + m_usecTimeLastRecvSeq = usecNow; + } +} + +bool LinkStatsTrackerBase::BCheckHaveDataToSendInstantaneous( SteamNetworkingMicroseconds usecNow ) +{ + Assert( !m_bDisconnected ); + + // How many packets a second to we expect to send on an "active" connection? + const int64 k_usecActiveConnectionSendInterval = 3*k_nMillion; + COMPILE_TIME_ASSERT( k_usecSteamDatagramClientPingTimeout*2 < k_usecActiveConnectionSendInterval ); + COMPILE_TIME_ASSERT( k_usecSteamDatagramClientBackupRouterKeepaliveInterval > k_usecActiveConnectionSendInterval*5 ); // make sure backup keepalive interval isn't anywhere near close enough to trigger this. + + // Calculate threshold based on how much time has elapsed and a very low packet rate + int64 usecElapsed = usecNow - m_usecPeerAckedInstaneous; + Assert( usecElapsed >= k_usecLinkStatsInstantaneousReportMinInterval ); // don't call this unless you know it's been long enough! + int nThreshold = usecElapsed / k_usecActiveConnectionSendInterval; + + // Have they been trying to talk to us? + if ( m_nPktsRecvSeqSinceSentInstantaneous > nThreshold || m_nPktsSentSinceSentInstantaneous > nThreshold ) + return true; + + // Connection has been idle since the last time we sent instantaneous stats. + // Don't actually send stats, but clear counters and timers and act like we did. + PeerAckedInstantaneous( usecNow ); + + // And don't send anything + return false; +} + +bool LinkStatsTrackerBase::BCheckHaveDataToSendLifetime( SteamNetworkingMicroseconds usecNow ) +{ + Assert( !m_bDisconnected ); + + // Make sure we have something new to report since the last time we sent stats + if ( m_nPktsRecvSeqSinceSentLifetime > 100 || m_nPktsSentSinceSentLifetime > 100 ) + return true; + + // Reset the timer. But do NOT reset the packet counters. So if the connection isn't + // dropped, and we are sending keepalives very slowly, this will just send some stats + // along about every 100 packets or so. Typically we'll drop the session before that + // happens. + m_usecPeerAckedLifetime = usecNow; + + // Don't send anything now + return false; +} + +bool LinkStatsTrackerBase::BNeedToSendStats( SteamNetworkingMicroseconds usecNow ) +{ + // Message already in flight? + if ( m_pktNumInFlight != 0 || m_bDisconnected ) + return false; + bool bNeedToSendInstantaneous = ( m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMaxInterval < usecNow ) && BCheckHaveDataToSendInstantaneous( usecNow ); + bool bNeedToSendLifetime = ( m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportMaxInterval < usecNow ) && BCheckHaveDataToSendLifetime( usecNow ); + return bNeedToSendInstantaneous || bNeedToSendLifetime; +} + +const char *LinkStatsTrackerBase::NeedToSendStats( SteamNetworkingMicroseconds usecNow, const char *const arpszReasonStrings[4] ) +{ + // Message already in flight? + if ( m_pktNumInFlight != 0 || m_bDisconnected ) + return nullptr; + int n = 0; + if ( m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMaxInterval < usecNow && BCheckHaveDataToSendInstantaneous( usecNow ) ) + n |= 1; + if ( m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportMaxInterval < usecNow && BCheckHaveDataToSendLifetime( usecNow ) ) + n |= 2; + return arpszReasonStrings[n]; +} + +SteamNetworkingMicroseconds LinkStatsTrackerBase::GetNextThinkTimeInternal( SteamNetworkingMicroseconds usecNow ) const +{ + SteamNetworkingMicroseconds usecResult = INT64_MAX; + if ( !m_bDisconnected ) + { + + // Expecting a reply? + if ( m_usecInFlightReplyTimeout ) + { + usecResult = std::min( usecResult, m_usecInFlightReplyTimeout ); + } + else if ( m_usecTimeLastRecv ) + { + // Time when BNeedToSendKeepalive will return true + usecResult = std::min( usecResult, m_usecTimeLastRecv + k_usecKeepAliveInterval ); + } + + // Time when BNeedToSendPingImmediate will return true + if ( m_nReplyTimeoutsSinceLastRecv > 0 ) + usecResult = std::min( usecResult, m_usecLastSendPacketExpectingImmediateReply+k_usecAggressivePingInterval ); + + // Time when we need to flush stats + if ( m_pktNumInFlight == 0 ) + { + usecResult = std::min( usecResult, m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMaxInterval ); + usecResult = std::min( usecResult, m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportMaxInterval ); + } + } + + return usecResult; +} + +void LinkStatsTrackerBase::PopulateMessage( CMsgSteamDatagramConnectionQuality &msg, SteamNetworkingMicroseconds usecNow ) +{ + if ( m_pktNumInFlight == 0 && !m_bDisconnected ) + { + + // Ready to send instantaneous stats? + if ( m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMinInterval < usecNow && BCheckHaveDataToSendInstantaneous( usecNow ) ) + { + // !KLUDGE! Go through public struct as intermediary to keep code simple. + SteamDatagramLinkInstantaneousStats sInstant; + GetInstantaneousStats( sInstant ); + LinkStatsInstantaneousStructToMsg( sInstant, *msg.mutable_instantaneous() ); + } + + // Ready to send lifetime stats? + if ( m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportMinInterval < usecNow && BCheckHaveDataToSendLifetime( usecNow ) ) + { + // !KLUDGE! Go through public struct as intermediary to keep code simple. + SteamDatagramLinkLifetimeStats sLifetime; + GetLifetimeStats( sLifetime ); + LinkStatsLifetimeStructToMsg( sLifetime, *msg.mutable_lifetime() ); + } + } +} + +void LinkStatsTrackerBase::TrackSentMessageExpectingReply( SteamNetworkingMicroseconds usecNow, bool bAllowDelayedReply ) +{ + if ( m_usecInFlightReplyTimeout == 0 ) + { + m_usecInFlightReplyTimeout = usecNow + m_ping.CalcConservativeTimeout(); + if ( bAllowDelayedReply ) + m_usecInFlightReplyTimeout += k_usecSteamDatagramRouterPendClientPing; + } + if ( !bAllowDelayedReply ) + m_usecLastSendPacketExpectingImmediateReply = usecNow; +} + +void LinkStatsTrackerBase::ProcessMessage( const CMsgSteamDatagramConnectionQuality &msg, SteamNetworkingMicroseconds usecNow ) +{ + if ( msg.has_instantaneous() ) + { + LinkStatsInstantaneousMsgToStruct( msg.instantaneous(), m_latestRemote ); + m_usecTimeRecvLatestRemote = usecNow; + } + if ( msg.has_lifetime() ) + { + LinkStatsLifetimeMsgToStruct( msg.lifetime(), m_lifetimeRemote ); + m_usecTimeRecvLifetimeRemote = usecNow; + } +} + +void LinkStatsTrackerBase::GetInstantaneousStats( SteamDatagramLinkInstantaneousStats &s ) const +{ + s.m_flOutPacketsPerSec = m_sent.m_packets.m_flRate; + s.m_flOutBytesPerSec = m_sent.m_bytes.m_flRate; + s.m_flInPacketsPerSec = m_recv.m_packets.m_flRate; + s.m_flInBytesPerSec = m_recv.m_bytes.m_flRate; + s.m_nPingMS = m_ping.m_nSmoothedPing; + s.m_flPacketsDroppedPct = m_flInPacketsDroppedPct; + s.m_flPacketsWeirdSequenceNumberPct = m_flInPacketsWeirdSequencePct; + s.m_usecMaxJitter = m_usecMaxJitterPreviousInterval; +} + +void LinkStatsTrackerBase::GetLifetimeStats( SteamDatagramLinkLifetimeStats &s ) const +{ + s.m_nPacketsSent = m_sent.m_packets.m_nTotal; + s.m_nBytesSent = m_sent.m_bytes.m_nTotal; + s.m_nPacketsRecv = m_recv.m_packets.m_nTotal; + s.m_nBytesRecv = m_recv.m_bytes.m_nTotal; + s.m_nPktsRecvSequenced = m_nPktsRecvSequenced; + s.m_nPktsRecvDropped = m_nPktsRecvDropped; + s.m_nPktsRecvOutOfOrder = m_nPktsRecvOutOfOrder; + s.m_nPktsRecvDuplicate = m_nPktsRecvDuplicate; + s.m_nPktsRecvSequenceNumberLurch = m_nPktsRecvSequenceNumberLurch; + + s.m_qualityHistogram = m_qualityHistogram; + + s.m_nQualityNtile50th = m_qualitySample.NumSamples() < 2 ? -1 : m_qualitySample.GetPercentile( .50f ); + s.m_nQualityNtile25th = m_qualitySample.NumSamples() < 4 ? -1 : m_qualitySample.GetPercentile( .25f ); + s.m_nQualityNtile5th = m_qualitySample.NumSamples() < 20 ? -1 : m_qualitySample.GetPercentile( .05f ); + s.m_nQualityNtile2nd = m_qualitySample.NumSamples() < 50 ? -1 : m_qualitySample.GetPercentile( .02f ); + + m_ping.GetLifetimeStats( s ); + + s.m_jitterHistogram = m_jitterHistogram; + + // + // Clear all end-to-end values + // + + s.m_nTXSpeedMax = -1; + + s.m_nTXSpeedHistogram16 = 0; + s.m_nTXSpeedHistogram32 = 0; + s.m_nTXSpeedHistogram64 = 0; + s.m_nTXSpeedHistogram128 = 0; + s.m_nTXSpeedHistogram256 = 0; + s.m_nTXSpeedHistogram512 = 0; + s.m_nTXSpeedHistogram1024 = 0; + s.m_nTXSpeedHistogramMax = 0; + + s.m_nTXSpeedNtile5th = -1; + s.m_nTXSpeedNtile50th = -1; + s.m_nTXSpeedNtile75th = -1; + s.m_nTXSpeedNtile95th = -1; + s.m_nTXSpeedNtile98th = -1; + + s.m_nRXSpeedMax = -1; + + s.m_nRXSpeedHistogram16 = 0; + s.m_nRXSpeedHistogram32 = 0; + s.m_nRXSpeedHistogram64 = 0; + s.m_nRXSpeedHistogram128 = 0; + s.m_nRXSpeedHistogram256 = 0; + s.m_nRXSpeedHistogram512 = 0; + s.m_nRXSpeedHistogram1024 = 0; + s.m_nRXSpeedHistogramMax = 0; + + s.m_nRXSpeedNtile5th = -1; + s.m_nRXSpeedNtile50th = -1; + s.m_nRXSpeedNtile75th = -1; + s.m_nRXSpeedNtile95th = -1; + s.m_nRXSpeedNtile98th = -1; +} + +void LinkStatsTrackerBase::GetLinkStats( SteamDatagramLinkStats &s, SteamNetworkingMicroseconds usecNow ) const +{ + GetInstantaneousStats( s.m_latest ); + GetLifetimeStats( s.m_lifetime ); + + if ( m_usecTimeRecvLatestRemote ) + { + s.m_latestRemote = m_latestRemote; + s.m_flAgeLatestRemote = ( usecNow - m_usecTimeRecvLatestRemote ) * 1e-6; + } + else + { + s.m_latestRemote.Clear(); + s.m_flAgeLatestRemote = -1.0f; + } + + if ( m_usecTimeRecvLifetimeRemote ) + { + s.m_lifetimeRemote = m_lifetimeRemote; + s.m_flAgeLifetimeRemote = ( usecNow - m_usecTimeRecvLifetimeRemote ) * 1e-6; + } + else + { + s.m_lifetimeRemote.Clear(); + s.m_flAgeLifetimeRemote = -1.0f; + } +} + +void LinkStatsTrackerEndToEnd::InitInternal( SteamNetworkingMicroseconds usecNow ) +{ + LinkStatsTrackerBase::InitInternal( usecNow ); + + m_TXSpeedSample.Clear(); + m_nTXSpeed = 0; + m_nTXSpeedHistogram16 = 0; // Speed at kb/s + m_nTXSpeedHistogram32 = 0; + m_nTXSpeedHistogram64 = 0; + m_nTXSpeedHistogram128 = 0; + m_nTXSpeedHistogram256 = 0; + m_nTXSpeedHistogram512 = 0; + m_nTXSpeedHistogram1024 = 0; + m_nTXSpeedHistogramMax = 0; + + m_RXSpeedSample.Clear(); + m_nRXSpeed = 0; + m_nRXSpeedHistogram16 = 0; // Speed at kb/s + m_nRXSpeedHistogram32 = 0; + m_nRXSpeedHistogram64 = 0; + m_nRXSpeedHistogram128 = 0; + m_nRXSpeedHistogram256 = 0; + m_nRXSpeedHistogram512 = 0; + m_nRXSpeedHistogram1024 = 0; + m_nRXSpeedHistogramMax = 0; + + StartNextSpeedInterval( usecNow ); +} + +void LinkStatsTrackerEndToEnd::ThinkInternal( SteamNetworkingMicroseconds usecNow ) +{ + LinkStatsTrackerBase::ThinkInternal( usecNow ); + + if ( m_usecSpeedIntervalStart + k_usecSteamDatagramSpeedStatsDefaultInterval < usecNow ) + { + UpdateSpeedInterval( usecNow ); + } +} + +void LinkStatsTrackerEndToEnd::StartNextSpeedInterval( SteamNetworkingMicroseconds usecNow ) +{ + m_usecSpeedIntervalStart = usecNow; +} + +void LinkStatsTrackerEndToEnd::UpdateSpeedInterval( SteamNetworkingMicroseconds usecNow ) +{ + float flElapsed = int64( usecNow - m_usecIntervalStart ) * 1e-6; + flElapsed = Max( flElapsed, .001f ); // make sure math doesn't blow up + + int nTXKBs = ( m_nTXSpeed + 512 ) / 1024; + m_TXSpeedSample.AddSample( nTXKBs ); + + if ( nTXKBs <= 16 ) ++m_nTXSpeedHistogram16; + else if ( nTXKBs <= 32 ) ++m_nTXSpeedHistogram32; + else if ( nTXKBs <= 64 ) ++m_nTXSpeedHistogram64; + else if ( nTXKBs <= 128 ) ++m_nTXSpeedHistogram128; + else if ( nTXKBs <= 256 ) ++m_nTXSpeedHistogram256; + else if ( nTXKBs <= 512 ) ++m_nTXSpeedHistogram512; + else if ( nTXKBs <= 1024 ) ++m_nTXSpeedHistogram1024; + else ++m_nTXSpeedHistogramMax; + + int nRXKBs = ( m_nRXSpeed + 512 ) / 1024; + m_RXSpeedSample.AddSample( nRXKBs ); + + if ( nRXKBs <= 16 ) ++m_nRXSpeedHistogram16; + else if ( nRXKBs <= 32 ) ++m_nRXSpeedHistogram32; + else if ( nRXKBs <= 64 ) ++m_nRXSpeedHistogram64; + else if ( nRXKBs <= 128 ) ++m_nRXSpeedHistogram128; + else if ( nRXKBs <= 256 ) ++m_nRXSpeedHistogram256; + else if ( nRXKBs <= 512 ) ++m_nRXSpeedHistogram512; + else if ( nRXKBs <= 1024 ) ++m_nRXSpeedHistogram1024; + else ++m_nRXSpeedHistogramMax; + + // Reset for next time + StartNextSpeedInterval( usecNow ); +} + +void LinkStatsTrackerEndToEnd::UpdateSpeeds( int nTXSpeed, int nRXSpeed ) +{ + m_nTXSpeed = nTXSpeed; + m_nRXSpeed = nRXSpeed; + + m_nTXSpeedMax = Max( m_nTXSpeedMax, nTXSpeed ); + m_nRXSpeedMax = Max( m_nRXSpeedMax, nRXSpeed ); +} + +void LinkStatsTrackerEndToEnd::GetLifetimeStats( SteamDatagramLinkLifetimeStats &s ) const +{ + LinkStatsTrackerBase::GetLifetimeStats(s); + + s.m_nTXSpeedMax = m_nTXSpeedMax; + + s.m_nTXSpeedHistogram16 = m_nTXSpeedHistogram16; + s.m_nTXSpeedHistogram32 = m_nTXSpeedHistogram32; + s.m_nTXSpeedHistogram64 = m_nTXSpeedHistogram64; + s.m_nTXSpeedHistogram128 = m_nTXSpeedHistogram128; + s.m_nTXSpeedHistogram256 = m_nTXSpeedHistogram256; + s.m_nTXSpeedHistogram512 = m_nTXSpeedHistogram512; + s.m_nTXSpeedHistogram1024 = m_nTXSpeedHistogram1024; + s.m_nTXSpeedHistogramMax = m_nTXSpeedHistogramMax; + + s.m_nTXSpeedNtile5th = m_TXSpeedSample.NumSamples() < 20 ? -1 : m_TXSpeedSample.GetPercentile( .05f ); + s.m_nTXSpeedNtile50th = m_TXSpeedSample.NumSamples() < 2 ? -1 : m_TXSpeedSample.GetPercentile( .50f ); + s.m_nTXSpeedNtile75th = m_TXSpeedSample.NumSamples() < 4 ? -1 : m_TXSpeedSample.GetPercentile( .75f ); + s.m_nTXSpeedNtile95th = m_TXSpeedSample.NumSamples() < 20 ? -1 : m_TXSpeedSample.GetPercentile( .95f ); + s.m_nTXSpeedNtile98th = m_TXSpeedSample.NumSamples() < 50 ? -1 : m_TXSpeedSample.GetPercentile( .98f ); + + s.m_nRXSpeedMax = m_nRXSpeedMax; + + s.m_nRXSpeedHistogram16 = m_nRXSpeedHistogram16; + s.m_nRXSpeedHistogram32 = m_nRXSpeedHistogram32; + s.m_nRXSpeedHistogram64 = m_nRXSpeedHistogram64; + s.m_nRXSpeedHistogram128 = m_nRXSpeedHistogram128; + s.m_nRXSpeedHistogram256 = m_nRXSpeedHistogram256; + s.m_nRXSpeedHistogram512 = m_nRXSpeedHistogram512; + s.m_nRXSpeedHistogram1024 = m_nRXSpeedHistogram1024; + s.m_nRXSpeedHistogramMax = m_nRXSpeedHistogramMax; + + s.m_nRXSpeedNtile5th = m_RXSpeedSample.NumSamples() < 20 ? -1 : m_RXSpeedSample.GetPercentile( .05f ); + s.m_nRXSpeedNtile50th = m_RXSpeedSample.NumSamples() < 2 ? -1 : m_RXSpeedSample.GetPercentile( .50f ); + s.m_nRXSpeedNtile75th = m_RXSpeedSample.NumSamples() < 4 ? -1 : m_RXSpeedSample.GetPercentile( .75f ); + s.m_nRXSpeedNtile95th = m_RXSpeedSample.NumSamples() < 20 ? -1 : m_RXSpeedSample.GetPercentile( .95f ); + s.m_nRXSpeedNtile98th = m_RXSpeedSample.NumSamples() < 50 ? -1 : m_RXSpeedSample.GetPercentile( .98f ); +} + +namespace SteamNetworkingSocketsLib +{ + +void LinkStatsInstantaneousStructToMsg( const SteamDatagramLinkInstantaneousStats &s, CMsgSteamDatagramLinkInstantaneousStats &msg ) +{ + msg.set_out_packets_per_sec_x10( uint32( s.m_flOutPacketsPerSec * 10.0f ) ); + msg.set_out_bytes_per_sec( uint32( s.m_flOutBytesPerSec ) ); + msg.set_in_packets_per_sec_x10( uint32( s.m_flInPacketsPerSec * 10.0f ) ); + msg.set_in_bytes_per_sec( uint32( s.m_flInBytesPerSec ) ); + if ( s.m_nPingMS >= 0 ) + msg.set_ping_ms( uint32( s.m_nPingMS ) ); + if ( s.m_flPacketsDroppedPct >= 0.0f ) + msg.set_packets_dropped_pct( uint32( s.m_flPacketsDroppedPct * 100.0f ) ); + if ( s.m_flPacketsWeirdSequenceNumberPct >= 0.0f ) + msg.set_packets_weird_sequence_pct( uint32( s.m_flPacketsWeirdSequenceNumberPct * 100.0f ) ); + if ( s.m_usecMaxJitter >= 0 ) + msg.set_peak_jitter_usec( s.m_usecMaxJitter ); +} + +void LinkStatsInstantaneousMsgToStruct( const CMsgSteamDatagramLinkInstantaneousStats &msg, SteamDatagramLinkInstantaneousStats &s ) +{ + s.m_flOutPacketsPerSec = msg.out_packets_per_sec_x10() * .1f; + s.m_flOutBytesPerSec = msg.out_bytes_per_sec(); + s.m_flInPacketsPerSec = msg.in_packets_per_sec_x10() * .1f; + s.m_flInBytesPerSec = msg.in_bytes_per_sec(); + if ( msg.has_ping_ms() ) + s.m_nPingMS = msg.ping_ms(); + else + s.m_nPingMS = -1; + + if ( msg.has_packets_dropped_pct() ) + s.m_flPacketsDroppedPct = msg.packets_dropped_pct() * .01f; + else + s.m_flPacketsDroppedPct = -1.0f; + + if ( msg.has_packets_weird_sequence_pct() ) + s.m_flPacketsWeirdSequenceNumberPct = msg.packets_weird_sequence_pct() * .01f; + else + s.m_flPacketsWeirdSequenceNumberPct = -1.0f; + + if ( msg.has_peak_jitter_usec() ) + s.m_usecMaxJitter = msg.peak_jitter_usec(); + else + s.m_usecMaxJitter = -1; + +} + +void LinkStatsLifetimeStructToMsg( const SteamDatagramLinkLifetimeStats &s, CMsgSteamDatagramLinkLifetimeStats &msg ) +{ + msg.set_packets_sent( s.m_nPacketsSent ); + msg.set_kb_sent( ( s.m_nBytesSent + 512 ) / 1024 ); + msg.set_packets_recv( s.m_nPacketsRecv ); + msg.set_kb_recv( ( s.m_nBytesRecv + 512 ) / 1024 ); + msg.set_packets_recv_sequenced( s.m_nPktsRecvSequenced ); + msg.set_packets_recv_dropped( s.m_nPktsRecvDropped ); + msg.set_packets_recv_out_of_order( s.m_nPktsRecvOutOfOrder ); + msg.set_packets_recv_duplicate( s.m_nPktsRecvDuplicate ); + msg.set_packets_recv_lurch( s.m_nPktsRecvSequenceNumberLurch ); + + #define SET_HISTOGRAM( mbr, field ) if ( mbr > 0 ) msg.set_ ## field( mbr ); + #define SET_NTILE( mbr, field ) if ( mbr >= 0 ) msg.set_ ## field( mbr ); + + SET_HISTOGRAM( s.m_qualityHistogram.m_n100 , quality_histogram_100 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n99 , quality_histogram_99 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n97 , quality_histogram_97 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n95 , quality_histogram_95 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n90 , quality_histogram_90 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n75 , quality_histogram_75 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n50 , quality_histogram_50 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n1 , quality_histogram_1 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_nDead, quality_histogram_dead ) + + SET_NTILE( s.m_nQualityNtile50th, quality_ntile_50th ) + SET_NTILE( s.m_nQualityNtile25th, quality_ntile_25th ) + SET_NTILE( s.m_nQualityNtile5th , quality_ntile_5th ) + SET_NTILE( s.m_nQualityNtile2nd , quality_ntile_2nd ) + + SET_HISTOGRAM( s.m_pingHistogram.m_n25 , ping_histogram_25 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n50 , ping_histogram_50 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n75 , ping_histogram_75 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n100, ping_histogram_100 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n125, ping_histogram_125 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n150, ping_histogram_150 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n200, ping_histogram_200 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n300, ping_histogram_300 ) + SET_HISTOGRAM( s.m_pingHistogram.m_nMax, ping_histogram_max ) + + SET_NTILE( s.m_nPingNtile5th , ping_ntile_5th ) + SET_NTILE( s.m_nPingNtile50th, ping_ntile_50th ) + SET_NTILE( s.m_nPingNtile75th, ping_ntile_75th ) + SET_NTILE( s.m_nPingNtile95th, ping_ntile_95th ) + SET_NTILE( s.m_nPingNtile98th, ping_ntile_98th ) + + SET_HISTOGRAM( s.m_jitterHistogram.m_nNegligible, jitter_histogram_negligible ) + SET_HISTOGRAM( s.m_jitterHistogram.m_n1, jitter_histogram_1 ) + SET_HISTOGRAM( s.m_jitterHistogram.m_n2, jitter_histogram_2 ) + SET_HISTOGRAM( s.m_jitterHistogram.m_n5, jitter_histogram_5 ) + SET_HISTOGRAM( s.m_jitterHistogram.m_n10, jitter_histogram_10 ) + SET_HISTOGRAM( s.m_jitterHistogram.m_n20, jitter_histogram_20 ) + + if ( s.m_nTXSpeedMax > 0 ) + msg.set_txspeed_max( s.m_nTXSpeedMax ); + + SET_HISTOGRAM( s.m_nTXSpeedHistogram16, txspeed_histogram_16 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogram32, txspeed_histogram_32 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogram64, txspeed_histogram_64 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogram128, txspeed_histogram_128 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogram256, txspeed_histogram_256 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogram512, txspeed_histogram_512 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogram1024, txspeed_histogram_1024 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogramMax, txspeed_histogram_max ) + + SET_NTILE( s.m_nTXSpeedNtile5th, txspeed_ntile_5th ) + SET_NTILE( s.m_nTXSpeedNtile50th, txspeed_ntile_50th ) + SET_NTILE( s.m_nTXSpeedNtile75th, txspeed_ntile_75th ) + SET_NTILE( s.m_nTXSpeedNtile95th, txspeed_ntile_95th ) + SET_NTILE( s.m_nTXSpeedNtile98th, txspeed_ntile_98th ) + + if ( s.m_nRXSpeedMax > 0 ) + msg.set_rxspeed_max( s.m_nRXSpeedMax ); + + SET_HISTOGRAM( s.m_nRXSpeedHistogram16, rxspeed_histogram_16 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogram32, rxspeed_histogram_32 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogram64, rxspeed_histogram_64 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogram128, rxspeed_histogram_128 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogram256, rxspeed_histogram_256 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogram512, rxspeed_histogram_512 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogram1024, rxspeed_histogram_1024 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogramMax, rxspeed_histogram_max ) + + SET_NTILE( s.m_nRXSpeedNtile5th, rxspeed_ntile_5th ) + SET_NTILE( s.m_nRXSpeedNtile50th, rxspeed_ntile_50th ) + SET_NTILE( s.m_nRXSpeedNtile75th, rxspeed_ntile_75th ) + SET_NTILE( s.m_nRXSpeedNtile95th, rxspeed_ntile_95th ) + SET_NTILE( s.m_nRXSpeedNtile98th, rxspeed_ntile_98th ) + + #undef SET_HISTOGRAM + #undef SET_NTILE +} + +void LinkStatsLifetimeMsgToStruct( const CMsgSteamDatagramLinkLifetimeStats &msg, SteamDatagramLinkLifetimeStats &s ) +{ + s.m_nPacketsSent = msg.packets_sent(); + s.m_nBytesSent = msg.kb_sent() * 1024; + s.m_nPacketsRecv = msg.packets_recv(); + s.m_nBytesRecv = msg.kb_recv() * 1024; + s.m_nPktsRecvSequenced = msg.packets_recv_sequenced(); + s.m_nPktsRecvDropped = msg.packets_recv_dropped(); + s.m_nPktsRecvOutOfOrder = msg.packets_recv_out_of_order(); + s.m_nPktsRecvDuplicate = msg.packets_recv_duplicate(); + s.m_nPktsRecvSequenceNumberLurch = msg.packets_recv_lurch(); + + #define SET_HISTOGRAM( mbr, field ) mbr = msg.field(); + #define SET_NTILE( mbr, field ) mbr = ( msg.has_ ## field() ? msg.field() : -1 ); + + SET_HISTOGRAM( s.m_qualityHistogram.m_n100 , quality_histogram_100 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n99 , quality_histogram_99 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n97 , quality_histogram_97 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n95 , quality_histogram_95 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n90 , quality_histogram_90 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n75 , quality_histogram_75 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n50 , quality_histogram_50 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_n1 , quality_histogram_1 ) + SET_HISTOGRAM( s.m_qualityHistogram.m_nDead, quality_histogram_dead ) + + SET_NTILE( s.m_nQualityNtile50th, quality_ntile_50th ) + SET_NTILE( s.m_nQualityNtile25th, quality_ntile_25th ) + SET_NTILE( s.m_nQualityNtile5th , quality_ntile_5th ) + SET_NTILE( s.m_nQualityNtile2nd , quality_ntile_2nd ) + + SET_HISTOGRAM( s.m_pingHistogram.m_n25 , ping_histogram_25 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n50 , ping_histogram_50 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n75 , ping_histogram_75 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n100, ping_histogram_100 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n125, ping_histogram_125 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n150, ping_histogram_150 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n200, ping_histogram_200 ) + SET_HISTOGRAM( s.m_pingHistogram.m_n300, ping_histogram_300 ) + SET_HISTOGRAM( s.m_pingHistogram.m_nMax, ping_histogram_max ) + + SET_NTILE( s.m_nPingNtile5th , ping_ntile_5th ) + SET_NTILE( s.m_nPingNtile50th, ping_ntile_50th ) + SET_NTILE( s.m_nPingNtile75th, ping_ntile_75th ) + SET_NTILE( s.m_nPingNtile95th, ping_ntile_95th ) + SET_NTILE( s.m_nPingNtile98th, ping_ntile_98th ) + + SET_HISTOGRAM( s.m_jitterHistogram.m_nNegligible, jitter_histogram_negligible ) + SET_HISTOGRAM( s.m_jitterHistogram.m_n1, jitter_histogram_1 ) + SET_HISTOGRAM( s.m_jitterHistogram.m_n2, jitter_histogram_2 ) + SET_HISTOGRAM( s.m_jitterHistogram.m_n5, jitter_histogram_5 ) + SET_HISTOGRAM( s.m_jitterHistogram.m_n10, jitter_histogram_10 ) + SET_HISTOGRAM( s.m_jitterHistogram.m_n20, jitter_histogram_20 ) + + s.m_nTXSpeedMax = msg.txspeed_max(); + + SET_HISTOGRAM( s.m_nTXSpeedHistogram16, txspeed_histogram_16 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogram32, txspeed_histogram_32 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogram64, txspeed_histogram_64 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogram128, txspeed_histogram_128 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogram256, txspeed_histogram_256 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogram512, txspeed_histogram_512 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogram1024, txspeed_histogram_1024 ) + SET_HISTOGRAM( s.m_nTXSpeedHistogramMax, txspeed_histogram_max ) + + SET_NTILE( s.m_nTXSpeedNtile5th, txspeed_ntile_5th ) + SET_NTILE( s.m_nTXSpeedNtile50th, txspeed_ntile_50th ) + SET_NTILE( s.m_nTXSpeedNtile75th, txspeed_ntile_75th ) + SET_NTILE( s.m_nTXSpeedNtile95th, txspeed_ntile_95th ) + SET_NTILE( s.m_nTXSpeedNtile98th, txspeed_ntile_98th ) + + s.m_nRXSpeedMax = msg.rxspeed_max(); + + SET_HISTOGRAM( s.m_nRXSpeedHistogram16, rxspeed_histogram_16 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogram32, rxspeed_histogram_32 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogram64, rxspeed_histogram_64 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogram128, rxspeed_histogram_128 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogram256, rxspeed_histogram_256 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogram512, rxspeed_histogram_512 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogram1024, rxspeed_histogram_1024 ) + SET_HISTOGRAM( s.m_nRXSpeedHistogramMax, rxspeed_histogram_max ) + + SET_NTILE( s.m_nRXSpeedNtile5th, rxspeed_ntile_5th ) + SET_NTILE( s.m_nRXSpeedNtile50th, rxspeed_ntile_50th ) + SET_NTILE( s.m_nRXSpeedNtile75th, rxspeed_ntile_75th ) + SET_NTILE( s.m_nRXSpeedNtile95th, rxspeed_ntile_95th ) + SET_NTILE( s.m_nRXSpeedNtile98th, rxspeed_ntile_98th ) + + #undef SET_HISTOGRAM + #undef SET_NTILE +} + +static void PrintPct( char (&szBuf)[32], float flPct ) +{ + flPct *= 100.0f; + if ( flPct < 0.0f ) + V_strcpy_safe( szBuf, "???" ); + else if ( flPct < 9.5f ) + V_sprintf_safe( szBuf, "%.2f", flPct ); + else if ( flPct < 99.5f ) + V_sprintf_safe( szBuf, "%.1f", flPct ); + else + V_sprintf_safe( szBuf, "%.0f", flPct ); +} + +void LinkStatsPrintInstantaneousToBuf( const char *pszLeader, const SteamDatagramLinkInstantaneousStats &stats, CUtlBuffer &buf ) +{ + buf.Printf( "%sSent:%6.1f pkts/sec%6.1f K/sec\n", pszLeader, stats.m_flOutPacketsPerSec, stats.m_flOutBytesPerSec/1024.0f ); + buf.Printf( "%sRecv:%6.1f pkts/sec%6.1f K/sec\n", pszLeader, stats.m_flInPacketsPerSec, stats.m_flInBytesPerSec/1024.0f ); + + if ( stats.m_nPingMS >= 0 || stats.m_usecMaxJitter >= 0 ) + { + char szPing[ 32 ]; + if ( stats.m_nPingMS < 0 ) + V_strcpy_safe( szPing, "???" ); + else + V_sprintf_safe( szPing, "%d", stats.m_nPingMS ); + + char szPeakJitter[ 32 ]; + if ( stats.m_usecMaxJitter < 0 ) + V_strcpy_safe( szPeakJitter, "???" ); + else + V_sprintf_safe( szPeakJitter, "%.1f", stats.m_usecMaxJitter*1e-3f ); + + buf.Printf( "%sPing:%sms Max latency variance: %sms\n", pszLeader, szPing, szPeakJitter ); + } + + if ( stats.m_flPacketsDroppedPct >= 0.0f && stats.m_flPacketsWeirdSequenceNumberPct >= 0.0f ) + { + char szDropped[ 32 ]; + PrintPct( szDropped, stats.m_flPacketsDroppedPct ); + + char szWeirdSeq[ 32 ]; + PrintPct( szWeirdSeq, stats.m_flPacketsWeirdSequenceNumberPct ); + + char szQuality[32]; + PrintPct( szQuality, 1.0f - stats.m_flPacketsDroppedPct - stats.m_flPacketsWeirdSequenceNumberPct ); + buf.Printf( "%sQuality:%5s%% (Dropped:%4s%% WeirdSeq:%4s%%)\n", pszLeader, szQuality, szDropped, szWeirdSeq); + } + + if ( stats.m_nSendRate > 0 ) + buf.Printf( "%sEst avail bandwidth: %.1fKB/s \n", pszLeader, stats.m_nSendRate/1024.0f ); + if ( stats.m_nPendingBytes >= 0 ) + buf.Printf( "%sBytes buffered: %s\n", pszLeader, NumberPrettyPrinter( stats.m_nPendingBytes ).String() ); +} + +void LinkStatsPrintLifetimeToBuf( const char *pszLeader, const SteamDatagramLinkLifetimeStats &stats, CUtlBuffer &buf ) +{ + buf.Printf( "%sTotals\n", pszLeader ); + buf.Printf( "%s Sent:%11s pkts %15s bytes\n", pszLeader, NumberPrettyPrinter( stats.m_nPacketsSent ).String(), NumberPrettyPrinter( stats.m_nBytesSent ).String() ); + buf.Printf( "%s Recv:%11s pkts %15s bytes\n", pszLeader, NumberPrettyPrinter( stats.m_nPacketsRecv ).String(), NumberPrettyPrinter( stats.m_nBytesRecv ).String() ); + if ( stats.m_nPktsRecvSequenced > 0 ) + { + buf.Printf( "%s Recv w seq:%11s pkts\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvSequenced ).String() ); + float flToPct = 100.0f / ( stats.m_nPktsRecvSequenced + stats.m_nPktsRecvDropped ); + buf.Printf( "%s Dropped :%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvDropped ).String(), stats.m_nPktsRecvDropped * flToPct ); + buf.Printf( "%s OutOfOrder:%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvOutOfOrder ).String(), stats.m_nPktsRecvOutOfOrder * flToPct ); + buf.Printf( "%s Duplicate :%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvDuplicate ).String(), stats.m_nPktsRecvDuplicate * flToPct ); + buf.Printf( "%s SeqLurch :%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvSequenceNumberLurch ).String(), stats.m_nPktsRecvSequenceNumberLurch * flToPct ); + } + + // Do we have enough ping samples such that the distribution might be interesting + { + int nPingSamples = stats.m_pingHistogram.TotalCount(); + if ( nPingSamples >= 5 ) + { + float flToPct = 100.0f / nPingSamples; + buf.Printf( "%sPing histogram: (%d total samples)\n", pszLeader, nPingSamples ); + buf.Printf( "%s 0-25 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n25 , stats.m_pingHistogram.m_n25 *flToPct ); + buf.Printf( "%s 25-50 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n50 , stats.m_pingHistogram.m_n50 *flToPct ); + buf.Printf( "%s 50-75 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n75 , stats.m_pingHistogram.m_n75 *flToPct ); + buf.Printf( "%s 75-100 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n100, stats.m_pingHistogram.m_n100*flToPct ); + buf.Printf( "%s 100-125 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n125, stats.m_pingHistogram.m_n125*flToPct ); + buf.Printf( "%s 125-150 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n150, stats.m_pingHistogram.m_n150*flToPct ); + buf.Printf( "%s 150-200 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n200, stats.m_pingHistogram.m_n200*flToPct ); + buf.Printf( "%s 200-300 :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_n300, stats.m_pingHistogram.m_n300*flToPct ); + buf.Printf( "%s 300+ :%5d %3.0f%%\n", pszLeader, stats.m_pingHistogram.m_nMax, stats.m_pingHistogram.m_nMax*flToPct ); + buf.Printf( "%sPing distribution:\n", pszLeader ); + if ( stats.m_nPingNtile5th >= 0 ) buf.Printf( "%s 5%% of pings <= %4dms\n", pszLeader, stats.m_nPingNtile5th ); + if ( stats.m_nPingNtile50th >= 0 ) buf.Printf( "%s 50%% of pings <= %4dms\n", pszLeader, stats.m_nPingNtile50th ); + if ( stats.m_nPingNtile75th >= 0 ) buf.Printf( "%s 75%% of pings <= %4dms\n", pszLeader, stats.m_nPingNtile75th ); + if ( stats.m_nPingNtile95th >= 0 ) buf.Printf( "%s 95%% of pings <= %4dms\n", pszLeader, stats.m_nPingNtile95th ); + if ( stats.m_nPingNtile98th >= 0 ) buf.Printf( "%s 98%% of pings <= %4dms\n", pszLeader, stats.m_nPingNtile98th ); + } + else + { + buf.Printf( "%sNo ping distribution available. (%d samples)\n", pszLeader, nPingSamples ); + } + } + + // Do we have enough quality samples such that the distribution might be interesting? + { + int nQualitySamples = stats.m_qualityHistogram.TotalCount(); + if ( nQualitySamples >= 5 ) + { + float flToPct = 100.0f / nQualitySamples; + + buf.Printf( "%sConnection quality histogram: (%d measurement intervals)\n", pszLeader, nQualitySamples ); + buf.Printf( "%s 100 :%5d %3.0f%% (All packets received in order)\n", pszLeader, stats.m_qualityHistogram.m_n100, stats.m_qualityHistogram.m_n100*flToPct ); + buf.Printf( "%s 99+ :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n99, stats.m_qualityHistogram.m_n99*flToPct ); + buf.Printf( "%s 97-99 :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n97, stats.m_qualityHistogram.m_n97*flToPct ); + buf.Printf( "%s 95-97 :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n95, stats.m_qualityHistogram.m_n95*flToPct ); + buf.Printf( "%s 90-95 :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n90, stats.m_qualityHistogram.m_n90*flToPct ); + buf.Printf( "%s 75-90 :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n75, stats.m_qualityHistogram.m_n75*flToPct ); + buf.Printf( "%s 50-75 :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n50, stats.m_qualityHistogram.m_n50*flToPct ); + buf.Printf( "%s <50 :%5d %3.0f%%\n", pszLeader, stats.m_qualityHistogram.m_n1, stats.m_qualityHistogram.m_n1*flToPct ); + buf.Printf( "%s dead :%5d %3.0f%% (Expected to receive something but didn't)\n", pszLeader, stats.m_qualityHistogram.m_nDead, stats.m_qualityHistogram.m_nDead*flToPct ); + buf.Printf( "%sConnection quality distribution:\n", pszLeader ); + if ( stats.m_nQualityNtile50th >= 0 ) buf.Printf( "%s 50%% of intervals >= %3d%%\n", pszLeader, stats.m_nQualityNtile50th ); + if ( stats.m_nQualityNtile25th >= 0 ) buf.Printf( "%s 75%% of intervals >= %3d%%\n", pszLeader, stats.m_nQualityNtile25th ); + if ( stats.m_nQualityNtile5th >= 0 ) buf.Printf( "%s 95%% of intervals >= %3d%%\n", pszLeader, stats.m_nQualityNtile5th ); + if ( stats.m_nQualityNtile2nd >= 0 ) buf.Printf( "%s 98%% of intervals >= %3d%%\n", pszLeader, stats.m_nQualityNtile2nd ); + } + else + { + buf.Printf( "%sNo connection quality distribution available. (%d measurement intervals)\n", pszLeader, nQualitySamples ); + } + } + + // Do we have any jitter samples? + { + int nJitterSamples = stats.m_jitterHistogram.TotalCount(); + if ( nJitterSamples >= 1 ) + { + float flToPct = 100.0f / nJitterSamples; + + buf.Printf( "%sLatency variance histogram: (%d total measurements)\n", pszLeader, nJitterSamples ); + buf.Printf( "%s <1 :%7d %3.0f%%\n", pszLeader, stats.m_jitterHistogram.m_nNegligible, stats.m_jitterHistogram.m_nNegligible*flToPct ); + buf.Printf( "%s 1-2 :%7d %3.0f%%\n", pszLeader, stats.m_jitterHistogram.m_n1 , stats.m_jitterHistogram.m_n1 *flToPct ); + buf.Printf( "%s 2-5 :%7d %3.0f%%\n", pszLeader, stats.m_jitterHistogram.m_n2 , stats.m_jitterHistogram.m_n2 *flToPct ); + buf.Printf( "%s 5-10 :%7d %3.0f%%\n", pszLeader, stats.m_jitterHistogram.m_n5 , stats.m_jitterHistogram.m_n5 *flToPct ); + buf.Printf( "%s 10-20 :%7d %3.0f%%\n", pszLeader, stats.m_jitterHistogram.m_n10, stats.m_jitterHistogram.m_n10*flToPct ); + buf.Printf( "%s >20 :%7d %3.0f%%\n", pszLeader, stats.m_jitterHistogram.m_n20, stats.m_jitterHistogram.m_n20*flToPct ); + } + else + { + buf.Printf( "%sLatency variance histogram not available\n", pszLeader ); + } + } + + // Do we have enough tx speed samples such that the distribution might be interesting? + { + int nTXSpeedSamples = stats.TXSpeedHistogramTotalCount(); + if ( nTXSpeedSamples >= 5 ) + { + float flToPct = 100.0f / nTXSpeedSamples; + buf.Printf( "%sTX Speed histogram: (%d total samples)\n", pszLeader, nTXSpeedSamples ); + buf.Printf( "%s 0 - 16 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram16, stats.m_nTXSpeedHistogram16 *flToPct ); + buf.Printf( "%s 16 - 32 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram32, stats.m_nTXSpeedHistogram32 *flToPct ); + buf.Printf( "%s 32 - 64 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram64, stats.m_nTXSpeedHistogram64 *flToPct ); + buf.Printf( "%s 64 - 128 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram128, stats.m_nTXSpeedHistogram128 *flToPct ); + buf.Printf( "%s 128 - 256 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram256, stats.m_nTXSpeedHistogram256 *flToPct ); + buf.Printf( "%s 256 - 512 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram512, stats.m_nTXSpeedHistogram512 *flToPct ); + buf.Printf( "%s 512 - 1024 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram1024, stats.m_nTXSpeedHistogram1024*flToPct ); + buf.Printf( "%s 1024+ KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogramMax, stats.m_nTXSpeedHistogramMax *flToPct ); + buf.Printf( "%sTransmit speed distribution:\n", pszLeader ); + if ( stats.m_nTXSpeedNtile5th >= 0 ) buf.Printf( "%s 5%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile5th ); + if ( stats.m_nTXSpeedNtile50th >= 0 ) buf.Printf( "%s 50%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile50th ); + if ( stats.m_nTXSpeedNtile75th >= 0 ) buf.Printf( "%s 75%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile75th ); + if ( stats.m_nTXSpeedNtile95th >= 0 ) buf.Printf( "%s 95%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile95th ); + if ( stats.m_nTXSpeedNtile98th >= 0 ) buf.Printf( "%s 98%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile98th ); + } + else + { + buf.Printf( "%sNo connection transmit speed distribution available. (%d measurement intervals)\n", pszLeader, nTXSpeedSamples ); + } + } + + // Do we have enough RX speed samples such that the distribution might be interesting? + { + int nRXSpeedSamples = stats.RXSpeedHistogramTotalCount(); + if ( nRXSpeedSamples >= 5 ) + { + float flToPct = 100.0f / nRXSpeedSamples; + buf.Printf( "%sRX Speed histogram: (%d total samples)\n", pszLeader, nRXSpeedSamples ); + buf.Printf( "%s 0 - 16 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram16, stats.m_nRXSpeedHistogram16 *flToPct ); + buf.Printf( "%s 16 - 32 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram32, stats.m_nRXSpeedHistogram32 *flToPct ); + buf.Printf( "%s 32 - 64 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram64, stats.m_nRXSpeedHistogram64 *flToPct ); + buf.Printf( "%s 64 - 128 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram128, stats.m_nRXSpeedHistogram128 *flToPct ); + buf.Printf( "%s 128 - 256 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram256, stats.m_nRXSpeedHistogram256 *flToPct ); + buf.Printf( "%s 256 - 512 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram512, stats.m_nRXSpeedHistogram512 *flToPct ); + buf.Printf( "%s 512 - 1024 KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram1024, stats.m_nRXSpeedHistogram1024*flToPct ); + buf.Printf( "%s 1024+ KB/s:%5d %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogramMax, stats.m_nRXSpeedHistogramMax *flToPct ); + buf.Printf( "%sReceive speed distribution:\n", pszLeader ); + if ( stats.m_nRXSpeedNtile5th >= 0 ) buf.Printf( "%s 5%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile5th ); + if ( stats.m_nRXSpeedNtile50th >= 0 ) buf.Printf( "%s 50%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile50th ); + if ( stats.m_nRXSpeedNtile75th >= 0 ) buf.Printf( "%s 75%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile75th ); + if ( stats.m_nRXSpeedNtile95th >= 0 ) buf.Printf( "%s 95%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile95th ); + if ( stats.m_nRXSpeedNtile98th >= 0 ) buf.Printf( "%s 98%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile98th ); + } + else + { + buf.Printf( "%sNo connection recieve speed distribution available. (%d measurement intervals)\n", pszLeader, nRXSpeedSamples ); + } + } + +} + +void LinkStatsPrintToBuf( const char *pszLeader, const SteamDatagramLinkStats &stats, CUtlBuffer &buf ) +{ + std::string sIndent( pszLeader ); sIndent.append( " " ); + + buf.Printf( "%sCurrent rates:\n", pszLeader ); + LinkStatsPrintInstantaneousToBuf( sIndent.c_str(), stats.m_latest, buf ); + buf.Printf( "%sLifetime stats:\n", pszLeader ); + LinkStatsPrintLifetimeToBuf( sIndent.c_str(), stats.m_lifetime, buf ); + + if ( stats.m_flAgeLatestRemote < 0.0f ) + { + buf.Printf( "%sNo rate stats received from remote host\n", pszLeader ); + } + else + { + buf.Printf( "%sRate stats received from remote host %.1fs ago:\n", pszLeader, stats.m_flAgeLatestRemote ); + LinkStatsPrintInstantaneousToBuf( sIndent.c_str(), stats.m_latestRemote, buf ); + } + + if ( stats.m_flAgeLifetimeRemote < 0.0f ) + { + buf.Printf( "%sNo lifetime stats received from remote host\n", pszLeader ); + } + else + { + buf.Printf( "%sLifetime stats received from remote host %.1fs ago:\n", pszLeader, stats.m_flAgeLifetimeRemote ); + LinkStatsPrintLifetimeToBuf( sIndent.c_str(), stats.m_lifetimeRemote, buf ); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// +// SteamNetworkingDetailedConnectionStatus +// +/////////////////////////////////////////////////////////////////////////////// + +void SteamNetworkingDetailedConnectionStatus::Clear() +{ + V_memset( this, 0, sizeof(*this) ); + COMPILE_TIME_ASSERT( k_ESteamNetworkingAvailability_Unknown == 0 ); + m_statsEndToEnd.Clear(); + m_statsPrimaryRouter.Clear(); + m_nPrimaryRouterBackPing = -1; + m_nBackupRouterFrontPing = -1; + m_nBackupRouterBackPing = -1; +} + +int SteamNetworkingDetailedConnectionStatus::Print( char *pszBuf, int cbBuf ) +{ + CUtlBuffer buf( 0, 8*1024, CUtlBuffer::TEXT_BUFFER ); + + // If we don't have network, there's nothing else we can really do + if ( m_eAvailNetworkConfig != k_ESteamNetworkingAvailability_Current && m_eAvailNetworkConfig != k_ESteamNetworkingAvailability_Unknown ) + { + buf.Printf( "Network configuration: %s\n", GetAvailabilityString( m_eAvailNetworkConfig ) ); + buf.Printf( " Cannot communicate with relays without network config." ); + } + + // Unable to talk to any routers? + if ( m_eAvailAnyRouterCommunication != k_ESteamNetworkingAvailability_Current && m_eAvailAnyRouterCommunication != k_ESteamNetworkingAvailability_Unknown ) + { + buf.Printf( "Router network: %s\n", GetAvailabilityString( m_eAvailAnyRouterCommunication ) ); + } + + switch ( m_info.m_eState ) + { + case k_ESteamNetworkingConnectionState_Connecting: + buf.Printf( "End-to-end connection: connecting\n" ); + break; + + case k_ESteamNetworkingConnectionState_FindingRoute: + buf.Printf( "End-to-end connection: performing rendezvous\n" ); + break; + + case k_ESteamNetworkingConnectionState_Connected: + buf.Printf( "End-to-end connection: connected\n" ); + break; + + case k_ESteamNetworkingConnectionState_ClosedByPeer: + buf.Printf( "End-to-end connection: closed by remote host, reason code %d. (%s)\n", m_info.m_eEndReason, m_info.m_szEndDebug ); + break; + + case k_ESteamNetworkingConnectionState_ProblemDetectedLocally: + buf.Printf( "End-to-end connection: closed due to problem detected locally, reason code %d. (%s)\n", m_info.m_eEndReason, m_info.m_szEndDebug ); + break; + + case k_ESteamNetworkingConnectionState_None: + buf.Printf( "End-to-end connection: closed, reason code %d. (%s)\n", m_info.m_eEndReason, m_info.m_szEndDebug ); + break; + + default: + buf.Printf( "End-to-end connection: BUG: invalid state %d!\n", m_info.m_eState ); + break; + } + + if ( m_info.m_idPOPRemote ) + { + buf.Printf( " Remote host is in data center '%s'\n", SteamNetworkingPOPIDRender( m_info.m_idPOPRemote ).c_str() ); + } + + // If we ever tried to send a packet end-to-end, dump end-to-end stats. + if ( m_statsEndToEnd.m_lifetime.m_nPacketsSent > 0 ) + { + LinkStatsPrintToBuf( " ", m_statsEndToEnd, buf ); + } + + if ( m_unPrimaryRouterIP ) + { + buf.Printf( "Primary router: %s", m_szPrimaryRouterName ); + + int nPrimaryFrontPing = m_statsPrimaryRouter.m_latest.m_nPingMS; + if ( m_nPrimaryRouterBackPing >= 0 ) + buf.Printf( " Ping = %d+%d=%d (front+back=total)\n", nPrimaryFrontPing, m_nPrimaryRouterBackPing,nPrimaryFrontPing+m_nPrimaryRouterBackPing ); + else + buf.Printf( " Ping to relay = %d\n", nPrimaryFrontPing ); + LinkStatsPrintToBuf( " ", m_statsPrimaryRouter, buf ); + + if ( m_unBackupRouterIP == 0 ) + { + // Probably should only print this if we have reason to expect that a backup relay is expected + //buf.Printf( "No backup router selected\n" ); + } + else + { + buf.Printf( "Backup router: %s Ping = %d+%d=%d (front+back=total)\n", + m_szBackupRouterName, + m_nBackupRouterFrontPing, m_nBackupRouterBackPing,m_nBackupRouterFrontPing+m_nBackupRouterBackPing + ); + } + } + else if ( m_info.m_idPOPRelay ) + { + buf.Printf( "Communicating via relay in '%s'\n", SteamNetworkingPOPIDRender( m_info.m_idPOPRelay ).c_str() ); + } + + int sz = buf.TellPut()+1; + if ( pszBuf && cbBuf > 0 ) + { + int l = Min( sz, cbBuf ) - 1; + V_memcpy( pszBuf, buf.Base(), l ); + pszBuf[l] = '\0'; + if ( cbBuf >= sz ) + return 0; + } + + return sz; +} + +} // namespace SteamNetworkingSocketsLib diff --git a/tests/test_connection.cpp b/tests/test_connection.cpp index 4373431..12fb9ad 100644 --- a/tests/test_connection.cpp +++ b/tests/test_connection.cpp @@ -74,7 +74,8 @@ static void InitSteamDatagramConnectionSockets() #else //SteamAPI_Init(); - SteamDatagramClient_SetAppIDAndUniverse( 570, k_EUniverseDev ); // Just set something, doesn't matter what + SteamDatagramClient_SetAppID( 570 ); // Just set something, doesn't matter what + //SteamDatagramClient_SetUniverse( k_EUniverseDev ); SteamDatagramErrMsg errMsg; if ( !SteamDatagramClient_Init( true, errMsg ) )