Skip to content

feat: Adding in Performance Tick Data that stores data for external consumption (RFC #7) #491

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,7 @@ internal static void HandleNetworkedVarDeltas(List<INetworkedVar> networkedVarLi
long readStartPos = stream.Position;

networkedVarList[i].ReadDelta(stream, IsServer);
PerformanceDataManager.Increment(ProfilerConstants.NumberNetworkVarsReceived);
ProfilerStatManager.networkVarsRcvd.Record();

if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety)
Expand Down Expand Up @@ -836,6 +837,7 @@ internal static void HandleNetworkedVarUpdate(List<INetworkedVar> networkedVarLi
long readStartPos = stream.Position;

networkedVarList[i].ReadField(stream);
PerformanceDataManager.Increment(ProfilerConstants.NumberNetworkVarsReceived);
ProfilerStatManager.networkVarsRcvd.Record();

if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety)
Expand Down
25 changes: 25 additions & 0 deletions com.unity.multiplayer.mlapi/Runtime/Core/NetworkingManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ public class NetworkingManager : MonoBehaviour, INetworkUpdateSystem
internal RpcQueueContainer rpcQueueContainer { get; private set; }
internal NetworkTickSystem networkTickSystem { get; private set; }

public delegate void PerformanceDataEventHandler(PerformanceTickData profilerData);

public static event PerformanceDataEventHandler OnPerformanceDataEvent;

/// <summary>
/// A synchronized time, represents the time in seconds since the server application started. Is replicated across all clients
/// </summary>
Expand Down Expand Up @@ -747,11 +751,18 @@ public void NetworkUpdate(NetworkUpdateStage updateStage)

private void OnNetworkEarlyUpdate()
{
PerformanceDataManager.BeginNewTick();
if (NetworkConfig.NetworkTransport is ITransportProfilerData profileTransport)
{
profileTransport.BeginNewTick();
}

if (IsListening)
{
// Process received data
if ((NetworkTime - m_LastReceiveTickTime >= (1f / NetworkConfig.ReceiveTickrate)) || NetworkConfig.ReceiveTickrate <= 0)
{
PerformanceDataManager.Increment(ProfilerConstants.ReceiveTickRate);
ProfilerStatManager.rcvTickRate.Record();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_ReceiveTick.Begin();
Expand Down Expand Up @@ -861,6 +872,14 @@ private void OnNetworkPreUpdate()
currentNetworkTimeOffset += Mathf.Clamp(networkTimeOffset - currentNetworkTimeOffset, -maxDelta, maxDelta);
}
}

if(NetworkConfig.NetworkTransport is ITransportProfilerData profileTransport)
{
var transportProfilerData = profileTransport.GetTransportProfilerData();
PerformanceDataManager.AddTransportData(transportProfilerData);
}

OnPerformanceDataEvent?.Invoke(PerformanceDataManager.GetData());
}

internal void UpdateNetworkTime(ulong clientId, float netTime, float receiveTime, bool warp = false)
Expand Down Expand Up @@ -914,6 +933,7 @@ internal IEnumerator TimeOutSwitchSceneProgress(SceneSwitchProgress switchSceneP

private void HandleRawTransportPoll(NetEventType eventType, ulong clientId, Channel channel, ArraySegment<byte> payload, float receiveTime)
{
PerformanceDataManager.Increment(ProfilerConstants.NumberBytesReceived, payload.Count);
ProfilerStatManager.bytesRcvd.Record(payload.Count);
switch (eventType)
{
Expand Down Expand Up @@ -1174,6 +1194,7 @@ internal void HandleIncomingData(ulong clientId, Channel channel, ArraySegment<b
{
m_RpcBatcher.ReceiveItems(messageStream, ReceiveCallback, RpcQueueContainer.QueueItemType.ServerRpc, clientId, receiveTime);
ProfilerStatManager.rpcBatchesRcvd.Record();
PerformanceDataManager.Increment(ProfilerConstants.NumberOfRPCBatchesReceived);
}
else
{
Expand All @@ -1196,6 +1217,7 @@ internal void HandleIncomingData(ulong clientId, Channel channel, ArraySegment<b
{
m_RpcBatcher.ReceiveItems(messageStream, ReceiveCallback, RpcQueueContainer.QueueItemType.ClientRpc, clientId, receiveTime);
ProfilerStatManager.rpcBatchesRcvd.Record();
PerformanceDataManager.Increment(ProfilerConstants.NumberOfRPCBatchesReceived);
}
else
{
Expand Down Expand Up @@ -1347,6 +1369,7 @@ public void DisconnectClient(ulong clientId)
{
if (ConnectedClientsList[i].ClientId == clientId) {
ConnectedClientsList.RemoveAt(i);
PerformanceDataManager.Increment(ProfilerConstants.NumberOfConnections, -1);
ProfilerStatManager.connections.Record(-1);
}
}
Expand Down Expand Up @@ -1412,6 +1435,7 @@ internal void OnClientDisconnectFromServer(ulong clientId)
if (ConnectedClientsList[i].ClientId == clientId)
{
ConnectedClientsList.RemoveAt(i);
PerformanceDataManager.Increment(ProfilerConstants.NumberOfConnections, -1);
ProfilerStatManager.connections.Record(-1);
break;
}
Expand Down Expand Up @@ -1460,6 +1484,7 @@ internal void HandleApproval(ulong clientId, bool createPlayerObject, ulong? pla
ConnectedClients.Add(clientId, client);
ConnectedClientsList.Add(client);

PerformanceDataManager.Increment(ProfilerConstants.NumberOfConnections);
ProfilerStatManager.connections.Record();

// This packet is unreliable, but if it gets through it should provide a much better sync than the potentially huge approval message.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -694,13 +694,15 @@ internal static void RPCReceiveQueueItem(ulong clientId, Stream stream, float re
}

ProfilerStatManager.rpcsRcvd.Record();
PerformanceDataManager.Increment(ProfilerConstants.NumberOfRPCsReceived);

var rpcQueueContainer = NetworkingManager.Singleton.rpcQueueContainer;
rpcQueueContainer.AddQueueItemToInboundFrame(queueItemType, receiveTime, clientId, (BitStream)stream);
}

internal static void HandleUnnamedMessage(ulong clientId, Stream stream)
{
PerformanceDataManager.Increment(ProfilerConstants.NumberOfUnnamedMessages);
ProfilerStatManager.unnamedMessage.Record();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_HandleUnnamedMessage.Begin();
Expand All @@ -713,6 +715,7 @@ internal static void HandleUnnamedMessage(ulong clientId, Stream stream)

internal static void HandleNamedMessage(ulong clientId, Stream stream)
{
PerformanceDataManager.Increment(ProfilerConstants.NumberOfNamedMessages);
ProfilerStatManager.namedMessage.Record();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_HandleNamedMessage.Begin();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ internal static void Send(ulong clientId, byte messageType, Channel channel, Bit

NetworkingManager.Singleton.NetworkConfig.NetworkTransport.Send(clientId, new ArraySegment<byte>(stream.GetBuffer(), 0, (int)stream.Length), channel);
ProfilerStatManager.bytesSent.Record((int)stream.Length);
PerformanceDataManager.Increment(ProfilerConstants.NumberBytesSent, (int)stream.Length);

NetworkProfiler.EndEvent();
}
Expand Down Expand Up @@ -57,6 +58,7 @@ internal static void Send(byte messageType, Channel channel, BitStream messageSt

NetworkingManager.Singleton.NetworkConfig.NetworkTransport.Send(NetworkingManager.Singleton.ConnectedClientsList[i].ClientId, new ArraySegment<byte>(stream.GetBuffer(), 0, (int)stream.Length), channel);
ProfilerStatManager.bytesSent.Record((int)stream.Length);
PerformanceDataManager.Increment(ProfilerConstants.NumberBytesSent, (int)stream.Length);
}
NetworkProfiler.EndEvent();
}
Expand Down Expand Up @@ -95,6 +97,7 @@ internal static void Send(byte messageType, Channel channel, List<ulong> clientI

NetworkingManager.Singleton.NetworkConfig.NetworkTransport.Send(clientIds[i], new ArraySegment<byte>(stream.GetBuffer(), 0, (int)stream.Length), channel);
ProfilerStatManager.bytesSent.Record((int)stream.Length);
PerformanceDataManager.Increment(ProfilerConstants.NumberBytesSent, (int)stream.Length);
}
NetworkProfiler.EndEvent();
}
Expand Down Expand Up @@ -131,6 +134,7 @@ internal static void Send(byte messageType, Channel channel, ulong clientIdToIgn

NetworkingManager.Singleton.NetworkConfig.NetworkTransport.Send(NetworkingManager.Singleton.ConnectedClientsList[i].ClientId, new ArraySegment<byte>(stream.GetBuffer(), 0, (int)stream.Length), channel);
ProfilerStatManager.bytesSent.Record((int)stream.Length);
PerformanceDataManager.Increment(ProfilerConstants.NumberBytesSent, (int)stream.Length);
}
NetworkProfiler.EndEvent();
}
Expand Down
3 changes: 3 additions & 0 deletions com.unity.multiplayer.mlapi/Runtime/Messaging/RpcBatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ public void QueueItem(in RpcFrameQueueItem queueItem)

ProfilerStatManager.bytesSent.Record(queueItem.messageData.Count);
ProfilerStatManager.rpcsSent.Record();
PerformanceDataManager.Increment(ProfilerConstants.NumberBytesSent, queueItem.messageData.Count);
PerformanceDataManager.Increment(ProfilerConstants.NumberOfRPCsSent);
}
}

Expand Down Expand Up @@ -178,6 +180,7 @@ public void SendItems(int thresholdBytes, SendCallbackType sendCallback)
entry.Value.Stream.Position = 0;
entry.Value.IsEmpty = true;
ProfilerStatManager.rpcBatchesSent.Record();
PerformanceDataManager.Increment(ProfilerConstants.NumberOfRPCBatchesSent);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,12 @@ public void AdvanceFrameHistory(QueueHistoryFrame.QueueFrameType queueType)
if (queueType == QueueHistoryFrame.QueueFrameType.Inbound)
{
ProfilerStatManager.rpcInQueueSize.Record((int)queueHistoryItem.totalSize);
PerformanceDataManager.Increment(ProfilerConstants.NumberOfRPCsInQueueSize, (int)queueHistoryItem.totalSize);
}
else
{
ProfilerStatManager.rpcOutQueueSize.Record((int)queueHistoryItem.totalSize);
PerformanceDataManager.Increment(ProfilerConstants.NumberOfRPCsOutQueueSize, (int)queueHistoryItem.totalSize);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public void ProcessReceiveQueue(NetworkUpdateStage currentStage)

NetworkingManager.InvokeRpc(currentQueueItem);
ProfilerStatManager.rpcsQueueProc.Record();
PerformanceDataManager.Increment(ProfilerConstants.NumberOfRPCQueueProcessed);
currentQueueItem = CurrentFrame.GetNextQueueItem();
}

Expand Down Expand Up @@ -130,6 +131,7 @@ public void InternalMessagesSendAndFlush()
InternalMessageSender.Send(clientId, MLAPIConstants.MLAPI_ADD_OBJECT, queueItem.channel, PoolStream, queueItem.sendFlags);
}

PerformanceDataManager.Increment(ProfilerConstants.NumberOfRPCsSent, queueItem.clientIds.Length);
ProfilerStatManager.rpcsSent.Record(queueItem.clientIds.Length);
break;
}
Expand All @@ -140,6 +142,7 @@ public void InternalMessagesSendAndFlush()
InternalMessageSender.Send(clientId, MLAPIConstants.MLAPI_DESTROY_OBJECT, queueItem.channel, PoolStream, queueItem.sendFlags);
}

PerformanceDataManager.Increment(ProfilerConstants.NumberOfRPCsSent, queueItem.clientIds.Length);
ProfilerStatManager.rpcsSent.Record(queueItem.clientIds.Length);
break;
}
Expand Down Expand Up @@ -228,6 +231,9 @@ private void SendFrameQueueItem(RpcFrameQueueItem queueItem)
NetworkingManager.Singleton.NetworkConfig.NetworkTransport.Send(queueItem.networkId, queueItem.messageData, queueItem.channel);

//For each packet sent, we want to record how much data we have sent

PerformanceDataManager.Increment(ProfilerConstants.NumberBytesSent, (int)queueItem.streamSize);
PerformanceDataManager.Increment(ProfilerConstants.NumberOfRPCsSent);
ProfilerStatManager.bytesSent.Record((int)queueItem.streamSize);
ProfilerStatManager.rpcsSent.Record();
break;
Expand All @@ -239,10 +245,12 @@ private void SendFrameQueueItem(RpcFrameQueueItem queueItem)
NetworkingManager.Singleton.NetworkConfig.NetworkTransport.Send(clientid, queueItem.messageData, queueItem.channel);

//For each packet sent, we want to record how much data we have sent
PerformanceDataManager.Increment(ProfilerConstants.NumberBytesSent, (int)queueItem.streamSize);
ProfilerStatManager.bytesSent.Record((int)queueItem.streamSize);
}

//For each client we send to, we want to record how many RPCs we have sent
PerformanceDataManager.Increment(ProfilerConstants.NumberOfRPCsSent, queueItem.clientIds.Length);
ProfilerStatManager.rpcsSent.Record(queueItem.clientIds.Length);

break;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Collections.Generic;

namespace MLAPI.Profiling
{
public interface ITransportProfilerData
{
void BeginNewTick();
IReadOnlyDictionary<string, int> GetTransportProfilerData();
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace MLAPI.Profiling
{
static class PerformanceDataManager
{
static PerformanceTickData s_ProfilerData;
static int s_TickID;

internal static void BeginNewTick()
{
s_TickID = Math.Max(s_TickID, 0);
s_ProfilerData = new PerformanceTickData
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to solve, but this is going to allocate on the heap for every network tick. I could see re-using problematic since we shared this data out and who knows whether consumers hold a reference to it / process async. But I'm curious if you or any other reviewers have any thoughts on ways to avoid an alloc here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we absolutely have to do an allocation a pool might help

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to Matt's point and also turning things into structs as much as we can might help a little on GC pressure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also hashing and caching strings might help a little more on string allocation but I'm not sure how applicable these options are here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we went with the generic approach, it needs to exist on the heap (dictionary<,>) since it's an indeterminate size. Although we should definitely make the containers for the heap objects structs when possible to reduce it

{
tickID = s_TickID++,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know the odds are very low, but there's a risk of overflowing. We should at least use an unsigned. Probably unsigned long? No sense in having a negative tick id.

};
}

internal static void Increment(string fieldName, int count = 1)
{
s_ProfilerData?.Increment(fieldName, count);
}

internal static void AddTransportData(IReadOnlyDictionary<string, int> transportProfilerData)
{
s_ProfilerData?.AddNonDuplicateData(transportProfilerData);
}

internal static PerformanceTickData GetData()
{
return s_ProfilerData;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Linq;

namespace MLAPI.Profiling
{
public class PerformanceTickData
{
public int tickID;

readonly ProfilingDataStore m_TickData = new ProfilingDataStore();

public void Increment(string fieldName, int count = 1)
{
m_TickData.Increment(fieldName, count);
}

public void AddNonDuplicateData(IReadOnlyDictionary<string, int> transportProfilerData)
{
IEnumerable<KeyValuePair<string, int>> nonDuplicates = transportProfilerData.Where(entry => !m_TickData.HasData(entry.Key));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this linq query is unnecessary here. you could do the HasData check inside the loop and avoid the linq cost

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://stackoverflow.com/questions/4044400/linq-performance-faq

The allocations and performance AFAIK are the same. nonDuplicates isn't an allocation and we probably save speed by iterating over less entries.
That is to say, the line in question doesn't get evaluated until the bottom for loop lazily. And I feel that this reads better.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost all Linq queries, including .Where, allocate an IEnumerable on the heap to wrap the iteration of the collection. this line also allocates an anonymous function - it can't be static since it captures the member field m_TickData

I won't hold approval for this since it's not a high frequency alloc but I do think it could be optimized

foreach (KeyValuePair<string, int> entry in nonDuplicates)
{
m_TickData.Add(entry.Key, entry.Value);
}
}

public int GetData(string fieldName)
{
return m_TickData.GetData(fieldName);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace MLAPI.Profiling
{
public static class ProfilerConstants {
public const string NumberOfConnections = nameof(NumberOfConnections);
public const string ReceiveTickRate = nameof(ReceiveTickRate);

public const string NumberOfNamedMessages = nameof(NumberOfNamedMessages);
public const string NumberOfUnnamedMessages = nameof(NumberOfUnnamedMessages);
public const string NumberBytesSent = nameof(NumberBytesSent);
public const string NumberBytesReceived = nameof(NumberBytesReceived);
public const string NumberNetworkVarsReceived = nameof(NumberNetworkVarsReceived);
public const string NumberOfRPCsSent = nameof(NumberOfRPCsSent);
public const string NumberOfRPCsReceived = nameof(NumberOfRPCsReceived);
public const string NumberOfRPCBatchesSent = nameof(NumberOfRPCBatchesSent);
public const string NumberOfRPCBatchesReceived = nameof(NumberOfRPCBatchesReceived);
public const string NumberOfRPCQueueProcessed = nameof(NumberOfRPCQueueProcessed);
public const string NumberOfRPCsInQueueSize = nameof(NumberOfRPCsInQueueSize);
public const string NumberOfRPCsOutQueueSize = nameof(NumberOfRPCsOutQueueSize);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading