controller | 您要设置其选择的 Profiler 模块的控制器对象。当值为 null 时,Unity 会抛出 NullArgumentException 异常。 |
markerNameOrMarkerNamePath | 要选择的样本的名称,或样本堆栈中所有样本的名称。使用 / 分隔每个名称,并在要选择的样本处结束。不要添加尾随 / 。如果 Unity 找不到与该名称或样本堆栈匹配的样本,则不会设置选择,并且此方法返回 false。当此值为 null 或空字符串时,Unity 会抛出 ArgumentException 异常。 |
frameIndex | 基于 0 的帧索引。此值默认为 -1,这意味着选择是在当前显示的帧上设置的。请注意,Profiler 窗口 UI 将帧索引显示为 n+1。当此值超出由 ProfilerWindow.firstAvailableFrameIndex 和 ProfilerWindow.lastAvailableFrameIndex 描述的范围,或不为 -1 时,Unity 会抛出 ArgumentOutOfRangeException 异常。 |
threadGroupName | 线程组的名称。该参数默认为空字符串。Null 或空字符串表示线程不是线程组的一部分。“Job”、“Loading”和“Scripting Threads”是此类线程组名称的示例。 |
threadName | 线程的名称,例如“Main Thread”、“Render Thread”或“Worker 0”。此参数默认为“Main Thread”。当此值为 null 或空字符串时,Unity 会抛出 ArgumentException 异常。 |
threadId | 线程的 ID。当传递 FrameDataView.invalidThreadId 的默认值时,Unity 会在与提供的 threadGroupName 和 threadName 匹配的第一个线程中搜索样本。如果有多个名称相同的线程,请指定此 threadId。如果您需要它具有特定性,请使用 RawFrameDataView.threadId 或 HierarchyFrameDataView.threadId 获取特定线程的 ID。 |
bool 如果选择成功设置,则返回 true;如果由于找不到合适的样本而被拒绝,则返回 false。
在基于帧时间样本的 Profiler 模块(例如 CPU 使用率模块 和 GPU 使用率 Profiler 模块)中设置当前选择。
使用这些扩展方法到 IProfilerFrameTimeViewSampleSelectionController 以根据其名称或 Profiler 标记 ID、帧和发生在其中的线程以及可选的应在其内找到的样本堆栈来搜索样本。如果找到匹配这些条件的样本,则此方法将返回 true,并且您可以通过 IProfilerFrameTimeViewSampleSelectionController.selection 获取有关找到并设置的选择的详细信息。
当 Profiler 窗口中没有可用帧数据时,Unity 会抛出异常。您可以检查 ProfilerWindow.firstAvailableFrameIndex 是否大于或等于 0 以验证帧数据是否可用。
如果找不到指定的线程,Unity 会抛出 ArgumentException 异常。
如果您知道要选择的样本的 rawSampleIndex,则可以直接使用 IProfilerFrameTimeViewSampleSelectionController.SetSelection 设置选择。其他资源:IProfilerFrameTimeViewSampleSelectionController.SetSelection 和 IProfilerFrameTimeViewSampleSelectionController.ClearSelection。
controller | 您要设置其选择的 Profiler 模块的控制器对象。当值为 null 时,Unity 会抛出 NullArgumentException 异常。 |
frameIndex | 基于 0 的帧索引。请注意,Profiler 窗口 UI 将帧索引显示为 n+1。当此值超出由 ProfilerWindow.firstAvailableFrameIndex 和 ProfilerWindow.lastAvailableFrameIndex 描述的范围,或小于 0 时,Unity 会抛出 ArgumentOutOfRangeException 异常。 |
threadGroupName | 线程组的名称。Null 或空字符串表示线程不是线程组的一部分。“Job”、“Loading”和“Scripting Threads”是此类线程组名称的示例。 |
threadName | 线程的名称,例如“Main Thread”、“Render Thread”或“Worker 0”。当此值为 null 或空字符串时,Unity 会抛出 ArgumentException 异常。 |
sampleName | 要选择的样本的名称。如果 Unity 找不到与该名称匹配的样本,则不会设置选择,并且此方法返回 false。当此值为 null 或空字符串时,Unity 分别抛出 ArgumentNullException 或 ArgumentException 异常。 |
markerNamePath | 样本堆栈中所有样本的名称,每个名称都用 / 分隔,定义搜索的基本路径。类似于文件文件夹结构,此基本路径定义了 Unity 在哪里查找与 sampleName 匹配的样本。搜索到的 sampleName 可以是该标记路径中的最后一个项目或其任何子样本。不要添加尾随 / 。如果找不到与该样本堆栈路径和 sampleName 匹配的样本,则不会设置选择,并且此方法返回 false。这默认为 null,这意味着对样本的样本堆栈没有设置任何要求,并且选择第一个适合 sampleName 的样本。 |
threadId | 线程的 ID。当传递 FrameDataView.invalidThreadId 的默认值时,Unity 会在与提供的 threadGroupName 和 threadName 匹配的第一个线程中搜索样本。如果有多个名称相同的线程,请指定此 threadId。如果您需要它具有特定性,请使用 RawFrameDataView.threadId 或 HierarchyFrameDataView.threadId 获取特定线程的 ID。 |
sampleMarkerId | 使用 HierarchyFrameDataView 或 RawFrameDataView 获取标记 ID。如果找不到与该样本堆栈路径和 sampleMarkerId 匹配的样本,则不会设置选择,并且此方法返回 false。 |
markerIdPath | 样本堆栈中所有样本的 Profiler 标记 ID 列表,定义搜索的基本路径。类似于文件文件夹结构,此基本路径定义了 Unity 在哪里查找与 sampleMarkerId 匹配的样本。搜索到的 sampleMarkerId 可以是该标记路径中的最后一个项目或其任何子样本。如果找不到与该样本堆栈路径和 sampleMarkerId 匹配的样本,则不会设置选择,并且此方法返回 false。这默认为 null,这意味着对样本的样本堆栈没有设置任何要求,并且选择第一个适合 sampleMarkerId 的样本。 |
bool 如果选择成功设置,则返回 true;如果由于找不到合适的样本而被拒绝,则返回 false。
在基于帧时间样本的 Profiler 模块(例如 CPU 使用率模块 和 GPU 使用率 Profiler 模块)中设置当前选择。
使用这些扩展方法到 IProfilerFrameTimeViewSampleSelectionController 以根据其名称或 Profiler 标记 ID、帧和发生在其中的线程以及可选的应在其内找到的样本堆栈来搜索样本。如果找到匹配这些条件的样本,则此方法将返回 true,并且您可以通过 IProfilerFrameTimeViewSampleSelectionController.selection 获取有关找到并设置的选择的详细信息。
当 Profiler 窗口中没有可用帧数据时,Unity 会抛出异常。您可以检查 ProfilerWindow.firstAvailableFrameIndex 是否大于或等于 0 以验证帧数据是否可用。
当 Profiler 窗口中没有可用帧数据时,Unity 会抛出异常。您可以检查 ProfilerWindow.firstAvailableFrameIndex 是否大于或等于 0 以验证帧数据是否可用。
如果找不到指定的线程,Unity 会抛出 ArgumentException 异常。
如果您知道要选择的样本的 rawSampleIndex,则可以直接使用 IProfilerFrameTimeViewSampleSelectionController.SetSelection 设置选择。其他资源:IProfilerFrameTimeViewSampleSelectionController.SetSelection 和 IProfilerFrameTimeViewSampleSelectionController.ClearSelection。
using System; using System.Collections.Generic; using UnityEditor; using UnityEditor.Profiling; using UnityEditorInternal; using UnityEngine;
// This example assumes the profiled scene contained a component of MyScript.cs: /* using UnityEngine;
public class MyScript : MonoBehaviour { void Update() { MethodWithABoxingAllocation(); }
object MethodWithABoxingAllocation() { return 1; } }*/
public class Example : EditorWindow { const string k_MainThreadGroupName = ""; const string k_MainThreadName = "Main Thread"; const string k_GCAllocSampleName = "GC.Alloc"; // Profiler samples that were instrumented by Unity's Message Invoking mechanism, // e.g. Update(), Start(), FixedUpdate() ... get an " [Invoke]" postfix const string k_InvokePostFix = " [Invoke]"; public enum UpdateNameMatchType { Short, Full, }
ProfilerWindow m_Profiler = null; UpdateNameMatchType m_UpdateNameMatchType = UpdateNameMatchType.Short; bool m_UseMarkerNames = true;
string GetUpdateSampleName(UpdateNameMatchType updateNameMatchType, bool deepProfiling) { switch (updateNameMatchType) { case UpdateNameMatchType.Short: if (deepProfiling) return k_UpdateSampleNameShort; return k_UpdateSampleNameShort + k_InvokePostFix; case UpdateNameMatchType.Full: if (deepProfiling) return k_UpdateSampleNameFull;
return k_UpdateSampleNameFull + k_InvokePostFix; default: throw new NotImplementedException(); } }
const string k_UpdateSampleNameFull = "Assembly-CSharp.dll!::MyScript.Update()"; // Invoked method samples or samples instrumented via Deep Profiling will by default be shown // without their fully qualifying type name as above // Instead the Profiler UI will strip out everything before the '!::' part of their name. // SetSelection will still find these samples, if the UI is set to not Show Full Scripting Method Names // Note that RawFrameDataView and HierarchyFrameDataView will not be able to identify the Marker IDs // for such samples from this shorter name. const string k_UpdateSampleNameShort = "MyScript.Update()";
static readonly List<string> k_SampleNames = new List<string> { "PlayerLoop", "Update.ScriptRunBehaviourUpdate", "BehaviourUpdate", };
[MenuItem("Window/Analysis/Profiler Extension")] public static void ShowExampleWindow() { var window = GetWindow<Example>(); window.m_Profiler = EditorWindow.GetWindow<ProfilerWindow>(); }
void OnGUI() { // First make sure there is an open Profiler Window if (m_Profiler == null) m_Profiler = EditorWindow.GetWindow<ProfilerWindow>();
// For demonstration purposes, let the user choose if the names or if Marker IDs should be used. m_UseMarkerNames = GUILayout.Toggle(m_UseMarkerNames, "Use Marker names instead of IDs"); if (!m_UseMarkerNames) m_UpdateNameMatchType = UpdateNameMatchType.Full;
// Marker IDs need to be gotten from the fully qualified type name, so the shorter name is not an option when using IDs using (new EditorGUI.DisabledScope(!m_UseMarkerNames)) { // For demonstration purposes, let the user choose if the short or the long name should be used. m_UpdateNameMatchType = (UpdateNameMatchType)EditorGUILayout.EnumPopup(m_UpdateNameMatchType); }
// If the currently selected Module is not the CPU Usage module, setting the selection will not be visible to the user immediately if (m_Profiler.selectedModuleIdentifier == ProfilerWindow.cpuModuleIdentifier) { // Get the CPU Usage Profiler module's selection controller interface to interact with the selection var cpuSampleSelectionController = m_Profiler.GetFrameTimeViewSampleSelectionController(ProfilerWindow.cpuModuleIdentifier); // If the current selection object is null, there is no selection to print out. using (new EditorGUI.DisabledScope(m_Profiler.lastAvailableFrameIndex < 0)) { if (GUILayout.Button("Check my Script for GC.Alloc")) { string samplePath = ""; for (int i = 0; i < k_SampleNames.Count; i++) { samplePath += $"{k_SampleNames[i]}/"; }
var samplePathDeepProfiling = samplePath + $"{GetUpdateSampleName(m_UpdateNameMatchType, true)}/"; samplePath = samplePath + $"{GetUpdateSampleName(m_UpdateNameMatchType, false)}/"; // the sample we are looking for, without a trailing '/' samplePath += k_GCAllocSampleName; samplePathDeepProfiling += k_GCAllocSampleName;
// This check will fail in Deep Profiling because "MethodWithABoxingAllocation()" will be instrumented // and sitting in the sample stack between "Update()" and the "GC.Alloc". if (cpuSampleSelectionController.SetSelection(samplePath) || cpuSampleSelectionController.SetSelection(samplePathDeepProfiling)) { Debug.LogWarning("MyScript allocates in its Update loop"); } else { if (m_UseMarkerNames) { samplePath = ""; for (int i = 0; i < k_SampleNames.Count; i++) { samplePath += $"{k_SampleNames[i]}{ (i < k_SampleNames.Count - 1 ?"/": "")}"; } // SetSelection calls that take sample names as strings will find shortened scripting sample names var mySctiprSamplePathDeepProfiling = $"{samplePath}/{GetUpdateSampleName(m_UpdateNameMatchType, true)}"; var myScriptSamplePath = $"{samplePath}/{GetUpdateSampleName(m_UpdateNameMatchType, false)}";
if (cpuSampleSelectionController.SetSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, k_GCAllocSampleName, myScriptSamplePath) || cpuSampleSelectionController.SetSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, k_GCAllocSampleName, mySctiprSamplePathDeepProfiling)) { Debug.LogWarning("MyScript allocates in its Update loop"); } // MyScript did not have a GC.Alloc sample underneath it, but maybe a different Update sample allocated // Search through all Update() samples else if (cpuSampleSelectionController.SetSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, k_GCAllocSampleName, samplePath)) { Debug.LogWarning($"MyScript does not allocate but {cpuSampleSelectionController.selection.markerNamePath[k_SampleNames.Count]} allocates in its Update loop"); } else { Debug.Log("No Script is allocating in its Update Loop"); FindAnyGCAllocSample(cpuSampleSelectionController); } } else { List<int> markerIdPath = new List<int>(k_SampleNames.Count + 1); List<int> deepProfilingmarkerIdPath = new List<int>(k_SampleNames.Count + 1); int gcAllocMarkerId = FrameDataView.invalidMarkerId; using (var frameData = ProfilerDriver.GetRawFrameDataView((int)m_Profiler.selectedFrameIndex, 0)) { for (int i = 0; i < k_SampleNames.Count; i++) { markerIdPath.Add(frameData.GetMarkerId(k_SampleNames[i])); } deepProfilingmarkerIdPath.AddRange(markerIdPath); // GetMarkerId needs the full length marker name to be able to identify this sample. markerIdPath.Add(frameData.GetMarkerId(GetUpdateSampleName(UpdateNameMatchType.Full, false))); deepProfilingmarkerIdPath.Add(frameData.GetMarkerId(GetUpdateSampleName(UpdateNameMatchType.Full, true)));
gcAllocMarkerId = frameData.GetMarkerId(k_GCAllocSampleName); }
if (cpuSampleSelectionController.SetSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, gcAllocMarkerId, markerIdPath) || cpuSampleSelectionController.SetSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, gcAllocMarkerId, deepProfilingmarkerIdPath)) { Debug.LogWarning("MyScript allocates in its Update loop"); } else { // MyScript did not have a GC.Alloc sample underneath it, but maybe a different Update sample allocated // Remove the MyScript sample id from the path and search through all Update() samples markerIdPath.Remove(markerIdPath.Count - 1); if (cpuSampleSelectionController.SetSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, gcAllocMarkerId, markerIdPath)) { Debug.LogWarning($"MyScript does not allocate but {cpuSampleSelectionController.selection.markerNamePath[k_SampleNames.Count]} allocates in its Update loop"); return; } Debug.Log("No Script is allocating in its Update Loop"); FindAnyGCAllocSample(cpuSampleSelectionController); } } } } } } }
void FindAnyGCAllocSample(IProfilerFrameTimeViewSampleSelectionController cpuSampleSelectionController) { using (var frameData = ProfilerDriver.GetRawFrameDataView((int)m_Profiler.selectedFrameIndex, 0)) { var gcAllocMarkerId = frameData.GetMarkerId(k_GCAllocSampleName); for (int i = 0; i < frameData.sampleCount; i++) { if (frameData.GetSampleMarkerId(i) == gcAllocMarkerId) { var selection = new ProfilerTimeSampleSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, frameData.threadId, i); if (cpuSampleSelectionController.SetSelection(selection)) { // do not use the selection object here. The CPU Profiler Module created a new one. // Instead Get the selection object from the Profiler Debug.LogWarning($"MyScript does not allocate but {cpuSampleSelectionController.selection.markerNamePath[k_SampleNames.Count]} allocates in its Update loop"); return; } } } Debug.Log("No Script is allocating anything"); } } }
此示例显示了 SetSelection 的所有主要变体,并展示了它们之间的区别。