using System; using System.Collections.Generic; using UnityEditor.Analytics; using UnityEngine; using UnityEngine.Analytics; namespace UnityEditor.Performance.ProfileAnalyzer { class ProfileAnalyzerAnalytics { const int k_MaxEventsPerHour = 100; const int k_MaxEventItems = 1000; const string k_VendorKey = "unity.profileanalyzer"; const string k_EventTopicName = "usability"; static bool s_EnableAnalytics = false; public static void EnableAnalytics() { AnalyticsResult result = EditorAnalytics.RegisterEventWithLimit(k_EventTopicName, k_MaxEventsPerHour, k_MaxEventItems, k_VendorKey); if (result == AnalyticsResult.Ok) s_EnableAnalytics = true; } public enum UIButton { Pull, OpenProfiler, CloseProfiler, JumpToFrame, ExportSingleFrames, ExportComparisonFrames, }; public enum UIUsageMode { Single, Comparison, }; public enum UIVisibility { FrameTimeContextMenu, Filters, TopTen, Frames, Threads, Markers, }; public enum UIResizeView { Single, Comparison, }; [Serializable] struct ProfileAnalyzerUIButtonEventParameters { public string name; public ProfileAnalyzerUIButtonEventParameters(string name) { this.name = name; } } // camelCase since these events get serialized to Json and naming convention in analytics is camelCase [Serializable] struct ProfileAnalyzerUIButtonEvent { public ProfileAnalyzerUIButtonEvent(string name, float durationInTicks) { subtype = "profileAnalyzerUIButton"; // ts is auto added so no need to include it here //ts = (int)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; this.duration = durationInTicks; parameters = new ProfileAnalyzerUIButtonEventParameters(name); } public string subtype; //public int ts; public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds public ProfileAnalyzerUIButtonEventParameters parameters; } [Serializable] struct ProfileAnalyzerUIUsageEventParameters { public string name; public ProfileAnalyzerUIUsageEventParameters(string name) { this.name = name; } } [Serializable] struct ProfileAnalyzerUIUsageEvent { public ProfileAnalyzerUIUsageEvent(string name, float durationInTicks) { subtype = "profileAnalyzerModeUsage"; this.duration = durationInTicks; parameters = new ProfileAnalyzerUIUsageEventParameters(name); } public string subtype; public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds public ProfileAnalyzerUIUsageEventParameters parameters; } [Serializable] struct ProfileAnalyzerUIVisibilityEventParameters { public string name; public bool show; public ProfileAnalyzerUIVisibilityEventParameters(string name, bool show) { this.name = name; this.show = show; } } [Serializable] struct ProfileAnalyzerUIVisibilityEvent { public ProfileAnalyzerUIVisibilityEvent(string name, float durationInTicks, bool show) { subtype = "profileAnalyzerUIVisibility"; this.duration = durationInTicks; parameters = new ProfileAnalyzerUIVisibilityEventParameters(name, show); } public string subtype; public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds public ProfileAnalyzerUIVisibilityEventParameters parameters; } [Serializable] struct ProfileAnalyzerUIResizeEventParameters { public string name; public float width; public float height; public float screenWidth; public float screenHeight; public bool docked; public ProfileAnalyzerUIResizeEventParameters(string name, float width, float height, float screenWidth, float screenHeight, bool isDocked) { this.name = name; this.width = width; this.height = height; this.screenWidth = screenWidth; this.screenHeight = screenHeight; docked = isDocked; } } [Serializable] struct ProfileAnalyzerUIResizeEvent { public ProfileAnalyzerUIResizeEvent(string name, float durationInTicks, float width, float height, float screenWidth, float screenHeight, bool isDocked) { subtype = "profileAnalyzerUIResize"; this.duration = durationInTicks; parameters = new ProfileAnalyzerUIResizeEventParameters(name, width, height, screenWidth, screenHeight, isDocked); } public string subtype; public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds public ProfileAnalyzerUIResizeEventParameters parameters; } static float SecondsToTicks(float durationInSeconds) { return durationInSeconds * 10000; } public static bool SendUIButtonEvent(UIButton uiButton, float durationInSeconds) { if (!s_EnableAnalytics) return false; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds float durationInTicks = SecondsToTicks(durationInSeconds); ProfileAnalyzerUIButtonEvent uiButtonEvent; switch (uiButton) { case UIButton.Pull: uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerGrab", durationInTicks); break; case UIButton.OpenProfiler: uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerOpenProfiler", durationInTicks); break; case UIButton.CloseProfiler: uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerCloseProfiler", durationInTicks); break; case UIButton.JumpToFrame: uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerJumpToFrame", durationInTicks); break; case UIButton.ExportSingleFrames: uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerExportSingleFrames", durationInTicks); break; case UIButton.ExportComparisonFrames: uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerExportComparisonFrames", durationInTicks); break; default: Debug.LogFormat("SendUIButtonEvent: Unsupported button type : {0}", uiButton); return false; } AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiButtonEvent); if (result != AnalyticsResult.Ok) return false; return true; } public static bool SendUIUsageModeEvent(UIUsageMode uiUsageMode, float durationInSeconds) { if (!s_EnableAnalytics) return false; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds float durationInTicks = SecondsToTicks(durationInSeconds); ProfileAnalyzerUIUsageEvent uiUsageEvent; switch (uiUsageMode) { case UIUsageMode.Single: uiUsageEvent = new ProfileAnalyzerUIUsageEvent("profileAnalyzerSingle", durationInTicks); break; case UIUsageMode.Comparison: uiUsageEvent = new ProfileAnalyzerUIUsageEvent("profileAnalyzerCompare", durationInTicks); break; default: Debug.LogFormat("SendUsageEvent: Unsupported usage mode : {0}", uiUsageMode); return false; } AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiUsageEvent); if (result != AnalyticsResult.Ok) return false; return true; } public static bool SendUIVisibilityEvent(UIVisibility uiVisibility, float durationInSeconds, bool show) { if (!s_EnableAnalytics) return false; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds float durationInTicks = SecondsToTicks(durationInSeconds); ProfileAnalyzerUIVisibilityEvent uiUsageEvent; switch (uiVisibility) { case UIVisibility.FrameTimeContextMenu: uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerFrameTimeContextMenu", durationInTicks, show); break; case UIVisibility.Filters: uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerFilters", durationInTicks, show); break; case UIVisibility.TopTen: uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerTopTen", durationInTicks, show); break; case UIVisibility.Frames: uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerFrames", durationInTicks, show); break; case UIVisibility.Threads: uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerThreads", durationInTicks, show); break; case UIVisibility.Markers: uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerMarkers", durationInTicks, show); break; default: Debug.LogFormat("SendUIVisibilityEvent: Unsupported visibililty item : {0}", uiVisibility); return false; } AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiUsageEvent); if (result != AnalyticsResult.Ok) return false; return true; } public static bool SendUIResizeEvent(UIResizeView uiResizeView, float durationInSeconds, float width, float height, bool isDocked) { if (!s_EnableAnalytics) return false; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds float durationInTicks = SecondsToTicks(durationInSeconds); ProfileAnalyzerUIResizeEvent uiResizeEvent; switch (uiResizeView) { case UIResizeView.Single: // Screen.width, Screen.height is game view size uiResizeEvent = new ProfileAnalyzerUIResizeEvent("profileAnalyzerSingle", durationInTicks, width, height, Screen.currentResolution.width, Screen.currentResolution.height, isDocked); break; case UIResizeView.Comparison: uiResizeEvent = new ProfileAnalyzerUIResizeEvent("profileAnalyzerCompare", durationInTicks, width, height, Screen.currentResolution.width, Screen.currentResolution.height, isDocked); break; default: Debug.LogFormat("SendUIResizeEvent: Unsupported view : {0}", uiResizeView); return false; } AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiResizeEvent); if (result != AnalyticsResult.Ok) return false; return true; } internal class Analytic { double m_StartTime; float m_DurationInSeconds; public Analytic() { m_StartTime = EditorApplication.timeSinceStartup; m_DurationInSeconds = 0; } public void End() { m_DurationInSeconds = (float)(EditorApplication.timeSinceStartup - m_StartTime); } public float GetDurationInSeconds() { return m_DurationInSeconds; } } static public Analytic BeginAnalytic() { return new Analytic(); } static public void SendUIButtonEvent(UIButton uiButton, Analytic instance) { instance.End(); SendUIButtonEvent(uiButton, instance.GetDurationInSeconds()); } static public void SendUIUsageModeEvent(UIUsageMode uiUsageMode, Analytic instance) { instance.End(); SendUIUsageModeEvent(uiUsageMode, instance.GetDurationInSeconds()); } } }