using System.Collections.Concurrent; using System.Diagnostics; using System.Threading.Channels; using LibreHardwareMonitor.Hardware; namespace ThingHK; /// /// 硬件访问层:封装 LibreHardwareMonitor Computer, /// 负责硬件发现、分层 Update(快/慢通道)、快照构建。 /// /// 设计要点: /// - Computer 非 IDisposable(LHB 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 设计) /// 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; // 传感器 ID 缓存:避免每秒为每个传感器重复拼接字符串(减少 GC 压力) private readonly Dictionary _sensorIdCache = new(); private bool _coldStartSent; private bool _ready; private bool _closed; private HardwareConfig _config; // 缓存"哪个硬件属于快通道"——按 hardware type 判断更稳定(不同机型传感器命名不一致) // 实际分频逻辑见 SamplingScheduler,此处只暴露硬件列表给调度器 public IReadOnlyList AllHardware => _visitor.AllHardware; public bool IsAdmin => _isAdmin; public double ColdStartMs => _coldStartMs; public bool Ready => _ready; public HardwareConfig Config => _config; public HardwareManager(HardwareConfig config) { _config = config ?? HardwareConfig.Default; _isAdmin = IsRunningAsAdmin(); _startupSw = Stopwatch.StartNew(); // LHB 行为:主板的传感器实际通过 SubHardware(SuperIO/EC)暴露, // 而 SuperIO 作为顶层硬件时需 IsControllerEnabled 才能枚举。 // 联动策略:启用 motherboard 时自动启用 controller,避免主板勾选后无数据。 bool motherboardEnabled = _config.IsHardwareEnabled("motherboard"); bool controllerEnabled = _config.IsHardwareEnabled("controller") || motherboardEnabled; _computer = new Computer { IsCpuEnabled = _config.IsHardwareEnabled("cpu"), IsGpuEnabled = _config.IsHardwareEnabled("gpu"), IsMemoryEnabled = _config.IsHardwareEnabled("memory"), IsStorageEnabled = _config.IsHardwareEnabled("storage"), IsMotherboardEnabled = motherboardEnabled, IsControllerEnabled = controllerEnabled, IsBatteryEnabled = _config.IsHardwareEnabled("battery"), IsNetworkEnabled = _config.IsHardwareEnabled("network"), IsPsuEnabled = _config.IsHardwareEnabled("psu"), }; _computer.Open(); // 首轮扫描:发现所有硬件(Update 前的 Sensors 通常为空,但 Hardware 列表就绪) _computer.Accept(_visitor); // 首轮 Update:填充传感器值 // 注意:部分传感器第二轮才有值,调度器会持续 Update UpdateAll(); _coldStartMs = _startupSw.Elapsed.TotalMilliseconds; _startupSw.Stop(); _ready = true; } /// 热更新传感器类型过滤(无需重启 Kernel,下次 BuildSnapshot 生效) public void UpdateConfig(HardwareConfig newConfig) { _config = newConfig; } /// /// 全量 Update 所有硬件。 /// 仅用于构造函数首轮填充,运行期由调度器分频调用 UpdateFastOnly/UpdateSlowOnly。 /// public void UpdateAll() { foreach (var hw in _visitor.AllHardware) { try { hw.Update(); } catch { /* 单个硬件 Update 失败不影响整体 */ } } } /// /// 仅 Update 快通道硬件(CPU/GPU/Memory/Network 等)。 /// 由调度器快通道 tick 高频调用,避免 SMART 等重查询拖慢采样节奏。 /// public void UpdateFastOnly() { foreach (var hw in _visitor.AllHardware) { if (!IsSlowHardware(hw.HardwareType)) { try { hw.Update(); } catch { /* 单个硬件 Update 失败不影响整体 */ } } } } /// /// 仅 Update 慢通道硬件(Storage/PSU/Battery 等)。 /// 快通道硬件(CPU/GPU/Memory/Network)由调度器更高频调用 UpdateFastOnly。 /// public void UpdateSlowOnly() { foreach (var hw in _visitor.AllHardware) { if (IsSlowHardware(hw.HardwareType)) { try { hw.Update(); } catch { /* ignore */ } } } } /// /// 构建 SensorSnapshot:遍历当前所有传感器,不触发 Update。 /// 调用者应先 Update 再 Snapshot,避免读到旧值。 /// public SensorSnapshot BuildSnapshot() { var snap = new SensorSnapshot { SchemaVersion = 1, Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), IsAdmin = _isAdmin, Ready = _ready, }; // 首个快照带上冷启动耗时,后续为 0(修复:此前每个快照都携带 ColdStartMs) if (_coldStartMs > 0 && !_coldStartSent) { snap.ColdStartMs = Math.Round(_coldStartMs, 1); _coldStartSent = true; } // 重新遍历以读取最新传感器值(visitor 缓存的是 hardware 引用,sensor 值实时) var groups = new Dictionary(); // 为已启用但 LHB 未枚举到的硬件类型预创建空分组, // 确保前端能显示"已启用但无数据"的硬件(而不是直接隐藏,让用户误以为配置未生效) EnsureGroupsForEnabledHardware(groups, snap); 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) { // 按配置过滤传感器类型:关闭的类型不加入快照,减少传输数据量 string sensorType = s.SensorType.ToString().ToLowerInvariant(); if (!_config.IsSensorTypeEnabled(sensorType)) continue; g.Sensors.Add(new SensorEntry { Id = GetSensorId(s, groupId, hw.Name), Name = s.Name, Type = sensorType, Value = s.Value, Unit = UnitFor(s.SensorType), HardwareName = hw.Name, }); } } return snap; } /// /// 获取传感器稳定 ID(带缓存)。 /// ID 格式:{groupId}/{hwName}/{sensorType}/{sensorName},空格转下划线、小写化。 /// 缓存命中直接返回,未命中(新传感器)计算后入缓存。 /// private string GetSensorId(ISensor s, string groupId, string hwName) { if (!_sensorIdCache.TryGetValue(s, out var id)) { id = $"{groupId}/{hwName}/{s.SensorType}/{s.Name}".Replace(' ', '_').ToLowerInvariant(); _sensorIdCache[s] = id; } return id; } /// /// 为已启用但 LHB 未枚举到的硬件类型预创建空分组。 /// 场景:用户在设置中勾选了主板/电池/电源等,但 LHB 在当前权限或机型下检测不到对应硬件, /// 此时仍创建空分组让前端显示"已启用但无数据",避免用户误以为配置未生效。 /// GPU 特殊处理:LHB 会枚举到 GpuIntel/GpuAmd/GpuNvidia 之一,不预创建通用分组。 /// private void EnsureGroupsForEnabledHardware(Dictionary groups, SensorSnapshot snap) { // (configKey, groupId, groupName) // 注意:controller 对应 SuperIO 和 EmbeddedController 两个 HardwareType var mapping = new (string, string, string)[] { ("cpu", "cpu", "CPU"), ("memory", "memory", "Memory"), ("storage", "storage", "Storage"), ("motherboard", "motherboard", "Motherboard"), ("controller", "superio", "SuperIO"), ("controller", "embeddedcontroller", "EmbeddedController"), ("battery", "battery", "Battery"), ("network", "network", "Network"), ("psu", "psu", "Psu"), }; foreach (var (key, groupId, groupName) in mapping) { if (_config.IsHardwareEnabled(key) && !groups.ContainsKey(groupId)) { var g = new SensorGroup { Id = groupId, Name = groupName }; groups[groupId] = g; snap.Groups.Add(g); } } } /// /// 判断硬件是否属于慢通道(低频 Update 即可)。 /// 快通道:CPU/GPU/Memory/Network(变化快、查询轻量) /// 慢通道:Storage/PSU/Motherboard/Battery/SuperIO/EmbeddedController(查询重或变化慢) /// 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 下偶有反射清理异常 */ } } } /// /// 遍历 Computer,收集所有 Hardware(含 SubHardware)。 /// LHB 的 Visit 会递归到 SubHardware。 /// 引用缓存:visitor 持有 hardware 引用,传感器值通过 hardware.Sensors 实时读取。 /// internal sealed class SnapshotVisitor : IVisitor { public List 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) { } } /// /// 采样调度器:按快/慢通道分频驱动 HardwareManager 的分层 Update。 /// 使用 Channel 向 SSE 推送层广播快照(解耦:调度器不关心有几个订阅者)。 /// /// 调度策略: /// - 快通道 tick:UpdateFastOnly(仅 CPU/GPU/Memory/Network 等轻量硬件), /// 避免 SMART 等重查询每秒执行拖慢采样节奏 /// - 慢通道 tick:UpdateSlowOnly(仅 Storage/PSU/Motherboard 等) /// - 快通道每个 tick 结束后构建快照并广播(慢通道更新后的值随下一帧带出) /// internal sealed class SamplingScheduler : IDisposable { private readonly HardwareManager _hw; private readonly CancellationTokenSource _cts = new(); private readonly Channel _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(new BoundedChannelOptions(8) { FullMode = BoundedChannelFullMode.DropOldest, SingleReader = false, SingleWriter = true, }); _cache = new SnapshotCache(); } /// /// 启动调度循环。立即推送首个快照(已由 HardwareManager 构造时 Update 过)。 /// 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.UpdateFastOnly(); 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}"); } } } /// /// 订阅快照流。每个 SSE 客户端调用一次。 /// 返回的 IAsyncEnumerable 会在调度器停止或订阅者取消时结束。 /// public IAsyncEnumerable 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(); } } /// /// 快照缓存:存储最新快照,供 GET /snapshot 直接返回,避免触发底层重扫描。 /// 线程安全:读写均加锁,快照对象本身不可变(每次 Update 替换引用)。 /// 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; } } }