#ifndef STEAMNETWORKING_STATSUTILS_H
#define STEAMNETWORKING_STATSUTILS_H
#pragma once
#include <tier0/basetypes.h>
#include <tier0/t0constants.h>
#include "percentile_generator.h"
#include "steamnetworking_stats.h"
#include "steamnetworkingsockets_internal.h"
#include "steamnetworkingsockets_thinker.h"
#include <tier0/memdbgoff.h>
#include <steamnetworkingsockets_messages.pb.h>
#include <tier0/memdbgon.h>
class CMsgSteamDatagramConnectionQuality;
namespace SteamNetworkingSocketsLib {
class CPossibleOutOfOrderPacket;
const SteamNetworkingMicroseconds k_usecSteamDatagramLinkStatsDefaultInterval = 5 * k_nMillion;
const SteamNetworkingMicroseconds k_usecSteamDatagramSpeedStatsDefaultInterval = 1 * k_nMillion;
const SteamNetworkingMicroseconds k_usecLinkStatsMinPingRequestInterval = 5 * k_nMillion;
const SteamNetworkingMicroseconds k_usecLinkStatsMaxPingRequestInterval = 7 * k_nMillion;
const SteamNetworkingMicroseconds k_usecLinkStatsInstantaneousReportInterval = 20 * k_nMillion;
const SteamNetworkingMicroseconds k_usecLinkStatsInstantaneousReportMaxInterval = 30 * k_nMillion;
const SteamNetworkingMicroseconds k_usecLinkStatsLifetimeReportInterval = 120 * k_nMillion;
const SteamNetworkingMicroseconds k_usecLinkStatsLifetimeReportMaxInterval = 140 * k_nMillion;
const SteamNetworkingMicroseconds k_usecAggressivePingInterval = 200*1000;
const SteamNetworkingMicroseconds k_usecKeepAliveIntervalActive = 10*k_nMillion;
const SteamNetworkingMicroseconds k_usecKeepAliveIntervalIdle = 60*k_nMillion;
extern void LinkStatsInstantaneousStructToMsg( const SteamDatagramLinkInstantaneousStats &s, CMsgSteamDatagramLinkInstantaneousStats &msg );
extern void LinkStatsInstantaneousMsgToStruct( const CMsgSteamDatagramLinkInstantaneousStats &msg, SteamDatagramLinkInstantaneousStats &s );
extern void LinkStatsLifetimeStructToMsg( const SteamDatagramLinkLifetimeStats &s, CMsgSteamDatagramLinkLifetimeStats &msg );
extern void LinkStatsLifetimeMsgToStruct( const CMsgSteamDatagramLinkLifetimeStats &msg, SteamDatagramLinkLifetimeStats &s );
struct Rate_t
{
void Reset() { memset( this, 0, sizeof(*this) ); }
int64 m_nCurrentInterval;
int64 m_nAccumulator; float m_flRate;
int64 Total() const { return m_nAccumulator + m_nCurrentInterval; }
inline void Process( int64 nIncrement )
{
m_nCurrentInterval += nIncrement;
}
inline void UpdateInterval( float flIntervalDuration )
{
m_flRate = float(m_nCurrentInterval) / flIntervalDuration;
m_nAccumulator += m_nCurrentInterval;
m_nCurrentInterval = 0;
}
inline void operator+=( const Rate_t &x )
{
m_nCurrentInterval += x.m_nCurrentInterval;
m_nAccumulator += x.m_nAccumulator;
m_flRate += x.m_flRate;
}
};
struct PacketRate_t
{
void Reset() { memset( this, 0, sizeof(*this) ); }
Rate_t m_packets;
Rate_t m_bytes;
inline void ProcessPacket( int sz )
{
m_packets.Process( 1 );
m_bytes.Process( sz );
}
void UpdateInterval( float flIntervalDuration )
{
m_packets.UpdateInterval( flIntervalDuration );
m_bytes.UpdateInterval( flIntervalDuration );
}
inline void operator+=( const PacketRate_t &x )
{
m_packets += x.m_packets;
m_bytes += x.m_bytes;
}
};
struct PingTracker
{
struct Ping
{
int m_nPingMS;
SteamNetworkingMicroseconds m_usecTimeRecv;
};
Ping m_arPing[ 3 ];
int m_nValidPings;
SteamNetworkingMicroseconds TimeRecvMostRecentPing() const { return m_arPing[0].m_usecTimeRecv; }
int WorstPingInRecentSample() const;
SteamNetworkingMicroseconds CalcConservativeTimeout() const
{
constexpr SteamNetworkingMicroseconds k_usecMaxTimeout = 1250000;
if ( m_nSmoothedPing < 0 )
return k_usecMaxTimeout;
return std::min( SteamNetworkingMicroseconds{ WorstPingInRecentSample()*2000 + 250000 }, k_usecMaxTimeout );
}
int m_nSmoothedPing;
SteamNetworkingMicroseconds m_usecTimeLastSentPingRequest;
static constexpr SteamNetworkingMicroseconds k_usecMinPingSampleSpacing = 100*1000;
protected:
void Reset();
SteamNetworkingMicroseconds m_usecTimeAllowNewSample;
void ReceivedPing( int nPingMS, SteamNetworkingMicroseconds usecNow );
};
struct PingTrackerDetailed : PingTracker
{
void Reset()
{
PingTracker::Reset();
m_sample.Clear();
m_histogram.Reset();
}
void ReceivedPing( int nPingMS, SteamNetworkingMicroseconds usecNow )
{
PingTracker::ReceivedPing( nPingMS, usecNow );
m_sample.AddSample( uint16( std::min( nPingMS, 0xffff ) ) );
m_histogram.AddSample( nPingMS );
}
CPercentileGenerator<uint16> m_sample;
PingHistogram m_histogram;
void GetLifetimeStats( SteamDatagramLinkLifetimeStats &s ) const
{
s.m_pingHistogram = m_histogram;
s.m_nPingNtile5th = m_sample.NumSamples() < 20 ? -1 : m_sample.GetPercentile( .05f );
s.m_nPingNtile50th = m_sample.NumSamples() < 2 ? -1 : m_sample.GetPercentile( .50f );
s.m_nPingNtile75th = m_sample.NumSamples() < 4 ? -1 : m_sample.GetPercentile( .75f );
s.m_nPingNtile95th = m_sample.NumSamples() < 20 ? -1 : m_sample.GetPercentile( .95f );
s.m_nPingNtile98th = m_sample.NumSamples() < 50 ? -1 : m_sample.GetPercentile( .98f );
}
};
template<int N, SteamNetworkingMicroseconds W>
struct PingTrackerBuckets : PingTracker
{
static constexpr int k_nTimeBucketCount = N;
static constexpr SteamNetworkingMicroseconds k_usecTimeBucketWidth = W; static constexpr int k_nPingOverride_None = -2;
struct TimeBucket
{
SteamNetworkingMicroseconds m_usecEnd; int m_nPingCount;
int m_nMinPing; int m_nMaxPing; };
TimeBucket m_arTimeBuckets[ k_nTimeBucketCount ];
int m_idxCurrentBucket;
int m_nTotalPingsReceived;
int m_nPingOverride = k_nPingOverride_None;
void Reset()
{
PingTracker::Reset();
m_nTotalPingsReceived = 0;
m_idxCurrentBucket = 0;
for ( TimeBucket &b: m_arTimeBuckets )
{
b.m_usecEnd = 0;
b.m_nPingCount = 0;
b.m_nMinPing = INT_MAX;
b.m_nMaxPing = INT_MIN;
}
}
void ReceivedPing( int nPingMS, SteamNetworkingMicroseconds usecNow )
{
if ( m_nPingOverride > k_nPingOverride_None )
{
if ( m_nPingOverride == -1 )
return;
nPingMS = m_nPingOverride;
}
PingTracker::ReceivedPing( nPingMS, usecNow );
++m_nTotalPingsReceived;
SteamNetworkingMicroseconds usecCurrentBucketEnd = m_arTimeBuckets[ m_idxCurrentBucket ].m_usecEnd;
if ( usecCurrentBucketEnd > usecNow )
{
TimeBucket &curBucket = m_arTimeBuckets[ m_idxCurrentBucket ];
++curBucket.m_nPingCount;
curBucket.m_nMinPing = std::min( curBucket.m_nMinPing, nPingMS );
curBucket.m_nMaxPing = std::max( curBucket.m_nMaxPing, nPingMS );
}
else
{
++m_idxCurrentBucket;
if ( m_idxCurrentBucket >= k_nTimeBucketCount )
m_idxCurrentBucket = 0;
TimeBucket &newBucket = m_arTimeBuckets[ m_idxCurrentBucket ];
if ( usecCurrentBucketEnd + (k_usecTimeBucketWidth/2) >= usecNow )
{
newBucket.m_usecEnd = usecCurrentBucketEnd + k_usecTimeBucketWidth;
}
else
{
newBucket.m_usecEnd = usecNow + k_usecTimeBucketWidth;
}
newBucket.m_nPingCount = 1;
newBucket.m_nMinPing = nPingMS;
newBucket.m_nMaxPing = nPingMS;
}
}
void SetPingOverride( int nPing )
{
m_nPingOverride = nPing;
if ( m_nPingOverride <= k_nPingOverride_None )
return;
if ( m_nPingOverride < 0 )
{
m_nValidPings = 0;
m_nSmoothedPing = -1;
return;
}
m_nSmoothedPing = nPing;
for ( int i = 0 ; i < m_nValidPings ; ++i )
m_arPing[i].m_nPingMS = nPing;
TimeBucket &curBucket = m_arTimeBuckets[ m_idxCurrentBucket ];
curBucket.m_nMinPing = nPing;
curBucket.m_nMaxPing = nPing;
}
int GetPingRangeFromRecentBuckets( int &nOutMin, int &nOutMax, SteamNetworkingMicroseconds usecNow ) const
{
int nMin = m_nSmoothedPing;
int nMax = m_nSmoothedPing;
int nBucketsValid = 0;
if ( m_nSmoothedPing >= 0 )
{
SteamNetworkingMicroseconds usecRecentEndThreshold = usecNow - ( (k_nTimeBucketCount-1) * k_usecTimeBucketWidth );
for ( const TimeBucket &bucket: m_arTimeBuckets )
{
if ( bucket.m_usecEnd >= usecRecentEndThreshold )
{
Assert( bucket.m_nPingCount > 0 );
Assert( 0 <= bucket.m_nMinPing );
Assert( bucket.m_nMinPing <= bucket.m_nMaxPing );
++nBucketsValid;
nMin = std::min( nMin, bucket.m_nMinPing );
nMax = std::max( nMax, bucket.m_nMaxPing );
}
}
}
nOutMin = nMin;
nOutMax = nMax;
return nBucketsValid;
}
};
const int k_nRecentValidTimeBucketsToSwitchRoute = 15;
struct PingTrackerForRouteSelection : PingTrackerBuckets<k_nRecentValidTimeBucketsToSwitchRoute+2,k_nMillion>
{
static constexpr SteamNetworkingMicroseconds k_usecAntiFlapRouteCheckPingInterval = 200*1000;
SteamNetworkingMicroseconds TimeToSendNextAntiFlapRouteCheckPingRequest() const
{
return std::min(
m_arTimeBuckets[ m_idxCurrentBucket ].m_usecEnd, m_usecTimeLastSentPingRequest + k_usecAntiFlapRouteCheckPingInterval );
}
};
struct TokenBucketRateLimiter
{
TokenBucketRateLimiter() { Reset(); }
void Reset() { m_usecLastTime = 0; m_flTokenDeficitFromFull = 0.0f; }
bool BCheck( SteamNetworkingMicroseconds usecNow, float flMaxSteadyStateRate, float flMaxBurst )
{
Assert( flMaxBurst >= 1.0f );
Assert( flMaxSteadyStateRate > 0.0f );
float flElapsed = ( usecNow - m_usecLastTime ) * 1e-6f;
m_usecLastTime = usecNow;
m_flTokenDeficitFromFull = Max( m_flTokenDeficitFromFull - flElapsed*flMaxSteadyStateRate, 0.0f );
if ( m_flTokenDeficitFromFull + 1.0f > flMaxBurst )
return false;
m_flTokenDeficitFromFull += 1.0f;
return true;
}
private:
SteamNetworkingMicroseconds m_usecLastTime;
float m_flTokenDeficitFromFull;
};
constexpr int k_nSendStats_Instantanous_Due = 1;
constexpr int k_nSendStats_Instantanous_Ready = 2;
constexpr int k_nSendStats_Lifetime_Due = 4;
constexpr int k_nSendStats_Lifetime_Ready = 8;
constexpr int k_nSendStats_Instantanous = k_nSendStats_Instantanous_Due|k_nSendStats_Instantanous_Ready;
constexpr int k_nSendStats_Lifetime = k_nSendStats_Lifetime_Due|k_nSendStats_Lifetime_Ready;
constexpr int k_nSendStats_Due = k_nSendStats_Instantanous_Due|k_nSendStats_Lifetime_Due;
constexpr int k_nSendStats_Ready = k_nSendStats_Instantanous_Ready|k_nSendStats_Lifetime_Ready;
struct SequencedPacketCounters
{
int m_nRecv; int m_nDropped; int m_nOutOfOrder; int m_nOutOfOrderCorrected; int m_nLurch; int m_nDuplicate; int m_usecMaxJitter;
void Reset()
{
m_nRecv = 0;
m_nDropped = 0;
m_nOutOfOrder = 0;
m_nOutOfOrderCorrected = 0;
m_nLurch = 0;
m_nDuplicate = 0;
m_usecMaxJitter = -1;
}
void Accumulate( const SequencedPacketCounters &x )
{
m_nRecv += x.m_nRecv;
m_nDropped += x.m_nDropped;
m_nOutOfOrder += m_nOutOfOrder;
m_nOutOfOrderCorrected += x.m_nOutOfOrderCorrected;
m_nLurch += m_nLurch;
m_nDuplicate += x.m_nDuplicate;
m_usecMaxJitter = std::max( m_usecMaxJitter, x.m_usecMaxJitter );
}
inline int Weird() const { return m_nOutOfOrder + m_nLurch + m_nDuplicate; }
static inline float CalculateQuality( int nRecv, int nDropped, int nWeird )
{
Assert( nRecv >= nWeird );
int nSent = nRecv + nDropped;
if ( nSent <= 0 )
return -1.0f;
return (float)(nRecv - nWeird) / (float)nSent;
}
inline float CalculateQuality() const
{
return CalculateQuality( m_nRecv, m_nDropped, Weird() );
}
inline void OnRecv()
{
++m_nRecv;
}
inline void OnDropped( int nDropped )
{
m_nDropped += nDropped;
}
inline void OnDuplicate()
{
++m_nDuplicate;
}
inline void OnLurch()
{
++m_nLurch;
}
inline void OnOutOfOrder()
{
++m_nOutOfOrder;
if ( m_nDropped > 0 ) --m_nDropped;
}
inline void OnOutOfOrderCorrected()
{
++m_nOutOfOrderCorrected;
}
};
enum class ELinkActivityLevel
{
Active, Idle, Disconnected };
struct LinkStatsTrackerBase
{
~LinkStatsTrackerBase();
uint32 m_nPeerProtocolVersion;
PingTrackerDetailed m_ping;
int64 m_nNextSendSequenceNumber;
PacketRate_t m_sent;
SteamNetworkingMicroseconds m_usecTimeLastSentSeq;
inline void TrackSentPacket( int cbPktSize )
{
m_sent.ProcessPacket( cbPktSize );
}
inline uint16 ConsumeSendPacketNumberAndGetWireFmt( SteamNetworkingMicroseconds usecNow )
{
m_usecTimeLastSentSeq = usecNow;
return uint16( m_nNextSendSequenceNumber++ );
}
int64 m_nMaxRecvPktNum;
PacketRate_t m_recv;
int64 m_nDebugLastInitMaxRecvPktNum;
int64 m_nDebugPktsRecvInOrder;
int64 m_arDebugHistoryRecvSeqNum[ 8 ];
void InitMaxRecvPktNum( int64 nPktNum );
void ResetMaxRecvPktNumForIncomingWirePktNum( uint16 nPktNum )
{
InitMaxRecvPktNum( (int64)(uint16)( nPktNum - 1 ) );
}
uint64 m_recvPktNumberMask[2];
std::string RecvPktNumStateDebugString() const;
PacketRate_t m_recvExceedRateLimit;
SteamNetworkingMicroseconds m_usecTimeLastRecv;
SteamNetworkingMicroseconds m_usecTimeLastRecvSeq;
uint64 m_recvPktNumberMaskMultiPath[2][2]; int64 m_nMultiPathRecvLater[2];
int64 m_nMultiPathRecvSeq[2];
bool m_bMultiPathSendEnabled;
SequencedPacketCounters m_seqPktCounters;
float m_flInPacketsDroppedPct;
float m_flInPacketsWeirdSequencePct;
int m_usecMaxJitterPreviousInterval;
int64 m_nPktsRecvSequenced;
int64 m_nPktsRecvDroppedAccumulator;
int64 m_nPktsRecvOutOfOrderAccumulator;
int64 m_nPktsRecvOutOfOrderCorrectedAccumulator;
int64 m_nPktsRecvDuplicateAccumulator;
int64 m_nPktsRecvLurchAccumulator;
inline int64 PktsRecvDropped() const { return m_nPktsRecvDroppedAccumulator + m_seqPktCounters.m_nDropped; }
inline int64 PktsRecvOutOfOrder() const { return m_nPktsRecvOutOfOrderAccumulator + m_seqPktCounters.m_nOutOfOrder; }
inline int64 PktsRecvOutOfOrderCorrected() const { return m_nPktsRecvOutOfOrderCorrectedAccumulator + m_seqPktCounters.m_nOutOfOrderCorrected; }
inline int64 PktsRecvDuplicate() const { return m_nPktsRecvDuplicateAccumulator + m_seqPktCounters.m_nDuplicate; }
inline int64 PktsRecvLurch() const { return m_nPktsRecvLurchAccumulator + m_seqPktCounters.m_nLurch; }
CPercentileGenerator<uint8> m_qualitySample;
QualityHistogram m_qualityHistogram;
JitterHistogram m_jitterHistogram;
inline int ReadyToSendTracerPing( SteamNetworkingMicroseconds usecNow ) const
{
if ( m_eActivityLevel != ELinkActivityLevel::Active ) return 0;
SteamNetworkingMicroseconds usecTimeSince = usecNow - std::max( m_ping.m_usecTimeLastSentPingRequest, m_ping.TimeRecvMostRecentPing() );
if ( usecTimeSince > k_usecLinkStatsMaxPingRequestInterval )
return 2;
if ( usecTimeSince > k_usecLinkStatsMinPingRequestInterval )
return 1;
return 0;
}
inline bool BNeedToSendPingImmediate( SteamNetworkingMicroseconds usecNow ) const
{
return
unlikely( m_nReplyTimeoutsSinceLastRecv > 0 ) && m_eActivityLevel == ELinkActivityLevel::Active && m_usecLastSendPacketExpectingImmediateReply+k_usecAggressivePingInterval <= usecNow; }
inline bool BNeedToSendKeepalive( SteamNetworkingMicroseconds usecNow ) const
{
if ( likely( m_usecTimeLastRecv + k_usecKeepAliveIntervalActive > usecNow ) )
return false;
if ( m_usecInFlightReplyTimeout == 0 )
return false; if ( likely( m_eActivityLevel == ELinkActivityLevel::Active ) )
return true;
if ( likely( m_eActivityLevel == ELinkActivityLevel::Idle ) )
{
return m_usecTimeLastRecv + k_usecKeepAliveIntervalIdle <= usecNow; }
Assert( m_eActivityLevel == ELinkActivityLevel::Disconnected );
return false;
}
void PopulateMessage( int nNeedFlags, CMsgSteamDatagramConnectionQuality &msg, SteamNetworkingMicroseconds usecNow );
void PopulateLifetimeMessage( CMsgSteamDatagramLinkLifetimeStats &msg );
void TrackSentMessageExpectingReply( SteamNetworkingMicroseconds usecNow, bool bAllowDelayedReply );
SteamDatagramLinkInstantaneousStats m_latestRemote;
SteamNetworkingMicroseconds m_usecTimeRecvLatestRemote;
SteamDatagramLinkLifetimeStats m_lifetimeRemote;
SteamNetworkingMicroseconds m_usecTimeRecvLifetimeRemote;
int64 m_pktNumInFlight;
bool m_bInFlightInstantaneous;
bool m_bInFlightLifetime;
SteamNetworkingMicroseconds m_usecIntervalStart;
SteamNetworkingMicroseconds m_usecInFlightReplyTimeout;
SteamNetworkingMicroseconds m_usecLastSendPacketExpectingImmediateReply;
int m_nReplyTimeoutsSinceLastRecv;
SteamNetworkingMicroseconds m_usecWhenTimeoutStarted;
void GetLinkStats( SteamDatagramLinkStats &s, SteamNetworkingMicroseconds usecNow ) const;
virtual void GetLifetimeStats( SteamDatagramLinkLifetimeStats &s ) const;
inline void PeerAckedInstantaneous( SteamNetworkingMicroseconds usecNow )
{
m_usecPeerAckedInstaneous = usecNow;
m_nPktsRecvSeqWhenPeerAckInstantaneous = m_nPktsRecvSequenced;
m_nPktsSentWhenPeerAckInstantaneous = m_sent.m_packets.Total();
}
inline void PeerAckedLifetime( SteamNetworkingMicroseconds usecNow )
{
m_usecPeerAckedLifetime = usecNow;
m_nPktsRecvSeqWhenPeerAckLifetime = m_nPktsRecvSequenced;
m_nPktsSentWhenPeerAckLifetime = m_sent.m_packets.Total();
}
void InFlightPktAck( SteamNetworkingMicroseconds usecNow )
{
if ( m_bInFlightInstantaneous )
PeerAckedInstantaneous( usecNow );
if ( m_bInFlightLifetime )
PeerAckedLifetime( usecNow );
m_pktNumInFlight = 0;
m_bInFlightInstantaneous = m_bInFlightLifetime = false;
}
void InFlightPktTimeout()
{
m_pktNumInFlight = 0;
m_bInFlightInstantaneous = m_bInFlightLifetime = false;
}
inline void RecvInstantaneousStats( const CMsgSteamDatagramLinkInstantaneousStats &msg, SteamNetworkingMicroseconds usecNow )
{
LinkStatsInstantaneousMsgToStruct( msg, m_latestRemote );
m_usecTimeRecvLatestRemote = usecNow;
}
inline void RecvLifetimeStats( const CMsgSteamDatagramLinkLifetimeStats &msg, SteamNetworkingMicroseconds usecNow )
{
LinkStatsLifetimeMsgToStruct( msg, m_lifetimeRemote );
m_usecTimeRecvLifetimeRemote = usecNow;
}
int GetStatsSendNeed( SteamNetworkingMicroseconds usecNow );
virtual std::string Describe() const = 0;
#ifdef IS_STEAMDATAGRAMROUTER
virtual
#else
inline
#endif
void AddQualityHistogramSample( QualityHistogram::EBucket eBucket )
{
++m_qualityHistogram.m_arBuckets[ eBucket ];
}
CPossibleOutOfOrderPacket *GetPossibleOutOfOrderPacket() const { return m_pPossibleOutOfOrderPacket; }
virtual void ProcessSequencedPacket_OutOfOrderCorrected();
protected:
inline LinkStatsTrackerBase() {}
void InitInternal( SteamNetworkingMicroseconds usecNow );
template <typename TLinkStatsTracker>
inline static void ThinkInternal( TLinkStatsTracker *pThis, SteamNetworkingMicroseconds usecNow )
{
if ( pThis->m_eActivityLevel == ELinkActivityLevel::Active && pThis->m_usecIntervalStart + k_usecSteamDatagramLinkStatsDefaultInterval <= usecNow )
{
pThis->UpdateInterval( usecNow );
}
if ( pThis->m_usecInFlightReplyTimeout > 0 && pThis->m_usecInFlightReplyTimeout <= usecNow )
{
pThis->InFlightReplyTimeout( usecNow );
}
}
template <typename TLinkStatsTracker>
inline static void InFlightReplyTimeoutInternal( TLinkStatsTracker *pThis, SteamNetworkingMicroseconds usecNow )
{
pThis->m_usecInFlightReplyTimeout = 0;
if ( pThis->m_usecWhenTimeoutStarted == 0 )
{
Assert( pThis->m_nReplyTimeoutsSinceLastRecv == 0 );
pThis->m_usecWhenTimeoutStarted = usecNow;
}
++pThis->m_nReplyTimeoutsSinceLastRecv;
}
void GetInstantaneousStats( SteamDatagramLinkInstantaneousStats &s ) const;
template <typename TLinkStatsTracker>
inline static void TrackSentMessageExpectingSeqNumAckInternal( TLinkStatsTracker *pThis, SteamNetworkingMicroseconds usecNow, bool bAllowDelayedReply )
{
pThis->TrackSentPingRequest( usecNow, bAllowDelayedReply );
}
ELinkActivityLevel m_eActivityLevel;
void SetActivityLevelInternal( ELinkActivityLevel eActivityLevel, SteamNetworkingMicroseconds usecNow );
const char *InternalGetSendStatsReasonOrUpdateNextThinkTime( SteamNetworkingMicroseconds usecNow, const char *const arpszReasonStrings[4], SteamNetworkingMicroseconds &inOutNextThinkTime );
template <typename TLinkStatsTracker>
inline static void TrackSentPingRequestInternal( TLinkStatsTracker *pThis, SteamNetworkingMicroseconds usecNow, bool bAllowDelayedReply )
{
pThis->TrackSentMessageExpectingReply( usecNow, bAllowDelayedReply );
pThis->m_ping.m_usecTimeLastSentPingRequest = usecNow;
}
template <typename TLinkStatsTracker>
inline static void ReceivedPingInternal( TLinkStatsTracker *pThis, int nPingMS, SteamNetworkingMicroseconds usecNow )
{
pThis->m_ping.ReceivedPing( nPingMS, usecNow );
}
template <typename TLinkStatsTracker>
inline static void TrackRecvPacketInternal( TLinkStatsTracker *pThis, int cbPktSize, SteamNetworkingMicroseconds usecNow )
{
pThis->m_recv.ProcessPacket( cbPktSize );
pThis->m_usecTimeLastRecv = usecNow;
pThis->m_usecInFlightReplyTimeout = 0;
pThis->m_nReplyTimeoutsSinceLastRecv = 0;
pThis->m_usecWhenTimeoutStarted = 0;
}
inline bool BInternalNeedToSendPingImmediate( SteamNetworkingMicroseconds usecNow, SteamNetworkingMicroseconds &inOutNextThinkTime )
{
if ( likely( m_nReplyTimeoutsSinceLastRecv == 0 ) )
return false;
if ( m_eActivityLevel != ELinkActivityLevel::Active ) return false;
SteamNetworkingMicroseconds usecUrgentPing = m_usecLastSendPacketExpectingImmediateReply+k_usecAggressivePingInterval;
if ( usecUrgentPing <= usecNow )
return true;
if ( usecUrgentPing < inOutNextThinkTime )
inOutNextThinkTime = usecUrgentPing;
return false;
}
inline bool BInternalNeedToSendKeepAlive( SteamNetworkingMicroseconds usecNow, SteamNetworkingMicroseconds &inOutNextThinkTime )
{
if ( m_usecInFlightReplyTimeout == 0 )
{
SteamNetworkingMicroseconds usecWhenNextKeepAlive = m_usecTimeLastRecv;
if ( likely( m_eActivityLevel == ELinkActivityLevel::Active ) )
{
usecWhenNextKeepAlive += k_usecKeepAliveIntervalActive;
}
else
{
Assert( m_eActivityLevel == ELinkActivityLevel::Idle );
usecWhenNextKeepAlive += k_usecKeepAliveIntervalIdle;
}
if ( usecWhenNextKeepAlive <= usecNow )
return true;
if ( usecWhenNextKeepAlive < inOutNextThinkTime )
inOutNextThinkTime = usecWhenNextKeepAlive;
}
else
{
if ( m_usecInFlightReplyTimeout < inOutNextThinkTime )
inOutNextThinkTime = m_usecInFlightReplyTimeout;
}
return false;
}
inline void InternalProcessSequencedPacket_Count( int idxMultiPath )
{
m_seqPktCounters.OnRecv();
++m_nPktsRecvSequenced;
++m_nMultiPathRecvSeq[ idxMultiPath ];
}
void InternalProcessSequencedPacket_OutOfOrder( int64 nPktNum );
inline void InternalProcessSequencedPacket_Duplicate()
{
m_seqPktCounters.OnDuplicate();
}
inline void InternalProcessSequencedPacket_Lurch()
{
m_seqPktCounters.OnLurch();
}
inline void InternalProcessSequencedPacket_Dropped( int nDropped )
{
m_seqPktCounters.OnDropped( nDropped );
}
inline void InternalProcessJitterSample( int usecJitter )
{
usecJitter = abs( usecJitter );
m_seqPktCounters.m_usecMaxJitter = std::max( m_seqPktCounters.m_usecMaxJitter, usecJitter );
m_jitterHistogram.AddSample( usecJitter );
}
template <typename TLinkStatsTracker>
inline static void InternalProcessMessage( TLinkStatsTracker *pThis, const CMsgSteamDatagramConnectionQuality &msg, SteamNetworkingMicroseconds usecNow )
{
if ( msg.has_instantaneous() )
pThis->RecvInstantaneousStats( msg.instantaneous(), usecNow );
if ( msg.has_lifetime() )
pThis->RecvLifetimeStats( msg.lifetime(), usecNow );
}
private:
friend class CPossibleOutOfOrderPacket;
int64 m_nPktsRecvSeqWhenPeerAckInstantaneous;
int64 m_nPktsSentWhenPeerAckInstantaneous;
int64 m_nPktsRecvSeqWhenPeerAckLifetime;
int64 m_nPktsSentWhenPeerAckLifetime;
SteamNetworkingMicroseconds m_usecPeerAckedLifetime;
SteamNetworkingMicroseconds m_usecPeerAckedInstaneous;
bool BCheckHaveDataToSendInstantaneous( SteamNetworkingMicroseconds usecNow );
bool BCheckHaveDataToSendLifetime( SteamNetworkingMicroseconds usecNow );
void UpdateInterval( SteamNetworkingMicroseconds usecNow );
void StartNextInterval( SteamNetworkingMicroseconds usecNow );
CPossibleOutOfOrderPacket *m_pPossibleOutOfOrderPacket = nullptr;
};
struct LinkStatsTrackerEndToEnd : public LinkStatsTrackerBase
{
virtual void GetLifetimeStats( SteamDatagramLinkLifetimeStats &s ) const OVERRIDE;
SteamNetworkingMicroseconds CalcSenderRetryTimeout() const
{
if ( m_ping.m_nSmoothedPing < 0 )
return k_nMillion;
return m_ping.m_nSmoothedPing*3000 + ( k_usecMaxDataAckDelay + 10000 );
}
SteamNetworkingMicroseconds m_usecWhenStartedConnectedState;
SteamNetworkingMicroseconds m_usecWhenEndedConnectedState;
SteamNetworkingMicroseconds m_usecSpeedIntervalStart;
int m_usecAPIRealtimeStatusMaxJitter;
void UpdateSpeeds( int nTXSpeed, int nRXSpeed );
inline const char *GetSendReasonOrUpdateNextThinkTime( SteamNetworkingMicroseconds usecNow, EStatsReplyRequest &eReplyRequested, SteamNetworkingMicroseconds &inOutNextThinkTime )
{
if ( m_eActivityLevel == ELinkActivityLevel::Disconnected )
{
eReplyRequested = k_EStatsReplyRequest_NothingToSend;
return nullptr;
}
if ( m_usecInFlightReplyTimeout > 0 && m_usecInFlightReplyTimeout < inOutNextThinkTime )
inOutNextThinkTime = m_usecInFlightReplyTimeout;
if ( BInternalNeedToSendPingImmediate( usecNow, inOutNextThinkTime ) )
{
eReplyRequested = k_EStatsReplyRequest_Immediate;
return "E2EUrgentPing";
}
if ( BInternalNeedToSendKeepAlive( usecNow, inOutNextThinkTime ) )
{
eReplyRequested = k_EStatsReplyRequest_DelayedOK;
return "E2EKeepAlive";
}
static const char *arpszReasons[4] =
{
nullptr,
"E2EInstantaneousStats",
"E2ELifetimeStats",
"E2EAllStats"
};
const char *pszReason = LinkStatsTrackerBase::InternalGetSendStatsReasonOrUpdateNextThinkTime( usecNow, arpszReasons, inOutNextThinkTime );
if ( pszReason )
{
eReplyRequested = k_EStatsReplyRequest_DelayedOK;
return pszReason;
}
eReplyRequested = k_EStatsReplyRequest_NothingToSend;
return nullptr;
}
virtual std::string Describe() const override { return "EndToEnd"; }
protected:
void InitInternal( SteamNetworkingMicroseconds usecNow );
template <typename TLinkStatsTracker>
inline static void ThinkInternal( TLinkStatsTracker *pThis, SteamNetworkingMicroseconds usecNow )
{
LinkStatsTrackerBase::ThinkInternal( pThis, usecNow );
if ( pThis->m_usecSpeedIntervalStart + k_usecSteamDatagramSpeedStatsDefaultInterval <= usecNow )
{
pThis->UpdateSpeedInterval( usecNow );
}
}
inline void InternalProcessJitterSample( int usecJitter )
{
LinkStatsTrackerBase::InternalProcessJitterSample( usecJitter );
m_usecAPIRealtimeStatusMaxJitter = std::max( m_usecAPIRealtimeStatusMaxJitter, abs( usecJitter ) );
}
private:
void UpdateSpeedInterval( SteamNetworkingMicroseconds usecNow );
void StartNextSpeedInterval( SteamNetworkingMicroseconds usecNow );
};
template <typename TLinkStatsTracker>
struct LinkStatsTracker final : public TLinkStatsTracker
{
inline void Init( SteamNetworkingMicroseconds usecNow, ELinkActivityLevel eLinkActivityLevel )
{
TLinkStatsTracker::InitInternal( usecNow );
TLinkStatsTracker::SetActivityLevelInternal( eLinkActivityLevel, usecNow );
}
inline void Think( SteamNetworkingMicroseconds usecNow ) { if ( likely( TLinkStatsTracker::m_eActivityLevel != ELinkActivityLevel::Disconnected ) ) TLinkStatsTracker::ThinkInternal( this, usecNow ); }
inline void SetActivityLevel( ELinkActivityLevel eActivityLevel, SteamNetworkingMicroseconds usecNow ) { if ( TLinkStatsTracker::m_eActivityLevel != eActivityLevel ) TLinkStatsTracker::SetActivityLevelInternal( eActivityLevel, usecNow ); }
inline bool IsActive() const { return TLinkStatsTracker::m_eActivityLevel == ELinkActivityLevel::Active; }
inline bool IsDisconnected() const { return TLinkStatsTracker::m_eActivityLevel == ELinkActivityLevel::Disconnected; }
inline ELinkActivityLevel GetActivityLevel() const { return TLinkStatsTracker::m_eActivityLevel; }
inline void TrackSentMessageExpectingSeqNumAck( SteamNetworkingMicroseconds usecNow, bool bAllowDelayedReply ) { TLinkStatsTracker::TrackSentMessageExpectingSeqNumAckInternal( this, usecNow, bAllowDelayedReply ); }
inline void TrackSentPingRequest( SteamNetworkingMicroseconds usecNow, bool bAllowDelayedReply ) { TLinkStatsTracker::TrackSentPingRequestInternal( this, usecNow, bAllowDelayedReply ); }
inline void ReceivedPing( int nPingMS, SteamNetworkingMicroseconds usecNow ) { TLinkStatsTracker::ReceivedPingInternal( this, nPingMS, usecNow ); }
inline void TrackRecvPacket( int cbPktSize, SteamNetworkingMicroseconds usecNow ) { TLinkStatsTracker::TrackRecvPacketInternal( this, cbPktSize, usecNow ); }
inline void InFlightReplyTimeout( SteamNetworkingMicroseconds usecNow ) { TLinkStatsTracker::InFlightReplyTimeoutInternal( this, usecNow ); }
inline void ProcessMessage( const CMsgSteamDatagramConnectionQuality &msg, SteamNetworkingMicroseconds usecNow ) { TLinkStatsTracker::InternalProcessMessage( this, msg, usecNow ); }
void TrackSentStats( const CMsgSteamDatagramConnectionQuality &msg, SteamNetworkingMicroseconds usecNow, bool bAllowDelayedReply )
{
Assert( TLinkStatsTracker::m_eActivityLevel != ELinkActivityLevel::Disconnected );
TLinkStatsTracker::m_pktNumInFlight = TLinkStatsTracker::m_nNextSendSequenceNumber-1;
TLinkStatsTracker::m_bInFlightInstantaneous = msg.has_instantaneous();
TLinkStatsTracker::m_bInFlightLifetime = msg.has_lifetime();
TrackSentMessageExpectingSeqNumAck( usecNow, bAllowDelayedReply );
}
inline bool RecvPackedAcks( const google::protobuf::RepeatedField<google::protobuf::uint32> &msgField, SteamNetworkingMicroseconds usecNow )
{
bool bResult = true;
for ( uint32 nPackedAck: msgField )
{
if ( !TLinkStatsTracker::RecvPackedAckInternal( this, nPackedAck, usecNow ) )
bResult = false;
}
return bResult;
}
inline SteamNetworkingMicroseconds GetNextThinkTime( SteamNetworkingMicroseconds usecNow )
{
SteamNetworkingMicroseconds usecNextThink = k_nThinkTime_Never;
EStatsReplyRequest eReplyRequested;
if ( TLinkStatsTracker::GetSendReasonOrUpdateNextThinkTime( usecNow, eReplyRequested, usecNextThink ) )
return k_nThinkTime_ASAP;
return usecNextThink;
}
int64 ExpandWirePacketNumberAndCheck( uint16 nWireSeqNum, int idxMultiPath )
{
int16 nGap = (int16)( nWireSeqNum - (uint16)TLinkStatsTracker::m_nMaxRecvPktNum );
int64 nPktNum = TLinkStatsTracker::m_nMaxRecvPktNum + nGap;
constexpr int N = V_ARRAYSIZE(TLinkStatsTracker::m_arDebugHistoryRecvSeqNum);
COMPILE_TIME_ASSERT( ( N & (N-1) ) == 0 );
TLinkStatsTracker::m_arDebugHistoryRecvSeqNum[ TLinkStatsTracker::m_nPktsRecvSequenced & (N-1) ] = nPktNum;
TLinkStatsTracker::InternalProcessSequencedPacket_Count( idxMultiPath );
if ( likely( nPktNum > TLinkStatsTracker::m_nMaxRecvPktNum ) )
return nPktNum;
int64 B = TLinkStatsTracker::m_nMaxRecvPktNum & ~int64{63};
int64 idxRecvBitmask = ( ( nPktNum - B ) >> 6 ) + 1;
Assert( idxRecvBitmask < 2 );
if ( idxRecvBitmask < 0 )
{
TLinkStatsTracker::InternalProcessSequencedPacket_Lurch(); return 0;
}
uint64 bit = uint64{1} << ( nPktNum & 63 );
if ( TLinkStatsTracker::m_recvPktNumberMask[ idxRecvBitmask ] & bit )
{
if ( TLinkStatsTracker::m_recvPktNumberMaskMultiPath[idxMultiPath][idxRecvBitmask] & bit )
{
TLinkStatsTracker::InternalProcessSequencedPacket_Duplicate();
}
else
{
++TLinkStatsTracker::m_nMultiPathRecvLater[ idxMultiPath ];
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[idxMultiPath][idxRecvBitmask] |= bit;
}
return 0;
}
Assert( nPktNum > 0 && nPktNum < TLinkStatsTracker::m_nMaxRecvPktNum );
return nPktNum;
}
int64 ExpandWirePacketNumberAndCheckMaybeInitialize( uint16 nWireSeqNum, int idxMultiPath )
{
if ( unlikely( TLinkStatsTracker::m_nMaxRecvPktNum == 0 ) )
TLinkStatsTracker::ResetMaxRecvPktNumForIncomingWirePktNum( nWireSeqNum );
return ExpandWirePacketNumberAndCheck( nWireSeqNum, idxMultiPath );
}
inline void TrackProcessSequencedPacket( int64 nPktNum, SteamNetworkingMicroseconds usecNow, int usecSenderTimeSincePrev, int idxMultiPath )
{
Assert( nPktNum > 0 );
int64 B = TLinkStatsTracker::m_nMaxRecvPktNum & ~int64{63};
int64 idxRecvBitmask = ( ( nPktNum - B ) >> 6 ) + 1;
Assert( idxRecvBitmask >= 0 ); if ( idxRecvBitmask >= 2 ) {
if ( idxRecvBitmask == 2 )
{
TLinkStatsTracker::m_recvPktNumberMask[0] = TLinkStatsTracker::m_recvPktNumberMask[1];
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[0][0] = TLinkStatsTracker::m_recvPktNumberMaskMultiPath[0][1];
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[1][0] = TLinkStatsTracker::m_recvPktNumberMaskMultiPath[1][1];
}
else
{
TLinkStatsTracker::m_recvPktNumberMask[0] = 0;
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[0][0] = 0;
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[1][0] = 0;
}
TLinkStatsTracker::m_recvPktNumberMask[1] = 0;
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[0][1] = 0;
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[1][1] = 0;
idxRecvBitmask = 1;
}
uint64 bit = uint64{1} << ( nPktNum & 63 );
Assert( !( TLinkStatsTracker::m_recvPktNumberMask[ idxRecvBitmask ] & bit ) ); TLinkStatsTracker::m_recvPktNumberMask[ idxRecvBitmask ] |= bit;
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[idxMultiPath][idxRecvBitmask] |= bit;
int64 nGap = nPktNum - TLinkStatsTracker::m_nMaxRecvPktNum;
if ( likely( nGap == 1 ) )
{
++TLinkStatsTracker::m_nDebugPktsRecvInOrder;
if ( usecSenderTimeSincePrev >= 0 && usecSenderTimeSincePrev < k_usecTimeSinceLastPacketMaxReasonable )
{
SteamNetworkingMicroseconds usecRecvTimeSincePrev = ( usecNow - TLinkStatsTracker::m_usecTimeLastRecvSeq );
Assert( usecRecvTimeSincePrev >= 0 );
if ( (uint64)usecRecvTimeSincePrev < (uint64)k_usecTimeSinceLastPacketMaxReasonable )
{
int usecJitter = usecRecvTimeSincePrev - usecSenderTimeSincePrev;
TLinkStatsTracker::InternalProcessJitterSample( usecJitter );
}
}
}
else if ( unlikely( nGap <= 0 ) )
{
Assert( nGap != 0 );
Assert( nGap >= -8 * (int64)sizeof(TLinkStatsTracker::m_recvPktNumberMask) );
TLinkStatsTracker::InternalProcessSequencedPacket_OutOfOrder( nPktNum );
return;
}
else
{
if ( unlikely( nGap >= 100 ) )
{
TLinkStatsTracker::InternalProcessSequencedPacket_Lurch();
TLinkStatsTracker::InitMaxRecvPktNum( nPktNum );
TLinkStatsTracker::m_usecTimeLastRecvSeq = usecNow;
return;
}
TLinkStatsTracker::InternalProcessSequencedPacket_Dropped( nGap-1 );
}
TLinkStatsTracker::m_nMaxRecvPktNum = nPktNum;
TLinkStatsTracker::m_usecTimeLastRecvSeq = usecNow;
}
};
}
#endif