Skip to content

Commit fae6b2f

Browse files
feat: add networkmanager topology analytics [MTT-9974] (#3309)
This PR creates a small footprint `NetworkManager` configuration usage analytics pass when running from within the Unity editor. It includes some additional modifications and organization to make any future analytics easier to add and write integration tests for. [MTT-9974](https://jira.unity3d.com/browse/MTT-9974) ## Changelog NA ## Testing and Documentation - Includes the `AnalyticsTests` integration test. - No documentation changes or additions were necessary. <!-- Uncomment and mark items off with a * if this PR deprecates any API: ### Deprecated API - [ ] An `[Obsolete]` attribute was added along with a `(RemovedAfter yyyy-mm-dd)` entry. - [ ] An [api updater] was added. - [ ] Deprecation of the API is explained in the CHANGELOG. - [ ] The users can understand why this API was removed and what they should use instead. -->
1 parent 16c4409 commit fae6b2f

19 files changed

+813
-117
lines changed

com.unity.netcode.gameobjects/Editor/Analytics.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#if UNITY_EDITOR
2+
using System;
3+
using UnityEngine.Analytics;
4+
5+
namespace Unity.Netcode.Editor
6+
{
7+
internal class AnalyticsHandler<T> : IAnalytic where T : IAnalytic.IData
8+
{
9+
private T m_Data;
10+
11+
internal T Data => m_Data;
12+
13+
public AnalyticsHandler(T data)
14+
{
15+
m_Data = data;
16+
}
17+
public bool TryGatherData(out IAnalytic.IData data, out Exception error)
18+
{
19+
data = m_Data;
20+
error = null;
21+
return data != null;
22+
}
23+
}
24+
}
25+
#endif

com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
#if UNITY_EDITOR
2+
using System.Collections.Generic;
3+
using UnityEditor;
4+
using UnityEngine;
5+
using UnityEngine.Analytics;
6+
7+
namespace Unity.Netcode.Editor
8+
{
9+
/// <summary>
10+
/// Used to collection network session configuration information
11+
/// </summary>
12+
internal struct NetworkSessionInfo
13+
{
14+
public int SessionIndex;
15+
public bool SessionStopped;
16+
public bool WasServer;
17+
public bool WasClient;
18+
public bool UsedCMBService;
19+
public string Transport;
20+
public NetworkConfig NetworkConfig;
21+
}
22+
23+
/// <summary>
24+
/// Netcode for GameObjects Analytics Class
25+
/// </summary>
26+
internal class NetcodeAnalytics : NetworkManager.NetcodeAnalytics
27+
{
28+
/// <summary>
29+
/// Determines if we are running an integration test of the analytics integration
30+
/// </summary>
31+
internal static bool IsIntegrationTest = false;
32+
#if ENABLE_NGO_ANALYTICS_LOGGING
33+
internal static bool EnableLogging = true;
34+
#else
35+
internal static bool EnableLogging = false;
36+
#endif
37+
38+
// Preserves the analytics enabled flag
39+
private bool m_OriginalAnalyticsEnabled;
40+
41+
internal override void OnOneTimeSetup()
42+
{
43+
m_OriginalAnalyticsEnabled = EditorAnalytics.enabled;
44+
// By default, we always disable analytics during integration testing
45+
EditorAnalytics.enabled = false;
46+
}
47+
48+
internal override void OnOneTimeTearDown()
49+
{
50+
// Reset analytics to the original value
51+
EditorAnalytics.enabled = m_OriginalAnalyticsEnabled;
52+
}
53+
54+
internal List<NetworkManagerAnalyticsHandler> AnalyticsTestResults = new List<NetworkManagerAnalyticsHandler>();
55+
56+
internal List<NetworkSessionInfo> RecentSessions = new List<NetworkSessionInfo>();
57+
/// <summary>
58+
/// Invoked from <see cref="NetworkManager.ModeChanged(PlayModeStateChange)"/>.
59+
/// </summary>
60+
/// <param name="playModeState">The new <see cref="PlayModeStateChange"/> state.</param>
61+
/// <param name="networkManager">The current <see cref="NetworkManager"/> instance when play mode was entered.</param>
62+
internal override void ModeChanged(PlayModeStateChange playModeState, NetworkManager networkManager)
63+
{
64+
switch (playModeState)
65+
{
66+
case PlayModeStateChange.EnteredPlayMode:
67+
{
68+
if (IsIntegrationTest)
69+
{
70+
AnalyticsTestResults.Clear();
71+
}
72+
break;
73+
}
74+
case PlayModeStateChange.ExitingPlayMode:
75+
{
76+
// Update analytics
77+
UpdateAnalytics(networkManager);
78+
break;
79+
}
80+
}
81+
}
82+
83+
/// <summary>
84+
/// Editor Only
85+
/// Invoked when the session is started.
86+
/// </summary>
87+
/// <param name="networkManager">The <see cref="NetworkManager"/> instance when the session is started.</param>
88+
internal override void SessionStarted(NetworkManager networkManager)
89+
{
90+
// If analytics is disabled and we are not running an integration test, then exit early.
91+
if (!EditorAnalytics.enabled && !IsIntegrationTest)
92+
{
93+
return;
94+
}
95+
96+
var newSession = new NetworkSessionInfo()
97+
{
98+
SessionIndex = RecentSessions.Count,
99+
WasClient = networkManager.IsClient,
100+
WasServer = networkManager.IsServer,
101+
NetworkConfig = networkManager.NetworkConfig.Copy(),
102+
Transport = networkManager.NetworkConfig.NetworkTransport != null ? networkManager.NetworkConfig.NetworkTransport.GetType().Name : "None",
103+
};
104+
RecentSessions.Add(newSession);
105+
}
106+
107+
/// <summary>
108+
/// Editor Only
109+
/// Invoked when the session is stopped or upon exiting play mode.
110+
/// </summary>
111+
/// <param name="networkManager">The <see cref="NetworkManager"/> instance.</param>
112+
internal override void SessionStopped(NetworkManager networkManager)
113+
{
114+
// If analytics is disabled and we are not running an integration test or there are no sessions, then exit early.
115+
if ((!EditorAnalytics.enabled && !IsIntegrationTest) || RecentSessions.Count == 0)
116+
{
117+
return;
118+
}
119+
120+
var lastIndex = RecentSessions.Count - 1;
121+
var recentSession = RecentSessions[lastIndex];
122+
// If the session has already been finalized, then exit early.
123+
if (recentSession.SessionStopped)
124+
{
125+
return;
126+
}
127+
recentSession.UsedCMBService = networkManager.CMBServiceConnection;
128+
recentSession.SessionStopped = true;
129+
RecentSessions[lastIndex] = recentSession;
130+
}
131+
132+
/// <summary>
133+
/// Invoked from within <see cref="NetworkManager.ModeChanged"/> when exiting play mode.
134+
/// </summary>
135+
private void UpdateAnalytics(NetworkManager networkManager)
136+
{
137+
// If analytics is disabled and we are not running an integration test or there are no sessions to process, then exit early.
138+
if ((!EditorAnalytics.enabled && !IsIntegrationTest) || RecentSessions.Count == 0)
139+
{
140+
return;
141+
}
142+
143+
// If the NetworkManager isn't null, then make sure the last entry is marked off as stopped.
144+
// If the last session is stopped, then SessionStopped will exit early.
145+
if (networkManager != null)
146+
{
147+
SessionStopped(networkManager);
148+
}
149+
150+
// Parse through all of the recent network sessions to generate and send NetworkManager analytics
151+
for (int i = 0; i < RecentSessions.Count; i++)
152+
{
153+
var networkManagerAnalytics = GetNetworkManagerAnalytics(RecentSessions[i]);
154+
155+
var isDuplicate = false;
156+
foreach (var analytics in AnalyticsTestResults)
157+
{
158+
// If we have any sessions with identical configurations,
159+
// then we want to ignore those.
160+
if (analytics.Data.Equals(networkManagerAnalytics))
161+
{
162+
isDuplicate = true;
163+
break;
164+
}
165+
}
166+
167+
if (isDuplicate)
168+
{
169+
continue;
170+
}
171+
172+
// If not running an integration test, then go ahead and send the anlytics event data.
173+
if (!IsIntegrationTest)
174+
{
175+
var result = EditorAnalytics.SendAnalytic(new NetworkManagerAnalyticsHandler(networkManagerAnalytics));
176+
if (EnableLogging && result != AnalyticsResult.Ok)
177+
{
178+
Debug.LogWarning($"[Analytics] Problem sending analytics: {result}");
179+
}
180+
}
181+
else
182+
{
183+
AnalyticsTestResults.Add(new NetworkManagerAnalyticsHandler(networkManagerAnalytics));
184+
}
185+
}
186+
187+
if (IsIntegrationTest && EnableLogging)
188+
{
189+
var count = 0;
190+
foreach (var entry in AnalyticsTestResults)
191+
{
192+
entry.Data.LogAnalyticData(count);
193+
count++;
194+
}
195+
}
196+
RecentSessions.Clear();
197+
}
198+
199+
/// <summary>
200+
/// Generates a <see cref="NetworkManagerAnalytics"/> based on the <see cref="NetworkManager.NetworkSessionInfo"/> passed in
201+
/// </summary>
202+
/// <param name="networkSession">Represents a network session with the used NetworkManager configuration</param>
203+
/// <returns></returns>
204+
private NetworkManagerAnalytics GetNetworkManagerAnalytics(NetworkSessionInfo networkSession)
205+
{
206+
var multiplayerSDKInstalled = false;
207+
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
208+
multiplayerSDKInstalled = true;
209+
#endif
210+
if (EnableLogging && !networkSession.SessionStopped)
211+
{
212+
Debug.LogWarning($"Session-{networkSession.SessionIndex} was not considered stopped!");
213+
}
214+
var networkManagerAnalytics = new NetworkManagerAnalytics()
215+
{
216+
IsDistributedAuthority = networkSession.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority,
217+
WasServer = networkSession.WasServer,
218+
WasClient = networkSession.WasClient,
219+
UsedCMBService = networkSession.UsedCMBService,
220+
IsUsingMultiplayerSDK = multiplayerSDKInstalled,
221+
NetworkTransport = networkSession.Transport,
222+
EnableSceneManagement = networkSession.NetworkConfig.EnableSceneManagement,
223+
TickRate = (int)networkSession.NetworkConfig.TickRate,
224+
};
225+
return networkManagerAnalytics;
226+
}
227+
}
228+
}
229+
#endif

com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#if UNITY_EDITOR
2+
using System;
3+
using System.Text;
4+
using UnityEngine;
5+
using UnityEngine.Analytics;
6+
7+
namespace Unity.Netcode.Editor
8+
{
9+
[Serializable]
10+
internal struct NetworkManagerAnalytics : IAnalytic.IData, IEquatable<NetworkManagerAnalytics>
11+
{
12+
public bool IsDistributedAuthority;
13+
public bool WasServer;
14+
public bool WasClient;
15+
public bool UsedCMBService;
16+
public bool IsUsingMultiplayerSDK;
17+
public string NetworkTransport;
18+
public bool EnableSceneManagement;
19+
public int TickRate;
20+
public override string ToString()
21+
{
22+
var message = new StringBuilder();
23+
message.AppendLine($"{nameof(IsDistributedAuthority)}: {IsDistributedAuthority}");
24+
message.AppendLine($"{nameof(WasServer)}: {WasServer}");
25+
message.AppendLine($"{nameof(WasClient)}: {WasClient}");
26+
message.AppendLine($"{nameof(UsedCMBService)}: {UsedCMBService}");
27+
message.AppendLine($"{nameof(IsUsingMultiplayerSDK)}: {IsUsingMultiplayerSDK}");
28+
message.AppendLine($"{nameof(NetworkTransport)}: {NetworkTransport}");
29+
message.AppendLine($"{nameof(EnableSceneManagement)}: {EnableSceneManagement}");
30+
message.AppendLine($"{nameof(TickRate)}: {TickRate}");
31+
return message.ToString();
32+
}
33+
34+
internal void LogAnalyticData(int sessionNumber)
35+
{
36+
Debug.Log($"{nameof(NetworkManagerAnalytics)} Session-{sessionNumber}:\n {ToString()}");
37+
}
38+
public bool Equals(NetworkManagerAnalytics other)
39+
{
40+
return IsDistributedAuthority == other.IsDistributedAuthority && WasServer == other.WasServer && WasClient == other.WasClient
41+
&& UsedCMBService == other.UsedCMBService && IsUsingMultiplayerSDK == other.IsUsingMultiplayerSDK
42+
&& EnableSceneManagement == other.EnableSceneManagement && TickRate == other.TickRate
43+
&& NetworkTransport.Equals(other.NetworkTransport);
44+
}
45+
}
46+
}
47+
#endif

com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#if UNITY_EDITOR
2+
using UnityEngine.Analytics;
3+
4+
namespace Unity.Netcode.Editor
5+
{
6+
[AnalyticInfo("NGO_NetworkManager", "unity.netcode", 5, 100, 1000)]
7+
internal class NetworkManagerAnalyticsHandler : AnalyticsHandler<NetworkManagerAnalytics>
8+
{
9+
public NetworkManagerAnalyticsHandler(NetworkManagerAnalytics networkManagerAnalytics) : base(networkManagerAnalytics) { }
10+
}
11+
}
12+
#endif

com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

com.unity.netcode.gameobjects/Editor/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
#if UNITY_INCLUDE_TESTS
44
#if UNITY_EDITOR
55
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")]
6+
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
67
#endif // UNITY_EDITOR
78
#endif // UNITY_INCLUDE_TESTS

com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class NetworkManagerHelper : NetworkManager.INetworkManagerHelper
2727
private static void InitializeOnload()
2828
{
2929
Singleton = new NetworkManagerHelper();
30+
3031
NetworkManager.NetworkManagerHelper = Singleton;
3132
EditorApplication.playModeStateChanged -= EditorApplication_playModeStateChanged;
3233
EditorApplication.hierarchyChanged -= EditorApplication_hierarchyChanged;
@@ -224,6 +225,17 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool
224225
}
225226
return isParented;
226227
}
228+
229+
internal NetcodeAnalytics NetcodeAnalytics = new NetcodeAnalytics();
230+
231+
/// <summary>
232+
/// Directly define the interface method to keep this internal
233+
/// </summary>
234+
/// <returns>The <see cref="NetcodeAnalytics"/> instance which is derived from the <see cref="NetworkManager.NetcodeAnalytics"/> abstract class.</returns>
235+
NetworkManager.NetcodeAnalytics NetworkManager.INetworkManagerHelper.Analytics()
236+
{
237+
return NetcodeAnalytics;
238+
}
227239
}
228240
#endif
229241
}

0 commit comments

Comments
 (0)