[U-thread] P5.U-perf-service
[部门] 后端 .NET · ADR-AIOS-08 §2.3 性能监控实装 · skill: dotnet-realtime-communication
[Worker CWD] d:/work/25_claude/workspace/AlgoDepartment/04_development/
[Occupies] P5.K-services-metrics(write · 扩展 MetricsAggregator + 新增 PerModuleMetricsCollector) + P5.K-api-routes(write · 新增 /api/perf/per-module) + P5.K-controllers(write · 新增 PerfController)
[优先级] P1 · 2.0d · 跨栈独立 · 与 P5.U-error-channel(0.5d) / P5.U-runtime-mode-refactor(2.0d) 同 ClaudeB 并行(三 fork 文件正交)
[ADR] d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/ADR/ADR-AIOS-08-xilink-stage-ux.md (§2.3 + §3.2 边界铁律 #5 + §4.1 fork P5.U-perf-service)
[参考文档]
- 上述 ADR 主文(accepted v1.1)· 重点读 §2.3 双层界面(Layer 1 概况 + Layer 2 Per-Module 表格)+ §3.2 边界铁律 #5(perf 数据采集复用现有进程 · 严禁起 daemon)+ §4.1 fork 实施清单
- 同部门标本(强制 read · 4 维度对齐):d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/prompts/done/P5.U-meter-tap-multi-tool--48cf0ba.md(同部门 ClaudeB · 同 P5 · 同 WS 路由 · MeterToolRouter 模式参考)
- **🚨 现状必读(本 fork 核心 · "就地扩展"方向用户已拍板 · 不许新建 PerformanceService 类)**:
· backend_csharp/Services/Metrics/MetricsAggregator.cs(167 行 · Phase 8 已实装 · 重点读全文):
- 第 7-13 行 ModuleMetrics(已定义 InstanceId / ProcessTimeUs / ProcessTimeUs_Ewma / CpuPercentOfBlock)
- 第 15-24 行 MetricsSnapshot(已定义 CpuLoadPercent/Peak/Underrun/Overrun/TotalLatencyMs/Modules/TimestampMs)
- 第 30-167 行 MetricsAggregator(100ms 定时聚合 + EWMA + DspManifestInterop.TryGetMetrics + WS broadcast metrics_update)
- ⚠️ 第 134 行 `Modules = new List<ModuleMetrics>()` 当前永远是空 list(本任务的核心填充点)
· backend_csharp/Interop/DspManifestInterop.cs(看 TryGetMetrics 现状 · 是否能拿 per-module · 如果不能 · 本任务做 EWMA 聚合方案)
· backend_csharp/Services/AudioEngine/AudioEngineService.cs / AudioEngineChainBuilder.cs(看 module 实例如何创建/索引 · 拿 instanceId 列表)
· backend_csharp/Services/AudioEngine/WasapiDrivenEngine.cs(看 RecordBlockTime 调用方 · 看是否能扩展为 per-module 计时)
· backend_csharp/Routes/(看现有 REST 路由风格 · 如 LegacyTestRoutes / MeterDevApiRoutes)
- 上游 ADR:d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/ADR/ADR-AIOS-07-p3-p4-overlap.md(§1.3.4 三层分工铁律 · 必读)
- contract-v1.0(已 frozen · 不修改):d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/contracts/protocol-v1.md(本任务走 dev-api · 待 K2-protocol-v2 正式入)
- 同期派发 P5.U-error-channel(读以同步 namespace · 避免冲突):d:/work/25_claude/workspace/AlgoDepartment/06_docs/site-build/docs/08-implementation/40-aios/prompts/active/P5.U-error-channel.md
【背景】
ADR-AIOS-08 已 accepted v1.1(2026-05-29 17:25)· 用户 2026-05-30 09:00 拍板「方向 A 就地扩展」原话"zhujian"。
🚨 **真值核查发现**(.clinerules v1.3 §🚨 真值核查铁律):
ADR-08 §1.3 第二份 subagent 核查写"无 PerformanceService / MetricsService / TelemetryService"是字面正确(类名确实没有) · 但 **`MetricsAggregator.cs` 已存在 167 行 · 已实装 100ms 定时 metrics_update WS 广播 + EWMA + DspManifestInterop.TryGetMetrics 对接** · 系统级指标 99% 就位 · 仅 per-module 数据未填充(第 134 行空 list)。
**本任务核心**:**扩展不重写**。
- ✅ 把 `MetricsAggregator.MetricsSnapshot.Modules` 字段从空 list 填实(per-module 数据)
- ✅ 新增 `PerModuleMetricsCollector` 服务 · 从 AudioEngine 链路实例 + DLL DspManifestInterop 收集 per-module 指标
- ✅ 新增 REST `GET /api/perf/per-module` · 拉详细模块级表格(ADR §2.3 Layer 2)
- ❌ **不新建 `PerformanceService` 类**(避免与 MetricsAggregator 命名重叠)
- ❌ **不新建 `/ws/perf` channel**(现有 100ms `metrics_update` 已足够 · ADR 1Hz 是字面要求 · 实测 100ms 更好)
- ❌ **不改 100ms 定时频率**(零回归)
**ADR §2.3 双层界面**:
- Layer 1 概况(系统级):CPU% / Mem / Latency / Buffer / TickHz · ✅ 现有 metrics_update 已含 4 项 · 仅缺 Mem 和 TickHz · 本任务补
- Layer 2 详细(per-module):每模块 PeakCPU / AvgCPU / PeakMem / TickRate · ❌ 当前空 · 本任务核心实装
**DLL 配合策略**(.clinerules §跨栈协作):
- DspManifestInterop.TryGetMetrics 当前只暴露聚合 · 若 DLL 不能拿 per-module → 本任务用 .NET 端 RecordBlockTime 现有 EWMA 机制 + module 实例列表 + 模拟 / 估算 per-module(避免阻塞 P6)
- commit message 写 `[need: ClaudeB-P6] expose DSPAlgo_GetPerModuleMetrics(moduleId, out ModuleMetrics)` 留 P6 后续接入(本任务先用估算)
**契约策略**:不修改 contract-v1.0(已 frozen) · `metrics_update` WS 字段扩展(向后兼容)+ `/api/perf/per-module` 走 dev-api 桥接(待 K2-protocol-v2 正式入)。
【执行步骤】
1. 自查 + 读现状(必读 · 本 fork 核心):
git status # 工作区干净
git branch --show-current # = xistudio
git pull origin xistudio --no-rebase # 拿同期派 P5.U-error-channel(若已 push)
# 必读现状(>= 5 文件):
cat backend_csharp/Services/Metrics/MetricsAggregator.cs # 167 行全文
cat backend_csharp/Interop/DspManifestInterop.cs # 看 TryGetMetrics 字段
cat backend_csharp/Services/AudioEngine/AudioEngineChainBuilder.cs # 看 module 实例创建/索引
cat backend_csharp/Services/AudioEngine/WasapiDrivenEngine.cs # 看 RecordBlockTime 调用方
ls backend_csharp/Routes/ # 看 REST 路由风格
2. 扩展 backend_csharp/Services/Metrics/MetricsAggregator.cs(就地扩展 · 不重命名类):
- **保留** Phase 8 现有 100ms 定时 + EWMA + metrics_update 广播(零回归)
- 第 22 行 `MetricsSnapshot` 加可选字段:
· long ProcessMemoryBytes // 系统级内存(System.Diagnostics.Process.GetCurrentProcess().WorkingSet64)
· int TickRateHz // 系统级 Tick 频率(48000 / blockSize · 从 LinkPropagator 拿 blockSize 推算)
- 第 7-13 行 `ModuleMetrics` 加字段:
· double PeakCpuPercent // 模块级峰值 CPU%
· double AvgCpuPercent // 模块级平均 CPU%(沿用 CpuPercentOfBlock 现有概念)
· long PeakMemoryBytes // 模块级峰值内存(估算 · 见 step 3)
· int ModuleTickRateHz // 模块级采样率(从 SignalProvider 拿)
- 第 96-166 行 `Aggregate()` 内部:
· 在现有 systemic 指标聚合后 · 调 PerModuleMetricsCollector.Collect() 填 snapshot.Modules
· WS 广播 metrics_update JSON 增加 modules 数组(每元素含 InstanceId + 4 新字段)· **向后兼容**(旧客户端忽略新字段)
3. 新增 backend_csharp/Services/Metrics/PerModuleMetricsCollector.cs(独立收集器):
- public interface IPerModuleMetricsCollector {
List<ModuleMetrics> Collect(); // 100ms 调一次
ModuleMetrics? GetByInstanceId(string instanceId); // REST 端点用
void RecordModuleBlockTime(string instanceId, double processTimeUs); // 后续 dsp 端可用
}
- 数据来源(优先级):
· ① P6-DLL DspManifestInterop.TryGetPerModuleMetrics(可选 · DLL 未实装则 fallback)
· ② .NET 端 EWMA 估算:从 AudioEngineChainBuilder 拿 module 实例列表 + 复用 MetricsAggregator EWMA · 按 module 占用比例分配
· ③ Memory 用 ProcessMemoryUsage 总值 / module 数(粗估 · 留 P6 精确化)
- ❌ 不做 FFT/RMS/相位等数学(三层分工铁律)· 仅做计数/聚合/字段映射
4. DI 注册(Program.cs):
- AddSingleton<IPerModuleMetricsCollector, PerModuleMetricsCollector>()
- 验证 MetricsAggregator 已注册(应已落)
- 注入 PerModuleMetricsCollector 到 MetricsAggregator(若用构造函数 · 注意循环依赖)
5. 新增 backend_csharp/Models/Perf/(目录新建):
- PerfPerModuleResponse.cs(record · REST /api/perf/per-module 响应):
· List<ModuleMetricsRow> Modules(InstanceId/ModuleType/PeakCpu/AvgCpu/PeakMem/TickRate)
· long TimestampMs
· string SortBy(默认 "PeakCpuDesc" · 可选 "AvgCpu" / "PeakMem")
6. 新增 backend_csharp/Controllers/Perf/PerfController.cs:
- [Route("api/perf")](走 /api/perf/* · 命名空间统一)
- GET /api/perf/per-module:可选 query ?sortBy=PeakCpu|AvgCpu|PeakMem(默认 PeakCpu)· 返回 PerfPerModuleResponse
- 调 IPerModuleMetricsCollector.Collect() · 排序 · 包装返回
- 错误处理:链路未启动返回 200 + 空 modules · 内部错误 500
- **不修改既有** REST(零回归)
7. 单测 + 验收:
cd backend_csharp
# 新增 Tests/Services/Metrics/PerModuleMetricsCollectorTests.cs(>= 4 case:Collect 空链路 + 单 module + 多 module + GetByInstanceId)
# 新增 Tests/Services/Metrics/MetricsAggregatorPerModuleTests.cs(>= 2 case:扩展 Aggregate 后 modules 字段填充 + JSON 序列化向后兼容)
# 新增 Tests/Controllers/Perf/PerfControllerTests.cs(>= 3 case:默认排序 + sortBy=AvgCpu + 空链路 200)
# 至少 9 个新 case
dotnet build # 零错误零警告
dotnet test # 通过 + 新增 >= 9 用例(基线 147/0 → >= 156/0)
# 手动 e2e(可选):
curl http://localhost:5000/api/perf/per-module?sortBy=PeakCpu
8. Commit + push:
git add backend_csharp/Services/Metrics/ \
backend_csharp/Models/Perf/ \
backend_csharp/Controllers/Perf/ \
backend_csharp/Tests/Services/Metrics/ \
backend_csharp/Tests/Controllers/Perf/ \
backend_csharp/Program.cs
git commit -m "..."(见下)
git push origin xistudio
【验收】
- 8-10 文件落地(Services/Metrics ×2 改动 + Models/Perf ×1 + Controllers/Perf ×1 + Tests ×3 + Program.cs DI)
- dotnet build 零错误零警告 · dotnet test 全绿(基线 147/0 → >= 156/0 · +9 用例)
- ✅ **现状未破坏**:旧 metrics_update 100ms 推送(零回归 · 现有 4 字段 cpuLoadPercent/cpuLoadPeak/underrunCount/overrunCount 完整 · 新增 modules 数组 + processMemoryBytes + tickRateHz)
- ✅ **per-module 数据可见**:metrics_update 推送时 modules 数组非空(非空链路场景)· 单测覆盖
- ✅ **REST 端点可达**:GET /api/perf/per-module 返回 200 + 排序正确 · Swagger UI 可见
- ❌ **PR 自查 1**:确认未新建 PerformanceService 类(grep "class PerformanceService" 应零命中)
- ❌ **PR 自查 2**:确认未新建 /ws/perf channel(grep "ws/perf" 应在 Tests 之外零命中)
- ❌ **PR 自查 3**:确认 Aggregate() 100ms 频率未改(diff 第 70 行 TimeSpan.FromMilliseconds(100) 应保持)
- ❌ **PR 自查 4**:确认 .NET 代码零数学(grep MathNet|FFT|fft 在 Tests 之外零命中)· 所有数学走 P6-DLL 或 P7-pysidecar
- 三元组 trailer 完整 · commit hash 7 位
【commit】
commit subject:`feat(P5): fill MetricsAggregator per-module + add /api/perf/per-module REST (in-place expansion · backward-compat) [ADR-AIOS-08 §2.3 fork]`
trailer(必须精确):
[step=1/1] [pid=P5] [uid=U-perf-service] [occupies=P5.K-services-metrics+P5.K-api-routes+P5.K-controllers]
[files=backend_csharp/Services/Metrics/MetricsAggregator.cs, backend_csharp/Services/Metrics/PerModuleMetricsCollector.cs, backend_csharp/Models/Perf/**, backend_csharp/Controllers/Perf/PerfController.cs, backend_csharp/Tests/Services/Metrics/**, backend_csharp/Tests/Controllers/Perf/**, backend_csharp/Program.cs]
[ipc=ws/metrics_update(extended-modules-field), api/perf/per-module]
[need: ClaudeB-P6] expose DSPAlgo_GetPerModuleMetrics(instanceId, out ModuleMetrics) for accurate per-module data (current using .NET EWMA estimation)
回告格式参考 done/P5.U-meter-tap-multi-tool--48cf0ba.md 末尾(同部门 · 同 metrics 模式)。
【禁止】
- ❌ **不许新建 PerformanceService / MetricsService / TelemetryService 类**(用户拍板方向 A 就地扩展 · 必须扩展现有 MetricsAggregator)
- ❌ **不许新建 /ws/perf WS channel**(现有 metrics_update 100ms 已足够)
- ❌ **不许改 100ms 定时频率**(MetricsAggregator 第 70 行 TimeSpan.FromMilliseconds(100) 零修改 · ADR §2.3 1Hz 字面要求让位现实)
- ❌ **不许在 .NET 实装数学计算**(FFT / RMS / 相位 / 功率谱 / 频域分析 全部禁止 · 三层分工铁律 ADR-07 §1.3.4)
- ❌ **不许引入 MathNet.Numerics / Accord.NET / 任何 .NET 数学库**
- ❌ 不许修改 contracts/protocol-v1.md(已 frozen · 留 K2-protocol-v2)
- ❌ 不许修改 MetricsAggregator 第 154-165 行 error_event 广播(同期派 P5.U-error-channel 拥有此区域所有权)
- ❌ 不许改前端代码(P1.U-perf-monitor-frontend 由 ClaudeA 跑)
- ❌ 不许改 P6-dsp_algo / pysidecar 代码(留 commit trailer [need: ClaudeB-P6] 协调)
- ❌ 不许跳验收 · dotnet build/test 任一红必须修到全绿
- ❌ 不许省略三元组 trailer
- ❌ 不许自改本 prompt 文件