-
Notifications
You must be signed in to change notification settings - Fork 312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NEW: Input System Profiler Module #2038
base: develop
Are you sure you want to change the base?
Changes from 5 commits
86ac756
94afade
ba2588d
5429203
6f4c894
a3f8272
4046fad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
{ | ||
/// <summary> | ||
/// A profiler module that integrates Input System with the Profiler editor window. | ||
/// </summary> | ||
[ProfilerModuleMetadata("Input System")] | ||
internal sealed class InputSystemProfilerModule : ProfilerModule | ||
{ | ||
/// <summary> | ||
/// A profiler module detail view that extends the Profiler window and shows details for the selected frame. | ||
/// </summary> | ||
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(InputStatistics.UpdateCountName, InputStatistics.Category), | ||
new(InputStatistics.EventCountName, InputStatistics.Category), | ||
new(InputStatistics.EventSizeName, InputStatistics.Category), | ||
new(InputStatistics.StateBufferSizeBytesName, InputStatistics.Category), | ||
new(InputStatistics.AverageLatencyName, InputStatistics.Category), | ||
new(InputStatistics.MaxLatencyName, InputStatistics.Category), | ||
new(InputStatistics.EventProcessingTimeName, InputStatistics.Category), | ||
new(InputStatistics.DeviceCountName, InputStatistics.Category), | ||
new(InputStatistics.ControlCountName, InputStatistics.Category), | ||
}; | ||
|
||
public InputSystemProfilerModule() | ||
: base(Counters) | ||
{} | ||
|
||
public override ProfilerModuleViewController CreateDetailsViewController() | ||
{ | ||
return new InputSystemDetailsViewController(ProfilerWindow); | ||
} | ||
} | ||
} | ||
|
||
#endif // UNITY_EDITOR |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Process input events. | ||
/// </summary> | ||
|
@@ -3072,8 +3079,25 @@ internal bool ShouldRunUpdate(InputUpdateType updateType) | |
/// which buffers we activate in the update and write the event data into. | ||
/// </remarks> | ||
/// <exception cref="InvalidOperationException">Thrown if OnUpdate is called recursively.</exception> | ||
[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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs more work to extract some stuff from DoUpdate below |
||
} | ||
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? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this last line here is an anti pattern, ideally I would like to include the Update profiler marker into the profiler module, is this possible? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. During hackweek I used StopWatch. It was preallocated and reset and reused every frame and accumulated in a ProfilerCounter There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. which is what it looks like you're doing here :) |
||
|
||
m_InputEventStream.Close(ref eventBuffer); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
using Unity.Profiling; | ||
|
||
namespace UnityEngine.InputSystem | ||
{ | ||
/// <summary> | ||
/// Input Statistics for Unity Profiler integration. | ||
/// </summary> | ||
internal static class InputStatistics | ||
{ | ||
/// <summary> | ||
/// The Profiler Category to be used. | ||
/// </summary> | ||
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"; | ||
|
||
/// <summary> | ||
/// Counter reflecting the number of input events. | ||
/// </summary> | ||
/// <remarks> | ||
/// 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. | ||
/// </remarks> | ||
public static readonly ProfilerCounterValue<int> EventCount = new ProfilerCounterValue<int>( | ||
Category, EventCountName, ProfilerMarkerDataUnit.Count, | ||
ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush); | ||
|
||
/// <summary> | ||
/// Counter reflecting the accumulated input event size in bytes. | ||
/// </summary> | ||
/// <remarks> | ||
/// 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. | ||
/// </remarks> | ||
public static readonly ProfilerCounterValue<int> EventSize = new ProfilerCounterValue<int>( | ||
Category, EventSizeName, ProfilerMarkerDataUnit.Bytes, | ||
ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush); | ||
|
||
/// <summary> | ||
/// Counter value reflecting the average input latency. | ||
/// </summary> | ||
/// <remarks> | ||
/// 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. | ||
/// </remarks> | ||
public static readonly ProfilerCounterValue<double> AverageLatency = new ProfilerCounterValue<double>( | ||
Category, AverageLatencyName, ProfilerMarkerDataUnit.TimeNanoseconds, | ||
ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush); | ||
|
||
/// <summary> | ||
/// Counter value reflecting the maximum input latency. | ||
/// </summary> | ||
/// <remarks> | ||
/// 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. | ||
/// </remarks> | ||
public static readonly ProfilerCounterValue<double> MaxLatency = new ProfilerCounterValue<double>( | ||
Category, MaxLatencyName, ProfilerMarkerDataUnit.TimeNanoseconds, | ||
ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush); | ||
|
||
/// <summary> | ||
/// Counter value reflecting the accumulated event processing time (Update) during a rendering frame. | ||
/// </summary> | ||
/// <remarks> | ||
/// 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. | ||
/// </remarks> | ||
public static readonly ProfilerCounterValue<double> EventProcessingTime = new ProfilerCounterValue<double>( | ||
Category, EventProcessingTimeName, ProfilerMarkerDataUnit.TimeNanoseconds, | ||
ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush); | ||
|
||
/// <summary> | ||
/// The number of devices currently added to the Input System. | ||
/// </summary> | ||
public static readonly ProfilerCounter<int> DeviceCount = new ProfilerCounter<int>( | ||
Category, DeviceCountName, ProfilerMarkerDataUnit.Count); | ||
|
||
/// <summary> | ||
/// The total number of device controls currently in the Input System. | ||
/// </summary> | ||
public static readonly ProfilerCounter<int> ControlCount = new ProfilerCounter<int>( | ||
Category, ControlCountName, ProfilerMarkerDataUnit.Count); | ||
|
||
/// <summary> | ||
/// The total state buffer size in bytes. | ||
/// </summary> | ||
public static readonly ProfilerCounter<int> StateBufferSizeBytes = new ProfilerCounter<int>( | ||
Category, StateBufferSizeBytesName, ProfilerMarkerDataUnit.Bytes); | ||
|
||
/// <summary> | ||
/// The total update count. | ||
/// </summary> | ||
/// <remarks> | ||
/// Update may get called multiple times, e.g. either via manual updates, dynamic update, fixed update | ||
/// or editor update while running in the editor. | ||
/// </remarks> | ||
public static readonly ProfilerCounterValue<int> UpdateCount = new ProfilerCounterValue<int>( | ||
Category, UpdateCountName, ProfilerMarkerDataUnit.Count, | ||
ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a basic label list at the moment. Is it possible to pass any extra information via the profiler API? Or extract sample data to e.g. create a distribution within detail window?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a ProfilerRecorder that you can use to gather samples, which then allows you to do more statistical computations.