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