diff --git a/LibreHardwareMonitor.Windows.WinUI/Services/HardwareMonitorService.cs b/LibreHardwareMonitor.Windows.WinUI/Services/HardwareMonitorService.cs index ea1eb5c..9933c7a 100644 --- a/LibreHardwareMonitor.Windows.WinUI/Services/HardwareMonitorService.cs +++ b/LibreHardwareMonitor.Windows.WinUI/Services/HardwareMonitorService.cs @@ -17,6 +17,7 @@ public sealed class HardwareMonitorService : IDisposable { private const string DeferDimmDetectionSetting = "memory.deferDimmDetection"; private const string DeferCpuInitialUpdateSetting = "cpu.deferInitialUpdate"; + private const string DeferTscEstimationSetting = "cpu.deferTscEstimation"; private const string DeferNetworkDetectionSetting = "network.deferDetection"; private const string DeferNvidiaDetectionSetting = "nvidia.deferDetection"; private const string DeferStorageDetectionSetting = "storage.deferDetection"; @@ -212,6 +213,9 @@ public sealed class HardwareMonitorService : IDisposable if (!Settings.Contains(DeferCpuInitialUpdateSetting)) Settings.SetValue(DeferCpuInitialUpdateSetting, true); + if (!Settings.Contains(DeferTscEstimationSetting)) + Settings.SetValue(DeferTscEstimationSetting, true); + if (!Settings.Contains(DeferNvidiaDetectionSetting)) Settings.SetValue(DeferNvidiaDetectionSetting, true); diff --git a/LibreHardwareMonitorLib/Hardware/Cpu/GenericCpu.cs b/LibreHardwareMonitorLib/Hardware/Cpu/GenericCpu.cs index 6b8e715..730ecdc 100644 --- a/LibreHardwareMonitorLib/Hardware/Cpu/GenericCpu.cs +++ b/LibreHardwareMonitorLib/Hardware/Cpu/GenericCpu.cs @@ -9,11 +9,15 @@ using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text; +using System.Threading.Tasks; namespace LibreHardwareMonitor.Hardware.Cpu; public class GenericCpu : Hardware { + private const string DeferTscEstimationEnvironmentVariable = "LHM_CPU_DEFER_TSC_ESTIMATION"; + private const string DeferTscEstimationSetting = "cpu.deferTscEstimation"; + protected readonly int _coreCount; protected readonly CpuId[][] _cpuId; protected readonly uint _family; @@ -23,16 +27,19 @@ public class GenericCpu : Hardware protected readonly int _threadCount; private readonly CpuLoad _cpuLoad; - private readonly double _estimatedTimeStampCounterFrequency; - private readonly double _estimatedTimeStampCounterFrequencyError; + private double _estimatedTimeStampCounterFrequency; + private double _estimatedTimeStampCounterFrequencyError; private readonly bool _isInvariantTimeStampCounter; private readonly Sensor[] _threadLoads; private readonly Sensor _totalLoad; private readonly Sensor _maxLoad; private readonly Vendor _vendor; + private readonly object _timeStampCounterFrequencyLock = new(); private long _lastTime; private ulong _lastTimeStampCount; + private double _timeStampCounterFrequency; + private Task _timeStampCounterFrequencyTask; public GenericCpu(int processorIndex, CpuId[][] cpuId, ISettings settings) : this(processorIndex, cpuId, settings, null) @@ -96,23 +103,29 @@ public class GenericCpu : Hardware if (HasTimeStampCounter) { - GroupAffinity previousAffinity = ThreadAffinity.Set(cpuId[0][0].Affinity); - double estimatedTimeStampCounterFrequency = 0; - double estimatedTimeStampCounterFrequencyError = 0; - Measure(startupTrace, - "GenericCpu.EstimateTimeStampCounterFrequency", - () => EstimateTimeStampCounterFrequency(out estimatedTimeStampCounterFrequency, out estimatedTimeStampCounterFrequencyError)); - _estimatedTimeStampCounterFrequency = estimatedTimeStampCounterFrequency; - _estimatedTimeStampCounterFrequencyError = estimatedTimeStampCounterFrequencyError; - ThreadAffinity.Set(previousAffinity); + GroupAffinity affinity = cpuId[0][0].Affinity; + if (ShouldDeferTscEstimation(settings)) + { + // The estimate is only a seed for the first Update() (~1 s out) and is then self-corrected from real + // TSC deltas, so the ~25 ms measurement window can run off the startup critical path. + startupTrace?.Skip("GenericCpu.EstimateTimeStampCounterFrequency", "Deferred to background."); + _timeStampCounterFrequencyTask = Task.Run(() => + { + try + { + EstimateAndStoreTimeStampCounterFrequency(affinity, null); + } + catch (Exception ex) + { + Debug.WriteLine($"Deferred TSC frequency estimation failed: {ex}"); + } + }); + } + else + { + EstimateAndStoreTimeStampCounterFrequency(affinity, startupTrace); + } } - else - { - _estimatedTimeStampCounterFrequency = 0; - _estimatedTimeStampCounterFrequencyError = 0; - } - - TimeStampCounterFrequency = _estimatedTimeStampCounterFrequency; } /// @@ -131,7 +144,19 @@ public class GenericCpu : Hardware /// public int Index { get; } - public double TimeStampCounterFrequency { get; private set; } + public double TimeStampCounterFrequency + { + get + { + lock (_timeStampCounterFrequencyLock) + return _timeStampCounterFrequency; + } + private set + { + lock (_timeStampCounterFrequencyLock) + _timeStampCounterFrequency = value; + } + } protected string CoreString(int i) { @@ -216,10 +241,65 @@ public class GenericCpu : Hardware error = beginError + endError; } + private void EstimateAndStoreTimeStampCounterFrequency(GroupAffinity affinity, HardwareStartupTrace startupTrace) + { + GroupAffinity previousAffinity = ThreadAffinity.Set(affinity); + try + { + double frequency = 0; + double error = 0; + Measure(startupTrace, + "GenericCpu.EstimateTimeStampCounterFrequency", + () => EstimateTimeStampCounterFrequency(out frequency, out error)); + StoreTimeStampCounterFrequency(frequency, error); + } + finally + { + ThreadAffinity.Set(previousAffinity); + } + } + + private void StoreTimeStampCounterFrequency(double frequency, double error) + { + lock (_timeStampCounterFrequencyLock) + { + _estimatedTimeStampCounterFrequency = frequency; + _estimatedTimeStampCounterFrequencyError = error; + _timeStampCounterFrequency = frequency; + } + } + + private static bool ShouldDeferTscEstimation(ISettings settings) + { + string environmentValue = Environment.GetEnvironmentVariable(DeferTscEstimationEnvironmentVariable); + if (!string.IsNullOrWhiteSpace(environmentValue)) + return IsTruthy(environmentValue); + + return IsTruthy(settings.GetValue(DeferTscEstimationSetting, "false")); + } + + private static bool IsTruthy(string value) + { + return value.Equals("1", StringComparison.OrdinalIgnoreCase) + || value.Equals("true", StringComparison.OrdinalIgnoreCase) + || value.Equals("yes", StringComparison.OrdinalIgnoreCase) + || value.Equals("on", StringComparison.OrdinalIgnoreCase); + } + public override string GetReport() { StringBuilder r = new(); + double estimatedTimeStampCounterFrequency; + double estimatedTimeStampCounterFrequencyError; + double timeStampCounterFrequency; + + lock (_timeStampCounterFrequencyLock) + { + estimatedTimeStampCounterFrequency = _estimatedTimeStampCounterFrequency; + estimatedTimeStampCounterFrequencyError = _estimatedTimeStampCounterFrequencyError; + timeStampCounterFrequency = _timeStampCounterFrequency; + } switch (_vendor) { @@ -240,17 +320,31 @@ public class GenericCpu : Hardware r.AppendFormat("Threads per Core: {0}{1}", _cpuId[0].Length, Environment.NewLine); r.AppendLine(string.Format(CultureInfo.InvariantCulture, "Timer Frequency: {0} MHz", Stopwatch.Frequency * 1e-6)); r.AppendLine("Time Stamp Counter: " + (HasTimeStampCounter ? _isInvariantTimeStampCounter ? "Invariant" : "Not Invariant" : "None")); - r.AppendLine(string.Format(CultureInfo.InvariantCulture, "Estimated Time Stamp Counter Frequency: {0} MHz", Math.Round(_estimatedTimeStampCounterFrequency * 100) * 0.01)); + r.AppendLine(string.Format(CultureInfo.InvariantCulture, "Estimated Time Stamp Counter Frequency: {0} MHz", Math.Round(estimatedTimeStampCounterFrequency * 100) * 0.01)); r.AppendLine(string.Format(CultureInfo.InvariantCulture, "Estimated Time Stamp Counter Frequency Error: {0} Mhz", - Math.Round(_estimatedTimeStampCounterFrequency * _estimatedTimeStampCounterFrequencyError * 1e5) * 1e-5)); + Math.Round(estimatedTimeStampCounterFrequency * estimatedTimeStampCounterFrequencyError * 1e5) * 1e-5)); - r.AppendLine(string.Format(CultureInfo.InvariantCulture, "Time Stamp Counter Frequency: {0} MHz", Math.Round(TimeStampCounterFrequency * 100) * 0.01)); + r.AppendLine(string.Format(CultureInfo.InvariantCulture, "Time Stamp Counter Frequency: {0} MHz", Math.Round(timeStampCounterFrequency * 100) * 0.01)); r.AppendLine(); return r.ToString(); } + public override void Close() + { + try + { + _timeStampCounterFrequencyTask?.Wait(TimeSpan.FromMilliseconds(100)); + } + catch (AggregateException ex) + { + Debug.WriteLine($"Deferred TSC frequency estimation failed while closing: {ex}"); + } + + base.Close(); + } + public override void Update() { if (HasTimeStampCounter && _isInvariantTimeStampCounter)