WIP - optimizing start times

This commit is contained in:
2026-05-30 15:11:32 -05:00
parent 45bf81ebfa
commit b744f70b0b
8 changed files with 795 additions and 98 deletions
@@ -3,7 +3,9 @@
// Copyright (C) LibreHardwareMonitor and Contributors.
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using LibreHardwareMonitor.Hardware;
using LibreHardwareMonitor.Hardware.Storage;
@@ -13,13 +15,21 @@ namespace LibreHardwareMonitor.Windows.WinUI.Services;
public sealed class HardwareMonitorService : IDisposable
{
private const string DeferDimmDetectionSetting = "memory.deferDimmDetection";
private const string DeferCpuInitialUpdateSetting = "cpu.deferInitialUpdate";
private const string DeferNetworkDetectionSetting = "network.deferDetection";
private const string DeferNvidiaDetectionSetting = "nvidia.deferDetection";
private const string DeferStorageDetectionSetting = "storage.deferDetection";
private readonly object _updateLock = new();
private readonly UpdateVisitor _updateVisitor = new();
private bool _isOpen;
private int _treeRebuildQueued;
public HardwareMonitorService(AppSettings settings)
{
Settings = settings;
ApplyWinUiHardwareDefaults();
Computer = new Computer(settings);
Computer.HardwareAdded += HardwareChanged;
Computer.HardwareRemoved += HardwareChanged;
@@ -181,6 +191,24 @@ public sealed class HardwareMonitorService : IDisposable
ForceDriveWakeup = Settings.GetValue("forceDriveWakeupItem", false);
}
private void ApplyWinUiHardwareDefaults()
{
if (!Settings.Contains(DeferDimmDetectionSetting))
Settings.SetValue(DeferDimmDetectionSetting, true);
if (!Settings.Contains(DeferCpuInitialUpdateSetting))
Settings.SetValue(DeferCpuInitialUpdateSetting, true);
if (!Settings.Contains(DeferNvidiaDetectionSetting))
Settings.SetValue(DeferNvidiaDetectionSetting, true);
if (!Settings.Contains(DeferStorageDetectionSetting))
Settings.SetValue(DeferStorageDetectionSetting, true);
if (!Settings.Contains(DeferNetworkDetectionSetting))
Settings.SetValue(DeferNetworkDetectionSetting, true);
}
private void SetHardwareEnabled(string settingName, bool value, Action<bool> setter)
{
setter(value);
@@ -192,6 +220,31 @@ public sealed class HardwareMonitorService : IDisposable
private void HardwareChanged(IHardware hardware)
{
if (_isOpen)
RebuildTree();
QueueTreeRebuild();
}
private void QueueTreeRebuild()
{
if (Interlocked.Exchange(ref _treeRebuildQueued, 1) == 1)
return;
_ = Task.Run(async () =>
{
try
{
await Task.Delay(100).ConfigureAwait(false);
if (_isOpen)
RebuildTree();
}
catch (Exception ex)
{
Debug.WriteLine($"Hardware tree rebuild failed: {ex}");
}
finally
{
Interlocked.Exchange(ref _treeRebuildQueued, 0);
}
});
}
}
@@ -6,11 +6,13 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using LibreHardwareMonitor.Hardware;
using LibreHardwareMonitor.Hardware.Storage;
using LibreHardwareMonitor.Windows.WinUI.Services;
using LibreHardwareMonitor.Windows.WinUI.Utilities;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Windows.UI;
@@ -73,6 +75,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
];
private readonly HardwareMonitorService _hardwareMonitor;
private readonly DispatcherQueue _dispatcherQueue;
private readonly Logger _logger;
private readonly Dictionary<string, PlotSeriesViewModel> _plotSeriesByIdentifier = new();
private readonly RemoteWebServer _remoteWebServer;
@@ -91,6 +94,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
private bool _throttleAtaUpdate;
private bool _isStarted;
private bool _isStarting;
private int _rootUpdateQueued;
private string _statusText = "";
private TemperatureUnit _temperatureUnit;
private int _updateIntervalIndex;
@@ -98,6 +102,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
public MainWindowViewModel(AppSettings settings)
{
Settings = settings;
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_hardwareMonitor = new HardwareMonitorService(settings);
_logger = new Logger(_hardwareMonitor.Computer);
_remoteWebServer = new RemoteWebServer(
@@ -703,7 +708,31 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
private void HardwareMonitor_TreeRebuilt(object? sender, EventArgs e)
{
UpdateRoot();
if (_dispatcherQueue.HasThreadAccess)
{
UpdateRoot();
UpdateStatus();
return;
}
if (Interlocked.Exchange(ref _rootUpdateQueued, 1) == 1)
return;
if (!_dispatcherQueue.TryEnqueue(() =>
{
try
{
UpdateRoot();
UpdateStatus();
}
finally
{
Interlocked.Exchange(ref _rootUpdateQueued, 0);
}
}))
{
Interlocked.Exchange(ref _rootUpdateQueued, 0);
}
}
private void ApplySensorValuesTimeWindow()
+175 -28
View File
@@ -9,6 +9,8 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using LibreHardwareMonitor.Hardware.Battery;
using LibreHardwareMonitor.Hardware.Controller.AeroCool;
using LibreHardwareMonitor.Hardware.Controller.AquaComputer;
@@ -35,6 +37,13 @@ namespace LibreHardwareMonitor.Hardware;
/// </summary>
public class Computer : IComputer
{
private const string DeferNetworkDetectionEnvironmentVariable = "LHM_NETWORK_DEFER_DETECTION";
private const string DeferNetworkDetectionSetting = "network.deferDetection";
private const string DeferNvidiaDetectionEnvironmentVariable = "LHM_NVIDIA_DEFER_DETECTION";
private const string DeferNvidiaDetectionSetting = "nvidia.deferDetection";
private const string DeferStorageDetectionEnvironmentVariable = "LHM_STORAGE_DEFER_DETECTION";
private const string DeferStorageDetectionSetting = "storage.deferDetection";
private readonly List<IGroup> _groups = new();
private readonly object _lock = new();
private readonly ISettings _settings;
@@ -51,6 +60,7 @@ public class Computer : IComputer
private bool _psuEnabled;
private SMBios _smbios;
private bool _storageEnabled;
private CancellationTokenSource _deferredGroupCancellationTokenSource;
/// <summary>
/// Creates a new <see cref="IComputer" /> instance with basic initial <see cref="Settings" />.
@@ -510,67 +520,201 @@ public class Computer : IComputer
if (_open)
return;
_smbios = new SMBios();
StartDeferredGroupRun();
if (Software.OperatingSystem.IsWindows8OrGreater)
Mutexes.Open();
try
{
using HardwareStartupTrace startupTrace = HardwareStartupTrace.Create(_settings);
OpCode.Open();
_smbios = Measure(startupTrace, "SMBios", () => new SMBios());
AddGroups();
if (Software.OperatingSystem.IsWindows8OrGreater)
Measure(startupTrace, "Mutexes.Open", Mutexes.Open);
else
startupTrace?.Skip("Mutexes.Open", "Operating system is older than Windows 8.");
_open = true;
Measure(startupTrace, "OpCode.Open", OpCode.Open);
AddGroups(startupTrace);
_open = true;
}
catch
{
CancelDeferredGroupRun();
throw;
}
}
private void AddGroups()
private void AddGroups(HardwareStartupTrace startupTrace)
{
if (_motherboardEnabled)
Add(new MotherboardGroup(_smbios, _settings));
AddMeasuredGroup(startupTrace, "MotherboardGroup", () => new MotherboardGroup(_smbios, _settings));
if (_cpuEnabled)
Add(new CpuGroup(_settings));
AddMeasuredGroup(startupTrace, "CpuGroup", () => new CpuGroup(_settings, startupTrace));
if (_memoryEnabled)
Add(new MemoryGroup(_settings));
AddMeasuredGroup(startupTrace, "MemoryGroup", () => new MemoryGroup(_settings, startupTrace));
if (_gpuEnabled)
{
Add(new AmdGpuGroup(_settings));
Add(new NvidiaGroup(_settings));
AddMeasuredGroup(startupTrace, "AmdGpuGroup", () => new AmdGpuGroup(_settings));
AddMeasuredGroup(startupTrace,
"NvidiaGroup",
() => new NvidiaGroup(_settings),
() => _gpuEnabled,
DeferNvidiaDetectionSetting,
DeferNvidiaDetectionEnvironmentVariable);
if (_cpuEnabled)
Add(new IntelGpuGroup(GetIntelCpus(), _settings));
AddMeasuredGroup(startupTrace, "IntelGpuGroup", () => new IntelGpuGroup(GetIntelCpus(), _settings));
}
if (_powerMonitorEnabled)
Add(new PowerMonitorGroup(_settings));
AddMeasuredGroup(startupTrace, "PowerMonitorGroup", () => new PowerMonitorGroup(_settings));
if (_controllerEnabled)
{
Add(new TBalancerGroup(_settings));
Add(new HeatmasterGroup(_settings));
Add(new AquaComputerGroup(_settings));
Add(new AeroCoolGroup(_settings));
Add(new NzxtGroup(_settings));
Add(new RazerGroup(_settings));
Add(new ArcticGroup(_settings));
Add(new MsiGroup(_settings));
AddMeasuredGroup(startupTrace, "TBalancerGroup", () => new TBalancerGroup(_settings));
AddMeasuredGroup(startupTrace, "HeatmasterGroup", () => new HeatmasterGroup(_settings));
AddMeasuredGroup(startupTrace, "AquaComputerGroup", () => new AquaComputerGroup(_settings));
AddMeasuredGroup(startupTrace, "AeroCoolGroup", () => new AeroCoolGroup(_settings));
AddMeasuredGroup(startupTrace, "NzxtGroup", () => new NzxtGroup(_settings));
AddMeasuredGroup(startupTrace, "RazerGroup", () => new RazerGroup(_settings));
AddMeasuredGroup(startupTrace, "ArcticGroup", () => new ArcticGroup(_settings));
AddMeasuredGroup(startupTrace, "MsiGroup", () => new MsiGroup(_settings));
}
if (_storageEnabled)
Add(new StorageGroup(_settings));
AddMeasuredGroup(startupTrace,
"StorageGroup",
() => new StorageGroup(_settings),
() => _storageEnabled,
DeferStorageDetectionSetting,
DeferStorageDetectionEnvironmentVariable);
if (_networkEnabled)
Add(new NetworkGroup(_settings));
AddMeasuredGroup(startupTrace,
"NetworkGroup",
() => new NetworkGroup(_settings),
() => _networkEnabled,
DeferNetworkDetectionSetting,
DeferNetworkDetectionEnvironmentVariable);
if (_psuEnabled)
{
Add(new CorsairPsuGroup(_settings));
Add(new MsiPsuGroup(_settings));
AddMeasuredGroup(startupTrace, "CorsairPsuGroup", () => new CorsairPsuGroup(_settings));
AddMeasuredGroup(startupTrace, "MsiPsuGroup", () => new MsiPsuGroup(_settings));
}
if (_batteryEnabled)
Add(new BatteryGroup(_settings));
AddMeasuredGroup(startupTrace, "BatteryGroup", () => new BatteryGroup(_settings));
}
private static void Measure(HardwareStartupTrace startupTrace, string phase, Action action)
{
if (startupTrace != null)
startupTrace.Measure(phase, action);
else
action();
}
private static T Measure<T>(HardwareStartupTrace startupTrace, string phase, Func<T> action)
{
return startupTrace != null ? startupTrace.Measure(phase, action) : action();
}
private void AddMeasuredGroup(HardwareStartupTrace startupTrace, string phase, Func<IGroup> createGroup)
{
Add(Measure(startupTrace, phase, createGroup));
}
private void AddMeasuredGroup(HardwareStartupTrace startupTrace, string phase, Func<IGroup> createGroup, Func<bool> isEnabled, string deferSettingName, string deferEnvironmentVariable)
{
if (!ShouldDeferDetection(deferSettingName, deferEnvironmentVariable))
{
AddMeasuredGroup(startupTrace, phase, createGroup);
return;
}
startupTrace?.Skip(phase, "Deferred to background.");
AddDeferredGroup(createGroup, isEnabled);
}
private void AddDeferredGroup(Func<IGroup> createGroup, Func<bool> isEnabled)
{
CancellationTokenSource cancellationTokenSource = _deferredGroupCancellationTokenSource;
if (cancellationTokenSource == null)
{
if (isEnabled())
Add(createGroup());
return;
}
CancellationToken cancellationToken = cancellationTokenSource.Token;
Task.Run(() =>
{
IGroup group = null;
try
{
group = createGroup();
}
catch (Exception ex)
{
if (!cancellationToken.IsCancellationRequested)
System.Diagnostics.Debug.WriteLine($"Deferred hardware detection failed: {ex}");
return;
}
if (group == null)
return;
if (cancellationToken.IsCancellationRequested || !isEnabled())
{
group.Close();
return;
}
Add(group);
});
}
private bool ShouldDeferDetection(string settingName, string environmentVariable)
{
string environmentValue = Environment.GetEnvironmentVariable(environmentVariable);
if (!string.IsNullOrWhiteSpace(environmentValue))
return IsTruthy(environmentValue);
return IsTruthy(_settings.GetValue(settingName, "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);
}
private void StartDeferredGroupRun()
{
CancelDeferredGroupRun();
_deferredGroupCancellationTokenSource = new CancellationTokenSource();
}
private void CancelDeferredGroupRun()
{
CancellationTokenSource cancellationTokenSource = _deferredGroupCancellationTokenSource;
_deferredGroupCancellationTokenSource = null;
if (cancellationTokenSource == null)
return;
cancellationTokenSource.Cancel();
cancellationTokenSource.Dispose();
}
private static void NewSection(TextWriter writer)
@@ -655,6 +799,8 @@ public class Computer : IComputer
if (!_open)
return;
CancelDeferredGroupRun();
lock (_lock)
{
while (_groups.Count > 0)
@@ -679,8 +825,9 @@ public class Computer : IComputer
if (!_open)
return;
StartDeferredGroupRun();
RemoveGroups();
AddGroups();
AddGroups(null);
}
private void RemoveGroups()
+102 -38
View File
@@ -1,7 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// Partial Copyright (C) Michael M�ller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
@@ -17,8 +17,15 @@ internal class CpuGroup : IGroup
private readonly CpuId[][][] _threads;
public CpuGroup(ISettings settings)
: this(settings, null)
{ }
internal CpuGroup(ISettings settings, HardwareStartupTrace startupTrace)
{
CpuId[][] processorThreads = GetProcessorThreads();
CpuId[][] processorThreads = Measure(startupTrace,
"CpuGroup.GetProcessorThreads",
GetProcessorThreads,
DescribeProcessorThreads);
_threads = new CpuId[processorThreads.Length][][];
int index = 0;
@@ -27,44 +34,18 @@ internal class CpuGroup : IGroup
if (threads.Length == 0)
continue;
CpuId[][] coreThreads = GroupThreadsByCore(threads);
CpuId[][] coreThreads = Measure(startupTrace,
$"CpuGroup.Processor{index}.GroupThreadsByCore",
() => GroupThreadsByCore(threads),
cores => $"{cores.Length} core(s), {threads.Length} thread(s)");
_threads[index] = coreThreads;
switch (threads[0].Vendor)
{
case Vendor.Intel:
_hardware.Add(new IntelCpu(index, coreThreads, settings));
break;
case Vendor.AMD:
switch (threads[0].Family)
{
case 0x0F:
_hardware.Add(new Amd0FCpu(index, coreThreads, settings));
break;
case 0x10:
case 0x11:
case 0x12:
case 0x14:
case 0x15:
case 0x16:
// TODO: https://github.com/namazso/PawnIO.Modules/issues/32
//_hardware.Add(new Amd10Cpu(index, coreThreads, settings));
break;
case 0x17:
case 0x19:
case 0x1A:
_hardware.Add(new Amd17Cpu(index, coreThreads, settings));
break;
default:
_hardware.Add(new GenericCpu(index, coreThreads, settings));
break;
}
break;
default:
_hardware.Add(new GenericCpu(index, coreThreads, settings));
break;
}
GenericCpu cpu = Measure(startupTrace,
$"CpuGroup.Processor{index}.{GetCpuConstructorName(threads[0])}",
() => CreateCpu(index, coreThreads, settings, startupTrace),
DescribeCpu);
if (cpu != null)
_hardware.Add(cpu);
index++;
}
@@ -123,6 +104,88 @@ internal class CpuGroup : IGroup
}
}
private static GenericCpu CreateCpu(int index, CpuId[][] coreThreads, ISettings settings, HardwareStartupTrace startupTrace)
{
CpuId thread = coreThreads[0][0];
switch (thread.Vendor)
{
case Vendor.Intel:
return new IntelCpu(index, coreThreads, settings, startupTrace);
case Vendor.AMD:
switch (thread.Family)
{
case 0x0F:
return new Amd0FCpu(index, coreThreads, settings);
case 0x10:
case 0x11:
case 0x12:
case 0x14:
case 0x15:
case 0x16:
// TODO: https://github.com/namazso/PawnIO.Modules/issues/32
return null;
case 0x17:
case 0x19:
case 0x1A:
return new Amd17Cpu(index, coreThreads, settings);
default:
return new GenericCpu(index, coreThreads, settings);
}
default:
return new GenericCpu(index, coreThreads, settings);
}
}
private static string GetCpuConstructorName(CpuId thread)
{
switch (thread.Vendor)
{
case Vendor.Intel:
return nameof(IntelCpu);
case Vendor.AMD:
switch (thread.Family)
{
case 0x0F:
return nameof(Amd0FCpu);
case 0x10:
case 0x11:
case 0x12:
case 0x14:
case 0x15:
case 0x16:
return "UnsupportedAmdCpu";
case 0x17:
case 0x19:
case 0x1A:
return nameof(Amd17Cpu);
default:
return nameof(GenericCpu);
}
default:
return nameof(GenericCpu);
}
}
private static string DescribeProcessorThreads(CpuId[][] processorThreads)
{
int threadCount = 0;
foreach (CpuId[] threads in processorThreads)
threadCount += threads.Length;
return $"{processorThreads.Length} processor(s), {threadCount} thread(s)";
}
private static string DescribeCpu(GenericCpu cpu)
{
return cpu != null ? $"{cpu.Name}, {cpu.Sensors.Length} sensor(s)" : "Unsupported CPU family";
}
private static T Measure<T>(HardwareStartupTrace startupTrace, string phase, Func<T> action, Func<T, string> getDetail)
{
return startupTrace != null ? startupTrace.Measure(phase, action, getDetail) : action();
}
private static CpuId[][] GetProcessorThreads()
{
List<CpuId> threads = new();
@@ -210,3 +273,4 @@ internal class CpuGroup : IGroup
}
}
}
@@ -34,7 +34,11 @@ public class GenericCpu : Hardware
private long _lastTime;
private ulong _lastTimeStampCount;
public GenericCpu(int processorIndex, CpuId[][] cpuId, ISettings settings) : base(cpuId[0][0].Name, CreateIdentifier(cpuId[0][0].Vendor, processorIndex), settings)
public GenericCpu(int processorIndex, CpuId[][] cpuId, ISettings settings)
: this(processorIndex, cpuId, settings, null)
{ }
internal GenericCpu(int processorIndex, CpuId[][] cpuId, ISettings settings, HardwareStartupTrace startupTrace) : base(cpuId[0][0].Name, CreateIdentifier(cpuId[0][0].Vendor, processorIndex), settings)
{
_cpuId = cpuId;
_vendor = cpuId[0][0].Vendor;
@@ -59,7 +63,7 @@ public class GenericCpu : Hardware
_totalLoad = _coreCount > 1 ? new Sensor("CPU Total", 0, SensorType.Load, this, settings) : null;
_maxLoad = _coreCount > 1 ? new Sensor("CPU Core Max", 1, SensorType.Load, this, settings) : null;
_cpuLoad = new CpuLoad(cpuId);
_cpuLoad = Measure(startupTrace, "GenericCpu.CpuLoad", () => new CpuLoad(cpuId), load => load.IsAvailable ? "Available" : "Unavailable");
if (_cpuLoad.IsAvailable)
{
_threadLoads = new Sensor[_threadCount];
@@ -93,12 +97,19 @@ public class GenericCpu : Hardware
if (HasTimeStampCounter)
{
GroupAffinity previousAffinity = ThreadAffinity.Set(cpuId[0][0].Affinity);
EstimateTimeStampCounterFrequency(out _estimatedTimeStampCounterFrequency, out _estimatedTimeStampCounterFrequencyError);
double estimatedTimeStampCounterFrequency = 0;
double estimatedTimeStampCounterFrequencyError = 0;
Measure(startupTrace,
"GenericCpu.EstimateTimeStampCounterFrequency",
() => EstimateTimeStampCounterFrequency(out estimatedTimeStampCounterFrequency, out estimatedTimeStampCounterFrequencyError));
_estimatedTimeStampCounterFrequency = estimatedTimeStampCounterFrequency;
_estimatedTimeStampCounterFrequencyError = estimatedTimeStampCounterFrequencyError;
ThreadAffinity.Set(previousAffinity);
}
else
{
_estimatedTimeStampCounterFrequency = 0;
_estimatedTimeStampCounterFrequencyError = 0;
}
TimeStampCounterFrequency = _estimatedTimeStampCounterFrequency;
@@ -142,6 +153,19 @@ public class GenericCpu : Hardware
return new Identifier(s, processorIndex.ToString(CultureInfo.InvariantCulture));
}
private static void Measure(HardwareStartupTrace startupTrace, string phase, Action action)
{
if (startupTrace != null)
startupTrace.Measure(phase, action);
else
action();
}
private static T Measure<T>(HardwareStartupTrace startupTrace, string phase, Func<T> action, Func<T, string> getDetail)
{
return startupTrace != null ? startupTrace.Measure(phase, action, getDetail) : action();
}
private static void EstimateTimeStampCounterFrequency(out double frequency, out double error)
{
// preload the function
@@ -14,6 +14,9 @@ namespace LibreHardwareMonitor.Hardware.Cpu;
internal sealed class IntelCpu : GenericCpu
{
private const string DeferInitialUpdateEnvironmentVariable = "LHM_CPU_DEFER_INITIAL_UPDATE";
private const string DeferInitialUpdateSetting = "cpu.deferInitialUpdate";
private readonly Sensor _busClock;
private readonly Sensor _coreAvg;
private readonly Sensor[] _coreClocks;
@@ -36,12 +39,16 @@ internal sealed class IntelCpu : GenericCpu
private readonly IntelMsr _pawnModule;
public IntelCpu(int processorIndex, CpuId[][] cpuId, ISettings settings) : base(processorIndex, cpuId, settings)
public IntelCpu(int processorIndex, CpuId[][] cpuId, ISettings settings)
: this(processorIndex, cpuId, settings, null)
{ }
internal IntelCpu(int processorIndex, CpuId[][] cpuId, ISettings settings, HardwareStartupTrace startupTrace) : base(processorIndex, cpuId, settings, startupTrace)
{
if (Software.OperatingSystem.IsUnix)
return;
_pawnModule = new IntelMsr();
_pawnModule = Measure(startupTrace, "IntelCpu.CreateMsrModule", () => new IntelMsr());
uint eax;
@@ -527,7 +534,17 @@ internal sealed class IntelCpu : GenericCpu
ActivateSensor(_coreVIDs[i]);
}
Update();
if (ShouldDeferInitialUpdate(settings))
{
if (HasTimeStampCounter && _timeStampCounterMultiplier > 0)
ActivateSensor(_busClock);
startupTrace?.Skip("IntelCpu.InitialUpdate", "Deferred until first regular update.");
}
else
{
Measure(startupTrace, "IntelCpu.InitialUpdate", Update);
}
}
public float EnergyUnitsMultiplier { get; }
@@ -555,6 +572,36 @@ internal sealed class IntelCpu : GenericCpu
return result;
}
private static void Measure(HardwareStartupTrace startupTrace, string phase, Action action)
{
if (startupTrace != null)
startupTrace.Measure(phase, action);
else
action();
}
private static T Measure<T>(HardwareStartupTrace startupTrace, string phase, Func<T> action)
{
return startupTrace != null ? startupTrace.Measure(phase, action) : action();
}
private static bool ShouldDeferInitialUpdate(ISettings settings)
{
string environmentValue = Environment.GetEnvironmentVariable(DeferInitialUpdateEnvironmentVariable);
if (!string.IsNullOrWhiteSpace(environmentValue))
return IsTruthy(environmentValue);
return IsTruthy(settings.GetValue(DeferInitialUpdateSetting, "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();
@@ -0,0 +1,240 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
namespace LibreHardwareMonitor.Hardware;
internal sealed class HardwareStartupTrace : IDisposable
{
private const string EnabledEnvironmentVariable = "LHM_HARDWARE_STARTUP_TIMING";
private const string PathEnvironmentVariable = "LHM_HARDWARE_STARTUP_TIMING_PATH";
private const string EnabledSetting = "diagnostics.hardwareStartupTiming";
private const string PathSetting = "diagnostics.hardwareStartupTimingPath";
private readonly List<Entry> _entries = new();
private readonly string _fileName;
private readonly Stopwatch _totalStopwatch;
private bool _disposed;
private HardwareStartupTrace(ISettings settings)
{
_fileName = GetLogFileName(settings);
_totalStopwatch = Stopwatch.StartNew();
}
public static HardwareStartupTrace Create(ISettings settings)
{
if (!IsEnabled(settings))
return null;
return new HardwareStartupTrace(settings);
}
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
_totalStopwatch.Stop();
WriteLog();
}
public T Measure<T>(string phase, Func<T> action)
{
return Measure(phase, action, null);
}
public T Measure<T>(string phase, Func<T> action, Func<T, string> getDetail)
{
Stopwatch stopwatch = Stopwatch.StartNew();
try
{
T result = action();
stopwatch.Stop();
AddEntry(phase, stopwatch.Elapsed, "OK", GetHardwareCount(result), GetDetail(result, getDetail));
return result;
}
catch (Exception ex)
{
stopwatch.Stop();
AddEntry(phase, stopwatch.Elapsed, "ERROR", null, $"{ex.GetType().FullName}: {ex.Message}");
throw;
}
}
public void Measure(string phase, Action action)
{
Stopwatch stopwatch = Stopwatch.StartNew();
try
{
action();
stopwatch.Stop();
AddEntry(phase, stopwatch.Elapsed, "OK", null, "");
}
catch (Exception ex)
{
stopwatch.Stop();
AddEntry(phase, stopwatch.Elapsed, "ERROR", null, $"{ex.GetType().FullName}: {ex.Message}");
throw;
}
}
public void Skip(string phase, string reason)
{
AddEntry(phase, TimeSpan.Zero, "SKIPPED", null, reason);
}
private static bool IsEnabled(ISettings settings)
{
string settingValue = settings.GetValue(EnabledSetting, "false");
string environmentValue = Environment.GetEnvironmentVariable(EnabledEnvironmentVariable) ?? "";
return IsTruthy(settingValue) || IsTruthy(environmentValue);
}
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);
}
private static string GetLogFileName(ISettings settings)
{
string configuredPath = Environment.GetEnvironmentVariable(PathEnvironmentVariable);
if (string.IsNullOrWhiteSpace(configuredPath))
configuredPath = settings.GetValue(PathSetting, "");
string fileName = $"LibreHardwareMonitor.HardwareStartupTiming-{DateTime.Now:yyyyMMdd-HHmmss-fff}.log";
if (string.IsNullOrWhiteSpace(configuredPath))
return Path.Combine(AppContext.BaseDirectory, fileName);
if (configuredPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)
|| configuredPath.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal)
|| Directory.Exists(configuredPath))
{
return Path.Combine(configuredPath, fileName);
}
return configuredPath;
}
private static int? GetHardwareCount<T>(T result)
{
return result is IGroup group ? group.Hardware.Count : null;
}
private static string GetDetail<T>(T result, Func<T, string> getDetail)
{
if (getDetail == null)
return "";
try
{
return getDetail(result) ?? "";
}
catch (Exception ex)
{
return $"Detail unavailable: {ex.GetType().FullName}: {ex.Message}";
}
}
private void AddEntry(string phase, TimeSpan elapsed, string status, int? hardwareCount, string detail)
{
Entry entry = new(phase, elapsed, status, hardwareCount, detail);
_entries.Add(entry);
Debug.WriteLine($"Hardware startup: {entry}");
}
private void WriteLog()
{
try
{
string directory = Path.GetDirectoryName(_fileName);
if (!string.IsNullOrEmpty(directory))
Directory.CreateDirectory(directory);
File.WriteAllText(_fileName, BuildLog());
}
catch
{
// Startup tracing must never affect hardware initialization.
}
}
private string BuildLog()
{
StringBuilder builder = new();
builder.AppendLine("Libre Hardware Monitor hardware startup timing");
builder.Append("Timestamp: ");
builder.AppendLine(DateTimeOffset.Now.ToString("u", CultureInfo.InvariantCulture));
builder.Append("Total elapsed: ");
builder.Append(_totalStopwatch.Elapsed.TotalMilliseconds.ToString("F3", CultureInfo.InvariantCulture));
builder.AppendLine(" ms");
builder.AppendLine();
builder.AppendLine("Phase,ElapsedMs,HardwareCount,Status,Detail");
foreach (Entry entry in _entries)
{
builder.Append(EscapeCsv(entry.Phase));
builder.Append(',');
builder.Append(entry.Elapsed.TotalMilliseconds.ToString("F3", CultureInfo.InvariantCulture));
builder.Append(',');
builder.Append(entry.HardwareCount?.ToString(CultureInfo.InvariantCulture) ?? "");
builder.Append(',');
builder.Append(entry.Status);
builder.Append(',');
builder.AppendLine(EscapeCsv(entry.Detail));
}
return builder.ToString();
}
private static string EscapeCsv(string value)
{
if (string.IsNullOrEmpty(value))
return "";
if (!value.Contains(",") && !value.Contains("\"") && !value.Contains("\r") && !value.Contains("\n"))
return value;
return "\"" + value.Replace("\"", "\"\"") + "\"";
}
private sealed class Entry
{
public Entry(string phase, TimeSpan elapsed, string status, int? hardwareCount, string detail)
{
Phase = phase;
Elapsed = elapsed;
Status = status;
HardwareCount = hardwareCount;
Detail = detail;
}
public string Phase { get; }
public TimeSpan Elapsed { get; }
public string Status { get; }
public int? HardwareCount { get; }
public string Detail { get; }
public override string ToString()
{
string hardwareCount = HardwareCount?.ToString(CultureInfo.InvariantCulture) ?? "-";
return $"{Phase}: {Elapsed.TotalMilliseconds.ToString("F3", CultureInfo.InvariantCulture)} ms, {Status}, {hardwareCount} hardware, {Detail}";
}
}
}
@@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -20,6 +21,9 @@ namespace LibreHardwareMonitor.Hardware.Memory;
internal class MemoryGroup : IGroup, IHardwareChanged
{
private const string DeferDimmDetectionEnvironmentVariable = "LHM_MEMORY_DEFER_DIMM_DETECTION";
private const string DeferDimmDetectionSetting = "memory.deferDimmDetection";
private static readonly TimeSpan _retryInterval = TimeSpan.FromSeconds(2.5);
private static readonly object _lock = new();
private List<Hardware> _hardware = [];
@@ -28,23 +32,35 @@ internal class MemoryGroup : IGroup, IHardwareChanged
private bool _disposed = false;
public MemoryGroup(ISettings settings)
: this(settings, null)
{ }
internal MemoryGroup(ISettings settings, HardwareStartupTrace startupTrace)
{
if (DriverManager.Driver is null || !DriverManager.Driver.IsOpen)
Measure(startupTrace, "MemoryGroup.Driver.Configure", () =>
{
// Assign implementation of IDriver.
DriverManager.Driver = new RAMSPDToolkitDriver();
SMBusManager.UseWMI = false;
}
if (DriverManager.Driver is null || !DriverManager.Driver.IsOpen)
{
// Assign implementation of IDriver.
DriverManager.Driver = new RAMSPDToolkitDriver();
SMBusManager.UseWMI = false;
}
});
_hardware.Add(new VirtualMemory(settings));
_hardware.Add(new TotalMemory(settings));
_hardware.Add(Measure(startupTrace, "MemoryGroup.VirtualMemory", () => new VirtualMemory(settings)));
_hardware.Add(Measure(startupTrace, "MemoryGroup.TotalMemory", () => new TotalMemory(settings)));
if (DriverManager.Driver == null || !DriverManager.LoadDriver())
if (!Measure(startupTrace, "MemoryGroup.Driver.LoadDriver", () => DriverManager.Driver != null && DriverManager.LoadDriver(), loaded => loaded ? "Loaded" : "Unavailable"))
{
return;
}
if (!TryAddDimms(settings))
if (ShouldDeferDimmDetection(settings))
{
startupTrace?.Skip("MemoryGroup.DimmDetection", "Deferred to background.");
StartDimmDetectionTask(settings, TimeSpan.Zero);
}
else if (!TryAddDimms(settings, startupTrace))
{
StartRetryTask(settings);
}
@@ -96,6 +112,11 @@ internal class MemoryGroup : IGroup, IHardwareChanged
}
private bool TryAddDimms(ISettings settings)
{
return TryAddDimms(settings, null);
}
private bool TryAddDimms(ISettings settings, HardwareStartupTrace startupTrace)
{
try
{
@@ -106,9 +127,14 @@ internal class MemoryGroup : IGroup, IHardwareChanged
return false;
}
if (DetectThermalSensors(out List<SPDAccessor> accessors))
List<SPDAccessor> accessors = [];
bool detected = Measure(startupTrace,
"MemoryGroup.DetectThermalSensors",
() => DetectThermalSensors(out accessors, startupTrace),
result => result ? $"{accessors.Count} DIMM accessor(s)" : "No DIMM accessors");
if (detected)
{
AddDimms(accessors, settings);
Measure(startupTrace, "MemoryGroup.AddDimms", () => AddDimms(accessors, settings, startupTrace));
return true;
}
}
@@ -123,41 +149,67 @@ internal class MemoryGroup : IGroup, IHardwareChanged
}
private void StartRetryTask(ISettings settings)
{
StartDimmDetectionTask(settings, _retryInterval);
}
private void StartDimmDetectionTask(ISettings settings, TimeSpan initialDelay)
{
_cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = _cancellationTokenSource.Token;
Task.Run(async () =>
{
int retryRemaining = 5;
while (!_cancellationTokenSource.IsCancellationRequested && --retryRemaining > 0)
try
{
await Task.Delay(TimeSpan.FromSeconds(2.5), _cancellationTokenSource.Token).ConfigureAwait(false);
int retryRemaining = 5;
TimeSpan delay = initialDelay;
if (TryAddDimms(settings))
while (!cancellationToken.IsCancellationRequested && retryRemaining-- > 0)
{
break;
if (delay > TimeSpan.Zero)
await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
if (TryAddDimms(settings))
break;
delay = _retryInterval;
}
}
}, _cancellationTokenSource.Token);
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
}
}, cancellationToken);
}
private static bool DetectThermalSensors(out List<SPDAccessor> accessors)
{
return DetectThermalSensors(out accessors, null);
}
private static bool DetectThermalSensors(out List<SPDAccessor> accessors, HardwareStartupTrace startupTrace)
{
accessors = [];
bool ramDetected = false;
SMBusManager.DetectSMBuses();
Measure(startupTrace, "MemoryGroup.SMBus.DetectSMBuses", SMBusManager.DetectSMBuses);
//Go through detected SMBuses
int busIndex = 0;
foreach (SMBusInterface smbus in SMBusManager.RegisteredSMBuses)
{
string busName = $"{smbus.GetType().Name}[{busIndex}]";
//Go through possible RAM slots
for (byte i = SPDConstants.SPD_BEGIN; i <= SPDConstants.SPD_END; ++i)
{
//Detect type of RAM, if available
SPDDetector detector = new(smbus, i);
string address = "0x" + i.ToString("X2", CultureInfo.InvariantCulture);
SPDDetector detector = Measure(startupTrace,
$"MemoryGroup.SPDDetector.{busName}.{address}",
() => new SPDDetector(smbus, i),
result => result.Accessor != null ? $"Detected {result.Accessor.GetType().Name} index {result.Accessor.Index}" : "No RAM");
//RAM available and detected
if (detector.Accessor != null)
@@ -168,12 +220,31 @@ internal class MemoryGroup : IGroup, IHardwareChanged
ramDetected = true;
}
}
busIndex++;
}
return ramDetected;
}
private void AddDimms(List<SPDAccessor> accessors, ISettings settings)
private static bool ShouldDeferDimmDetection(ISettings settings)
{
string environmentValue = Environment.GetEnvironmentVariable(DeferDimmDetectionEnvironmentVariable);
if (!string.IsNullOrWhiteSpace(environmentValue))
return IsTruthy(environmentValue);
return IsTruthy(settings.GetValue(DeferDimmDetectionSetting, "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);
}
private void AddDimms(List<SPDAccessor> accessors, ISettings settings, HardwareStartupTrace startupTrace)
{
List<Hardware> additions = [];
@@ -181,12 +252,16 @@ internal class MemoryGroup : IGroup, IHardwareChanged
{
//Default value
string name = $"DIMM #{ram.Index}";
string phasePrefix = $"MemoryGroup.DIMM{ram.Index}";
//Check if we can switch to the correct page
if (ram.ChangePage(PageData.ModulePartNumber))
name = $"{ram.GetModuleManufacturerString()} - {ram.ModulePartNumber()} (#{ram.Index})";
if (Measure(startupTrace, $"{phasePrefix}.ChangePage", () => ram.ChangePage(PageData.ModulePartNumber), changed => changed ? "Module part number page selected" : "Module part number page unavailable"))
name = Measure(startupTrace, $"{phasePrefix}.ReadName", () => $"{ram.GetModuleManufacturerString()} - {ram.ModulePartNumber()} (#{ram.Index})", result => result);
DimmMemory memory = new(ram, name, new Identifier("memory", "dimm", $"{ram.Index}"), settings);
DimmMemory memory = Measure(startupTrace,
$"{phasePrefix}.CreateHardware",
() => new DimmMemory(ram, name, new Identifier("memory", "dimm", $"{ram.Index}"), settings),
memory => $"{memory.Sensors.Length} sensor(s)");
additions.Add(memory);
}
@@ -194,4 +269,22 @@ internal class MemoryGroup : IGroup, IHardwareChanged
foreach (Hardware hardware in additions)
HardwareAdded?.Invoke(hardware);
}
private static void Measure(HardwareStartupTrace startupTrace, string phase, Action action)
{
if (startupTrace != null)
startupTrace.Measure(phase, action);
else
action();
}
private static T Measure<T>(HardwareStartupTrace startupTrace, string phase, Func<T> action)
{
return startupTrace != null ? startupTrace.Measure(phase, action) : action();
}
private static T Measure<T>(HardwareStartupTrace startupTrace, string phase, Func<T> action, Func<T, string> getDetail)
{
return startupTrace != null ? startupTrace.Measure(phase, action, getDetail) : action();
}
}