docs.unity3d.com
    Show / Hide Table of Contents

    Profiler counters API guide

    You can use the ProfilerCounter or ProfilerCounterValue API to track the integral metrics of your application and make them visible in the Unity Profiler or in other code. This is particularly useful if you want to track performance changes in your application, and it speeds up the investigation of performance issues because you can use the information from your Profiler counters in conjunction with Unity built-in counters and instrumentation data.

    If you are an Asset Store package developer, you can add Profiler counters to your code to help other developers to understand important performance characteristics of your system, and they can use this information for optimization or budgeting tooling.

    The following diagram displays a high level overview of the Profiler counters data flow: Profiler counters flow.
    Profiler counters flow.

    The ProfilerRecorder API retrieves Profiler counter data in your application code, and the RawFrameDataView or HierarchyFrameDataView APIs retrieves Profiler counter data in the Editor code. Additionally, you can visualize this counter data in the Profiler Window by configuring a custom Profiler Module in the Module Editor.

    ProfilerCounter and ProfilerCounterValue support the following types:

    • int
    • long
    • float
    • double

    How to pass counter values to the Profiler

    The Profiler counters API supports push and pull operations. You can push the value of the counter to the Profiler, or the Profiler can pull the value at the end of the frame.

    If your data changes infrequently - for example once per frame, use the ProfilerCounter API to push the counter data to the Profiler. If your data changes multiple times per frame, use the ProfilerCounterValue API. This makes the Profiler automatically pick up the last value at the end of the frame. The following example shows you how to use these APIs to set up and pass counter values to the Profiler

    1. Define counters

    To pass counter values to the Profiler, you must first define which counters you want to send. The following example uses ProfilerCounter for the enemies count because the value changes infrequently. Additionally, the example uses ProfilerCounterValue for bullet count because a player or enemies might fire many bullets per frame.

    using Unity.Profiling;
    
    class GameStats
    {
        public static readonly ProfilerCategory MyProfilerCategory = ProfilerCategory.Scripts;
    
        public static readonly ProfilerCounter<int> EnemyCount =
            new ProfilerCounter<int>(MyProfilerCategory, "Enemy Count", ProfilerMarkerDataUnit.Count);
    
        public static ProfilerCounterValue<int> BulletCount =
            new ProfilerCounterValue<int>(MyProfilerCategory, "Bullet Count",
                ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.FlushOnEndOfFrame);
    }
    

    2. Update the value

    The next example assumes that the GameManager class handles high level logic and knows about enemies. To update the value of the counter, in the Update or LateUpdate method (depending on when the logic is performed with spawning or destroying enemies), you can use Sample method to push the enemies count value to the Profiler.

    using UnityEngine;
    using Unity.Profiling;
    
    class GameManager : MonoBehaviour
    {
        Enemy[] m_Enemies;
    
        void Update()
        {
            GameStats.EnemyCount.Sample(m_Enemies.Length);
        }
    }
    

    To pass the bullet count to the Profiler, this example assumes that there is a Shell script that manages the bullet lifecycle. It then increases the GameStats.BulletCount value in Awake and decreases it in OnDestroy to give accurate information about the current bullet flow in the game.

    using UnityEngine;
    using Unity.Profiling;
    
    public class Shell : MonoBehaviour
    {
        void Awake()
        {
            GameStats.BulletCount.Value += 1;
        }
        void OnDestroy()
        {
            GameStats.BulletCount.Value -= 1;
        }
    }
    
    Note

    Both ProfilerCounter and ProfilerCounterValue are compiled out in non-development builds.

    Viewing the counters in the Profiler Window

    You can view the data that ProfilerCounter or ProfilerCounterValue generates in the Profiler Window in a custom Profiler Module. This might help to visually recognise relationships with other system metrics and identify performance issues quickly. You can use the Module Editor to select built-in or newly added counters for the visualization. To open the Profiler Module Editor, open the Profiler Window (Window > Analysis > Profiler) and then select the Profiler Module dropdown in the top left of the window. Click the gear icon, and the Profiler Module Editor opens in a new window.

    Profiler Module Editor
    Profiler Module Editor window.

    You can then view the data in the Profiler Window alongside with other counters.

    Module with custom counters in Profiler Window.
    Module with custom counters in the Profiler Window.

    Note

    Counters declared as static are dynamically initialized in the C# code when a type is initialized and might not be available until they are actually initialized and used. This applies to both Edit and Play Modes. If you don't see your counters appearing in the Module Editor, record some data with the profiler first until some frames passed that should have send values through these counters.

    Getting counter values in Players

    Profiler counters give you an insight into important game or engine system metrics. If you have a continuous integration setup or want to visualize key performance metrics in your application during a test play through you can use the ProfilerRecorder API to get custom Profiler counter values as well as Unity built-in counters.

    For example, the following script displays the frame time, Mono/IL2CPP heap size, and total memory that the application uses.

    using System.Collections.Generic;
    using System.Text;
    using Unity.Profiling;
    using UnityEngine;
    
    public class StatsScript : MonoBehaviour
    {
        string statsText;
        ProfilerRecorder systemMemoryRecorder;
        ProfilerRecorder gcMemoryRecorder;
        ProfilerRecorder mainThreadTimeRecorder;
    
        double GetRecorderFrameAverage(ProfilerRecorder recorder)
        {
            var samplesCount = recorder.Capacity;
            if (samplesCount == 0)
                return 0;
    
            double r = 0;
            unsafe
            {
                var samples = stackalloc ProfilerRecorderSample[samplesCount];
                recorder.CopyTo(samples, samplesCount);
                for (var i = 0; i < samplesCount; ++i)
                    r += samples[i].Value;
                r /= samplesCount;
            }
    
            return r;
        }
    
        void OnEnable()
        {
            systemMemoryRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "System Used Memory");
            gcMemoryRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "GC Reserved Memory");
            mainThreadTimeRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Internal, "Main Thread", 15);
        }
    
        void OnDisable()
        {
            systemMemoryRecorder.Dispose();
            gcMemoryRecorder.Dispose();
            mainThreadTimeRecorder.Dispose();
        }
    
        void Update()
        {
            var sb = new StringBuilder(500);
            sb.AppendLine($"Frame Time: {GetRecorderFrameAverage(mainThreadTimeRecorder) * (1e-6f):F1} ms");
            sb.AppendLine($"GC Memory: {gcMemoryRecorder.LastValue / (1024 * 1024)} MB");
            sb.AppendLine($"System Memory: {systemMemoryRecorder.LastValue / (1024 * 1024)} MB");
            statsText = sb.ToString();
        }
    
        void OnGUI()
        {
            GUI.TextArea(new Rect(10, 30, 250, 50), statsText);
        }
    }
    

    Don't forget to use ProfilerRecorder.Dispose() to free unmanaged resources associated with the ProfilerRecorder.

    Note

    Not all Profiler counters are available in the Release Players. Use ProfilerRecorder.Valid to determine if the data is available and that the Profiler can record it. Alternatively, you can use ProfilerRecorderHandle.GetAvailable to enumerate all available Profiler stats.

    Getting counter values from Profiler Frame data in the Editor

    To get Profiler counter values when processing Profiler frame data in the Editor, use the FrameDataView API.

    You can use the FrameDataView.GetCounterValueAsInt, FrameDataView.GetCounterValueAsLong, FrameDataView.GetCounterValueAsFloat and FrameDataView.GetCounterValueAsDouble to get a frame value of the specific counter, like so:

    using UnityEditor.Profiling;
    
    class Example
    {
        static int ExtractMyCounterValue(FrameDataView frameData, string counterName)
        {
            var counterMarkerId = frameData.GetMarkerId(counterName);
            return frameData.GetCounterValueAsInt(counterMarkerId);
        }
    }
    
    Back to top
    Terms of use
    Copyright © 2023 Unity Technologies — Terms of use
    • Legal
    • Privacy Policy
    • Cookies
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)
    "Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
    Generated by DocFX on 18 October 2023