Traffic Stats
Stats
Every PhotonPeer counts what it sends and receives while connected. In Realtime, that peer is RealtimeClient.RealtimePeer, so the counters are at:
C#
TrafficStats stats = client.RealtimePeer.Stats; // Photon.Client.TrafficStats
Nothing has to be enabled: the stats always run (v4's TrafficStatsEnabled is obsolete and without function). Stats is replaced with a fresh instance on every Connect(), so all values are per-connection and only meaningful while connected.
Before digging into numbers, raise the log level — most connection and matchmaking problems are already spelled out in the text log (see Logging). The client also logs a stats one-liner for you: with LogLevel.Info, RealtimeClient.LogStatsInterval (default 5000 ms) logs RealtimePeer.VitalStatsToString(false) in that interval.
C#
client.RealtimePeer.VitalStatsToString(false);
// Stats duration: 12.00 sec. rtt(var): 34(3)ms. 120 kB -> 10 kB/sec.
client.RealtimePeer.VitalStatsToString(true); // same line + the full TrafficStats breakdown
Two related values are on the peer itself, not in Stats: QueuedIncomingCommands and QueuedOutgoingCommands. Growing queues point at a dispatch/send loop that is not called often enough.
Traffic Stats
TrafficStats counters accumulate for the lifetime of the connection, so a single reading tells you little. The useful pattern is snapshot → delta:
TrafficStats.ToSnapshot()returns aTrafficStatsSnapshot: all counters plusSnapshotTimestamp(ms sinceConnect()).TrafficStats.ToDelta(reference)returns aTrafficStatsDelta—now - reference, includingDeltaTimein milliseconds.
C#
private TrafficStatsSnapshot reference;
void LogTraffic() // call in some interval
{
if (!client.IsConnected)
{
this.reference = null; // drop stale reference; Stats is new per connection
return;
}
TrafficStats stats = client.RealtimePeer.Stats;
if (this.reference == null)
{
this.reference = stats.ToSnapshot();
return;
}
TrafficStatsDelta delta = stats.ToDelta(this.reference);
this.reference = stats.ToSnapshot(); // this "now" is the next interval's reference
float seconds = delta.DeltaTime / 1000f;
int inPerSec = seconds > 0f ? (int)(delta.BytesIn / seconds) : 0;
int outPerSec = seconds > 0f ? (int)(delta.BytesOut / seconds) : 0;
Log.Info($"in {inPerSec} B/s, out {outPerSec} B/s, rtt {stats.RoundtripTime} ms");
}
TrafficStatsDelta.ToString(udpValues, rttValues, callValues) already formats bytes, rates, UDP commands, RTT and call counts, if you just want a log line. TrafficStats.ToString(extended) does the same for the absolute values. demo-realtime-console/GameLogic.cs (LogTrafficStats()) shows the whole loop.
Reading the values
Volume (TrafficStatsBase, so present in stats, snapshots and deltas):
| Value | Meaning |
|---|---|
BytesIn / BytesOut |
Payload bytes, excluding transport-layer headers. |
PackagesIn / PackagesOut |
Datagrams / packages. |
UdpFragmentsIn / UdpFragmentsOut |
Commands split because they exceed the MTU. Steady fragments mean your messages are too big for one datagram. |
UdpUnreliableCommandsSent |
Unreliable commands sent (total, all channels). |
UdpReliableCommandsSent |
Reliable commands sent, excluding resends. |
UdpReliableCommandsResent |
Reliable commands repeated because no ACK arrived in time — the client-side indicator for packet loss. |
UdpReliableCommandsInFlight |
Sent but not yet acknowledged. A rising value means the connection is not keeping up. |
DispatchIncomingCommandsCalls / SendOutgoingCommandsCalls |
How often you called those methods. |
Timings (only on TrafficStats, the live instance):
| Value | Meaning |
|---|---|
LongestDeltaBetweenSendOutgoingCalls |
Worst gap between SendOutgoingCommands() calls. Crucial: without sending, the server times out this client. |
LongestDeltaBetweenDispatchCalls |
Worst gap between DispatchIncomingCommands() calls. Not fatal, but it adds local lag to events already received. |
LastSendOutgoingDeltaTime, LastDispatchDeltaTime, LastReceiveDeltaTime, LastSendAckDeltaTime |
Milliseconds since the last send / dispatch / anything received / ACKs sent. |
LastDispatchDuration |
Milliseconds spent in the last dispatch callback — long callbacks stall the loop. |
ResetMaximumCounters() clears the two Longest… values (and the internal last-call timestamps), e.g. to measure one level or match at a time.
Notes:
- UDP-specific values stay 0 on TCP / WebSocket connections.
- The delta's
RoundtripTime,RoundtripTimeVarianceandLastRoundtripTimeare differences between the two snapshots, not absolute values. For absolutes, useReferenceRoundtripTime/LaterRoundtripTimeon the delta, or readStats.RoundtripTimedirectly. - Migrating from v4: the split
TrafficStatsGameLevel/ incoming / outgoing instances,TrafficStatsEnabledandTrafficStatsReset()are gone. Everything is inPhotonPeer.Stats, and resetting is replaced by keeping a snapshot.
Round-Trip Time
Stats.RoundtripTime is the time until a reliable command is acknowledged by the server, in milliseconds — a running estimate, not a single measurement. Every acknowledged reliable command contributes a fraction (Jacobson/Karels smoothing, starting at 200 ms on connect). On UDP it includes the server's configured ACK-delay; on TCP and WebSocket there is none, so values are slightly lower.
Stats.RoundtripTimeVarianceis the jitter of that estimate. It also feeds the SDK's resend timing, so a high variance delays resends.Stats.LastRoundtripTimeis the single last measurement — spiky, useful for debugging, not for display.
Updates come from any reliable command on UDP; on TCP and WebSocket, from the automatic ping. PhotonPeer.PingInterval (default 1000 ms) controls how often pings are sent — on UDP, pings are skipped when a reliable command was sent in that interval anyway.
Practically: RoundtripTime is also the approximate time until a raised event reaches another client or an operation result comes back. Add half of it as expected one-way delay when tuning interpolation or input delay. Watch RoundtripTimeVariance and UdpReliableCommandsResent together — a stable 120 ms connection plays better than one averaging 60 ms with heavy jitter and resends.
The obsolete PhotonPeer.RoundTripTime, RoundTripTimeVariance and LastRoundTripTime properties still work but forward to Stats; use Stats in new code.