Repository: Unity-Technologies/com.unity.editoriterationprofiler Branch: master Commit: 02a50237af74 Files: 83 Total size: 148.4 KB Directory structure: gitextract_9o_f0g0j/ ├── .gitignore ├── CHANGELOG.md ├── CHANGELOG.md.meta ├── CONTRIBUTING.md ├── CONTRIBUTING.md.meta ├── Documentation~/ │ ├── TableOfContents.md │ ├── editor-iteration-profiler.md │ ├── exporting-data.md │ └── index.md ├── Editor/ │ ├── DataCollector.cs │ ├── DataCollector.cs.meta │ ├── EditorIterationProfiler.API/ │ │ ├── IEditorIterationProfilerController.cs │ │ ├── IEditorIterationProfilerController.cs.meta │ │ ├── IEventSubscriber.cs │ │ ├── IEventSubscriber.cs.meta │ │ ├── IIterationList.cs │ │ ├── IIterationList.cs.meta │ │ ├── IProfilerDataCollector.cs │ │ └── IProfilerDataCollector.cs.meta │ ├── EditorIterationProfiler.API.meta │ ├── EditorIterationProfilerAnalytics.cs │ ├── EditorIterationProfilerAnalytics.cs.meta │ ├── EditorIterationProfilerController.cs │ ├── EditorIterationProfilerController.cs.meta │ ├── EditorIterationProfilerIntegration.cs │ ├── EditorIterationProfilerIntegration.cs.meta │ ├── EditorIterationProfilerSettings.cs │ ├── EditorIterationProfilerSettings.cs.meta │ ├── EditorIterationProfilerTreeView.cs │ ├── EditorIterationProfilerTreeView.cs.meta │ ├── EditorIterationProfilerWindow.cs │ ├── EditorIterationProfilerWindow.cs.meta │ ├── EventData.cs │ ├── EventData.cs.meta │ ├── Formatters/ │ │ ├── Aggregator.cs │ │ ├── Aggregator.cs.meta │ │ ├── DataReporterProvider.cs │ │ ├── DataReporterProvider.cs.meta │ │ ├── DataReporters.cs │ │ ├── DataReporters.cs.meta │ │ ├── FileReporter.cs │ │ ├── FileReporter.cs.meta │ │ ├── Formatter.cs │ │ ├── Formatter.cs.meta │ │ ├── IndentationProvider.cs │ │ ├── IndentationProvider.cs.meta │ │ ├── Reporters/ │ │ │ ├── CSVReporter.cs │ │ │ ├── CSVReporter.cs.meta │ │ │ ├── ChromeTracingReporter.cs │ │ │ ├── ChromeTracingReporter.cs.meta │ │ │ ├── EditorLogReporter.cs │ │ │ ├── EditorLogReporter.cs.meta │ │ │ ├── HTMLPerfReport.cs │ │ │ ├── HTMLPerfReport.cs.meta │ │ │ ├── HTMLReporter.cs │ │ │ ├── HTMLReporter.cs.meta │ │ │ ├── HTMLReporterPrefix.txt │ │ │ ├── HTMLReporterPrefix.txt.meta │ │ │ ├── PlaintextReporter.cs │ │ │ └── PlaintextReporter.cs.meta │ │ └── Reporters.meta │ ├── Formatters.meta │ ├── IterationEventKind.cs │ ├── IterationEventKind.cs.meta │ ├── IterationEventRoot.cs │ ├── IterationEventRoot.cs.meta │ ├── IterationList.cs │ ├── IterationList.cs.meta │ ├── ProfilerDataCollector.cs │ ├── ProfilerDataCollector.cs.meta │ ├── Unity.EditorIterationProfiler.Editor.asmdef │ ├── Unity.EditorIterationProfiler.Editor.asmdef.meta │ ├── UnityEditorEvents.cs │ ├── UnityEditorEvents.cs.meta │ ├── UnityProfiling.cs │ └── UnityProfiling.cs.meta ├── Editor.meta ├── LICENSE.md ├── LICENSE.md.meta ├── README.md ├── README.md.meta ├── package.json └── package.json.meta ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # =============== # # Unity generated # # =============== # [Ll]ibrary/ [Tt]emp/ [Oo]bj/ [Bb]uild/ [Bb]uilds/ [Uu]nity[Pp]ackage[Mm]anager/ [Ll]ogs/ [Pp]ackages/[Mm]icrosoft*/ *.pidb.meta *.pdb.meta *.mdb.meta *.apk *.unitypackage *.log app.config packages.config # Unity3D Generated File On Crash Reports # sysinfo.txt # ===================================== # # Visual Studio / MonoDevelop / Rider etc. generated # # ===================================== # ExportedObj/ .consulo/ .vs/ *.csproj *.unityproj *.sln *.suo *.tmp *.user *.userprefs *.pidb *.booproj *.svd *.pdb *.mdb *.opendb *.VC.db .idea # ============ # # OS generated # # ============ # .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes ehthumbs.db Thumbs.db ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to the webrtc package will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [0.1.2-preview] - 2021-09-23 ### Changed - Deferred drawing the GUI so it pollutes the data less. - Fixed an issue where "Domain unloader" wasn't found as it was renamed to "Domain Unloader". ## [0.1.1-preview] - 2020-10-03 ### Changed - Fixed documentation references. - Removed square brackets from the name of the entries in this window. ## [0.1.0-preview] - 2020-06-10 - Initial Release. ================================================ FILE: CHANGELOG.md.meta ================================================ fileFormatVersion: 2 guid: 249ab3ebb372f234e98dbac31f5e3265 TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing ## All contributions are subject to the [Unity Contribution Agreement(UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement) By making a pull request, you are confirming agreement to the terms and conditions of the UCA, including that your Contributions are your original creation and that you have complete right and authority to make your Contributions. ## Once you have a change ready following these ground rules. Simply make a pull request ================================================ FILE: CONTRIBUTING.md.meta ================================================ fileFormatVersion: 2 guid: 2b6a7b8e81611044a8d28e71c0e71043 TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Documentation~/TableOfContents.md ================================================ * [About the Editor Iteration Profiler](index.md) * [Using the Editor Iteration Profiler](editor-iteration-profiler.md) * [Exporting data](exporting-data.md) ================================================ FILE: Documentation~/editor-iteration-profiler.md ================================================ # Using the Editor Iteration Profiler Once you’ve installed the Editor Iteration Profiler, navigate to __Window > Analysis > Editor Iteration Profiler > Show Window__. This opens the EIP in its own window. ![](images/editor-iteration-profiler-window.png)
*The Editor Iteration profiler window* ## Editor Iteration Profiler controls The controls for the EIP are in the toolbar at the top of the window. You can use these controls to navigate through the data and export it. |**Control**|**Function**| |---|---| |**Enable**|Enables the Editor Iteration Profiler and starts collecting data. When you select this button, the EIP also enables the Profiler and sets it to run with the __Editor__ as its profiling target.| |**Deep Profile**|Collects all information on managed calls. For more information on deep profiling, see the documentation on the [Profiler window](https://docs.unity3d.com/Manual/ProfilerWindow.html#deep-profiling).| |**Flatten**|Collapses events in the table that contain multiple parent items with one child. This is useful if you are investigating GUI code or have performed Deep Profiling.| |**User Code**|Filters out Unity engine code and displays information related to your own code.| |**Clear**|Removes all recorded events from the EIP.| |**Collapse All**|Recursively collapses all events in the table.| |**Print to Console**|Logs all data to the console in a plain text format.| |**Export**|Export all of the data in the EIP. You can choose from HTML, JSON, CSV, Plaintext, or HTML Performance Report. For more information on this see the documentation on [Exporting data](exporting-data.md).| |**Export Profiler Data**|Export the data in the EIP for the selected Profiler frame, or choose a range of Profiler frames to export the EIP data for. When you select **Multiple Frames**, the beginning and end limits are inclusive. For more information, see the documentation on [How to export data](exporting-data#how-to-export-data.md)| ================================================ FILE: Documentation~/exporting-data.md ================================================ # Exporting Data You can export the data that the Editor Iteration Profiler captures in several different file formats so that you can inspect the performance of your application in greater detail. The following file formats are available: * [.html](exporting-data#html.md) * [.json](exporting-data#json.md) (for [chrome tracing](http://www.chromium.org/developers/how-tos/trace-event-profiling-tool)) * [.csv](exporting-data#csv.md) * [Plain text](exporting-data#plain-text.md) * [HTML Performance Report](exporting-data#html-report.md) ## How to export data To export all of the data that the EIP has captured, select __Export__ in the toolbar at the top of the EIP window, and then choose the format you want to export the data in. The EIP then displays a Save dialog box, where you can choose where to save the data to. To export the data that the EIP has captured for a particular frame in the Profiler, select that frame in the Profiler window. Then in the EIP window’s toolbar, select __Export Profiler Data > Export Selected Frame__ and choose the format you’d like to export the data to. The EIP then displays a Save dialog box, where you can choose where to save the data to. You can also export the data the EIP has captured for a range of Profiler frames. To do this, select __Export Profiler Data > Multiple Frames__ in the EIP window’s toolbar, and then choose the format you’d like to export the data to. The EIP then displays a dialog box that you can use to set the beginning and end range for the frames you want to export the data for. ![](images/export-profiler-frame-selection.png)
*The frame range selector dialog box* Select the __Export__ button and then the EIP displays a Save dialog box where you can choose where to save the data to. ## HTML export option The __HTML__ export option exports the EIP data to a .html format that you can then open in a web browser and inspect further. It contains the same data in the EIP window, but also displays a percentage of how much time was spent on each event. ![](images/export-html.png)
*EIP data in HTML format* > [!NOTE] > Events in square brackets are leaf items without any children. ## JSON export option The __JSON__ export option exports the EIP data to a .json format, which you can then use to visualize the data with the [Chrome Tracing](http://www.chromium.org/developers/how-tos/trace-event-profiling-tool) tool. To use Chrome Tracing, go to `chrome://tracing` in a Chromium-based web browser(Google Chrome, Microsoft Edge Chromium, Opera, etc.), and then import the .json file. ![](images/export-chrome-tracing.png)
*EIP data in .json format imported into Chrome Tracing* ## CSV export option The __CSV__ export option exports the EIP data to a .csv format which you can then use to import the data into other programs, such as Excel or a Python library. > [!NOTE] >This file contains a header in which environment information is stored (system specs, date etc.) ## Plaintext export option The __Plaintext__ export option exports the EIP data into a plain text file format. This is the same data that you can display in the console if you select __Print to Console__ button in the EIP window’s toolbar. It also gives you the option to isolate the data and remove other information which is in the `Editor.log` file. ## HTML Performance Report export option The __HTML Performance Report__ displays the EIP data in a similar way to the HTML export option, but additionally groups together information and also reduces the number of levels you need to click through to get to important information. ![](images/export-html-performance-report.png)
*HTML Performance Report view* Items in curly brackets `{}` represent items that have children that the EIP hid because the parents were under the minimum set threshold, which is currently set to a hard-coded value of 1%. The colored items represent ‘buckets’ of similar data, which the EIP groups in one place for convenience. The EIP does not add the total time of these to the original time, and it gives an estimate for that iteration. ================================================ FILE: Documentation~/index.md ================================================ # About Editor Iteration Profiler The Editor Iteration Profiler (EIP) is a tool that you can use alongside [Unity's built-in Profiler](https://docs.unity3d.com/Manual/Profiler.html) to monitor Editor iteration (domain reload) times. It helps you to understand why Unity takes a long time to compile your scripts or enter Play Mode. An iteration is a process that contains instructions that it repeats until a condition is met. Unity relies on a variety of iteration types. The EIP uses the data from the Profiler to monitor iterations that relate to the scripting side of Unity, specifically: * Entering and exiting Play Mode * Assembly reloads * Script compilations During these iterations, the Profiler also monitors Asset import time. The EIP saves all of this information in a separate window which you can then use to navigate through the data the Profiler produces. What’s more, the data that the EIP collects persists for the lifetime of the Editor, unlike the built-in Profiler which is limited to storing a set number of frames. You can then [export the captured data](exporting-data.md) to either HTML, JSON, CSV, or plain text format. In addition to these formats, you can export the data to a HTML Performance Report, which groups the data together to make it easier to see areas you can optimize. ## Preview package This package is available as a preview, so it is not ready for production use. The features and documentation in this package might change before it is verified for release. ## Package contents The following table describes the package folder structure: |**Location**|**Description**| |---|---| |`Editor`| Contains the code for this package.| |`Documentation~`| Contains the documentation for the package.| ## Installation To install this package, perform the following steps: * Download the repository from GitHub * Place it into your Project’s `Packages` folder ## Requirements This version of Editor Iteration Profiler is compatible with the following versions of the Unity Editor: * 2019.3 and later ## Known limitations The Editor Iteration Profiler package has the following known limitations: * During Deep Profiling the system might run out of memory and freeze the whole Editor for extended periods of time, especially in large Projects. * If the script compilation taxes more than 600 frames, the EIP might not capture all data. ## Feedback and troubleshooting Please report bugs/suggestions at https://github.com/Unity-Technologies/com.unity.editoriterationprofiler/issues . If you encounter any problems with the EIP, either select the __Clear__ button in the toolbar of the EIP window, or go to __Window > Analysis > Editor Iteration Profiler > Purge Cache__ to clear the EIP’s cache. This usually resolves most issues. ================================================ FILE: Editor/DataCollector.cs ================================================ using System; using UnityEditor.EditorIterationProfiler.API; using UnityEngine.Assertions; namespace UnityEditor.EditorIterationProfiler { [Serializable] class DataCollector : IEventSubscriber { IIterationList m_IterationList; IProfilerDataCollector m_ProfilerDataCollector; public DataCollector(IProfilerDataCollector profilerCollector, IIterationList iterationList) { Initialize(profilerCollector, iterationList); } void Initialize(IProfilerDataCollector profilerCollector, IIterationList iterationList) { m_IterationList = iterationList; m_ProfilerDataCollector = profilerCollector; } public void Subscribe() { UnityEditorEvents.Subscribe(); UnityEditorEvents.EditorEvent += EditorEvent; } public void Unsubscribe() { UnityEditorEvents.Unsubscribe(); UnityEditorEvents.EditorEvent -= EditorEvent; } void EditorEvent(UnityEditorEvents.Event evt, string data) { Assert.IsNotNull(m_IterationList); if (!UnityProfiling.EditorProfilingEnabled) { return; } //Debug.Log($"EditorEvent: {evt} {data}"); switch (evt) { case UnityEditorEvents.Event.ScriptCompilationStarted: { m_IterationList.NewIteration(IterationEventKind.ScriptCompilation); var assetImportEvent = m_IterationList.LastIterationEventRoot.StartEvent(IterationEventKind.AssetImport); m_ProfilerDataCollector.Collect(IterationEventKind.AssetImport, m_IterationList.LastIterationEventRoot, assetImportEvent); var scriptCompilationEvent = m_IterationList.LastIterationEventRoot.StartEvent(IterationEventKind.ScriptCompilation); scriptCompilationEvent.SetStartTime(); break; } case UnityEditorEvents.Event.ScriptCompilationFinished: { m_IterationList.LastIterationEventRoot.FinishEvent(IterationEventKind.ScriptCompilation); break; } case UnityEditorEvents.Event.AssemblyCompilationStarted: { var eventData = m_IterationList.LastIterationEventRoot.StartEvent(IterationEventKind.AssemblyCompilation, data, null); m_IterationList.LastIterationEventRoot.SetParent(eventData, IterationEventKind.ScriptCompilation); //m_ProfilerDataCollector.Collect(IterationEventKind.AssemblyCompilationStart, m_IterationList.LastIterationEventRoot, eventData); eventData.SetStartTime(); break; } case UnityEditorEvents.Event.AssemblyCompilationFinished: { //var ev = m_IterationList.LastIterationEventRoot.FindLastEvent(IterationEventKind.ScriptCompilation); //m_ProfilerDataCollector.Collect(IterationEventKind.AssemblyCompilationFinish, m_IterationList.LastIterationEventRoot, ev); m_IterationList.LastIterationEventRoot.FinishEvent(data); break; } case UnityEditorEvents.Event.AssemblyReloadStarted: { if (m_IterationList.LastIterationEventRoot != null) { var enterPlayModeEvent = m_IterationList.LastIterationEventRoot.FindLastEvent(IterationEventKind.EnterPlayMode); // If this assembly reload is part of a enter play mode iteration, // then do no add the event, as it happens in the same frame and // profiling data will contain the domain reload event data. if (enterPlayModeEvent != null) { return; } var eventData = m_IterationList.LastIterationEventRoot.StartEvent(IterationEventKind.AssemblyReload); m_ProfilerDataCollector.Collect(IterationEventKind.AssemblyReload, m_IterationList.LastIterationEventRoot, eventData); } break; } case UnityEditorEvents.Event.AssemblyReloadFinished: { break; } case UnityEditorEvents.Event.EnteringPlayMode: { m_IterationList.NewIteration(IterationEventKind.EnterPlayMode); var eventData = m_IterationList.LastIterationEventRoot.StartEvent(IterationEventKind.EnterPlayMode); m_ProfilerDataCollector.Collect(IterationEventKind.EnterPlayMode, m_IterationList.LastIterationEventRoot, eventData); break; } case UnityEditorEvents.Event.EnteredPlayMode: { break; } case UnityEditorEvents.Event.ExitingPlayMode: { m_IterationList.NewIteration(IterationEventKind.ExitPlayMode); var eventData = m_IterationList.LastIterationEventRoot.StartEvent(IterationEventKind.ExitPlayMode); m_ProfilerDataCollector.Collect(IterationEventKind.ExitPlayMode, m_IterationList.LastIterationEventRoot, eventData); break; } case UnityEditorEvents.Event.ExitedPlayMode: { break; } } } } } ================================================ FILE: Editor/DataCollector.cs.meta ================================================ fileFormatVersion: 2 guid: ea639bdc1b7738841a40c85658e414f1 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/EditorIterationProfiler.API/IEditorIterationProfilerController.cs ================================================ namespace UnityEditor.EditorIterationProfiler.API { public interface IEditorIterationProfilerController { IIterationList IterationList { get; } IDataReporterProvider DataReporterProvider { get; } EditorIterationProfilerSettings Settings { get; } } } ================================================ FILE: Editor/EditorIterationProfiler.API/IEditorIterationProfilerController.cs.meta ================================================ fileFormatVersion: 2 guid: 3b753f26d3c16a64fb73f79a76bfc62c MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/EditorIterationProfiler.API/IEventSubscriber.cs ================================================ namespace UnityEditor.EditorIterationProfiler { public interface IEventSubscriber { void Subscribe(); void Unsubscribe(); } } ================================================ FILE: Editor/EditorIterationProfiler.API/IEventSubscriber.cs.meta ================================================ fileFormatVersion: 2 guid: dfbe793476c38d545acebf95bef144a9 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/EditorIterationProfiler.API/IIterationList.cs ================================================ using System; using System.Collections.Generic; using UnityEditor.EditorIterationProfiler.Formatting; namespace UnityEditor.EditorIterationProfiler { public interface IIterationList { List IterationEventRoots { get; } IterationEventRoot LastIterationEventRoot { get; } List IterationEventKinds { get; } Action Updated { get; set; } void NewIteration(IterationEventKind kind); void NotifyUpdated(); } } ================================================ FILE: Editor/EditorIterationProfiler.API/IIterationList.cs.meta ================================================ fileFormatVersion: 2 guid: b3881855ff94b2a419205af6f42f003b MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/EditorIterationProfiler.API/IProfilerDataCollector.cs ================================================ namespace UnityEditor.EditorIterationProfiler.API { public interface IProfilerDataCollector : IEventSubscriber { void Collect(IterationEventKind iterationEventKind, IterationEventRoot iterationEventRoot, EventData rootEvent); void Clear(); } } ================================================ FILE: Editor/EditorIterationProfiler.API/IProfilerDataCollector.cs.meta ================================================ fileFormatVersion: 2 guid: a368e7eb7b9059c429c7bb9837c8f3fa MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/EditorIterationProfiler.API.meta ================================================ fileFormatVersion: 2 guid: 69bdb52499db5994ca64e22a6bc04cf5 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/EditorIterationProfilerAnalytics.cs ================================================ using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Analytics; namespace UnityEditor.EditorIterationProfiler { class EditorIterationProfilerAnalytics { const string k_VendorKey = "unity.editoriterationprofiler"; static bool s_ExportEventRegistered; static bool s_InteractionEventRegistered; const int k_MaxEventsPerHour = 1000; const int k_MaxNumberOfElements = 1000; const string k_ExportEventName = "eipExport"; const string k_InteractionEventName = "eipInteraction"; static bool EnableAnalytics() { AnalyticsResult resultExport = EditorAnalytics.RegisterEventWithLimit(k_ExportEventName, k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey); AnalyticsResult resultInteraction = EditorAnalytics.RegisterEventWithLimit(k_InteractionEventName, k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey); if (resultExport == AnalyticsResult.Ok) { s_ExportEventRegistered = true; } if (resultExport == AnalyticsResult.Ok) { s_InteractionEventRegistered = true; } return s_ExportEventRegistered && s_InteractionEventRegistered; } internal enum ExportStatus { Started, Finished, Error } internal enum ExportType { Selected, MultiWindow, Multi, Captured, CapturedAll } struct ExportEventData { public string guid; public string format; public string type; public string status; } struct InteractionEventData { public bool eipState; public bool isPlaying; public bool deepProfile; public bool flatten; public bool userCode; } public static void SendExportEvent(string format, string type, string status, string guid) { if (!UnityEngine.Analytics.Analytics.enabled || !EnableAnalytics()) { return; } var data = new ExportEventData() { format = format, type = type, status = status, guid = guid }; EditorAnalytics.SendEventWithLimit(k_ExportEventName, data); } public static void SendInteractionEvent(bool state, bool isPlaying, bool deepProfile, bool flatten, bool userCode) { if (!UnityEngine.Analytics.Analytics.enabled || !EnableAnalytics()) { return; } var data = new InteractionEventData() { eipState = state, isPlaying = isPlaying, deepProfile = deepProfile, flatten = flatten, userCode = userCode }; EditorAnalytics.SendEventWithLimit(k_InteractionEventName, data); } } } ================================================ FILE: Editor/EditorIterationProfilerAnalytics.cs.meta ================================================ fileFormatVersion: 2 guid: 5248670f7ba3f2c419bab949189a46bc MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/EditorIterationProfilerController.cs ================================================ using System.Collections.Generic; using UnityEditor.EditorIterationProfiler.API; using UnityEngine; using UnityEngine.Assertions; namespace UnityEditor.EditorIterationProfiler { public class EditorIterationProfilerController : ScriptableObject, IEditorIterationProfilerController { [SerializeField] IterationList m_IterationList; public IIterationList IterationList => m_IterationList; [SerializeField] ProfilerDataCollector m_ProfilerDataCollector; public IProfilerDataCollector ProfilerDataCollector => m_ProfilerDataCollector; [SerializeField] DataReporterProvider m_DataReporterProvider; public IDataReporterProvider DataReporterProvider => m_DataReporterProvider; [SerializeField] EditorIterationProfilerSettings m_Settings; public EditorIterationProfilerSettings Settings => m_Settings; [SerializeField] DataCollector m_DataCollector; public EditorIterationProfilerController() { m_IterationList = new IterationList(); m_ProfilerDataCollector = new ProfilerDataCollector(m_IterationList); m_DataCollector = new DataCollector(m_ProfilerDataCollector, m_IterationList); m_DataReporterProvider = new DataReporterProvider(); m_Settings = new EditorIterationProfilerSettings(); } public EditorIterationProfilerController(IIterationList iterationList, IProfilerDataCollector profilerDataCollector) { m_IterationList = iterationList as IterationList; m_ProfilerDataCollector = profilerDataCollector as ProfilerDataCollector; m_DataCollector = new DataCollector(m_ProfilerDataCollector, m_IterationList); m_DataReporterProvider = new DataReporterProvider(); m_Settings = new EditorIterationProfilerSettings(); } void OnEnable() { m_DataCollector.Subscribe(); m_ProfilerDataCollector.Subscribe(); } void OnDisable() { m_DataCollector.Unsubscribe(); m_ProfilerDataCollector.Unsubscribe(); } internal void Initialize() { m_IterationList.Reload(); } public void Clear() { m_IterationList.Clear(); m_ProfilerDataCollector.Clear(); } } } ================================================ FILE: Editor/EditorIterationProfilerController.cs.meta ================================================ fileFormatVersion: 2 guid: a7e1f201c78caea4ab211abb5ca49032 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/EditorIterationProfilerIntegration.cs ================================================ using System.Linq; using UnityEditor.EditorIterationProfiler.API; using UnityEngine; using Object = UnityEngine.Object; namespace UnityEditor.EditorIterationProfiler { [InitializeOnLoad] public static class EditorIterationProfilerIntegration { static EditorIterationProfilerController s_Instance; public static IEditorIterationProfilerController Instance { get { if (s_Instance != null) { return s_Instance; } FindOrCreateInstance(); return s_Instance; } } static EditorIterationProfilerIntegration() { EditorApplication.delayCall += Initialize; } internal static void Initialize() { FindOrCreateInstance(); } static void FindOrCreateInstance() { EditorIterationProfilerController[] instances = Resources.FindObjectsOfTypeAll(); if (instances != null) { s_Instance = instances.FirstOrDefault(); } if (s_Instance == null) { s_Instance = ScriptableObject.CreateInstance(); s_Instance.hideFlags = HideFlags.DontSave; } s_Instance.Initialize(); } public static void Clear() { s_Instance.Clear(); } [MenuItem("Window/Analysis/Editor Iteration Profiler/Purge Caches", priority = 21)] public static void PurgeScriptableObjects() { Debug.Log("Caches Purged!", s_Instance); if (s_Instance != null) { Clear(); } EditorIterationProfilerController[] instances = Resources.FindObjectsOfTypeAll(); foreach (var instance in instances) { Object.DestroyImmediate(instance); } FindOrCreateInstance(); } } } ================================================ FILE: Editor/EditorIterationProfilerIntegration.cs.meta ================================================ fileFormatVersion: 2 guid: 9920ea31757240744bbf4d4b9c7f9f14 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/EditorIterationProfilerSettings.cs ================================================ using System; using System.Text; using UnityEditor.Compilation; using UnityEngine; namespace UnityEditor.EditorIterationProfiler.API { [Serializable] public class EditorIterationProfilerSettings { [field: SerializeField] public bool DeepProfile { get; set; } [field: SerializeField] public bool Flatten { get; set; } [field: SerializeField] public bool UserCode { get; set; } internal string UnityVersion => Application.unityVersion; internal string ProductName => PlayerSettings.productName; internal string Time => DateTime.UtcNow + " UTC"; internal bool FastEnterPlayMode => EditorSettings.enterPlayModeOptionsEnabled; internal string Platform => Application.platform.ToString(); #if UNITY_2020_OR_NEWER internal string CodeOptimization => CompilationPipeline.codeOptimization.ToString(); #endif internal string SystemInfo => UnityEngine.SystemInfo.processorType.TrimEnd() + "; " + UnityEngine.SystemInfo.systemMemorySize / 1000 + " GB RAM"; public override string ToString() { var sb = new StringBuilder(); sb.AppendLine($"Time: {EditorIterationProfilerIntegration.Instance.Settings.Time}"); sb.AppendLine($"Unity Version: {EditorIterationProfilerIntegration.Instance.Settings.UnityVersion}"); sb.AppendLine($"Platform: {EditorIterationProfilerIntegration.Instance.Settings.Platform}"); sb.AppendLine($"System Specs: {EditorIterationProfilerIntegration.Instance.Settings.SystemInfo}"); sb.AppendLine($"Product Name: {EditorIterationProfilerIntegration.Instance.Settings.ProductName}"); sb.AppendLine($"Deep Profile: {EditorIterationProfilerIntegration.Instance.Settings.DeepProfile}"); sb.AppendLine($"Flatten: {EditorIterationProfilerIntegration.Instance.Settings.Flatten}"); sb.AppendLine($"User Code: {EditorIterationProfilerIntegration.Instance.Settings.UserCode}"); sb.AppendLine($"Fast EnterPlayMode: {EditorIterationProfilerIntegration.Instance.Settings.FastEnterPlayMode}"); #if UNITY_2020_OR_NEWER sb.Append($"Editor Code Optimization: {EditorIterationProfilerIntegration.Instance.Settings.CodeOptimization}"); #endif return sb.ToString(); } } } ================================================ FILE: Editor/EditorIterationProfilerSettings.cs.meta ================================================ fileFormatVersion: 2 guid: 0635b7d495ef2fa4a97e5eb88021bacf MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/EditorIterationProfilerTreeView.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using UnityEditor.IMGUI.Controls; using UnityEngine; using UnityEngine.Assertions; namespace UnityEditor.EditorIterationProfiler { [Serializable] class EditorIterationProfilerTreeViewItem : TreeViewItem { public string Details { get; set; } public double Duration { get; set; } } public class EditorIterationProfilerTreeView : TreeView { enum ColumnId { Event, Details, Duration, } static readonly ColumnId[] k_ColumnTypes = { ColumnId.Event, ColumnId.Details, ColumnId.Duration, }; bool m_UserCodeOnly; public bool UserCodeOnly { get => m_UserCodeOnly; set { m_UserCodeOnly = value; EditorIterationProfilerIntegration.Instance.Settings.UserCode = value; } } bool m_Flatten; public bool Flatten { get => m_Flatten; set { m_Flatten = value; EditorIterationProfilerIntegration.Instance.Settings.Flatten = value; } } public EditorIterationProfilerTreeView(TreeViewState state, MultiColumnHeader header) : base(state, header) { if (header == null) { throw new ArgumentNullException(nameof(header), "Header is null"); } showAlternatingRowBackgrounds = true; showBorder = true; header.sortingChanged += OnSortingChanged; UserCodeOnly = EditorPrefs.GetBool(EditorIterationProfilerWindow.Styles.k_UserCodePref); Flatten = EditorPrefs.GetBool(EditorIterationProfilerWindow.Styles.k_FlattenPref); EditorIterationProfilerIntegration.Instance.IterationList.Updated += iterationList => Reload(); Reload(); } void OnSortingChanged(MultiColumnHeader multiColumnHeader) { var index = multiColumnHeader.sortedColumnIndex; var columnTypes = k_ColumnTypes[index]; if (hasSearch) { //Sort(GetRows(), columnTypes, multiColumnHeader.IsSortedAscending(index)); } else { //SortHierarchical(rootItem, columnTypes, multiColumnHeader.IsSortedAscending(index)); } Reload(); } static void Sort(IList rows, ColumnId columnId, bool ascending) { if (rows == null) { return; } IList sortedRows; switch (columnId) { case ColumnId.Details: { sortedRows = rows.Order(x => ((EditorIterationProfilerTreeViewItem)x).Details, ascending).ToList(); break; } case ColumnId.Duration: { sortedRows = rows.Order(x => ((EditorIterationProfilerTreeViewItem)x).Duration, ascending).ToList(); break; } default: { sortedRows = rows.Order(x => ((EditorIterationProfilerTreeViewItem)x).displayName, ascending).ToList(); break; } } rows.Clear(); foreach (var r in sortedRows) { rows.Add(r); } } static void SortHierarchical(TreeViewItem root, ColumnId columnId, bool ascending = true) { if (root == null) { return; } SortItems(root.children, columnId, ascending); } static void SortItems(IList children, ColumnId columnId, bool ascending = true) { if (children == null) { return; } IList sortedRows; switch (columnId) { case ColumnId.Details: { sortedRows = children.Order(x => ((EditorIterationProfilerTreeViewItem)x).Details, ascending).ToList(); break; } case ColumnId.Duration: { sortedRows = children.Order(x => ((EditorIterationProfilerTreeViewItem)x).Duration, ascending).ToList(); break; } default: { sortedRows = children.Order(x => ((EditorIterationProfilerTreeViewItem)x).displayName, ascending).ToList(); break; } } children.Clear(); foreach (var r in sortedRows) { children.Add(r); } foreach (var c in children) { SortItems(c.children, columnId, ascending); } } protected override void DoubleClickedItem(int id) { if (hasSearch && state.selectedIDs.Count != 0) { ClearSearch(); CollapseAll(); FrameItem(id); } else { SetExpanded(id, !IsExpanded(id)); } } internal void ClearSearch() { searchString = string.Empty; } protected override TreeViewItem BuildRoot() { var root = new EditorIterationProfilerTreeViewItem { id = 0, depth = -1, displayName = "Root", Details = null }; var allItems = new List(); var iterationList = EditorIterationProfilerIntegration.Instance.IterationList; var idCounter = 1; var iterationCounter = 1; var eventDataFlagsFilter = EventDataFlags.None; if (UserCodeOnly) { eventDataFlagsFilter |= EventDataFlags.UserCode; } if (Flatten) { eventDataFlagsFilter |= EventDataFlags.Flatten; } for (var i = 0; i < iterationList.IterationEventRoots.Count; ++i) { var eventDataList = iterationList.IterationEventRoots[i]; var iterationItem = new EditorIterationProfilerTreeViewItem { id = idCounter++, depth = 0, displayName = string.Empty }; double iterationDuration = 0; allItems.Add(iterationItem); foreach (var eventData in eventDataList.Events) { if (eventData.ParentIndex < 0) { iterationDuration += eventData.Duration; AddEventDataRecursive(eventData, allItems, ref idCounter, 1, eventDataFlagsFilter); } } iterationItem.displayName = $"Iteration Event {iterationCounter++} ({iterationList.IterationEventKinds[i]})"; iterationItem.Duration = iterationDuration; } SetupParentsAndChildrenFromDepths(root, allItems); return root; } static bool ShouldAddTreeViewItem(EventData eventData, EventDataFlags filter) { var flags = eventData.Flags; if (eventData.Kind != IterationEventKind.None) { return true; } if (filter == EventDataFlags.None) { return true; } if (filter.HasFlag(EventDataFlags.UserCode) && filter.HasFlag(EventDataFlags.Flatten)) { if ((flags & filter).HasFlag(EventDataFlags.UserCode)) { if ((flags & filter).HasFlag(EventDataFlags.Flatten)) { return false; } return true; } return false; } if ((flags & filter).HasFlag(EventDataFlags.UserCode)) { return true; } if (!(flags & filter).HasFlag(EventDataFlags.Flatten)) { if (filter.HasFlag(EventDataFlags.Flatten)) { return true; } } return false; } static void AddEventDataRecursive(EventData eventData, List treeViewItems, ref int idCounter, int depth, EventDataFlags eventDataFlagsFilter) { if (ShouldAddTreeViewItem(eventData, eventDataFlagsFilter)) { treeViewItems.Add(new EditorIterationProfilerTreeViewItem { id = idCounter++, depth = depth++, displayName = eventData.DisplayName, Details = eventData.Details, Duration = eventData.Duration }); } if (eventData.Children == null) { return; } foreach (var child in eventData.Children) { AddEventDataRecursive(child, treeViewItems, ref idCounter, depth, eventDataFlagsFilter); } } protected override void RowGUI(RowGUIArgs args) { var item = (EditorIterationProfilerTreeViewItem)args.item; for (var i = 0; i < args.GetNumVisibleColumns(); ++i) { CellGUI(args.GetCellRect(i), item, args.GetColumn(i), ref args); } } void CellGUI(Rect cellRect, EditorIterationProfilerTreeViewItem item, int column, ref RowGUIArgs args) { args.rowRect = cellRect; switch (column) { case 0: { base.RowGUI(args); break; } case 1: { if (!string.IsNullOrEmpty(item.Details)) { DefaultGUI.Label(cellRect, item.Details, args.selected, args.focused); } break; } case 2: { DefaultGUI.LabelRightAligned(cellRect, $"{item.Duration:0.000}", args.selected, args.focused); break; } } } public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState() { var columns = new[] { new MultiColumnHeaderState.Column { headerContent = new GUIContent("Event"), headerTextAlignment = TextAlignment.Left, sortingArrowAlignment = TextAlignment.Left, minWidth = 250, autoResize = true, allowToggleVisibility = false }, new MultiColumnHeaderState.Column { headerContent = new GUIContent("Details"), headerTextAlignment = TextAlignment.Left, sortingArrowAlignment = TextAlignment.Center, minWidth = 100, autoResize = false, allowToggleVisibility = true }, new MultiColumnHeaderState.Column { headerContent = new GUIContent("Duration (ms)"), headerTextAlignment = TextAlignment.Left, sortingArrowAlignment = TextAlignment.Right, minWidth = 100, autoResize = false, allowToggleVisibility = false } }; Assert.AreEqual(columns.Length, k_ColumnTypes.Length, L10n.Tr($"Number of columns should match number of {k_ColumnTypes}")); return new MultiColumnHeaderState(columns); } } static class ExtensionMethods { public static IOrderedEnumerable Order(this IEnumerable source, Func selector, bool ascending) { if (ascending) { return source.OrderBy(selector); } return source.OrderByDescending(selector); } public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func selector, bool ascending) { if (ascending) { return source.ThenBy(selector); } else { return source.ThenByDescending(selector); } } } } ================================================ FILE: Editor/EditorIterationProfilerTreeView.cs.meta ================================================ fileFormatVersion: 2 guid: e6651dd15ebe6e1478d78afed8ff3dee MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/EditorIterationProfilerWindow.cs ================================================ using System; using System.IO; using System.Reflection; using UnityEditor.EditorIterationProfiler.Formatting; using UnityEditor.IMGUI.Controls; using UnityEditorInternal; using UnityEngine; using UnityEngine.Assertions; using ExportStatus = UnityEditor.EditorIterationProfiler.EditorIterationProfilerAnalytics.ExportStatus; using ExportType = UnityEditor.EditorIterationProfiler.EditorIterationProfilerAnalytics.ExportType; namespace UnityEditor.EditorIterationProfiler { public class EditorIterationProfilerWindow : EditorWindow { internal static class Styles { public static readonly GUIContent k_EnableProfiler = EditorGUIUtility.TrTextContent("Enable"); public static readonly GUIContent k_ClearProfilerData = EditorGUIUtility.TrTextContent("Clear"); public static readonly GUIContent k_DeepProfile = EditorGUIUtility.TrTextContent("Deep Profile", "Show all managed calls"); public static readonly GUIContent k_UserCode = EditorGUIUtility.TrTextContent("User Code", "Only show user code"); public static readonly GUIContent k_Flatten = EditorGUIUtility.TrTextContent("Flatten", "Flatten markers"); public static readonly GUIContent k_CollapseAll = EditorGUIUtility.TrTextContent("Collapse All", "Collapse all foldouts"); public static readonly GUIContent k_LogConsole = EditorGUIUtility.TrTextContent("Print to Console", "Prints plaintext into the log and console"); public static readonly GUIContent k_Export = EditorGUIUtility.TrTextContent("Export...", "Exports the data captured by the Editor Iteration Profiler"); public static readonly GUIContent k_ExportProfiler = EditorGUIUtility.TrTextContent("Export Profiler Data...", "Exports the selected frame or the last frame in the Profiler"); public const string k_EditorIterationProfilerPrefix = "EIP_"; public const string k_EnableProfilerPref = k_EditorIterationProfilerPrefix + "ProfilingEnabled"; public const string k_EnableDeepProfile = k_EditorIterationProfilerPrefix + "DeepProfilingEnabled"; public const string k_UserCodePref = k_EditorIterationProfilerPrefix + "UserCodeOnly"; public const string k_FlattenPref = k_EditorIterationProfilerPrefix + "Flatten"; public const string k_EventHeaderWidthSizePref = k_EditorIterationProfilerPrefix + "EventHeaderWidthSizePref"; public const string k_DetailsHeaderWidthSizePref = k_EditorIterationProfilerPrefix + "DetailsWidthSizePref"; public const string k_DurationHeaderWidthSizePref = k_EditorIterationProfilerPrefix + "HeaderWidthSizePref"; public const float k_SingleLineHeight = #if UNITY_2019_3_OR_NEWER 18; #else 16; #endif } [NonSerialized] bool m_Initialized; [SerializeField] MultiColumnHeaderState m_MultiColumnHeaderState; [SerializeField] TreeViewState m_TreeViewState; EditorIterationProfilerTreeView m_TreeView; SearchField m_SearchField; bool isWindowReady = false; [MenuItem("Window/Analysis/Editor Iteration Profiler/Show Window", priority = 8)] static void ShowProfilerWindow() { var window = GetWindow("Editor Iteration Profiler"); window.minSize = new Vector2(500, 300); } void OnEnable() { isWindowReady = false; EditorApplication.delayCall += OnEnableInitialize; } void OnEnableInitialize() { if (!m_Initialized) { InitializeButtonStateFromPrefs(); if (m_TreeViewState == null) { m_TreeViewState = new TreeViewState(); } bool firstInit = m_MultiColumnHeaderState == null; var headerState = EditorIterationProfilerTreeView.CreateDefaultMultiColumnHeaderState(); if (MultiColumnHeaderState.CanOverwriteSerializedFields(m_MultiColumnHeaderState, headerState)) { MultiColumnHeaderState.OverwriteSerializedFields(m_MultiColumnHeaderState, headerState); } m_MultiColumnHeaderState = headerState; InitializeHeaderStateFromPrefs(headerState); var multiColumnHeader = new MultiColumnHeader(headerState); if (firstInit) { multiColumnHeader.ResizeToFit(); } m_TreeView = new EditorIterationProfilerTreeView(m_TreeViewState, multiColumnHeader); m_SearchField = new SearchField(); m_SearchField.downOrUpArrowKeyPressed += m_TreeView.SetFocusAndEnsureSelectedItem; m_Initialized = true; isWindowReady = true; } } void DrawToolbar() { GUILayout.BeginHorizontal(EditorStyles.toolbar); UnityProfiling.EditorProfilingEnabled = GUILayout.Toggle(UnityProfiling.EditorProfilingEnabled, Styles.k_EnableProfiler, EditorStyles.toolbarButton); UnityProfiling.SetProfileDeepScripts(GUILayout.Toggle(ProfilerDriver.deepProfiling, Styles.k_DeepProfile, EditorStyles.toolbarButton)); var flatten = GUILayout.Toggle(m_TreeView.Flatten, Styles.k_Flatten, EditorStyles.toolbarButton); var userCodeOnly = GUILayout.Toggle(m_TreeView.UserCodeOnly, Styles.k_UserCode, EditorStyles.toolbarButton); GUILayout.Space(5); if (GUILayout.Button(Styles.k_ClearProfilerData, EditorStyles.toolbarButton)) { EditorIterationProfilerIntegration.Clear(); } if (GUILayout.Button(Styles.k_CollapseAll, EditorStyles.toolbarButton)) { m_TreeView.CollapseAll(); } if (GUILayout.Button(Styles.k_LogConsole, EditorStyles.toolbarButton)) { var reporter = EditorIterationProfilerIntegration.Instance.DataReporterProvider.TryGetDataReporter("EditorLogReporter"); reporter.Report(EditorIterationProfilerIntegration.Instance.IterationList); } if (EditorGUILayout.DropdownButton(Styles.k_Export, FocusType.Passive, EditorStyles.toolbarPopup)) { var menu = new GenericMenu(); var exporters = EditorIterationProfilerIntegration.Instance.DataReporterProvider.GetAllReporters(); foreach (var exporter in exporters) { menu.AddItem(new GUIContent(exporter.Name), false, () => ReportExtension(exporter, EditorIterationProfilerIntegration.Instance.IterationList, ExportType.Captured)); } menu.AddSeparator(""); menu.AddItem(new GUIContent("Export all available formats"), false, () => ReportAllExtensions(EditorIterationProfilerIntegration.Instance.IterationList)); var dropDownRect = EditorGUILayout.GetControlRect(); dropDownRect.x += 500; dropDownRect.y += 18; menu.DropDown(dropDownRect); } if (EditorGUILayout.DropdownButton(Styles.k_ExportProfiler, FocusType.Passive, EditorStyles.toolbarPopup)) { var menu = new GenericMenu(); var exporters = EditorIterationProfilerIntegration.Instance.DataReporterProvider.GetAllReporters(); var assembly = typeof(Editor).Assembly; var windowType = assembly.GetType("UnityEditor.ProfilerWindow"); var profilerWindow = GetWindow(windowType); var currentFrameInfo = windowType.GetField("m_CurrentFrame", BindingFlags.NonPublic | BindingFlags.Instance); Assert.IsNotNull(currentFrameInfo); int currentFrameIndex = (int)currentFrameInfo.GetValue(profilerWindow); foreach (var exporter in exporters) { menu.AddItem(new GUIContent("Selected Frame/" + exporter.Name), false, () => ReportSelectedFrame(exporter, currentFrameIndex)); } menu.AddSeparator(""); foreach (var exporter in exporters) { menu.AddItem(new GUIContent("Multiple Frames/" + exporter.Name), false, () => { var window = GetWindow(); window.Initialize(exporter); }); } var dropDownRect = EditorGUILayout.GetControlRect(); dropDownRect.x += 600; dropDownRect.y += 18; menu.DropDown(dropDownRect); } GUILayout.Space(5); GUILayout.FlexibleSpace(); if (m_TreeView.UserCodeOnly != userCodeOnly) { EditorIterationProfilerAnalytics.SendInteractionEvent(UnityProfiling.EditorProfilingEnabled, EditorApplication.isPlaying, ProfilerDriver.deepProfiling, EditorIterationProfilerIntegration.Instance.Settings.Flatten, EditorIterationProfilerIntegration.Instance.Settings.UserCode); m_TreeView.UserCodeOnly = userCodeOnly; EditorPrefs.SetBool(Styles.k_UserCodePref, m_TreeView.UserCodeOnly); m_TreeView.Reload(); } if (m_TreeView.Flatten != flatten) { EditorIterationProfilerAnalytics.SendInteractionEvent(UnityProfiling.EditorProfilingEnabled, EditorApplication.isPlaying, ProfilerDriver.deepProfiling, EditorIterationProfilerIntegration.Instance.Settings.Flatten, EditorIterationProfilerIntegration.Instance.Settings.UserCode); m_TreeView.Flatten = flatten; EditorPrefs.SetBool(Styles.k_FlattenPref, m_TreeView.Flatten); m_TreeView.Reload(); } DrawSearchBar(); GUILayout.EndHorizontal(); } public static void ReportMultipleFrames(IFileDataReporter reporter, int beginRange, int endRange, string guid) { var exportType = ExportType.Multi; if (beginRange == -1) { ReportSelectedFrame(reporter, beginRange, exportType, guid); return; } EditorIterationProfilerAnalytics.SendExportEvent(reporter.Name, exportType.ToString(), ExportStatus.Started.ToString(), guid); var path = EditorUtility.SaveFolderPanel($"Export frames to folder", "", reporter.Extension); for (int i = beginRange - 1; i < endRange; ++i) { var list = new IterationList(); var hfdv = UnityProfiling.GetFrame(i, 0); if (!hfdv.valid) { hfdv = UnityProfiling.GetFrame(-1, 0); if (!hfdv.valid) { EditorIterationProfilerAnalytics.SendExportEvent(reporter.Name, exportType.ToString(), ExportStatus.Error.ToString(), guid); Debug.LogError($"There was an issue getting the frame {i}"); return; } } list.NewIteration(IterationEventKind.None); Assert.IsNotNull(list.LastIterationEventRoot); list.LastIterationEventRoot.StartEvent(IterationEventKind.None); ProfilerDataCollector.ReadProfilingData(list.LastIterationEventRoot, list.LastIterationEventRoot.FindLastEvent(IterationEventKind.None), hfdv, hfdv.GetRootItemID(), EventDataFlags.None, false); var lastEvent = list.LastIterationEventRoot.FindLastEvent(IterationEventKind.None); Assert.IsNotNull(lastEvent); lastEvent.SetStartFinishTimeFromChildren(); lastEvent.Identifier = $"Data (Frame {i + 1})"; var filename = "EditorIterationData_" + $"Frame{i + 1}_" + $"{DateTime.Now.ToString("MMddyyyy_HHmmss")}" + $".{reporter.Extension}"; if (path.Length != 0) { reporter.Report(list, Path.Combine(path, filename)); } } if (path.Length != 0) { EditorIterationProfilerAnalytics.SendExportEvent(reporter.Name, exportType.ToString(), ExportStatus.Finished.ToString(), guid); } } static void ReportSelectedFrame(IFileDataReporter reporter, int currentFrameIndex, ExportType exportType = ExportType.Selected, string guid = "") { if (string.IsNullOrEmpty(guid)) { guid = Guid.NewGuid().ToString(); EditorIterationProfilerAnalytics.SendExportEvent(reporter.Name, exportType.ToString(), ExportStatus.Started.ToString(), guid); } var list = new IterationList(); var hfdv = UnityProfiling.GetFrame(currentFrameIndex, 0); if (!hfdv.valid) { hfdv = UnityProfiling.GetFrame(-1, 0); if (!hfdv.valid) { EditorIterationProfilerAnalytics.SendExportEvent(reporter.Name, exportType.ToString(), ExportStatus.Error.ToString(), guid); Debug.LogError($"There was an issue getting the frame {currentFrameIndex}"); return; } } list.NewIteration(IterationEventKind.None); Assert.IsNotNull(list.LastIterationEventRoot); list.LastIterationEventRoot.StartEvent(IterationEventKind.None); ProfilerDataCollector.ReadProfilingData(list.LastIterationEventRoot, list.LastIterationEventRoot.FindLastEvent(IterationEventKind.None), hfdv, hfdv.GetRootItemID(), EventDataFlags.None, false); var lastEvent = list.LastIterationEventRoot.FindLastEvent(IterationEventKind.None); Assert.IsNotNull(lastEvent); lastEvent.SetStartFinishTimeFromChildren(); lastEvent.Identifier = $"Data (Frame {currentFrameIndex + 1})"; ReportExtension(reporter, list, exportType, guid); } static void ReportAllExtensions(IIterationList iterationList) { var guid = Guid.NewGuid().ToString(); var path = EditorUtility.SaveFolderPanel($"Export frames to folder", "", ""); var extensions = EditorIterationProfilerIntegration.Instance.DataReporterProvider.GetAllReporters(); foreach (var extension in extensions) { ReportExtension(extension, iterationList, ExportType.CapturedAll, guid, path); } } static void ReportExtension(IFileDataReporter fileReporterType, IIterationList iterationList, ExportType exportType, string guid = "", string folderPath = "") { if (string.IsNullOrEmpty(guid)) { guid = Guid.NewGuid().ToString(); } if(ExportType.Selected != exportType) { EditorIterationProfilerAnalytics.SendExportEvent(fileReporterType.Name, exportType.ToString(), ExportStatus.Started.ToString(), guid); } var reporter = EditorIterationProfilerIntegration.Instance.DataReporterProvider.TryGetReporter(fileReporterType.GetType()); if (reporter == null) { return; } var filename = "EditorIterationData_" + $"{reporter.Name.Replace(" ", "")}_" + $"{DateTime.Now.ToString("MMddyyyy_HHmmss")}" + $".{reporter.Extension}"; string path; if (string.IsNullOrEmpty(folderPath)) { path = EditorUtility.SaveFilePanel($"Export {reporter.Extension}", "", filename, reporter.Extension); } else { path = Path.Combine(folderPath, filename); } if (path.Length != 0) { reporter.Report(iterationList, path); EditorIterationProfilerAnalytics.SendExportEvent(reporter.Name, exportType.ToString(), ExportStatus.Finished.ToString(), guid); } } void DrawSearchBar() { var rect = GUILayoutUtility.GetRect(50f, 300f, Styles.k_SingleLineHeight, Styles.k_SingleLineHeight, EditorStyles.toolbarSearchField); var searchLength = m_TreeView.searchString?.Length ?? 0; m_TreeView.searchString = m_SearchField.OnToolbarGUI(rect, m_TreeView.searchString); if (searchLength > 0 && !m_TreeView.hasSearch) { m_TreeView.CollapseAll(); try { m_TreeView.FrameItem(m_TreeView.state.lastClickedID); } catch (ArgumentException) { m_TreeView.state.lastClickedID = -1; } } } void DrawTreeView() { var rect = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true)); m_TreeView.OnGUI(rect); } void OnLostFocus() { EditorIterationProfilerAnalytics.SendInteractionEvent(UnityProfiling.EditorProfilingEnabled, EditorApplication.isPlaying, ProfilerDriver.deepProfiling, EditorIterationProfilerIntegration.Instance.Settings.Flatten, EditorIterationProfilerIntegration.Instance.Settings.UserCode); } void OnGUI() { if(!isWindowReady) { return; } DrawToolbar(); DrawTreeView(); UpdatePrefs(); } static void InitializeButtonStateFromPrefs() { if (!EditorPrefs.HasKey(Styles.k_EnableProfilerPref)) { EditorPrefs.SetBool(Styles.k_EnableProfilerPref, false); } if (!EditorPrefs.HasKey(Styles.k_UserCodePref)) { EditorPrefs.SetBool(Styles.k_UserCodePref, false); } if (!EditorPrefs.HasKey(Styles.k_FlattenPref)) { EditorPrefs.SetBool(Styles.k_FlattenPref, false); } if (!EditorPrefs.HasKey(Styles.k_EnableDeepProfile)) { EditorPrefs.SetBool(Styles.k_EnableDeepProfile, false); } UnityProfiling.EditorProfilingEnabled = EditorPrefs.GetBool(Styles.k_EnableProfilerPref); UnityProfiling.SetProfileDeepScripts(EditorPrefs.GetBool(Styles.k_EnableDeepProfile)); } static void InitializeHeaderStateFromPrefs(MultiColumnHeaderState state) { int columnIndex = 0; if (!EditorPrefs.HasKey(Styles.k_EventHeaderWidthSizePref)) { EditorPrefs.SetFloat(Styles.k_EventHeaderWidthSizePref, state.columns[columnIndex++].minWidth); } if (!EditorPrefs.HasKey(Styles.k_DetailsHeaderWidthSizePref)) { EditorPrefs.SetFloat(Styles.k_DetailsHeaderWidthSizePref, state.columns[columnIndex++].minWidth); } if (!EditorPrefs.HasKey(Styles.k_DurationHeaderWidthSizePref)) { EditorPrefs.SetFloat(Styles.k_DurationHeaderWidthSizePref, state.columns[columnIndex].minWidth); } columnIndex = 0; state.columns[columnIndex++].width = EditorPrefs.GetFloat(Styles.k_EventHeaderWidthSizePref); state.columns[columnIndex++].width = EditorPrefs.GetFloat(Styles.k_DetailsHeaderWidthSizePref); state.columns[columnIndex].width = EditorPrefs.GetFloat(Styles.k_DurationHeaderWidthSizePref); } void UpdatePrefs() { EditorPrefs.SetFloat(Styles.k_EventHeaderWidthSizePref, m_MultiColumnHeaderState.columns[0].width); EditorPrefs.SetFloat(Styles.k_DetailsHeaderWidthSizePref, m_MultiColumnHeaderState.columns[1].width); EditorPrefs.SetFloat(Styles.k_DurationHeaderWidthSizePref, m_MultiColumnHeaderState.columns[2].width); EditorPrefs.SetBool(Styles.k_EnableProfilerPref, UnityProfiling.EditorProfilingEnabled); } [MenuItem("Window/Analysis/Editor Iteration Profiler/Clear Editor Preferences", priority = 20)] static void DeleteAllEIPPrefKeys() { if (EditorUtility.DisplayDialog("Delete EIP Editor Preferences", "Delete EIP Editor Preferences?", "Yes", "No")) { EditorPrefs.DeleteKey(Styles.k_DurationHeaderWidthSizePref); EditorPrefs.DeleteKey(Styles.k_DetailsHeaderWidthSizePref); EditorPrefs.DeleteKey(Styles.k_EventHeaderWidthSizePref); EditorPrefs.DeleteKey(Styles.k_UserCodePref); EditorPrefs.DeleteKey(Styles.k_EnableDeepProfile); EditorPrefs.DeleteKey(Styles.k_EnableProfilerPref); Debug.Log("EIP Editor Preferences deleted"); } } } class ProfilerMultiFrameSelector : EditorWindow { public static readonly GUIContent k_RangeBeginning = EditorGUIUtility.TrTextContent("Range Beginning", "Range beginning, value is inclusive."); public static readonly GUIContent k_RangeEnd = EditorGUIUtility.TrTextContent("Range End", "Range end, value is inclusive."); public static readonly GUIContent k_WindowTitle = EditorGUIUtility.TrTextContent("Multi-Frame Exporter"); IFileDataReporter m_Exporter; int m_FirstRange = -1; int m_SecondRange = -1; string m_EventGuid; static void OpenWindow() { GetWindow(); } public void Initialize(IFileDataReporter exporter) { m_Exporter = exporter; m_EventGuid = Guid.NewGuid().ToString(); EditorIterationProfilerAnalytics.SendExportEvent(exporter.Name, ExportType.MultiWindow.ToString(), ExportStatus.Started.ToString(), m_EventGuid); } public void OnDestroy() { EditorIterationProfilerAnalytics.SendExportEvent(m_Exporter.Name, ExportType.MultiWindow.ToString(), ExportStatus.Finished.ToString(), m_EventGuid); } void OnEnable() { maxSize = new Vector2(300, 125); minSize = new Vector2(300, 125); titleContent = k_WindowTitle; ShowUtility(); } void OnGUI() { m_FirstRange = EditorGUILayout.IntField(k_RangeBeginning, m_FirstRange); m_SecondRange = EditorGUILayout.IntField(k_RangeEnd, m_SecondRange); EditorGUILayout.HelpBox("Integers only. The limits are INCLUSIVE. If frame does not exist it won't be exported. -1 is the newest frame, however you can't do -1 and 15, for example; only -1 and -1.", MessageType.Info); if (m_FirstRange > m_SecondRange) { int x = m_FirstRange; m_FirstRange = m_SecondRange; m_SecondRange = x; } bool areValid = (m_FirstRange == m_SecondRange && m_FirstRange == -1 || m_FirstRange >= 0 && m_SecondRange >= 0); EditorGUI.BeginDisabledGroup(!areValid); if (GUILayout.Button("Export")) { Debug.Log($"Exporting Frames [{m_FirstRange},{m_SecondRange}] ({Mathf.Abs(m_SecondRange) - Mathf.Abs(m_FirstRange) + 1} frames)"); EditorIterationProfilerWindow.ReportMultipleFrames(m_Exporter, m_FirstRange, m_SecondRange, m_EventGuid); Close(); } EditorGUI.EndDisabledGroup(); } } } ================================================ FILE: Editor/EditorIterationProfilerWindow.cs.meta ================================================ fileFormatVersion: 2 guid: 906f7aecdbdac3e4ba9518ba48388df9 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/EventData.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using UnityEngine; namespace UnityEditor.EditorIterationProfiler { [Flags] public enum EventDataFlags { None = 0, UserCode = 1 << 0, Flatten = 1 << 1, } [Serializable] public class EventData { [field: NonSerialized] public List Children { get; set; } = new List(); public int Index { get; } public int ParentIndex { get; set; } public string Identifier { get; set; } public double StartTime { get; set; } public double FinishTime { get; set; } public EventDataFlags Flags { get; set; } public IterationEventKind Kind { get; } [SerializeField] string m_Metadata; public string Details => m_Metadata; public string DisplayName { get { if (Kind == IterationEventKind.None) { return $"{Identifier}"; } string kindString = Kind.ToString(); if (kindString == Identifier) { return kindString; } return $"{kindString}: {Identifier}"; } } public double Duration => FinishTime - StartTime; static double CurrentTime { get { long timeStamp = Stopwatch.GetTimestamp() / TimeSpan.TicksPerMillisecond; return timeStamp; } } public EventData(IterationEventKind kind, string identifier, string metadata, int index) { Kind = kind; Flags = EventDataFlags.None; Identifier = identifier; m_Metadata = metadata; Index = index; ParentIndex = -1; StartTime = -1.0f; FinishTime = -1.0f; } public EventData(string identifier, string metadata, int index, double startTime, double finishTime, EventDataFlags flags) { Kind = IterationEventKind.None; Identifier = identifier; m_Metadata = metadata; Index = index; ParentIndex = -1; StartTime = startTime; FinishTime = finishTime; Flags = flags; } public void SetStartTime() { StartTime = CurrentTime; FinishTime = StartTime; } public void Finish() { if (StartTime > 0.0f) { FinishTime = CurrentTime; } } public void SetStartFinishTimeFromChildren() { if (Children.Count == 0) { return; } StartTime = Children.First().StartTime; FinishTime = Children.First().FinishTime; foreach (var child in Children) { StartTime = Math.Min(child.StartTime, StartTime); FinishTime = Math.Max(child.FinishTime, FinishTime); } } public void PostProcess(bool flatten = false) { if (Children.Count == 0) { return; } if (flatten && Children.Count == 1) { Flags |= EventDataFlags.Flatten; } foreach (var child in Children) { child.PostProcess(Children.Count == 1); } } } } ================================================ FILE: Editor/EventData.cs.meta ================================================ fileFormatVersion: 2 guid: 7ffe19a10dc4360459985d005f84ceab MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/Aggregator.cs ================================================ using System; using System.Text; using System.Text.RegularExpressions; using static System.Double; namespace UnityEditor.EditorIterationProfiler.Formatting { public class Aggregator { public string Name { get; private set; } public double Time { get; private set; } public double TotalTime { get; private set; } public Regex Pattern { get; private set; } public int Depth { get; private set; } public int Calls { get; private set; } public bool IsRoot => !string.IsNullOrEmpty(Name) && m_Sb.Length != 0; public double Percentage => (Math.Abs(TotalTime) < Epsilon || Math.Abs(Time) < Epsilon) ? 0 : Time / TotalTime * 100; StringBuilder m_Sb; public Aggregator(int depth, string name, Regex pattern) { m_Sb = new StringBuilder(); Time = 0; TotalTime = 0; Calls = 0; Name = name; Depth = depth; Pattern = pattern; } public void Aggregate(string s, bool isCall = false) { m_Sb.Append(s); if (isCall) { ++Calls; } } public void Aggregate(double time) { ++Calls; Time += time; } public void Aggregate(string s, double time) { Aggregate(s); Aggregate(time); } public void Reset() { Calls = 0; m_Sb.Clear(); Time = 0; TotalTime = 0; } public void Reset(double totalTime) { Calls = 0; m_Sb.Clear(); Time = 0; TotalTime = totalTime; } public override string ToString() { return m_Sb.ToString(); } } public class HTMLAggregator : Aggregator { public string Style { get; private set;} public HTMLAggregator(int depth, string name, Regex pattern, string style) : base(depth, name, pattern) { Style = style; } } } ================================================ FILE: Editor/Formatters/Aggregator.cs.meta ================================================ fileFormatVersion: 2 guid: 25551dbb66e6c084ab84c7a8e6cfc2e2 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/DataReporterProvider.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using UnityEditor.EditorIterationProfiler.Formatting; using UnityEngine.Assertions; namespace UnityEditor.EditorIterationProfiler { public interface IDataReporterProvider { IList GetAllReporters() where T : IDataReporter; T TryGetReporter(Type type) where T : IDataReporter; bool IsFileExtensionSupported(string extension); IDataReporter TryGetDataReporter(string typeName); IFileDataReporter TryGetDataFileReporter(string typeName); } public class DataReporterProvider : IDataReporterProvider { HashSet m_DataReporters; HashSet m_DataFileReporters; public DataReporterProvider() { InitializeDataFileReporters(); InitializeDataReporters(); } void InitializeDataFileReporters() { m_DataFileReporters = new HashSet(); foreach (var rep in TypeCache.GetTypesDerivedFrom(typeof(IFileDataReporter))) { if (rep.GetConstructors().Length > 0) { var instance = (IFileDataReporter)Activator.CreateInstance(rep); var processedExtension = SanitizeExtension(instance.Extension); if (processedExtension != instance.Extension) { throw new ArgumentException($"Extension must be lowercase characters only ({rep})"); } else { m_DataFileReporters.Add(instance); } } } } void InitializeDataReporters() { m_DataReporters = new HashSet(); foreach (var rep in TypeCache.GetTypesDerivedFrom(typeof(IDataReporter))) { if (rep.GetConstructors().Length > 0) { var instance = (IDataReporter)Activator.CreateInstance(rep); if (!(instance is FileReporter)) { m_DataReporters.Add(instance); } } } } public IList GetAllReporters() where T : IDataReporter { if (typeof(T) == typeof(IDataReporter)) { return (IList)m_DataReporters.ToList(); } if (typeof(T) == typeof(IFileDataReporter)) { return (IList)m_DataFileReporters.ToList(); } return default; } public T TryGetReporter(Type type) where T : IDataReporter { if (typeof(T) == typeof(IDataReporter)) { return (T)m_DataReporters.FirstOrDefault(x => x.GetType() == type); } if (typeof(T) == typeof(IFileDataReporter)) { return (T)m_DataFileReporters.FirstOrDefault(x => x.GetType() == type); } return default; } public IDataReporter TryGetDataReporter(string typeName) { return m_DataReporters.FirstOrDefault(reporter => reporter.GetType().Name == typeName); } public IFileDataReporter TryGetDataFileReporter(string typeName) { return m_DataFileReporters.FirstOrDefault(reporter => reporter.GetType().Name == typeName); } public bool IsFileExtensionSupported(string extension) { return m_DataFileReporters.Any(reporter => reporter.Extension == SanitizeExtension(extension)); } static string SanitizeExtension(string extension) { Assert.IsNotNull(extension, "Extension cannot be null"); if (extension.StartsWith(".")) { return extension.Substring(1); } extension = extension.ToLower(); return extension; } } } ================================================ FILE: Editor/Formatters/DataReporterProvider.cs.meta ================================================ fileFormatVersion: 2 guid: ff2c9a40c83aa784bb9b3ff6047ee281 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/DataReporters.cs ================================================ namespace UnityEditor.EditorIterationProfiler.Formatting { public interface IDataReporter { string GetFormatString(in IIterationList iterationList); void Report(in IIterationList iterationList, string path = null); } public interface IFileDataReporter : IDataReporter { string Extension { get; } string Name { get; } new void Report(in IIterationList iterationList, string path); } } ================================================ FILE: Editor/Formatters/DataReporters.cs.meta ================================================ fileFormatVersion: 2 guid: 81edb0b5aa41ccd48ad73a8a21e67758 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/FileReporter.cs ================================================ using System; using System.IO; using UnityEngine; namespace UnityEditor.EditorIterationProfiler.Formatting { public abstract class FileReporter : Formatter, IFileDataReporter { string m_Extension = null; public virtual string Extension => m_Extension; string m_Name = null; public virtual string Name => m_Name; public virtual void Report(in IIterationList iterationList, string path) { ReportToFile(GetFormatString(iterationList), path); } protected virtual void ReportToFile(string message, string path) { if (path == null) { throw new ArgumentNullException(nameof(path), "Path cannot be null"); } File.WriteAllText(path, message); Debug.Log($"\"{path}\" exported!"); } public abstract override string ToString(); } } ================================================ FILE: Editor/Formatters/FileReporter.cs.meta ================================================ fileFormatVersion: 2 guid: 9d728ac882039e643be468b5db8f65b9 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/Formatter.cs ================================================ using System; using System.Text; using static System.FormattableString; namespace UnityEditor.EditorIterationProfiler.Formatting { public abstract class Formatter { public virtual string GetFormatString(in IIterationList iterationList) { var finalStringBuilder = GetPrefixStringBuilder(iterationList).Append(GetMainStringBuilder(iterationList).Append(GetPostfixStringBuilder(iterationList))); return finalStringBuilder.ToString(); } protected virtual StringBuilder GetPrefixStringBuilder(in IIterationList iterationList = null) { return new StringBuilder(); } protected virtual StringBuilder GetMainStringBuilder(in IIterationList iterationList) { var sb = new StringBuilder(); if (iterationList.IterationEventRoots.Count > 0) { RecursiveIterationList(iterationList, ref sb); } return sb; } protected virtual StringBuilder GetPostfixStringBuilder(in IIterationList iterationList = null) { return new StringBuilder(); } protected virtual void RecursiveIterationList(in IIterationList iterationList, ref StringBuilder sb, params object[] parameters) { if (iterationList.IterationEventRoots.Count == 0) { return; } for (int i = 0; i < iterationList.IterationEventRoots.Count; ++i) { RecursiveIterationEventRoot(iterationList.IterationEventRoots[i], ref sb, 1, parameters); } } protected virtual void RecursiveIterationEventRoot(in IterationEventRoot iterationEventRoot, ref StringBuilder sb, params object[] parameters) { foreach (var ed in iterationEventRoot.Events) { if (ed.ParentIndex < 0) { RecursiveEventData(ed, iterationEventRoot, ref sb, parameters); } } } protected virtual void RecursiveEventData(in EventData ed, in IterationEventRoot iterationEventRoot, ref StringBuilder sb, params object[] parameters) { RecursiveEventDataWalker(in ed, in iterationEventRoot, ref sb, parameters); } protected virtual void RecursiveEventDataWalker(in EventData ed, in IterationEventRoot iterationEventRoot, ref StringBuilder sb, params object[] parameters) { if (ed.Children == null || ed.Children.Count == 0) { return; } foreach (var child in ed.Children) { var newParameters = new object[parameters.Length]; Array.Copy(parameters, newParameters, parameters.Length); newParameters[0] = (int)newParameters[0] + 1; RecursiveEventData(child, iterationEventRoot, ref sb, newParameters); } } public static string ToTimeString(double time) { return Invariant($"{time:0.000} ms"); } public static string ToPercentageString(double percentage) { return Invariant($"{percentage:0.00}%"); } } } ================================================ FILE: Editor/Formatters/Formatter.cs.meta ================================================ fileFormatVersion: 2 guid: ac320eeadb74a314986b94329a9f324b MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/IndentationProvider.cs ================================================ using System.Collections; using System.Collections.Generic; using UnityEngine; namespace UnityEditor.EditorIterationProfiler.Formatting { public static class IndentationProvider { static Dictionary m_Cache = new Dictionary(32); const char k_IndentationChar = '\t'; public static string Get(int count) { if (count < 1) { return string.Empty; } if (!m_Cache.ContainsKey(count)) { m_Cache.Add(count, new string(k_IndentationChar, count)); } return m_Cache[count]; } } } ================================================ FILE: Editor/Formatters/IndentationProvider.cs.meta ================================================ fileFormatVersion: 2 guid: 3b0423ed7f3314147a6125b4bca1b837 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/Reporters/CSVReporter.cs ================================================ using System.Text; namespace UnityEditor.EditorIterationProfiler.Formatting { sealed class CSVReporter : FileReporter { string m_Extension = "csv"; public override string Extension => m_Extension; string m_Name = "CSV"; public override string Name => m_Name; protected override StringBuilder GetPrefixStringBuilder(in IIterationList iterationList = null) { var sb = new StringBuilder(); sb.AppendLine($"{EditorIterationProfilerIntegration.Instance.Settings}"); sb.AppendLine(); return sb; } protected override void RecursiveIterationList(in IIterationList iterationList, ref StringBuilder sb, params object[] parameters) { sb.AppendLine($"Iteration ID,NodeType,Name,Details,Duration (ms)"); for (int i = 0; i < iterationList.IterationEventRoots.Count; ++i) { RecursiveIterationEventRoot(iterationList.IterationEventRoots[i], ref sb, 1); } } protected override void RecursiveEventData(in EventData ed, in IterationEventRoot parent, ref StringBuilder sb, params object[] parameters) { if (ed.Children.Count == 0) { sb.AppendLine($"{parent.IterationIndex + 1} ({parent.IterationEventKind}), Leaf, {ed.Identifier}, {ed.Details}, {ed.Duration:0.000}"); } else { sb.AppendLine($"{parent.IterationIndex + 1} ({parent.IterationEventKind}), Parent, {ed.Identifier}, {ed.Details}, {ed.Duration:0.000}"); } RecursiveEventDataWalker(in ed, in parent, ref sb, parameters); } public override string ToString() { return GetType().Name; } } } ================================================ FILE: Editor/Formatters/Reporters/CSVReporter.cs.meta ================================================ fileFormatVersion: 2 guid: 6b8bb4034b7400549b745b3ae7c02f4c MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/Reporters/ChromeTracingReporter.cs ================================================ using System; using System.Text; using static System.FormattableString; namespace UnityEditor.EditorIterationProfiler.Formatting { sealed class ChromeTracingReporter : FileReporter { string m_Extension = "json"; public override string Extension => m_Extension; string m_Name = "JSON for Chrometrace"; public override string Name => m_Name; double m_ParentTotalDuration; protected override StringBuilder GetPrefixStringBuilder(in IIterationList iterationList = null) { var sb = new StringBuilder(32); sb.AppendLine("{"); sb.AppendLine("\"otherData\": {"); sb.AppendLine($"\"Readme\": \"Data serialized for use with Chrome Tracing. Load this file into chrome://tracing/ \","); sb.AppendLine($"\"Data\": \"{EditorIterationProfilerIntegration.Instance.Settings.ToString().Replace(Environment.NewLine, "; ")}\""); sb.AppendLine("},"); sb.AppendLine("\"traceEvents\": ["); return sb; } protected override StringBuilder GetMainStringBuilder(in IIterationList iterationList) { var sb = new StringBuilder(); if (iterationList.IterationEventRoots.Count > 0) { RecursiveIterationList(iterationList, ref sb); // Remove the last comma for (int i = sb.Length - 1; i > 0; --i) { if (sb[i] == ',') { sb.Remove(i, 1); break; } } } return sb; } protected override StringBuilder GetPostfixStringBuilder(in IIterationList iterationList = null) { var sb = new StringBuilder(4); sb.AppendLine("]"); sb.AppendLine("}"); return sb; } protected override void RecursiveIterationEventRoot(in IterationEventRoot iterationEventRoot, ref StringBuilder sb, params object[] parameters) { double totalDuration = 0; foreach (var ed in iterationEventRoot.Events) { if (ed.ParentIndex < 0 && ed.Duration > 0) { totalDuration += ed.Duration; } } m_ParentTotalDuration = totalDuration; sb.AppendLine(MetadataEvent("process_name", (iterationEventRoot.IterationIndex + 1).ToString(), "1", $"Iteration {iterationEventRoot.IterationIndex + 1} ({iterationEventRoot.IterationEventKind.ToString()}) ({totalDuration:0.000}ms) ")); foreach (var ed in iterationEventRoot.Events) { if (ed.ParentIndex < 0) { RecursiveEventData(ed, iterationEventRoot, ref sb, ed.StartTime, parameters); } } } void RecursiveEventData(in EventData ed, in IterationEventRoot iterationEventRoot, ref StringBuilder sb, double parentStartTime, params object[] parameters) { if (ed.Duration > float.Epsilon) { sb.AppendLine(DurationEvent(ed.Identifier, (iterationEventRoot.IterationIndex + 1).ToString(), "1", iterationEventRoot.IterationEventKind.ToString(), ed.StartTime - parentStartTime, ed.Duration)); } RecursiveEventDataWalker(in ed, in iterationEventRoot, ref sb, parentStartTime, parameters); } void RecursiveEventDataWalker(in EventData ed, in IterationEventRoot iterationEventRoot, ref StringBuilder sb, double parentStartTime, params object[] parameters) { if (ed.Children == null || ed.Children.Count == 0) { return; } foreach (var child in ed.Children) { var newParameters = new object[parameters.Length]; Array.Copy(parameters, newParameters, parameters.Length); newParameters[0] = (int)newParameters[0] + 1; RecursiveEventData(child, iterationEventRoot, ref sb, parentStartTime, newParameters); } } string DurationEvent(string identifier, string pid, string tid, string category, double startTime, double duration) { return Invariant($"{{ \"pid\": {pid}, \"tid\": {tid}, \"ph\": \"X\", \"name\": \"{identifier}\", \"cat\": \"{category}\", \"ts\": {startTime * 1000.0:0.000}, \"dur\": {duration * 1000}, \"args\": {{ \"Duration (ms)\": {duration:0.000}, \"Start Time (ms)\": {startTime:0.000}, \"Percentage of total\": {(duration / m_ParentTotalDuration * 100.0):0.000} }} }},"); } static string MetadataEvent(string identifier, string pid, string tid, string name) { return $"{{ \"pid\": {pid}, \"tid\": {tid}, \"ph\": \"M\", \"name\": \"{identifier}\", \"args\": {{ \"name\": \"{name}\" }} }},"; } public override string ToString() { return GetType().Name; } } } ================================================ FILE: Editor/Formatters/Reporters/ChromeTracingReporter.cs.meta ================================================ fileFormatVersion: 2 guid: 72b0e40bb4a14f3478fcab6254a33ddf MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/Reporters/EditorLogReporter.cs ================================================ using System.Text; using UnityEngine; namespace UnityEditor.EditorIterationProfiler.Formatting { public class EditorLogReporter : Formatter, IDataReporter { protected override StringBuilder GetPrefixStringBuilder(in IIterationList iterationList = null) { var sb = new StringBuilder(); sb.AppendLine($"{EditorIterationProfilerIntegration.Instance.Settings}"); sb.AppendLine(); return sb; } protected override void RecursiveIterationEventRoot(in IterationEventRoot iterationEventRoot, ref StringBuilder sb, params object[] parameters) { double totalDuration = 0; foreach (var ed in iterationEventRoot.Events) { if (ed.ParentIndex < 0 && ed.Duration > 0) { totalDuration += ed.Duration; } } sb.AppendLine($"Iteration {iterationEventRoot.IterationIndex + 1} ({iterationEventRoot.IterationEventKind}) [{totalDuration:0.000} ms]"); foreach (var ed in iterationEventRoot.Events) { if (ed.ParentIndex < 0) { RecursiveEventData(ed, iterationEventRoot, ref sb, parameters); } } } protected override void RecursiveEventData(in EventData ed, in IterationEventRoot parent, ref StringBuilder sb, params object[] parameters) { var indentation = new string('\t', (int)parameters[0]); sb.AppendLine($"{indentation}{ed.Identifier} ({ed.Duration:0.000} ms; {ed.Details})"); RecursiveEventDataWalker(in ed, in parent, ref sb, parameters); } public void Report(in IIterationList iterationList, string path = null) { var message = GetFormatString(iterationList); Debug.Log(message); } } } ================================================ FILE: Editor/Formatters/Reporters/EditorLogReporter.cs.meta ================================================ fileFormatVersion: 2 guid: 5699fdd33406e234598169fc247e67e6 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/Reporters/HTMLPerfReport.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using UnityEditor.EditorIterationProfiler.Formatting; using UnityEngine; namespace UnityEditor.EditorIterationProfiler { public class HTMLPerfReporter : FileReporter { string m_Extension = "html"; public override string Extension => m_Extension; string m_Name = "HTML Performance Report"; public override string Name => m_Name; // [0-100] percentage. Determines when the children of the current node will stop being logged. If the Parent is under this percentage, only the direct children of it will be printed. After that it stops. const double k_PrunePercentage = 1; // [0-100] percentage. Determines what is considered as a divergence in times. This is used to 'flatten' the structure. 1 will basically turn it 'off'. const double k_DivergenceFactor = 75; IList m_Aggregators = new List(); double m_ParentTotalDuration; public HTMLPerfReporter() { m_Aggregators.Add(new HTMLAggregator(1, "Garbage Collector", new Regex(@"GC\.C"), "color:#F00")); m_Aggregators.Add(new HTMLAggregator(1, "Mono.JIT", new Regex("Mono.JIT"), "color:#FA1")); m_Aggregators.Add(new HTMLAggregator(1, "[MISSING MARKER TIME]", new Regex("MISSING MARKER TIME"), "color:#B54")); m_Aggregators.Add(new HTMLAggregator(1, "EIP Time", new Regex(@"EditorIterationProfiler"), "color:#3A7")); m_Aggregators.Add(new HTMLAggregator(1, "Object Stuff", new Regex(@"Object\."), "color:#1C7")); m_Aggregators.Add(new HTMLAggregator(1, "OnEnable & Awake Stuff", new Regex(@"\.Awake|\.OnEnable"), "color:#17C")); m_Aggregators.Add(new HTMLAggregator(1, "UnityEditor Stuff", new Regex(@"UnityEditor\."), "color:#52F")); m_Aggregators.Add(new HTMLAggregator(1, "UnityEngine Stuff", new Regex(@"UnityEngine\."), "color:#81B")); m_Aggregators.Add(new HTMLAggregator(1, "GUI Stuff", new Regex(@"GUI"), "color:#C0F")); } public override string GetFormatString(in IIterationList iterationList) { var finalStringBuilder = GetPrefixStringBuilder(iterationList).Append(GetMainStringBuilder(iterationList).Append(GetPostfixStringBuilder(iterationList))); return finalStringBuilder.ToString(); } protected override StringBuilder GetPrefixStringBuilder(in IIterationList iterationList = null) { var sb = new StringBuilder(); var fileGUID = AssetDatabase.FindAssets("HTMLReporterPrefix").FirstOrDefault(); var filePath = AssetDatabase.GUIDToAssetPath(fileGUID); var file = AssetDatabase.LoadAssetAtPath(filePath); sb.Append(file.text); sb.AppendLine($"
{EditorIterationProfilerIntegration.Instance.Settings}
"); sb.AppendLine($"
"); sb.AppendLine(); sb.AppendLine($""); sb.AppendLine($"
Expand all
"); sb.AppendLine($"
Collapse all
"); return sb; } protected override StringBuilder GetMainStringBuilder(in IIterationList iterationList) { var sb = new StringBuilder(); if (iterationList.IterationEventRoots.Count > 0) { RecursiveIterationList(iterationList, ref sb); } else { sb.AppendLine($"
ERROR: No data to export!
"); } return sb; } protected void RecursiveIterationList(in IIterationList iterationList, ref StringBuilder sb) { if (iterationList.IterationEventRoots.Count == 0) { return; } for (int i = 0; i < iterationList.IterationEventRoots.Count; ++i) { RecursiveIterationEventRoot(iterationList.IterationEventRoots[i], ref sb); } } protected void RecursiveIterationEventRoot(in IterationEventRoot iterationEventRoot, ref StringBuilder sb) { double totalDuration = 0; foreach (var ed in iterationEventRoot.Events) { if (ed.ParentIndex < 0 && ed.Duration > 0) { totalDuration += ed.Duration; } } m_ParentTotalDuration = totalDuration; foreach (var agg in m_Aggregators) { agg.Reset(totalDuration); } var foundRoot = new List(m_Aggregators.Count); for (int i = 0; i < foundRoot.Capacity; ++i) { foundRoot.Add(false); } var indentation1 = IndentationProvider.Get(1); sb.AppendLine($"
"); sb.AppendLine(TimeDisplay(1, totalDuration)); sb.AppendLine(PercentageDisplay(1, 100)); sb.AppendLine($"{indentation1}
Iteration {iterationEventRoot.IterationIndex + 1} ({iterationEventRoot.IterationEventKind})
"); foreach (var ed in iterationEventRoot.Events) { if (ed.ParentIndex < 0) { RecursiveEventData(ed, iterationEventRoot, ref sb, 1, 100, foundRoot); sb.AppendLine($"{indentation1}
"); } } sb.AppendLine(AggregateDataToString()); sb.AppendLine($""); } protected void RecursiveEventData(in EventData ed, in IterationEventRoot parent, ref StringBuilder sb, int depth, double parentPercentage, List foundRoot) { var indentation1 = IndentationProvider.Get(depth); var indentation2 = IndentationProvider.Get(depth + 1); double percentage = ed.Duration / m_ParentTotalDuration * 100; var currentFoundRoot = new List(foundRoot); AggregateEventData(ed, percentage, ref currentFoundRoot); if (parentPercentage * k_DivergenceFactor / 100 < percentage && ed.Children.Count != 0 && depth > 1 && percentage > 1) { RecursiveEventDataWalker(in ed, in parent, ref sb, depth, percentage, currentFoundRoot); return; } bool nodeWasWritten = false; if (percentage >= k_PrunePercentage) { if (ed.Children.Count > 0) { sb.AppendLine($"{indentation1}
"); sb.AppendLine(TimeDisplay(depth + 1, ed.Duration)); sb.AppendLine(PercentageDisplay(depth + 1, percentage)); sb.AppendLine($"{indentation2}
{ed.Identifier}
"); double childrenTime = 0; foreach (var child in ed.Children) { childrenTime += child.Duration; } var ag = FindAggregator("MISSING MARKER TIME"); if (ag != null) { if(ed.Duration > childrenTime) { ag.Aggregate(ed.Duration - childrenTime); } } } else { sb.AppendLine($"{indentation1}
"); sb.AppendLine(TimeDisplay(depth + 1, ed.Duration)); sb.AppendLine(PercentageDisplay(depth + 1, percentage)); if (!string.IsNullOrEmpty(ed.Details)) { sb.AppendLine(SimpleLabel(depth + 1, $"[{ed.Identifier}] ({ed.Details})")); } else { sb.AppendLine(SimpleLabel(depth + 1, $"[{ed.Identifier}]")); } } nodeWasWritten = true; } else if (parentPercentage >= k_PrunePercentage && percentage < k_PrunePercentage) { sb.AppendLine($"{indentation1}
"); sb.AppendLine(TimeDisplay(depth + 1, ed.Duration)); sb.AppendLine(PercentageDisplay(depth + 1, percentage)); if (!string.IsNullOrEmpty(ed.Details)) { if (ed.Children.Count == 0) { sb.AppendLine(SimpleLabel(depth + 1, $"[{ed.Identifier}] ({ed.Details})")); } else { sb.AppendLine(SimpleLabel(depth + 1, $"{{{ed.Identifier}}} ({ed.Details})")); } } else { if (ed.Children.Count == 0) { sb.AppendLine(SimpleLabel(depth + 1, $"[{ed.Identifier}]")); } else { sb.AppendLine(SimpleLabel(depth + 1, $"{{{ed.Identifier}}}")); } } nodeWasWritten = true; } RecursiveEventDataWalker(in ed, in parent, ref sb, depth, percentage, currentFoundRoot); if (nodeWasWritten && depth != 1) { sb.AppendLine($"{indentation1}
"); } } protected void RecursiveEventDataWalker(in EventData ed, in IterationEventRoot iterationEventRoot, ref StringBuilder sb, int depth, double percentage, List foundRoot) { if (ed.Children == null || ed.Children.Count == 0) { return; } foreach (var child in ed.Children) { RecursiveEventData(child, iterationEventRoot, ref sb, depth + 1, percentage, foundRoot); } } protected static string TimeDisplay(int depth, double time) { return $"{IndentationProvider.Get(depth)}
{ToTimeString(time)}
"; } protected static string PercentageDisplay(int depth, double percentage) { return $"{IndentationProvider.Get(depth)}
{ToPercentageString(percentage)}
"; } protected static string SimpleLabel(int depth, string s, string style = "") { if (!string.IsNullOrEmpty(style)) { return $"{IndentationProvider.Get(depth)}
{s}
"; } return $"{IndentationProvider.Get(depth)}
{s}
"; } protected void AggregateEventData(EventData ed, double percentage, ref List foundRoot) { for (int i = 0; i < m_Aggregators.Count; ++i) { var agg = m_Aggregators[i]; if (agg.Pattern.IsMatch(ed.Identifier) && !foundRoot[i]) { agg.Aggregate(TimeDisplay(agg.Depth + 1, ed.Duration) + Environment.NewLine, ed.Duration); agg.Aggregate(PercentageDisplay(agg.Depth + 1, percentage) + Environment.NewLine); agg.Aggregate(SimpleLabel(agg.Depth + 1, ed.Identifier + (string.IsNullOrEmpty(ed.Details) ? "" : $" ({ed.Details})"), agg.Style) + Environment.NewLine); foundRoot[i] = true; } } } protected void AggregateData(string name, double time) { foreach (var agg in m_Aggregators) { if (agg.Pattern.IsMatch(name)) { agg.Aggregate(time); } } } protected string AggregateDataToString() { var sb = new StringBuilder(); foreach (var agg in m_Aggregators) { sb.Append(AggregatorLabel(agg)); } return sb.ToString(); } protected HTMLAggregator FindAggregator(string name) { return m_Aggregators.FirstOrDefault(x => x.Pattern.IsMatch(name)); } protected static StringBuilder AggregatorLabel(HTMLAggregator ag) { var sb = new StringBuilder(); string indentation = IndentationProvider.Get(ag.Depth); if (ag.IsRoot) { sb.AppendLine($"{indentation}
"); sb.AppendLine($"{indentation}
{ToTimeString(ag.Time)}
"); sb.AppendLine($"{indentation}
{ToPercentageString(ag.Percentage)}
"); sb.AppendLine($"{indentation}
{ag.Name} (Found Instances: {ag.Calls})
"); sb.AppendLine($"{indentation}
"); sb.AppendLine(ag.ToString()); sb.AppendLine($"{indentation}
"); } else { sb.AppendLine($"{indentation}
"); sb.AppendLine($"{indentation}
{ToTimeString(ag.Time)}
"); sb.AppendLine($"{indentation}
{ToPercentageString(ag.Percentage)}
"); sb.AppendLine($"{indentation}
{ag.Name} ({ag.Calls})
"); } sb.AppendLine($"{indentation}
"); return sb; } public override string ToString() { return GetType().Name; } } } ================================================ FILE: Editor/Formatters/Reporters/HTMLPerfReport.cs.meta ================================================ fileFormatVersion: 2 guid: 5982d768475803a4185fccf2a6ac8642 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/Reporters/HTMLReporter.cs ================================================ using System.Text; using UnityEditor.EditorIterationProfiler.Formatting; using System.IO; using System.Linq; using UnityEngine; namespace UnityEditor.EditorIterationProfiler { sealed class HTMLReporter : FileReporter { string m_Extension = "html"; public override string Extension => m_Extension; string m_Name = "HTML"; public override string Name => m_Name; double m_ParentTotalDuration; protected override StringBuilder GetPrefixStringBuilder(in IIterationList iterationList = null) { var sb = new StringBuilder(); var fileGUID = AssetDatabase.FindAssets("HTMLReporterPrefix").FirstOrDefault(); var filePath = AssetDatabase.GUIDToAssetPath(fileGUID); var file = AssetDatabase.LoadAssetAtPath(filePath); sb.Append(file.text); sb.AppendLine($"
{EditorIterationProfilerIntegration.Instance.Settings}
"); sb.AppendLine($"
"); sb.AppendLine(); sb.AppendLine($""); sb.AppendLine($"
Expand all
"); sb.AppendLine($"
Collapse all
"); return sb; } protected override StringBuilder GetMainStringBuilder(in IIterationList iterationList) { var sb = new StringBuilder(); if (iterationList.IterationEventRoots.Count > 0) { RecursiveIterationList(iterationList, ref sb); } else { sb.AppendLine($"
ERROR: No data to export!
"); } return sb; } void RecursiveIterationList(in IIterationList iterationList, ref StringBuilder sb) { if (iterationList.IterationEventRoots.Count == 0) { return; } for (int i = 0; i < iterationList.IterationEventRoots.Count; ++i) { RecursiveIterationEventRoot(iterationList.IterationEventRoots[i], ref sb); } } void RecursiveIterationEventRoot(in IterationEventRoot iterationEventRoot, ref StringBuilder sb) { double totalDuration = 0; foreach (var ed in iterationEventRoot.Events) { if (ed.ParentIndex < 0 && ed.Duration > 0) { totalDuration += ed.Duration; } } m_ParentTotalDuration = totalDuration; var indentation1 = IndentationProvider.Get(1); sb.AppendLine($"
"); sb.AppendLine(TimeDisplay(1, totalDuration)); sb.AppendLine(PercentageDisplay(1, 100)); sb.AppendLine($"{indentation1}
Iteration {iterationEventRoot.IterationIndex + 1} ({iterationEventRoot.IterationEventKind})
"); foreach (var ed in iterationEventRoot.Events) { if (ed.ParentIndex < 0) { RecursiveEventData(ed, iterationEventRoot, ref sb, 1, 100); sb.AppendLine($"{indentation1}
"); } } sb.AppendLine($"
"); } void RecursiveEventData(in EventData ed, in IterationEventRoot parent, ref StringBuilder sb, int depth, double parentPercentage) { var indentation1 = IndentationProvider.Get(depth); var indentation2 = IndentationProvider.Get(depth + 1); double percentage = ed.Duration / m_ParentTotalDuration * 100; if (ed.Children.Count > 0) { sb.AppendLine($"{indentation1}
"); sb.AppendLine(TimeDisplay(depth + 1, ed.Duration)); sb.AppendLine(PercentageDisplay(depth + 1, percentage)); if (!string.IsNullOrEmpty(ed.Details)) { sb.AppendLine($"{indentation2}
{ed.Identifier} ({ed.Details})
"); } else { sb.AppendLine($"{indentation2}
{ed.Identifier}
"); } } else { sb.AppendLine($"{indentation1}
"); sb.AppendLine(TimeDisplay(depth + 1, ed.Duration)); sb.AppendLine(PercentageDisplay(depth + 1, percentage)); if (!string.IsNullOrEmpty(ed.Details)) { sb.AppendLine(SimpleLabel(depth + 1, $"[{ed.Identifier}] ({ed.Details})")); } else { sb.AppendLine(SimpleLabel(depth + 1, $"[{ed.Identifier}]")); } } RecursiveEventDataWalker(in ed, in parent, ref sb, depth, percentage); if (depth != 1) { sb.AppendLine($"{indentation1}
"); } } void RecursiveEventDataWalker(in EventData ed, in IterationEventRoot iterationEventRoot, ref StringBuilder sb, int depth, double percentage) { if (ed.Children == null || ed.Children.Count == 0) { return; } foreach (var child in ed.Children) { RecursiveEventData(child, iterationEventRoot, ref sb, depth + 1, percentage); } } string TimeDisplay(int depth, double time) { return $"{IndentationProvider.Get(depth)}
{ToTimeString(time)}
"; } string PercentageDisplay(int depth, double percentage) { return $"{IndentationProvider.Get(depth)}
{ToPercentageString(percentage)}
"; } string SimpleLabel(int depth, string s, string style = "") { if (!string.IsNullOrEmpty(style)) { return $"{IndentationProvider.Get(depth)}
{s}
"; } return $"{IndentationProvider.Get(depth)}
{s}
"; } public override string ToString() { return GetType().Name; } } } ================================================ FILE: Editor/Formatters/Reporters/HTMLReporter.cs.meta ================================================ fileFormatVersion: 2 guid: e616717ebe9a5aa41a86a75e07d38c16 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/Reporters/HTMLReporterPrefix.txt ================================================  ================================================ FILE: Editor/Formatters/Reporters/HTMLReporterPrefix.txt.meta ================================================ fileFormatVersion: 2 guid: fa0a687d251c5484281dc568e5a0e2ad TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/Reporters/PlaintextReporter.cs ================================================ namespace UnityEditor.EditorIterationProfiler.Formatting { public sealed class PlaintextReporter : FileReporter { string m_Extension = "txt"; public override string Extension => m_Extension; string m_Name = "Plaintext"; public override string Name => m_Name; IDataReporter m_El = new EditorLogReporter(); public override void Report(in IIterationList iterationList, string path) { ReportToFile(m_El.GetFormatString(iterationList), path); } public override string ToString() { return GetType().Name; } } } ================================================ FILE: Editor/Formatters/Reporters/PlaintextReporter.cs.meta ================================================ fileFormatVersion: 2 guid: 843fc8e922ce90b4fb75e591fe507647 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters/Reporters.meta ================================================ fileFormatVersion: 2 guid: dded3bae191a0ba46be87613decab5af folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Formatters.meta ================================================ fileFormatVersion: 2 guid: 313fa6debd8d20c46ad511bfbb6b4213 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/IterationEventKind.cs ================================================ using System; namespace UnityEditor.EditorIterationProfiler { public enum IterationEventKind { None = 0, AssetImport, AssetPostProcess, ScriptCompilation, AssemblyCompilation, EnterPlayMode, ExitPlayMode, AssemblyReload, AssemblyCompilationStart, AssemblyCompilationFinish } } ================================================ FILE: Editor/IterationEventKind.cs.meta ================================================ fileFormatVersion: 2 guid: c0ea77b406086944dbb826a1c0895a29 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/IterationEventRoot.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace UnityEditor.EditorIterationProfiler { [Serializable] public class IterationEventRoot { [SerializeField] List m_Events = new List(); public List Events => m_Events; [SerializeField] int m_IterationIndex; public int IterationIndex { get => m_IterationIndex; set => m_IterationIndex = value; } [SerializeField] IterationEventKind m_IterationEventKind; public IterationEventKind IterationEventKind => m_IterationEventKind; [SerializeField] Dictionary m_EventDictionary = new Dictionary(); public IterationEventKind LastIterationEventKind { get { if (Events.Count == 0) { return IterationEventKind.None; } return Events.Last().Kind; } } public IterationEventRoot(int index) { IterationIndex = index; m_IterationEventKind = IterationEventKind.None; } public IterationEventRoot(int index, IterationEventKind eventKind) { IterationIndex = index; m_IterationEventKind = eventKind; } public void Reload() { foreach (var eventData in Events) { eventData.Children = new List(); } foreach (var eventData in Events) { int parentIndex = eventData.ParentIndex; if (parentIndex >= 0) { Events[parentIndex].Children.Add(eventData); } } } public EventData StartEvent(IterationEventKind kind, string identifier, string metadata) { int index = Events.Count; var eventData = new EventData(kind, identifier, metadata, index); Events.Add(eventData); m_EventDictionary[identifier] = eventData; return eventData; } public EventData StartEvent(IterationEventKind kind) { return StartEvent(kind, kind.ToString(), null); } public void FinishEvent(string identifier) { var eventData = FindLastEvent(identifier); eventData?.Finish(); } public void FinishEvent(IterationEventKind kind) { string identifier = kind.ToString(); FinishEvent(identifier); } public EventData AddChildEvent(string identifier, string metadata, double startTime, double finishTime, EventData parentEvent, EventDataFlags flags = EventDataFlags.None) { int index = Events.Count; var eventData = new EventData(identifier, metadata, index, startTime, finishTime, flags); Events.Add(eventData); SetParent(eventData, parentEvent); return eventData; } public static void SetParent(EventData child, EventData parent) { if (child == null) { throw new ArgumentNullException(nameof(child), "Argument is null"); } if (parent == null) { throw new ArgumentNullException(nameof(parent), "Argument is null"); } child.ParentIndex = parent.Index; parent.Children.Add(child); } public void SetParent(EventData child, IterationEventKind kind) { var parent = FindLastEvent(kind); SetParent(child, parent); } public EventData FindLastEvent(IterationEventKind kind) { string identifier = kind.ToString(); return FindLastEvent(identifier); } public EventData FindLastEvent(string identifier) { if (m_EventDictionary != null && m_EventDictionary.TryGetValue(identifier, out var eventData)) { return eventData; } // If an assembly reload happens, then eventDictionary is // empty and we search for the last event with matching // identifier. for (int i = Events.Count - 1; i >= 0; --i) { if (Events[i].Identifier == identifier) { return Events[i]; } } return null; } } } ================================================ FILE: Editor/IterationEventRoot.cs.meta ================================================ fileFormatVersion: 2 guid: 6efa6c94e301cfc4da89c7584f3ceb2a MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/IterationList.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace UnityEditor.EditorIterationProfiler { [Serializable] class IterationList : IIterationList { [SerializeField] List m_IterationIterationEventRoots; public List IterationEventRoots => m_IterationIterationEventRoots; public IterationEventRoot LastIterationEventRoot => m_IterationIterationEventRoots.LastOrDefault(); [SerializeField] List m_IterationEventKinds; public List IterationEventKinds => m_IterationEventKinds; [SerializeField] Action m_Updated; public Action Updated { get; set; } public IterationList() { m_IterationIterationEventRoots = new List(); m_IterationEventKinds = new List(); } public void NewIteration(IterationEventKind kind) { int index = IterationEventRoots.Count; IterationEventRoots.Add(new IterationEventRoot(index, kind)); IterationEventKinds.Add(kind); } public void Clear() { IterationEventRoots.Clear(); IterationEventKinds.Clear(); NotifyUpdated(); } public void Reload() { foreach (var eventDataList in IterationEventRoots) { eventDataList.Reload(); } NotifyUpdated(); } public void NotifyUpdated() { Updated?.Invoke(this); } } } ================================================ FILE: Editor/IterationList.cs.meta ================================================ fileFormatVersion: 2 guid: 685ab7a1693ff6d4588b129ce76cef56 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/ProfilerDataCollector.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEditor.EditorIterationProfiler.API; using UnityEditor.Profiling; using UnityEditorInternal; using UnityEngine; using UnityEngine.Profiling; namespace UnityEditor.EditorIterationProfiler { [Serializable] public class ProfilerDataCollector : IProfilerDataCollector { IIterationList m_IterationList; //static Dictionary s_FlattenMarkers = new Dictionary(); static Dictionary s_KeyMarkers = new Dictionary(); static Dictionary s_MarkerFlags = new Dictionary(); static Dictionary s_MarkerThread = new Dictionary(); [SerializeField] List m_FrameSearchDataList = new List(); public ProfilerDataCollector(IIterationList iterationList) { Initialize(iterationList); // Find frame where asset import kicks off compilation. AddKeyMarkers(IterationEventKind.AssetImport, "CompilationPipeline.CompileScripts"); //AddKeyMarkers(IterationEventKind.AssemblyCompilationStart, "CompilationPipeline.CompileAssemblyStart"); //AddKeyMarkers(IterationEventKind.AssemblyCompilationFinish, "CompilationPipeline.CompileAssemblyFinish"); AddKeyMarkers(IterationEventKind.AssemblyReload, "ReloadAssemblies"); AddKeyMarkers(IterationEventKind.EnterPlayMode, "EnterPlayMode"); AddKeyMarkers(IterationEventKind.ExitPlayMode, "ExitPlayMode"); //AddFlattenMarker("ReloadAssembly", "ReloadAssemblies"); //AddFlattenMarker("BeginReloadAssembly", "ReloadAssemblies"); //AddFlattenMarker("EndReloadAssembly", "ReloadAssemblies"); string[] userCodeMarkers = { "AssemblyReloadEvents.OnBeforeAssemblyReload()", "AssemblyReloadEvents.OnAfterAssemblyReload()", "DisabledScriptedObjects", "BackupScriptedObjects", "RestoreManagedReferences", "ProcessInitializeOnLoadAttributes", "ProcessInitializeOnLoadMethodAttributes", "AwakeScriptedObjects", "UnloadDomain" }; foreach (string marker in userCodeMarkers) { AddMarkerFlags(marker, EventDataFlags.UserCode); } #if UNITY_2020_3_OR_NEWER AddThreadMarker("UnloadDomain", "Domain Unloader", "Finalizer"); #else AddThreadMarker("UnloadDomain", "Domain unloader", "Finalizer"); #endif } void Initialize(IIterationList iterationList) { m_IterationList = iterationList; } public void Clear() { m_FrameSearchDataList.Clear(); } public void Subscribe() { UnityProfiling.NewProfilerFrameRecorded += ProfilerNewFrame; } public void Unsubscribe() { UnityProfiling.NewProfilerFrameRecorded -= ProfilerNewFrame; } static void AddKeyMarkers(IterationEventKind iterationEventKind, params string[] markers) { s_KeyMarkers[iterationEventKind] = markers; } static string[] GetKeyMarkers(IterationEventKind iterationEventKind) { if (s_KeyMarkers.TryGetValue(iterationEventKind, out string[] marker)) { return marker; } return null; } //static void AddFlattenMarker(string marker, string parentMarker) //{ // s_FlattenMarkers[marker] = parentMarker; //} static void AddMarkerFlags(string marker, EventDataFlags flags) { s_MarkerFlags[marker] = flags; } static void AddThreadMarker(string marker, params string[] threadNames) { s_MarkerThread[marker] = threadNames; } public void Collect(IterationEventKind iterationEventKind, IterationEventRoot iterationEventRoot, EventData rootEvent) { var frameSearchData = new FrameSearchData { IterationEventKind = iterationEventKind, EventListIndex = iterationEventRoot.IterationIndex, EventDataIndex = rootEvent.Index, MaxNumFrames = 600 }; m_FrameSearchDataList.Add(frameSearchData); #if DEVELOPMENT_BUILD Debug.Log($"Starting to look for {iterationEventKind}"); #endif } void ProfilerNewFrame(int connectionId, int newFrameIndex) { // Remove frames which exceeded the search limit. for (var i = m_FrameSearchDataList.Count - 1; i >= 0; --i) { m_FrameSearchDataList[i].MaxNumFrames -= 1; if (m_FrameSearchDataList[i].MaxNumFrames <= 0) { #if DEVELOPMENT_BUILD Debug.Log($"Stopping search for event {m_FrameSearchDataList[i].IterationEventKind}"); #endif m_FrameSearchDataList.RemoveAt(i); } } if (m_FrameSearchDataList.Count == 0) { return; } var frameData = UnityProfiling.GetFrame(newFrameIndex, 0); if (!frameData.valid) { #if DEVELOPMENT_BUILD Debug.LogErrorFormat($"Unable to retrieve profiler data for frame {newFrameIndex}"); #endif return; } var markerData = new MarkerData(m_FrameSearchDataList.Count); for (var i = 0; i < markerData.Length; ++i) { var frameSearchData = m_FrameSearchDataList[i]; frameSearchData.MaxNumFrames -= 1; markerData.Markers[i] = GetKeyMarkers(frameSearchData.IterationEventKind); } var framesNotFound = new List(); for (int i = 0; i < m_FrameSearchDataList.Count; ++i) { if (markerData.FrameIndices[i] == 0) { framesNotFound.Add(m_FrameSearchDataList[i]); } } #if DEVELOPMENT_BUILD Debug.Log($"Searching for events {string.Join(",", framesNotFound.Select(f => f.IterationEventKind.ToString()).ToArray())} in frame {newFrameIndex}"); #endif if (!FindMarkersInFrameData(frameData, frameData.GetRootItemID(), markerData, ProfilerDriver.deepProfiling ? 12 : 8)) { #if DEVELOPMENT_BUILD Debug.LogWarning($"Didn't find all markers in {newFrameIndex}"); #endif return; } var iterationListUpdated = false; for (int i = markerData.Length - 1; i >= 0; --i) { var frameSearchData = m_FrameSearchDataList[i]; if (markerData.SampleIds[i] == UnityProfiling.InvalidSampleId) { if (frameSearchData.MaxNumFrames <= 0) { #if DEVELOPMENT_BUILD Debug.Log($"Stopping search for event {frameSearchData.IterationEventKind}"); #endif m_FrameSearchDataList.RemoveAt(i); } continue; } #if DEVELOPMENT_BUILD Debug.Log($"Reading profiling data for event {frameSearchData.IterationEventKind} from frame {markerData.FrameIndices[i]}"); #endif var eventDataList = m_IterationList.IterationEventRoots[frameSearchData.EventListIndex]; var eventData = eventDataList.Events[frameSearchData.EventDataIndex]; ReadProfilingData(eventDataList, eventData, markerData.FrameData[i], markerData.FrameData[i].GetRootItemID(), EventDataFlags.None, false); eventData.SetStartFinishTimeFromChildren(); eventData.PostProcess(); iterationListUpdated = true; m_FrameSearchDataList.RemoveAt(i); } if (iterationListUpdated) { m_IterationList.NotifyUpdated(); } } internal static void ReadProfilingData(IterationEventRoot iterationEventRoot, EventData parentEventData, HierarchyFrameDataView frameData, int sampleId, EventDataFlags flags, bool parentHasSingleChild) { double duration = frameData.GetItemColumnDataAsDouble(sampleId, HierarchyFrameDataView.columnTotalTime); if (duration < 0.01) { return; } string markerName = frameData.GetItemName(sampleId); double startTime = frameData.GetItemColumnDataAsDouble(sampleId, HierarchyFrameDataView.columnStartTime); double finishTime = startTime + duration; int metadataCount = frameData.GetItemMetadataCount(sampleId); var sb = new StringBuilder(); sb.Append("GC Alloc: " + frameData.GetItemColumnData(sampleId, 4) + "; "); sb.Append("Calls: " + frameData.GetItemColumnData(sampleId, 3) + "; "); if (metadataCount > 0) { sb.Append("Metadata: "); for (var i = 0; i < metadataCount; ++i) { sb.Append(frameData.GetItemMetadata(sampleId, i)); sb.Append("; "); } } var markerMetadata = sb.Replace(',', ';').Remove(sb.Length - 2, 1).ToString().Trim(); if (s_MarkerFlags.TryGetValue(markerName, out var eventMarkerFlags)) { flags |= eventMarkerFlags; } var childEvent = iterationEventRoot.AddChildEvent(markerName, markerMetadata, startTime, finishTime, parentEventData, flags); if (s_MarkerThread.TryGetValue(markerName, out string[] threadNames)) { foreach (string threadName in threadNames) { var threadFrameData = UnityProfiling.GetFrame(frameData.frameIndex, threadName); var threadEvent = iterationEventRoot.AddChildEvent($"Thread: {threadName}", markerMetadata, parentEventData.StartTime, parentEventData.FinishTime, childEvent, flags); var threadChildIds = new List(); threadFrameData.GetItemChildren(threadFrameData.GetRootItemID(), threadChildIds); foreach (int childId in threadChildIds) { ReadProfilingData(iterationEventRoot, threadEvent, threadFrameData, childId, flags, threadChildIds.Count == 1); } threadEvent.SetStartFinishTimeFromChildren(); } } var childIds = new List(); frameData.GetItemChildren(sampleId, childIds); foreach (int childId in childIds) { ReadProfilingData(iterationEventRoot, childEvent, frameData, childId, flags, childIds.Count == 1); } } static bool FindMarkersInFrameData(HierarchyFrameDataView frameData, int parentId, MarkerData markerData, int maxDepth) { if (maxDepth == 0) { return false; } var childrenIds = new List(); frameData.GetItemChildren(parentId, childrenIds); string[][] markers = markerData.Markers; int[] sampleIds = markerData.SampleIds; int[] frameIndices = markerData.FrameIndices; foreach (int childId in childrenIds) { string name = frameData.GetItemName(childId); for (var i = 0; i < markers.Length; ++i) { if (markers[i] == null) { continue; } string[] localKeyMarkers = markers[i]; for (var j = 0; j < localKeyMarkers.Length; ++j) { if (name == localKeyMarkers[j]) { sampleIds[i] = childId; frameIndices[i] = frameData.frameIndex; markerData.FrameData[i] = frameData; } } } if (markerData.FoundAllSampleIds) { return true; } if (FindMarkersInFrameData(frameData, childId, markerData, maxDepth - 1)) { return true; } } return false; } [Serializable] public class FrameSearchData { public int EventDataIndex; public int EventListIndex; public IterationEventKind IterationEventKind; public int MaxNumFrames; } struct MarkerData { public string[][] Markers; public int[] SampleIds; public int[] FrameIndices; public HierarchyFrameDataView[] FrameData; public MarkerData(int length) { Markers = new string[length][]; SampleIds = new int[length]; FrameIndices = new int[length]; FrameData = new HierarchyFrameDataView[length]; for (var i = 0; i < length; ++i) { SampleIds[i] = UnityProfiling.InvalidSampleId; } } public bool FoundAllSampleIds { get { for (var i = 0; i < Length; ++i) { if (SampleIds[i] == UnityProfiling.InvalidSampleId) { return false; } } return true; } } public int Length => Markers.Length; } } } ================================================ FILE: Editor/ProfilerDataCollector.cs.meta ================================================ fileFormatVersion: 2 guid: d1e3dc04d1e8e8344a237bfc5e3b756b MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/Unity.EditorIterationProfiler.Editor.asmdef ================================================ { "name": "Unity.EditorIterationProfiler.Editor", "references": [], "includePlatforms": [ "Editor" ], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": false, "precompiledReferences": [], "autoReferenced": true, "defineConstraints": [], "versionDefines": [], "noEngineReferences": false } ================================================ FILE: Editor/Unity.EditorIterationProfiler.Editor.asmdef.meta ================================================ fileFormatVersion: 2 guid: 00680d720d027674dbb1d8c51e1aaa52 AssemblyDefinitionImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/UnityEditorEvents.cs ================================================ using System; using System.IO; using UnityEditor.Compilation; namespace UnityEditor.EditorIterationProfiler { static class UnityEditorEvents { public enum Event { None = 0, ScriptCompilationStarted, ScriptCompilationFinished, AssemblyCompilationStarted, AssemblyCompilationFinished, AssemblyReloadStarted, AssemblyReloadFinished, EnteringPlayMode, EnteredPlayMode, ExitingPlayMode, ExitedPlayMode } public static event Action EditorEvent; public static void Subscribe() { CompilationPipeline.compilationStarted += ScriptCompilationStarted; CompilationPipeline.compilationFinished += ScriptCompilationFinished; CompilationPipeline.assemblyCompilationStarted += AssemblyCompilationStarted; CompilationPipeline.assemblyCompilationFinished += AssemblyCompilationFinished; AssemblyReloadEvents.beforeAssemblyReload += AssemblyReloadStarted; AssemblyReloadEvents.afterAssemblyReload += AssemblyReloadFinished; EditorApplication.playModeStateChanged += PlayModeStateChanged; } public static void Unsubscribe() { CompilationPipeline.compilationStarted -= ScriptCompilationStarted; CompilationPipeline.compilationFinished -= ScriptCompilationFinished; CompilationPipeline.assemblyCompilationStarted -= AssemblyCompilationStarted; CompilationPipeline.assemblyCompilationFinished -= AssemblyCompilationFinished; AssemblyReloadEvents.beforeAssemblyReload -= AssemblyReloadStarted; AssemblyReloadEvents.afterAssemblyReload -= AssemblyReloadFinished; EditorApplication.playModeStateChanged -= PlayModeStateChanged; } static void ScriptCompilationStarted(object obj) { EditorEvent?.Invoke(Event.ScriptCompilationStarted, null); } static void ScriptCompilationFinished(object obj) { EditorEvent?.Invoke(Event.ScriptCompilationFinished, null); } static void AssemblyCompilationStarted(string assembly) { EditorEvent?.Invoke(Event.AssemblyCompilationStarted, Path.GetFileName(assembly)); } static void AssemblyCompilationFinished(string assembly, CompilerMessage[] messages) { EditorEvent?.Invoke(Event.AssemblyCompilationFinished, Path.GetFileName(assembly)); } static void AssemblyReloadStarted() { EditorEvent?.Invoke(Event.AssemblyReloadStarted, null); } static void AssemblyReloadFinished() { EditorEvent?.Invoke(Event.AssemblyReloadFinished, null); } static void PlayModeStateChanged(PlayModeStateChange state) { switch (state) { case PlayModeStateChange.ExitingEditMode: { EditorEvent?.Invoke(Event.EnteringPlayMode, null); } break; case PlayModeStateChange.EnteredPlayMode: { EditorEvent?.Invoke(Event.EnteredPlayMode, null); } break; case PlayModeStateChange.ExitingPlayMode: { EditorEvent?.Invoke(Event.ExitingPlayMode, null); } break; case PlayModeStateChange.EnteredEditMode: { EditorEvent?.Invoke(Event.ExitedPlayMode, null); } break; } } } } ================================================ FILE: Editor/UnityEditorEvents.cs.meta ================================================ fileFormatVersion: 2 guid: 521d993316e2dc54f9f64b52fe96eadf MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor/UnityProfiling.cs ================================================ using System; using UnityEditor.Profiling; using UnityEditorInternal; namespace UnityEditor.EditorIterationProfiler { static class UnityProfiling { public static bool EditorProfilingEnabled { get => ProfilerDriver.enabled && ProfilerDriver.profileEditor; set { ProfilerDriver.enabled = value; ProfilerDriver.profileEditor = value; } } public static void SetProfileDeepScripts(bool deep) { if (ProfilerDriver.deepProfiling == deep) return; bool doApply = true; // When enabling / disabling deep script profiling we need to reload scripts. In play mode this might be intrusive, so we ask the user first. if (EditorApplication.isPlaying) { if (deep) { doApply = EditorUtility.DisplayDialog("Enable deep script profiling", "Enabling deep profiling requires reloading scripts.", "Reload", "Cancel"); } else { doApply = EditorUtility.DisplayDialog("Disable deep script profiling", "Disabling deep profiling requires reloading all scripts", "Reload", "Cancel"); } } if (doApply) { EditorIterationProfilerAnalytics.SendInteractionEvent(EditorProfilingEnabled, EditorApplication.isPlaying, ProfilerDriver.deepProfiling, EditorIterationProfilerIntegration.Instance.Settings.Flatten, EditorIterationProfilerIntegration.Instance.Settings.UserCode); ProfilerDriver.deepProfiling = deep; EditorPrefs.SetBool(EditorIterationProfilerWindow.Styles.k_EnableDeepProfile, deep); EditorIterationProfilerIntegration.Instance.Settings.DeepProfile = deep; EditorUtility.RequestScriptReload(); } } public static int InvalidSampleId => HierarchyFrameDataView.invalidSampleId; public static event Action NewProfilerFrameRecorded { add => ProfilerDriver.NewProfilerFrameRecorded += value; remove => ProfilerDriver.NewProfilerFrameRecorded -= value; } public static HierarchyFrameDataView GetFrame(int frameIndex, int threadIndex) { var frame = ProfilerDriver.GetHierarchyFrameDataView(frameIndex, threadIndex, HierarchyFrameDataView.ViewModes.MergeSamplesWithTheSameName, HierarchyFrameDataView.columnTotalTime, false); return frame; } public static HierarchyFrameDataView GetFrame(int frameIndex, string threadName) { var iter = new ProfilerFrameDataIterator(); iter.SetRoot(frameIndex, 0); int threadCount = iter.GetThreadCount(frameIndex); for (var i = 0; i < threadCount; ++i) { iter.SetRoot(frameIndex, i); string currentThreadName = iter.GetThreadName(); if (currentThreadName.Equals(threadName, StringComparison.OrdinalIgnoreCase)) { iter.Dispose(); return GetFrame(frameIndex, i); } } iter.Dispose(); throw new ArgumentException($"Could not find thread named '{threadName}'. Depending on the Unity version, it could have been renamed."); } } } ================================================ FILE: Editor/UnityProfiling.cs.meta ================================================ fileFormatVersion: 2 guid: dc5baee3fb0bc9f41ba836bff674eb06 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Editor.meta ================================================ fileFormatVersion: 2 guid: 9949f7c4ae59ee7499a083d831233f16 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: LICENSE.md ================================================ Editor Iteration Profiler copyright © 2020 Unity Technologies ApS Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](https://unity3d.com/legal/licenses/Unity_Companion_License). Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions. ================================================ FILE: LICENSE.md.meta ================================================ fileFormatVersion: 2 guid: 0515e52add3ab464880c7466bcf136b8 TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: README.md ================================================ # Editor Iteration Profiler Assists in capturing frames from the Profiler of Domain Reloads in the Unity Editor. Compatible with Unity 2019.3+. # Please see the forum post for details on how to use, gotchas, etc. https://forum.unity.com/threads/introducing-the-editor-iteration-profiler.908390/ ================================================ FILE: README.md.meta ================================================ fileFormatVersion: 2 guid: 41ae1fb9d1a93684bb23091b2c54ad2b TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: package.json ================================================ { "name": "com.unity.editoriterationprofiler", "displayName":"Editor Iteration Profiler", "version": "0.1.2-preview", "unity": "2019.3", "description": "The Editor Iteration Profiler is an Editor tool which monitors domain reloads. It makes use of the Profiler and works by collecting and storing the relevant frames of the domain reload.\n\nMain features:\n▪ Enter/Exit playmode domain reload monitoring\n▪ Script Compilation monitoring\n▪ Ability to export its data or Profiler data to a number of formats such as CSV, HTML, JSON, etc.\n", "dependencies": { } } ================================================ FILE: package.json.meta ================================================ fileFormatVersion: 2 guid: 9d114ba67f600c34994534b2b6994a8a PackageManifestImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: