Profiling: how is the context object passed to ProfilerMarker.Begin() accessed in RawFrameDataView?

Hi

I’m trying to profile cumulative cost of Instantiate and Destroy in our codebase to get a baseline before retrofitting object pooling everywhere that they are currently (ab)used.

I’ve got this code

public static class ProfileGCMarker
{
    public const            string         MARKER_NAME = "GameInstantiate";
    private static readonly ProfilerMarker s_marker    = new ProfilerMarker( ProfilerCategory.Memory, MARKER_NAME );
  
    public static void Begin( UnityEngine.Object context ) => s_marker.Begin( context );
    public static void End()                               => s_marker.End();  
}

wrapping a ProfileMarker

Everywhere we’re calling Instantiate I have something like this, so that the context object passed to ProfileMarker.Begin() is the object being instantiated.

ProfileGCMarker.Begin(  m_projectilePrefab );          

GameObject instance = Instantiate( m_projectilePrefab, origin.position, origin.rotation );

ProfileGCMarker.End();

I am using UnityEditorInternal.ProfilerDriver.NewProfilerFrameRecorded to get a callback for each profiler frame which is working fine.

My callback function looks like this:

private void ProfilerDriverFrameIteratorCallback( int connectionId, int frameIndex )
{
    using
    (
        var frameData =  UnityEditorInternal.ProfilerDriver.GetRawFrameDataView
        (
            frameIndex:  frameIndex,
            threadIndex: UNITY_MAINTHREAD_INDEX
        )
    )
    {
        int markerId = frameData.GetMarkerId( ProfileGCMarker.MARKER_NAME );

        if( UnityEditor.Profiling.RawFrameDataView.invalidMarkerId == markerId )
        {
            return;
        }

        int maxSamples = frameData.sampleCount;

        for( int i = 0; i < maxSamples; ++i )
        {
            if( frameData.GetSampleMarkerId( i ) != markerId )
            {
                continue;                  
            }
          
           //
           // I get here whenever ProfileGCMarker.Begin() is called but:
           // the sample has no metadata, no callstack info, and I have no idea how to get at the context object
           // which was passed to ProfileGCMarker.Begin()
           //
          Debug.Log( $"found a {ProfileGCMarker.MARKER_NAME} {frameData.GetMarkerFlags( i )} [{frameData.GetSampleMetadataCount( i )} metadata][{frameData.GetSampleTimeMs( i )} ms ({frameData.GetSampleTimeNs( i )} ns)][callstack size: {m_sampleCallstack.Count}]" );
        }                                                                                                                                                                                                
    }
}

In the log line I get valid sample times, but no metadata, no special flags.

I realise this is an internal API but I couldn’t find any other way to get at this data in a way which will allow me to aggregate the profiler sample data over a meaningful play session of our game (i.e. 30 seconds to a minute at least).

Any and all help appreciated!

Alex

In the profiler window I can see the context object associated with the ProfilerMarker just fine:

ok. Gonna answer my own question here…

Since it was giving me no useful info, I gave up trying to use UnityEditor.Profiling.RawFrameDataView.

I’m now using UnityEditor.Profiling.HierarchyFrameDataView which - if you’re trying not to allocate a buttload of temporary lists - is non-trivial to work with but seems to have all the information I wanted.

I hope this helps someone because all I could find on this stuff was the Unity manual pages:

The callback for UnityEditorInternal.ProfilerDriver.NewProfilerFrameRecorded now looks as below…

NOTE: I wrote a helper class to handle allocation-free stack based iteration of UnityEditor.Profiling.HierarchyFrameDataView which is not included in the snippet, and which I will leave as an exercise for the reader.

private void ProfilerDriverFrameIteratorCallback( int connectionId, int frameIndex )
{
    using
    (
        var frameData =  UnityEditorInternal.ProfilerDriver.GetHierarchyFrameDataView
        (
            frameIndex:  frameIndex,
            threadIndex: UNITY_MAINTHREAD_INDEX, // note: this is 0
            viewMode:   UnityEditor.Profiling.HierarchyFrameDataView.ViewModes.HideEditorOnlySamples,
            sortColumn: UnityEditor.Profiling.HierarchyFrameDataView.columnTotalTime,
            sortAscending: false
        )
    )
    {
        //
        // m_stackIterator is the custom stack-based iterator I wrote...
        // it holds data to manage recursive iteration of UnityEditor.Profiling.HierarchyFrameDataView
        // essentially a stack of lists of childItemIds and their parent itemId
        //
        m_stackIterator.Initialise( frameData );
         
        // local function to simplify iterating the hierarchy         
        // note: m_stackIterator is a stack of IterationStackFrame
        int GetChildOfItemWithName( IterationStackFrame frame, string childNameToFind )
        {
            foreach( int childItemId in frame._childIdList )
            {
                var childItemName = frameData.GetItemName( childItemId );
                if( childItemName == childNameToFind )
                {
                    return childItemId;
                }
            }

            return Constants.k_InvalidIndex;
        }

        //
        // the first few chunks of code find the part of the hierarchy where I know the info I want is
        // i.e. under PlayerLoop => Update.ScriptRunBehaviourUpdate => BehaviourUpdate...
        //
        int markerId = frameData.GetMarkerId( ProfileGCMarker.MARKER_NAME );

        if( UnityEditor.Profiling.HierarchyFrameDataView.invalidMarkerId == markerId )
        {
            return;
        }

      
        // --------------------------------
        {          
            int playerLoopId = GetChildOfItemWithName( m_stackIterator.Current(), "PlayerLoop" );

            if( Constants.k_InvalidIndex == playerLoopId )
            {
                return;
            }

            m_stackIterator.Push( playerLoopId );
        }
      
        // --------------------------------
        {
            int scriptBehaviourUpdateId = GetChildOfItemWithName( m_stackIterator.Current(), "Update.ScriptRunBehaviourUpdate" );

            if( Constants.k_InvalidIndex == scriptBehaviourUpdateId )
            {
                return;
            }

            m_stackIterator.Push( scriptBehaviourUpdateId );
        }

        // --------------------------------
        {
            int behaviourUpdateId = GetChildOfItemWithName( m_stackIterator.Current(), "BehaviourUpdate" );

            if( Constants.k_InvalidIndex == behaviourUpdateId )
            {
                return;
            }
          
            m_stackIterator.Push( behaviourUpdateId );
        }

        //
        // now we know we're under PlayerLoop => Update.ScriptRunBehaviourUpdate => BehaviourUpdate
        // I use this recursive local function to iterate the remaining hierarchy tree to find the marker(s) I'm looking for
        //
        void RecursiveIterateFrameDataToFindChildItemWithMarkerId( FrameDataStackIterator stackIterator, int targetMarkerId, Action< FrameDataStackIterator, int > callbackWhenFindItemWithMarkerId )
        {              
            var currentChildren = stackIterator.Current()._childIdList;
            foreach( var childIdOfCurrent in currentChildren )
            {
                if( stackIterator.FrameData.GetItemMarkerID( childIdOfCurrent ) == targetMarkerId )
                {
                    callbackWhenFindItemWithMarkerId(stackIterator, childIdOfCurrent );
                }

                if( stackIterator.FrameData.HasItemChildren( childIdOfCurrent ) )
                {
                    stackIterator.Push( childIdOfCurrent );
                    RecursiveIterateFrameDataToFindChildItemWithMarkerId( stackIterator, targetMarkerId, callbackWhenFindItemWithMarkerId );
                    stackIterator.Pop();
                }
            }
        }
                              
        RecursiveIterateFrameDataToFindChildItemWithMarkerId
        (
            m_stackIterator,
            markerId,
            ( stackIterator, foundItemId ) =>
            {
                Debug.Log(      $"found {ProfileGCMarker.MARKER_NAME} "
                           +    $"instantiating: {stackIterator.FrameData.GetItemColumnData( foundItemId, UnityEditor.Profiling.HierarchyFrameDataView.columnObjectName )} "
                           +    $"time: {stackIterator.FrameData.GetItemColumnData( foundItemId,          UnityEditor.Profiling.HierarchyFrameDataView.columnTotalTime )} ms "
                           +    $"allocated: {stackIterator.FrameData.GetItemColumnData( foundItemId,     UnityEditor.Profiling.HierarchyFrameDataView.columnGcMemory )}" );
            }
        );
    }