diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Internal/InputSystemProfilerModule.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Internal/InputSystemProfilerModule.cs
new file mode 100644
index 0000000000..ee1a39fee0
--- /dev/null
+++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Internal/InputSystemProfilerModule.cs
@@ -0,0 +1,148 @@
+#if UNITY_EDITOR // Input System currently do not have proper asmdef for editor code.
+
+using Unity.Profiling.Editor;
+using UnityEditor;
+using UnityEditorInternal;
+using UnityEngine.UIElements;
+
+namespace UnityEngine.InputSystem.Editor
+{
+ ///
+ /// A profiler module that integrates Input System with the Profiler editor window.
+ ///
+ [ProfilerModuleMetadata("Input System")]
+ internal sealed class InputSystemProfilerModule : ProfilerModule
+ {
+ ///
+ /// A profiler module detail view that extends the Profiler window and shows details for the selected frame.
+ ///
+ private sealed class InputSystemDetailsViewController : ProfilerModuleViewController
+ {
+ public InputSystemDetailsViewController(ProfilerWindow profilerWindow)
+ : base(profilerWindow)
+ {}
+
+ private Label m_UpdateCountLabel;
+ private Label m_EventCountLabel;
+ private Label m_EventSizeLabel;
+ private Label m_AverageLatencyLabel;
+ private Label m_MaxLatencyLabel;
+ private Label m_EventProcessingTimeLabel;
+ private Label m_DeviceCountLabel;
+ private Label m_ControlCountLabel;
+ private Label m_StateBufferSizeLabel;
+
+ private Label CreateLabel()
+ {
+ return new Label() { style = { paddingTop = 8, paddingLeft = 8 } };
+ }
+
+ protected override VisualElement CreateView()
+ {
+ var view = new VisualElement();
+
+ m_UpdateCountLabel = CreateLabel();
+ m_EventCountLabel = CreateLabel();
+ m_EventSizeLabel = CreateLabel();
+ m_AverageLatencyLabel = CreateLabel();
+ m_MaxLatencyLabel = CreateLabel();
+ m_EventProcessingTimeLabel = CreateLabel();
+ m_DeviceCountLabel = CreateLabel();
+ m_ControlCountLabel = CreateLabel();
+ m_StateBufferSizeLabel = CreateLabel();
+
+ view.Add(m_UpdateCountLabel);
+ view.Add(m_EventCountLabel);
+ view.Add(m_EventSizeLabel);
+ view.Add(m_AverageLatencyLabel);
+ view.Add(m_MaxLatencyLabel);
+ view.Add(m_EventProcessingTimeLabel);
+ view.Add(m_DeviceCountLabel);
+ view.Add(m_ControlCountLabel);
+ view.Add(m_StateBufferSizeLabel);
+
+ // Populate the label with the current data for the selected frame.
+ ReloadData();
+
+ // Be notified when the selected frame index in the Profiler Window changes, so we can update the label.
+ ProfilerWindow.SelectedFrameIndexChanged += OnSelectedFrameIndexChanged;
+
+ return view;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ // Unsubscribe from the Profiler window event that we previously subscribed to.
+ ProfilerWindow.SelectedFrameIndexChanged -= OnSelectedFrameIndexChanged;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ void ReloadData()
+ {
+ var selectedFrameIndex = System.Convert.ToInt32(ProfilerWindow.selectedFrameIndex);
+
+ var updateCount = ProfilerDriver.GetFormattedCounterValue(selectedFrameIndex,
+ InputStatistics.Category.Name, InputStatistics.UpdateCountName);
+ var eventCount = ProfilerDriver.GetFormattedCounterValue(selectedFrameIndex,
+ InputStatistics.Category.Name, InputStatistics.EventCountName);
+ var eventSizeBytes = ProfilerDriver.GetFormattedCounterValue(selectedFrameIndex,
+ InputStatistics.Category.Name, InputStatistics.EventSizeName);
+ var averageLatency = ProfilerDriver.GetFormattedCounterValue(selectedFrameIndex,
+ InputStatistics.Category.Name, InputStatistics.AverageLatencyName);
+ var maxLatency = ProfilerDriver.GetFormattedCounterValue(selectedFrameIndex,
+ InputStatistics.Category.Name, InputStatistics.MaxLatencyName);
+ var eventProcessingTime = ProfilerDriver.GetFormattedCounterValue(selectedFrameIndex,
+ InputStatistics.Category.Name, InputStatistics.EventProcessingTimeName);
+ var stateBufferSizeBytes = ProfilerDriver.GetFormattedCounterValue(selectedFrameIndex,
+ InputStatistics.Category.Name, InputStatistics.StateBufferSizeBytesName);
+ var deviceCount = ProfilerDriver.GetFormattedCounterValue(selectedFrameIndex,
+ InputStatistics.Category.Name, InputStatistics.DeviceCountName);
+ var controlCount = ProfilerDriver.GetFormattedCounterValue(selectedFrameIndex,
+ InputStatistics.Category.Name, InputStatistics.ControlCountName);
+
+ m_UpdateCountLabel.text = $"{InputStatistics.UpdateCountName}: {updateCount}";
+ m_EventCountLabel.text = $"{InputStatistics.EventCountName}: {eventCount}";
+ m_EventSizeLabel.text = $"{InputStatistics.EventSizeName}: {eventSizeBytes}";
+ m_AverageLatencyLabel.text = $"{InputStatistics.AverageLatencyName}: {averageLatency}";
+ m_MaxLatencyLabel.text = $"{InputStatistics.MaxLatencyName}: {maxLatency}";
+ m_EventProcessingTimeLabel.text = $"{InputStatistics.EventProcessingTimeName}: {eventProcessingTime}";
+ m_StateBufferSizeLabel.text = $"{InputStatistics.StateBufferSizeBytesName}: {stateBufferSizeBytes}";
+ m_DeviceCountLabel.text = $"{InputStatistics.DeviceCountName}: {deviceCount}";
+ m_ControlCountLabel.text = $"{InputStatistics.ControlCountName}: {controlCount}";
+ }
+
+ void OnSelectedFrameIndexChanged(long selectedFrameIndex)
+ {
+ ReloadData();
+ }
+ }
+
+ private static readonly ProfilerCounterDescriptor[] Counters = new ProfilerCounterDescriptor[]
+ {
+ new ProfilerCounterDescriptor(InputStatistics.UpdateCountName, InputStatistics.Category),
+ new ProfilerCounterDescriptor(InputStatistics.EventCountName, InputStatistics.Category),
+ new ProfilerCounterDescriptor(InputStatistics.EventSizeName, InputStatistics.Category),
+ new ProfilerCounterDescriptor(InputStatistics.StateBufferSizeBytesName, InputStatistics.Category),
+ new ProfilerCounterDescriptor(InputStatistics.AverageLatencyName, InputStatistics.Category),
+ new ProfilerCounterDescriptor(InputStatistics.MaxLatencyName, InputStatistics.Category),
+ new ProfilerCounterDescriptor(InputStatistics.EventProcessingTimeName, InputStatistics.Category),
+ new ProfilerCounterDescriptor(InputStatistics.DeviceCountName, InputStatistics.Category),
+ new ProfilerCounterDescriptor(InputStatistics.ControlCountName, InputStatistics.Category),
+ };
+
+ public InputSystemProfilerModule()
+ : base(Counters)
+ {}
+
+ public override ProfilerModuleViewController CreateDetailsViewController()
+ {
+ return new InputSystemDetailsViewController(ProfilerWindow);
+ }
+ }
+}
+
+#endif // UNITY_EDITOR
diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Internal/InputSystemProfilerModule.cs.meta b/Packages/com.unity.inputsystem/InputSystem/Editor/Internal/InputSystemProfilerModule.cs.meta
new file mode 100644
index 0000000000..87f697b3f2
--- /dev/null
+++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Internal/InputSystemProfilerModule.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 820e154f9c3b47a2b7a50c6680dd4aed
+timeCreated: 1729795357
\ No newline at end of file
diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs
index 10e3c17fb8..5b3504db2f 100644
--- a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs
+++ b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs
@@ -80,6 +80,13 @@ internal partial class InputManager
static readonly ProfilerMarker k_InputOnDeviceChangeMarker = new ProfilerMarker("InpustSystem.onDeviceChange");
static readonly ProfilerMarker k_InputOnActionsChangeMarker = new ProfilerMarker("InpustSystem.onActionsChange");
+ private int CountControls()
+ {
+ var count = m_DevicesCount;
+ for (var i = 0; i < m_DevicesCount; ++i)
+ count += m_Devices[i].allControls.Count;
+ return count;
+ }
public InputMetrics metrics
{
@@ -89,11 +96,7 @@ public InputMetrics metrics
result.currentNumDevices = m_DevicesCount;
result.currentStateSizeInBytes = (int)m_StateBuffers.totalSize;
-
- // Count controls.
- result.currentControlCount = m_DevicesCount;
- for (var i = 0; i < m_DevicesCount; ++i)
- result.currentControlCount += m_Devices[i].allControls.Count;
+ result.currentControlCount = CountControls();
// Count layouts.
result.currentLayoutCount = m_Layouts.layoutTypes.Count;
@@ -3055,6 +3058,10 @@ internal bool ShouldRunUpdate(InputUpdateType updateType)
return (updateType & mask) != 0;
}
+ struct UpdateMetrics
+ {
+ }
+
///
/// Process input events.
///
@@ -3072,8 +3079,25 @@ internal bool ShouldRunUpdate(InputUpdateType updateType)
/// which buffers we activate in the update and write the event data into.
///
/// Thrown if OnUpdate is called recursively.
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals", Justification = "TODO: Refactor later.")]
private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer eventBuffer)
+ {
+ try
+ {
+ DoUpdate(updateType, ref eventBuffer);
+ }
+ finally
+ {
+ // According to documentation, profile counter calls should be stripped out automatically in
+ // non-development builds.
+ InputStatistics.DeviceCount.Sample(m_DevicesCount);
+ InputStatistics.StateBufferSizeBytes.Sample((int)m_StateBuffers.totalSize);
+ InputStatistics.ControlCount.Sample(CountControls());
+ ++InputStatistics.UpdateCount.Value;
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals", Justification = "TODO: Refactor later.")]
+ private unsafe void DoUpdate(InputUpdateType updateType, ref InputEventBuffer eventBuffer)
{
// NOTE: This is *not* using try/finally as we've seen unreliability in the EndSample()
// execution (and we're not sure where it's coming from).
@@ -3204,7 +3228,10 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev
}
var processingStartTime = Stopwatch.GetTimestamp();
+ var totalEventCount = 0;
+ var totalEventSizeBytes = 0;
var totalEventLag = 0.0;
+ var maxEventLag = 0.0;
#if UNITY_EDITOR
var isPlaying = gameIsPlaying;
@@ -3465,9 +3492,14 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev
// Update metrics.
if (currentEventTimeInternal <= currentTime)
- totalEventLag += currentTime - currentEventTimeInternal;
- ++m_Metrics.totalEventCount;
- m_Metrics.totalEventBytes += (int)currentEventReadPtr->sizeInBytes;
+ {
+ var lag = currentTime - currentEventTimeInternal;
+ totalEventLag += lag;
+ if (lag > maxEventLag)
+ maxEventLag = lag;
+ }
+ ++totalEventCount;
+ totalEventSizeBytes += (int)currentEventReadPtr->sizeInBytes;
// Process.
switch (currentEventType)
@@ -3621,11 +3653,22 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev
break;
}
- m_Metrics.totalEventProcessingTime +=
+ ResetCurrentProcessedEventBytesForDevices();
+
+ // Update metrics (exposed via analytics and debugger)
+ var eventProcessingTime =
((double)(Stopwatch.GetTimestamp() - processingStartTime)) / Stopwatch.Frequency;
+ m_Metrics.totalEventCount += totalEventCount;
+ m_Metrics.totalEventBytes += totalEventSizeBytes;
+ m_Metrics.totalEventProcessingTime += eventProcessingTime;
m_Metrics.totalEventLagTime += totalEventLag;
- ResetCurrentProcessedEventBytesForDevices();
+ // Profiler counters
+ InputStatistics.EventCount.Value += totalEventCount;
+ InputStatistics.EventSize.Value += totalEventSizeBytes;
+ InputStatistics.AverageLatency.Value += ((totalEventLag / totalEventCount) * 1e9);
+ InputStatistics.MaxLatency.Value += (maxEventLag * 1e9);
+ InputStatistics.EventProcessingTime.Value += eventProcessingTime * 1e9; // TODO Possible to replace Stopwatch with marker somehow?
m_InputEventStream.Close(ref eventBuffer);
}
diff --git a/Packages/com.unity.inputsystem/InputSystem/Unity.InputSystem.asmdef b/Packages/com.unity.inputsystem/InputSystem/Unity.InputSystem.asmdef
index 6bc8cc3e6a..b2c9545c97 100644
--- a/Packages/com.unity.inputsystem/InputSystem/Unity.InputSystem.asmdef
+++ b/Packages/com.unity.inputsystem/InputSystem/Unity.InputSystem.asmdef
@@ -2,7 +2,8 @@
"name": "Unity.InputSystem",
"rootNamespace": "",
"references": [
- "Unity.ugui"
+ "Unity.ugui",
+ "Unity.Profiling.Core"
],
"includePlatforms": [],
"excludePlatforms": [],
diff --git a/Packages/com.unity.inputsystem/InputSystem/Utilities/InputStatistics.cs b/Packages/com.unity.inputsystem/InputSystem/Utilities/InputStatistics.cs
new file mode 100644
index 0000000000..4d7220718c
--- /dev/null
+++ b/Packages/com.unity.inputsystem/InputSystem/Utilities/InputStatistics.cs
@@ -0,0 +1,115 @@
+using Unity.Profiling;
+
+namespace UnityEngine.InputSystem
+{
+ ///
+ /// Input Statistics for Unity Profiler integration.
+ ///
+ internal static class InputStatistics
+ {
+ ///
+ /// The Profiler Category to be used.
+ ///
+ internal static readonly ProfilerCategory Category = ProfilerCategory.Input;
+
+ internal const string EventCountName = "Total Input Event Count";
+ internal const string EventSizeName = "Total Input Event Size";
+ internal const string AverageLatencyName = "Average Input Latency";
+ internal const string MaxLatencyName = "Max Input Latency";
+ internal const string EventProcessingTimeName = "Total Input Event Processing Time";
+ internal const string DeviceCountName = "Input Device Count";
+ internal const string ControlCountName = "Active Control Count";
+ internal const string CurrentStateMemoryBytesName = "Current State Memory Bytes";
+ internal const string StateBufferSizeBytesName = "Total State Buffer Size";
+ internal const string UpdateCountName = "Update Count";
+
+ ///
+ /// Counter reflecting the number of input events.
+ ///
+ ///
+ /// We use ProfilerCounterValue instead of ProfilerCounter since there may be multiple Input System updates
+ /// per frame and we want it to accumulate for the profilers perspective on what a frame is but auto-reset
+ /// when outside the profilers perspective of a frame.
+ ///
+ public static readonly ProfilerCounterValue EventCount = new ProfilerCounterValue(
+ Category, EventCountName, ProfilerMarkerDataUnit.Count,
+ ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush);
+
+ ///
+ /// Counter reflecting the accumulated input event size in bytes.
+ ///
+ ///
+ /// We use ProfilerCounterValue instead of ProfilerCounter since there may be multiple Input System updates
+ /// per frame and we want it to accumulate for the profilers perspective on what a frame is but auto-reset
+ /// when outside the profilers perspective of a frame.
+ ///
+ public static readonly ProfilerCounterValue EventSize = new ProfilerCounterValue(
+ Category, EventSizeName, ProfilerMarkerDataUnit.Bytes,
+ ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush);
+
+ ///
+ /// Counter value reflecting the average input latency.
+ ///
+ ///
+ /// We use ProfilerCounterValue instead of ProfilerCounter since there may be multiple Input System updates
+ /// per frame and we want it to accumulate for the profilers perspective on what a frame is but auto-reset
+ /// when outside the profilers perspective of a frame.
+ ///
+ public static readonly ProfilerCounterValue AverageLatency = new ProfilerCounterValue(
+ Category, AverageLatencyName, ProfilerMarkerDataUnit.TimeNanoseconds,
+ ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush);
+
+ ///
+ /// Counter value reflecting the maximum input latency.
+ ///
+ ///
+ /// We use ProfilerCounterValue instead of ProfilerCounter since there may be multiple Input System updates
+ /// per frame and we want it to accumulate for the profilers perspective on what a frame is but auto-reset
+ /// when outside the profilers perspective of a frame.
+ ///
+ public static readonly ProfilerCounterValue MaxLatency = new ProfilerCounterValue(
+ Category, MaxLatencyName, ProfilerMarkerDataUnit.TimeNanoseconds,
+ ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush);
+
+ ///
+ /// Counter value reflecting the accumulated event processing time (Update) during a rendering frame.
+ ///
+ ///
+ /// We use ProfilerCounterValue instead of ProfilerCounter since there may be multiple Input System updates
+ /// per frame and we want it to accumulate for the profilers perspective on what a frame is but auto-reset
+ /// when outside the profilers perspective of a frame.
+ ///
+ public static readonly ProfilerCounterValue EventProcessingTime = new ProfilerCounterValue(
+ Category, EventProcessingTimeName, ProfilerMarkerDataUnit.TimeNanoseconds,
+ ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush);
+
+ ///
+ /// The number of devices currently added to the Input System.
+ ///
+ public static readonly ProfilerCounter DeviceCount = new ProfilerCounter(
+ Category, DeviceCountName, ProfilerMarkerDataUnit.Count);
+
+ ///
+ /// The total number of device controls currently in the Input System.
+ ///
+ public static readonly ProfilerCounter ControlCount = new ProfilerCounter(
+ Category, ControlCountName, ProfilerMarkerDataUnit.Count);
+
+ ///
+ /// The total state buffer size in bytes.
+ ///
+ public static readonly ProfilerCounter StateBufferSizeBytes = new ProfilerCounter(
+ Category, StateBufferSizeBytesName, ProfilerMarkerDataUnit.Bytes);
+
+ ///
+ /// The total update count.
+ ///
+ ///
+ /// Update may get called multiple times, e.g. either via manual updates, dynamic update, fixed update
+ /// or editor update while running in the editor.
+ ///
+ public static readonly ProfilerCounterValue UpdateCount = new ProfilerCounterValue(
+ Category, UpdateCountName, ProfilerMarkerDataUnit.Count,
+ ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush);
+ }
+}
diff --git a/Packages/com.unity.inputsystem/InputSystem/Utilities/InputStatistics.cs.meta b/Packages/com.unity.inputsystem/InputSystem/Utilities/InputStatistics.cs.meta
new file mode 100644
index 0000000000..d48433f422
--- /dev/null
+++ b/Packages/com.unity.inputsystem/InputSystem/Utilities/InputStatistics.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 2656d8b72fb540fd836a3bc0c02f8e20
+timeCreated: 1729795584
\ No newline at end of file
diff --git a/Packages/manifest.json b/Packages/manifest.json
index 4acf434955..1e558fda9a 100644
--- a/Packages/manifest.json
+++ b/Packages/manifest.json
@@ -5,12 +5,12 @@
"com.unity.coding": "0.1.0-preview.24",
"com.unity.ide.rider": "1.2.1",
"com.unity.ide.visualstudio": "2.0.15",
+ "com.unity.profiling.core": "1.0.2",
"com.unity.settings-manager": "1.0.3",
"com.unity.test-framework": "1.4.5",
"com.unity.test-framework.build": "0.0.1-preview.12",
"com.unity.test-framework.performance": "3.0.3",
"com.unity.test-framework.utp-reporter": "1.1.0-preview",
- "com.unity.textmeshpro": "2.1.4",
"com.unity.ugui": "1.0.0",
"nuget.mono-cecil": "0.1.6-preview",
"com.unity.modules.ai": "1.0.0",