MemoryProfiler.TakeSnapshot
Declaration
public static void TakeSnapshot(string path, Action<string,bool> finishCallback, Unity.Profiling.Memory.CaptureFlags captureFlags);public static void TakeSnapshot(string path, Action<string,bool> finishCallback, Action<string,bool,DebugScreenCapture> screenshotCallback, Unity.Profiling.Memory.CaptureFlags captureFlags);Параметры
| Параметр | Описание |
|---|---|
| путь | Путь назначения для файла снимка памяти. |
| finishCallback | Событие, которое запускается после того, как снимок памяти завершил процесс захвата данных. Первый параметр обозначает, что файл снимка был успешно создан на вызывающем устройстве, второй параметр содержит путь к полученному снимку. |
| screenshotCallback | Событие, которое можно указать для получения скриншота после завершения снима. Первый параметр указывает, был ли файл скриншота успешно создан на вызывающем устройстве, второй содержит путь к полученному скриншоту, а третий — данные снима в виде DebugScreenCapture. |
| captureFlags | Маска флага, определяющая содержимое снимка памяти. |
Описание
Вызывает захват снимка памяти, чтобы получить снимок состояния памяти, который можно открыть и разобрать в Memory Profiler.
Не все поля, соответствующие флагам захвата, собираются. Это зависит от целевой сборки, используемой для захвата снимка. В частности, CaptureFlags.NativeAllocationSites и CaptureFlags.NativeStackTraces относятся к данным, которые могут быть собраны только из сборки, поддерживающей сбор информации о собственном стеке вызовов. Сбор информации о собственном стеке вызовов в настоящее время требует доступа к исходному коду. При предоставлении этих флагов для захвата из сборок, которые их не поддерживают, они игнорируются.
Параметр path относится к устройству, на котором выполняется вызов, но именно состояние подключения редактора или плеера, из которого вызывается этот метод, определяет, какой процесс будет захвачен и где будет создан файл снимка. В системе есть два разных варианта поведения при сборе данных:
* При вызове этого метода из проигрывателя, подключенного к редактору через PlayerConnection, файл не хранится локально, а передаётся в подключенный редактор. Указанный путь должен быть пустым, или вместо этого может быть вызван MemoryProfiler.TakeTempSnapshot. Булевое значение finishCallback будет false, даже если снимки были успешно сохранены в редакторе.
* Когда вы вызываете это из проигрывателя, который не подключен к редактору через PlayerConnection, файл создается на хост-устройстве проигрывателя.
* Когда вы вызываете это из редактора, файл создается на хост-устройстве редактора. Если редактор не подключен к проигрывателю через EditorConnection, он захватит проигрыватель, в противном случае он захватит редактор.
Сбор метаданных происходит непосредственно перед тем, как будет сделан снимок, если хотя бы один слушатель был зарегистрирован на событие MemoryProfiler.CreatingMetadata. В конце процесса запускается finishCallback. Если к вызову был предоставлен обратный вызов скриншота, это вызывает в конце текущего кадра.
Примечание: * Если предоставлен абсолютный путь, убедитесь, что приложение может записывать в этот путь. Если предоставлен только имя файла или относительный путь, снимок будет храниться в пути относительно Application.dataPath и API, как File.Open, может использоваться с одной и той же информацией относительного пути. * Слушатели события MemoryProfiler.CreatingMetadata уведомляются в Player или Editor, которые захватываются, что может быть не Editor, который вызвал этот метод, а подключенный Player вместо этого. * Screenshot callbacks вызываются только в автономных проигрывателях или в режиме Play. Если не предоставлены callbacks для screenshotCallback, то screenshot не будет сделан. * Рекомендуется вызывать это API из Coroutine, оставляя до тех пор, пока оба callbacks не будут вызваны, потому что процесс захвата блокирует исполнение кода на хост-устройстве, пока он захватывается и занимает время для выполнения. Вызов API из coroutine предотвращает заблокирование Editor захватов в это время. * Нет способа захватить режим Play в изоляции, только весь Editor. Использование памяти в Editor может резко отличаться от того, что в встроенном Player. Всегда убедитесь, что вы анализируете использование памяти в разработке Players, работающих на ваших целевых устройствах. * Вы можете только сделать следующий снимок после того, как finishCallback был вызван. * Используйте Пакет Memory Profiler для открытия и анализа полученных файлов.
using System; using System.Collections; using System.IO; using Unity.Collections.LowLevel.Unsafe; using Unity.Collections; using Unity.Profiling; using Unity.Profiling.Memory; using UnityEngine;
#if UNITY_EDITOR using UnityEditor; using UnityEditor.Networking.PlayerConnection; using UnityEngine.Networking.PlayerConnection; // Reguires com.unity.editorcoroutines package to be installed and its assembly referenced using Unity.EditorCoroutines.Editor; #endif
#if UNITY_EDITOR public class MemoryProfilerExampleWindow : EditorWindow { IConnectionState m_PlayerConnectionState; [MenuItem("Windows/Analysis/MemoryProfilerAPIExample")] static void InitializeOnLoad() { var window = EditorWindow.GetWindow<MemoryProfilerExampleWindow>(); window.m_PlayerConnectionState = PlayerConnectionGUIUtility.GetConnectionState(window); window.Show(); }
void TakeSnapshot() { // In the Unity Editor, the screenshot function only gets called when the not profiling Playmode or if Playmode is active. // In otherwords, capturing an Editor outside of Playmode does not create a screenshot and waiting for the callback would create and endless loop. var takeScreenshot = m_PlayerConnectionState.connectedToTarget == ConnectionTarget.Player || Application.isPlaying; EditorCoroutineUtility.StartCoroutine(MemoryProfilerExample.TakeSnapshot(takeScreenshot), this); } } #endif
public static class MemoryProfilerExample { public static IEnumerator TakeSnapshot(bool takeScreenshot) { var snapshotFileName = "SnapshotName.tmpsnap"; // Make sure the file does not exist, e.g. as a left over of a failed previous attempt to take a snapshot. if (File.Exists(snapshotFileName)) File.Delete(snapshotFileName);
var snapshotFinished = false; var screenshoFinished = false; string resultingSnapshotPath = null; string resultingScreenshotPath = null; var captureFlags = CaptureFlags.ManagedObjects | CaptureFlags.NativeObjects | CaptureFlags.NativeAllocations; Action<string, bool> snapshotCaptureFunction = (snapshotFilePath, success) => { snapshotFinished = true; if (success) { resultingSnapshotPath = Path.GetFullPath(snapshotFilePath); Debug.Log($"Snapshot captured and stored at {resultingSnapshotPath}."); } else { Debug.LogError("Failed to take a snapshot."); } }; Action<string, bool, DebugScreenCapture> screenshotCaptureFunction = (screenshotFilePath, success, screenshotData) => { screenshoFinished = true; if (!success || screenshotData.RawImageDataReference.Length == 0) return;
// Note: for the Memory Profiler to be able to pick up the screenshot, the name and path needs to match that of the snapshot file, safe for the extension. // The path provided by the callback is based on the path provided to TakeSnapshot. if (Path.HasExtension(screenshotFilePath)) { screenshotFilePath = Path.ChangeExtension(screenshotFilePath, ".tmppng"); }
var texture = new Texture2D(screenshotData.Width, screenshotData.Height, screenshotData.ImageFormat, false, false); CopyDataToTexture(texture, screenshotData.RawImageDataReference); File.WriteAllBytes(screenshotFilePath, texture.EncodeToPNG()); if (Application.isPlaying) UnityEngine.Object.Destroy(texture); else UnityEngine.Object.DestroyImmediate(texture); resultingScreenshotPath = screenshotFilePath; };
if (takeScreenshot) MemoryProfiler.TakeSnapshot(snapshotFileName, snapshotCaptureFunction, screenshotCaptureFunction, captureFlags); else MemoryProfiler.TakeSnapshot(snapshotFileName, snapshotCaptureFunction, captureFlags);
// The finishCallback is called first. while (!snapshotFinished) { yield return null; } // The screenshotCallback is called second and can take a moment longer, // but don't wait for it if it is not being taken. while (takeScreenshot && !screenshoFinished) { yield return null; }
if (resultingSnapshotPath != null && File.Exists(resultingSnapshotPath)) { var finalSnapshotPath = Path.ChangeExtension(resultingSnapshotPath, ".snap"); var finalScreenshotPath = resultingScreenshotPath != null ? Path.ChangeExtension(resultingScreenshotPath, ".png") : null;
// Remove any pre-existing files first. if (File.Exists(finalSnapshotPath)) File.Delete(finalSnapshotPath); if (finalScreenshotPath != null && File.Exists(finalScreenshotPath)) File.Delete(finalScreenshotPath);
// Now that writing to the file has succesfully completed, rename the file to the .snap extension to denote that the Memory Profiler can open it. File.Move(resultingSnapshotPath, finalSnapshotPath); if (finalScreenshotPath != null) File.Move(resultingScreenshotPath, finalScreenshotPath);
// If you don't have access to the Player's file system you could also upload the file to an end-point that is accessible to you here. } }
static void CopyDataToTexture(Texture2D tex, NativeArray<byte> byteArray) { unsafe { void* srcPtr = NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(byteArray); void* dstPtr = tex.GetRawTextureData<byte>().GetUnsafeReadOnlyPtr(); UnsafeUtility.MemCpy(dstPtr, srcPtr, byteArray.Length * sizeof(byte)); } } }
Дополнительные ресурсы: Память Profiler пакет, Application.dataPath, DebugScreenCapture, MemoryProfiler.CreatingMetadata, PlayerConnectionGUIUtility.GetConnectionState, EditorCoroutineUtility.