diff --git a/ATxCommon/ATxCommon.csproj b/ATxCommon/ATxCommon.csproj
index 96b7ac83d3d63bb003ea2210b47566ca1f5342fd..d281301c0b8684077a38c1f8a74b267414ed8809 100644
--- a/ATxCommon/ATxCommon.csproj
+++ b/ATxCommon/ATxCommon.csproj
@@ -47,6 +47,7 @@
     <Compile Include="BuildDetails.cs" />
     <Compile Include="Conv.cs" />
     <Compile Include="FsUtils.cs" />
+    <Compile Include="Monitoring\Cpu.cs" />
     <Compile Include="NLog\RateLimitWrapper.cs" />
     <Compile Include="Serializables\DriveToCheck.cs" />
     <Compile Include="Serializables\ServiceConfig.cs" />
diff --git a/ATxCommon/Monitoring/Cpu.cs b/ATxCommon/Monitoring/Cpu.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c0272ee12d3961aac3b544b1bdfb4b0421f04f2d
--- /dev/null
+++ b/ATxCommon/Monitoring/Cpu.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Timers;
+using NLog;
+
+namespace ATxCommon.Monitoring
+{
+    public class Cpu
+    {
+        private static readonly Logger Log = LogManager.GetCurrentClassLogger();
+
+        private readonly Timer _monitoringTimer;
+        private readonly PerformanceCounter _cpuCounter;
+        private readonly float[] _loadReadings = {0F, 0F, 0F, 0F};
+        private float _load;
+
+        public float Load() => _load;
+
+        public Cpu() {
+            Log.Debug("Initializing CPU monitoring...");
+            try {
+                _cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
+                _monitoringTimer = new Timer(250);
+                _monitoringTimer.Elapsed += UpdateCpuLoad;
+                _monitoringTimer.Enabled = true;
+            }
+            catch (Exception ex) {
+                Log.Error("Initializing CPU monitoring completed ({1}): {0}", ex.Message, ex.GetType());
+                throw;
+            }
+
+            Log.Debug("Initializing CPU monitoring completed.");
+        }
+
+        private void UpdateCpuLoad(object sender, ElapsedEventArgs e) {
+            _monitoringTimer.Enabled = false;
+            try {
+                Log.Trace("load values: {0}, average: {1}", string.Join(", ", _loadReadings), _load);
+                // ConstrainedCopy seems to be the most efficient approach to shift the array:
+                Array.ConstrainedCopy(_loadReadings, 1, _loadReadings, 0, 3);
+                _loadReadings[3] = _cpuCounter.NextValue();
+                _load = _loadReadings.Average();
+                Log.Trace("load values: {0}, average: {1}", string.Join(", ", _loadReadings), _load);
+            }
+            catch (Exception ex) {
+                Log.Error("UpdateCpuLoad failed: {0}", ex.Message);
+            }
+            finally {
+                _monitoringTimer.Enabled = true;
+            }
+        }
+
+    }
+}