Skip to content

Commit 02a6ed9

Browse files
feat: add NetworkMessageSent/Received metrics (Unity-Technologies#1112)
* Implemented NetworkMessageSent/NetworkMessageReceived metrics * Added tests and fixed missing messages during batching * updated the message type names to be a readonly * fix against latest develop merge * bumped tools version to Unity-Technologies#8 * fixed unnecessary using * fixed errant tab (thanks github merge....)
1 parent 704255c commit 02a6ed9

File tree

9 files changed

+169
-54
lines changed

9 files changed

+169
-54
lines changed

Runtime/Core/NetworkManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,7 @@ internal void HandleIncomingData(ulong clientId, NetworkChannel networkChannel,
12431243
{
12441244
var messageType = (MessageQueueContainer.MessageType)messageStream.ReadByte();
12451245
MessageHandler.MessageReceiveQueueItem(clientId, messageStream, receiveTime, messageType, networkChannel);
1246+
NetworkMetrics.TrackNetworkMessageReceived(clientId, MessageQueueContainer.GetMessageTypeName(messageType), data.Count);
12461247
}
12471248
#if DEVELOPMENT_BUILD || UNITY_EDITOR
12481249
s_HandleIncomingData.End();
@@ -1253,6 +1254,7 @@ private void ReceiveCallback(NetworkBuffer messageBuffer, MessageQueueContainer.
12531254
float receiveTime, NetworkChannel receiveChannel)
12541255
{
12551256
MessageHandler.MessageReceiveQueueItem(clientId, messageBuffer, receiveTime, messageType, receiveChannel);
1257+
NetworkMetrics.TrackNetworkMessageReceived(clientId, MessageQueueContainer.GetMessageTypeName(messageType), messageBuffer.Length);
12561258
}
12571259

12581260
/// <summary>

Runtime/Messaging/MessageBatcher.cs

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.IO;
3-
using System.Linq;
43
using System.Collections.Generic;
54

65
namespace Unity.Netcode
@@ -34,8 +33,7 @@ public void Shutdown()
3433
m_SendDict.Clear();
3534
}
3635

37-
// Used to store targets, internally
38-
private ulong[] m_TargetList = new ulong[0];
36+
3937

4038
// Used to mark longer lengths. Works because we can't have zero-sized messages
4139
private const byte k_LongLenMarker = 0;
@@ -84,40 +82,18 @@ private int PopLength(in NetworkBuffer messageBuffer)
8482
return len1 + len2 * 256;
8583
}
8684

87-
/// <summary>
88-
/// FillTargetList
89-
/// Fills a list with the ClientId's an item is targeted to
90-
/// </summary>
91-
/// <param name="item">the FrameQueueItem we want targets for</param>
92-
/// <param name="networkIdList">the list to fill</param>
93-
private static void FillTargetList(in MessageFrameItem item, ref ulong[] networkIdList)
94-
{
95-
switch (item.MessageType)
96-
{
97-
// todo: revisit .resize() and .ToArry() usage, for performance
98-
case MessageQueueContainer.MessageType.ServerRpc:
99-
Array.Resize(ref networkIdList, 1);
100-
networkIdList[0] = item.NetworkId;
101-
break;
102-
default:
103-
// todo: consider the implications of default usage of queueItem.clientIds
104-
case MessageQueueContainer.MessageType.ClientRpc:
105-
// copy the list
106-
networkIdList = item.ClientNetworkIds.ToArray();
107-
break;
108-
}
109-
}
110-
11185
/// <summary>
11286
/// QueueItem
11387
/// Add a FrameQueueItem to be sent
11488
/// </summary>queueItem
11589
/// <param name="item">the threshold in bytes</param>
116-
public void QueueItem(in MessageFrameItem item, int automaticSendThresholdBytes, SendCallbackType sendCallback)
90+
public void QueueItem(
91+
IReadOnlyCollection<ulong> targetList,
92+
in MessageFrameItem item,
93+
int automaticSendThresholdBytes,
94+
SendCallbackType sendCallback)
11795
{
118-
FillTargetList(item, ref m_TargetList);
119-
120-
foreach (ulong clientId in m_TargetList)
96+
foreach (ulong clientId in targetList)
12197
{
12298
if (!m_SendDict.ContainsKey(clientId))
12399
{

Runtime/Messaging/MessageQueue/MessageQueueContainer.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,29 @@ public enum MessageQueueProcessingTypes
4444
Receive,
4545
}
4646

47+
private static readonly IReadOnlyDictionary<int, string> k_MessageTypeNames;
48+
49+
static MessageQueueContainer()
50+
{
51+
var messageTypeNames = new Dictionary<int, string>();
52+
foreach(var messageType in Enum.GetValues(typeof(MessageType)))
53+
{
54+
messageTypeNames.Add((int)messageType, messageType.ToString());
55+
}
56+
57+
k_MessageTypeNames = messageTypeNames;
58+
}
59+
60+
public static string GetMessageTypeName(MessageType messageType)
61+
{
62+
if (!k_MessageTypeNames.TryGetValue((int)messageType, out var messageTypeName))
63+
{
64+
messageTypeName = string.Empty;
65+
}
66+
67+
return messageTypeName;
68+
}
69+
4770
// Inbound and Outbound QueueHistoryFrames
4871
private readonly Dictionary<MessageQueueHistoryFrame.QueueFrameType, Dictionary<int, Dictionary<NetworkUpdateStage, MessageQueueHistoryFrame>>> m_QueueHistory =
4972
new Dictionary<MessageQueueHistoryFrame.QueueFrameType, Dictionary<int, Dictionary<NetworkUpdateStage, MessageQueueHistoryFrame>>>();

Runtime/Messaging/MessageQueue/MessageQueueProcessor.cs

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using Unity.Profiling;
34
using UnityEngine;
45

@@ -29,6 +30,7 @@ internal class MessageQueueProcessor
2930
private MessageQueueContainer m_MessageQueueContainer;
3031

3132
private readonly NetworkManager m_NetworkManager;
33+
private readonly List<ulong> m_TargetIdBuffer = new List<ulong>();
3234

3335
public void Shutdown()
3436
{
@@ -217,6 +219,28 @@ internal void ProcessSendQueue(bool isListening)
217219
#endif
218220
}
219221

222+
/// <summary>
223+
/// FillTargetList
224+
/// Fills a list with the ClientId's an item is targeted to
225+
/// </summary>
226+
/// <param name="item">the MessageQueueItem we want targets for</param>
227+
/// <param name="targetList">the list to fill</param>
228+
private static void FillTargetList(in MessageFrameItem item, List<ulong> targetList)
229+
{
230+
switch (item.MessageType)
231+
{
232+
case MessageQueueContainer.MessageType.ServerRpc:
233+
targetList.Add(item.NetworkId);
234+
break;
235+
default:
236+
// todo: consider the implications of default usage of queueItem.clientIds
237+
case MessageQueueContainer.MessageType.ClientRpc:
238+
// copy the list
239+
targetList.AddRange(item.ClientNetworkIds);
240+
break;
241+
}
242+
}
243+
220244
/// <summary>
221245
/// Sends all message queue items in the current outbound frame
222246
/// </summary>
@@ -235,13 +259,21 @@ private void MessageQueueSendAndFlush(bool isListening)
235259
advanceFrameHistory = true;
236260
if (isListening)
237261
{
262+
m_TargetIdBuffer.Clear();
263+
FillTargetList(currentQueueItem, m_TargetIdBuffer);
264+
238265
if (m_MessageQueueContainer.IsUsingBatching())
239266
{
240-
m_MessageBatcher.QueueItem(currentQueueItem, k_BatchThreshold, SendCallback);
267+
m_MessageBatcher.QueueItem(m_TargetIdBuffer, currentQueueItem, k_BatchThreshold, SendCallback);
241268
}
242269
else
243270
{
244-
SendFrameQueueItem(currentQueueItem);
271+
SendFrameQueueItem(m_TargetIdBuffer, currentQueueItem);
272+
}
273+
274+
foreach (var target in m_TargetIdBuffer)
275+
{
276+
m_NetworkManager.NetworkMetrics.TrackNetworkMessageSent(target, MessageQueueContainer.GetMessageTypeName(currentQueueItem.MessageType), currentQueueItem.MessageData.Count);
245277
}
246278
}
247279

@@ -293,7 +325,7 @@ private void SendCallback(ulong clientId, MessageBatcher.SendStream sendStream)
293325
/// Sends the Message Queue Item to the specified destination
294326
/// </summary>
295327
/// <param name="item">Information on what to send</param>
296-
private void SendFrameQueueItem(MessageFrameItem item)
328+
private void SendFrameQueueItem(IReadOnlyCollection<ulong> targetIds, in MessageFrameItem item)
297329
{
298330
var channel = item.NetworkChannel;
299331
// If the length is greater than the fragmented threshold, switch to a fragmented channel.
@@ -304,25 +336,11 @@ private void SendFrameQueueItem(MessageFrameItem item)
304336
{
305337
channel = NetworkChannel.Fragmented;
306338
}
307-
switch (item.MessageType)
308-
{
309-
case MessageQueueContainer.MessageType.ServerRpc:
310-
// TODO: Can we remove this special case for server RPCs?
311-
{
312-
m_MessageQueueContainer.NetworkManager.NetworkMetrics.TrackTransportBytesSent(item.MessageData.Count);
313-
m_MessageQueueContainer.NetworkManager.NetworkConfig.NetworkTransport.Send(item.NetworkId, item.MessageData, channel);
314-
break;
315-
}
316-
default:
317-
{
318-
foreach (ulong clientid in item.ClientNetworkIds)
319-
{
320-
m_MessageQueueContainer.NetworkManager.NetworkMetrics.TrackTransportBytesSent(item.MessageData.Count);
321-
m_MessageQueueContainer.NetworkManager.NetworkConfig.NetworkTransport.Send(clientid, item.MessageData, channel);
322-
}
323339

324-
break;
325-
}
340+
foreach (var clientId in targetIds)
341+
{
342+
m_MessageQueueContainer.NetworkManager.NetworkMetrics.TrackTransportBytesSent(item.MessageData.Count);
343+
m_MessageQueueContainer.NetworkManager.NetworkConfig.NetworkTransport.Send(clientId, item.MessageData, channel);
326344
}
327345
}
328346

Runtime/Metrics/INetworkMetrics.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ internal interface INetworkMetrics
88

99
void TrackTransportBytesReceived(long bytesCount);
1010

11+
void TrackNetworkMessageSent(ulong receivedClientId, string messageType, long bytesCount);
12+
13+
void TrackNetworkMessageReceived(ulong senderClientId, string messageType, long bytesCount);
14+
1115
void TrackNetworkObject(NetworkObject networkObject);
1216

1317
void TrackNamedMessageSent(ulong receiverClientId, string messageName, long bytesCount);

Runtime/Metrics/NetworkMetrics.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ internal class NetworkMetrics : INetworkMetrics
1717
ShouldResetOnDispatch = true,
1818
};
1919

20+
readonly EventMetric<NetworkMessageEvent> m_NetworkMessageSentEvent = new EventMetric<NetworkMessageEvent>(NetworkMetricTypes.NetworkMessageSent.Id);
21+
readonly EventMetric<NetworkMessageEvent> m_NetworkMessageReceivedEvent = new EventMetric<NetworkMessageEvent>(NetworkMetricTypes.NetworkMessageReceived.Id);
2022
readonly EventMetric<NamedMessageEvent> m_NamedMessageSentEvent = new EventMetric<NamedMessageEvent>(NetworkMetricTypes.NamedMessageSent.Id);
2123
readonly EventMetric<NamedMessageEvent> m_NamedMessageReceivedEvent = new EventMetric<NamedMessageEvent>(NetworkMetricTypes.NamedMessageReceived.Id);
2224
readonly EventMetric<UnnamedMessageEvent> m_UnnamedMessageSentEvent = new EventMetric<UnnamedMessageEvent>(NetworkMetricTypes.UnnamedMessageSent.Id);
@@ -42,6 +44,7 @@ public NetworkMetrics()
4244
{
4345
Dispatcher = new MetricDispatcherBuilder()
4446
.WithCounters(m_TransportBytesSent, m_TransportBytesReceived)
47+
.WithMetricEvents(m_NetworkMessageSentEvent, m_NetworkMessageReceivedEvent)
4548
.WithMetricEvents(m_NamedMessageSentEvent, m_NamedMessageReceivedEvent)
4649
.WithMetricEvents(m_UnnamedMessageSentEvent, m_UnnamedMessageReceivedEvent)
4750
.WithMetricEvents(m_NetworkVariableDeltaSentEvent, m_NetworkVariableDeltaReceivedEvent)
@@ -76,6 +79,16 @@ public void TrackNetworkObject(NetworkObject networkObject)
7679
}
7780
}
7881

82+
public void TrackNetworkMessageSent(ulong receivedClientId, string messageType, long bytesCount)
83+
{
84+
m_NetworkMessageSentEvent.Mark(new NetworkMessageEvent(new ConnectionInfo(receivedClientId), messageType, bytesCount));
85+
}
86+
87+
public void TrackNetworkMessageReceived(ulong senderClientId, string messageType, long bytesCount)
88+
{
89+
m_NetworkMessageReceivedEvent.Mark(new NetworkMessageEvent(new ConnectionInfo(senderClientId), messageType, bytesCount));
90+
}
91+
7992
public void TrackNamedMessageSent(ulong receiverClientId, string messageName, long bytesCount)
8093
{
8194
m_NamedMessageSentEvent.Mark(new NamedMessageEvent(new ConnectionInfo(receiverClientId), messageName, bytesCount));

Runtime/Metrics/NullNetworkMetrics.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ public void TrackTransportBytesSent(long bytesCount)
1111
public void TrackTransportBytesReceived(long bytesCount)
1212
{
1313
}
14+
15+
public void TrackNetworkMessageSent(ulong receivedClientId, string messageType, long bytesCount)
16+
{
17+
}
18+
19+
public void TrackNetworkMessageReceived(ulong senderClientId, string messageType, long bytesCount)
20+
{
21+
}
1422

1523
public void TrackNetworkObject(NetworkObject networkObject)
1624
{

Tests/Editor/MessageBatcherTests.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ public void SendWithThreshold()
2626
MessageType = i % 2 == 0 ? MessageQueueContainer.MessageType.ServerRpc : MessageQueueContainer.MessageType.ClientRpc,
2727
MessageData = new ArraySegment<byte>(randomData, 0, randomData.Length)
2828
};
29-
sendBatcher.QueueItem(queueItem,
29+
sendBatcher.QueueItem(
30+
queueItem.ClientNetworkIds,
31+
queueItem,
3032
k_BatchThreshold,
3133
(networkId, sendStream) =>
3234
{
@@ -75,7 +77,9 @@ public void SendWithoutThreshold()
7577
MessageType = i % 2 == 0 ? MessageQueueContainer.MessageType.ServerRpc : MessageQueueContainer.MessageType.ClientRpc,
7678
MessageData = new ArraySegment<byte>(randomData, 0, randomData.Length)
7779
};
78-
sendBatcher.QueueItem(queueItem,
80+
sendBatcher.QueueItem(
81+
queueItem.ClientNetworkIds,
82+
queueItem,
7983
k_BatchThreshold,
8084
(networkId, sendStream) =>
8185
{

Tests/Runtime/Metrics/MessagingMetricsTests.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,73 @@ public class MessagingMetricsTests : DualClientMetricTestBase
2222

2323
protected override int NbClients => 2;
2424

25+
[UnityTest]
26+
public IEnumerator TrackNetworkMessageSentMetric()
27+
{
28+
var messageName = Guid.NewGuid().ToString();
29+
using var memoryStream = new MemoryStream();
30+
using var binaryWriter = new BinaryWriter(memoryStream);
31+
binaryWriter.Write(messageName);
32+
33+
var waitForMetricValues = new WaitForMetricValues<NetworkMessageEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageSent);
34+
35+
Server.CustomMessagingManager.SendNamedMessage(messageName, FirstClient.LocalClientId, memoryStream);
36+
37+
yield return waitForMetricValues.WaitForMetricsReceived();
38+
39+
var networkMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
40+
Assert.AreEqual(1, networkMessageSentMetricValues.Count);
41+
42+
var networkMessageEvent = networkMessageSentMetricValues.First();
43+
Assert.AreEqual(MessageQueueContainer.GetMessageTypeName(MessageQueueContainer.MessageType.NamedMessage), networkMessageEvent.Name);
44+
Assert.AreEqual(FirstClient.LocalClientId, networkMessageEvent.Connection.Id);
45+
}
46+
47+
[UnityTest]
48+
public IEnumerator TrackNetworkMessageSentMetricToMultipleClients()
49+
{
50+
var messageName = Guid.NewGuid().ToString();
51+
using var memoryStream = new MemoryStream();
52+
using var binaryWriter = new BinaryWriter(memoryStream);
53+
binaryWriter.Write(messageName);
54+
55+
var waitForMetricValues = new WaitForMetricValues<NetworkMessageEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageSent);
56+
57+
Server.CustomMessagingManager.SendNamedMessage(messageName, new List<ulong> { FirstClient.LocalClientId, SecondClient.LocalClientId }, memoryStream);
58+
59+
yield return waitForMetricValues.WaitForMetricsReceived();
60+
61+
var networkMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
62+
Assert.AreEqual(2, networkMessageSentMetricValues.Count(x => x.Name.Equals(MessageQueueContainer.GetMessageTypeName(MessageQueueContainer.MessageType.NamedMessage))));
63+
}
64+
65+
[UnityTest]
66+
public IEnumerator TrackNetworkMessageReceivedMetric()
67+
{
68+
var messageName = Guid.NewGuid().ToString();
69+
using var memoryStream = new MemoryStream();
70+
using var binaryWriter = new BinaryWriter(memoryStream);
71+
binaryWriter.Write(messageName);
72+
73+
LogAssert.Expect(LogType.Log, $"Received from {Server.LocalClientId}");
74+
FirstClient.CustomMessagingManager.RegisterNamedMessageHandler(messageName, (sender, payload) =>
75+
{
76+
Debug.Log($"Received from {sender}");
77+
});
78+
79+
var waitForMetricValues = new WaitForMetricValues<NetworkMessageEvent>(FirstClientMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageReceived);
80+
81+
Server.CustomMessagingManager.SendNamedMessage(messageName, FirstClient.LocalClientId, memoryStream);
82+
83+
yield return waitForMetricValues.WaitForMetricsReceived();
84+
85+
var networkMessageReceivedValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
86+
Assert.AreEqual(1, networkMessageReceivedValues.Count(x => x.Name.Equals(MessageQueueContainer.GetMessageTypeName(MessageQueueContainer.MessageType.NamedMessage))));
87+
88+
var namedMessageReceived = networkMessageReceivedValues.First();
89+
Assert.AreEqual(Server.LocalClientId, namedMessageReceived.Connection.Id);
90+
}
91+
2592
[UnityTest]
2693
public IEnumerator TrackNamedMessageSentMetric()
2794
{

0 commit comments

Comments
 (0)