#include <atomic>
#include <tier1/utlbuffer.h>
#include "steamnetworking_statsutils.h"
#ifdef IS_STEAMDATAGRAMROUTER
#include "router/sdr.h"
#else
#include "clientlib/steamnetworkingsockets_lowlevel.h"
#endif
#include <tier0/memdbgon.h>
using namespace SteamNetworkingSocketsLib;
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_nConnectedSeconds = -1;
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;
}
void SteamDatagramLinkStats::Clear()
{
m_latest.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;
m_usecTimeAllowNewSample = 0;
}
void PingTracker::ReceivedPing( int nPingMS, SteamNetworkingMicroseconds usecNow )
{
Assert( nPingMS >= 0 );
COMPILE_TIME_ASSERT( V_ARRAYSIZE(m_arPing) == 3 );
if ( usecNow < m_usecTimeAllowNewSample && m_nValidPings >= V_ARRAYSIZE(m_arPing) )
{
if ( nPingMS >= m_arPing[0].m_nPingMS )
{
m_arPing[0].m_usecTimeRecv = usecNow;
return;
}
}
else
{
m_arPing[2] = m_arPing[1];
m_arPing[1] = m_arPing[0];
m_usecTimeAllowNewSample = usecNow + k_usecMinPingSampleSpacing;
}
m_arPing[0].m_nPingMS = nPingMS;
m_arPing[0].m_usecTimeRecv = usecNow;
switch ( m_nValidPings )
{
case 0:
m_nValidPings = 1;
m_nSmoothedPing = nPingMS;
m_usecTimeAllowNewSample = 0; break;
case 1:
m_nValidPings = 2;
m_nSmoothedPing = std::min( m_arPing[0].m_nPingMS, m_arPing[1].m_nPingMS );
m_usecTimeAllowNewSample = 0; break;
default:
AssertMsg1( false, "Unexpected valid ping count %d", m_nValidPings );
case 2:
m_nValidPings = 3;
case 3:
{
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::WorstPingInRecentSample() const
{
if ( unlikely( m_nValidPings < 1 ) )
{
if ( m_nSmoothedPing >= 0 )
return m_nSmoothedPing;
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;
}
LinkStatsTrackerBase::~LinkStatsTrackerBase()
{
if ( unlikely( m_pPossibleOutOfOrderPacket != nullptr ) )
{
m_pPossibleOutOfOrderPacket->Destroy();
Assert( m_pPossibleOutOfOrderPacket == nullptr );
}
}
void CPossibleOutOfOrderPacket::Destroy()
{
Detach();
DoDestroy();
}
void CPossibleOutOfOrderPacket::Detach()
{
if ( m_pOwner )
{
#ifdef IS_STEAMDATAGRAMROUTER
if ( unlikely( m_pOwner->m_pPossibleOutOfOrderPacket != this ) )
{
*(volatile int *)nullptr = 0;
}
#else
Assert( m_pOwner->m_pPossibleOutOfOrderPacket == this );
#endif
m_pOwner->m_pPossibleOutOfOrderPacket = nullptr;
m_pOwner = nullptr;
}
}
void CPossibleOutOfOrderPacket::DoDestroy()
{
delete this;
}
void CPossibleOutOfOrderPacket::SetOwner( LinkStatsTrackerBase *pOwner )
{
if ( m_pOwner )
{
Assert( false );
Detach();
}
if ( pOwner->m_pPossibleOutOfOrderPacket )
{
Assert( false );
pOwner->m_pPossibleOutOfOrderPacket->Detach();
}
m_pOwner = pOwner;
m_pOwner->m_pPossibleOutOfOrderPacket = this;
}
CPossibleOutOfOrderPacket::~CPossibleOutOfOrderPacket()
{
Assert( m_pOwner == nullptr );
}
void LinkStatsTrackerBase::InitInternal( SteamNetworkingMicroseconds usecNow )
{
m_nPeerProtocolVersion = 0;
m_ping.Reset();
m_nNextSendSequenceNumber = 1;
m_sent.Reset();
m_usecTimeLastSentSeq = 0;
InitMaxRecvPktNum( 0 );
m_recv.Reset();
m_nDebugPktsRecvInOrder = 0;
m_recvExceedRateLimit.Reset();
m_usecTimeLastRecv = 0;
m_usecTimeLastRecvSeq = 0;
m_nMultiPathRecvLater[0] = m_nMultiPathRecvLater[1] = 0;
m_nMultiPathRecvSeq[0] = m_nMultiPathRecvSeq[1] = 0;
m_bMultiPathSendEnabled = false;
m_seqPktCounters.Reset();
m_flInPacketsDroppedPct = -1.0f;
m_flInPacketsWeirdSequencePct = -1.0f;
m_usecMaxJitterPreviousInterval = -1;
m_nPktsRecvSequenced = 0;
m_nPktsRecvDroppedAccumulator = 0;
m_nPktsRecvOutOfOrderAccumulator = 0;
m_nPktsRecvOutOfOrderCorrectedAccumulator = 0;
m_nPktsRecvDuplicateAccumulator = 0;
m_nPktsRecvLurchAccumulator = 0;
m_qualitySample.Clear();
m_qualityHistogram.Reset();
m_jitterHistogram.Reset();
m_latestRemote.Clear();
m_usecTimeRecvLatestRemote = 0;
m_lifetimeRemote.Clear();
m_usecTimeRecvLifetimeRemote = 0;
m_pktNumInFlight = 0;
m_bInFlightInstantaneous = false;
m_bInFlightLifetime = false;
m_usecIntervalStart = 0;
m_usecInFlightReplyTimeout = 0;
m_usecLastSendPacketExpectingImmediateReply = 0;
m_nReplyTimeoutsSinceLastRecv = 0;
m_usecWhenTimeoutStarted = 0;
m_eActivityLevel = ELinkActivityLevel::Disconnected;
if ( unlikely( m_pPossibleOutOfOrderPacket ) )
{
Assert( false );
m_pPossibleOutOfOrderPacket->Destroy();
Assert( m_pPossibleOutOfOrderPacket == nullptr );
}
}
void LinkStatsTrackerBase::SetActivityLevelInternal( ELinkActivityLevel eActivityLevel, SteamNetworkingMicroseconds usecNow )
{
if ( eActivityLevel == ELinkActivityLevel::Disconnected || m_eActivityLevel == ELinkActivityLevel::Disconnected )
{
m_usecInFlightReplyTimeout = 0;
}
m_eActivityLevel = eActivityLevel;
m_pktNumInFlight = 0;
m_bInFlightInstantaneous = false;
m_bInFlightLifetime = false;
PeerAckedInstantaneous( usecNow );
PeerAckedLifetime( usecNow );
StartNextInterval( usecNow );
}
void LinkStatsTrackerBase::StartNextInterval( SteamNetworkingMicroseconds usecNow )
{
m_nPktsRecvDroppedAccumulator += m_seqPktCounters.m_nDropped;
m_nPktsRecvOutOfOrderAccumulator += m_seqPktCounters.m_nOutOfOrder;
m_nPktsRecvOutOfOrderCorrectedAccumulator += m_seqPktCounters.m_nOutOfOrderCorrected;
m_nPktsRecvDuplicateAccumulator += m_seqPktCounters.m_nDuplicate;
m_nPktsRecvLurchAccumulator += m_seqPktCounters.m_nLurch;
m_seqPktCounters.Reset();
m_usecIntervalStart = usecNow;
}
void LinkStatsTrackerBase::UpdateInterval( SteamNetworkingMicroseconds usecNow )
{
float flElapsed = int64( usecNow - m_usecIntervalStart ) * 1e-6;
flElapsed = Max( flElapsed, .001f );
COMPILE_TIME_ASSERT( k_usecSteamDatagramLinkStatsDefaultInterval >= 5*k_nMillion );
if ( flElapsed > 4.5f )
{
if ( m_seqPktCounters.m_nRecv > 5 )
{
int nWeird = m_seqPktCounters.Weird();
int nBad = m_seqPktCounters.m_nDropped + nWeird;
if ( nBad == 0 )
{
m_qualitySample.AddSample( 100 );
AddQualityHistogramSample( QualityHistogram::k_nBucket_100 );
}
else
{
int nTotalSent = m_seqPktCounters.m_nRecv + m_seqPktCounters.m_nDropped;
int nRecvGood = m_seqPktCounters.m_nRecv - nWeird;
int nQuality = nRecvGood * 100 / nTotalSent;
if ( nQuality >= 99 )
{
m_qualitySample.AddSample( 99 );
AddQualityHistogramSample( QualityHistogram::k_nBucket_99 );
}
else if ( nQuality <= 1 ) {
m_qualitySample.AddSample( 1 );
AddQualityHistogramSample( QualityHistogram::k_nBucket_1 );
}
else
{
m_qualitySample.AddSample( nQuality );
if ( nQuality >= 97 )
AddQualityHistogramSample( QualityHistogram::k_nBucket_97 );
else if ( nQuality >= 95 )
AddQualityHistogramSample( QualityHistogram::k_nBucket_95 );
else if ( nQuality >= 90 )
AddQualityHistogramSample( QualityHistogram::k_nBucket_90 );
else if ( nQuality >= 75 )
AddQualityHistogramSample( QualityHistogram::k_nBucket_75 );
else if ( nQuality >= 50 )
AddQualityHistogramSample( QualityHistogram::k_nBucket_50 );
else
AddQualityHistogramSample( QualityHistogram::k_nBucket_1 );
}
}
}
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 );
m_qualitySample.AddSample(0);
AddQualityHistogramSample( QualityHistogram::k_nBucket_Dead );
}
}
m_sent.UpdateInterval( flElapsed );
m_recv.UpdateInterval( flElapsed );
m_recvExceedRateLimit.UpdateInterval( flElapsed );
int nWeirdSequenceCurrentInterval = m_seqPktCounters.Weird();
Assert( nWeirdSequenceCurrentInterval <= m_seqPktCounters.m_nRecv );
if ( m_seqPktCounters.m_nRecv <= 0 )
{
m_flInPacketsDroppedPct = -1.0f;
m_flInPacketsWeirdSequencePct = -1.0f;
}
else
{
float flToPct = 1.0f / float( m_seqPktCounters.m_nRecv + m_seqPktCounters.m_nDropped );
m_flInPacketsDroppedPct = m_seqPktCounters.m_nDropped * flToPct;
m_flInPacketsWeirdSequencePct = nWeirdSequenceCurrentInterval * flToPct;
}
m_usecMaxJitterPreviousInterval = m_seqPktCounters.m_usecMaxJitter;
StartNextInterval( usecNow );
}
void LinkStatsTrackerBase::InitMaxRecvPktNum( int64 nPktNum )
{
Assert( nPktNum >= 0 );
m_nMaxRecvPktNum = nPktNum;
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;
m_recvPktNumberMaskMultiPath[0][0] = m_recvPktNumberMaskMultiPath[1][0] = m_recvPktNumberMask[0];
m_recvPktNumberMaskMultiPath[0][1] = m_recvPktNumberMaskMultiPath[1][1] = m_recvPktNumberMask[1];
m_nDebugLastInitMaxRecvPktNum = nPktNum;
}
std::string LinkStatsTrackerBase::RecvPktNumStateDebugString() const
{
char buf[256];
V_sprintf_safe( buf,
"maxrecv=%lld, init=%lld, inorder=%lld, mask=%llx,%llx",
(long long)m_nMaxRecvPktNum, (long long)m_nDebugLastInitMaxRecvPktNum, (long long)m_nDebugPktsRecvInOrder,
(unsigned long long)m_recvPktNumberMask[0], (unsigned long long)m_recvPktNumberMask[1] );
std::string result( buf );
constexpr int N = V_ARRAYSIZE( m_arDebugHistoryRecvSeqNum );
COMPILE_TIME_ASSERT( ( N & (N-1) ) == 0 );
int nMaxPkts = (int)std::min( (int64)std::min( 8, N ), m_nPktsRecvSequenced );
int64 idx = m_nPktsRecvSequenced;
const char *pszLeader = " | ";
while ( --nMaxPkts >= 0 && --idx >= 0 )
{
char buf2[32];
V_sprintf_safe( buf2, "%s%lld", pszLeader, (long long)m_arDebugHistoryRecvSeqNum[ idx & (N-1) ] );
result.append( buf2 );
pszLeader = ",";
}
return result;
}
void LinkStatsTrackerBase::InternalProcessSequencedPacket_OutOfOrder( int64 nPktNum )
{
if ( PktsRecvDropped() == 0 )
{
if ( m_nPktsRecvSequenced < 256 && m_nPeerProtocolVersion >= 9 )
{
AssertMsg( false,
"No dropped packets, pkt num %lld, dup bit not set? recvseq=%lld inorder=%lld, dup=%lld, lurch=%lld, ooo=%lld, %s. (%s)",
(long long)nPktNum, (long long)m_nPktsRecvSequenced,
(long long)m_nDebugPktsRecvInOrder, (long long)PktsRecvDuplicate(),
(long long)PktsRecvLurch(), (long long)PktsRecvOutOfOrder(),
RecvPktNumStateDebugString().c_str(),
Describe().c_str()
);
}
}
m_seqPktCounters.OnOutOfOrder();
}
void LinkStatsTrackerBase::ProcessSequencedPacket_OutOfOrderCorrected()
{
m_seqPktCounters.OnOutOfOrderCorrected();
}
bool LinkStatsTrackerBase::BCheckHaveDataToSendInstantaneous( SteamNetworkingMicroseconds usecNow )
{
Assert( m_eActivityLevel == ELinkActivityLevel::Active );
const int64 k_usecActiveConnectionSendInterval = 3*k_nMillion;
COMPILE_TIME_ASSERT( k_usecSteamDatagramClientPingTimeout*2 < k_usecActiveConnectionSendInterval );
COMPILE_TIME_ASSERT( k_usecSteamDatagramClientBackupRouterKeepaliveInterval > k_usecActiveConnectionSendInterval*5 );
int64 usecElapsed = usecNow - m_usecPeerAckedInstaneous;
Assert( usecElapsed >= k_usecLinkStatsInstantaneousReportInterval ); int nThreshold = usecElapsed / k_usecActiveConnectionSendInterval;
if ( m_nPktsRecvSeqWhenPeerAckInstantaneous + nThreshold < m_nPktsRecvSequenced || m_nPktsSentWhenPeerAckInstantaneous + nThreshold < m_sent.m_packets.Total() )
return true;
PeerAckedInstantaneous( usecNow );
return false;
}
bool LinkStatsTrackerBase::BCheckHaveDataToSendLifetime( SteamNetworkingMicroseconds usecNow )
{
Assert( m_eActivityLevel == ELinkActivityLevel::Active );
if ( m_nPktsRecvSeqWhenPeerAckLifetime + 100 < m_nPktsRecvSequenced || m_nPktsSentWhenPeerAckLifetime + 100 < m_sent.m_packets.Total() )
return true;
m_usecPeerAckedLifetime = usecNow;
return false;
}
int LinkStatsTrackerBase::GetStatsSendNeed( SteamNetworkingMicroseconds usecNow )
{
int nResult = 0;
if ( m_pktNumInFlight == 0 && m_eActivityLevel == ELinkActivityLevel::Active )
{
if ( m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportInterval <= usecNow && BCheckHaveDataToSendInstantaneous( usecNow ) )
{
if ( m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMaxInterval <= usecNow )
nResult |= k_nSendStats_Instantanous_Due;
else
nResult |= k_nSendStats_Instantanous_Ready;
}
if ( m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportInterval <= usecNow && BCheckHaveDataToSendLifetime( usecNow ) )
{
if ( m_usecPeerAckedInstaneous + k_usecLinkStatsLifetimeReportMaxInterval <= usecNow )
nResult |= k_nSendStats_Lifetime_Due;
else
nResult |= k_nSendStats_Lifetime_Ready;
}
}
return nResult;
}
const char *LinkStatsTrackerBase::InternalGetSendStatsReasonOrUpdateNextThinkTime( SteamNetworkingMicroseconds usecNow, const char *const arpszReasonStrings[4], SteamNetworkingMicroseconds &inOutNextThinkTime )
{
if ( m_usecInFlightReplyTimeout > 0 && m_usecInFlightReplyTimeout < inOutNextThinkTime )
inOutNextThinkTime = m_usecInFlightReplyTimeout;
if ( m_eActivityLevel != ELinkActivityLevel::Active )
{
Assert( m_eActivityLevel == ELinkActivityLevel::Idle ); return nullptr;
}
if ( m_pktNumInFlight )
return nullptr;
int n = 0;
if ( m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMaxInterval <= usecNow && BCheckHaveDataToSendInstantaneous( usecNow ) )
{
n |= 1;
}
else
{
SteamNetworkingMicroseconds usecNextCheck = m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMaxInterval;
if ( usecNextCheck < inOutNextThinkTime )
inOutNextThinkTime = usecNextCheck;
}
if ( m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportMaxInterval <= usecNow && BCheckHaveDataToSendLifetime( usecNow ) )
{
n |= 2;
}
else
{
SteamNetworkingMicroseconds usecNextCheck = m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportMaxInterval;
if ( usecNextCheck < inOutNextThinkTime )
inOutNextThinkTime = usecNextCheck;
}
return arpszReasonStrings[n];
}
void LinkStatsTrackerBase::PopulateMessage( int nNeedFlags, CMsgSteamDatagramConnectionQuality &msg, SteamNetworkingMicroseconds usecNow )
{
Assert( m_pktNumInFlight == 0 && m_eActivityLevel == ELinkActivityLevel::Active );
if ( nNeedFlags & k_nSendStats_Instantanous )
{
SteamDatagramLinkInstantaneousStats sInstant;
GetInstantaneousStats( sInstant );
LinkStatsInstantaneousStructToMsg( sInstant, *msg.mutable_instantaneous() );
}
if ( nNeedFlags & k_nSendStats_Lifetime )
{
PopulateLifetimeMessage( *msg.mutable_lifetime() );
}
}
void LinkStatsTrackerBase::PopulateLifetimeMessage( CMsgSteamDatagramLinkLifetimeStats &msg )
{
SteamDatagramLinkLifetimeStats sLifetime;
GetLifetimeStats( sLifetime );
LinkStatsLifetimeStructToMsg( sLifetime, msg );
}
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::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.Total();
s.m_nBytesSent = m_sent.m_bytes.Total();
s.m_nPacketsRecv = m_recv.m_packets.Total();
s.m_nBytesRecv = m_recv.m_bytes.Total();
s.m_nPktsRecvSequenced = m_nPktsRecvSequenced;
s.m_nPktsRecvDropped = PktsRecvDropped();
s.m_nPktsRecvOutOfOrder = PktsRecvOutOfOrder();
s.m_nPktsRecvOutOfOrderCorrected = PktsRecvOutOfOrderCorrected();
s.m_nPktsRecvDuplicate = PktsRecvDuplicate();
s.m_nPktsRecvSequenceNumberLurch = PktsRecvLurch();
s.m_bMultiPathSendEnabled = m_bMultiPathSendEnabled;
s.m_nMultiPathRecvSeq[0] = m_nMultiPathRecvSeq[0]; s.m_nMultiPathRecvSeq[1] = m_nMultiPathRecvSeq[1];
s.m_nMultiPathRecvLater[0] = m_nMultiPathRecvLater[0]; s.m_nMultiPathRecvLater[1] = m_nMultiPathRecvLater[1];
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;
}
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_usecWhenStartedConnectedState = 0;
m_usecWhenEndedConnectedState = 0;
m_usecAPIRealtimeStatusMaxJitter = -1;
StartNextSpeedInterval( usecNow );
}
void LinkStatsTrackerEndToEnd::StartNextSpeedInterval( SteamNetworkingMicroseconds usecNow )
{
m_usecSpeedIntervalStart = usecNow;
}
void LinkStatsTrackerEndToEnd::UpdateSpeedInterval( SteamNetworkingMicroseconds usecNow )
{
StartNextSpeedInterval( usecNow );
}
void LinkStatsTrackerEndToEnd::UpdateSpeeds( int nTXSpeed, int nRXSpeed )
{
}
void LinkStatsTrackerEndToEnd::GetLifetimeStats( SteamDatagramLinkLifetimeStats &s ) const
{
LinkStatsTrackerBase::GetLifetimeStats(s);
if ( m_usecWhenStartedConnectedState == 0 || m_usecWhenStartedConnectedState == m_usecWhenEndedConnectedState )
{
s.m_nConnectedSeconds = 0;
}
else
{
SteamNetworkingMicroseconds usecWhenEnded = m_usecWhenEndedConnectedState ? m_usecWhenEndedConnectedState : SteamNetworkingSockets_GetLocalTimestamp();
s.m_nConnectedSeconds = std::max( k_nMillion, usecWhenEnded - m_usecWhenStartedConnectedState + 500000 ) / k_nMillion;
}
}
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 )
{
if ( s.m_nConnectedSeconds >= 0 )
msg.set_connected_seconds( s.m_nConnectedSeconds );
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_out_of_order_corrected( s.m_nPktsRecvOutOfOrderCorrected );
msg.set_packets_recv_duplicate( s.m_nPktsRecvDuplicate );
msg.set_packets_recv_lurch( s.m_nPktsRecvSequenceNumberLurch );
if ( s.m_bMultiPathSendEnabled )
msg.set_multipath_send_enabled( 1 );
if ( s.m_bMultiPathSendEnabled || s.m_nMultiPathRecvSeq[1] > 0 )
{
msg.add_multipath_packets_recv_sequenced( s.m_nMultiPathRecvSeq[0] );
msg.add_multipath_packets_recv_sequenced( s.m_nMultiPathRecvSeq[1] );
msg.add_multipath_packets_recv_later( s.m_nMultiPathRecvLater[0] );
msg.add_multipath_packets_recv_later( s.m_nMultiPathRecvLater[1] );
}
#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_arBuckets[ QualityHistogram::k_nBucket_100 ], quality_histogram_100 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_99 ], quality_histogram_99 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_97 ], quality_histogram_97 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_95 ], quality_histogram_95 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_90 ], quality_histogram_90 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_75 ], quality_histogram_75 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_50 ], quality_histogram_50 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_1 ], quality_histogram_1 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_Dead ], 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 )
#undef SET_HISTOGRAM
#undef SET_NTILE
}
void LinkStatsLifetimeMsgToStruct( const CMsgSteamDatagramLinkLifetimeStats &msg, SteamDatagramLinkLifetimeStats &s )
{
s.m_nConnectedSeconds = msg.has_connected_seconds() ? msg.connected_seconds() : -1;
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_nPktsRecvOutOfOrderCorrected = msg.packets_recv_out_of_order_corrected();
s.m_nPktsRecvDuplicate = msg.packets_recv_duplicate();
s.m_nPktsRecvSequenceNumberLurch = msg.packets_recv_lurch();
s.m_bMultiPathSendEnabled = msg.multipath_send_enabled() > 0;
s.m_nMultiPathRecvSeq[0] = ( msg.multipath_packets_recv_sequenced_size() > 0 ) ? msg.multipath_packets_recv_sequenced(0) : 0;
s.m_nMultiPathRecvSeq[1] = ( msg.multipath_packets_recv_sequenced_size() > 1 ) ? msg.multipath_packets_recv_sequenced(1) : 0;
s.m_nMultiPathRecvLater[0] = ( msg.multipath_packets_recv_later_size() > 0 ) ? msg.multipath_packets_recv_later(0) : 0;
s.m_nMultiPathRecvLater[1] = ( msg.multipath_packets_recv_later_size() > 1 ) ? msg.multipath_packets_recv_later(1) : 0;
#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_arBuckets[ QualityHistogram::k_nBucket_100 ], quality_histogram_100 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_99 ], quality_histogram_99 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_97 ], quality_histogram_97 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_95 ], quality_histogram_95 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_90 ], quality_histogram_90 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_75 ], quality_histogram_75 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_50 ], quality_histogram_50 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_1 ], quality_histogram_1 )
SET_HISTOGRAM( s.m_qualityHistogram.m_arBuckets[ QualityHistogram::k_nBucket_Dead ], 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 )
#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 )
{
char temp1[256];
char temp2[256];
char num[64];
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 OOOFixed :%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvOutOfOrderCorrected ).String(), stats.m_nPktsRecvOutOfOrderCorrected * 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 );
if ( stats.m_nMultiPathRecvSeq[1] > 0 )
{
AssertMsg( stats.m_nMultiPathRecvSeq[0] + stats.m_nMultiPathRecvSeq[1] == stats.m_nPktsRecvSequenced, "multipath seq bookkeeping error %lld + %lld != %lld",
(long long)stats.m_nMultiPathRecvSeq[0], (long long)stats.m_nMultiPathRecvSeq[1], (long long)stats.m_nPktsRecvSequenced );
buf.Printf( "%s Pth0 w seq:%11s pkts\n", pszLeader, NumberPrettyPrinter( stats.m_nMultiPathRecvSeq[0] ).String() );
buf.Printf( "%s Pth1 w seq:%11s pkts\n", pszLeader, NumberPrettyPrinter( stats.m_nMultiPathRecvSeq[1] ).String() );
buf.Printf( "%s Pth0 later:%11s pkts\n", pszLeader, NumberPrettyPrinter( stats.m_nMultiPathRecvLater[0] ).String() );
buf.Printf( "%s Pth1 later:%11s pkts\n", pszLeader, NumberPrettyPrinter( stats.m_nMultiPathRecvLater[1] ).String() );
}
}
{
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 25-50 50-75 75-100 100-125 125-150 150-200 200-300 300+\n", pszLeader );
buf.Printf( "%s %9d%9d%9d%9d%9d%9d%9d%9d%9d\n",
pszLeader,
stats.m_pingHistogram.m_n25,
stats.m_pingHistogram.m_n50,
stats.m_pingHistogram.m_n75,
stats.m_pingHistogram.m_n100,
stats.m_pingHistogram.m_n125,
stats.m_pingHistogram.m_n150,
stats.m_pingHistogram.m_n200,
stats.m_pingHistogram.m_n300,
stats.m_pingHistogram.m_nMax );
buf.Printf( "%s %8.1f%%%8.1f%%%8.1f%%%8.1f%%%8.1f%%%8.1f%%%8.1f%%%8.1f%%%8.1f%%\n",
pszLeader,
stats.m_pingHistogram.m_n25 *flToPct,
stats.m_pingHistogram.m_n50 *flToPct,
stats.m_pingHistogram.m_n75 *flToPct,
stats.m_pingHistogram.m_n100*flToPct,
stats.m_pingHistogram.m_n125*flToPct,
stats.m_pingHistogram.m_n150*flToPct,
stats.m_pingHistogram.m_n200*flToPct,
stats.m_pingHistogram.m_n300*flToPct,
stats.m_pingHistogram.m_nMax*flToPct );
temp1[0] = '\0';
temp2[0] = '\0';
#define PING_NTILE( ntile, val ) \
if ( val >= 0 ) \
{ \
V_sprintf_safe( num, "%7s", ntile ); V_strcat_safe( temp1, num ); \
V_sprintf_safe( num, "%5dms", val ); V_strcat_safe( temp2, num ); \
}
PING_NTILE( "5th", stats.m_nPingNtile5th )
PING_NTILE( "50th", stats.m_nPingNtile50th );
PING_NTILE( "75th", stats.m_nPingNtile75th );
PING_NTILE( "95th", stats.m_nPingNtile95th );
PING_NTILE( "98th", stats.m_nPingNtile98th );
#undef PING_NTILE
if ( temp1[0] != '\0' )
{
buf.Printf( "%sPing distribution:\n", pszLeader );
buf.Printf( "%s%s\n", pszLeader, temp1 );
buf.Printf( "%s%s\n", pszLeader, temp2 );
}
}
else
{
buf.Printf( "%sNo ping distribution available. (%d samples)\n", pszLeader, nPingSamples );
}
}
{
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 perfect 99+ 97-99 95-97 90-95 75-90 50-75 <50 dead\n", pszLeader );
buf.Printf( "%s %7d%7d%7d%7d%7d%7d%7d%7d%7d\n",
pszLeader,
stats.m_qualityHistogram.N100(),
stats.m_qualityHistogram.N99(),
stats.m_qualityHistogram.N97(),
stats.m_qualityHistogram.N95(),
stats.m_qualityHistogram.N90(),
stats.m_qualityHistogram.N75(),
stats.m_qualityHistogram.N50(),
stats.m_qualityHistogram.N1(),
stats.m_qualityHistogram.NDead()
);
buf.Printf( "%s %6.1f%%%6.1f%%%6.1f%%%6.1f%%%6.1f%%%6.1f%%%6.1f%%%6.1f%%%6.1f%%\n",
pszLeader,
stats.m_qualityHistogram.N100 () *flToPct,
stats.m_qualityHistogram.N99 ()*flToPct,
stats.m_qualityHistogram.N97 ()*flToPct,
stats.m_qualityHistogram.N95 ()*flToPct,
stats.m_qualityHistogram.N90 ()*flToPct,
stats.m_qualityHistogram.N75 ()*flToPct,
stats.m_qualityHistogram.N50 ()*flToPct,
stats.m_qualityHistogram.N1 ()*flToPct,
stats.m_qualityHistogram.NDead()*flToPct
);
temp1[0] = '\0';
temp2[0] = '\0';
#define QUALITY_NTILE( ntile, val ) \
if ( val >= 0 ) \
{ \
V_sprintf_safe( num, "%6s", ntile ); V_strcat_safe( temp1, num ); \
V_sprintf_safe( num, "%5d%%", val ); V_strcat_safe( temp2, num ); \
}
QUALITY_NTILE( "50th", stats.m_nQualityNtile50th );
QUALITY_NTILE( "25th", stats.m_nQualityNtile25th );
QUALITY_NTILE( "5th", stats.m_nQualityNtile5th );
QUALITY_NTILE( "2nd", stats.m_nQualityNtile2nd );
#undef QUALITY_NTILE
if ( temp1[0] != '\0' )
{
buf.Printf( "%sConnection quality distribution:\n", pszLeader );
buf.Printf( "%s%s\n", pszLeader, temp1 );
buf.Printf( "%s%s\n", pszLeader, temp2 );
}
}
else
{
buf.Printf( "%sNo connection quality distribution available. (%d measurement intervals)\n", pszLeader, nQualitySamples );
}
}
{
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 1-2 2-5 5-10 10-20 >20\n", pszLeader );
buf.Printf( "%s %8d%8d%8d%8d%8d%8d\n", pszLeader,
stats.m_jitterHistogram.m_nNegligible,
stats.m_jitterHistogram.m_n1 ,
stats.m_jitterHistogram.m_n2 ,
stats.m_jitterHistogram.m_n5 ,
stats.m_jitterHistogram.m_n10,
stats.m_jitterHistogram.m_n20 );
buf.Printf( "%s %7.1f%%%7.1f%%%7.1f%%%7.1f%%%7.1f%%%7.1f%%\n", pszLeader,
stats.m_jitterHistogram.m_nNegligible*flToPct,
stats.m_jitterHistogram.m_n1 *flToPct,
stats.m_jitterHistogram.m_n2 *flToPct,
stats.m_jitterHistogram.m_n5 *flToPct,
stats.m_jitterHistogram.m_n10*flToPct,
stats.m_jitterHistogram.m_n20*flToPct );
}
else
{
buf.Printf( "%sLatency variance histogram not available\n", pszLeader );
}
}
}
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 );
}
}
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 ( 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." );
}
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 ( m_statsEndToEnd.m_lifetime.m_nPacketsSent > 0 )
{
LinkStatsPrintToBuf( " ", m_statsEndToEnd, buf );
}
if ( m_szPrimaryRouterName[0] != '\0' )
{
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_szBackupRouterName[0] != '\0' )
{
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;
}
}