Files
Thing/ThingHK/HardwareManager.cs
T

382 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading.Channels;
using LibreHardwareMonitor.Hardware;
namespace ThingHK;
/// <summary>
/// 硬件访问层:封装 LibreHardwareMonitor Computer
/// 负责硬件发现、分层 Update(快/慢通道)、快照构建。
///
/// 设计要点:
/// - Computer 非 IDisposableLHB 0.9.5),用 Close() 显式释放
/// - 传感器按 SensorType 分快/慢通道:
/// * 快通道(temp/load/clock/power/voltage/throughput):高频,1s
/// * 慢通道(data/smalldata/factor/level/control/timing/energy 等):低频,5s
/// 原因:SMART 查询会阻塞,混在一起会拖慢整体;data 类(如内存使用量)变化缓慢
/// - Update 只作用于硬件设备,传感器读取由 visitor 遍历收集(LHB 设计)
/// </summary>
internal sealed class HardwareManager : IDisposable
{
private readonly Computer _computer;
private readonly SnapshotVisitor _visitor = new();
private readonly bool _isAdmin;
private readonly double _coldStartMs;
private readonly Stopwatch _startupSw;
private bool _ready;
private bool _closed;
// 缓存"哪个硬件属于快通道"——按 hardware type 判断更稳定(不同机型传感器命名不一致)
// 实际分频逻辑见 SamplingScheduler,此处只暴露硬件列表给调度器
public IReadOnlyList<IHardware> AllHardware => _visitor.AllHardware;
public bool IsAdmin => _isAdmin;
public double ColdStartMs => _coldStartMs;
public bool Ready => _ready;
public HardwareManager(bool basic)
{
_isAdmin = IsRunningAsAdmin();
_startupSw = Stopwatch.StartNew();
_computer = new Computer
{
IsCpuEnabled = true,
IsGpuEnabled = true,
IsMemoryEnabled = true,
IsStorageEnabled = true,
// basic 模式只开核心四类硬件(CPU/GPU/RAM/Storage),降低冷启动耗时
IsMotherboardEnabled = !basic,
IsControllerEnabled = !basic,
IsBatteryEnabled = !basic,
IsNetworkEnabled = !basic,
IsPsuEnabled = !basic,
};
_computer.Open();
// 首轮扫描:发现所有硬件(Update 前的 Sensors 通常为空,但 Hardware 列表就绪)
_computer.Accept(_visitor);
// 首轮 Update:填充传感器值
// 注意:部分传感器第二轮才有值,调度器会持续 Update
UpdateAll();
_coldStartMs = _startupSw.Elapsed.TotalMilliseconds;
_startupSw.Stop();
_ready = true;
}
/// <summary>
/// 全量 Update 所有硬件。
/// 由 SamplingScheduler 按通道分频调用。
/// </summary>
public void UpdateAll()
{
foreach (var hw in _visitor.AllHardware)
{
try { hw.Update(); } catch { /* 单个硬件 Update 失败不影响整体 */ }
}
}
/// <summary>
/// 仅 Update 慢通道硬件(Storage/PSU/Battery 等)。
/// 快通道硬件(CPU/GPU/Memory/Network)由调度器更高频调用 UpdateAll。
/// </summary>
public void UpdateSlowOnly()
{
foreach (var hw in _visitor.AllHardware)
{
if (IsSlowHardware(hw.HardwareType))
{
try { hw.Update(); } catch { /* ignore */ }
}
}
}
/// <summary>
/// 构建 SensorSnapshot:遍历当前所有传感器,不触发 Update。
/// 调用者应先 Update 再 Snapshot,避免读到旧值。
/// </summary>
public SensorSnapshot BuildSnapshot()
{
var snap = new SensorSnapshot
{
SchemaVersion = 1,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
IsAdmin = _isAdmin,
Ready = _ready,
};
// 首个快照带上冷启动耗时,后续为 0
if (_coldStartMs > 0 && snap.Timestamp > 0)
{
snap.ColdStartMs = Math.Round(_coldStartMs, 1);
}
// 重新遍历以读取最新传感器值(visitor 缓存的是 hardware 引用,sensor 值实时)
var groups = new Dictionary<string, SensorGroup>();
foreach (var hw in _visitor.AllHardware)
{
string groupId = hw.HardwareType.ToString().ToLowerInvariant();
string groupName = hw.HardwareType.ToString();
if (!groups.TryGetValue(groupId, out var g))
{
g = new SensorGroup { Id = groupId, Name = groupName };
groups[groupId] = g;
snap.Groups.Add(g);
}
foreach (var s in hw.Sensors)
{
g.Sensors.Add(new SensorEntry
{
Id = $"{groupId}/{hw.Name}/{s.SensorType}/{s.Name}".Replace(' ', '_').ToLowerInvariant(),
Name = s.Name,
Type = s.SensorType.ToString().ToLowerInvariant(),
Value = s.Value,
Unit = UnitFor(s.SensorType),
HardwareName = hw.Name,
});
}
}
return snap;
}
/// <summary>
/// 判断硬件是否属于慢通道(低频 Update 即可)。
/// 快通道:CPU/GPU/Memory/Network(变化快、查询轻量)
/// 慢通道:Storage/PSU/Motherboard/Battery/SuperIO/EmbeddedController(查询重或变化慢)
/// </summary>
public static bool IsSlowHardware(HardwareType t) => t switch
{
HardwareType.Storage => true,
HardwareType.Psu => true,
HardwareType.Motherboard => true,
HardwareType.Battery => true,
HardwareType.SuperIO => true,
HardwareType.EmbeddedController => true,
_ => false,
};
private static string UnitFor(SensorType t) => t switch
{
SensorType.Temperature => "°C",
SensorType.Load => "%",
SensorType.Power => "W",
SensorType.Voltage => "V",
SensorType.Fan => "RPM",
SensorType.Clock => "MHz",
SensorType.Data => "GB",
SensorType.SmallData => "MB",
SensorType.Frequency => "Hz",
SensorType.Throughput => "B/s",
SensorType.Level => "%",
SensorType.Factor => "",
SensorType.Control => "%",
SensorType.Flow => "L/h",
SensorType.TimeSpan => "s",
SensorType.Energy => "mWh",
SensorType.Noise => "dBA",
SensorType.Conductivity => "µS/cm",
SensorType.Humidity => "%",
_ => "",
};
private static bool IsRunningAsAdmin()
{
try
{
using var identity = System.Security.Principal.WindowsIdentity.GetCurrent();
var principal = new System.Security.Principal.WindowsPrincipal(identity);
return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
}
catch
{
return false;
}
}
public void Dispose()
{
if (_closed) return;
_closed = true;
try { _computer.Close(); } catch { /* Close 在 AOT 下偶有反射清理异常 */ }
}
}
/// <summary>
/// 遍历 Computer,收集所有 Hardware(含 SubHardware)。
/// LHB 的 Visit 会递归到 SubHardware。
/// 引用缓存:visitor 持有 hardware 引用,传感器值通过 hardware.Sensors 实时读取。
/// </summary>
internal sealed class SnapshotVisitor : IVisitor
{
public List<IHardware> AllHardware { get; } = new();
public void VisitComputer(IComputer computer)
{
foreach (var hw in computer.Hardware) hw.Accept(this);
}
public void VisitHardware(IHardware hardware)
{
AllHardware.Add(hardware);
foreach (var sub in hardware.SubHardware) sub.Accept(this);
}
public void VisitSensor(ISensor sensor) { }
public void VisitParameter(IParameter parameter) { }
}
/// <summary>
/// 采样调度器:按快/慢通道分频驱动 HardwareManager.UpdateAll。
/// 使用 Channel 向 SSE 推送层广播快照(解耦:调度器不关心有几个订阅者)。
///
/// 调度策略:
/// - 快通道 tickUpdateAll(含慢通道硬件,因 UpdateAll 成本主要在 SMART,已通过慢通道分频减少调用频率)
/// 实际优化:快通道 tick 只 Update 快通道硬件(UpdateFastOnly),慢通道单独按慢节奏 Update
/// - 慢通道 tickUpdateSlowOnly(仅 Storage/PSU/Motherboard 等)
/// - 每个 tick 结束后构建快照并广播
/// </summary>
internal sealed class SamplingScheduler : IDisposable
{
private readonly HardwareManager _hw;
private readonly CancellationTokenSource _cts = new();
private readonly Channel<SensorSnapshot> _broadcast;
private readonly SnapshotCache _cache;
private int _fastIntervalMs = 1000;
private int _slowIntervalMs = 5000;
public SnapshotCache Cache => _cache;
public int FastIntervalMs => _fastIntervalMs;
public int SlowIntervalMs => _slowIntervalMs;
public SamplingScheduler(HardwareManager hw, int fastIntervalMs, int slowIntervalMs)
{
_hw = hw;
_fastIntervalMs = Math.Max(200, fastIntervalMs);
_slowIntervalMs = Math.Max(_fastIntervalMs, slowIntervalMs);
// unbounded channel:快照丢失风险 < 内存爆涨风险(消费慢时丢弃最旧的)
// 这里用 bounded + DropOldest:保证 SSE 慢消费者不阻塞调度器
_broadcast = Channel.CreateBounded<SensorSnapshot>(new BoundedChannelOptions(8)
{
FullMode = BoundedChannelFullMode.DropOldest,
SingleReader = false,
SingleWriter = true,
});
_cache = new SnapshotCache();
}
/// <summary>
/// 启动调度循环。立即推送首个快照(已由 HardwareManager 构造时 Update 过)。
/// </summary>
public Task StartAsync()
{
// 首个快照(含 ColdStartMs
var first = _hw.BuildSnapshot();
_cache.Update(first);
_broadcast.Writer.TryWrite(first);
// 快/慢通道并行循环
_ = Task.Run(() => FastLoopAsync(_cts.Token));
_ = Task.Run(() => SlowLoopAsync(_cts.Token));
return Task.CompletedTask;
}
private async Task FastLoopAsync(CancellationToken ct)
{
using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(_fastIntervalMs));
while (await timer.WaitForNextTickAsync(ct).ConfigureAwait(false))
{
try
{
_hw.UpdateAll();
var snap = _hw.BuildSnapshot();
_cache.Update(snap);
_broadcast.Writer.TryWrite(snap);
}
catch (Exception ex)
{
Console.Error.WriteLine($"[Scheduler] 快通道异常: {ex.Message}");
}
}
}
private async Task SlowLoopAsync(CancellationToken ct)
{
using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(_slowIntervalMs));
while (await timer.WaitForNextTickAsync(ct).ConfigureAwait(false))
{
try
{
// 慢通道单独 Update,避免依赖快通道的 UpdateAll
// 注:下一次快通道 tick 构建的快照会包含本次慢通道更新后的值
_hw.UpdateSlowOnly();
}
catch (Exception ex)
{
Console.Error.WriteLine($"[Scheduler] 慢通道异常: {ex.Message}");
}
}
}
/// <summary>
/// 订阅快照流。每个 SSE 客户端调用一次。
/// 返回的 IAsyncEnumerable 会在调度器停止或订阅者取消时结束。
/// </summary>
public IAsyncEnumerable<SensorSnapshot> SubscribeAsync(CancellationToken ct) =>
_broadcast.Reader.ReadAllAsync(ct);
public void UpdateIntervals(int? fastMs, int? slowMs)
{
// 注意:PeriodicTimer 已启动后无法修改间隔,下次重启 Kernel 才生效。
// 阶段二简化处理:记录新值,实际生效需重启。阶段三若需热更新可重建 timer。
if (fastMs.HasValue && fastMs.Value >= 200) _fastIntervalMs = fastMs.Value;
if (slowMs.HasValue && slowMs.Value >= _fastIntervalMs) _slowIntervalMs = slowMs.Value;
}
public void Dispose()
{
_cts.Cancel();
_broadcast.Writer.TryComplete();
_cts.Dispose();
}
}
/// <summary>
/// 快照缓存:存储最新快照,供 GET /snapshot 直接返回,避免触发底层重扫描。
/// 线程安全:读写均加锁,快照对象本身不可变(每次 Update 替换引用)。
/// </summary>
internal sealed class SnapshotCache
{
private readonly object _lock = new();
private SensorSnapshot? _latest;
private int _sensorCount;
public void Update(SensorSnapshot snap)
{
lock (_lock)
{
_latest = snap;
_sensorCount = snap.Groups.Sum(g => g.Sensors.Count);
}
}
public SensorSnapshot? GetLatest()
{
lock (_lock)
{
return _latest;
}
}
public int SensorCount
{
get { lock (_lock) return _sensorCount; }
}
}