Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

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

Discussion in 'Editor & General Support' started by darbotron, Oct 2, 2023.

  1. darbotron

    darbotron

    Joined:
    Aug 9, 2010
    Posts:
    359
    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
    Code (CSharp):
    1. public static class ProfileGCMarker
    2. {
    3.     public const            string         MARKER_NAME = "GameInstantiate";
    4.     private static readonly ProfilerMarker s_marker    = new ProfilerMarker( ProfilerCategory.Memory, MARKER_NAME );
    5.  
    6.     public static void Begin( UnityEngine.Object context ) => s_marker.Begin( context );
    7.     public static void End()                               => s_marker.End();  
    8. }
    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.

    Code (CSharp):
    1. ProfileGCMarker.Begin(  m_projectilePrefab );          
    2.  
    3. GameObject instance = Instantiate( m_projectilePrefab, origin.position, origin.rotation );
    4.  
    5. ProfileGCMarker.End();                          
    6.  

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

    My callback function looks like this:

    Code (CSharp):
    1. private void ProfilerDriverFrameIteratorCallback( int connectionId, int frameIndex )
    2. {
    3.     using
    4.     (
    5.         var frameData =  UnityEditorInternal.ProfilerDriver.GetRawFrameDataView
    6.         (
    7.             frameIndex:  frameIndex,
    8.             threadIndex: UNITY_MAINTHREAD_INDEX
    9.         )
    10.     )
    11.     {
    12.         int markerId = frameData.GetMarkerId( ProfileGCMarker.MARKER_NAME );
    13.  
    14.         if( UnityEditor.Profiling.RawFrameDataView.invalidMarkerId == markerId )
    15.         {
    16.             return;
    17.         }
    18.  
    19.         int maxSamples = frameData.sampleCount;
    20.  
    21.         for( int i = 0; i < maxSamples; ++i )
    22.         {
    23.             if( frameData.GetSampleMarkerId( i ) != markerId )
    24.             {
    25.                 continue;                  
    26.             }
    27.          
    28.            //
    29.            // I get here whenever ProfileGCMarker.Begin() is called but:
    30.            // the sample has no metadata, no callstack info, and I have no idea how to get at the context object
    31.            // which was passed to ProfileGCMarker.Begin()
    32.            //
    33.           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}]" );
    34.         }                                                                                                                                                                                                
    35.     }
    36. }
    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
     
    Last edited: Oct 3, 2023
  2. darbotron

    darbotron

    Joined:
    Aug 9, 2010
    Posts:
    359
    In the profiler window I can see the context object associated with the ProfilerMarker just fine:

    upload_2023-10-3_9-9-25.png
     
  3. darbotron

    darbotron

    Joined:
    Aug 9, 2010
    Posts:
    359
    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.


    Code (CSharp):
    1. private void ProfilerDriverFrameIteratorCallback( int connectionId, int frameIndex )
    2. {
    3.     using
    4.     (
    5.         var frameData =  UnityEditorInternal.ProfilerDriver.GetHierarchyFrameDataView
    6.         (
    7.             frameIndex:  frameIndex,
    8.             threadIndex: UNITY_MAINTHREAD_INDEX, // note: this is 0
    9.             viewMode:   UnityEditor.Profiling.HierarchyFrameDataView.ViewModes.HideEditorOnlySamples,
    10.             sortColumn: UnityEditor.Profiling.HierarchyFrameDataView.columnTotalTime,
    11.             sortAscending: false
    12.         )
    13.     )
    14.     {
    15.         //
    16.         // m_stackIterator is the custom stack-based iterator I wrote...
    17.         // it holds data to manage recursive iteration of UnityEditor.Profiling.HierarchyFrameDataView
    18.         // essentially a stack of lists of childItemIds and their parent itemId
    19.         //
    20.         m_stackIterator.Initialise( frameData );
    21.          
    22.         // local function to simplify iterating the hierarchy        
    23.         // note: m_stackIterator is a stack of IterationStackFrame
    24.         int GetChildOfItemWithName( IterationStackFrame frame, string childNameToFind )
    25.         {
    26.             foreach( int childItemId in frame._childIdList )
    27.             {
    28.                 var childItemName = frameData.GetItemName( childItemId );
    29.                 if( childItemName == childNameToFind )
    30.                 {
    31.                     return childItemId;
    32.                 }
    33.             }
    34.  
    35.             return Constants.k_InvalidIndex;
    36.         }
    37.  
    38.         //
    39.         // the first few chunks of code find the part of the hierarchy where I know the info I want is
    40.         // i.e. under PlayerLoop => Update.ScriptRunBehaviourUpdate => BehaviourUpdate...
    41.         //
    42.         int markerId = frameData.GetMarkerId( ProfileGCMarker.MARKER_NAME );
    43.  
    44.         if( UnityEditor.Profiling.HierarchyFrameDataView.invalidMarkerId == markerId )
    45.         {
    46.             return;
    47.         }
    48.  
    49.      
    50.         // --------------------------------
    51.         {          
    52.             int playerLoopId = GetChildOfItemWithName( m_stackIterator.Current(), "PlayerLoop" );
    53.  
    54.             if( Constants.k_InvalidIndex == playerLoopId )
    55.             {
    56.                 return;
    57.             }
    58.  
    59.             m_stackIterator.Push( playerLoopId );
    60.         }
    61.      
    62.         // --------------------------------
    63.         {
    64.             int scriptBehaviourUpdateId = GetChildOfItemWithName( m_stackIterator.Current(), "Update.ScriptRunBehaviourUpdate" );
    65.  
    66.             if( Constants.k_InvalidIndex == scriptBehaviourUpdateId )
    67.             {
    68.                 return;
    69.             }
    70.  
    71.             m_stackIterator.Push( scriptBehaviourUpdateId );
    72.         }
    73.  
    74.         // --------------------------------
    75.         {
    76.             int behaviourUpdateId = GetChildOfItemWithName( m_stackIterator.Current(), "BehaviourUpdate" );
    77.  
    78.             if( Constants.k_InvalidIndex == behaviourUpdateId )
    79.             {
    80.                 return;
    81.             }
    82.          
    83.             m_stackIterator.Push( behaviourUpdateId );
    84.         }
    85.  
    86.         //
    87.         // now we know we're under PlayerLoop => Update.ScriptRunBehaviourUpdate => BehaviourUpdate
    88.         // I use this recursive local function to iterate the remaining hierarchy tree to find the marker(s) I'm looking for
    89.         //
    90.         void RecursiveIterateFrameDataToFindChildItemWithMarkerId( FrameDataStackIterator stackIterator, int targetMarkerId, Action< FrameDataStackIterator, int > callbackWhenFindItemWithMarkerId )
    91.         {              
    92.             var currentChildren = stackIterator.Current()._childIdList;
    93.             foreach( var childIdOfCurrent in currentChildren )
    94.             {
    95.                 if( stackIterator.FrameData.GetItemMarkerID( childIdOfCurrent ) == targetMarkerId )
    96.                 {
    97.                     callbackWhenFindItemWithMarkerId(stackIterator, childIdOfCurrent );
    98.                 }
    99.  
    100.                 if( stackIterator.FrameData.HasItemChildren( childIdOfCurrent ) )
    101.                 {
    102.                     stackIterator.Push( childIdOfCurrent );
    103.                     RecursiveIterateFrameDataToFindChildItemWithMarkerId( stackIterator, targetMarkerId, callbackWhenFindItemWithMarkerId );
    104.                     stackIterator.Pop();
    105.                 }
    106.             }
    107.         }
    108.                              
    109.         RecursiveIterateFrameDataToFindChildItemWithMarkerId
    110.         (
    111.             m_stackIterator,
    112.             markerId,
    113.             ( stackIterator, foundItemId ) =>
    114.             {
    115.                 Debug.Log(      $"found {ProfileGCMarker.MARKER_NAME} "
    116.                            +    $"instantiating: {stackIterator.FrameData.GetItemColumnData( foundItemId, UnityEditor.Profiling.HierarchyFrameDataView.columnObjectName )} "
    117.                            +    $"time: {stackIterator.FrameData.GetItemColumnData( foundItemId,          UnityEditor.Profiling.HierarchyFrameDataView.columnTotalTime )} ms "
    118.                            +    $"allocated: {stackIterator.FrameData.GetItemColumnData( foundItemId,     UnityEditor.Profiling.HierarchyFrameDataView.columnGcMemory )}" );
    119.             }
    120.         );
    121.     }