WIP - optimizing start times
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user