diff --git a/ThingHK/ThingHK_GUIDE.md b/ThingHK/ThingHK_GUIDE.md index d264206..1809a08 100644 --- a/ThingHK/ThingHK_GUIDE.md +++ b/ThingHK/ThingHK_GUIDE.md @@ -205,6 +205,139 @@ Rust (Tauri App) <---HTTP / SSE (127.0.0.1:8730)---> ThingHK.exe (独立进程 | [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 集成 | 文件 | 职责 | @@ -237,9 +370,9 @@ ThingHK.exe serve --port 8730 --basic --fast-ms 1000 --slow-ms 5000 ThingHK.exe scan --json --basic ``` -## 7. 提权机制 +## 8. 提权机制 -### 7.1 背景 +### 8.1 背景 LibreHardwareMonitor 访问 CPU MSR(温度/时钟)、存储 SMART、SMBus、EC 传感器需要管理员权限。普通权限下可读传感器受限: @@ -270,7 +403,7 @@ LibreHardwareMonitor 访问 CPU MSR(温度/时钟)、存储 SMART、SMBus、 [HttpEndpoints.cs](file:///d:/Atie/Gitea/Thing/ThingHK/HttpEndpoints.cs) 增加 `POST /shutdown` 路由,触发 `CancellationTokenSource.Cancel` 让 `app.RunAsync` 优雅退出。响应先返回 200,延迟 100ms 后取消,确保响应体完整发送。 -## 8. 后续可选优化 +## 9. 后续可选优化 1. **ProcessManager Mutex 锁阻塞问题**:`check_and_cleanup` 中 800ms `sleep`(端口等待)在持有 `processes` Mutex 锁的情况下执行,会阻塞所有进程状态查询。建议将重启逻辑移出锁作用域(已知问题,与 proxy 模块共享)。 2. **免 UAC 提权**:当前每次提权需弹 UAC,可通过 Windows 任务计划程序注册"以最高权限运行"的任务实现免 UAC 启动(需一次性管理员权限注册任务)。