Add a new jitter simulation that does "clumping"

P4:9002451
This commit is contained in:
Fletcher Dunn
2024-08-28 16:16:47 -07:00
parent b10a6f38c0
commit 4b68b406df
4 changed files with 168 additions and 66 deletions
+42 -4
View File
@@ -1402,15 +1402,53 @@ enum ESteamNetworkingConfigValue
k_ESteamNetworkingConfig_FakePacketLag_Send = 4,
k_ESteamNetworkingConfig_FakePacketLag_Recv = 5,
/// [global float] 0-100 Percentage of packets we will add additional delay
/// to (causing them to be reordered)
/// Simulated jitter/clumping.
///
/// For each packet, a jitter value is determined (which may
/// be zero). This amount is added as extra delay to the
/// packet. When a subsequent packet is queued, it receives its
/// own random jitter amount from the current time. if this would
/// result in the packets being delivered out of order, the later
/// packet queue time is adjusted to happen after the first packet.
/// Thus simulating jitter by itself will not reorder packets, but it
/// can "clump" them.
///
/// - Avg: A random jitter time is generated using an exponential
/// distribution using this value as the mean (ms). The default
/// is zero, which disables random jitter.
/// - Max: Limit the random jitter time to this value (ms).
/// - Pct: odds (0-100) that a random jitter value for the packet
/// will be generated. Otherwise, a jitter value of zero
/// is used, and the packet will only be delayed by the jitter
/// system if necessary to retain order, due to the jitter of a
/// previous packet.
///
/// All values are [global float]
///
/// Fake jitter is simulated after fake lag, but before reordering.
k_ESteamNetworkingConfig_FakePacketJitter_Send_Avg = 53,
k_ESteamNetworkingConfig_FakePacketJitter_Send_Max = 54,
k_ESteamNetworkingConfig_FakePacketJitter_Send_Pct = 55,
k_ESteamNetworkingConfig_FakePacketJitter_Recv_Avg = 56,
k_ESteamNetworkingConfig_FakePacketJitter_Recv_Max = 57,
k_ESteamNetworkingConfig_FakePacketJitter_Recv_Pct = 58,
/// [global float] 0-100 Percentage of packets we will add additional
/// delay to. If other packet(s) are sent/received within this delay
/// window (that doesn't also randomly receive the same extra delay),
/// then the packets become reordered.
///
/// This mechanism is primarily intended to generate out-of-order
/// packets. To simulate random jitter, use the FakePacketJitter.
/// Fake packet reordering is applied after fake lag and jitter
k_ESteamNetworkingConfig_FakePacketReorder_Send = 6,
k_ESteamNetworkingConfig_FakePacketReorder_Recv = 7,
/// [global int32] Extra delay, in ms, to apply to reordered packets.
/// [global int32] Extra delay, in ms, to apply to reordered
/// packets. The same time value is used for sending and receiving.
k_ESteamNetworkingConfig_FakePacketReorder_Time = 8,
/// [global float 0--100] Globally duplicate some percentage of packets we send
/// [global float 0--100] Globally duplicate some percentage of packets.
k_ESteamNetworkingConfig_FakePacketDup_Send = 26,
k_ESteamNetworkingConfig_FakePacketDup_Recv = 27,
@@ -56,6 +56,12 @@ DEFINE_GLOBAL_CONFIGVAL( int32, FakeRateLimit_Send_Burst, 16*1024, 0, 1024*1024
DEFINE_GLOBAL_CONFIGVAL( int32, FakeRateLimit_Recv_Rate, 0, 0, 1024*1024*1024 );
DEFINE_GLOBAL_CONFIGVAL( int32, FakeRateLimit_Recv_Burst, 16*1024, 0, 1024*1024 );
DEFINE_GLOBAL_CONFIGVAL( int32, OutOfOrderCorrectionWindowMicroseconds, 1000, 0, 50*1000 );
DEFINE_GLOBAL_CONFIGVAL( float, FakePacketJitter_Send_Avg, 0.0f, 0.0f, 2000.0f );
DEFINE_GLOBAL_CONFIGVAL( float, FakePacketJitter_Send_Max, 100.0f, 0.0f, 5000.0f );
DEFINE_GLOBAL_CONFIGVAL( float, FakePacketJitter_Send_Pct, 75.0f, 0.0f, 100.0f );
DEFINE_GLOBAL_CONFIGVAL( float, FakePacketJitter_Recv_Avg, 0.0f, 0.0f, 2000.0f );
DEFINE_GLOBAL_CONFIGVAL( float, FakePacketJitter_Recv_Max, 100.0f, 0.0f, 5000.0f );
DEFINE_GLOBAL_CONFIGVAL( float, FakePacketJitter_Recv_Pct, 75.0f, 0.0f, 100.0f );
DEFINE_GLOBAL_CONFIGVAL( void *, Callback_AuthStatusChanged, nullptr );
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_STEAMNETWORKINGMESSAGES
@@ -1306,7 +1306,7 @@ class CPacketLagger : private IThinker
public:
~CPacketLagger() { Clear(); }
void LagPacket( CRawUDPSocketImpl *pSock, const netadr_t &adr, int msDelay, int nChunks, const iovec *pChunks )
void LagPacket( CRawUDPSocketImpl *pSock, const netadr_t &adr, SteamNetworkingMicroseconds usecTime, int nChunks, const iovec *pChunks )
{
SteamNetworkingGlobalLock::AssertHeldByCurrentThread( "LagPacket" );
@@ -1326,16 +1326,6 @@ public:
return;
}
if ( msDelay < 1 )
{
AssertMsg( false, "Packet lag time must be positive!" );
msDelay = 1;
}
// Limit to something sane
msDelay = std::min( msDelay, 5000 );
const SteamNetworkingMicroseconds usecTime = SteamNetworkingSockets_GetLocalTimestamp() + msDelay * 1000;
CLaggedPacket *pkt = new CLaggedPacket( pSock, adr, usecTime, cbPkt );
// Gather the payload data data into the buffer
@@ -1759,6 +1749,29 @@ void WakeServiceThread()
#endif
}
inline SteamNetworkingMicroseconds RandomJitter( const GlobalConfigValue<float> &ValAvg, const GlobalConfigValue<float> &ValMax, const GlobalConfigValue<float> &ValPct )
{
// The defaults disable jitter by setting the *average* to 0, so check that first.
if ( likely( ValAvg.Get() ) <= 0.0f )
return 0;
if ( ValMax.Get() <= 0.0f )
return 0;
if ( !RandomBoolWithOdds( ValPct.Get() ) )
return 0;
// Unscaled exponential distribution
float r = WeakRandomFloat( 0.000001f, 1.0f ); // log of 0 is undefined, set the minimum to a value that can be subtracted from 1. (Something close to FLT_EPSILON.)
float x = -logf( r );
if ( !( x > 0.0f ) ) // Written "backwards" just in case math blows up
return 0;
// Scale and clamp
float flJitterMS = std::min( x*ValAvg.Get(), ValMax.Get() );
// Convert to integer microseconds
return (SteamNetworkingMicroseconds)( flJitterMS * 1000.0f );
}
bool CRawUDPSocketImpl::BSendRawPacketGather( int nChunks, const iovec *pChunks, const netadr_t &adrTo ) const
{
SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
@@ -1797,28 +1810,47 @@ bool CRawUDPSocketImpl::BSendRawPacketGather( int nChunks, const iovec *pChunks,
if ( RandomBoolWithOdds( GlobalConfig::FakePacketLoss_Send.Get() ) )
return true;
// Fake lag?
int32 nPacketFakeLagTotal = GlobalConfig::FakePacketLag_Send.Get();
// Check for simulating random packet reordering
// Read convars and decide if we're going to simulate any lag, jitter, reordering, or duplication
int msFakeLag = GlobalConfig::FakePacketLag_Send.Get();
SteamNetworkingMicroseconds usecReorderLag = 0;
if ( RandomBoolWithOdds( GlobalConfig::FakePacketReorder_Send.Get() ) )
{
nPacketFakeLagTotal += GlobalConfig::FakePacketReorder_Time.Get();
}
usecReorderLag = GlobalConfig::FakePacketReorder_Time.Get()*1000;
SteamNetworkingMicroseconds usecJitter = RandomJitter( GlobalConfig::FakePacketJitter_Send_Avg, GlobalConfig::FakePacketJitter_Send_Max, GlobalConfig::FakePacketJitter_Send_Pct );
bool bDup = RandomBoolWithOdds( GlobalConfig::FakePacketDup_Send.Get() );
// Check for simulating random packet duplication
if ( RandomBoolWithOdds( GlobalConfig::FakePacketDup_Send.Get() ) )
// Anything active?
static SteamNetworkingMicroseconds s_usecMinNextJitteredTime;
if ( unlikely( msFakeLag > 0 || usecReorderLag > 0 || usecJitter > 0 || bDup || s_usecMinNextJitteredTime != 0 ) )
{
int32 nDupLag = nPacketFakeLagTotal + WeakRandomInt( 0, GlobalConfig::FakePacketDup_TimeMax.Get() );
nDupLag = std::max( 1, nDupLag );
s_packetLagQueueSend.LagPacket( const_cast<CRawUDPSocketImpl *>( this ), adrTo, nDupLag, nChunks, pChunks );
}
SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
SteamNetworkingMicroseconds usecWhenProcess = usecNow + msFakeLag*1000;
usecJitter = std::max( usecJitter, s_usecMinNextJitteredTime - usecWhenProcess );
if ( usecJitter > 0 )
{
usecWhenProcess += usecJitter;
s_usecMinNextJitteredTime = usecWhenProcess + 1;
}
else
{
// End of clump, clear this so we can switch back to the
// fast path once options are turned off
s_usecMinNextJitteredTime = 0;
}
usecWhenProcess += usecReorderLag;
// Lag the original packet?
if ( nPacketFakeLagTotal > 0 )
{
s_packetLagQueueSend.LagPacket( const_cast<CRawUDPSocketImpl *>( this ), adrTo, nPacketFakeLagTotal, nChunks, pChunks );
return true;
// Check for simulating random packet duplication
if ( bDup )
{
SteamNetworkingMicroseconds usecDupLag = 1 + (SteamNetworkingMicroseconds)WeakRandomFloat( 0.0f, GlobalConfig::FakePacketDup_TimeMax.Get()*1000.0f );
s_packetLagQueueSend.LagPacket( const_cast<CRawUDPSocketImpl *>( this ), adrTo, usecWhenProcess + usecDupLag, nChunks, pChunks );
}
// Lag the original packet?
if ( usecWhenProcess > usecNow )
{
s_packetLagQueueSend.LagPacket( const_cast<CRawUDPSocketImpl *>( this ), adrTo, usecWhenProcess, nChunks, pChunks );
return true;
}
}
// Now really send it
@@ -2346,43 +2378,62 @@ static bool DrainSocket( CRawUDPSocketImpl *pSock )
pSock->TracePkt( false, info.m_adrFrom, 1, &tmp );
}
int32 nPacketFakeLagTotal = GlobalConfig::FakePacketLag_Recv.Get();
// Check for simulating random packet reordering
// Read convars and decide if we're going to simulate any lag, jitter, reordering, or duplication
int msFakeLag = GlobalConfig::FakePacketLag_Recv.Get();
SteamNetworkingMicroseconds usecReorderLag = 0;
if ( RandomBoolWithOdds( GlobalConfig::FakePacketReorder_Recv.Get() ) )
usecReorderLag = GlobalConfig::FakePacketReorder_Time.Get()*1000;
SteamNetworkingMicroseconds usecJitter = RandomJitter( GlobalConfig::FakePacketJitter_Recv_Avg, GlobalConfig::FakePacketJitter_Recv_Max, GlobalConfig::FakePacketJitter_Recv_Pct );
bool bDup = RandomBoolWithOdds( GlobalConfig::FakePacketDup_Recv.Get() );
// Anything active?
static SteamNetworkingMicroseconds s_usecMinNextJitteredTime;
if ( unlikely( msFakeLag > 0 || usecReorderLag > 0 || usecJitter > 0 || bDup || s_usecMinNextJitteredTime != 0 ) )
{
nPacketFakeLagTotal += GlobalConfig::FakePacketReorder_Time.Get();
SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
SteamNetworkingMicroseconds usecWhenProcess = usecNow + msFakeLag*1000;
usecJitter = std::max( usecJitter, s_usecMinNextJitteredTime - usecWhenProcess );
if ( usecJitter > 0 )
{
usecWhenProcess += usecJitter;
s_usecMinNextJitteredTime = usecWhenProcess + 1;
}
else
{
// End of clump, clear this so we can switch back to the
// fast path once options are turned off
s_usecMinNextJitteredTime = 0;
}
usecWhenProcess += usecReorderLag;
// Check for simulating random packet duplication
if ( bDup )
{
SteamNetworkingMicroseconds usecDupLag = 1 + (SteamNetworkingMicroseconds)WeakRandomFloat( 0.0f, GlobalConfig::FakePacketDup_TimeMax.Get()*1000.0f );
iovec temp;
temp.iov_len = ret;
temp.iov_base = buf;
s_packetLagQueueRecv.LagPacket( pSock, info.m_adrFrom, usecWhenProcess + usecDupLag, 1, &temp );
}
// Lag the original packet?
if ( usecWhenProcess > usecNow )
{
iovec temp;
temp.iov_len = ret;
temp.iov_base = buf;
s_packetLagQueueRecv.LagPacket( pSock, info.m_adrFrom, usecWhenProcess, 1, &temp );
continue;
}
}
// Check for simulating random packet duplication
if ( RandomBoolWithOdds( GlobalConfig::FakePacketDup_Recv.Get() ) )
{
int32 nDupLag = nPacketFakeLagTotal + WeakRandomInt( 0, GlobalConfig::FakePacketDup_TimeMax.Get() );
nDupLag = std::max( 1, nDupLag );
iovec temp;
temp.iov_len = ret;
temp.iov_base = buf;
s_packetLagQueueRecv.LagPacket( pSock, info.m_adrFrom, nDupLag, 1, &temp );
}
// Check for simulating lag
if ( nPacketFakeLagTotal > 0 )
{
iovec temp;
temp.iov_len = ret;
temp.iov_base = buf;
s_packetLagQueueRecv.LagPacket( pSock, info.m_adrFrom, nPacketFakeLagTotal, 1, &temp );
}
else
{
info.m_pPkt = buf;
info.m_cbPkt = ret;
info.m_usecNow = usecRecvFromEnd;
info.m_pSock = pSock;
info.m_bQueuedForOutOfOrder = false;
pSock->m_callback( info );
}
// Process the packet now
info.m_pPkt = buf;
info.m_cbPkt = ret;
info.m_usecNow = usecRecvFromEnd;
info.m_pSock = pSock;
info.m_bQueuedForOutOfOrder = false;
pSock->m_callback( info );
#ifdef STEAMNETWORKINGSOCKETS_LOWLEVEL_TIME_SOCKET_CALLS
SteamNetworkingMicroseconds usecProcessPacketEnd = SteamNetworkingSockets_GetLocalTimestamp();
@@ -927,6 +927,13 @@ namespace GlobalConfig
extern GlobalConfigValue<int32> FakePacketReorder_Time;
extern GlobalConfigValue<float> FakePacketDup_Send;
extern GlobalConfigValue<float> FakePacketDup_Recv;
extern GlobalConfigValue<float> FakePacketJitter_Send_Avg;
extern GlobalConfigValue<float> FakePacketJitter_Send_Max;
extern GlobalConfigValue<float> FakePacketJitter_Send_Pct;
extern GlobalConfigValue<float> FakePacketJitter_Recv_Avg;
extern GlobalConfigValue<float> FakePacketJitter_Recv_Max;
extern GlobalConfigValue<float> FakePacketJitter_Recv_Pct;
extern GlobalConfigValue<int32> FakePacketDup_TimeMax;
extern GlobalConfigValue<int32> PacketTraceMaxBytes;
extern GlobalConfigValue<int32> FakeRateLimit_Send_Rate;