using System; using System.Collections.Generic; using Unity.Pipeline.Commands; using Unity.Pipeline.Runtime.Telemetry; using UnityEngine; using UnityEngine.Profiling; namespace Unity.Pipeline.Runtime.Commands { /// /// Runtime status information for Unity Player builds. /// Provides Player-appropriate status without Editor-specific APIs. /// public static class RuntimeStatusCommand { [CliCommand("runtime_status", "Get comprehensive runtime application status", MainThreadRequired = true, RuntimeOnly = true)] public static RuntimeStatusResponse GetRuntimeStatus() { try { return new RuntimeStatusResponse { // Application info UnityVersion = Application.unityVersion, Platform = Application.platform.ToString(), BuildGuid = Application.buildGUID, Version = Application.version, // Runtime state IsPlaying = true, // Always true in Player builds TargetFrameRate = Application.targetFrameRate, TimeScale = Time.timeScale, RealTimeSinceStartup = Time.realtimeSinceStartup, // Performance info FrameCount = Time.frameCount, LoadedLevelName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name, LoadedLevelBuildIndex = UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex, // Live performance telemetry (fps, frame time, memory, profiler counters) Performance = BuildPerformanceTelemetry(), // Timestamp Timestamp = DateTime.UtcNow }; } catch (Exception ex) { Debug.LogError($"Pipeline: Runtime status command failed: {ex.Message}"); throw; } } /// /// Assemble the live performance section from two independent sources: /// the per-frame (fps / frame time / profiler counters), which only /// exists while a is feeding it, and the Profiler memory APIs, /// which can be read on demand at any time. Frame-stat fields stay at their defaults (and /// stays false) when no sampler is /// running — e.g. in EditMode tests — so a missing sampler degrades gracefully rather than throwing. /// private static RuntimePerformanceTelemetry BuildPerformanceTelemetry() { var perf = new RuntimePerformanceTelemetry(); var sampler = FrameStatsSampler.Shared; if (sampler != null) { var snap = sampler.GetSnapshot(); perf.FrameStatsAvailable = snap.Available; perf.SampleWindow = snap.SampleWindow; perf.Fps = snap.Fps; perf.AverageFrameTimeMs = snap.AverageFrameTimeMs; perf.MinFrameTimeMs = snap.MinFrameTimeMs; perf.MaxFrameTimeMs = snap.MaxFrameTimeMs; perf.LastFrameTimeMs = snap.LastFrameTimeMs; perf.GpuTimingAvailable = snap.GpuTimingAvailable; perf.CpuFrameTimeMs = snap.CpuFrameTimeMs; perf.GpuFrameTimeMs = snap.GpuFrameTimeMs; perf.Counters = snap.Counters; } else { perf.Counters = new Dictionary(); } // Memory is sampled directly (no per-frame accumulation needed). These return 0 in a // non-development build where the profiler is stripped, which is reported faithfully. perf.TotalAllocatedMemory = Profiler.GetTotalAllocatedMemoryLong(); perf.TotalReservedMemory = Profiler.GetTotalReservedMemoryLong(); perf.MonoUsedSize = Profiler.GetMonoUsedSizeLong(); perf.MonoHeapSize = Profiler.GetMonoHeapSizeLong(); perf.GcMemory = GC.GetTotalMemory(false); return perf; } } /// /// Response model for runtime status information. /// [Serializable] public class RuntimeStatusResponse { // Application Information public string UnityVersion { get; set; } public string Platform { get; set; } public string BuildGuid { get; set; } public string Version { get; set; } // Runtime State public bool IsPlaying { get; set; } // Always true for Player builds public int TargetFrameRate { get; set; } public float TimeScale { get; set; } public float RealTimeSinceStartup { get; set; } // Performance Information public int FrameCount { get; set; } public string LoadedLevelName { get; set; } public int LoadedLevelBuildIndex { get; set; } // Live performance telemetry public RuntimePerformanceTelemetry Performance { get; set; } // Metadata public DateTime Timestamp { get; set; } } /// /// Live performance telemetry for a running Player: rolling fps / frame-time statistics, optional /// CPU/GPU frame timing, memory usage, and a map of profiler counters. Frame-stat and counter fields /// are populated from the per-frame ; memory fields are read on demand. /// [Serializable] public class RuntimePerformanceTelemetry { // Frame timing — sampled over a rolling window by the RuntimePipelineManager. /// False when no sampler is running; the fps / frame-time fields below are then meaningless. public bool FrameStatsAvailable { get; set; } public int SampleWindow { get; set; } public float Fps { get; set; } public float AverageFrameTimeMs { get; set; } public float MinFrameTimeMs { get; set; } public float MaxFrameTimeMs { get; set; } public float LastFrameTimeMs { get; set; } // CPU/GPU frame timing via FrameTimingManager (not available on every platform). public bool GpuTimingAvailable { get; set; } public double CpuFrameTimeMs { get; set; } public double GpuFrameTimeMs { get; set; } // Memory (bytes) via the Profiler API; 0 when the profiler is stripped (non-development build). public long TotalAllocatedMemory { get; set; } public long TotalReservedMemory { get; set; } public long MonoUsedSize { get; set; } public long MonoHeapSize { get; set; } public long GcMemory { get; set; } // Profiler counters by reported name (e.g. DrawCalls, Triangles, GcAllocInFrame). public Dictionary Counters { get; set; } } }