#if MLA_UNITY_ANALYTICS_MODULE || !UNITY_2019_4_OR_NEWER #define MLA_UNITY_ANALYTICS_MODULE_ENABLED #endif using System; using System.Collections.Generic; using System.Diagnostics; using Unity.MLAgents.Actuators; using Unity.MLAgents.Sensors; using UnityEngine; #if MLA_UNITY_ANALYTICS_MODULE_ENABLED using UnityEngine.Analytics; #if UNITY_EDITOR using UnityEditor.Analytics; #endif #endif #if UNITY_EDITOR using UnityEditor; #endif namespace Unity.MLAgents.Analytics { internal class TrainingAnalytics { const string k_VendorKey = "unity.ml-agents"; const string k_TrainingEnvironmentInitializedEventName = "ml_agents_training_environment_initialized"; const string k_TrainingBehaviorInitializedEventName = "ml_agents_training_behavior_initialized"; const string k_RemotePolicyInitializedEventName = "ml_agents_remote_policy_initialized"; private static readonly string[] s_EventNames = { k_TrainingEnvironmentInitializedEventName, k_TrainingBehaviorInitializedEventName, k_RemotePolicyInitializedEventName }; /// /// Whether or not we've registered this particular event yet /// static bool s_EventsRegistered = false; /// /// Hourly limit for this event name /// const int k_MaxEventsPerHour = 1000; /// /// Maximum number of items in this event. /// const int k_MaxNumberOfElements = 1000; private static bool s_SentEnvironmentInitialized; /// /// Behaviors that we've already sent events for. /// private static HashSet s_SentRemotePolicyInitialized; private static HashSet s_SentTrainingBehaviorInitialized; private static Guid s_TrainingSessionGuid; // These are set when the RpcCommunicator connects private static string s_TrainerPackageVersion = ""; private static string s_TrainerCommunicationVersion = ""; static bool EnableAnalytics() { #if MLA_UNITY_ANALYTICS_MODULE_ENABLED if (s_EventsRegistered) { return true; } foreach (var eventName in s_EventNames) { #if UNITY_EDITOR AnalyticsResult result = EditorAnalytics.RegisterEventWithLimit(eventName, k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey); if (result != AnalyticsResult.Ok) { return false; } #else return false; #endif // UNITY_EDITOR } s_EventsRegistered = true; if (s_SentRemotePolicyInitialized == null) { s_SentRemotePolicyInitialized = new HashSet(); s_SentTrainingBehaviorInitialized = new HashSet(); s_TrainingSessionGuid = Guid.NewGuid(); } return s_EventsRegistered; #else return false; #endif // MLA_UNITY_ANALYTICS_MODULE_ENABLED } /// /// Cache information about the trainer when it becomes available in the RpcCommunicator. /// /// /// [Conditional("MLA_UNITY_ANALYTICS_MODULE_ENABLED")] public static void SetTrainerInformation(string packageVersion, string communicationVersion) { s_TrainerPackageVersion = packageVersion; s_TrainerCommunicationVersion = communicationVersion; } public static bool IsAnalyticsEnabled() { #if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE_ENABLED return EditorAnalytics.enabled; #else return false; #endif } [Conditional("MLA_UNITY_ANALYTICS_MODULE_ENABLED")] public static void TrainingEnvironmentInitialized(TrainingEnvironmentInitializedEvent tbiEvent) { if (!IsAnalyticsEnabled()) return; if (!EnableAnalytics()) return; if (s_SentEnvironmentInitialized) { // We already sent an TrainingEnvironmentInitializedEvent. Exit so we don't resend. return; } s_SentEnvironmentInitialized = true; tbiEvent.TrainingSessionGuid = s_TrainingSessionGuid.ToString(); // Note - to debug, use JsonUtility.ToJson on the event. // Debug.Log( // $"Would send event {k_TrainingEnvironmentInitializedEventName} with body {JsonUtility.ToJson(tbiEvent, true)}" // ); #if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE_ENABLED if (AnalyticsUtils.s_SendEditorAnalytics) { EditorAnalytics.SendEventWithLimit(k_TrainingEnvironmentInitializedEventName, tbiEvent); } #endif } [Conditional("MLA_UNITY_ANALYTICS_MODULE_ENABLED")] public static void RemotePolicyInitialized( string fullyQualifiedBehaviorName, IList sensors, ActionSpec actionSpec, IList actuators ) { if (!IsAnalyticsEnabled()) return; if (!EnableAnalytics()) return; // Extract base behavior name (no team ID) var behaviorName = ParseBehaviorName(fullyQualifiedBehaviorName); var added = s_SentRemotePolicyInitialized.Add(behaviorName); if (!added) { // We previously added this model. Exit so we don't resend. return; } var data = GetEventForRemotePolicy(behaviorName, sensors, actionSpec, actuators); // Note - to debug, use JsonUtility.ToJson on the event. // Debug.Log( // $"Would send event {k_RemotePolicyInitializedEventName} with body {JsonUtility.ToJson(data, true)}" // ); #if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE_ENABLED if (AnalyticsUtils.s_SendEditorAnalytics) { EditorAnalytics.SendEventWithLimit(k_RemotePolicyInitializedEventName, data); } #endif } internal static string ParseBehaviorName(string fullyQualifiedBehaviorName) { var lastQuestionIndex = fullyQualifiedBehaviorName.LastIndexOf("?"); if (lastQuestionIndex < 0) { // Nothing to remove return fullyQualifiedBehaviorName; } return fullyQualifiedBehaviorName.Substring(0, lastQuestionIndex); } [Conditional("MLA_UNITY_ANALYTICS_MODULE_ENABLED")] public static void TrainingBehaviorInitialized(TrainingBehaviorInitializedEvent tbiEvent) { if (!IsAnalyticsEnabled()) return; if (!EnableAnalytics()) return; var behaviorName = tbiEvent.BehaviorName; var added = s_SentTrainingBehaviorInitialized.Add(behaviorName); if (!added) { // We previously added this model. Exit so we don't resend. return; } // Hash the behavior name so that there's no concern about PII or "secret" data being leaked. tbiEvent.TrainingSessionGuid = s_TrainingSessionGuid.ToString(); tbiEvent.BehaviorName = AnalyticsUtils.Hash(tbiEvent.BehaviorName); // Note - to debug, use JsonUtility.ToJson on the event. // Debug.Log( // $"Would send event {k_TrainingBehaviorInitializedEventName} with body {JsonUtility.ToJson(tbiEvent, true)}" // ); #if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE_ENABLED if (AnalyticsUtils.s_SendEditorAnalytics) { EditorAnalytics.SendEventWithLimit(k_TrainingBehaviorInitializedEventName, tbiEvent); } #else return; #endif } internal static RemotePolicyInitializedEvent GetEventForRemotePolicy( string behaviorName, IList sensors, ActionSpec actionSpec, IList actuators ) { var remotePolicyEvent = new RemotePolicyInitializedEvent(); // Hash the behavior name so that there's no concern about PII or "secret" data being leaked. remotePolicyEvent.BehaviorName = AnalyticsUtils.Hash(behaviorName); remotePolicyEvent.TrainingSessionGuid = s_TrainingSessionGuid.ToString(); remotePolicyEvent.ActionSpec = EventActionSpec.FromActionSpec(actionSpec); remotePolicyEvent.ObservationSpecs = new List(sensors.Count); foreach (var sensor in sensors) { remotePolicyEvent.ObservationSpecs.Add(EventObservationSpec.FromSensor(sensor)); } remotePolicyEvent.ActuatorInfos = new List(actuators.Count); foreach (var actuator in actuators) { remotePolicyEvent.ActuatorInfos.Add(EventActuatorInfo.FromActuator(actuator)); } remotePolicyEvent.MLAgentsEnvsVersion = s_TrainerPackageVersion; remotePolicyEvent.TrainerCommunicationVersion = s_TrainerCommunicationVersion; return remotePolicyEvent; } } }