独立监控核心和模块

This commit is contained in:
zhongluofeng
2026-07-23 19:27:40 +08:00
parent 62db8e1982
commit e8267c244f
14 changed files with 2516 additions and 17 deletions
+381
View File
@@ -0,0 +1,381 @@
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; }
}
}