# ThingHK — Thing 硬件监控内核 ## 1. 架构 采用**微内核架构**,将硬件监控功能剥离为独立进程 `ThingHK.exe`(C# + LibreHardwareMonitor)。Tauri 主进程(Rust)仅作为"消费者",通过本地 HTTP + SSE 获取标准化数据,实现硬件层与业务层彻底解耦。 **核心数据流**: ``` Rust (Tauri App) <---HTTP / SSE (127.0.0.1:8730)---> ThingHK.exe (独立进程) ↓ emit("monitor-data") 前端 MonitorModule.vue ``` **设计取舍**: - **进程隔离而非进程内宿主**:LHB 或驱动崩溃不会拖垮 Tauri 主进程;独立进程 + Job Object 既隔离崩溃又复用项目现有架构。 - **C# + LibreHardwareMonitor 选型**:Rust 侧硬件监控生态薄弱(`sysinfo` 无 GPU 温度/主板电压,`wmi` 慢且字段不全,自实现 NVAPI/ADL/SMBus 等于重写 LHB)。LHB 是业界事实标准,覆盖 CPU/GPU/盘/主板/USB,可靠性最高。 - **复用 ProcessManager**:项目已有 [process_manager.rs](file:///d:/Atie/Gitea/Thing/src-tauri/src/process_manager.rs) 完整实现了生命周期管理(启动/停止/心跳/崩溃重启/Job Object),ThingHK 不重建这套逻辑,仅作为"数据消费者"。 - **HTTP 而非 Named Pipe**:数据量极小(传感器快照每秒 1-2 次、几 KB 量级),Named Pipe 的性能优势用不上;与项目技术栈一致(Cargo.toml 已有 `reqwest`,mihomo 也走 HTTP REST);调试可用浏览器/curl;跨平台抽象免费。 ## 2. 组件与职责 ### 2.1 ThingHK 独立进程(C#) - **角色**:硬件数据中台。 - **核心组件**: - **数据采集层**:以 LibreHardwareMonitorLib 为唯一数据源(LHB 已统一封装 NVAPI/ADL/SMBus/SMART 等来源)。 - **SamplingScheduler**:按传感器类型分层分频,避免慢查询拖累快通道。 - 快通道(CPU/GPU/Memory/Network):`PeriodicTimer` 默认 1000ms,`UpdateAll()` 全量刷新 - 慢通道(Storage/PSU/Motherboard/Battery/SuperIO/EmbeddedController):独立 `PeriodicTimer` 默认 5000ms,`UpdateSlowOnly()` 仅刷新慢硬件 - 两通道并行运行,每次快通道 tick 后构建快照并广播到 `Channel`(bounded=8,DropOldest 防慢消费者阻塞) - **SnapshotCache**:线程安全快照缓存,UI 请求时直接返回缓存数据,避免触发底层硬件重扫描。 - **HTTP 服务层**:ASP.NET Core minimal API,暴露 `127.0.0.1:8730` 本地 HTTP 服务。 ### 2.2 Tauri 主进程(Rust) - **角色**:管理者(复用 ProcessManager)与消费者。 - **职责**: - **生命周期管理**:复用 `ProcessManager`,通过 `start_with_subscription` 拉起 Kernel。崩溃自动重启、Job Object 异常清理、`stop_all` 退出清理均已内置。 - **数据接收**:作为 HTTP 客户端,轮询 `/status` 判就绪 → 订阅 `/stream` SSE → 解析事件 → `emit("monitor-data")`。 - **写入熔断 + 自动重连**:SSE 断开后停止转发,3 秒退避后重试;同时监听 `process-status-changed` 事件,Kernel 由 ProcessManager 自动重启恢复 Running 后主动重新订阅 SSE。 ### 2.3 前端(Vue 3) - **Pinia store**([monitorStore.ts](file:///d:/Atie/Gitea/Thing/src/stores/monitorStore.ts)):管理 status/snapshot 状态,订阅 Tauri 事件,提供 `connState` 状态机(idle/loading/connected/disconnected/error)。 - **MonitorModule.vue**([MonitorModule.vue](file:///d:/Atie/Gitea/Thing/src/modules/monitor/MonitorModule.vue)):3 个 Tab(概览/详细/设置),关键指标卡 + SVG sparkline 历史曲线(无外部图表库)+ Accordion 分组 + 断线/降级态提示。 ## 3. 通信协议与数据契约 ### 3.1 路由 | 路由 | 方法 | 说明 | |------|------|------| | `/` | GET | 健康检查 | | `/status` | GET | 冷启动就绪探测,前端轮询判断是否可订阅 | | `/snapshot` | GET | 一次性拉取最新缓存快照 | | `/stream` | GET (SSE) | 订阅推送,Kernel 按采样间隔主动 Push `SensorSnapshot` | | `/config` | POST | 运行时调整采样间隔(`{"FastIntervalMs":500,"SlowIntervalMs":3000}`) | | `/shutdown` | POST | 优雅关闭(提权模式下由 Tauri 调用,普通权限无法 kill 管理员进程) | ### 3.2 数据契约(显式版本化) 为避免 Kernel 升级后前端解析炸裂,数据 schema 显式版本化。前端按 `schemaVersion` 解析;Kernel 升级 schema 时递增版本号并提供兼容期。当前 `schemaVersion=1`。 ```jsonc { "schemaVersion": 1, "timestamp": 1730000000000, "coldStartMs": 4906, "isAdmin": true, "ready": true, "groups": [ { "id": "cpu", "name": "CPU", "sensors": [ { "id": "...", "name": "CPU Package", "type": "temperature", "hardwareName": "Intel Core i5-10400", "value": 52.3, "unit": "°C" } ] } ] } ``` **JSON 字段命名**:双方均 camelCase。C# 端 `JsonSourceGenerationOptions` 设置 `PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase`;Rust 端 `#[serde(rename_all = "camelCase")]`。 ### 3.3 Tauri 命令与事件 **7 个命令**: | 命令 | 作用 | |------|------| | `monitor_kernel_info` | 查询 Kernel 二进制路径/是否存在/端口 | | `monitor_status` | 查询运行状态(running/pid/ready/sensorCount/restartCount/elevated) | | `monitor_start` | 启动 Kernel + 自动订阅 SSE(命令和 setup 共用 `start_with_subscription`) | | `monitor_start_elevated` | 以管理员权限启动 Kernel(弹 UAC),停止走 `/shutdown` 接口 | | `monitor_stop` | 停止 Kernel(提权模式走 `/shutdown`,普通模式走 ProcessManager) | | `monitor_get_status` | 透传 Kernel `/status` | | `monitor_get_snapshot` | 透传 Kernel `/snapshot` | **5 个事件**:`monitor-data`(快照)、`monitor-ready`、`monitor-loading`、`monitor-disconnected`、`monitor-error`。 ## 4. 传感器覆盖度 在管理员权限下,`--basic` 模式(仅开 CPU/GPU/RAM/Storage)读取两轮后的结果: | 硬件分组 | 传感器数 | 覆盖项 | |----------|----------|--------| | CPU | 47 | 每核每线程负载、每核温度、TjMax、时钟、Package/Cores/Memory/Platform 功率、每核电压、Bus Speed | | Memory | 42 | 虚拟/物理内存使用量、可用、负载;两条 DDR4 SPD 时序 + 容量 | | GpuIntel | 17 | GPU 功率、D3D 共享内存总量/已用、D3D 3D/VideoDecode/Copy/VideoProcessing 各引擎负载 | | Storage | 11 | 温度、Power on count/hours、已用/可用/总空间、Read/Write/Total Activity、Read/Write Rate | | **合计** | **117** | 100% 有值 | `full` 模式额外多出 Network(60 个传感器)和 Motherboard(本机 0 个,需更深驱动/权限)。 **非提权降级**:LHB 访问 SMBus、部分 EC 传感器、某些 GPU 传感器需要管理员权限。默认非提权运行,覆盖大部分 CPU/GPU 温度(通过 OHM RPC/WMI 仍可读),牺牲部分主板/电压传感器。提权策略(任务计划程序免 UAC)作为可选优化。 ## 5. 实现要点与经验教训 ### 5.1 C# Kernel(Native AOT) **关键配置**([ThingHK.csproj](file:///d:/Atie/Gitea/Thing/ThingHK/ThingHK.csproj)): - `net8.0-windows`(LHB 在 Windows 下最稳) - `true` - `rd.xml`(LHB 反射根描述,`preserve="all"` 兜底) - JSON 使用 `[JsonSourceGeneration]` 消除 IL3050/IL2026 警告 **AOT 体积**:18.3 MB(含 ASP.NET Core runtime),无需 .NET Runtime,符合"最小安装"偏好。 **AOT 踩坑**: 1. **匿名类型无法序列化**:`Results.Ok(new { ... })` 在 AOT 下抛 `JsonTypeInfo metadata ... was not provided`。所有响应改用强类型 + `Results.Json(obj, ThingHKJsonContext.Default.T)`,新增 `HealthResponse`/`ErrorResponse` 类型注册到 `[JsonSerializable]`。 2. **async lambda 返回类型**:`app.MapPost("/config", async (ctx) => { return Results.Json(...); })` 在 AOT 下返回空响应体。原因:async lambda 返回 `IResult` 被框架当成 `Task` 的未等待任务。修复:显式声明 `async Task (HttpContext ctx) =>`。 3. **HardwareType 枚举变更**:LHB 0.9.5 的 `HardwareType` 无 `Controller`,改为 `SuperIO` + `EmbeddedController`。 4. **Computer 非 IDisposable**:LHB 0.9.5 的 `Computer` 类未实现 `IDisposable`,`HardwareManager.Dispose()` 改用显式 `_computer.Close()`。 ### 5.2 Rust 集成 **关键架构决策**: 1. **自动启动策略**:参照 proxy 模块的 `auto_start_on_launch` 模式,在 `setup` 中用 `tauri::async_runtime::spawn` 异步拉起 Kernel。硬件监控为被动读取、无副作用(不修改系统状态),故默认启用,无需用户配置开关。 2. **`start_with_subscription` 抽取**:将"启动进程 + 注册自动重连 + 启动 SSE 订阅"封装为单一方法,供 `monitor_start` 命令和 setup 自动启动复用,避免两条路径行为漂移。 3. **`MonitorKernel` 改为 `Clone` + `Arc` 共享状态**:`tauri::State::inner()` 返回 `&T` 而非 `&Arc`,无法直接 clone 出 `Arc`。将 `sub_handle` 和 `listener_ids` 改为 `Arc>`,`MonitorKernel` 派生 `Clone`,clone 出的实例与原实例共享订阅控制状态。 4. **`sse_client` 与 `client` 分离**:`client` 带 30s 超时,用于 `/status`、`/snapshot` 等短请求;`sse_client` 无超时,专用于 `/stream` 长连接。早期版本对 SSE 请求误用 `timeout(Duration::from_secs(0))`,导致 reqwest 立即超时失败。 5. **写入熔断 + 自动重连**:`subscribe_once` 收到 SSE 断开后停止 `emit`,3 秒退避后重试;Kernel 不响应则退出循环。同时监听 `process-status-changed` 事件,Kernel 由 ProcessManager 自动重启恢复 Running 后主动重新订阅 SSE。 **Rust 踩坑**: 1. **Tauri 2 async 命令必须返回 `Result`**:`AsyncCommandMustReturnResult` 未实现 for `MonitorStatus`。修复:`monitor_status` 返回类型从 `MonitorStatus` 改为 `Result`。 2. **Kernel `/status` 反序列化失败**:C# 默认输出 PascalCase(`Ready`、`IsAdmin`),Rust 期望 camelCase。修复:`Contracts.cs` 的 `JsonSourceGenerationOptions` 添加 `PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase`。 3. **崩溃重连无并发订阅问题**:旧 SSE 循环在 `is_kernel_alive` 返回 false(端口未释放)时正确退出,`register_auto_reconnect` 启动新订阅,netstat 确认仅 1 个 ESTABLISHED 连接。 ### 5.3 前端 **关键设计决策**: 1. **状态机 `connState`**:基于 status + eventCount + lastEventTime 推断 5 种状态(idle/loading/connected/disconnected/error),5 秒未收到 monitor-data 事件判定为 disconnected。 2. **`findSensorValue` 多条件查找**:支持 groupId + name + hardwareName + type 四维匹配,name 用精确匹配 + includes 子串兜底。用于解决 Memory 分组有两条同名 "Memory" 传感器(Virtual/Total)的歧义。 3. **SVG sparkline 无外部依赖**:关键指标历史用 30 点环形 buffer,SVG path 自绘,不引入 Chart.js(符合"最小安装、少依赖"偏好)。 4. **`schemaVersion` 守卫**:store 订阅 monitor-data 时检查 `schemaVersion !== 1` 并 warn,未来 Kernel 升级 schema 时前端不会静默解析错误数据。 5. **模块卸载不停止 Kernel**:`dispose` 仅取消事件订阅,不调用 `monitor_stop`。Kernel 生命周期由 ProcessManager 全局管理,与模块 UI 生命周期解耦。 6. **Ready 边沿触发拉取快照**:watch `store.status?.ready` 从 false→true 时主动 `fetchSnapshot()`,避免 Kernel 冷启动就绪后 UI 等待下一个 SSE tick 才有数据。 **传感器名匹配**(通过 `curl /snapshot` 验证实际传感器名): | 指标 | 查找逻辑 | 说明 | |------|----------|------| | CPU 封装温度 | `name: 'CPU Package', type: 'temperature'` | — | | CPU 总负载 | `name: 'CPU Total', type: 'load'` | — | | 内存负载 | `name: 'Memory', hardwareName: 'Total Memory', type: 'load'` | Memory 分组有两条同名传感器(Virtual/Total),需用 hardwareName 消歧 | | Intel GPU 3D 负载 | `name: 'D3D 3D'` ?? `name: 'GPU Core'` ?? `type: 'load'` | LHB 实际输出名是 "D3D 3D",无 "GPU" 前缀 | | 存储温度 | `type: 'temperature'` | 用 type 查找 | **前端踩坑**: 1. **`lucide-vue-next` 模块未找到**:项目实际使用 `@lucide/vue`(见 package.json),而非 `lucide-vue-next`。 2. **`connState === 'idle'` 类型比较错误**:vue-tsc 报 TS2367。v-else-if 已处理 idle 分支,v-else 块内 connState 类型被收窄为非 idle,`=== 'idle'` 永假。修复:移除该比较。 ### 5.4 稳定性与打包 **冷启动**:约 5 秒(`computer.Open()` 驱动加载占绝大部分,`Update()` 本身约 200ms)。前端 Loading 超时阈值设为 10 秒,轮询间隔 500ms。 **崩溃恢复验证**:kill ThingHK 进程后,ProcessManager 3s 巡检检测崩溃 → 800ms 端口等待 → spawn 新 Kernel → `register_auto_reconnect` 监听 `process-status-changed(running)` 重新订阅 SSE。旧 SSE 循环正确退出,无并发订阅问题。重启后 ready=True,sensorCount=117(与崩溃前一致)。 **退出清理验证**: - **正常退出**(quit_app / Ctrl+C):`stop_all()` 同步 kill + 3s 超时 wait,无残留。 - **异常崩溃**(kill thing.exe):Windows Job Object(`JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`)兜底自动清理,无残留。 **打包验证**: - `tauri build` 生成 MSI(31 MB)+ NSIS(22.3 MB)安装包。 - ThingHK.exe(18.3 MB)通过 [tauri.conf.json](file:///d:/Atie/Gitea/Thing/src-tauri/tauri.conf.json) `"resources": ["binaries/*"]` 打包进安装包。 - `BaseDirectory::Resource` 在 release 模式下正确解析到 `/binaries/ThingHK.exe`(Tauri 2 在打包后指向 exe 所在目录,而非 `resources/` 子目录)。 - Release exe 直接运行:Kernel 自动启动成功,ready=True,sensorCount=117。 **打包踩坑**:`tauri build` 在 TRAE 环境失败 `error: invalid value '1' for '--ci'`。TRAE 环境设置了 `CI=1`,导致 tauri CLI 误解析。修复:构建前清除 `$env:CI = $null`。 ## 6. 文件索引 ### C# Kernel | 文件 | 职责 | |------|------| | [ThingHK.csproj](file:///d:/Atie/Gitea/Thing/ThingHK/ThingHK.csproj) | .NET 8 AOT 配置,`Microsoft.NET.Sdk.Web` + ASP.NET Core minimal API | | [Program.cs](file:///d:/Atie/Gitea/Thing/ThingHK/Program.cs) | `serve`/`scan` 双子命令(System.CommandLine),默认无参数等价 `serve --port 8730 --basic` | | [Contracts.cs](file:///d:/Atie/Gitea/Thing/ThingHK/Contracts.cs) | 数据契约 + `ThingHKJsonContext`(JSON 源生成,camelCase) | | [HardwareManager.cs](file:///d:/Atie/Gitea/Thing/ThingHK/HardwareManager.cs) | `HardwareManager`(封装 LHB Computer,快慢通道分类)+ `SamplingScheduler`(PeriodicTimer 分频)+ `SnapshotCache`(线程安全缓存)+ `SnapshotVisitor`(递归收集) | | [HttpEndpoints.cs](file:///d:/Atie/Gitea/Thing/ThingHK/HttpEndpoints.cs) | 6 个路由(含 `/shutdown` 优雅关闭)+ 全局异常中间件 + `KernelHost`(统一生命周期) | | [rd.xml](file:///d:/Atie/Gitea/Thing/ThingHK/rd.xml) | LHB 反射根描述符,`preserve="all"` 兜底 AOT trimming | ## 7. FPS 监控方案(后续计划,暂不实施) 为扩展硬件监控模块对游戏 FPS 的支持,调研了四种实现方案。当前作为后续规划储备,暂不实施。 > 注:本节为后续规划,与第 8 节提权机制章节编号独立,不影响现有架构。 ### 7.1 方案对比 | 维度 | ① DWM API | ② ETW Present | ③ PresentMon | ④ RTSS | |------|-----------|---------------|--------------|--------| | 依赖 | 无(系统内置) | 无(系统内置) | PresentMon Service(需安装 MSI ~30MB) | RTSS(需安装) | | 体积增量 | 0 KB | 0 KB | ~30MB+ | 外部进程 | | 代码量 | ~60 行 | ~400 行 | ~1000+ 行(跨栈) | ~200 行(读共享内存) | | 精度 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | | 1% Low | ❌ | ❌(需自行实现统计算法) | ✅ 内置 | ✅ | | 进程级监控 | ❌ | ✅ | ✅ | ✅ | | 全屏独占兼容 | ❌ | ✅ | ✅ | ✅ | | 需管理员权限 | ❌ | ✅ | ✅ | ❌ | | 桌面静止时 | 显示刷新率(60/144,误导) | FPS=0(准确) | FPS=0(准确) | FPS=0 | | AOT 风险 | 低 | 低(P/Invoke) | 中(P/Invoke C API) | 低 | | 与"最小依赖"偏好 | ✅ 完美契合 | ✅ 契合 | ❌ 冲突 | ❌ 冲突 | ### 7.2 方案 ①:DWM API(最简单,降级方案) **原理**:调用 `DwmGetCompositionTimingInfo` 获取 DWM 合成帧计数,两次采样差值 / 时间差 = FPS。 **优点**: - 零依赖、零体积增量,`dwmapi.dll` 是 Windows 系统组件 - 实现极简(~60 行),AOT 原生友好 - 无需提权 **缺点**: - 全屏独占(FS Exclusive)模式下 DWM 不参与合成,FPS 读数为 0 - 桌面静止时仍以显示器刷新率合成,FPS 显示 60/144(误导) - 仅全局 FPS,无法按进程过滤 **定位**:降级方案。在用户未提权或仅需粗略 FPS 时使用。 **核心 P/Invoke**: ```csharp [DllImport("dwmapi.dll", PreserveSig = false)] static extern void DwmGetCompositionTimingInfo(IntPtr hwnd, ref DWM_TIMING_INFO pTimingInfo); ``` ### 7.3 方案 ②:自己监听 ETW 的 Present 事件(推荐主力方案) **原理**:订阅 `Microsoft-Windows-DXGI`(GUID `CA11C036-0102-4A2D-A6AD-F03CEDF728C7`)的 Present 事件(Event ID 0),滑动窗口统计单位时间 Present 调用次数 = FPS。 **实现路径**:原生 P/Invoke `advapi32.dll` 的 ETW API(`StartTraceW` → `EnableTraceEx2` → `OpenTrace` → `ProcessTrace`),零依赖。 **优点**: - 零依赖、零体积增量 - 进程级监控(按 PID 过滤前台游戏) - 全屏独占兼容(DXGI Present 事件仍触发) - AOT 原生友好 **缺点**: - 需管理员权限(与 ThingHK 提权体系兼容) - 实现复杂度中高(~400 行):需手工定义 `EVENT_TRACE_PROPERTIES`/`EVENT_RECORD` 等结构体 - DXGI Present 事件 payload 无公开 schema,需参考 PresentMon 源码硬编码偏移 - `ProcessTrace` 是阻塞调用,需后台线程管理生命周期 - 会话未正确清理会残留 ETW 会话影响系统性能 **FPS 计算**:1 秒滑动窗口,500ms 清理过期时间戳,`FPS = 队列长度 / 窗口时长`。 **实现步骤**: 1. 新增 `EtwFpsMonitor.cs`(P/Invoke + 会话管理 + FPS 计算) 2. `KernelHost` 持有实例,启动时检测管理员权限,未提权则 `Fps=null` 3. `Contracts.cs` 的 `SensorSnapshot` 增加 `Fps` 字段,递增 `SchemaVersion` 4. `SamplingScheduler.FastLoopAsync` 每 tick 调用 `SampleFps()` 5. 前端 OSD/详情页增加"FPS"特殊项 **参考**:[PresentMon/IntelPresentMon/PresentData/ETW/Microsoft_Windows_DXGI.cpp](https://github.com/GameTechDev/PresentMon/blob/main/IntelPresentMon/PresentData/ETW/Microsoft_Windows_DXGI.cpp) ### 7.4 方案 ③:PresentMon Service + API(功能最全,但依赖重) **原理**:安装 PresentMon Service(官方 MSI),ThingHK 通过 P/Invoke `PresentMonAPI2.dll` 连接 Service 获取 140+ 指标。 **部署约束(关键)**: - Service 是独立进程,不能"只丢几个 DLL" - 官方明确警告:客户端自带 DLL 不保证与服务二进制兼容,**必须用官方 MSI 安装** - MSI 含 CEF 运行时,安装包 ~30MB,安装后常驻进程 ~30-50MB 内存 - Service 需管理员权限 **API 用法**: ```c pmOpenSession(&hSession); pmStartTrackingProcess(hSession, processId); pmRegisterDynamicQuery(hSession, &hQuery, elements, 2, 1000, 0); pmPollDynamicQuery(hQuery, processId, blob, &numSwapChains); ``` **优点**:功能最全(FPS / 1% Low / 0.1% Low / 帧时间 / GPU 延迟 / 功耗),官方维护。 **缺点**: - 与"minimal installation size, few dependencies"偏好冲突 - 实现复杂度高(~1000+ 行跨栈:C# P/Invoke + Rust 下载安装管理 + 前端) - AOT 下 P/Invoke C API 需验证 - "游戏模式按需下载"实际是下载 MSI 静默安装,体验重 **定位**:不推荐。若用户已自行安装 PresentMon Service,可检测后切换为高精度模式(不在应用内下载安装)。 ### 7.5 方案 ④:RTSS(需安装第三方软件) **原理**:RTSS(RivaTuner Statistics Server)提供共享内存暴露 FPS/帧时间,ThingHK 读取共享内存。 **优点**:成熟稳定,精度高,支持 1% Low。 **缺点**: - 需用户安装 RTSS(商业软件,与 MSi Afterburner 捆绑) - 共享内存 schema 无官方文档,需逆向 - 与"few dependencies"偏好冲突 **定位**:不推荐。仅作为已安装 RTSS 用户的可选对接。 ### 7.6 推荐路线 **分两步走**: 1. **先实现方案 ①(DWM API)**:验证 FPS 展示链路(数据契约 → SSE → 前端 OSD)打通,0 风险、0 依赖。UI 标注"桌面合成 FPS"。 2. **升级为方案 ②(ETW Present)**:替换 `Sample()` 实现,前端无改动,数据契约不变。获得精确的进程级 FPS。 **统一数据契约**(两种方案共用): ```csharp public float? Fps { get; set; } // 实时 FPS(无游戏时为 0 或 null) public uint? FpsProcessId { get; set; } // 目标进程(可选,0=全局) ``` **权限策略**: - 未提权:降级为 DWM API 或返回 null - 已提权:启用 ETW 方案 ### Rust 集成 | 文件 | 职责 | |------|------| | [monitor_kernel.rs](file:///d:/Atie/Gitea/Thing/src-tauri/src/monitor_kernel.rs) | Tauri 侧 Kernel 客户端:复制二进制、构造 `StartProcessParams`、轮询就绪、SSE 订阅、写入熔断 + 自动重连、ShellExecuteW 提权启动 | | [lib.rs](file:///d:/Atie/Gitea/Thing/src-tauri/src/lib.rs) | 7 个 monitor 命令注册 + setup 自动启动 | | [process_manager.rs](file:///d:/Atie/Gitea/Thing/src-tauri/src/process_manager.rs) | 通用进程管理(复用,含崩溃重启 + Job Object) | ### 前端 | 文件 | 职责 | |------|------| | [monitorStore.ts](file:///d:/Atie/Gitea/Thing/src/stores/monitorStore.ts) | Pinia store:状态管理 + 事件订阅 + `connState` 状态机 + `findSensorValue` | | [MonitorModule.vue](file:///d:/Atie/Gitea/Thing/src/modules/monitor/MonitorModule.vue) | 3 Tab UI(概览/详细/设置)+ 关键指标卡 + SVG sparkline + Accordion 分组 | | [index.ts](file:///d:/Atie/Gitea/Thing/src/modules/monitor/index.ts) | 模块配置和 lifecycle 钩子 | ### 二进制 | 文件 | 说明 | |------|------| | [src-tauri/binaries/ThingHK.exe](file:///d:/Atie/Gitea/Thing/src-tauri/binaries/ThingHK.exe) | Kernel AOT 产物(18.3 MB),随安装包分发,运行时复制到 `{app_data_dir}/monitor/cores/ThingHK.exe` | ### 用法 ```bash # 默认 serve(无参数等价) ThingHK.exe serve --port 8730 --basic --fast-ms 1000 --slow-ms 5000 # 传感器覆盖矩阵测试 ThingHK.exe scan --json --basic ``` ## 8. 提权机制 ### 8.1 背景 LibreHardwareMonitor 访问 CPU MSR(温度/时钟)、存储 SMART、SMBus、EC 传感器需要管理员权限。普通权限下可读传感器受限: | 权限 | 分组 | 传感器数 | 可读项 | |------|------|----------|--------| | 普通 | cpu/memory/gpuintel | 62 | CPU 负载/功率、内存、GPU 负载(温度/时钟全 null) | | 管理员 | cpu/memory/gpuintel/storage | 117 | 完整温度/时钟/电压/存储 SMART | ### 7.2 实现方案 采用 **ShellExecuteW "runas"** 弹 UAC 提权启动 Kernel,架构上与普通模式分离: - **启动**:[monitor_kernel.rs](file:///d:/Atie/Gitea/Thing/src-tauri/src/monitor_kernel.rs) `start_elevated` → 停止当前 Kernel → `ShellExecuteW "runas"` 启动管理员 Kernel(隐藏控制台窗口)→ 等待 ready → 重新订阅 SSE - **停止**:调用 Kernel 的 `POST /shutdown` 接口(普通权限无法 `TerminateProcess` 管理员进程) - **状态查询**:通过 HTTP `/status` 判断运行状态(pid 不可用,返回 None) ### 7.3 提权模式限制 | 能力 | 普通模式 | 提权模式 | |------|----------|----------| | 进程管理 | ProcessManager(kill/restart/Job Object) | 不受管控(跨权限级别句柄不可用) | | 崩溃重启 | 3s 巡检 + 自动重启 | 不支持(需手动重新提权) | | 停止方式 | `ProcessManager.stop`(kill) | `POST /shutdown`(优雅退出) | | PID 查询 | 可用 | 不可用(返回 None) | | UAC 弹窗 | 无 | 每次启动需确认 | ### 7.4 C# Kernel /shutdown 接口 [HttpEndpoints.cs](file:///d:/Atie/Gitea/Thing/ThingHK/HttpEndpoints.cs) 增加 `POST /shutdown` 路由,触发 `CancellationTokenSource.Cancel` 让 `app.RunAsync` 优雅退出。响应先返回 200,延迟 100ms 后取消,确保响应体完整发送。 ## 9. 后续可选优化 1. **ProcessManager Mutex 锁阻塞问题**:`check_and_cleanup` 中 800ms `sleep`(端口等待)在持有 `processes` Mutex 锁的情况下执行,会阻塞所有进程状态查询。建议将重启逻辑移出锁作用域(已知问题,与 proxy 模块共享)。 2. **免 UAC 提权**:当前每次提权需弹 UAC,可通过 Windows 任务计划程序注册"以最高权限运行"的任务实现免 UAC 启动(需一次性管理员权限注册任务)。 3. **多机型回归**:AMD Ryzen / NVIDIA GPU / 笔记本场景的实际传感器覆盖度验证(需实际硬件)。 4. **Kernel 版本管理**:`prepare_kernel` 只在文件不存在时复制,不会覆盖更新。建议增加版本比对自动更新。