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 da89f5f736..16ebf9a4a0 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;
@@ -3116,6 +3119,10 @@ internal bool ShouldRunUpdate(InputUpdateType updateType)
             return (updateType & mask) != 0;
         }
 
+        struct UpdateMetrics
+        {
+        }
+
         /// 
         /// Process input events.
         /// 
@@ -3133,8 +3140,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).
@@ -3265,7 +3289,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;
@@ -3527,9 +3554,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)
@@ -3683,11 +3715,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 3adff080e5..4863441684 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 04e9d9d2b6..15c6bb5dbd 100644
--- a/Packages/manifest.json
+++ b/Packages/manifest.json
@@ -5,6 +5,7 @@
     "com.unity.coding": "0.1.0-preview.24",
     "com.unity.ide.rider": "3.0.31",
     "com.unity.ide.visualstudio": "2.0.22",
+    "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",