From 1a8f3215aa512a61605ea3ab5dcd106b647f3d51 Mon Sep 17 00:00:00 2001 From: Luiz Felipe Takakura Date: Tue, 26 Nov 2019 19:09:45 -0300 Subject: [PATCH 01/15] [WIP] Implement PitayaMetrics class --- .../Assets/Pitaya/PitayaClient.cs | 22 ++- .../Assets/Pitaya/PitayaMetrics.cs | 172 ++++++++++++++++++ .../Assets/Tests/PitayaClientTest.cs | 8 +- 3 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs b/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs index 66221d2e..892d6cca 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs @@ -13,6 +13,7 @@ public class PitayaClient : IDisposable, IPitayaListener private const int DEFAULT_CONNECTION_TIMEOUT = 30; private IntPtr _client = IntPtr.Zero; + private PitayaMetrics _metricsAggr; private EventManager _eventManager; private bool _disposed; private uint _reqUid; @@ -22,22 +23,27 @@ public class PitayaClient : IDisposable, IPitayaListener public PitayaClient() { - Init(null, false, false, false, DEFAULT_CONNECTION_TIMEOUT); + Init(null, false, false, false, DEFAULT_CONNECTION_TIMEOUT, null); } public PitayaClient(int connectionTimeout) { - Init(null, false, false, false, connectionTimeout); + Init(null, false, false, false, connectionTimeout, null); } public PitayaClient(string certificateName = null) { - Init(certificateName, certificateName != null, false, false, DEFAULT_CONNECTION_TIMEOUT); + Init(certificateName, certificateName != null, false, false, DEFAULT_CONNECTION_TIMEOUT, null); } - public PitayaClient(bool enableReconnect = false, string certificateName = null, int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT) + public PitayaClient(PitayaMetrics.MetricsCallback metricsCB = null) { - Init(certificateName, certificateName != null, false, enableReconnect, DEFAULT_CONNECTION_TIMEOUT); + Init(null, false, false, false, DEFAULT_CONNECTION_TIMEOUT, metricsCB); + } + + public PitayaClient(bool enableReconnect = false, string certificateName = null, int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT, PitayaMetrics.MetricsCallback metricsCB = null) + { + Init(certificateName, certificateName != null, false, enableReconnect, DEFAULT_CONNECTION_TIMEOUT, metricsCB); } ~PitayaClient() @@ -45,12 +51,13 @@ public PitayaClient(bool enableReconnect = false, string certificateName = null, Dispose(); } - private void Init(string certificateName, bool enableTlS, bool enablePolling, bool enableReconnect, int connTimeout) + private void Init(string certificateName, bool enableTlS, bool enablePolling, bool enableReconnect, int connTimeout, PitayaMetrics.MetricsCallback metricsCB) { _eventManager = new EventManager(); _typeRequestSubscriber = new TypeSubscriber(); _typePushSubscriber = new TypeSubscriber(); _client = PitayaBinding.CreateClient(enableTlS, enablePolling, enableReconnect, connTimeout, this); + _metricsAggr = new PitayaMetrics(metricsCB); if (certificateName != null) { @@ -83,11 +90,13 @@ public PitayaClientState State public void Connect(string host, int port, string handshakeOpts = null) { + _metricsAggr.Start(); PitayaBinding.Connect(_client, host, port, handshakeOpts); } public void Connect(string host, int port, Dictionary handshakeOpts) { + _metricsAggr.Start(); var opts = Pitaya.SimpleJson.SimpleJson.SerializeObject(handshakeOpts); PitayaBinding.Connect(_client, host, port, opts); } @@ -208,6 +217,7 @@ public void OnRequestError(uint rid, PitayaError error) public void OnNetworkEvent(PitayaNetWorkState state, NetworkError error) { if(NetWorkStateChangedEvent != null ) NetWorkStateChangedEvent.Invoke(state, error); + _metricsAggr.Update(state, error); } public void OnUserDefinedPush(string route, byte[] serializedBody) diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs new file mode 100644 index 00000000..1cb21a78 --- /dev/null +++ b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs @@ -0,0 +1,172 @@ +using System; +using System.Diagnostics; +using System.Collections.Generic; +using System.IO; +using Google.Protobuf; +using UnityEngine; +using System.Linq; + +namespace Pitaya +{ + public class PitayaMetrics + { + public ConnectionSessionStats connectionSessionStats; + public delegate void MetricsCallback(ConnectionSessionStats stats); + private Stopwatch connectionWatch = new Stopwatch(); + private Stopwatch sessionWatch = new Stopwatch(); + private MetricsCallback cb; + + public enum ConnectionFailure + { + CouldNotResolveHost, + Error, + Timeout, + } + + private static class disconnectionReason + { + public const string ConnectionEndedNormally = "ConnectionEndedNormally"; + public const string ConnectionTimeout = "ConnectionTimeout"; + public const string FailedToConnect = "FailedToConnect"; + public const string ConnectionErrored = "ConnectionErrored"; + public const string ConnectionClosed = "ConnectionClosed"; + public const string ConnectionKicked = "Kicked"; + } + + private PitayaNetWorkState[] sessionStartedStates = { + PitayaNetWorkState.Connected, PitayaNetWorkState.FailToConnect, PitayaNetWorkState.Timeout, PitayaNetWorkState.Error + }; + + private PitayaNetWorkState[] sessionErroredStates = { + PitayaNetWorkState.FailToConnect, PitayaNetWorkState.Timeout, PitayaNetWorkState.Error + }; + + private PitayaNetWorkState[] sessionEndStates = { + PitayaNetWorkState.Disconnected, PitayaNetWorkState.Kicked, PitayaNetWorkState.Closed + }; + + public struct PingStats + { + public uint Average; + public uint StandardDeviation; + public uint Loss; + } + + public struct ConnectionSessionStats + { + public uint Version; + public TimeSpan? SessionDurationSec; + public PingStats? Ping; + public string DisconnectionReason; + public ConnectionFailure ConnectionFailure; + public string ConnectionFailureDetails; + public TimeSpan ConnectionTime; + public string ConnectionRegion; + public Dictionary RoutesLatency; + public Dictionary RoutesStandardDeviation; + public string NetworkType; + public string LibPitayaVersion; + } + + public PitayaMetrics(MetricsCallback metricsCB = null) + { + cb = metricsCB; + } + + public void Start() + { + connectionSessionStats = new ConnectionSessionStats(); + sessionWatch.Start(); + connectionWatch.Start(); + } + + public void Update(PitayaNetWorkState state, NetworkError error) + { + // Session started + if (sessionStartedStates.Contains(state)) + { + connectionWatch.Stop(); + connectionSessionStats.ConnectionTime = connectionWatch.Elapsed; + } + + // Session ended + if (sessionEndStates.Contains(state) || sessionErroredStates.Contains(state)) + { + stop(state, error); + } + } + + private void stop(PitayaNetWorkState state=PitayaNetWorkState.Disconnected, NetworkError error=null) + { + sessionWatch.Stop(); + connectionSessionStats.SessionDurationSec = sessionWatch.Elapsed; + setDisconnectionReason(state, error); + + if (cb != null) + { + cb(connectionSessionStats); + } + } + + private void setDisconnectionReason(PitayaNetWorkState state, NetworkError error) + { + if (state == PitayaNetWorkState.Disconnected) + { + // Disconnected with errors + if (error != null) { + connectionSessionStats.DisconnectionReason = error.Error; + return; + } + + // Disconnected normally + connectionSessionStats.DisconnectionReason = disconnectionReason.ConnectionEndedNormally; + return; + } + + // Timeout trying to connect + if (state == PitayaNetWorkState.Timeout) + { + connectionSessionStats.DisconnectionReason = disconnectionReason.ConnectionTimeout; + connectionSessionStats.ConnectionFailure = ConnectionFailure.Timeout; + if (error != null) { + connectionSessionStats.ConnectionFailureDetails = error.Error; + } + return; + } + + // Disconnected due to errors + if (state == PitayaNetWorkState.Error || state == PitayaNetWorkState.FailToConnect) + { + connectionSessionStats.ConnectionFailure = ConnectionFailure.Error; + connectionSessionStats.DisconnectionReason = disconnectionReason.ConnectionErrored; + + if (state == PitayaNetWorkState.FailToConnect) + { + connectionSessionStats.DisconnectionReason = disconnectionReason.FailedToConnect; + } + + // Error string is set + if (error != null) { + connectionSessionStats.ConnectionFailureDetails = error.Error; + return; + } + + return; + } + + // Closed connection + if (state == PitayaNetWorkState.Closed) + { + connectionSessionStats.DisconnectionReason = disconnectionReason.ConnectionClosed; + return; + } + + // Kicked + if (state == PitayaNetWorkState.Kicked) + { + connectionSessionStats.DisconnectionReason = disconnectionReason.ConnectionKicked; + return; + } + } + } +} diff --git a/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs b/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs index 44751281..09bfe857 100644 --- a/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs +++ b/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs @@ -19,7 +19,7 @@ public class PitayaClientTest public void Setup() { _mainThread = Thread.CurrentThread; - _client = new PitayaClient(); + _client = new PitayaClient(metricsCallbackFunc); } [TearDown] @@ -31,6 +31,12 @@ public void TearDown() _client = null; } + private void metricsCallbackFunc(PitayaMetrics.ConnectionSessionStats connectionSessionStats) + { + UnityEngine.Debug.Log(string.Format("** REPORT **\n SessionTime = {0} | ConnectionTime = {1} | DisconnectionReason = {2} | ConnectionFailureDetails = {3}", + connectionSessionStats.SessionDurationSec, connectionSessionStats.ConnectionTime, connectionSessionStats.DisconnectionReason, connectionSessionStats.ConnectionFailureDetails)); + } + [Test] public void ShouldCreateClient() { From c09bb7e76c90fa23305c8433595fd20110e8a1df Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Fri, 6 Dec 2019 10:28:49 -0300 Subject: [PATCH 02/15] Fix styling issues --- .../Assets/Pitaya/PitayaMetrics.cs | 72 +++++++++---------- .../Assets/Pitaya/PitayaMetrics.cs.meta | 11 +++ 2 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs.meta diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs index 1cb21a78..3cf8f3be 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs @@ -10,11 +10,12 @@ namespace Pitaya { public class PitayaMetrics { - public ConnectionSessionStats connectionSessionStats; public delegate void MetricsCallback(ConnectionSessionStats stats); - private Stopwatch connectionWatch = new Stopwatch(); - private Stopwatch sessionWatch = new Stopwatch(); - private MetricsCallback cb; + + public ConnectionSessionStats connectionSessionStats; + private Stopwatch _connectionWatch = new Stopwatch(); + private Stopwatch _sessionWatch = new Stopwatch(); + private MetricsCallback _cb; public enum ConnectionFailure { @@ -23,7 +24,7 @@ public enum ConnectionFailure Timeout, } - private static class disconnectionReason + private static class DisconnectionReason { public const string ConnectionEndedNormally = "ConnectionEndedNormally"; public const string ConnectionTimeout = "ConnectionTimeout"; @@ -33,15 +34,15 @@ private static class disconnectionReason public const string ConnectionKicked = "Kicked"; } - private PitayaNetWorkState[] sessionStartedStates = { + private static readonly PitayaNetWorkState[] SessionStartedStates = { PitayaNetWorkState.Connected, PitayaNetWorkState.FailToConnect, PitayaNetWorkState.Timeout, PitayaNetWorkState.Error }; - private PitayaNetWorkState[] sessionErroredStates = { + private static readonly PitayaNetWorkState[] SessionErroredStates = { PitayaNetWorkState.FailToConnect, PitayaNetWorkState.Timeout, PitayaNetWorkState.Error }; - private PitayaNetWorkState[] sessionEndStates = { + private static readonly PitayaNetWorkState[] SessionEndStates = { PitayaNetWorkState.Disconnected, PitayaNetWorkState.Kicked, PitayaNetWorkState.Closed }; @@ -70,65 +71,64 @@ public struct ConnectionSessionStats public PitayaMetrics(MetricsCallback metricsCB = null) { - cb = metricsCB; + _cb = metricsCB; } public void Start() { connectionSessionStats = new ConnectionSessionStats(); - sessionWatch.Start(); - connectionWatch.Start(); + _sessionWatch.Start(); + _connectionWatch.Start(); } public void Update(PitayaNetWorkState state, NetworkError error) { // Session started - if (sessionStartedStates.Contains(state)) + if (SessionStartedStates.Contains(state)) { - connectionWatch.Stop(); - connectionSessionStats.ConnectionTime = connectionWatch.Elapsed; + _connectionWatch.Stop(); + connectionSessionStats.ConnectionTime = _connectionWatch.Elapsed; } // Session ended - if (sessionEndStates.Contains(state) || sessionErroredStates.Contains(state)) + if (SessionEndStates.Contains(state) || SessionErroredStates.Contains(state)) { - stop(state, error); + Stop(state, error); } } - private void stop(PitayaNetWorkState state=PitayaNetWorkState.Disconnected, NetworkError error=null) + private void Stop(PitayaNetWorkState state=PitayaNetWorkState.Disconnected, NetworkError error=null) { - sessionWatch.Stop(); - connectionSessionStats.SessionDurationSec = sessionWatch.Elapsed; - setDisconnectionReason(state, error); + _sessionWatch.Stop(); + connectionSessionStats.SessionDurationSec = _sessionWatch.Elapsed; + SetDisconnectionReason(state, error); - if (cb != null) - { - cb(connectionSessionStats); - } + _cb?.Invoke(connectionSessionStats); } - private void setDisconnectionReason(PitayaNetWorkState state, NetworkError error) + private void SetDisconnectionReason(PitayaNetWorkState state, NetworkError error) { if (state == PitayaNetWorkState.Disconnected) { // Disconnected with errors - if (error != null) { + if (error != null) + { connectionSessionStats.DisconnectionReason = error.Error; return; } // Disconnected normally - connectionSessionStats.DisconnectionReason = disconnectionReason.ConnectionEndedNormally; + connectionSessionStats.DisconnectionReason = DisconnectionReason.ConnectionEndedNormally; return; } // Timeout trying to connect if (state == PitayaNetWorkState.Timeout) { - connectionSessionStats.DisconnectionReason = disconnectionReason.ConnectionTimeout; + connectionSessionStats.DisconnectionReason = DisconnectionReason.ConnectionTimeout; connectionSessionStats.ConnectionFailure = ConnectionFailure.Timeout; - if (error != null) { + if (error != null) + { connectionSessionStats.ConnectionFailureDetails = error.Error; } return; @@ -138,34 +138,34 @@ private void setDisconnectionReason(PitayaNetWorkState state, NetworkError error if (state == PitayaNetWorkState.Error || state == PitayaNetWorkState.FailToConnect) { connectionSessionStats.ConnectionFailure = ConnectionFailure.Error; - connectionSessionStats.DisconnectionReason = disconnectionReason.ConnectionErrored; + connectionSessionStats.DisconnectionReason = DisconnectionReason.ConnectionErrored; if (state == PitayaNetWorkState.FailToConnect) { - connectionSessionStats.DisconnectionReason = disconnectionReason.FailedToConnect; + connectionSessionStats.DisconnectionReason = DisconnectionReason.FailedToConnect; } // Error string is set - if (error != null) { + if (error != null) + { connectionSessionStats.ConnectionFailureDetails = error.Error; return; } - + return; } // Closed connection if (state == PitayaNetWorkState.Closed) { - connectionSessionStats.DisconnectionReason = disconnectionReason.ConnectionClosed; + connectionSessionStats.DisconnectionReason = DisconnectionReason.ConnectionClosed; return; } // Kicked if (state == PitayaNetWorkState.Kicked) { - connectionSessionStats.DisconnectionReason = disconnectionReason.ConnectionKicked; - return; + connectionSessionStats.DisconnectionReason = DisconnectionReason.ConnectionKicked; } } } diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs.meta b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs.meta new file mode 100644 index 00000000..67e921a5 --- /dev/null +++ b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a081eb48df302442e8b66553ade668fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 7d84e7bc2f33ecfd5a88d813e51c322067ead9d1 Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Fri, 6 Dec 2019 17:20:57 -0300 Subject: [PATCH 03/15] Add method to get owned version string from pitaya --- include/pitaya.h | 6 +++++- src/pc_lib.c | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/include/pitaya.h b/include/pitaya.h index 44f6493d..78d7055c 100644 --- a/include/pitaya.h +++ b/include/pitaya.h @@ -165,6 +165,11 @@ typedef struct { PC_EXPORT int pc_lib_version(void); PC_EXPORT const char* pc_lib_version_str(void); +// This version of the function returns an owned version string, +// that the application has to call free on. This is useful for interop with +// C#, since it will always free a returned string from a unmanaged function. +PC_EXPORT const char* pc_lib_version_owned_str(void); + /** * If you do use default log callback, * this function will change the level of log out. @@ -363,7 +368,6 @@ PC_EXPORT int tr_uv_tls_set_ca_file(const char* ca_file, const char* ca_path); * Macro implementation */ #define pc_lib_version() PC_VERSION_NUM -#define pc_lib_version_str() PC_VERSION_STR #ifdef __cplusplus } diff --git a/src/pc_lib.c b/src/pc_lib.c index ddcc5b26..cc1e962f 100644 --- a/src/pc_lib.c +++ b/src/pc_lib.c @@ -460,3 +460,13 @@ void pc_lib_skip_key_pin_check(bool should_skip) { pc__skip_key_pin_check = should_skip; } + +const char *pc_lib_version_str(void) +{ + return PC_VERSION_STR; +} + +const char* pc_lib_version_owned_str(void) +{ + return pc_lib_strdup(pc_lib_version_str()); +} From cb5d7cbab2737f2d7058d4a87b3375b215e8d5e7 Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Fri, 6 Dec 2019 17:23:40 -0300 Subject: [PATCH 04/15] Get library version and specify calling convention for unmanaged functions. --- .../Assets/Pitaya/PitayaBinding.cs | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaBinding.cs b/unity/PitayaExample/Assets/Pitaya/PitayaBinding.cs index d4f7b3ec..a21a83c0 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaBinding.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaBinding.cs @@ -69,6 +69,11 @@ private static void DLog(object data) Debug.Log(data); } } + + public static string Version + { + get { return NativeLibVersion(); } + } static PitayaBinding() { @@ -546,81 +551,82 @@ private static void LogFunction(PitayaLogLevel level, string msg) #else private const string LibName = "libpitaya-linux"; #endif + [DllImport(LibName, EntryPoint = "pc_lib_version_owned_str", CallingConvention = CallingConvention.Cdecl)] + private static extern string NativeLibVersion(); - // ReSharper disable UnusedMember.Local - [DllImport(LibName, EntryPoint = "tr_uv_tls_set_ca_file")] + [DllImport(LibName, EntryPoint = "tr_uv_tls_set_ca_file", CallingConvention = CallingConvention.Cdecl)] private static extern void NativeSetCertificatePath(string caFile, string caPath); - [DllImport(LibName, EntryPoint = "pc_unity_lib_init")] + [DllImport(LibName, EntryPoint = "pc_unity_lib_init", CallingConvention = CallingConvention.Cdecl)] private static extern void NativeLibInit(int logLevel, string caFile, string caPath, NativeAssertCallback assert, string platform, string buildNumber, string version); - [DllImport(LibName, EntryPoint = "pc_lib_set_default_log_level")] + [DllImport(LibName, EntryPoint = "pc_lib_set_default_log_level", CallingConvention = CallingConvention.Cdecl)] private static extern void NativeLibSetLogLevel(int logLevel); - [DllImport(LibName, EntryPoint = "pc_client_ev_str")] + [DllImport(LibName, EntryPoint = "pc_client_ev_str", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr NativeEvToStr(int ev); - [DllImport(LibName, EntryPoint = "pc_client_rc_str")] + [DllImport(LibName, EntryPoint = "pc_client_rc_str", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr NativeRcToStr(int rc); - [DllImport(LibName, EntryPoint = "pc_unity_create")] + [DllImport(LibName, EntryPoint = "pc_unity_create", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr NativeCreate(bool enableTls, bool enablePoll, bool enableReconnect, int connTimeout); - [DllImport(LibName, EntryPoint = "pc_unity_destroy")] + [DllImport(LibName, EntryPoint = "pc_unity_destroy", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeDestroy(IntPtr client); - [DllImport(LibName, EntryPoint = "pc_client_connect")] + [DllImport(LibName, EntryPoint = "pc_client_connect", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeConnect(IntPtr client, string host, int port, string handshakeOpts); - [DllImport(LibName, EntryPoint = "pc_client_disconnect")] + [DllImport(LibName, EntryPoint = "pc_client_disconnect", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeDisconnect(IntPtr client); - [DllImport(LibName, EntryPoint = "pc_unity_request")] + [DllImport(LibName, EntryPoint = "pc_unity_request", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeRequest(IntPtr client, string route, string msg, uint cbUid, int timeout, NativeRequestCallback callback, NativeErrorCallback errorCallback); - [DllImport(LibName, EntryPoint = "pc_unity_binary_request")] + [DllImport(LibName, EntryPoint = "pc_unity_binary_request", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeBinaryRequest(IntPtr client, string route, byte[] data, long len, uint cbUid, int timeout, NativeRequestCallback callback, NativeErrorCallback errorCallback); - [DllImport(LibName, EntryPoint = "pc_string_notify_with_timeout")] + [DllImport(LibName, EntryPoint = "pc_string_notify_with_timeout", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeNotify(IntPtr client, string route, string msg, IntPtr exData, int timeout, NativeNotifyCallback callback); - [DllImport(LibName, EntryPoint = "pc_binary_notify_with_timeout")] + [DllImport(LibName, EntryPoint = "pc_binary_notify_with_timeout", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeBinaryNotify(IntPtr client, string route, byte[] data, long len, IntPtr exData, int timeout, NativeNotifyCallback callback); - [DllImport(LibName, EntryPoint = "pc_client_poll")] + [DllImport(LibName, EntryPoint = "pc_client_poll", CallingConvention = CallingConvention.Cdecl)] private static extern int NativePoll(IntPtr client); - [DllImport(LibName, EntryPoint = "pc_client_add_ev_handler")] + [DllImport(LibName, EntryPoint = "pc_client_add_ev_handler", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeAddEventHandler(IntPtr client, NativeEventCallback callback, IntPtr exData, IntPtr destructor); - [DllImport(LibName, EntryPoint = "pc_client_set_push_handler")] + [DllImport(LibName, EntryPoint = "pc_client_set_push_handler", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeAddPushHandler(IntPtr client, NativePushCallback callback); - [DllImport(LibName, EntryPoint = "pc_client_rm_ev_handler")] + [DllImport(LibName, EntryPoint = "pc_client_rm_ev_handler", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeRemoveEventHandler(IntPtr client, int handlerId); - [DllImport(LibName, EntryPoint = "pc_client_conn_quality")] + [DllImport(LibName, EntryPoint = "pc_client_conn_quality", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeQuality(IntPtr client); - [DllImport(LibName, EntryPoint = "pc_client_state")] + [DllImport(LibName, EntryPoint = "pc_client_state", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeState(IntPtr client); - [DllImport(LibName, EntryPoint = "pc_client_serializer")] + [DllImport(LibName, EntryPoint = "pc_client_serializer", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr NativeSerializer(IntPtr client); - [DllImport(LibName, EntryPoint = "pc_client_free_serializer")] + [DllImport(LibName, EntryPoint = "pc_client_free_serializer", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr NativeFreeSerializer(IntPtr serializer); // ReSharper restore UnusedMember.Local - [DllImport(LibName, EntryPoint = "pc_lib_add_pinned_public_key_from_certificate_string")] + [DllImport(LibName, EntryPoint = "pc_lib_add_pinned_public_key_from_certificate_string", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeAddPinnedPublicKeyFromCertificateString(string ca_string); - [DllImport(LibName, EntryPoint = "pc_lib_add_pinned_public_key_from_certificate_file")] + [DllImport(LibName, EntryPoint = "pc_lib_add_pinned_public_key_from_certificate_file", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeAddPinnedPublicKeyFromCertificateFile(string caPath); - [DllImport(LibName, EntryPoint = "pc_lib_skip_key_pin_check")] + [DllImport(LibName, EntryPoint = "pc_lib_skip_key_pin_check", CallingConvention = CallingConvention.Cdecl)] private static extern void NativeSkipKeyPinCheck(bool shouldSkip); - [DllImport(LibName, EntryPoint = "pc_lib_clear_pinned_public_keys")] + [DllImport(LibName, EntryPoint = "pc_lib_clear_pinned_public_keys", CallingConvention = CallingConvention.Cdecl)] private static extern void NativeClearPinnedPublicKeys(); - [DllImport(LibName, EntryPoint = "pc_unity_init_log_function")] + [DllImport(LibName, EntryPoint = "pc_unity_init_log_function", CallingConvention = CallingConvention.Cdecl)] private static extern int NativeInitLogFunction(NativeLogFunction fn); #if UNITY_IPHONE && !UNITY_EDITOR [DllImport("__Internal")] From 73b1aa86ef86e5c57ee0fa7f43dfb06e929e859b Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Fri, 6 Dec 2019 17:28:09 -0300 Subject: [PATCH 05/15] Rename names to be more idiomatic --- .../Assets/Pitaya/PitayaClient.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs b/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs index 892d6cca..d426cd97 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs @@ -10,7 +10,7 @@ public class PitayaClient : IDisposable, IPitayaListener { public event Action NetWorkStateChangedEvent; - private const int DEFAULT_CONNECTION_TIMEOUT = 30; + private const int DefaultConnectionTimeout = 30; private IntPtr _client = IntPtr.Zero; private PitayaMetrics _metricsAggr; @@ -23,7 +23,7 @@ public class PitayaClient : IDisposable, IPitayaListener public PitayaClient() { - Init(null, false, false, false, DEFAULT_CONNECTION_TIMEOUT, null); + Init(null, false, false, false, DefaultConnectionTimeout, null); } public PitayaClient(int connectionTimeout) @@ -33,17 +33,17 @@ public PitayaClient(int connectionTimeout) public PitayaClient(string certificateName = null) { - Init(certificateName, certificateName != null, false, false, DEFAULT_CONNECTION_TIMEOUT, null); + Init(certificateName, certificateName != null, false, false, DefaultConnectionTimeout, null); } - public PitayaClient(PitayaMetrics.MetricsCallback metricsCB = null) + public PitayaClient(PitayaMetrics.MetricsCallback metricsCb = null) { - Init(null, false, false, false, DEFAULT_CONNECTION_TIMEOUT, metricsCB); + Init(null, false, false, false, DefaultConnectionTimeout, metricsCb); } - public PitayaClient(bool enableReconnect = false, string certificateName = null, int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT, PitayaMetrics.MetricsCallback metricsCB = null) + public PitayaClient(bool enableReconnect = false, string certificateName = null, int connectionTimeout = DefaultConnectionTimeout, PitayaMetrics.MetricsCallback metricsCb = null) { - Init(certificateName, certificateName != null, false, enableReconnect, DEFAULT_CONNECTION_TIMEOUT, metricsCB); + Init(certificateName, certificateName != null, false, enableReconnect, DefaultConnectionTimeout, metricsCb); } ~PitayaClient() @@ -51,13 +51,13 @@ public PitayaClient(bool enableReconnect = false, string certificateName = null, Dispose(); } - private void Init(string certificateName, bool enableTlS, bool enablePolling, bool enableReconnect, int connTimeout, PitayaMetrics.MetricsCallback metricsCB) + private void Init(string certificateName, bool enableTlS, bool enablePolling, bool enableReconnect, int connTimeout, PitayaMetrics.MetricsCallback metricsCb) { _eventManager = new EventManager(); _typeRequestSubscriber = new TypeSubscriber(); _typePushSubscriber = new TypeSubscriber(); _client = PitayaBinding.CreateClient(enableTlS, enablePolling, enableReconnect, connTimeout, this); - _metricsAggr = new PitayaMetrics(metricsCB); + _metricsAggr = new PitayaMetrics(metricsCb); if (certificateName != null) { From 64489c825ecb79bcaba16a1b68b51ae56ab4d785 Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Fri, 6 Dec 2019 17:28:34 -0300 Subject: [PATCH 06/15] Reduce memory allocations --- .../Assets/Pitaya/PitayaClient.cs | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs b/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs index d426cd97..65589609 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs @@ -126,9 +126,12 @@ public void Request(string route, IMessage msg, int timeout, Action action _reqUid++; _typeRequestSubscriber.Subscribe(_reqUid, typeof(T)); - Action responseAction = res => { action((T) res); }; + void ResponseAction(object res) + { + action((T) res); + } - _eventManager.AddCallBack(_reqUid, responseAction, errorAction); + _eventManager.AddCallBack(_reqUid, ResponseAction, errorAction); var serializer = PitayaBinding.ClientSerializer(_client); @@ -138,9 +141,13 @@ public void Request(string route, IMessage msg, int timeout, Action action public void Request(string route, string msg, int timeout, Action action, Action errorAction) { _reqUid++; - Action responseAction = res => { action((string) res); }; - _eventManager.AddCallBack(_reqUid, responseAction, errorAction); + void ResponseAction(object res) + { + action((string) res); + } + + _eventManager.AddCallBack(_reqUid, ResponseAction, errorAction); PitayaBinding.Request(_client, route,JsonSerializer.Encode(msg), _reqUid, timeout); } @@ -168,17 +175,25 @@ public void Notify(string route, int timeout, string msg) public void OnRoute(string route, Action action) { - Action responseAction = res => { action((string) res); }; - _eventManager.AddOnRouteEvent(route, responseAction); + void ResponseAction(object res) + { + action((string) res); + } + + _eventManager.AddOnRouteEvent(route, ResponseAction); } // start listening to a route public void OnRoute(string route, Action action) { _typePushSubscriber.Subscribe(route, typeof(T)); - Action responseAction = res => { action((T) res); }; - _eventManager.AddOnRouteEvent(route, responseAction); + void ResponseAction(object res) + { + action((T) res); + } + + _eventManager.AddOnRouteEvent(route, ResponseAction); } public void OffRoute(string route) @@ -233,7 +248,7 @@ public void OnUserDefinedPush(string route, byte[] serializedBody) decoded = JsonSerializer.Decode(serializedBody); } - _eventManager.InvokeOnEvent(route, decoded); + _eventManager.InvokeOnEvent(route, decoded); } public void Dispose() From 935cfbe2cde0a72a7ef2668d7d76ee6b1c1d8179 Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Fri, 6 Dec 2019 17:28:58 -0300 Subject: [PATCH 07/15] Remove unused events --- unity/PitayaExample/Assets/Pitaya/PitayaConstants.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaConstants.cs b/unity/PitayaExample/Assets/Pitaya/PitayaConstants.cs index 6dff8b52..2369ed86 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaConstants.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaConstants.cs @@ -30,12 +30,9 @@ public NetworkError(string error, string description) public enum PitayaNetWorkState { - Closed, - Connecting, FailToConnect, Connected, Disconnected, - Timeout, Error, Kicked } From 54df53ef52bfe0b549f01a5054cc3f93b4ff4e9e Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Fri, 6 Dec 2019 17:30:30 -0300 Subject: [PATCH 08/15] Add more information to metrics and also call them when the client is disposedd --- .../Assets/Pitaya/PitayaClient.cs | 7 +- .../Assets/Pitaya/PitayaMetrics.cs | 250 +++++++++++------- 2 files changed, 157 insertions(+), 100 deletions(-) diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs b/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs index 65589609..7f5b980b 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs @@ -231,8 +231,8 @@ public void OnRequestError(uint rid, PitayaError error) public void OnNetworkEvent(PitayaNetWorkState state, NetworkError error) { - if(NetWorkStateChangedEvent != null ) NetWorkStateChangedEvent.Invoke(state, error); _metricsAggr.Update(state, error); + if(NetWorkStateChangedEvent != null ) NetWorkStateChangedEvent.Invoke(state, error); } public void OnUserDefinedPush(string route, byte[] serializedBody) @@ -261,6 +261,11 @@ public void Dispose() _reqUid = 0; PitayaBinding.Disconnect(_client); + + // We simulate a disconnect to the metrics aggregator. This is necessary because the dispose is called + // before the disconnect event can be fired. + _metricsAggr.Update(PitayaNetWorkState.Disconnected, null); + PitayaBinding.Dispose(_client); _client = IntPtr.Zero; diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs index 3cf8f3be..7ea613ee 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs @@ -5,6 +5,7 @@ using Google.Protobuf; using UnityEngine; using System.Linq; +using UnityEngine.Assertions; namespace Pitaya { @@ -12,40 +13,35 @@ public class PitayaMetrics { public delegate void MetricsCallback(ConnectionSessionStats stats); - public ConnectionSessionStats connectionSessionStats; - private Stopwatch _connectionWatch = new Stopwatch(); - private Stopwatch _sessionWatch = new Stopwatch(); - private MetricsCallback _cb; - - public enum ConnectionFailure + private enum State { - CouldNotResolveHost, - Error, - Timeout, + NotConnected, + Connecting, + Connected } - private static class DisconnectionReason + // The current version of the event that is being sent. This value should always increase when the + // format of the struct changes. + private const uint EventVersion = 1; + + // The current state of the pitaya connection. + private State _state; + + private ConnectionSessionStats _connectionSessionStats; + private readonly Stopwatch _connectionWatch; + private readonly Stopwatch _sessionWatch; + private readonly MetricsCallback _cb; + private bool _kickReceived; + + private static class ConnectionFinishReason { - public const string ConnectionEndedNormally = "ConnectionEndedNormally"; - public const string ConnectionTimeout = "ConnectionTimeout"; + public const string UserRequest = "UserRequest"; public const string FailedToConnect = "FailedToConnect"; - public const string ConnectionErrored = "ConnectionErrored"; - public const string ConnectionClosed = "ConnectionClosed"; - public const string ConnectionKicked = "Kicked"; + public const string ConnectionError = "ConnectionError"; + public const string Kick = "Kick"; + public const string UnknownError = "UnknownError"; } - private static readonly PitayaNetWorkState[] SessionStartedStates = { - PitayaNetWorkState.Connected, PitayaNetWorkState.FailToConnect, PitayaNetWorkState.Timeout, PitayaNetWorkState.Error - }; - - private static readonly PitayaNetWorkState[] SessionErroredStates = { - PitayaNetWorkState.FailToConnect, PitayaNetWorkState.Timeout, PitayaNetWorkState.Error - }; - - private static readonly PitayaNetWorkState[] SessionEndStates = { - PitayaNetWorkState.Disconnected, PitayaNetWorkState.Kicked, PitayaNetWorkState.Closed - }; - public struct PingStats { public uint Average; @@ -55,118 +51,174 @@ public struct PingStats public struct ConnectionSessionStats { + // TODO(lhahn): Consider the case where multiple clients are created, should a session contain an ID? + // Or should a pitaya client contain an id as well to distinguish different client instances? public uint Version; - public TimeSpan? SessionDurationSec; + public double? SessionDurationSec; public PingStats? Ping; - public string DisconnectionReason; - public ConnectionFailure ConnectionFailure; - public string ConnectionFailureDetails; - public TimeSpan ConnectionTime; + public string ConnectionFinishReason; + public string ConnectionFinishDetails; + public double ConnectionTimeMs; public string ConnectionRegion; public Dictionary RoutesLatency; public Dictionary RoutesStandardDeviation; public string NetworkType; public string LibPitayaVersion; + public uint ServerInvalidPackages; + + public string Serialize() + { + return SimpleJson.SimpleJson.SerializeObject(this); + } } public PitayaMetrics(MetricsCallback metricsCB = null) { _cb = metricsCB; + _state = State.NotConnected; + _connectionWatch = new Stopwatch(); + _sessionWatch = new Stopwatch(); + _kickReceived = false; } public void Start() { - connectionSessionStats = new ConnectionSessionStats(); - _sessionWatch.Start(); + _connectionSessionStats = DefaultConnectionSessionStats(); _connectionWatch.Start(); + _state = State.Connecting; + _kickReceived = false; } - public void Update(PitayaNetWorkState state, NetworkError error) + public void Update(PitayaNetWorkState pitayaState, NetworkError error) { - // Session started - if (SessionStartedStates.Contains(state)) + switch (_state) { - _connectionWatch.Stop(); - connectionSessionStats.ConnectionTime = _connectionWatch.Elapsed; + case State.NotConnected: + UpdateNotConnectedState(pitayaState, error); + break; + case State.Connecting: + UpdateConnectingState(pitayaState, error); + break; + case State.Connected: + UpdateConnectedState(pitayaState, error); + break; + default: + throw new ArgumentOutOfRangeException(); } + } + + private void UpdateConnectedState(PitayaNetWorkState pitayaState, NetworkError pitayaErr) + { + Assert.IsTrue(_state == State.Connected); - // Session ended - if (SessionEndStates.Contains(state) || SessionErroredStates.Contains(state)) + switch (pitayaState) { - Stop(state, error); + case PitayaNetWorkState.Kicked: + // LibPitaya sends a Kicked event and after that a Disconnected event. Therefore, + // we do not close the session yet, we just signal that a kick was received. + _kickReceived = true; + break; + case PitayaNetWorkState.Disconnected: + if (pitayaErr == null) + { + _connectionSessionStats.ConnectionFinishReason = _kickReceived + ? ConnectionFinishReason.Kick + : ConnectionFinishReason.UserRequest; + } + else + { + _connectionSessionStats.ConnectionFinishReason = ConnectionFinishReason.ConnectionError; + _connectionSessionStats.ConnectionFinishDetails = GetErrorDetails(pitayaErr); + } + StopSession(); + break; + case PitayaNetWorkState.Error: + // This event only happens when unknown data from the server was sent, so we just increment the counter. + _connectionSessionStats.ServerInvalidPackages++; + break; + default: + throw new Exception(string.Format("PitayaMetrics received pitaya state {0} when in not connected state", pitayaState)); } } - private void Stop(PitayaNetWorkState state=PitayaNetWorkState.Disconnected, NetworkError error=null) + private void UpdateNotConnectedState(PitayaNetWorkState pitayaState, NetworkError pitayaErr) { - _sessionWatch.Stop(); - connectionSessionStats.SessionDurationSec = _sessionWatch.Elapsed; - SetDisconnectionReason(state, error); - - _cb?.Invoke(connectionSessionStats); + Assert.IsTrue(_state == State.NotConnected); + throw new Exception(string.Format("PitayaMetrics received pitaya state {0} when in not connected state", pitayaState)); } - private void SetDisconnectionReason(PitayaNetWorkState state, NetworkError error) + private void UpdateConnectingState(PitayaNetWorkState pitayaState, NetworkError pitayaErr) { - if (state == PitayaNetWorkState.Disconnected) - { - // Disconnected with errors - if (error != null) - { - connectionSessionStats.DisconnectionReason = error.Error; - return; - } - - // Disconnected normally - connectionSessionStats.DisconnectionReason = DisconnectionReason.ConnectionEndedNormally; - return; - } + Assert.IsTrue(_state == State.Connecting); - // Timeout trying to connect - if (state == PitayaNetWorkState.Timeout) + switch (pitayaState) { - connectionSessionStats.DisconnectionReason = DisconnectionReason.ConnectionTimeout; - connectionSessionStats.ConnectionFailure = ConnectionFailure.Timeout; - if (error != null) - { - connectionSessionStats.ConnectionFailureDetails = error.Error; - } - return; + case PitayaNetWorkState.Connected: + // If the connection connected successfully, we start the session stopwatch and + // stop the connection watch. + _connectionWatch.Stop(); + _connectionSessionStats.ConnectionTimeMs = _connectionWatch.Elapsed.TotalMilliseconds; + _connectionWatch.Reset(); + _sessionWatch.Start(); + + _state = State.Connected; + break; + case PitayaNetWorkState.FailToConnect: + // If the connection failed while we were trying to connect, we should close the session with + // this information. + _connectionSessionStats.ConnectionFinishReason = ConnectionFinishReason.FailedToConnect; + _connectionSessionStats.ConnectionFinishDetails = GetErrorDetails(pitayaErr); + StopSession(); + break; + case PitayaNetWorkState.Error: + // This event only happens when unknown data from the server was sent, so we just increment the counter. + _connectionSessionStats.ServerInvalidPackages++; + break; + default: + throw new Exception(string.Format("PitayaMetrics received pitaya state {0} when in not connected state", pitayaState)); } + } - // Disconnected due to errors - if (state == PitayaNetWorkState.Error || state == PitayaNetWorkState.FailToConnect) - { - connectionSessionStats.ConnectionFailure = ConnectionFailure.Error; - connectionSessionStats.DisconnectionReason = DisconnectionReason.ConnectionErrored; - - if (state == PitayaNetWorkState.FailToConnect) - { - connectionSessionStats.DisconnectionReason = DisconnectionReason.FailedToConnect; - } - - // Error string is set - if (error != null) - { - connectionSessionStats.ConnectionFailureDetails = error.Error; - return; - } - - return; - } + private void StopSession() + { + _sessionWatch.Stop(); + _connectionSessionStats.SessionDurationSec = _sessionWatch.Elapsed.TotalSeconds; + _sessionWatch.Reset(); + + _cb(_connectionSessionStats); + _connectionSessionStats = DefaultConnectionSessionStats(); + _kickReceived = false; + _state = State.NotConnected; + } - // Closed connection - if (state == PitayaNetWorkState.Closed) + private ConnectionSessionStats DefaultConnectionSessionStats() + { + return new ConnectionSessionStats { - connectionSessionStats.DisconnectionReason = DisconnectionReason.ConnectionClosed; - return; - } + Version = EventVersion, + ServerInvalidPackages = 0, + NetworkType = GetNetworkType(), + LibPitayaVersion = PitayaBinding.Version, + // TODO(lhahn): remove hardcoded region here and use something better. + ConnectionRegion = "NA" + }; + } - // Kicked - if (state == PitayaNetWorkState.Kicked) + private static string GetNetworkType() + { + switch (Application.internetReachability) { - connectionSessionStats.DisconnectionReason = DisconnectionReason.ConnectionKicked; + case NetworkReachability.NotReachable: return "not-reachable"; + case NetworkReachability.ReachableViaCarrierDataNetwork: return "data"; + case NetworkReachability.ReachableViaLocalAreaNetwork: return "wifi"; + default: throw new ArgumentOutOfRangeException(); } } + + private static string GetErrorDetails(NetworkError e) + { + Assert.IsNotNull(e, "error should not be null"); + return string.Format("{0}: {1}", e.Error, e.Description); + } } } From 759481680f2e7e00038805f333a4d978d2cfaefe Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Fri, 6 Dec 2019 17:30:54 -0300 Subject: [PATCH 09/15] Fix tests --- .../PitayaExample/Assets/Tests/PitayaClientTest.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs b/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs index 09bfe857..78db6382 100644 --- a/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs +++ b/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs @@ -19,7 +19,7 @@ public class PitayaClientTest public void Setup() { _mainThread = Thread.CurrentThread; - _client = new PitayaClient(metricsCallbackFunc); + _client = new PitayaClient(MetricsCallbackFunc); } [TearDown] @@ -31,10 +31,15 @@ public void TearDown() _client = null; } - private void metricsCallbackFunc(PitayaMetrics.ConnectionSessionStats connectionSessionStats) + private static void MetricsCallbackFunc(PitayaMetrics.ConnectionSessionStats connectionSessionStats) { - UnityEngine.Debug.Log(string.Format("** REPORT **\n SessionTime = {0} | ConnectionTime = {1} | DisconnectionReason = {2} | ConnectionFailureDetails = {3}", - connectionSessionStats.SessionDurationSec, connectionSessionStats.ConnectionTime, connectionSessionStats.DisconnectionReason, connectionSessionStats.ConnectionFailureDetails)); + UnityEngine.Debug.Log(string.Format( + "** REPORT **\n SessionTime = {0} | ConnectionTime = {1} | DisconnectionReason = {2} | ConnectionFailureDetails = {3}", + connectionSessionStats.SessionDurationSec, + connectionSessionStats.ConnectionTimeMs, + connectionSessionStats.ConnectionFinishReason, + connectionSessionStats.ConnectionFinishDetails + )); } [Test] From ae7dfccea05a375e71372e6dc50f729ae5667571 Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Mon, 9 Dec 2019 16:17:21 -0300 Subject: [PATCH 10/15] Aggregate latency and standard deviation for routes --- unity/PitayaExample/Assets/Example.cs | 30 ++- .../Assets/Pitaya/PitayaClient.cs | 40 ++- .../Assets/Pitaya/PitayaMetrics.cs | 236 ++++++++++++++---- 3 files changed, 238 insertions(+), 68 deletions(-) diff --git a/unity/PitayaExample/Assets/Example.cs b/unity/PitayaExample/Assets/Example.cs index c2c3afeb..e61f3c64 100644 --- a/unity/PitayaExample/Assets/Example.cs +++ b/unity/PitayaExample/Assets/Example.cs @@ -2,6 +2,8 @@ using System.IO; using UnityEngine; using Pitaya; +using Pitaya.SimpleJson; +using UnityEngine.UI; public class Example : MonoBehaviour { @@ -9,11 +11,30 @@ public class Example : MonoBehaviour private bool _connected; private bool _requestSent; + public Button GetDataButton; + // Use this for initialization private void Start() { + GetDataButton.onClick.AddListener(() => + { + _client.Request("connector.getsessiondata", + action: data => + { + Debug.LogFormat("GetSessionData: {0}", data); + }, + errorAction: err => + { + Debug.LogFormat("GetSessionData Error: {0}", err); + }); + }); + // _client = new PitayaClient("ca.crt"); - _client = new PitayaClient(); + _client = new PitayaClient(metricsCb: stats => + { + Debug.Log("=========> Received connection stats!"); + Debug.Log(stats.Serialize()); + }); _connected = false; _requestSent = false; @@ -30,7 +51,7 @@ private void Start() } }; - _client.Connect("a1d127034f31611e8858512b1bea90da-838011280.us-east-1.elb.amazonaws.com", 3251, + _client.Connect("libpitaya-tests.tfgco.com", 3251, new Dictionary { {"oi", "mano"} @@ -43,12 +64,11 @@ private void Update() if (_connected && !_requestSent) { _client.Request("connector.getsessiondata", - (data) => + action: data => { Debug.Log("Got request data: " + data); - File.WriteAllText("/Users/lhahn/Downloads/OH_MY_GOD.txt", "I Got the request data: " + data); }, - (err) => + errorAction: (err) => { Debug.LogError("Got error: code = " + err.Code + ", msg = " + err.Msg); }); diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs b/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs index 7f5b980b..7923265d 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs @@ -57,7 +57,11 @@ private void Init(string certificateName, bool enableTlS, bool enablePolling, bo _typeRequestSubscriber = new TypeSubscriber(); _typePushSubscriber = new TypeSubscriber(); _client = PitayaBinding.CreateClient(enableTlS, enablePolling, enableReconnect, connTimeout, this); - _metricsAggr = new PitayaMetrics(metricsCb); + + if (metricsCb != null) + { + _metricsAggr = new PitayaMetrics(metricsCb); + } if (certificateName != null) { @@ -90,13 +94,13 @@ public PitayaClientState State public void Connect(string host, int port, string handshakeOpts = null) { - _metricsAggr.Start(); + if (_metricsAggr != null) _metricsAggr.Start(); PitayaBinding.Connect(_client, host, port, handshakeOpts); } public void Connect(string host, int port, Dictionary handshakeOpts) { - _metricsAggr.Start(); + if (_metricsAggr != null) _metricsAggr.Start(); var opts = Pitaya.SimpleJson.SimpleJson.SerializeObject(handshakeOpts); PitayaBinding.Connect(_client, host, port, opts); } @@ -123,15 +127,24 @@ public void Request(string route, IMessage msg, Action action, Action(string route, IMessage msg, int timeout, Action action, Action errorAction) { + if (_metricsAggr != null) _metricsAggr.StartRecordingRequest(route); + _reqUid++; _typeRequestSubscriber.Subscribe(_reqUid, typeof(T)); void ResponseAction(object res) { + if (_metricsAggr != null) _metricsAggr.StopRecordingRequest(route); action((T) res); } - _eventManager.AddCallBack(_reqUid, ResponseAction, errorAction); + void ErrorAction(PitayaError err) + { + if (_metricsAggr != null) _metricsAggr.StopRecordingRequest(route, err); + errorAction(err); + } + + _eventManager.AddCallBack(_reqUid, ResponseAction, ErrorAction); var serializer = PitayaBinding.ClientSerializer(_client); @@ -140,14 +153,23 @@ void ResponseAction(object res) public void Request(string route, string msg, int timeout, Action action, Action errorAction) { + if (_metricsAggr != null) _metricsAggr.StartRecordingRequest(route); + _reqUid++; void ResponseAction(object res) { + _metricsAggr.StopRecordingRequest(route); action((string) res); } - _eventManager.AddCallBack(_reqUid, ResponseAction, errorAction); + void ErrorAction(PitayaError err) + { + _metricsAggr.StopRecordingRequest(route, err); + errorAction(err); + } + + _eventManager.AddCallBack(_reqUid, ResponseAction, ErrorAction); PitayaBinding.Request(_client, route,JsonSerializer.Encode(msg), _reqUid, timeout); } @@ -231,7 +253,7 @@ public void OnRequestError(uint rid, PitayaError error) public void OnNetworkEvent(PitayaNetWorkState state, NetworkError error) { - _metricsAggr.Update(state, error); + if (_metricsAggr != null) _metricsAggr.Update(state, error); if(NetWorkStateChangedEvent != null ) NetWorkStateChangedEvent.Invoke(state, error); } @@ -261,11 +283,7 @@ public void Dispose() _reqUid = 0; PitayaBinding.Disconnect(_client); - - // We simulate a disconnect to the metrics aggregator. This is necessary because the dispose is called - // before the disconnect event can be fired. - _metricsAggr.Update(PitayaNetWorkState.Disconnected, null); - + if (_metricsAggr != null) _metricsAggr.ForceStop(); PitayaBinding.Dispose(_client); _client = IntPtr.Zero; diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs index 7ea613ee..0b7ff836 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs @@ -1,10 +1,7 @@ using System; using System.Diagnostics; using System.Collections.Generic; -using System.IO; -using Google.Protobuf; using UnityEngine; -using System.Linq; using UnityEngine.Assertions; namespace Pitaya @@ -13,6 +10,21 @@ public class PitayaMetrics { public delegate void MetricsCallback(ConnectionSessionStats stats); + public enum PingStatus + { + Ok, + Timeout, + Error + } + + private class RequestRecording + { + public List LatenciesMs = new List(); + public Stopwatch Watch = new Stopwatch(); + } + + public delegate void SendPing(Action onPingDone); + private enum State { NotConnected, @@ -20,18 +32,29 @@ private enum State Connected } + private class ConnectingState + { + public Stopwatch ConnectionWatch = new Stopwatch(); + } + + private class ConnectedState + { + public Stopwatch SessionWatch = new Stopwatch(); + public bool KickReceived = false; + } + // The current version of the event that is being sent. This value should always increase when the // format of the struct changes. private const uint EventVersion = 1; - + // The current state of the pitaya connection. private State _state; + private ConnectedState _connectedState; + private ConnectingState _connectingState; + private Dictionary _requestsLatencies = new Dictionary(15); private ConnectionSessionStats _connectionSessionStats; - private readonly Stopwatch _connectionWatch; - private readonly Stopwatch _sessionWatch; private readonly MetricsCallback _cb; - private bool _kickReceived; private static class ConnectionFinishReason { @@ -42,26 +65,22 @@ private static class ConnectionFinishReason public const string UnknownError = "UnknownError"; } - public struct PingStats - { - public uint Average; - public uint StandardDeviation; - public uint Loss; - } - public struct ConnectionSessionStats { // TODO(lhahn): Consider the case where multiple clients are created, should a session contain an ID? // Or should a pitaya client contain an id as well to distinguish different client instances? public uint Version; - public double? SessionDurationSec; - public PingStats? Ping; + public double SessionDurationSec; + public uint PingAverage; + public uint PingStdDeviation; + public uint PingTotal; + public uint PingLoss; public string ConnectionFinishReason; public string ConnectionFinishDetails; public double ConnectionTimeMs; public string ConnectionRegion; - public Dictionary RoutesLatency; - public Dictionary RoutesStandardDeviation; + public Dictionary RoutesLatencyMs; + public Dictionary RoutesStandardDeviation; public string NetworkType; public string LibPitayaVersion; public uint ServerInvalidPackages; @@ -72,21 +91,58 @@ public string Serialize() } } - public PitayaMetrics(MetricsCallback metricsCB = null) + public PitayaMetrics(MetricsCallback metricsCB) { + Assert.IsNotNull(metricsCB); _cb = metricsCB; _state = State.NotConnected; - _connectionWatch = new Stopwatch(); - _sessionWatch = new Stopwatch(); - _kickReceived = false; + _connectedState = null; + _connectingState = null; } public void Start() { _connectionSessionStats = DefaultConnectionSessionStats(); - _connectionWatch.Start(); _state = State.Connecting; - _kickReceived = false; + _connectingState = new ConnectingState + { + ConnectionWatch = new Stopwatch() + }; + _connectingState.ConnectionWatch.Start(); + Assert.IsNull(_connectedState); + } + + public void StartRecordingRequest(string route) + { + // We should not assume here that the _state variable will be of a specific value. LibPitaya can buffer + // requests even before the client is connected. + if (_requestsLatencies.TryGetValue(route, out RequestRecording recording)) + { + recording.Watch.Start(); + } + else + { + var r = new RequestRecording(); + _requestsLatencies.Add(route, r); + r.Watch.Start(); + } + } + + public void StopRecordingRequest(string route, PitayaError err = null) + { + if (err != null) + { + // TODO(lhahn): Should some errors not be collected here? For example, timeouts. + // for the moment just ignore errors... + } + + Assert.IsTrue(_requestsLatencies.ContainsKey(route)); + if (_requestsLatencies.TryGetValue(route, out RequestRecording recording)) + { + recording.Watch.Stop(); + recording.LatenciesMs.Add(recording.Watch.Elapsed.TotalMilliseconds); + recording.Watch.Reset(); + } } public void Update(PitayaNetWorkState pitayaState, NetworkError error) @@ -110,27 +166,18 @@ public void Update(PitayaNetWorkState pitayaState, NetworkError error) private void UpdateConnectedState(PitayaNetWorkState pitayaState, NetworkError pitayaErr) { Assert.IsTrue(_state == State.Connected); + Assert.IsNotNull(_connectedState); + Assert.IsNull(_connectingState); switch (pitayaState) { case PitayaNetWorkState.Kicked: // LibPitaya sends a Kicked event and after that a Disconnected event. Therefore, // we do not close the session yet, we just signal that a kick was received. - _kickReceived = true; + _connectedState.KickReceived = true; break; case PitayaNetWorkState.Disconnected: - if (pitayaErr == null) - { - _connectionSessionStats.ConnectionFinishReason = _kickReceived - ? ConnectionFinishReason.Kick - : ConnectionFinishReason.UserRequest; - } - else - { - _connectionSessionStats.ConnectionFinishReason = ConnectionFinishReason.ConnectionError; - _connectionSessionStats.ConnectionFinishDetails = GetErrorDetails(pitayaErr); - } - StopSession(); + StopConnectedState(pitayaErr); break; case PitayaNetWorkState.Error: // This event only happens when unknown data from the server was sent, so we just increment the counter. @@ -144,31 +191,40 @@ private void UpdateConnectedState(PitayaNetWorkState pitayaState, NetworkError p private void UpdateNotConnectedState(PitayaNetWorkState pitayaState, NetworkError pitayaErr) { Assert.IsTrue(_state == State.NotConnected); - throw new Exception(string.Format("PitayaMetrics received pitaya state {0} when in not connected state", pitayaState)); + Assert.IsNull(_connectedState); + Assert.IsNull(_connectingState); + + if (pitayaState != PitayaNetWorkState.Disconnected) + { + // It is possible to receive a duplicated disconnected event, so we just ignore it. + throw new Exception(string.Format("PitayaMetrics received pitaya state {0} when in not connected state", pitayaState)); + } } private void UpdateConnectingState(PitayaNetWorkState pitayaState, NetworkError pitayaErr) { Assert.IsTrue(_state == State.Connecting); + Assert.IsNull(_connectedState); + Assert.IsNotNull(_connectingState); switch (pitayaState) { case PitayaNetWorkState.Connected: // If the connection connected successfully, we start the session stopwatch and // stop the connection watch. - _connectionWatch.Stop(); - _connectionSessionStats.ConnectionTimeMs = _connectionWatch.Elapsed.TotalMilliseconds; - _connectionWatch.Reset(); - _sessionWatch.Start(); + _connectingState.ConnectionWatch.Stop(); + _connectionSessionStats.ConnectionTimeMs = _connectingState.ConnectionWatch.Elapsed.TotalMilliseconds; + _connectingState.ConnectionWatch.Reset(); + _connectingState = null; _state = State.Connected; + _connectedState = new ConnectedState(); + _connectedState.SessionWatch.Start(); break; case PitayaNetWorkState.FailToConnect: // If the connection failed while we were trying to connect, we should close the session with // this information. - _connectionSessionStats.ConnectionFinishReason = ConnectionFinishReason.FailedToConnect; - _connectionSessionStats.ConnectionFinishDetails = GetErrorDetails(pitayaErr); - StopSession(); + StopConnectingState(pitayaErr); break; case PitayaNetWorkState.Error: // This event only happens when unknown data from the server was sent, so we just increment the counter. @@ -179,16 +235,91 @@ private void UpdateConnectingState(PitayaNetWorkState pitayaState, NetworkError } } - private void StopSession() + public void ForceStop() { - _sessionWatch.Stop(); - _connectionSessionStats.SessionDurationSec = _sessionWatch.Elapsed.TotalSeconds; - _sessionWatch.Reset(); + // HACK(lhahn): We simulate a disconnect to the metrics aggregator. This is necessary because the dispose is called + // before the disconnect event can be fired in the PitayaClient class. This could be resolved in the future, + // but for the moment I think this solution won't have issues. + Update(PitayaNetWorkState.Disconnected, null); + } + + private void StopConnectingState(NetworkError pitayaErr) + { + Assert.IsNotNull(pitayaErr); + Assert.IsNull(_connectedState); + Assert.IsNotNull(_connectingState); + _connectionSessionStats.ConnectionFinishReason = ConnectionFinishReason.FailedToConnect; + _connectionSessionStats.ConnectionFinishDetails = GetErrorDetails(pitayaErr); + CalculateRoutesMetrics(ref _connectionSessionStats); + + SendConnectionStatsSummaryAndResetState(); + } + + private void StopConnectedState(NetworkError pitayaErr) + { + Assert.IsNotNull(_connectedState); + Assert.IsNull(_connectingState); + + if (pitayaErr == null) + { + _connectionSessionStats.ConnectionFinishReason = _connectedState.KickReceived + ? ConnectionFinishReason.Kick + : ConnectionFinishReason.UserRequest; + } + else + { + _connectionSessionStats.ConnectionFinishReason = ConnectionFinishReason.ConnectionError; + _connectionSessionStats.ConnectionFinishDetails = GetErrorDetails(pitayaErr); + } + + _connectedState.SessionWatch.Stop(); + _connectionSessionStats.SessionDurationSec = _connectedState.SessionWatch.Elapsed.TotalSeconds; + CalculateRoutesMetrics(ref _connectionSessionStats); + SendConnectionStatsSummaryAndResetState(); + } + + private void SendConnectionStatsSummaryAndResetState() + { _cb(_connectionSessionStats); _connectionSessionStats = DefaultConnectionSessionStats(); - _kickReceived = false; _state = State.NotConnected; + _connectingState = null; + _connectedState = null; + // TODO(lhahn): consider not clearing the dictionary here, since the routes will probably be reused anyways. + _requestsLatencies.Clear(); + } + + private void CalculateRoutesMetrics(ref ConnectionSessionStats connectionSessionStats) + { + Assert.IsTrue(connectionSessionStats.RoutesLatencyMs.Count == 0); + Assert.IsTrue(connectionSessionStats.RoutesStandardDeviation.Count == 0); + + foreach (var kv in _requestsLatencies) + { + string route = kv.Key; + RequestRecording recording = kv.Value; + + // Calculate the average + double averageLatency = 0; + for (var i = 0; i < recording.LatenciesMs.Count; ++i) + { + averageLatency += recording.LatenciesMs[i]; + } + averageLatency /= recording.LatenciesMs.Count; + + // Calculate the standard deviation + double stdDeviation = 0; + for (var i = 0; i < recording.LatenciesMs.Count; ++i) + { + stdDeviation += Math.Pow(recording.LatenciesMs[i] - averageLatency, 2); + } + stdDeviation /= recording.LatenciesMs.Count; + stdDeviation = Math.Sqrt(stdDeviation); + + connectionSessionStats.RoutesLatencyMs.Add(route, averageLatency); + connectionSessionStats.RoutesStandardDeviation.Add(route, stdDeviation); + } } private ConnectionSessionStats DefaultConnectionSessionStats() @@ -196,11 +327,12 @@ private ConnectionSessionStats DefaultConnectionSessionStats() return new ConnectionSessionStats { Version = EventVersion, - ServerInvalidPackages = 0, NetworkType = GetNetworkType(), LibPitayaVersion = PitayaBinding.Version, // TODO(lhahn): remove hardcoded region here and use something better. - ConnectionRegion = "NA" + ConnectionRegion = "NA", + RoutesLatencyMs = new Dictionary(), + RoutesStandardDeviation = new Dictionary() }; } From ea478ee88bc53759e94f75e2621d5a2e436b0869 Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Mon, 9 Dec 2019 17:47:18 -0300 Subject: [PATCH 11/15] Add ping metrics --- unity/PitayaExample/Assets/Example.cs | 5 +- .../Assets/Pitaya/PitayaClient.cs | 20 ++- .../Assets/Pitaya/PitayaMetrics.cs | 153 ++++++++++++------ .../Assets/Tests/PitayaClientTest.cs | 2 +- 4 files changed, 120 insertions(+), 60 deletions(-) diff --git a/unity/PitayaExample/Assets/Example.cs b/unity/PitayaExample/Assets/Example.cs index e61f3c64..816e201d 100644 --- a/unity/PitayaExample/Assets/Example.cs +++ b/unity/PitayaExample/Assets/Example.cs @@ -30,11 +30,12 @@ private void Start() }); // _client = new PitayaClient("ca.crt"); - _client = new PitayaClient(metricsCb: stats => + _client = new PitayaClient(new PitayaMetrics.Config(stats => { Debug.Log("=========> Received connection stats!"); Debug.Log(stats.Serialize()); - }); + }, "connector.getsessiondata")); + _connected = false; _requestSent = false; diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs b/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs index 7923265d..6081fee8 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaClient.cs @@ -36,14 +36,14 @@ public PitayaClient(string certificateName = null) Init(certificateName, certificateName != null, false, false, DefaultConnectionTimeout, null); } - public PitayaClient(PitayaMetrics.MetricsCallback metricsCb = null) + public PitayaClient(PitayaMetrics.Config config = null) { - Init(null, false, false, false, DefaultConnectionTimeout, metricsCb); + Init(null, false, false, false, DefaultConnectionTimeout, config); } - public PitayaClient(bool enableReconnect = false, string certificateName = null, int connectionTimeout = DefaultConnectionTimeout, PitayaMetrics.MetricsCallback metricsCb = null) + public PitayaClient(bool enableReconnect = false, string certificateName = null, int connectionTimeout = DefaultConnectionTimeout, PitayaMetrics.Config config = null) { - Init(certificateName, certificateName != null, false, enableReconnect, DefaultConnectionTimeout, metricsCb); + Init(certificateName, certificateName != null, false, enableReconnect, DefaultConnectionTimeout, config); } ~PitayaClient() @@ -51,16 +51,22 @@ public PitayaClient(bool enableReconnect = false, string certificateName = null, Dispose(); } - private void Init(string certificateName, bool enableTlS, bool enablePolling, bool enableReconnect, int connTimeout, PitayaMetrics.MetricsCallback metricsCb) + private void Init( + string certificateName, + bool enableTlS, + bool enablePolling, + bool enableReconnect, + int connTimeout, + PitayaMetrics.Config config) { _eventManager = new EventManager(); _typeRequestSubscriber = new TypeSubscriber(); _typePushSubscriber = new TypeSubscriber(); _client = PitayaBinding.CreateClient(enableTlS, enablePolling, enableReconnect, connTimeout, this); - if (metricsCb != null) + if (config != null) { - _metricsAggr = new PitayaMetrics(metricsCb); + _metricsAggr = new PitayaMetrics(config); } if (certificateName != null) diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs index 0b7ff836..15fea38e 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs @@ -10,11 +10,20 @@ public class PitayaMetrics { public delegate void MetricsCallback(ConnectionSessionStats stats); - public enum PingStatus + public class Config { - Ok, - Timeout, - Error + private MetricsCallback _cb; + private string _pingRoute; + + public string PingRoute { get { return _pingRoute; } } + public MetricsCallback Cb { get { return _cb; } } + + public Config(MetricsCallback cb, string pingRoute = null) + { + Assert.IsNotNull(cb); + _cb = cb; + _pingRoute = pingRoute; + } } private class RequestRecording @@ -23,7 +32,13 @@ private class RequestRecording public Stopwatch Watch = new Stopwatch(); } - public delegate void SendPing(Action onPingDone); + private class PingRecording + { + public List LatenciesMs = new List(); + public Stopwatch Watch = new Stopwatch(); + public uint Loss = 0; + public uint Total = 0; + } private enum State { @@ -51,10 +66,11 @@ private class ConnectedState private State _state; private ConnectedState _connectedState; private ConnectingState _connectingState; - private Dictionary _requestsLatencies = new Dictionary(15); + private readonly Dictionary _requestsLatencies; + private readonly PingRecording _pingRecording; + private Config _config; private ConnectionSessionStats _connectionSessionStats; - private readonly MetricsCallback _cb; private static class ConnectionFinishReason { @@ -71,8 +87,8 @@ public struct ConnectionSessionStats // Or should a pitaya client contain an id as well to distinguish different client instances? public uint Version; public double SessionDurationSec; - public uint PingAverage; - public uint PingStdDeviation; + public double PingAverage; + public double PingStdDeviation; public uint PingTotal; public uint PingLoss; public string ConnectionFinishReason; @@ -91,13 +107,15 @@ public string Serialize() } } - public PitayaMetrics(MetricsCallback metricsCB) + public PitayaMetrics(Config config) { - Assert.IsNotNull(metricsCB); - _cb = metricsCB; + Assert.IsNotNull(config); + _config = config; _state = State.NotConnected; _connectedState = null; _connectingState = null; + _pingRecording = new PingRecording(); + _requestsLatencies = new Dictionary(15); } public void Start() @@ -116,15 +134,22 @@ public void StartRecordingRequest(string route) { // We should not assume here that the _state variable will be of a specific value. LibPitaya can buffer // requests even before the client is connected. - if (_requestsLatencies.TryGetValue(route, out RequestRecording recording)) + if (route == _config.PingRoute) { - recording.Watch.Start(); + _pingRecording.Watch.Start(); } else { - var r = new RequestRecording(); - _requestsLatencies.Add(route, r); - r.Watch.Start(); + if (_requestsLatencies.TryGetValue(route, out RequestRecording recording)) + { + recording.Watch.Start(); + } + else + { + var r = new RequestRecording(); + _requestsLatencies.Add(route, r); + r.Watch.Start(); + } } } @@ -135,13 +160,27 @@ public void StopRecordingRequest(string route, PitayaError err = null) // TODO(lhahn): Should some errors not be collected here? For example, timeouts. // for the moment just ignore errors... } - - Assert.IsTrue(_requestsLatencies.ContainsKey(route)); - if (_requestsLatencies.TryGetValue(route, out RequestRecording recording)) + + if (route == _config.PingRoute) { - recording.Watch.Stop(); - recording.LatenciesMs.Add(recording.Watch.Elapsed.TotalMilliseconds); - recording.Watch.Reset(); + _pingRecording.Watch.Stop(); + _pingRecording.LatenciesMs.Add(_pingRecording.Watch.Elapsed.TotalMilliseconds); + _pingRecording.Watch.Reset(); + _pingRecording.Total++; + if (err != null && err.Code == "PC_RC_TIMEOUT") + { + _pingRecording.Loss++; + } + } + else + { + Assert.IsTrue(_requestsLatencies.ContainsKey(route)); + if (_requestsLatencies.TryGetValue(route, out RequestRecording recording)) + { + recording.Watch.Stop(); + recording.LatenciesMs.Add(recording.Watch.Elapsed.TotalMilliseconds); + recording.Watch.Reset(); + } } } @@ -163,6 +202,14 @@ public void Update(PitayaNetWorkState pitayaState, NetworkError error) } } + public void ForceStop() + { + // HACK(lhahn): We simulate a disconnect to the metrics aggregator. This is necessary because the dispose is called + // before the disconnect event can be fired in the PitayaClient class. This could be resolved in the future, + // but for the moment I think this solution won't have issues. + Update(PitayaNetWorkState.Disconnected, null); + } + private void UpdateConnectedState(PitayaNetWorkState pitayaState, NetworkError pitayaErr) { Assert.IsTrue(_state == State.Connected); @@ -235,14 +282,6 @@ private void UpdateConnectingState(PitayaNetWorkState pitayaState, NetworkError } } - public void ForceStop() - { - // HACK(lhahn): We simulate a disconnect to the metrics aggregator. This is necessary because the dispose is called - // before the disconnect event can be fired in the PitayaClient class. This could be resolved in the future, - // but for the moment I think this solution won't have issues. - Update(PitayaNetWorkState.Disconnected, null); - } - private void StopConnectingState(NetworkError pitayaErr) { Assert.IsNotNull(pitayaErr); @@ -250,7 +289,7 @@ private void StopConnectingState(NetworkError pitayaErr) Assert.IsNotNull(_connectingState); _connectionSessionStats.ConnectionFinishReason = ConnectionFinishReason.FailedToConnect; _connectionSessionStats.ConnectionFinishDetails = GetErrorDetails(pitayaErr); - CalculateRoutesMetrics(ref _connectionSessionStats); + CalculateRoutesAndPingMetrics(ref _connectionSessionStats); SendConnectionStatsSummaryAndResetState(); } @@ -274,14 +313,14 @@ private void StopConnectedState(NetworkError pitayaErr) _connectedState.SessionWatch.Stop(); _connectionSessionStats.SessionDurationSec = _connectedState.SessionWatch.Elapsed.TotalSeconds; - CalculateRoutesMetrics(ref _connectionSessionStats); + CalculateRoutesAndPingMetrics(ref _connectionSessionStats); SendConnectionStatsSummaryAndResetState(); } private void SendConnectionStatsSummaryAndResetState() { - _cb(_connectionSessionStats); + _config.Cb(_connectionSessionStats); _connectionSessionStats = DefaultConnectionSessionStats(); _state = State.NotConnected; _connectingState = null; @@ -290,36 +329,50 @@ private void SendConnectionStatsSummaryAndResetState() _requestsLatencies.Clear(); } - private void CalculateRoutesMetrics(ref ConnectionSessionStats connectionSessionStats) + private void CalculateRoutesAndPingMetrics(ref ConnectionSessionStats connectionSessionStats) { Assert.IsTrue(connectionSessionStats.RoutesLatencyMs.Count == 0); Assert.IsTrue(connectionSessionStats.RoutesStandardDeviation.Count == 0); - foreach (var kv in _requestsLatencies) + double CalculateAverage(List arr) { - string route = kv.Key; - RequestRecording recording = kv.Value; - // Calculate the average - double averageLatency = 0; - for (var i = 0; i < recording.LatenciesMs.Count; ++i) + double average = 0; + for (var i = 0; i < arr.Count; ++i) { - averageLatency += recording.LatenciesMs[i]; + average += arr[i]; } - averageLatency /= recording.LatenciesMs.Count; - - // Calculate the standard deviation + average /= arr.Count; + return average; + } + + double CalculateStdDeviation(List arr, double avg) + { double stdDeviation = 0; - for (var i = 0; i < recording.LatenciesMs.Count; ++i) + for (var i = 0; i < arr.Count; ++i) { - stdDeviation += Math.Pow(recording.LatenciesMs[i] - averageLatency, 2); + stdDeviation += Math.Pow(arr[i] - avg, 2); } - stdDeviation /= recording.LatenciesMs.Count; + stdDeviation /= arr.Count; stdDeviation = Math.Sqrt(stdDeviation); + return stdDeviation; + } - connectionSessionStats.RoutesLatencyMs.Add(route, averageLatency); - connectionSessionStats.RoutesStandardDeviation.Add(route, stdDeviation); + foreach (KeyValuePair kv in _requestsLatencies) + { + double averageLatency = CalculateAverage(kv.Value.LatenciesMs); + double stdDeviation = CalculateStdDeviation(kv.Value.LatenciesMs, averageLatency); + + connectionSessionStats.RoutesLatencyMs.Add(kv.Key, averageLatency); + connectionSessionStats.RoutesStandardDeviation.Add(kv.Key, stdDeviation); } + + connectionSessionStats.PingAverage = CalculateAverage(_pingRecording.LatenciesMs); + connectionSessionStats.PingStdDeviation = CalculateStdDeviation( + _pingRecording.LatenciesMs, connectionSessionStats.PingAverage + ); + connectionSessionStats.PingTotal = _pingRecording.Total; + connectionSessionStats.PingLoss = _pingRecording.Loss; } private ConnectionSessionStats DefaultConnectionSessionStats() diff --git a/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs b/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs index 78db6382..19c01a95 100644 --- a/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs +++ b/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs @@ -19,7 +19,7 @@ public class PitayaClientTest public void Setup() { _mainThread = Thread.CurrentThread; - _client = new PitayaClient(MetricsCallbackFunc); + _client = new PitayaClient(new PitayaMetrics.Config(MetricsCallbackFunc)); } [TearDown] From 842fe6a6675b8e8d7ec4f3c163c0d303068c0048 Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Mon, 9 Dec 2019 17:54:16 -0300 Subject: [PATCH 12/15] Fix comment position --- unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs index 15fea38e..6c3d54a3 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs @@ -241,9 +241,9 @@ private void UpdateNotConnectedState(PitayaNetWorkState pitayaState, NetworkErro Assert.IsNull(_connectedState); Assert.IsNull(_connectingState); + // It is possible to receive a duplicated disconnected event, so we just ignore it. if (pitayaState != PitayaNetWorkState.Disconnected) { - // It is possible to receive a duplicated disconnected event, so we just ignore it. throw new Exception(string.Format("PitayaMetrics received pitaya state {0} when in not connected state", pitayaState)); } } From 1addfd2d75670a115ae92a582c95a8dae663480e Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Mon, 9 Dec 2019 18:23:49 -0300 Subject: [PATCH 13/15] Update example scene --- unity/PitayaExample/Assets/Example.unity | 423 +++++++++++++++++++++-- 1 file changed, 397 insertions(+), 26 deletions(-) diff --git a/unity/PitayaExample/Assets/Example.unity b/unity/PitayaExample/Assets/Example.unity index de990268..42cbbaf1 100644 --- a/unity/PitayaExample/Assets/Example.unity +++ b/unity/PitayaExample/Assets/Example.unity @@ -13,7 +13,7 @@ OcclusionCullingSettings: --- !u!104 &2 RenderSettings: m_ObjectHideFlags: 0 - serializedVersion: 8 + serializedVersion: 9 m_Fog: 0 m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} m_FogMode: 3 @@ -38,7 +38,8 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0.4465934, g: 0.49642956, b: 0.5748249, a: 1} + m_IndirectSpecularColor: {r: 0.44657898, g: 0.49641287, b: 0.5748173, a: 1} + m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 @@ -49,16 +50,14 @@ LightmapSettings: m_BounceScale: 1 m_IndirectOutputScale: 1 m_AlbedoBoost: 1 - m_TemporalCoherenceThreshold: 1 m_EnvironmentLightingMode: 0 m_EnableBakedLightmaps: 1 m_EnableRealtimeLightmaps: 1 m_LightmapEditorSettings: - serializedVersion: 9 + serializedVersion: 10 m_Resolution: 2 m_BakeResolution: 40 - m_TextureWidth: 1024 - m_TextureHeight: 1024 + m_AtlasSize: 1024 m_AO: 0 m_AOMaxDistance: 1 m_CompAOExponent: 1 @@ -116,9 +115,10 @@ NavMeshSettings: --- !u!1 &376153440 GameObject: m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - serializedVersion: 5 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 m_Component: - component: {fileID: 376153445} - component: {fileID: 376153444} @@ -135,38 +135,48 @@ GameObject: --- !u!114 &376153441 MonoBehaviour: m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 376153440} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: a4482a4624a18440caceac740f4be357, type: 3} m_Name: m_EditorClassIdentifier: + GetDataButton: {fileID: 407976080} --- !u!81 &376153442 AudioListener: m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 376153440} m_Enabled: 1 --- !u!124 &376153443 Behaviour: m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 376153440} m_Enabled: 1 --- !u!20 &376153444 Camera: m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 376153440} m_Enabled: 1 serializedVersion: 2 m_ClearFlags: 1 m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_GateFitMode: 2 + m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -196,8 +206,9 @@ Camera: --- !u!4 &376153445 Transform: m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 376153440} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 1, z: -10} @@ -206,12 +217,369 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &407976078 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 407976079} + - component: {fileID: 407976082} + - component: {fileID: 407976081} + - component: {fileID: 407976080} + m_Layer: 5 + m_Name: GetDataButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &407976079 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 407976078} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 517537412} + m_Father: {fileID: 1517574242} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &407976080 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 407976078} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1392445389, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 407976081} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &407976081 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 407976078} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 +--- !u!222 &407976082 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 407976078} + m_CullTransparentMesh: 0 +--- !u!1 &517537411 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 517537412} + - component: {fileID: 517537414} + - component: {fileID: 517537413} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &517537412 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 517537411} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 407976079} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &517537413 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 517537411} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Button +--- !u!222 &517537414 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 517537411} + m_CullTransparentMesh: 0 +--- !u!1 &1517574238 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1517574242} + - component: {fileID: 1517574241} + - component: {fileID: 1517574240} + - component: {fileID: 1517574239} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1517574239 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1517574238} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1301386320, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &1517574240 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1517574238} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1980459831, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 +--- !u!223 &1517574241 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1517574238} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &1517574242 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1517574238} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_Children: + - {fileID: 407976079} + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &1693682351 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1693682354} + - component: {fileID: 1693682353} + - component: {fileID: 1693682352} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1693682352 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1693682351} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1077351063, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &1693682353 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1693682351} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -619905303, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &1693682354 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1693682351} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1782618146 GameObject: m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - serializedVersion: 5 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 m_Component: - component: {fileID: 1782618148} - component: {fileID: 1782618147} @@ -225,8 +593,9 @@ GameObject: --- !u!108 &1782618147 Light: m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1782618146} m_Enabled: 1 serializedVersion: 8 @@ -252,6 +621,7 @@ Light: serializedVersion: 2 m_Bits: 4294967295 m_Lightmapping: 4 + m_LightShadowCasterMode: 0 m_AreaSize: {x: 1, y: 1} m_BounceIntensity: 1 m_ColorTemperature: 6570 @@ -261,8 +631,9 @@ Light: --- !u!4 &1782618148 Transform: m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1782618146} m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} m_LocalPosition: {x: 0, y: 3, z: 0} From 555eb26e825941c35c0d2fafefa7651c55caa377 Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Mon, 9 Dec 2019 18:24:03 -0300 Subject: [PATCH 14/15] Add basic test --- .../PitayaExample/Assets/Tests/MetricsTest.cs | 58 +++++++++++++++++++ .../Assets/Tests/MetricsTest.cs.meta | 3 + .../Assets/Tests/PitayaClientTest.cs | 13 +---- 3 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 unity/PitayaExample/Assets/Tests/MetricsTest.cs create mode 100644 unity/PitayaExample/Assets/Tests/MetricsTest.cs.meta diff --git a/unity/PitayaExample/Assets/Tests/MetricsTest.cs b/unity/PitayaExample/Assets/Tests/MetricsTest.cs new file mode 100644 index 00000000..a0e0a822 --- /dev/null +++ b/unity/PitayaExample/Assets/Tests/MetricsTest.cs @@ -0,0 +1,58 @@ +using System.Collections; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Pitaya.Tests +{ + public class PitayaMetricsTest + { + const string ServerHost = "libpitaya-tests.tfgco.com"; + const int ServerPort = 3251; + + PitayaClient _client; + + [SetUp] + public void Setup() { } + + [TearDown] + public void TearDown() + { + if (_client == null) return; + _client.Disconnect(); + _client.Dispose(); + _client = null; + } + + [UnityTest] + public IEnumerator StatsShouldBeReportedAtEndOfConnection() + { + bool statsCalled = false; + + _client = new PitayaClient(new PitayaMetrics.Config(stats => { statsCalled = true; })); + + var called = false; + var connectionState = PitayaNetWorkState.Disconnected; + + _client.NetWorkStateChangedEvent += (networkState, error) => + { + called = true; + connectionState = networkState; + }; + + _client.Connect(ServerHost, ServerPort); + + while (!called) + { + yield return new WaitForSeconds(0.2f); + } + + Assert.True(called); + Assert.AreEqual(connectionState, PitayaNetWorkState.Connected); + + _client.Disconnect(); + yield return new WaitForSeconds(0.2f); + Assert.IsTrue(statsCalled); + } + } +} diff --git a/unity/PitayaExample/Assets/Tests/MetricsTest.cs.meta b/unity/PitayaExample/Assets/Tests/MetricsTest.cs.meta new file mode 100644 index 00000000..60b5269b --- /dev/null +++ b/unity/PitayaExample/Assets/Tests/MetricsTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 300dd9a6ad8541e9aa39e334e08f72f6 +timeCreated: 1575925345 \ No newline at end of file diff --git a/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs b/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs index 19c01a95..44751281 100644 --- a/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs +++ b/unity/PitayaExample/Assets/Tests/PitayaClientTest.cs @@ -19,7 +19,7 @@ public class PitayaClientTest public void Setup() { _mainThread = Thread.CurrentThread; - _client = new PitayaClient(new PitayaMetrics.Config(MetricsCallbackFunc)); + _client = new PitayaClient(); } [TearDown] @@ -31,17 +31,6 @@ public void TearDown() _client = null; } - private static void MetricsCallbackFunc(PitayaMetrics.ConnectionSessionStats connectionSessionStats) - { - UnityEngine.Debug.Log(string.Format( - "** REPORT **\n SessionTime = {0} | ConnectionTime = {1} | DisconnectionReason = {2} | ConnectionFailureDetails = {3}", - connectionSessionStats.SessionDurationSec, - connectionSessionStats.ConnectionTimeMs, - connectionSessionStats.ConnectionFinishReason, - connectionSessionStats.ConnectionFinishDetails - )); - } - [Test] public void ShouldCreateClient() { From 738fca9fce7b40e577dc4c00d81212e852ba0b64 Mon Sep 17 00:00:00 2001 From: Leonardo Hahn Date: Mon, 9 Dec 2019 19:14:41 -0300 Subject: [PATCH 15/15] Add basic ping test and fix bug when calculating avg and stdDeviation --- .../Assets/Pitaya/PitayaMetrics.cs | 19 ++++--- .../PitayaExample/Assets/Tests/MetricsTest.cs | 52 +++++++++++++++---- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs index 6c3d54a3..53bdc315 100644 --- a/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs +++ b/unity/PitayaExample/Assets/Pitaya/PitayaMetrics.cs @@ -14,7 +14,7 @@ public class Config { private MetricsCallback _cb; private string _pingRoute; - + public string PingRoute { get { return _pingRoute; } } public MetricsCallback Cb { get { return _cb; } } @@ -36,8 +36,8 @@ private class PingRecording { public List LatenciesMs = new List(); public Stopwatch Watch = new Stopwatch(); - public uint Loss = 0; - public uint Total = 0; + public uint Loss; + public uint Total; } private enum State @@ -55,7 +55,7 @@ private class ConnectingState private class ConnectedState { public Stopwatch SessionWatch = new Stopwatch(); - public bool KickReceived = false; + public bool KickReceived; } // The current version of the event that is being sent. This value should always increase when the @@ -68,7 +68,7 @@ private class ConnectedState private ConnectingState _connectingState; private readonly Dictionary _requestsLatencies; private readonly PingRecording _pingRecording; - private Config _config; + private readonly Config _config; private ConnectionSessionStats _connectionSessionStats; @@ -336,7 +336,9 @@ private void CalculateRoutesAndPingMetrics(ref ConnectionSessionStats connection double CalculateAverage(List arr) { - // Calculate the average + if (arr.Count == 0) + return 0; + double average = 0; for (var i = 0; i < arr.Count; ++i) { @@ -348,6 +350,9 @@ double CalculateAverage(List arr) double CalculateStdDeviation(List arr, double avg) { + if (arr.Count == 0) + return 0; + double stdDeviation = 0; for (var i = 0; i < arr.Count; ++i) { @@ -375,7 +380,7 @@ double CalculateStdDeviation(List arr, double avg) connectionSessionStats.PingLoss = _pingRecording.Loss; } - private ConnectionSessionStats DefaultConnectionSessionStats() + private static ConnectionSessionStats DefaultConnectionSessionStats() { return new ConnectionSessionStats { diff --git a/unity/PitayaExample/Assets/Tests/MetricsTest.cs b/unity/PitayaExample/Assets/Tests/MetricsTest.cs index a0e0a822..d0da7ae1 100644 --- a/unity/PitayaExample/Assets/Tests/MetricsTest.cs +++ b/unity/PitayaExample/Assets/Tests/MetricsTest.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using NUnit.Framework; using UnityEngine; @@ -8,6 +9,7 @@ namespace Pitaya.Tests public class PitayaMetricsTest { const string ServerHost = "libpitaya-tests.tfgco.com"; + const string GetSessionDataRoute = "connector.getsessiondata"; const int ServerPort = 3251; PitayaClient _client; @@ -24,23 +26,18 @@ public void TearDown() _client = null; } - [UnityTest] - public IEnumerator StatsShouldBeReportedAtEndOfConnection() + static IEnumerator Connect(PitayaClient client) { - bool statsCalled = false; - - _client = new PitayaClient(new PitayaMetrics.Config(stats => { statsCalled = true; })); - var called = false; var connectionState = PitayaNetWorkState.Disconnected; - _client.NetWorkStateChangedEvent += (networkState, error) => + client.NetWorkStateChangedEvent += (networkState, error) => { called = true; connectionState = networkState; }; - _client.Connect(ServerHost, ServerPort); + client.Connect(ServerHost, ServerPort); while (!called) { @@ -49,10 +46,47 @@ public IEnumerator StatsShouldBeReportedAtEndOfConnection() Assert.True(called); Assert.AreEqual(connectionState, PitayaNetWorkState.Connected); - + } + + [UnityTest] + public IEnumerator StatsShouldBeReportedAtEndOfConnection() + { + bool statsCalled = false; + + _client = new PitayaClient(new PitayaMetrics.Config(stats => + { + statsCalled = true; + Assert.Equals(stats.ConnectionFinishReason, "UserRequest"); + })); + yield return Connect(_client); _client.Disconnect(); yield return new WaitForSeconds(0.2f); Assert.IsTrue(statsCalled); } + + [UnityTest] + public IEnumerator PingStatsShouldBeReportedOnlyWhenRouteIsCalled() + { + { + bool statsCalled = false; + + _client = new PitayaClient(new PitayaMetrics.Config( + stats => + { + statsCalled = true; + Assert.Less(Math.Abs(stats.PingAverage), 0.0001); + Assert.Less(Math.Abs(stats.PingStdDeviation), 0.0001); + Assert.Equals(stats.PingTotal, 0); + Assert.Equals(stats.PingLoss, 0); + }, + GetSessionDataRoute + )); + + yield return Connect(_client); + _client.Disconnect(); + yield return new WaitForSeconds(0.3f); + Assert.IsTrue(statsCalled); + } + } } }