1 //====== Copyright Valve Corporation, All rights reserved. ====================
2 
3 #include <atomic>
4 #include <tier1/utlbuffer.h>
5 #include "steamnetworking_statsutils.h"
6 
7 // !KLUDGE! For SteamNetworkingSockets_GetLocalTimestamp
8 #ifdef IS_STEAMDATAGRAMROUTER
9 	#include "router/sdr.h"
10 #else
11 	#include "clientlib/steamnetworkingsockets_lowlevel.h"
12 #endif
13 
14 // Must be the last include
15 #include <tier0/memdbgon.h>
16 
17 using namespace SteamNetworkingSocketsLib;
18 
19 ///////////////////////////////////////////////////////////////////////////////
20 //
21 // LinkStatsTracker
22 //
23 ///////////////////////////////////////////////////////////////////////////////
24 
25 
Clear()26 void SteamDatagramLinkInstantaneousStats::Clear()
27 {
28 	memset( this, 0, sizeof(*this) );
29 	m_nPingMS = -1;
30 	m_flPacketsDroppedPct = -1.0f;
31 	m_flPacketsWeirdSequenceNumberPct = -1.0f;
32 	m_usecMaxJitter = -1;
33 	m_nSendRate = -1;
34 	m_nPendingBytes = 0;
35 }
36 
Clear()37 void SteamDatagramLinkLifetimeStats::Clear()
38 {
39 	memset( this, 0, sizeof(*this) );
40 	m_nConnectedSeconds = -1;
41 	m_nPingNtile5th = -1;
42 	m_nPingNtile50th = -1;
43 	m_nPingNtile75th = -1;
44 	m_nPingNtile95th = -1;
45 	m_nPingNtile98th = -1;
46 	m_nQualityNtile2nd = -1;
47 	m_nQualityNtile5th = -1;
48 	m_nQualityNtile25th = -1;
49 	m_nQualityNtile50th = -1;
50 	m_nTXSpeedNtile5th = -1;
51 	m_nTXSpeedNtile50th = -1;
52 	m_nTXSpeedNtile75th = -1;
53 	m_nTXSpeedNtile95th = -1;
54 	m_nTXSpeedNtile98th = -1;
55 	m_nRXSpeedNtile5th = -1;
56 	m_nRXSpeedNtile50th = -1;
57 	m_nRXSpeedNtile75th = -1;
58 	m_nRXSpeedNtile95th = -1;
59 	m_nRXSpeedNtile98th = -1;
60 }
61 
Clear()62 void SteamDatagramLinkStats::Clear()
63 {
64 	m_latest.Clear();
65 	//m_peak.Clear();
66 	m_lifetime.Clear();
67 	m_latestRemote.Clear();
68 	m_flAgeLatestRemote = -1.0f;
69 	m_lifetimeRemote.Clear();
70 	m_flAgeLifetimeRemote = -1.0f;
71 }
72 
73 
Reset()74 void PingTracker::Reset()
75 {
76 	memset( m_arPing, 0, sizeof(m_arPing) );
77 	m_nValidPings = 0;
78 	m_nSmoothedPing = -1;
79 	m_usecTimeLastSentPingRequest = 0;
80 }
81 
ReceivedPing(int nPingMS,SteamNetworkingMicroseconds usecNow)82 void PingTracker::ReceivedPing( int nPingMS, SteamNetworkingMicroseconds usecNow )
83 {
84 	Assert( nPingMS >= 0 );
85 	COMPILE_TIME_ASSERT( V_ARRAYSIZE(m_arPing) == 3 );
86 
87 	// Discard oldest, insert new sample at head
88 	m_arPing[2] = m_arPing[1];
89 	m_arPing[1] = m_arPing[0];
90 	m_arPing[0].m_nPingMS = nPingMS;
91 	m_arPing[0].m_usecTimeRecv = usecNow;
92 
93 	// Compute smoothed ping and update sample count based on existing sample size
94 	switch ( m_nValidPings )
95 	{
96 		case 0:
97 			// First sample.  Smoothed value is simply the same thing as the sample
98 			m_nValidPings = 1;
99 			m_nSmoothedPing = nPingMS;
100 			break;
101 
102 		case 1:
103 			// Second sample.  Smoothed value is the average
104 			m_nValidPings = 2;
105 			m_nSmoothedPing = ( m_arPing[0].m_nPingMS + m_arPing[1].m_nPingMS ) >> 1;
106 			break;
107 
108 		default:
109 			AssertMsg1( false, "Unexpected valid ping count %d", m_nValidPings );
110 			// FALLTHROUGH
111 		case 2:
112 			// Just received our final sample to complete the sample
113 			m_nValidPings = 3;
114 			// FALLTHROUGH
115 		case 3:
116 		{
117 			// Full sample.  Take the average of the two best.  Hopefully this strategy ignores a single
118 			// ping spike, but without being too optimistic and underestimating the sustained latency too
119 			// much.  (Another option might be to use the median?)
120 			int nMax = Max( m_arPing[0].m_nPingMS, m_arPing[1].m_nPingMS );
121 			nMax = Max( nMax, m_arPing[2].m_nPingMS );
122 			m_nSmoothedPing = ( m_arPing[0].m_nPingMS + m_arPing[1].m_nPingMS + m_arPing[2].m_nPingMS - nMax ) >> 1;
123 			break;
124 		}
125 	}
126 }
127 
WorstPingInRecentSample() const128 int PingTracker::WorstPingInRecentSample() const
129 {
130 	if ( m_nValidPings < 1 )
131 	{
132 		AssertMsg( false, "Tried to make a pessimistic ping estimate without any ping data at all!" );
133 		return 500;
134 	}
135 	int nResult = m_arPing[0].m_nPingMS;
136 	for ( int i = 1 ; i < m_nValidPings ; ++i )
137 		nResult = Max( nResult, m_arPing[i].m_nPingMS );
138 	return nResult;
139 }
140 
InitInternal(SteamNetworkingMicroseconds usecNow)141 void LinkStatsTrackerBase::InitInternal( SteamNetworkingMicroseconds usecNow )
142 {
143 	m_nPeerProtocolVersion = 0;
144 	m_bPassive = false;
145 	m_sent.Reset();
146 	m_recv.Reset();
147 	m_recvExceedRateLimit.Reset();
148 	m_ping.Reset();
149 	m_nNextSendSequenceNumber = 1;
150 	m_usecTimeLastSentSeq = 0;
151 	InitMaxRecvPktNum( 0 );
152 	m_seqPktCounters.Reset();
153 	m_flInPacketsDroppedPct = -1.0f;
154 	m_flInPacketsWeirdSequencePct = -1.0f;
155 	m_usecMaxJitterPreviousInterval = -1;
156 	m_nPktsRecvSequenced = 0;
157 	m_nDebugPktsRecvInOrder = 0;
158 	m_nPktsRecvDroppedAccumulator = 0;
159 	m_nPktsRecvOutOfOrderAccumulator = 0;
160 	m_nPktsRecvDuplicateAccumulator = 0;
161 	m_nPktsRecvLurchAccumulator = 0;
162 	m_usecTimeLastRecv = 0;
163 	m_usecTimeLastRecvSeq = 0;
164 	memset( &m_latestRemote, 0, sizeof(m_latestRemote) );
165 	m_usecTimeRecvLatestRemote = 0;
166 	memset( &m_lifetimeRemote, 0, sizeof(m_lifetimeRemote) );
167 	m_usecTimeRecvLifetimeRemote = 0;
168 	//m_seqnumUnackedSentLifetime = -1;
169 	//m_seqnumPendingAckRecvTimelife = -1;
170 	m_qualityHistogram.Reset();
171 	m_qualitySample.Clear();
172 	m_jitterHistogram.Reset();
173 }
174 
SetPassiveInternal(bool bFlag,SteamNetworkingMicroseconds usecNow)175 void LinkStatsTrackerBase::SetPassiveInternal( bool bFlag, SteamNetworkingMicroseconds usecNow )
176 {
177 	m_bPassive = bFlag;
178 
179 	m_pktNumInFlight = 0;
180 	m_bInFlightInstantaneous = false;
181 	m_bInFlightLifetime = false;
182 	PeerAckedInstantaneous( usecNow );
183 	PeerAckedLifetime( usecNow );
184 
185 	// Clear acks we expect, on either state change.
186 	m_usecInFlightReplyTimeout = 0;
187 	m_usecLastSendPacketExpectingImmediateReply = 0;
188 	m_nReplyTimeoutsSinceLastRecv = 0;
189 	m_usecWhenTimeoutStarted = 0;
190 
191 	if ( !bFlag )
192 	{
193 		StartNextInterval( usecNow );
194 	}
195 }
196 
StartNextInterval(SteamNetworkingMicroseconds usecNow)197 void LinkStatsTrackerBase::StartNextInterval( SteamNetworkingMicroseconds usecNow )
198 {
199 	m_nPktsRecvDroppedAccumulator += m_seqPktCounters.m_nDropped;
200 	m_nPktsRecvOutOfOrderAccumulator += m_seqPktCounters.m_nOutOfOrder;
201 	m_nPktsRecvDuplicateAccumulator += m_seqPktCounters.m_nDuplicate;
202 	m_nPktsRecvLurchAccumulator += m_seqPktCounters.m_nLurch;
203 	m_seqPktCounters.Reset();
204 	m_usecIntervalStart = usecNow;
205 }
206 
UpdateInterval(SteamNetworkingMicroseconds usecNow)207 void LinkStatsTrackerBase::UpdateInterval( SteamNetworkingMicroseconds usecNow )
208 {
209 	float flElapsed = int64( usecNow - m_usecIntervalStart ) * 1e-6;
210 	flElapsed = Max( flElapsed, .001f ); // make sure math doesn't blow up
211 
212 	// Check if enough happened in this interval to make a meaningful judgment about connection quality
213 	COMPILE_TIME_ASSERT( k_usecSteamDatagramLinkStatsDefaultInterval >= 5*k_nMillion );
214 	if ( flElapsed > 4.5f )
215 	{
216 		if ( m_seqPktCounters.m_nRecv > 5 )
217 		{
218 			int nWeird = m_seqPktCounters.Weird();
219 			int nBad = m_seqPktCounters.m_nDropped + nWeird;
220 			if ( nBad == 0 )
221 			{
222 				// Perfect connection.  This will hopefully be relatively common
223 				m_qualitySample.AddSample( 100 );
224 				++m_qualityHistogram.m_n100;
225 			}
226 			else
227 			{
228 
229 				// Less than perfect.  Compute quality metric.
230 				int nTotalSent = m_seqPktCounters.m_nRecv + m_seqPktCounters.m_nDropped;
231 				int nRecvGood = m_seqPktCounters.m_nRecv - nWeird;
232 				int nQuality = nRecvGood * 100 / nTotalSent;
233 
234 				// Cap at 99, since 100 is reserved to mean "perfect",
235 				// I don't think it's possible for the calculation above to ever produce 100, but whatever.
236 				if ( nQuality >= 99 )
237 				{
238 					m_qualitySample.AddSample( 99 );
239 					++m_qualityHistogram.m_n99;
240 				}
241 				else if ( nQuality <= 1 ) // in case accounting is hosed or every single packet was out of order, clamp.  0 means "totally dead connection"
242 				{
243 					m_qualitySample.AddSample( 1 );
244 					++m_qualityHistogram.m_n1;
245 				}
246 				else
247 				{
248 					m_qualitySample.AddSample( nQuality );
249 					if ( nQuality >= 97 )
250 						++m_qualityHistogram.m_n97;
251 					else if ( nQuality >= 95 )
252 						++m_qualityHistogram.m_n95;
253 					else if ( nQuality >= 90 )
254 						++m_qualityHistogram.m_n90;
255 					else if ( nQuality >= 75 )
256 						++m_qualityHistogram.m_n75;
257 					else if ( nQuality >= 50 )
258 						++m_qualityHistogram.m_n50;
259 					else
260 						++m_qualityHistogram.m_n1;
261 				}
262 			}
263 		}
264 		else if ( m_recv.m_packets.m_nCurrentInterval == 0 && m_sent.m_packets.m_nCurrentInterval > (int64)( flElapsed ) && m_nReplyTimeoutsSinceLastRecv >= 2 )
265 		{
266 			COMPILE_TIME_ASSERT( k_usecSteamDatagramClientPingTimeout + k_usecSteamDatagramRouterPendClientPing < k_nMillion );
267 
268 			// He's dead, Jim.  But we've been trying pretty hard to talk to him, so it probably isn't
269 			// because the connection is just idle or shutting down.  The connection has probably
270 			// dropped.
271 			m_qualitySample.AddSample(0);
272 			++m_qualityHistogram.m_nDead;
273 		}
274 	}
275 
276 	// PacketRate class does most of the work
277 	m_sent.UpdateInterval( flElapsed );
278 	m_recv.UpdateInterval( flElapsed );
279 	m_recvExceedRateLimit.UpdateInterval( flElapsed );
280 
281 	int nWeirdSequenceCurrentInterval = m_seqPktCounters.Weird();
282 	Assert( nWeirdSequenceCurrentInterval <= m_seqPktCounters.m_nRecv );
283 	if ( m_seqPktCounters.m_nRecv <= 0 )
284 	{
285 		// No sequenced packets received during interval, so no data available
286 		m_flInPacketsDroppedPct = -1.0f;
287 		m_flInPacketsWeirdSequencePct = -1.0f;
288 	}
289 	else
290 	{
291 		float flToPct = 1.0f / float( m_seqPktCounters.m_nRecv + m_seqPktCounters.m_nDropped );
292 		m_flInPacketsDroppedPct = m_seqPktCounters.m_nDropped * flToPct;
293 		m_flInPacketsWeirdSequencePct = nWeirdSequenceCurrentInterval * flToPct;
294 	}
295 
296 	// Peak jitter value
297 	m_usecMaxJitterPreviousInterval = m_seqPktCounters.m_usecMaxJitter;
298 
299 	// Reset for next time
300 	StartNextInterval( usecNow );
301 }
302 
InitMaxRecvPktNum(int64 nPktNum)303 void LinkStatsTrackerBase::InitMaxRecvPktNum( int64 nPktNum )
304 {
305 	Assert( nPktNum >= 0 );
306 	m_nMaxRecvPktNum = nPktNum;
307 
308 	// Set bits, to mark that all values <= this packet number have been
309 	// received.
310 	m_recvPktNumberMask[0] = ~(uint64)0;
311 	unsigned nBitsToSet = (unsigned)( nPktNum & 63 ) + 1;
312 	if ( nBitsToSet == 64 )
313 		m_recvPktNumberMask[1] = ~(uint64)0;
314 	else
315 		m_recvPktNumberMask[1] = ( (uint64)1 << nBitsToSet ) - 1;
316 
317 	m_nDebugLastInitMaxRecvPktNum = nPktNum;
318 }
319 
RecvPktNumStateDebugString() const320 std::string LinkStatsTrackerBase::RecvPktNumStateDebugString() const
321 {
322 	char buf[256];
323 	V_sprintf_safe( buf,
324 		"maxrecv=%lld, init=%lld, inorder=%lld, mask=%llx,%llx",
325 		(long long)m_nMaxRecvPktNum, (long long)m_nDebugLastInitMaxRecvPktNum, (long long)m_nDebugPktsRecvInOrder,
326 		(unsigned long long)m_recvPktNumberMask[0], (unsigned long long)m_recvPktNumberMask[1] );
327 	std::string result( buf );
328 
329 	constexpr int N = V_ARRAYSIZE( m_arDebugHistoryRecvSeqNum );
330 	COMPILE_TIME_ASSERT( ( N & (N-1) ) == 0 );
331 	int nMaxPkts = (int)std::min( (int64)std::min( 8, N ), m_nPktsRecvSequenced );
332 
333 	int64 idx = m_nPktsRecvSequenced;
334 	const char *pszLeader = " | ";
335 	while ( --nMaxPkts >= 0 && --idx >= 0 )
336 	{
337 		char buf2[32];
338 		V_sprintf_safe( buf2, "%s%lld", pszLeader, (long long)m_arDebugHistoryRecvSeqNum[ idx & (N-1) ] );
339 		result.append( buf2 );
340 		pszLeader = ",";
341 	}
342 
343 	return result;
344 }
345 
InternalProcessSequencedPacket_OutOfOrder(int64 nPktNum)346 void LinkStatsTrackerBase::InternalProcessSequencedPacket_OutOfOrder( int64 nPktNum )
347 {
348 
349 	// We should have previously counted this packet as dropped.
350 	if ( PktsRecvDropped() == 0 )
351 	{
352 		// This is weird.
353 		// !TEST! Only assert if we can provide more detailed info to debug.
354 		// Also note that on the relay, old peers are using a single sequence
355 		// number stream, shred across multiple sessions, and we are not
356 		// tracking this properly, because we don't know which session we
357 		// marked the "drop" in.
358 		if ( m_nPktsRecvSequenced < 256 && m_nPeerProtocolVersion >= 9 )
359 		{
360 				AssertMsg( false,
361 					"No dropped packets, pkt num %lld, dup bit not set?  recvseq=%lld inorder=%lld, dup=%lld, lurch=%lld, ooo=%lld, %s.  (%s)",
362 					(long long)nPktNum, (long long)m_nPktsRecvSequenced,
363 					(long long)m_nDebugPktsRecvInOrder, (long long)PktsRecvDuplicate(),
364 					(long long)PktsRecvLurch(), (long long)PktsRecvOutOfOrder(),
365 					RecvPktNumStateDebugString().c_str(),
366 					Describe().c_str()
367 				);
368 		}
369 	}
370 
371 	m_seqPktCounters.OnOutOfOrder();
372 }
373 
BCheckHaveDataToSendInstantaneous(SteamNetworkingMicroseconds usecNow)374 bool LinkStatsTrackerBase::BCheckHaveDataToSendInstantaneous( SteamNetworkingMicroseconds usecNow )
375 {
376 	Assert( !m_bPassive );
377 
378 	// How many packets a second to we expect to send on an "active" connection?
379 	const int64 k_usecActiveConnectionSendInterval = 3*k_nMillion;
380 	COMPILE_TIME_ASSERT( k_usecSteamDatagramClientPingTimeout*2 < k_usecActiveConnectionSendInterval );
381 	COMPILE_TIME_ASSERT( k_usecSteamDatagramClientBackupRouterKeepaliveInterval > k_usecActiveConnectionSendInterval*5 ); // make sure backup keepalive interval isn't anywhere near close enough to trigger this.
382 
383 	// Calculate threshold based on how much time has elapsed and a very low packet rate
384 	int64 usecElapsed = usecNow - m_usecPeerAckedInstaneous;
385 	Assert( usecElapsed >= k_usecLinkStatsInstantaneousReportInterval ); // don't call this unless you know it's been long enough!
386 	int nThreshold = usecElapsed / k_usecActiveConnectionSendInterval;
387 
388 	// Has there been any traffic worth reporting on in this interval?
389 	if ( m_nPktsRecvSeqWhenPeerAckInstantaneous + nThreshold < m_nPktsRecvSequenced || m_nPktsSentWhenPeerAckInstantaneous + nThreshold < m_sent.m_packets.Total() )
390 		return true;
391 
392 	// Connection has been idle since the last time we sent instantaneous stats.
393 	// Don't actually send stats, but clear counters and timers and act like we did.
394 	PeerAckedInstantaneous( usecNow );
395 
396 	// And don't send anything
397 	return false;
398 }
399 
BCheckHaveDataToSendLifetime(SteamNetworkingMicroseconds usecNow)400 bool LinkStatsTrackerBase::BCheckHaveDataToSendLifetime( SteamNetworkingMicroseconds usecNow )
401 {
402 	Assert( !m_bPassive );
403 
404 	// Make sure we have something new to report since the last time we sent stats
405 	if ( m_nPktsRecvSeqWhenPeerAckLifetime + 100 < m_nPktsRecvSequenced || m_nPktsSentWhenPeerAckLifetime + 100 < m_sent.m_packets.Total() )
406 		return true;
407 
408 	// Reset the timer.  But do NOT reset the packet counters.  So if the connection isn't
409 	// dropped, and we are sending keepalives very slowly, this will just send some stats
410 	// along about every 100 packets or so.  Typically we'll drop the session before that
411 	// happens.
412 	m_usecPeerAckedLifetime = usecNow;
413 
414 	// Don't send anything now
415 	return false;
416 }
417 
GetStatsSendNeed(SteamNetworkingMicroseconds usecNow)418 int LinkStatsTrackerBase::GetStatsSendNeed( SteamNetworkingMicroseconds usecNow )
419 {
420 	int nResult = 0;
421 
422 	// Message already in flight?
423 	if ( m_pktNumInFlight == 0 && !m_bPassive )
424 	{
425 		if ( m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportInterval < usecNow && BCheckHaveDataToSendInstantaneous( usecNow ) )
426 		{
427 			if ( m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMaxInterval < usecNow )
428 				nResult |= k_nSendStats_Instantanous_Due;
429 			else
430 				nResult |= k_nSendStats_Instantanous_Ready;
431 		}
432 
433 		if ( m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportInterval < usecNow && BCheckHaveDataToSendLifetime( usecNow ) )
434 		{
435 			if ( m_usecPeerAckedInstaneous + k_usecLinkStatsLifetimeReportMaxInterval < usecNow )
436 				nResult |= k_nSendStats_Lifetime_Due;
437 			else
438 				nResult |= k_nSendStats_Lifetime_Ready;
439 		}
440 	}
441 
442 	return nResult;
443 }
444 
InternalGetSendStatsReasonOrUpdateNextThinkTime(SteamNetworkingMicroseconds usecNow,const char * const arpszReasonStrings[4],SteamNetworkingMicroseconds & inOutNextThinkTime)445 const char *LinkStatsTrackerBase::InternalGetSendStatsReasonOrUpdateNextThinkTime( SteamNetworkingMicroseconds usecNow, const char *const arpszReasonStrings[4], SteamNetworkingMicroseconds &inOutNextThinkTime )
446 {
447 	if ( m_bPassive )
448 		return nullptr;
449 	if ( m_usecInFlightReplyTimeout > 0 && m_usecInFlightReplyTimeout < inOutNextThinkTime )
450 		inOutNextThinkTime = m_usecInFlightReplyTimeout;
451 
452 	// Message already in flight?
453 	if ( m_pktNumInFlight )
454 		return nullptr;
455 
456 	int n = 0;
457 	if ( m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMaxInterval < usecNow && BCheckHaveDataToSendInstantaneous( usecNow ) )
458 	{
459 		n |= 1;
460 	}
461 	else
462 	{
463 		SteamNetworkingMicroseconds usecNextCheck = m_usecPeerAckedInstaneous + k_usecLinkStatsInstantaneousReportMaxInterval;
464 		if ( usecNextCheck < inOutNextThinkTime )
465 			inOutNextThinkTime = usecNextCheck;
466 	}
467 	if ( m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportMaxInterval < usecNow && BCheckHaveDataToSendLifetime( usecNow ) )
468 	{
469 		n |= 2;
470 	}
471 	else
472 	{
473 		SteamNetworkingMicroseconds usecNextCheck = m_usecPeerAckedLifetime + k_usecLinkStatsLifetimeReportMaxInterval;
474 		if ( usecNextCheck < inOutNextThinkTime )
475 			inOutNextThinkTime = usecNextCheck;
476 	}
477 	return arpszReasonStrings[n];
478 }
479 
PopulateMessage(int nNeedFlags,CMsgSteamDatagramConnectionQuality & msg,SteamNetworkingMicroseconds usecNow)480 void LinkStatsTrackerBase::PopulateMessage( int nNeedFlags, CMsgSteamDatagramConnectionQuality &msg, SteamNetworkingMicroseconds usecNow )
481 {
482 	Assert( m_pktNumInFlight == 0 && !m_bPassive );
483 
484 	// Ready to send instantaneous stats?
485 	if ( nNeedFlags & k_nSendStats_Instantanous )
486 	{
487 		// !KLUDGE! Go through public struct as intermediary to keep code simple.
488 		SteamDatagramLinkInstantaneousStats sInstant;
489 		GetInstantaneousStats( sInstant );
490 		LinkStatsInstantaneousStructToMsg( sInstant, *msg.mutable_instantaneous() );
491 	}
492 
493 	// Ready to send lifetime stats?
494 	if ( nNeedFlags & k_nSendStats_Lifetime )
495 	{
496 		PopulateLifetimeMessage( *msg.mutable_lifetime() );
497 	}
498 }
499 
PopulateLifetimeMessage(CMsgSteamDatagramLinkLifetimeStats & msg)500 void LinkStatsTrackerBase::PopulateLifetimeMessage( CMsgSteamDatagramLinkLifetimeStats &msg )
501 {
502 	// !KLUDGE! Go through public struct as intermediary to keep code simple.
503 	SteamDatagramLinkLifetimeStats sLifetime;
504 	GetLifetimeStats( sLifetime );
505 	LinkStatsLifetimeStructToMsg( sLifetime, msg );
506 }
507 
TrackSentMessageExpectingReply(SteamNetworkingMicroseconds usecNow,bool bAllowDelayedReply)508 void LinkStatsTrackerBase::TrackSentMessageExpectingReply( SteamNetworkingMicroseconds usecNow, bool bAllowDelayedReply )
509 {
510 	if ( m_usecInFlightReplyTimeout == 0 )
511 	{
512 		m_usecInFlightReplyTimeout = usecNow + m_ping.CalcConservativeTimeout();
513 		if ( bAllowDelayedReply )
514 			m_usecInFlightReplyTimeout += k_usecSteamDatagramRouterPendClientPing;
515 	}
516 	if ( !bAllowDelayedReply )
517 		m_usecLastSendPacketExpectingImmediateReply = usecNow;
518 }
519 
ProcessMessage(const CMsgSteamDatagramConnectionQuality & msg,SteamNetworkingMicroseconds usecNow)520 void LinkStatsTrackerBase::ProcessMessage( const CMsgSteamDatagramConnectionQuality &msg, SteamNetworkingMicroseconds usecNow )
521 {
522 	if ( msg.has_instantaneous() )
523 	{
524 		LinkStatsInstantaneousMsgToStruct( msg.instantaneous(), m_latestRemote );
525 		m_usecTimeRecvLatestRemote = usecNow;
526 	}
527 	if ( msg.has_lifetime() )
528 	{
529 		LinkStatsLifetimeMsgToStruct( msg.lifetime(), m_lifetimeRemote );
530 		m_usecTimeRecvLifetimeRemote = usecNow;
531 	}
532 }
533 
GetInstantaneousStats(SteamDatagramLinkInstantaneousStats & s) const534 void LinkStatsTrackerBase::GetInstantaneousStats( SteamDatagramLinkInstantaneousStats &s ) const
535 {
536 	s.m_flOutPacketsPerSec = m_sent.m_packets.m_flRate;
537 	s.m_flOutBytesPerSec = m_sent.m_bytes.m_flRate;
538 	s.m_flInPacketsPerSec = m_recv.m_packets.m_flRate;
539 	s.m_flInBytesPerSec = m_recv.m_bytes.m_flRate;
540 	s.m_nPingMS = m_ping.m_nSmoothedPing;
541 	s.m_flPacketsDroppedPct = m_flInPacketsDroppedPct;
542 	s.m_flPacketsWeirdSequenceNumberPct = m_flInPacketsWeirdSequencePct;
543 	s.m_usecMaxJitter = m_usecMaxJitterPreviousInterval;
544 }
545 
GetLifetimeStats(SteamDatagramLinkLifetimeStats & s) const546 void LinkStatsTrackerBase::GetLifetimeStats( SteamDatagramLinkLifetimeStats &s ) const
547 {
548 	s.m_nPacketsSent = m_sent.m_packets.Total();
549 	s.m_nBytesSent = m_sent.m_bytes.Total();
550 	s.m_nPacketsRecv = m_recv.m_packets.Total();
551 	s.m_nBytesRecv = m_recv.m_bytes.Total();
552 	s.m_nPktsRecvSequenced = m_nPktsRecvSequenced;
553 	s.m_nPktsRecvDropped = PktsRecvDropped();
554 	s.m_nPktsRecvOutOfOrder = PktsRecvOutOfOrder();
555 	s.m_nPktsRecvDuplicate = PktsRecvDuplicate();
556 	s.m_nPktsRecvSequenceNumberLurch = PktsRecvLurch();
557 
558 	s.m_qualityHistogram = m_qualityHistogram;
559 
560 	s.m_nQualityNtile50th = m_qualitySample.NumSamples() <  2 ? -1 : m_qualitySample.GetPercentile( .50f );
561 	s.m_nQualityNtile25th = m_qualitySample.NumSamples() <  4 ? -1 : m_qualitySample.GetPercentile( .25f );
562 	s.m_nQualityNtile5th  = m_qualitySample.NumSamples() < 20 ? -1 : m_qualitySample.GetPercentile( .05f );
563 	s.m_nQualityNtile2nd  = m_qualitySample.NumSamples() < 50 ? -1 : m_qualitySample.GetPercentile( .02f );
564 
565 	m_ping.GetLifetimeStats( s );
566 
567 	s.m_jitterHistogram = m_jitterHistogram;
568 
569 	//
570 	// Clear all end-to-end values
571 	//
572 
573 	s.m_nTXSpeedMax           = -1;
574 
575 	s.m_nTXSpeedHistogram16   = 0;
576 	s.m_nTXSpeedHistogram32   = 0;
577 	s.m_nTXSpeedHistogram64   = 0;
578 	s.m_nTXSpeedHistogram128  = 0;
579 	s.m_nTXSpeedHistogram256  = 0;
580 	s.m_nTXSpeedHistogram512  = 0;
581 	s.m_nTXSpeedHistogram1024 = 0;
582 	s.m_nTXSpeedHistogramMax  = 0;
583 
584 	s.m_nTXSpeedNtile5th  = -1;
585 	s.m_nTXSpeedNtile50th = -1;
586 	s.m_nTXSpeedNtile75th = -1;
587 	s.m_nTXSpeedNtile95th = -1;
588 	s.m_nTXSpeedNtile98th = -1;
589 
590 	s.m_nRXSpeedMax           = -1;
591 
592 	s.m_nRXSpeedHistogram16   = 0;
593 	s.m_nRXSpeedHistogram32   = 0;
594 	s.m_nRXSpeedHistogram64   = 0;
595 	s.m_nRXSpeedHistogram128  = 0;
596 	s.m_nRXSpeedHistogram256  = 0;
597 	s.m_nRXSpeedHistogram512  = 0;
598 	s.m_nRXSpeedHistogram1024 = 0;
599 	s.m_nRXSpeedHistogramMax  = 0;
600 
601 	s.m_nRXSpeedNtile5th  = -1;
602 	s.m_nRXSpeedNtile50th = -1;
603 	s.m_nRXSpeedNtile75th = -1;
604 	s.m_nRXSpeedNtile95th = -1;
605 	s.m_nRXSpeedNtile98th = -1;
606 }
607 
GetLinkStats(SteamDatagramLinkStats & s,SteamNetworkingMicroseconds usecNow) const608 void LinkStatsTrackerBase::GetLinkStats( SteamDatagramLinkStats &s, SteamNetworkingMicroseconds usecNow ) const
609 {
610 	GetInstantaneousStats( s.m_latest );
611 	GetLifetimeStats( s.m_lifetime );
612 
613 	if ( m_usecTimeRecvLatestRemote )
614 	{
615 		s.m_latestRemote = m_latestRemote;
616 		s.m_flAgeLatestRemote = ( usecNow - m_usecTimeRecvLatestRemote ) * 1e-6;
617 	}
618 	else
619 	{
620 		s.m_latestRemote.Clear();
621 		s.m_flAgeLatestRemote = -1.0f;
622 	}
623 
624 	if ( m_usecTimeRecvLifetimeRemote )
625 	{
626 		s.m_lifetimeRemote = m_lifetimeRemote;
627 		s.m_flAgeLifetimeRemote = ( usecNow - m_usecTimeRecvLifetimeRemote ) * 1e-6;
628 	}
629 	else
630 	{
631 		s.m_lifetimeRemote.Clear();
632 		s.m_flAgeLifetimeRemote = -1.0f;
633 	}
634 }
635 
InitInternal(SteamNetworkingMicroseconds usecNow)636 void LinkStatsTrackerEndToEnd::InitInternal( SteamNetworkingMicroseconds usecNow )
637 {
638 	LinkStatsTrackerBase::InitInternal( usecNow );
639 
640 	m_usecWhenStartedConnectedState = 0;
641 	m_usecWhenEndedConnectedState = 0;
642 
643 	m_TXSpeedSample.Clear();
644 	m_nTXSpeed = 0;
645 	m_nTXSpeedHistogram16 = 0; // Speed at kb/s
646 	m_nTXSpeedHistogram32 = 0;
647 	m_nTXSpeedHistogram64 = 0;
648 	m_nTXSpeedHistogram128 = 0;
649 	m_nTXSpeedHistogram256 = 0;
650 	m_nTXSpeedHistogram512 = 0;
651 	m_nTXSpeedHistogram1024 = 0;
652 	m_nTXSpeedHistogramMax = 0;
653 
654 	m_RXSpeedSample.Clear();
655 	m_nRXSpeed = 0;
656 	m_nRXSpeedHistogram16 = 0; // Speed at kb/s
657 	m_nRXSpeedHistogram32 = 0;
658 	m_nRXSpeedHistogram64 = 0;
659 	m_nRXSpeedHistogram128 = 0;
660 	m_nRXSpeedHistogram256 = 0;
661 	m_nRXSpeedHistogram512 = 0;
662 	m_nRXSpeedHistogram1024 = 0;
663 	m_nRXSpeedHistogramMax = 0;
664 
665 	StartNextSpeedInterval( usecNow );
666 }
667 
StartNextSpeedInterval(SteamNetworkingMicroseconds usecNow)668 void LinkStatsTrackerEndToEnd::StartNextSpeedInterval( SteamNetworkingMicroseconds usecNow )
669 {
670 	m_usecSpeedIntervalStart = usecNow;
671 }
672 
UpdateSpeedInterval(SteamNetworkingMicroseconds usecNow)673 void LinkStatsTrackerEndToEnd::UpdateSpeedInterval( SteamNetworkingMicroseconds usecNow )
674 {
675 	float flElapsed = int64( usecNow - m_usecIntervalStart ) * 1e-6;
676 	flElapsed = Max( flElapsed, .001f ); // make sure math doesn't blow up
677 
678 	int nTXKBs = ( m_nTXSpeed + 512 ) / 1024;
679 	m_TXSpeedSample.AddSample( nTXKBs );
680 
681 	if ( nTXKBs <= 16 ) ++m_nTXSpeedHistogram16;
682 	else if ( nTXKBs <= 32 ) ++m_nTXSpeedHistogram32;
683 	else if ( nTXKBs <= 64 ) ++m_nTXSpeedHistogram64;
684 	else if ( nTXKBs <= 128 ) ++m_nTXSpeedHistogram128;
685 	else if ( nTXKBs <= 256 ) ++m_nTXSpeedHistogram256;
686 	else if ( nTXKBs <= 512 ) ++m_nTXSpeedHistogram512;
687 	else if ( nTXKBs <= 1024 ) ++m_nTXSpeedHistogram1024;
688 	else ++m_nTXSpeedHistogramMax;
689 
690 	int nRXKBs = ( m_nRXSpeed + 512 ) / 1024;
691 	m_RXSpeedSample.AddSample( nRXKBs );
692 
693 	if ( nRXKBs <= 16 ) ++m_nRXSpeedHistogram16;
694 	else if ( nRXKBs <= 32 ) ++m_nRXSpeedHistogram32;
695 	else if ( nRXKBs <= 64 ) ++m_nRXSpeedHistogram64;
696 	else if ( nRXKBs <= 128 ) ++m_nRXSpeedHistogram128;
697 	else if ( nRXKBs <= 256 ) ++m_nRXSpeedHistogram256;
698 	else if ( nRXKBs <= 512 ) ++m_nRXSpeedHistogram512;
699 	else if ( nRXKBs <= 1024 ) ++m_nRXSpeedHistogram1024;
700 	else ++m_nRXSpeedHistogramMax;
701 
702 	// Reset for next time
703 	StartNextSpeedInterval( usecNow );
704 }
705 
UpdateSpeeds(int nTXSpeed,int nRXSpeed)706 void LinkStatsTrackerEndToEnd::UpdateSpeeds( int nTXSpeed, int nRXSpeed )
707 {
708 	m_nTXSpeed = nTXSpeed;
709 	m_nRXSpeed = nRXSpeed;
710 
711 	m_nTXSpeedMax = Max( m_nTXSpeedMax, nTXSpeed );
712 	m_nRXSpeedMax = Max( m_nRXSpeedMax, nRXSpeed );
713 }
714 
GetLifetimeStats(SteamDatagramLinkLifetimeStats & s) const715 void LinkStatsTrackerEndToEnd::GetLifetimeStats( SteamDatagramLinkLifetimeStats &s ) const
716 {
717 	LinkStatsTrackerBase::GetLifetimeStats(s);
718 
719 	if ( m_usecWhenStartedConnectedState == 0 || m_usecWhenStartedConnectedState == m_usecWhenEndedConnectedState )
720 	{
721 		s.m_nConnectedSeconds = 0;
722 	}
723 	else
724 	{
725 		SteamNetworkingMicroseconds usecWhenEnded = m_usecWhenEndedConnectedState ? m_usecWhenEndedConnectedState : SteamNetworkingSockets_GetLocalTimestamp();
726 		s.m_nConnectedSeconds = std::max( k_nMillion, usecWhenEnded - m_usecWhenStartedConnectedState + 500000 ) / k_nMillion;
727 	}
728 
729 	s.m_nTXSpeedMax           = m_nTXSpeedMax;
730 
731 	s.m_nTXSpeedHistogram16   = m_nTXSpeedHistogram16;
732 	s.m_nTXSpeedHistogram32   = m_nTXSpeedHistogram32;
733 	s.m_nTXSpeedHistogram64   = m_nTXSpeedHistogram64;
734 	s.m_nTXSpeedHistogram128  = m_nTXSpeedHistogram128;
735 	s.m_nTXSpeedHistogram256  = m_nTXSpeedHistogram256;
736 	s.m_nTXSpeedHistogram512  = m_nTXSpeedHistogram512;
737 	s.m_nTXSpeedHistogram1024 = m_nTXSpeedHistogram1024;
738 	s.m_nTXSpeedHistogramMax  = m_nTXSpeedHistogramMax;
739 
740 	s.m_nTXSpeedNtile5th  = m_TXSpeedSample.NumSamples() < 20 ? -1 : m_TXSpeedSample.GetPercentile( .05f );
741 	s.m_nTXSpeedNtile50th = m_TXSpeedSample.NumSamples() <  2 ? -1 : m_TXSpeedSample.GetPercentile( .50f );
742 	s.m_nTXSpeedNtile75th = m_TXSpeedSample.NumSamples() <  4 ? -1 : m_TXSpeedSample.GetPercentile( .75f );
743 	s.m_nTXSpeedNtile95th = m_TXSpeedSample.NumSamples() < 20 ? -1 : m_TXSpeedSample.GetPercentile( .95f );
744 	s.m_nTXSpeedNtile98th = m_TXSpeedSample.NumSamples() < 50 ? -1 : m_TXSpeedSample.GetPercentile( .98f );
745 
746 	s.m_nRXSpeedMax           = m_nRXSpeedMax;
747 
748 	s.m_nRXSpeedHistogram16   = m_nRXSpeedHistogram16;
749 	s.m_nRXSpeedHistogram32   = m_nRXSpeedHistogram32;
750 	s.m_nRXSpeedHistogram64   = m_nRXSpeedHistogram64;
751 	s.m_nRXSpeedHistogram128  = m_nRXSpeedHistogram128;
752 	s.m_nRXSpeedHistogram256  = m_nRXSpeedHistogram256;
753 	s.m_nRXSpeedHistogram512  = m_nRXSpeedHistogram512;
754 	s.m_nRXSpeedHistogram1024 = m_nRXSpeedHistogram1024;
755 	s.m_nRXSpeedHistogramMax  = m_nRXSpeedHistogramMax;
756 
757 	s.m_nRXSpeedNtile5th  = m_RXSpeedSample.NumSamples() < 20 ? -1 : m_RXSpeedSample.GetPercentile( .05f );
758 	s.m_nRXSpeedNtile50th = m_RXSpeedSample.NumSamples() <  2 ? -1 : m_RXSpeedSample.GetPercentile( .50f );
759 	s.m_nRXSpeedNtile75th = m_RXSpeedSample.NumSamples() <  4 ? -1 : m_RXSpeedSample.GetPercentile( .75f );
760 	s.m_nRXSpeedNtile95th = m_RXSpeedSample.NumSamples() < 20 ? -1 : m_RXSpeedSample.GetPercentile( .95f );
761 	s.m_nRXSpeedNtile98th = m_RXSpeedSample.NumSamples() < 50 ? -1 : m_RXSpeedSample.GetPercentile( .98f );
762 }
763 
764 namespace SteamNetworkingSocketsLib
765 {
766 
LinkStatsInstantaneousStructToMsg(const SteamDatagramLinkInstantaneousStats & s,CMsgSteamDatagramLinkInstantaneousStats & msg)767 void LinkStatsInstantaneousStructToMsg( const SteamDatagramLinkInstantaneousStats &s, CMsgSteamDatagramLinkInstantaneousStats &msg )
768 {
769 	msg.set_out_packets_per_sec_x10( uint32( s.m_flOutPacketsPerSec * 10.0f ) );
770 	msg.set_out_bytes_per_sec( uint32( s.m_flOutBytesPerSec ) );
771 	msg.set_in_packets_per_sec_x10( uint32( s.m_flInPacketsPerSec * 10.0f ) );
772 	msg.set_in_bytes_per_sec( uint32( s.m_flInBytesPerSec ) );
773 	if ( s.m_nPingMS >= 0 )
774 		msg.set_ping_ms( uint32( s.m_nPingMS ) );
775 	if ( s.m_flPacketsDroppedPct >= 0.0f )
776 		msg.set_packets_dropped_pct( uint32( s.m_flPacketsDroppedPct * 100.0f ) );
777 	if ( s.m_flPacketsWeirdSequenceNumberPct >= 0.0f )
778 		msg.set_packets_weird_sequence_pct( uint32( s.m_flPacketsWeirdSequenceNumberPct * 100.0f ) );
779 	if ( s.m_usecMaxJitter >= 0 )
780 		msg.set_peak_jitter_usec( s.m_usecMaxJitter );
781 }
782 
LinkStatsInstantaneousMsgToStruct(const CMsgSteamDatagramLinkInstantaneousStats & msg,SteamDatagramLinkInstantaneousStats & s)783 void LinkStatsInstantaneousMsgToStruct( const CMsgSteamDatagramLinkInstantaneousStats &msg, SteamDatagramLinkInstantaneousStats &s )
784 {
785 	s.m_flOutPacketsPerSec = msg.out_packets_per_sec_x10() * .1f;
786 	s.m_flOutBytesPerSec = msg.out_bytes_per_sec();
787 	s.m_flInPacketsPerSec = msg.in_packets_per_sec_x10() * .1f;
788 	s.m_flInBytesPerSec = msg.in_bytes_per_sec();
789 	if ( msg.has_ping_ms() )
790 		s.m_nPingMS = msg.ping_ms();
791 	else
792 		s.m_nPingMS = -1;
793 
794 	if ( msg.has_packets_dropped_pct() )
795 		s.m_flPacketsDroppedPct = msg.packets_dropped_pct() * .01f;
796 	else
797 		s.m_flPacketsDroppedPct = -1.0f;
798 
799 	if ( msg.has_packets_weird_sequence_pct() )
800 		s.m_flPacketsWeirdSequenceNumberPct = msg.packets_weird_sequence_pct() * .01f;
801 	else
802 		s.m_flPacketsWeirdSequenceNumberPct = -1.0f;
803 
804 	if ( msg.has_peak_jitter_usec() )
805 		s.m_usecMaxJitter = msg.peak_jitter_usec();
806 	else
807 		s.m_usecMaxJitter = -1;
808 
809 }
810 
LinkStatsLifetimeStructToMsg(const SteamDatagramLinkLifetimeStats & s,CMsgSteamDatagramLinkLifetimeStats & msg)811 void LinkStatsLifetimeStructToMsg( const SteamDatagramLinkLifetimeStats &s, CMsgSteamDatagramLinkLifetimeStats &msg )
812 {
813 	if ( s.m_nConnectedSeconds >= 0 )
814 		msg.set_connected_seconds( s.m_nConnectedSeconds );
815 
816 	msg.set_packets_sent( s.m_nPacketsSent );
817 	msg.set_kb_sent( ( s.m_nBytesSent + 512 ) / 1024 );
818 	msg.set_packets_recv( s.m_nPacketsRecv );
819 	msg.set_kb_recv( ( s.m_nBytesRecv + 512 ) / 1024 );
820 	msg.set_packets_recv_sequenced( s.m_nPktsRecvSequenced );
821 	msg.set_packets_recv_dropped( s.m_nPktsRecvDropped );
822 	msg.set_packets_recv_out_of_order( s.m_nPktsRecvOutOfOrder );
823 	msg.set_packets_recv_duplicate( s.m_nPktsRecvDuplicate );
824 	msg.set_packets_recv_lurch( s.m_nPktsRecvSequenceNumberLurch );
825 
826 	#define SET_HISTOGRAM( mbr, field ) if ( mbr > 0 ) msg.set_ ## field( mbr );
827 	#define SET_NTILE( mbr, field ) if ( mbr >= 0 ) msg.set_ ## field( mbr );
828 
829 	SET_HISTOGRAM( s.m_qualityHistogram.m_n100 , quality_histogram_100  )
830 	SET_HISTOGRAM( s.m_qualityHistogram.m_n99  , quality_histogram_99   )
831 	SET_HISTOGRAM( s.m_qualityHistogram.m_n97  , quality_histogram_97   )
832 	SET_HISTOGRAM( s.m_qualityHistogram.m_n95  , quality_histogram_95   )
833 	SET_HISTOGRAM( s.m_qualityHistogram.m_n90  , quality_histogram_90   )
834 	SET_HISTOGRAM( s.m_qualityHistogram.m_n75  , quality_histogram_75   )
835 	SET_HISTOGRAM( s.m_qualityHistogram.m_n50  , quality_histogram_50   )
836 	SET_HISTOGRAM( s.m_qualityHistogram.m_n1   , quality_histogram_1    )
837 	SET_HISTOGRAM( s.m_qualityHistogram.m_nDead, quality_histogram_dead )
838 
839 	SET_NTILE( s.m_nQualityNtile50th, quality_ntile_50th )
840 	SET_NTILE( s.m_nQualityNtile25th, quality_ntile_25th )
841 	SET_NTILE( s.m_nQualityNtile5th , quality_ntile_5th  )
842 	SET_NTILE( s.m_nQualityNtile2nd , quality_ntile_2nd  )
843 
844 	SET_HISTOGRAM( s.m_pingHistogram.m_n25 , ping_histogram_25  )
845 	SET_HISTOGRAM( s.m_pingHistogram.m_n50 , ping_histogram_50  )
846 	SET_HISTOGRAM( s.m_pingHistogram.m_n75 , ping_histogram_75  )
847 	SET_HISTOGRAM( s.m_pingHistogram.m_n100, ping_histogram_100 )
848 	SET_HISTOGRAM( s.m_pingHistogram.m_n125, ping_histogram_125 )
849 	SET_HISTOGRAM( s.m_pingHistogram.m_n150, ping_histogram_150 )
850 	SET_HISTOGRAM( s.m_pingHistogram.m_n200, ping_histogram_200 )
851 	SET_HISTOGRAM( s.m_pingHistogram.m_n300, ping_histogram_300 )
852 	SET_HISTOGRAM( s.m_pingHistogram.m_nMax, ping_histogram_max )
853 
854 	SET_NTILE( s.m_nPingNtile5th , ping_ntile_5th  )
855 	SET_NTILE( s.m_nPingNtile50th, ping_ntile_50th )
856 	SET_NTILE( s.m_nPingNtile75th, ping_ntile_75th )
857 	SET_NTILE( s.m_nPingNtile95th, ping_ntile_95th )
858 	SET_NTILE( s.m_nPingNtile98th, ping_ntile_98th )
859 
860 	SET_HISTOGRAM( s.m_jitterHistogram.m_nNegligible, jitter_histogram_negligible )
861 	SET_HISTOGRAM( s.m_jitterHistogram.m_n1,  jitter_histogram_1  )
862 	SET_HISTOGRAM( s.m_jitterHistogram.m_n2,  jitter_histogram_2  )
863 	SET_HISTOGRAM( s.m_jitterHistogram.m_n5,  jitter_histogram_5  )
864 	SET_HISTOGRAM( s.m_jitterHistogram.m_n10, jitter_histogram_10 )
865 	SET_HISTOGRAM( s.m_jitterHistogram.m_n20, jitter_histogram_20 )
866 
867 	if ( s.m_nTXSpeedMax > 0 )
868 		msg.set_txspeed_max( s.m_nTXSpeedMax );
869 
870 	SET_HISTOGRAM( s.m_nTXSpeedHistogram16,   txspeed_histogram_16   )
871 	SET_HISTOGRAM( s.m_nTXSpeedHistogram32,   txspeed_histogram_32   )
872 	SET_HISTOGRAM( s.m_nTXSpeedHistogram64,   txspeed_histogram_64   )
873 	SET_HISTOGRAM( s.m_nTXSpeedHistogram128,  txspeed_histogram_128  )
874 	SET_HISTOGRAM( s.m_nTXSpeedHistogram256,  txspeed_histogram_256  )
875 	SET_HISTOGRAM( s.m_nTXSpeedHistogram512,  txspeed_histogram_512  )
876 	SET_HISTOGRAM( s.m_nTXSpeedHistogram1024, txspeed_histogram_1024 )
877 	SET_HISTOGRAM( s.m_nTXSpeedHistogramMax,  txspeed_histogram_max  )
878 
879 	SET_NTILE( s.m_nTXSpeedNtile5th,  txspeed_ntile_5th  )
880 	SET_NTILE( s.m_nTXSpeedNtile50th, txspeed_ntile_50th )
881 	SET_NTILE( s.m_nTXSpeedNtile75th, txspeed_ntile_75th )
882 	SET_NTILE( s.m_nTXSpeedNtile95th, txspeed_ntile_95th )
883 	SET_NTILE( s.m_nTXSpeedNtile98th, txspeed_ntile_98th )
884 
885 	if ( s.m_nRXSpeedMax > 0 )
886 		msg.set_rxspeed_max( s.m_nRXSpeedMax );
887 
888 	SET_HISTOGRAM( s.m_nRXSpeedHistogram16,   rxspeed_histogram_16   )
889 	SET_HISTOGRAM( s.m_nRXSpeedHistogram32,   rxspeed_histogram_32   )
890 	SET_HISTOGRAM( s.m_nRXSpeedHistogram64,   rxspeed_histogram_64   )
891 	SET_HISTOGRAM( s.m_nRXSpeedHistogram128,  rxspeed_histogram_128  )
892 	SET_HISTOGRAM( s.m_nRXSpeedHistogram256,  rxspeed_histogram_256  )
893 	SET_HISTOGRAM( s.m_nRXSpeedHistogram512,  rxspeed_histogram_512  )
894 	SET_HISTOGRAM( s.m_nRXSpeedHistogram1024, rxspeed_histogram_1024 )
895 	SET_HISTOGRAM( s.m_nRXSpeedHistogramMax,  rxspeed_histogram_max  )
896 
897 	SET_NTILE( s.m_nRXSpeedNtile5th,  rxspeed_ntile_5th  )
898 	SET_NTILE( s.m_nRXSpeedNtile50th, rxspeed_ntile_50th )
899 	SET_NTILE( s.m_nRXSpeedNtile75th, rxspeed_ntile_75th )
900 	SET_NTILE( s.m_nRXSpeedNtile95th, rxspeed_ntile_95th )
901 	SET_NTILE( s.m_nRXSpeedNtile98th, rxspeed_ntile_98th )
902 
903 	#undef SET_HISTOGRAM
904 	#undef SET_NTILE
905 }
906 
LinkStatsLifetimeMsgToStruct(const CMsgSteamDatagramLinkLifetimeStats & msg,SteamDatagramLinkLifetimeStats & s)907 void LinkStatsLifetimeMsgToStruct( const CMsgSteamDatagramLinkLifetimeStats &msg, SteamDatagramLinkLifetimeStats &s )
908 {
909 	s.m_nConnectedSeconds = msg.has_connected_seconds() ? msg.connected_seconds() : -1;
910 	s.m_nPacketsSent = msg.packets_sent();
911 	s.m_nBytesSent = msg.kb_sent() * 1024;
912 	s.m_nPacketsRecv = msg.packets_recv();
913 	s.m_nBytesRecv = msg.kb_recv() * 1024;
914 	s.m_nPktsRecvSequenced = msg.packets_recv_sequenced();
915 	s.m_nPktsRecvDropped = msg.packets_recv_dropped();
916 	s.m_nPktsRecvOutOfOrder = msg.packets_recv_out_of_order();
917 	s.m_nPktsRecvDuplicate = msg.packets_recv_duplicate();
918 	s.m_nPktsRecvSequenceNumberLurch = msg.packets_recv_lurch();
919 
920 	#define SET_HISTOGRAM( mbr, field ) mbr = msg.field();
921 	#define SET_NTILE( mbr, field ) mbr = ( msg.has_ ## field() ? msg.field() : -1 );
922 
923 	SET_HISTOGRAM( s.m_qualityHistogram.m_n100 , quality_histogram_100  )
924 	SET_HISTOGRAM( s.m_qualityHistogram.m_n99  , quality_histogram_99   )
925 	SET_HISTOGRAM( s.m_qualityHistogram.m_n97  , quality_histogram_97   )
926 	SET_HISTOGRAM( s.m_qualityHistogram.m_n95  , quality_histogram_95   )
927 	SET_HISTOGRAM( s.m_qualityHistogram.m_n90  , quality_histogram_90   )
928 	SET_HISTOGRAM( s.m_qualityHistogram.m_n75  , quality_histogram_75   )
929 	SET_HISTOGRAM( s.m_qualityHistogram.m_n50  , quality_histogram_50   )
930 	SET_HISTOGRAM( s.m_qualityHistogram.m_n1   , quality_histogram_1    )
931 	SET_HISTOGRAM( s.m_qualityHistogram.m_nDead, quality_histogram_dead )
932 
933 	SET_NTILE( s.m_nQualityNtile50th, quality_ntile_50th )
934 	SET_NTILE( s.m_nQualityNtile25th, quality_ntile_25th )
935 	SET_NTILE( s.m_nQualityNtile5th , quality_ntile_5th  )
936 	SET_NTILE( s.m_nQualityNtile2nd , quality_ntile_2nd  )
937 
938 	SET_HISTOGRAM( s.m_pingHistogram.m_n25 , ping_histogram_25  )
939 	SET_HISTOGRAM( s.m_pingHistogram.m_n50 , ping_histogram_50  )
940 	SET_HISTOGRAM( s.m_pingHistogram.m_n75 , ping_histogram_75  )
941 	SET_HISTOGRAM( s.m_pingHistogram.m_n100, ping_histogram_100 )
942 	SET_HISTOGRAM( s.m_pingHistogram.m_n125, ping_histogram_125 )
943 	SET_HISTOGRAM( s.m_pingHistogram.m_n150, ping_histogram_150 )
944 	SET_HISTOGRAM( s.m_pingHistogram.m_n200, ping_histogram_200 )
945 	SET_HISTOGRAM( s.m_pingHistogram.m_n300, ping_histogram_300 )
946 	SET_HISTOGRAM( s.m_pingHistogram.m_nMax, ping_histogram_max )
947 
948 	SET_NTILE( s.m_nPingNtile5th , ping_ntile_5th  )
949 	SET_NTILE( s.m_nPingNtile50th, ping_ntile_50th )
950 	SET_NTILE( s.m_nPingNtile75th, ping_ntile_75th )
951 	SET_NTILE( s.m_nPingNtile95th, ping_ntile_95th )
952 	SET_NTILE( s.m_nPingNtile98th, ping_ntile_98th )
953 
954 	SET_HISTOGRAM( s.m_jitterHistogram.m_nNegligible, jitter_histogram_negligible )
955 	SET_HISTOGRAM( s.m_jitterHistogram.m_n1,  jitter_histogram_1  )
956 	SET_HISTOGRAM( s.m_jitterHistogram.m_n2,  jitter_histogram_2  )
957 	SET_HISTOGRAM( s.m_jitterHistogram.m_n5,  jitter_histogram_5  )
958 	SET_HISTOGRAM( s.m_jitterHistogram.m_n10, jitter_histogram_10 )
959 	SET_HISTOGRAM( s.m_jitterHistogram.m_n20, jitter_histogram_20 )
960 
961 	s.m_nTXSpeedMax = msg.txspeed_max();
962 
963 	SET_HISTOGRAM( s.m_nTXSpeedHistogram16,   txspeed_histogram_16   )
964 	SET_HISTOGRAM( s.m_nTXSpeedHistogram32,   txspeed_histogram_32   )
965 	SET_HISTOGRAM( s.m_nTXSpeedHistogram64,   txspeed_histogram_64   )
966 	SET_HISTOGRAM( s.m_nTXSpeedHistogram128,  txspeed_histogram_128  )
967 	SET_HISTOGRAM( s.m_nTXSpeedHistogram256,  txspeed_histogram_256  )
968 	SET_HISTOGRAM( s.m_nTXSpeedHistogram512,  txspeed_histogram_512  )
969 	SET_HISTOGRAM( s.m_nTXSpeedHistogram1024, txspeed_histogram_1024 )
970 	SET_HISTOGRAM( s.m_nTXSpeedHistogramMax,  txspeed_histogram_max  )
971 
972 	SET_NTILE( s.m_nTXSpeedNtile5th,  txspeed_ntile_5th  )
973 	SET_NTILE( s.m_nTXSpeedNtile50th, txspeed_ntile_50th )
974 	SET_NTILE( s.m_nTXSpeedNtile75th, txspeed_ntile_75th )
975 	SET_NTILE( s.m_nTXSpeedNtile95th, txspeed_ntile_95th )
976 	SET_NTILE( s.m_nTXSpeedNtile98th, txspeed_ntile_98th )
977 
978 	s.m_nRXSpeedMax = msg.rxspeed_max();
979 
980 	SET_HISTOGRAM( s.m_nRXSpeedHistogram16,   rxspeed_histogram_16   )
981 	SET_HISTOGRAM( s.m_nRXSpeedHistogram32,   rxspeed_histogram_32   )
982 	SET_HISTOGRAM( s.m_nRXSpeedHistogram64,   rxspeed_histogram_64   )
983 	SET_HISTOGRAM( s.m_nRXSpeedHistogram128,  rxspeed_histogram_128  )
984 	SET_HISTOGRAM( s.m_nRXSpeedHistogram256,  rxspeed_histogram_256  )
985 	SET_HISTOGRAM( s.m_nRXSpeedHistogram512,  rxspeed_histogram_512  )
986 	SET_HISTOGRAM( s.m_nRXSpeedHistogram1024, rxspeed_histogram_1024 )
987 	SET_HISTOGRAM( s.m_nRXSpeedHistogramMax,  rxspeed_histogram_max  )
988 
989 	SET_NTILE( s.m_nRXSpeedNtile5th,  rxspeed_ntile_5th  )
990 	SET_NTILE( s.m_nRXSpeedNtile50th, rxspeed_ntile_50th )
991 	SET_NTILE( s.m_nRXSpeedNtile75th, rxspeed_ntile_75th )
992 	SET_NTILE( s.m_nRXSpeedNtile95th, rxspeed_ntile_95th )
993 	SET_NTILE( s.m_nRXSpeedNtile98th, rxspeed_ntile_98th )
994 
995 	#undef SET_HISTOGRAM
996 	#undef SET_NTILE
997 }
998 
PrintPct(char (& szBuf)[32],float flPct)999 static void PrintPct( char (&szBuf)[32], float flPct )
1000 {
1001 	flPct *= 100.0f;
1002 	if ( flPct < 0.0f )
1003 		V_strcpy_safe( szBuf, "???" );
1004 	else if ( flPct < 9.5f )
1005 		V_sprintf_safe( szBuf, "%.2f", flPct );
1006 	else if ( flPct < 99.5f )
1007 		V_sprintf_safe( szBuf, "%.1f", flPct );
1008 	else
1009 		V_sprintf_safe( szBuf, "%.0f", flPct );
1010 }
1011 
LinkStatsPrintInstantaneousToBuf(const char * pszLeader,const SteamDatagramLinkInstantaneousStats & stats,CUtlBuffer & buf)1012 void LinkStatsPrintInstantaneousToBuf( const char *pszLeader, const SteamDatagramLinkInstantaneousStats &stats, CUtlBuffer &buf )
1013 {
1014 	buf.Printf( "%sSent:%6.1f pkts/sec%6.1f K/sec\n", pszLeader, stats.m_flOutPacketsPerSec, stats.m_flOutBytesPerSec/1024.0f );
1015 	buf.Printf( "%sRecv:%6.1f pkts/sec%6.1f K/sec\n", pszLeader, stats.m_flInPacketsPerSec, stats.m_flInBytesPerSec/1024.0f );
1016 
1017 	if ( stats.m_nPingMS >= 0 || stats.m_usecMaxJitter >= 0 )
1018 	{
1019 		char szPing[ 32 ];
1020 		if ( stats.m_nPingMS < 0 )
1021 			V_strcpy_safe( szPing, "???" );
1022 		else
1023 			V_sprintf_safe( szPing, "%d", stats.m_nPingMS );
1024 
1025 		char szPeakJitter[ 32 ];
1026 		if ( stats.m_usecMaxJitter < 0 )
1027 			V_strcpy_safe( szPeakJitter, "???" );
1028 		else
1029 			V_sprintf_safe( szPeakJitter, "%.1f", stats.m_usecMaxJitter*1e-3f );
1030 
1031 		buf.Printf( "%sPing:%sms    Max latency variance: %sms\n", pszLeader, szPing, szPeakJitter );
1032 	}
1033 
1034 	if ( stats.m_flPacketsDroppedPct >= 0.0f && stats.m_flPacketsWeirdSequenceNumberPct >= 0.0f )
1035 	{
1036 		char szDropped[ 32 ];
1037 		PrintPct( szDropped, stats.m_flPacketsDroppedPct );
1038 
1039 		char szWeirdSeq[ 32 ];
1040 		PrintPct( szWeirdSeq, stats.m_flPacketsWeirdSequenceNumberPct );
1041 
1042 		char szQuality[32];
1043 		PrintPct( szQuality, 1.0f - stats.m_flPacketsDroppedPct - stats.m_flPacketsWeirdSequenceNumberPct );
1044 		buf.Printf( "%sQuality:%5s%%  (Dropped:%4s%%  WeirdSeq:%4s%%)\n", pszLeader, szQuality, szDropped, szWeirdSeq);
1045 	}
1046 
1047 	if ( stats.m_nSendRate > 0 )
1048 		buf.Printf( "%sEst avail bandwidth: %.1fKB/s  \n", pszLeader, stats.m_nSendRate/1024.0f );
1049 	if ( stats.m_nPendingBytes >= 0 )
1050 		buf.Printf( "%sBytes buffered: %s\n", pszLeader, NumberPrettyPrinter( stats.m_nPendingBytes ).String() );
1051 }
1052 
LinkStatsPrintLifetimeToBuf(const char * pszLeader,const SteamDatagramLinkLifetimeStats & stats,CUtlBuffer & buf)1053 void LinkStatsPrintLifetimeToBuf( const char *pszLeader, const SteamDatagramLinkLifetimeStats &stats, CUtlBuffer &buf )
1054 {
1055 	char temp1[256];
1056 	char temp2[256];
1057 	char num[64];
1058 
1059 	buf.Printf( "%sTotals\n", pszLeader );
1060 	buf.Printf( "%s    Sent:%11s pkts %15s bytes\n", pszLeader, NumberPrettyPrinter( stats.m_nPacketsSent ).String(), NumberPrettyPrinter( stats.m_nBytesSent ).String() );
1061 	buf.Printf( "%s    Recv:%11s pkts %15s bytes\n", pszLeader, NumberPrettyPrinter( stats.m_nPacketsRecv ).String(), NumberPrettyPrinter( stats.m_nBytesRecv ).String() );
1062 	if ( stats.m_nPktsRecvSequenced > 0 )
1063 	{
1064 		buf.Printf( "%s    Recv w seq:%11s pkts\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvSequenced ).String() );
1065 		float flToPct = 100.0f / ( stats.m_nPktsRecvSequenced + stats.m_nPktsRecvDropped );
1066 		buf.Printf( "%s    Dropped   :%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvDropped ).String(), stats.m_nPktsRecvDropped * flToPct );
1067 		buf.Printf( "%s    OutOfOrder:%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvOutOfOrder ).String(), stats.m_nPktsRecvOutOfOrder * flToPct );
1068 		buf.Printf( "%s    Duplicate :%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvDuplicate ).String(), stats.m_nPktsRecvDuplicate * flToPct );
1069 		buf.Printf( "%s    SeqLurch  :%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvSequenceNumberLurch ).String(), stats.m_nPktsRecvSequenceNumberLurch * flToPct );
1070 	}
1071 
1072 	// Do we have enough ping samples such that the distribution might be interesting
1073 	{
1074 		int nPingSamples = stats.m_pingHistogram.TotalCount();
1075 		if ( nPingSamples >= 5 )
1076 		{
1077 			float flToPct = 100.0f / nPingSamples;
1078 			buf.Printf( "%sPing histogram: (%d total samples)\n", pszLeader, nPingSamples );
1079 
1080 			buf.Printf( "%s         0-25    25-50    50-75   75-100  100-125  125-150  150-200  200-300     300+\n", pszLeader );
1081 			buf.Printf( "%s    %9d%9d%9d%9d%9d%9d%9d%9d%9d\n",
1082 				pszLeader,
1083 				stats.m_pingHistogram.m_n25,
1084 				stats.m_pingHistogram.m_n50,
1085 				stats.m_pingHistogram.m_n75,
1086 				stats.m_pingHistogram.m_n100,
1087 				stats.m_pingHistogram.m_n125,
1088 				stats.m_pingHistogram.m_n150,
1089 				stats.m_pingHistogram.m_n200,
1090 				stats.m_pingHistogram.m_n300,
1091 				stats.m_pingHistogram.m_nMax );
1092 			buf.Printf( "%s    %8.1f%%%8.1f%%%8.1f%%%8.1f%%%8.1f%%%8.1f%%%8.1f%%%8.1f%%%8.1f%%\n",
1093 				pszLeader,
1094 				stats.m_pingHistogram.m_n25 *flToPct,
1095 				stats.m_pingHistogram.m_n50 *flToPct,
1096 				stats.m_pingHistogram.m_n75 *flToPct,
1097 				stats.m_pingHistogram.m_n100*flToPct,
1098 				stats.m_pingHistogram.m_n125*flToPct,
1099 				stats.m_pingHistogram.m_n150*flToPct,
1100 				stats.m_pingHistogram.m_n200*flToPct,
1101 				stats.m_pingHistogram.m_n300*flToPct,
1102 				stats.m_pingHistogram.m_nMax*flToPct );
1103 			temp1[0] = '\0';
1104 			temp2[0] = '\0';
1105 
1106 			#define PING_NTILE( ntile, val ) \
1107 				if ( val >= 0 ) \
1108 				{ \
1109 					V_sprintf_safe( num, "%7s", ntile ); V_strcat_safe( temp1, num ); \
1110 					V_sprintf_safe( num, "%5dms", val ); V_strcat_safe( temp2, num ); \
1111 				}
1112 
1113 			PING_NTILE( "5th", stats.m_nPingNtile5th )
1114 			PING_NTILE( "50th", stats.m_nPingNtile50th );
1115 			PING_NTILE( "75th", stats.m_nPingNtile75th );
1116 			PING_NTILE( "95th", stats.m_nPingNtile95th );
1117 			PING_NTILE( "98th", stats.m_nPingNtile98th );
1118 
1119 			#undef PING_NTILE
1120 
1121 			if ( temp1[0] != '\0' )
1122 			{
1123 				buf.Printf( "%sPing distribution:\n", pszLeader );
1124 				buf.Printf( "%s%s\n", pszLeader, temp1 );
1125 				buf.Printf( "%s%s\n", pszLeader, temp2 );
1126 			}
1127 		}
1128 		else
1129 		{
1130 			buf.Printf( "%sNo ping distribution available.  (%d samples)\n", pszLeader, nPingSamples );
1131 		}
1132 	}
1133 
1134 	// Do we have enough quality samples such that the distribution might be interesting?
1135 	{
1136 		int nQualitySamples = stats.m_qualityHistogram.TotalCount();
1137 		if ( nQualitySamples >= 5 )
1138 		{
1139 			float flToPct = 100.0f / nQualitySamples;
1140 
1141 			buf.Printf( "%sConnection quality histogram: (%d measurement intervals)\n", pszLeader, nQualitySamples );
1142 
1143 			buf.Printf( "%s    perfect    99+  97-99  95-97  90-95  75-90  50-75    <50   dead\n", pszLeader );
1144 			buf.Printf( "%s    %7d%7d%7d%7d%7d%7d%7d%7d%7d\n",
1145 				pszLeader,
1146 				stats.m_qualityHistogram.m_n100,
1147 				stats.m_qualityHistogram.m_n99,
1148 				stats.m_qualityHistogram.m_n97,
1149 				stats.m_qualityHistogram.m_n95,
1150 				stats.m_qualityHistogram.m_n90,
1151 				stats.m_qualityHistogram.m_n75,
1152 				stats.m_qualityHistogram.m_n50,
1153 				stats.m_qualityHistogram.m_n1,
1154 				stats.m_qualityHistogram.m_nDead
1155 			);
1156 			buf.Printf( "%s    %6.1f%%%6.1f%%%6.1f%%%6.1f%%%6.1f%%%6.1f%%%6.1f%%%6.1f%%%6.1f%%\n",
1157 				pszLeader,
1158 				stats.m_qualityHistogram.m_n100 *flToPct,
1159 				stats.m_qualityHistogram.m_n99  *flToPct,
1160 				stats.m_qualityHistogram.m_n97  *flToPct,
1161 				stats.m_qualityHistogram.m_n95  *flToPct,
1162 				stats.m_qualityHistogram.m_n90  *flToPct,
1163 				stats.m_qualityHistogram.m_n75  *flToPct,
1164 				stats.m_qualityHistogram.m_n50  *flToPct,
1165 				stats.m_qualityHistogram.m_n1   *flToPct,
1166 				stats.m_qualityHistogram.m_nDead*flToPct
1167 			);
1168 
1169 			temp1[0] = '\0';
1170 			temp2[0] = '\0';
1171 
1172 			#define QUALITY_NTILE( ntile, val ) \
1173 				if ( val >= 0 ) \
1174 				{ \
1175 					V_sprintf_safe( num, "%6s", ntile ); V_strcat_safe( temp1, num ); \
1176 					V_sprintf_safe( num, "%5d%%", val ); V_strcat_safe( temp2, num ); \
1177 				}
1178 
1179 			QUALITY_NTILE( "50th", stats.m_nQualityNtile50th );
1180 			QUALITY_NTILE( "25th", stats.m_nQualityNtile25th );
1181 			QUALITY_NTILE(  "5th", stats.m_nQualityNtile5th  );
1182 			QUALITY_NTILE(  "2nd", stats.m_nQualityNtile2nd  );
1183 
1184 			#undef QUALITY_NTILE
1185 
1186 			if ( temp1[0] != '\0' )
1187 			{
1188 				buf.Printf( "%sConnection quality distribution:\n", pszLeader );
1189 				buf.Printf( "%s%s\n", pszLeader, temp1 );
1190 				buf.Printf( "%s%s\n", pszLeader, temp2 );
1191 			}
1192 		}
1193 		else
1194 		{
1195 			buf.Printf( "%sNo connection quality distribution available.  (%d measurement intervals)\n", pszLeader, nQualitySamples );
1196 		}
1197 	}
1198 
1199 	// Do we have any jitter samples?
1200 	{
1201 		int nJitterSamples = stats.m_jitterHistogram.TotalCount();
1202 		if ( nJitterSamples >= 1 )
1203 		{
1204 			float flToPct = 100.0f / nJitterSamples;
1205 
1206 			buf.Printf( "%sLatency variance histogram: (%d total measurements)\n", pszLeader, nJitterSamples );
1207 			buf.Printf( "%s          <1     1-2     2-5    5-10   10-20     >20\n", pszLeader );
1208 			buf.Printf( "%s    %8d%8d%8d%8d%8d%8d\n", pszLeader,
1209 				stats.m_jitterHistogram.m_nNegligible,
1210 				stats.m_jitterHistogram.m_n1 ,
1211 				stats.m_jitterHistogram.m_n2 ,
1212 				stats.m_jitterHistogram.m_n5 ,
1213 				stats.m_jitterHistogram.m_n10,
1214 				stats.m_jitterHistogram.m_n20 );
1215 			buf.Printf( "%s    %7.1f%%%7.1f%%%7.1f%%%7.1f%%%7.1f%%%7.1f%%\n", pszLeader,
1216 				stats.m_jitterHistogram.m_nNegligible*flToPct,
1217 				stats.m_jitterHistogram.m_n1 *flToPct,
1218 				stats.m_jitterHistogram.m_n2 *flToPct,
1219 				stats.m_jitterHistogram.m_n5 *flToPct,
1220 				stats.m_jitterHistogram.m_n10*flToPct,
1221 				stats.m_jitterHistogram.m_n20*flToPct );
1222 		}
1223 		else
1224 		{
1225 			buf.Printf( "%sLatency variance histogram not available\n", pszLeader );
1226 		}
1227 	}
1228 
1229 	// This is all bogus right now, just don't print it
1230 	//// Do we have enough tx speed samples such that the distribution might be interesting?
1231 	//{
1232 	//	int nTXSpeedSamples = stats.TXSpeedHistogramTotalCount();
1233 	//	if ( nTXSpeedSamples >= 5 )
1234 	//	{
1235 	//		float flToPct = 100.0f / nTXSpeedSamples;
1236 	//		buf.Printf( "%sTX Speed histogram: (%d total samples)\n", pszLeader, nTXSpeedSamples );
1237 	//		buf.Printf( "%s     0 - 16 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram16,   stats.m_nTXSpeedHistogram16  *flToPct );
1238 	//		buf.Printf( "%s    16 - 32 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram32,   stats.m_nTXSpeedHistogram32  *flToPct );
1239 	//		buf.Printf( "%s    32 - 64 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram64,   stats.m_nTXSpeedHistogram64  *flToPct );
1240 	//		buf.Printf( "%s   64 - 128 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram128,  stats.m_nTXSpeedHistogram128 *flToPct );
1241 	//		buf.Printf( "%s  128 - 256 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram256,  stats.m_nTXSpeedHistogram256 *flToPct );
1242 	//		buf.Printf( "%s  256 - 512 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram512,  stats.m_nTXSpeedHistogram512 *flToPct );
1243 	//		buf.Printf( "%s 512 - 1024 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogram1024, stats.m_nTXSpeedHistogram1024*flToPct );
1244 	//		buf.Printf( "%s      1024+ KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nTXSpeedHistogramMax,  stats.m_nTXSpeedHistogramMax *flToPct );
1245 	//		buf.Printf( "%sTransmit speed distribution:\n", pszLeader );
1246  	//		if ( stats.m_nTXSpeedNtile5th  >= 0 ) buf.Printf( "%s     5%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile5th  );
1247 	//		if ( stats.m_nTXSpeedNtile50th >= 0 ) buf.Printf( "%s    50%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile50th );
1248 	//		if ( stats.m_nTXSpeedNtile75th >= 0 ) buf.Printf( "%s    75%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile75th );
1249 	//		if ( stats.m_nTXSpeedNtile95th >= 0 ) buf.Printf( "%s    95%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile95th );
1250 	//		if ( stats.m_nTXSpeedNtile98th >= 0 ) buf.Printf( "%s    98%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nTXSpeedNtile98th );
1251 	//	}
1252 	//	else
1253 	//	{
1254 	//		buf.Printf( "%sNo connection transmit speed distribution available.  (%d measurement intervals)\n", pszLeader, nTXSpeedSamples );
1255 	//	}
1256 	//}
1257 	//
1258 	//// Do we have enough RX speed samples such that the distribution might be interesting?
1259 	//{
1260 	//	int nRXSpeedSamples = stats.RXSpeedHistogramTotalCount();
1261 	//	if ( nRXSpeedSamples >= 5 )
1262 	//	{
1263 	//		float flToPct = 100.0f / nRXSpeedSamples;
1264 	//		buf.Printf( "%sRX Speed histogram: (%d total samples)\n", pszLeader, nRXSpeedSamples );
1265 	//		buf.Printf( "%s     0 - 16 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram16,   stats.m_nRXSpeedHistogram16  *flToPct );
1266 	//		buf.Printf( "%s    16 - 32 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram32,   stats.m_nRXSpeedHistogram32  *flToPct );
1267 	//		buf.Printf( "%s    32 - 64 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram64,   stats.m_nRXSpeedHistogram64  *flToPct );
1268 	//		buf.Printf( "%s   64 - 128 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram128,  stats.m_nRXSpeedHistogram128 *flToPct );
1269 	//		buf.Printf( "%s  128 - 256 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram256,  stats.m_nRXSpeedHistogram256 *flToPct );
1270 	//		buf.Printf( "%s  256 - 512 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram512,  stats.m_nRXSpeedHistogram512 *flToPct );
1271 	//		buf.Printf( "%s 512 - 1024 KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogram1024, stats.m_nRXSpeedHistogram1024*flToPct );
1272 	//		buf.Printf( "%s      1024+ KB/s:%5d  %3.0f%%\n", pszLeader, stats.m_nRXSpeedHistogramMax,  stats.m_nRXSpeedHistogramMax *flToPct );
1273 	//		buf.Printf( "%sReceive speed distribution:\n", pszLeader );
1274 	//		if ( stats.m_nRXSpeedNtile5th  >= 0 ) buf.Printf( "%s     5%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile5th  );
1275 	//		if ( stats.m_nRXSpeedNtile50th >= 0 ) buf.Printf( "%s    50%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile50th );
1276 	//		if ( stats.m_nRXSpeedNtile75th >= 0 ) buf.Printf( "%s    75%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile75th );
1277 	//		if ( stats.m_nRXSpeedNtile95th >= 0 ) buf.Printf( "%s    95%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile95th );
1278 	//		if ( stats.m_nRXSpeedNtile98th >= 0 ) buf.Printf( "%s    98%% of speeds <= %4d KB/s\n", pszLeader, stats.m_nRXSpeedNtile98th );
1279 	//	}
1280 	//	else
1281 	//	{
1282 	//		buf.Printf( "%sNo connection recieve speed distribution available.  (%d measurement intervals)\n", pszLeader, nRXSpeedSamples );
1283 	//	}
1284 	//}
1285 
1286 }
1287 
LinkStatsPrintToBuf(const char * pszLeader,const SteamDatagramLinkStats & stats,CUtlBuffer & buf)1288 void LinkStatsPrintToBuf( const char *pszLeader, const SteamDatagramLinkStats &stats, CUtlBuffer &buf )
1289 {
1290 	std::string sIndent( pszLeader ); sIndent.append( "    " );
1291 
1292 	buf.Printf( "%sCurrent rates:\n", pszLeader );
1293 	LinkStatsPrintInstantaneousToBuf( sIndent.c_str(), stats.m_latest, buf );
1294 	buf.Printf( "%sLifetime stats:\n", pszLeader );
1295 	LinkStatsPrintLifetimeToBuf( sIndent.c_str(), stats.m_lifetime, buf );
1296 
1297 	if ( stats.m_flAgeLatestRemote < 0.0f )
1298 	{
1299 		buf.Printf( "%sNo rate stats received from remote host\n", pszLeader );
1300 	}
1301 	else
1302 	{
1303 		buf.Printf( "%sRate stats received from remote host %.1fs ago:\n", pszLeader, stats.m_flAgeLatestRemote );
1304 		LinkStatsPrintInstantaneousToBuf( sIndent.c_str(), stats.m_latestRemote, buf );
1305 	}
1306 
1307 	if ( stats.m_flAgeLifetimeRemote < 0.0f )
1308 	{
1309 		buf.Printf( "%sNo lifetime stats received from remote host\n", pszLeader );
1310 	}
1311 	else
1312 	{
1313 		buf.Printf( "%sLifetime stats received from remote host %.1fs ago:\n", pszLeader, stats.m_flAgeLifetimeRemote );
1314 		LinkStatsPrintLifetimeToBuf( sIndent.c_str(), stats.m_lifetimeRemote, buf );
1315 	}
1316 }
1317 
1318 ///////////////////////////////////////////////////////////////////////////////
1319 //
1320 // SteamNetworkingDetailedConnectionStatus
1321 //
1322 ///////////////////////////////////////////////////////////////////////////////
1323 
Clear()1324 void SteamNetworkingDetailedConnectionStatus::Clear()
1325 {
1326 	V_memset( this, 0, sizeof(*this) );
1327 	COMPILE_TIME_ASSERT( k_ESteamNetworkingAvailability_Unknown == 0 );
1328 	m_statsEndToEnd.Clear();
1329 	m_statsPrimaryRouter.Clear();
1330 	m_nPrimaryRouterBackPing = -1;
1331 	m_nBackupRouterFrontPing = -1;
1332 	m_nBackupRouterBackPing = -1;
1333 }
1334 
Print(char * pszBuf,int cbBuf)1335 int SteamNetworkingDetailedConnectionStatus::Print( char *pszBuf, int cbBuf )
1336 {
1337 	CUtlBuffer buf( 0, 8*1024, CUtlBuffer::TEXT_BUFFER );
1338 
1339 	// If we don't have network, there's nothing else we can really do
1340 	if ( m_eAvailNetworkConfig != k_ESteamNetworkingAvailability_Current && m_eAvailNetworkConfig != k_ESteamNetworkingAvailability_Unknown )
1341 	{
1342 		buf.Printf( "Network configuration: %s\n", GetAvailabilityString( m_eAvailNetworkConfig ) );
1343 		buf.Printf( "   Cannot communicate with relays without network config." );
1344 	}
1345 
1346 	// Unable to talk to any routers?
1347 	if ( m_eAvailAnyRouterCommunication != k_ESteamNetworkingAvailability_Current && m_eAvailAnyRouterCommunication != k_ESteamNetworkingAvailability_Unknown )
1348 	{
1349 		buf.Printf( "Router network: %s\n", GetAvailabilityString( m_eAvailAnyRouterCommunication ) );
1350 	}
1351 
1352 	switch ( m_info.m_eState )
1353 	{
1354 		case k_ESteamNetworkingConnectionState_Connecting:
1355 			buf.Printf( "End-to-end connection: connecting\n" );
1356 			break;
1357 
1358 		case k_ESteamNetworkingConnectionState_FindingRoute:
1359 			buf.Printf( "End-to-end connection: performing rendezvous\n" );
1360 			break;
1361 
1362 		case k_ESteamNetworkingConnectionState_Connected:
1363 			buf.Printf( "End-to-end connection: connected\n" );
1364 			break;
1365 
1366 		case k_ESteamNetworkingConnectionState_ClosedByPeer:
1367 			buf.Printf( "End-to-end connection: closed by remote host, reason code %d.  (%s)\n", m_info.m_eEndReason, m_info.m_szEndDebug );
1368 			break;
1369 
1370 		case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
1371 			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 );
1372 			break;
1373 
1374 		case k_ESteamNetworkingConnectionState_None:
1375 			buf.Printf( "End-to-end connection: closed, reason code %d.  (%s)\n", m_info.m_eEndReason, m_info.m_szEndDebug );
1376 			break;
1377 
1378 		default:
1379 			buf.Printf( "End-to-end connection: BUG: invalid state %d!\n", m_info.m_eState );
1380 			break;
1381 	}
1382 
1383 	if ( m_info.m_idPOPRemote )
1384 	{
1385 		buf.Printf( "    Remote host is in data center '%s'\n", SteamNetworkingPOPIDRender( m_info.m_idPOPRemote ).c_str() );
1386 	}
1387 
1388 	// If we ever tried to send a packet end-to-end, dump end-to-end stats.
1389 	if ( m_statsEndToEnd.m_lifetime.m_nPacketsSent > 0 )
1390 	{
1391 		LinkStatsPrintToBuf( "    ", m_statsEndToEnd, buf );
1392 	}
1393 
1394 	if ( m_szPrimaryRouterName[0] != '\0' )
1395 	{
1396 		buf.Printf( "Primary router: %s", m_szPrimaryRouterName );
1397 
1398 		int nPrimaryFrontPing = m_statsPrimaryRouter.m_latest.m_nPingMS;
1399 		if ( m_nPrimaryRouterBackPing >= 0 )
1400 			buf.Printf( "  Ping = %d+%d=%d (front+back=total)\n", nPrimaryFrontPing, m_nPrimaryRouterBackPing,nPrimaryFrontPing+m_nPrimaryRouterBackPing );
1401 		else
1402 			buf.Printf( "  Ping to relay = %d\n", nPrimaryFrontPing );
1403 		LinkStatsPrintToBuf( "    ", m_statsPrimaryRouter, buf );
1404 
1405 		if ( m_szBackupRouterName[0] != '\0' )
1406 		{
1407 			buf.Printf( "Backup router: %s  Ping = %d+%d=%d (front+back=total)\n",
1408 				m_szBackupRouterName,
1409 				m_nBackupRouterFrontPing, m_nBackupRouterBackPing,m_nBackupRouterFrontPing+m_nBackupRouterBackPing
1410 			);
1411 		}
1412 	}
1413 	else if ( m_info.m_idPOPRelay )
1414 	{
1415 		buf.Printf( "Communicating via relay in '%s'\n", SteamNetworkingPOPIDRender( m_info.m_idPOPRelay ).c_str() );
1416 	}
1417 
1418 	int sz = buf.TellPut()+1;
1419 	if ( pszBuf && cbBuf > 0 )
1420 	{
1421 		int l = Min( sz, cbBuf ) - 1;
1422 		V_memcpy( pszBuf, buf.Base(), l );
1423 		pszBuf[l] = '\0';
1424 		if ( cbBuf >= sz )
1425 			return 0;
1426 	}
1427 
1428 	return sz;
1429 }
1430 
1431 } // namespace SteamNetworkingSocketsLib
1432