Skip to content

Commit fcd900e

Browse files
Performance Data Propagation (#7)
* Create 0003-performance-data.md * Update 0003-performance-data.md * Update 0003-performance-data.md * Update 0003-performance-data.md * Update 0003-performance-data.md * Update 0003-performance-data.md * Update 0003-performance-data.md * Update 0003-performance-data.md * Update 0003-performance-data.md * Update 0003-performance-data.md * Update 0003-performance-data.md * Update 0003-performance-data.md * Update 0003-performance-data.md * Update 0003-performance-data.md * minor visual/styling polish * update issue link Co-authored-by: M. Fatih MAR <mfatihmar@gmail.com>
1 parent 65a4b9e commit fcd900e

1 file changed

Lines changed: 179 additions & 0 deletions

File tree

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
- Feature Name: `performance-data-propagation`
2+
- Start Date: 2021-01-29
3+
- RFC PR: [RFC#7](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/pull/7)
4+
- Issue: [MLAPI#484](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/issues/484)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Currently there is Transport performance data and MLAPI performance data that is not being propagated up externally for any consumer to render or consume. The use cases for this would be useful for technologies such as a **Network Stats Overlay** or a **Profiler** (**Network Messages** and **Network Operation** need this data in the vanilla Unity Profiler). Users need a viable and low overhead way to obtain this data (such as RPCs sent this frame/tick) from MLAPI and utilize this data in the way that they want for their own metrics.
10+
11+
# Motivation
12+
[motivation]: #motivation
13+
14+
This is needed to provide both a default and user specific implementations for future Network Profilers and Network Stats overlays. The MLAPI source code can remain Open Source, and yet, the data being streamed can be hooked up to external and low level systems.
15+
16+
# Guide-level explanation
17+
[guide-level-explanation]: #guide-level-explanation
18+
19+
In order to allow first and third party rendering of the data we will need to allow both MLAPI and its transports to propagate data up the chain.
20+
21+
If the profiler flags are enabled the following will need to happen:
22+
23+
Transports will need to output the number of bytes being sent in the current tick and the latency. The data will be written out to a `ProfilerTickData` chunk. This data will be generic and transport agnostic.
24+
25+
MLAPI will need to output the number of networked objects and the amount of RPCs being sent. This data will be added to the `ProfilerTickData` chunk.
26+
27+
External Tools and APIs will be able to then get this fully written `ProfilerTickData` at the end of the frame for them to do what they wish (Render to Profiler Stats Overlay or Display/Record in a Profiler or file)
28+
29+
As this data is largely a `Dictionary`, accessing its data will be as simple as accessing things by specified fields. Additionally the rendering or logging of these statistics will fall on the user and not affect the Tick or be done at all within the MLAPI framework.
30+
31+
# Reference-level explanation
32+
[reference-level-explanation]: #reference-level-explanation
33+
34+
In order to limit the interface, we are pushing the collected set or performance data to be obtainable only at the end of a tick. The following is roughly what this object could look like. Currently this object will be updated in `NetworkManager.LateUpdate()` for the MLAPI and `Transport.Send()` and other relevant transport method for the transport layer. At the end of the tick the user will be notified that the relevant data is complete and ready (both the MLAPI data and Transport data). Since MLAPI is responsible for collecting the data, and the data from transports will be generic and apply to all/most transports, MLAPI as the owner of the collection of performance data will call `GetTransportGetData()` on the transports through an interface in order to get the data exactly when it needs it in the pipeline before propagating the data out externally. Whether or not the data is collected per transport can also be toggled by a runtime flag `profilerEnabled`.
35+
36+
## Pseudo-code
37+
38+
```cs
39+
namespace MLAPI
40+
{
41+
class ProfilerConstants
42+
{
43+
public const string NumberOfServerRPCs = "numberOfServerRPCs";
44+
public const string TickDuration = "tickDuration";
45+
public const string NumberOfBuffMessagesOut = "numberOfBuffMessagesOut";
46+
public const string NumberOfBuffMessagesIn = "numberOfBuffMessagesIn";
47+
public const string NumberOfUnBuffMessagesOut = "numberOfUnBuffMessagesOut";
48+
public const string NumberOfUnBuffMessagesIn = "numberOfUnBuffMessagesIn";
49+
public const string NumberBytesSent = "numberBytesSent";
50+
public const string NumberBytesReceived = "numberBytesReceived";
51+
}
52+
53+
public class ProfilerTickData
54+
{
55+
public int tickID;
56+
57+
public readonly Dictionary<string, int> tickData = new Dictionary<string, int>();
58+
}
59+
60+
public interface ITransportProfilerData
61+
{
62+
Dictionary<string, int> GetTransportGetData();
63+
}
64+
}
65+
66+
namespace MLAPI
67+
{
68+
class NetworkManager
69+
{
70+
private static int tickID = 0;
71+
72+
public delegate void PerfomanceDataEventHandler(ProfilerTickData profilerData);
73+
74+
event PerfomanceDataEventHandler PerfomanceDataEvent;
75+
76+
void LateUpdate()
77+
{
78+
// ...
79+
ProfilerTickData profilerData = new ProfilerTickData();
80+
profilerData.tickID = tickID++;
81+
// ...
82+
// Transports do their send and receives
83+
// ...
84+
profilerData.tickData[ProfilerConstants.NumberOfServerRPCs] += 1;
85+
// ...
86+
var profileTransport = NetworkConfig.NetworkTransport as ITransportProfilerData;
87+
if (profileTransport != null)
88+
{
89+
var transportProfilerData = profileTransport.GetTransportGetData();
90+
transportProfilerData.ForEach(x =>
91+
{
92+
if (!profilerData.tickData.ContainsKey(x.Key)) profilerData.tickData.Add(x.Key, x.Value);
93+
});
94+
}
95+
96+
PerfomanceDataEvent?.Invoke(profilerData);
97+
}
98+
}
99+
}
100+
101+
102+
namespace MLAPI.Transport
103+
{
104+
class UNetTransport : Transport, ITransportProfilerData
105+
{
106+
private static Dictionary<string, int> transportProfilerData;
107+
private static bool profilerEnabled;
108+
109+
void Init()
110+
{
111+
transportProfilerData = new Dictionary<string, int>();
112+
}
113+
114+
Dictionary<string, int> GetTransportGetData()
115+
{
116+
return transportProfilerData;
117+
}
118+
119+
void Send(ulong clientId, ArraySegment<byte> data, string channelName)
120+
{
121+
if (profilerEnabled)
122+
{
123+
transportProfilerData[ProfilerConstants.NumberOfBuffMessagesOut] += 1;
124+
}
125+
}
126+
}
127+
}
128+
129+
namespace ThirdParty
130+
{
131+
class StatsRenderer
132+
{
133+
void Start()
134+
{
135+
NetworkManager.instance.PerfomanceDataEvent += OnPerformanceEvent;
136+
}
137+
138+
void OnPerformanceEvent(ProfilerTickData profilerData)
139+
{
140+
IMGUI.textlabel("Num Server RPCs", profilerData.tickData[ProfilerConstants.NumberOfServerRPCs]);
141+
}
142+
}
143+
}
144+
```
145+
146+
# Drawbacks
147+
[drawbacks]: #drawbacks
148+
149+
With a dictionary we occur the performance overhead of boxing somewhat. Additionally we require our callers to look up the appropriate types for the data that they are pulling out of the dictionary.
150+
151+
However, changes to the `ProfilerTickData` will not cause large scale changes to the transport, MLAPI, and all third party consumers.
152+
153+
Each transport is also required to implement any required changes needed to fill out the relevant transport-level information. Of course not filling out the fields simply means the Overlay, Profiler, Logger will just display default values for the field types. This means they can implement after an initial release of their transport, if need be.
154+
155+
# Rationale and alternatives
156+
[rationale-and-alternatives]: #rationale-and-alternatives
157+
158+
This design of single memory block is the best for locality of data.
159+
160+
There is less room for user error since there is only one readonly object that they get access to.
161+
162+
There is no room for user injected data or exceptions since we aren’t using an event signaling or actions. All user interaction for rendering happens at end of tick so they can’t affect the time of profiling or cause unforeseen issues.
163+
164+
The impact of not doing a Network Tick and Profiler Notification System is that we’d loose the Profiling capabilities that existed in the vanilla Profiler with Mirror/UNET.
165+
166+
# Prior art
167+
[prior-art]: #prior-art
168+
169+
Writing all pertinent data per tick is a similar method to what most rendering pipelines do, which is gather relevant data store in a buffer and render it. Since Multiplayer requires the same effective speed as our Render Pipelines we want to make sure it is as stable and robust as possible.
170+
171+
# Unresolved questions
172+
[unresolved-questions]: #unresolved-questions
173+
174+
Some of the design involving `profilerEnabled` flag and using an interface is based upon the MultiPlex Transport which may use other transports underneath. Is this truly necessary?
175+
176+
# Future possibilities
177+
[future-possibilities]: #future-possibilities
178+
179+
This is a low impact and visibility change. All in all, it's unlikely that there would be much in future possibilities or requests to this feature.

0 commit comments

Comments
 (0)