mirror of
https://github.com/HKUDS/CLI-Anything.git
synced 2026-08-29 07:30:51 +08:00
fix(nsight-graphics): avoid stale gpu trace summaries
This commit is contained in:
@@ -14,7 +14,7 @@ CLI-Anything: Bridging the Gap Between AI Agents and the World's Software</stron
|
||||
<p align="center">
|
||||
<a href="#-quick-start"><img src="https://img.shields.io/badge/Quick_Start-5_min-blue?style=for-the-badge" alt="Quick Start"></a>
|
||||
<a href="https://hkuds.github.io/CLI-Anything/"><img src="https://img.shields.io/badge/CLI_Hub-Browse_%26_Install-ff69b4?style=for-the-badge" alt="CLI Hub"></a>
|
||||
<a href="#-demonstrations"><img src="https://img.shields.io/badge/Demos-16_Apps-green?style=for-the-badge" alt="Demos"></a>
|
||||
<a href="#-demonstrations"><img src="https://img.shields.io/badge/Demos-32_Apps-green?style=for-the-badge" alt="Demos"></a>
|
||||
<a href="#-test-results"><img src="https://img.shields.io/badge/Tests-2%2C162_Passing-brightgreen?style=for-the-badge" alt="Tests"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow?style=for-the-badge" alt="License"></a>
|
||||
</p>
|
||||
@@ -586,7 +586,7 @@ AI agents are great at reasoning but terrible at using real professional softwar
|
||||
| 💸 "UI automation breaks constantly" | No screenshots, no clicking, no RPA fragility. Pure command-line reliability with structured interfaces |
|
||||
| 📊 "Agents need structured data" | Built-in JSON output for seamless agent consumption + human-readable formats for debugging |
|
||||
| 🔧 "Custom integrations are expensive" | One Claude plugin auto-generates CLIs for ANY codebase through proven 7-phase pipeline |
|
||||
| ⚡ "Prototype vs Production gap" | 2,162+ tests with real software validation. Battle-tested across 16 major applications |
|
||||
| ⚡ "Prototype vs Production gap" | 2,162+ tests with real software validation. Battle-tested across 32 major applications |
|
||||
|
||||
---
|
||||
|
||||
@@ -745,7 +745,7 @@ An agent uses the VideoCaptioner CLI to automatically generate and overlay style
|
||||
CLI-Anything works on any software with a codebase — no domain restrictions or architectural limitations.
|
||||
|
||||
### 🏭 Professional-Grade Testing
|
||||
Tested across 16 diverse, complex applications spanning creative, productivity, communication, diagramming, AI image generation, AI content generation, network ad blocking, and local LLM inference domains previously inaccessible to AI agents.
|
||||
Tested across 32 diverse, complex applications spanning creative, productivity, communication, diagramming, AI image generation, AI content generation, network ad blocking, local LLM inference, GPU debugging, and more.
|
||||
|
||||
### 🎨 Diverse Domain Coverage
|
||||
From creative workflows (image editing, 3D modeling, vector graphics) to production tools (audio, office, live streaming, video editing).
|
||||
@@ -1213,7 +1213,7 @@ HARNESS.md is our definitive SOP for making any software agent-accessible via au
|
||||
|
||||
It encodes proven patterns and methodologies refined through automated generation processes.
|
||||
|
||||
The playbook distills key insights from successfully building all 16 diverse, production-ready harnesses.
|
||||
The playbook distills key insights from successfully building all 32 diverse, production-ready harnesses.
|
||||
|
||||
### Critical Lessons
|
||||
|
||||
@@ -1338,7 +1338,7 @@ MIT License — free to use, modify, and distribute.
|
||||
|
||||
**CLI-Anything** — *Make any software with a codebase Agent-native.*
|
||||
|
||||
<sub>A methodology for the age of AI agents | 16 professional software demos | 2,162 passing tests</sub>
|
||||
<sub>A methodology for the age of AI agents | 32 professional software demos | 2,162 passing tests</sub>
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
+16
-7
@@ -9,8 +9,8 @@ CLI-Anything:连接 AI Agent 与全世界软件的桥梁</strong><br>
|
||||
|
||||
<p align="center">
|
||||
<a href="#-快速上手"><img src="https://img.shields.io/badge/快速上手-5_分钟-blue?style=for-the-badge" alt="Quick Start"></a>
|
||||
<a href="#-实测展示"><img src="https://img.shields.io/badge/Demo-11_款软件-green?style=for-the-badge" alt="Demos"></a>
|
||||
<a href="#-测试结果"><img src="https://img.shields.io/badge/测试-1%2C508_通过-brightgreen?style=for-the-badge" alt="Tests"></a>
|
||||
<a href="#-实测展示"><img src="https://img.shields.io/badge/Demo-15_款软件-green?style=for-the-badge" alt="Demos"></a>
|
||||
<a href="#-测试结果"><img src="https://img.shields.io/badge/测试-1%2C660_通过-brightgreen?style=for-the-badge" alt="Tests"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow?style=for-the-badge" alt="License"></a>
|
||||
</p>
|
||||
|
||||
@@ -471,7 +471,7 @@ CLI-Anything 适用于任何有代码库的软件 —— 不限领域,不限
|
||||
|
||||
### 🏭 专业级测试
|
||||
|
||||
在 11 款复杂应用上进行了实测,涵盖创意、生产力、通信、图表和 AI 内容生成领域 —— 这些软件此前对 AI Agent 来说几乎不可触及。
|
||||
在 15 款复杂应用上进行了实测,涵盖创意、生产力、通信、图表、AI 内容生成和 GPU 调试领域 —— 这些软件此前对 AI Agent 来说几乎不可触及。
|
||||
|
||||
### 🎨 覆盖多元领域
|
||||
|
||||
@@ -588,12 +588,19 @@ CLI-Anything 适用于任何有代码库的软件 —— 不限领域,不限
|
||||
<td align="center">✅ 19</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><strong>🟩 <a href="nsight-graphics/agent-harness/">Nsight Graphics CLI</a></strong></td>
|
||||
<td>GPU 调试与性能分析</td>
|
||||
<td><code>cli-anything-nsight-graphics</code></td>
|
||||
<td>官方 ngfx / ngfx-capture 编排 + GPU Trace 摘要</td>
|
||||
<td align="center">✅ 32</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" colspan="4"><strong>合计</strong></td>
|
||||
<td align="center"><strong>✅ 1,527</strong></td>
|
||||
<td align="center"><strong>✅ 1,660</strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> 全部 1,628 项测试 **100% 通过** —— 1,151 项单元测试 + 458 项端到端测试 + 19 项 Node.js 测试。
|
||||
> 全部 1,660 项测试 **100% 通过** —— 1,182 项单元测试 + 459 项端到端测试 + 19 项 Node.js 测试。
|
||||
|
||||
---
|
||||
|
||||
@@ -623,8 +630,9 @@ zoom 22 passed ✅ (22 unit + 0 e2e)
|
||||
drawio 138 passed ✅ (116 unit + 22 e2e)
|
||||
anygen 50 passed ✅ (40 unit + 10 e2e)
|
||||
sketch 19 passed ✅ (19 jest, Node.js)
|
||||
nsight-graphics 32 passed ✅ (31 unit + 1 e2e)
|
||||
──────────────────────────────────────────────────────────────────────────────
|
||||
TOTAL 1,628 passed ✅ 100% pass rate
|
||||
TOTAL 1,660 passed ✅ 100% pass rate
|
||||
```
|
||||
|
||||
---
|
||||
@@ -691,7 +699,8 @@ cli-anything/
|
||||
├── 📞 zoom/agent-harness/ # Zoom CLI(22 项测试)
|
||||
├── 📐 drawio/agent-harness/ # Draw.io CLI(138 项测试)
|
||||
├── ✨ anygen/agent-harness/ # AnyGen CLI(50 项测试)
|
||||
└── 🎨 sketch/agent-harness/ # Sketch CLI(19 项测试,Node.js)
|
||||
├── 🎨 sketch/agent-harness/ # Sketch CLI(19 项测试,Node.js)
|
||||
└── 🟩 nsight-graphics/agent-harness/ # Nsight Graphics CLI(32 项测试)
|
||||
```
|
||||
|
||||
每个 `agent-harness/` 包含一个可安装的 Python 包,位于 `cli_anything.<软件名>/` 下,包含 Click CLI、核心模块、工具类(含 `repl_skin.py` 和后端适配器)以及完整的测试。
|
||||
|
||||
+16
-7
@@ -9,8 +9,8 @@ CLI-Anything: AIエージェントと世界のソフトウェアの架け橋</st
|
||||
|
||||
<p align="center">
|
||||
<a href="#-クイックスタート"><img src="https://img.shields.io/badge/Quick_Start-5_min-blue?style=for-the-badge" alt="クイックスタート"></a>
|
||||
<a href="#-デモンストレーション"><img src="https://img.shields.io/badge/Demos-11_Apps-green?style=for-the-badge" alt="デモ"></a>
|
||||
<a href="#-テスト結果"><img src="https://img.shields.io/badge/Tests-1%2C508_Passing-brightgreen?style=for-the-badge" alt="テスト"></a>
|
||||
<a href="#-デモンストレーション"><img src="https://img.shields.io/badge/Demos-12_Apps-green?style=for-the-badge" alt="デモ"></a>
|
||||
<a href="#-テスト結果"><img src="https://img.shields.io/badge/Tests-1%2C540_Passing-brightgreen?style=for-the-badge" alt="テスト"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow?style=for-the-badge" alt="ライセンス"></a>
|
||||
</p>
|
||||
|
||||
@@ -456,7 +456,7 @@ CLI-Anythingは**GUIベースのエージェントアプローチを完全に置
|
||||
CLI-Anythingはコードベースを持つあらゆるソフトウェアで動作します — ドメインの制限やアーキテクチャの制約はありません。
|
||||
|
||||
### 🏭 プロフェッショナルグレードのテスト
|
||||
クリエイティブ、生産性、コミュニケーション、ダイアグラム、AIコンテンツ生成など、以前はAIエージェントがアクセスできなかった11の多様で複雑なアプリケーションでテスト済み。
|
||||
クリエイティブ、生産性、コミュニケーション、ダイアグラム、AIコンテンツ生成、GPUデバッグなど、以前はAIエージェントがアクセスできなかった12の多様で複雑なアプリケーションでテスト済み。
|
||||
|
||||
### 🎨 多様なドメインカバレッジ
|
||||
クリエイティブワークフロー(画像編集、3Dモデリング、ベクターグラフィックス)からプロダクションツール(オーディオ、オフィス、ライブストリーミング、動画編集)まで。
|
||||
@@ -550,12 +550,19 @@ CLI-Anythingはコードベースを持つあらゆるソフトウェアで動
|
||||
<td align="center">✅ 50</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><strong>🟩 <a href="nsight-graphics/agent-harness/">Nsight Graphics CLI</a></strong></td>
|
||||
<td>GPUデバッグ & プロファイリング</td>
|
||||
<td><code>cli-anything-nsight-graphics</code></td>
|
||||
<td>公式 ngfx / ngfx-capture オーケストレーション + GPU Trace サマリー</td>
|
||||
<td align="center">✅ 32</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" colspan="4"><strong>合計</strong></td>
|
||||
<td align="center"><strong>✅ 1,508</strong></td>
|
||||
<td align="center"><strong>✅ 1,540</strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> **全1,508テストで100%パス** — 1,073ユニットテスト + 435エンドツーエンドテスト。
|
||||
> **全1,540テストで100%パス** — 1,104ユニットテスト + 436エンドツーエンドテスト。
|
||||
|
||||
---
|
||||
|
||||
@@ -583,8 +590,9 @@ shotcut 154 passed ✅ (110 unit + 44 e2e)
|
||||
zoom 22 passed ✅ (22 unit + 0 e2e)
|
||||
drawio 138 passed ✅ (116 unit + 22 e2e)
|
||||
anygen 50 passed ✅ (40 unit + 10 e2e)
|
||||
nsight-graphics 32 passed ✅ (31 unit + 1 e2e)
|
||||
──────────────────────────────────────────────────────────────────────────────
|
||||
合計 1,508 passed ✅ 100% パス率
|
||||
合計 1,540 passed ✅ 100% パス率
|
||||
```
|
||||
|
||||
---
|
||||
@@ -643,7 +651,8 @@ cli-anything/
|
||||
├── 🎬 shotcut/agent-harness/ # Shotcut CLI (154テスト)
|
||||
├── 📞 zoom/agent-harness/ # Zoom CLI (22テスト)
|
||||
├── 📐 drawio/agent-harness/ # Draw.io CLI (138テスト)
|
||||
└── ✨ anygen/agent-harness/ # AnyGen CLI (50テスト)
|
||||
├── ✨ anygen/agent-harness/ # AnyGen CLI (50テスト)
|
||||
└── 🟩 nsight-graphics/agent-harness/ # Nsight Graphics CLI (32テスト)
|
||||
```
|
||||
|
||||
各`agent-harness/`にはClick CLI、コアモジュール、ユーティリティ(`repl_skin.py`とバックエンドラッパーを含む)、包括的なテストを備えた`cli_anything.<software>/`配下のインストール可能なPythonパッケージが含まれています。
|
||||
|
||||
@@ -22,6 +22,8 @@ This harness targets the official command-line interfaces rather than the GUI.
|
||||
- Frame Debugger capture orchestration
|
||||
- GPU Trace capture orchestration
|
||||
- GPU Trace export summarization
|
||||
- newest-complete GPU Trace export selection when an output root contains
|
||||
multiple exports
|
||||
- Generate C++ Capture orchestration
|
||||
- artifact discovery in output directories
|
||||
|
||||
@@ -48,3 +50,7 @@ For one-step performance triage, the intended flow is:
|
||||
2. optionally pin a version with `--nsight-path`
|
||||
3. run `gpu-trace capture --auto-export --summarize`
|
||||
4. inspect the returned summary and the exported `.xls` tables if you need more detail
|
||||
|
||||
If the chosen output root already contains older GPU Trace exports, the harness
|
||||
summarizes the newest complete export directory to avoid mixing stale tables
|
||||
from earlier runs.
|
||||
|
||||
@@ -96,6 +96,11 @@ cli-anything-nsight-graphics gpu-trace summarize ^
|
||||
--input-dir D:\traces
|
||||
```
|
||||
|
||||
`--input-dir` may point either at a specific exported trace directory or at a
|
||||
parent output root that contains multiple exports. When multiple complete GPU
|
||||
Trace exports are present, the CLI summarizes the newest complete export
|
||||
directory so stale tables are not mixed into the result.
|
||||
|
||||
### Generate a C++ capture
|
||||
|
||||
```bash
|
||||
@@ -150,6 +155,8 @@ Capture-producing commands also include:
|
||||
|
||||
When `gpu-trace capture --summarize` is used, the result also includes:
|
||||
|
||||
- `summary.output_dir`
|
||||
- `summary.search_root`
|
||||
- `summary.frame_time_ms`
|
||||
- `summary.fps_estimate`
|
||||
- `summary.metrics`
|
||||
@@ -211,7 +218,8 @@ That gives you:
|
||||
|
||||
- the `.ngfx-gputrace` artifact
|
||||
- exported `FRAME.xls`, `GPUTRACE_FRAME.xls`, and `D3DPERF_EVENTS.xls`
|
||||
- a parsed summary with frame time, estimated FPS, selected counters, and top GPU events
|
||||
- a parsed summary from the newest complete export directory, with frame time,
|
||||
estimated FPS, selected counters, and top GPU events
|
||||
|
||||
## Human + AI Workflow
|
||||
|
||||
|
||||
@@ -21,11 +21,64 @@ SUMMARY_METRICS = {
|
||||
"pcie_throughput_pct": "pcie__throughput.avg.pct_of_peak_sustained_elapsed",
|
||||
}
|
||||
|
||||
REQUIRED_EXPORT_FILES = (
|
||||
"FRAME.xls",
|
||||
"GPUTRACE_FRAME.xls",
|
||||
"D3DPERF_EVENTS.xls",
|
||||
)
|
||||
|
||||
def _find_first(output_dir: str, name: str) -> str | None:
|
||||
"""Find the first matching file under an export directory."""
|
||||
matches = sorted(Path(output_dir).rglob(name))
|
||||
return str(matches[0]) if matches else None
|
||||
|
||||
def _find_export_dir(output_dir: str) -> tuple[str, dict[str, str]]:
|
||||
"""Pick the newest export directory containing a complete GPU Trace export."""
|
||||
output_root = Path(output_dir).resolve()
|
||||
matches_by_name = {
|
||||
name: sorted(output_root.rglob(name))
|
||||
for name in (*REQUIRED_EXPORT_FILES, "GPUTRACE_REGIMES.xls")
|
||||
}
|
||||
|
||||
candidate_dirs = {
|
||||
path.parent
|
||||
for name in REQUIRED_EXPORT_FILES
|
||||
for path in matches_by_name[name]
|
||||
}
|
||||
complete_candidates: list[tuple[int, Path, dict[str, str]]] = []
|
||||
for directory in candidate_dirs:
|
||||
required_paths = [directory / name for name in REQUIRED_EXPORT_FILES]
|
||||
if not all(path.is_file() for path in required_paths):
|
||||
continue
|
||||
newest_required_mtime = max(path.stat().st_mtime_ns for path in required_paths)
|
||||
files = {
|
||||
"frame": str(directory / "FRAME.xls"),
|
||||
"trace_frame": str(directory / "GPUTRACE_FRAME.xls"),
|
||||
"events": str(directory / "D3DPERF_EVENTS.xls"),
|
||||
"regimes": None,
|
||||
}
|
||||
regimes_path = directory / "GPUTRACE_REGIMES.xls"
|
||||
if regimes_path.is_file():
|
||||
files["regimes"] = str(regimes_path)
|
||||
complete_candidates.append((newest_required_mtime, directory, files))
|
||||
|
||||
if complete_candidates:
|
||||
_, export_dir, files = max(
|
||||
complete_candidates,
|
||||
key=lambda item: (item[0], str(item[1])),
|
||||
)
|
||||
return str(export_dir), files
|
||||
|
||||
missing = [
|
||||
name
|
||||
for name in REQUIRED_EXPORT_FILES
|
||||
if not matches_by_name[name]
|
||||
]
|
||||
if missing:
|
||||
raise RuntimeError(
|
||||
"GPU Trace export summary requires exported tables. Missing: "
|
||||
+ ", ".join(missing)
|
||||
)
|
||||
raise RuntimeError(
|
||||
"GPU Trace export summary requires FRAME.xls, GPUTRACE_FRAME.xls, and "
|
||||
"D3DPERF_EVENTS.xls to exist under the same export directory."
|
||||
)
|
||||
|
||||
|
||||
def _read_kv_file(path: str) -> dict[str, str]:
|
||||
@@ -124,25 +177,11 @@ def _make_highlights(
|
||||
def summarize_export_dir(output_dir: str, top_n: int = 10) -> dict[str, Any]:
|
||||
"""Summarize exported GPU Trace tables from an output directory."""
|
||||
output_root = str(Path(output_dir).resolve())
|
||||
frame_path = _find_first(output_root, "FRAME.xls")
|
||||
trace_frame_path = _find_first(output_root, "GPUTRACE_FRAME.xls")
|
||||
events_path = _find_first(output_root, "D3DPERF_EVENTS.xls")
|
||||
regimes_path = _find_first(output_root, "GPUTRACE_REGIMES.xls")
|
||||
|
||||
if not frame_path or not trace_frame_path or not events_path:
|
||||
missing = [
|
||||
name
|
||||
for name, path in {
|
||||
"FRAME.xls": frame_path,
|
||||
"GPUTRACE_FRAME.xls": trace_frame_path,
|
||||
"D3DPERF_EVENTS.xls": events_path,
|
||||
}.items()
|
||||
if not path
|
||||
]
|
||||
raise RuntimeError(
|
||||
"GPU Trace export summary requires exported tables. Missing: "
|
||||
+ ", ".join(missing)
|
||||
)
|
||||
export_dir, files = _find_export_dir(output_root)
|
||||
frame_path = files["frame"]
|
||||
trace_frame_path = files["trace_frame"]
|
||||
events_path = files["events"]
|
||||
regimes_path = files["regimes"]
|
||||
|
||||
frame_data = _read_kv_file(frame_path)
|
||||
trace_metrics = _read_kv_file(trace_frame_path)
|
||||
@@ -180,13 +219,9 @@ def summarize_export_dir(output_dir: str, top_n: int = 10) -> dict[str, Any]:
|
||||
top_level_events = [item for item in top_events if item["depth"] == 0]
|
||||
|
||||
return {
|
||||
"output_dir": output_root,
|
||||
"files": {
|
||||
"frame": frame_path,
|
||||
"trace_frame": trace_frame_path,
|
||||
"events": events_path,
|
||||
"regimes": regimes_path,
|
||||
},
|
||||
"output_dir": export_dir,
|
||||
"search_root": output_root,
|
||||
"files": files,
|
||||
"frame_time_ms": frame_time_ms,
|
||||
"fps_estimate": fps_estimate,
|
||||
"metrics": summary_metrics,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Test Inventory Plan
|
||||
|
||||
- `test_core.py`: 31 unit tests planned
|
||||
- `test_core.py`: 32 unit tests planned
|
||||
- `test_full_e2e.py`: 4 E2E tests planned
|
||||
|
||||
## Unit Test Plan
|
||||
@@ -19,6 +19,7 @@
|
||||
- unified and split command construction
|
||||
- artifact diffing behavior
|
||||
- exported GPU Trace summary parsing
|
||||
- newest-complete GPU Trace export selection when multiple exports share an output root
|
||||
|
||||
### `core/*.py`
|
||||
|
||||
@@ -64,8 +65,8 @@ python -m pytest cli_anything/nsight_graphics/tests -v --tb=no
|
||||
============================= test session starts =============================
|
||||
platform win32 -- Python 3.11.9, pytest-9.0.3, pluggy-1.6.0 -- C:\Users\aimidi\AppData\Local\Programs\Python\Python311\python.exe
|
||||
cachedir: .pytest_cache
|
||||
rootdir: D:\code\D5\CLI-Anything-nsight-graphics-dev\nsight-graphics\agent-harness
|
||||
collecting ... collected 35 items
|
||||
rootdir: C:\Users\aimidi\.codex\worktrees\da29\CLI-Anything\nsight-graphics\agent-harness
|
||||
collecting ... collected 36 items
|
||||
|
||||
cli_anything/nsight_graphics/tests/test_core.py::TestOutputAndErrors::test_output_json PASSED
|
||||
cli_anything/nsight_graphics/tests/test_core.py::TestOutputAndErrors::test_handle_error_debug PASSED
|
||||
@@ -82,6 +83,7 @@ cli_anything/nsight_graphics/tests/test_core.py::TestCommandBuilders::test_build
|
||||
cli_anything/nsight_graphics/tests/test_core.py::TestCommandBuilders::test_build_split_capture_command_maps_wait_seconds PASSED
|
||||
cli_anything/nsight_graphics/tests/test_core.py::TestCommandBuilders::test_diff_snapshots_reports_new_nonempty_files PASSED
|
||||
cli_anything/nsight_graphics/tests/test_core.py::TestCommandBuilders::test_gpu_trace_summary_from_export_dir PASSED
|
||||
cli_anything/nsight_graphics/tests/test_core.py::TestCommandBuilders::test_gpu_trace_summary_prefers_newest_complete_export_dir PASSED
|
||||
cli_anything/nsight_graphics/tests/test_core.py::TestCoreModules::test_frame_capture_uses_unified_ngfx PASSED
|
||||
cli_anything/nsight_graphics/tests/test_core.py::TestCoreModules::test_frame_capture_split_mode_rejects_perf_exports PASSED
|
||||
cli_anything/nsight_graphics/tests/test_core.py::TestCoreModules::test_gpu_trace_requires_arch_for_metric_set PASSED
|
||||
@@ -103,13 +105,13 @@ cli_anything/nsight_graphics/tests/test_full_e2e.py::TestTargetedE2E::test_frame
|
||||
cli_anything/nsight_graphics/tests/test_full_e2e.py::TestTargetedE2E::test_gpu_trace_capture SKIPPED
|
||||
cli_anything/nsight_graphics/tests/test_full_e2e.py::TestTargetedE2E::test_cpp_capture SKIPPED
|
||||
|
||||
======================== 32 passed, 3 skipped in 1.92s ========================
|
||||
======================== 33 passed, 3 skipped in 1.86s ========================
|
||||
```
|
||||
|
||||
## Summary Statistics
|
||||
|
||||
- Total tests collected: 35
|
||||
- Passed: 32
|
||||
- Total tests collected: 36
|
||||
- Passed: 33
|
||||
- Skipped: 3
|
||||
- Pass rate for executed tests: 100%
|
||||
|
||||
@@ -118,3 +120,5 @@ cli_anything/nsight_graphics/tests/test_full_e2e.py::TestTargetedE2E::test_cpp_c
|
||||
- `doctor info` E2E passed against the local Nsight Graphics installation.
|
||||
- Target-dependent E2E scenarios are implemented but currently skipped until
|
||||
`NSIGHT_GRAPHICS_TEST_EXE` (and optional args/workdir) are provided.
|
||||
- GPU Trace summary coverage now includes the case where an output root contains
|
||||
multiple export directories; the newest complete export is selected.
|
||||
|
||||
@@ -172,7 +172,8 @@ class TestBackendDiscovery:
|
||||
assert result["installations"][0]["tool_mode"] == "registered-only"
|
||||
|
||||
def test_list_installations_merges_registry_metadata_into_filesystem_entry(self, tmp_path):
|
||||
install_dir = tmp_path / "Nsight Graphics 2025.1" / "host" / "windows-desktop-nomad-x64"
|
||||
install_root = tmp_path / "Nsight Graphics 2025.1"
|
||||
install_dir = install_root / "host" / "windows-desktop-nomad-x64"
|
||||
install_dir.mkdir(parents=True)
|
||||
(install_dir / "ngfx.exe").write_text("", encoding="utf-8")
|
||||
|
||||
@@ -180,7 +181,7 @@ class TestBackendDiscovery:
|
||||
{
|
||||
"display_name": "NVIDIA Nsight Graphics 2025.1",
|
||||
"display_version": "25.1.0",
|
||||
"install_location": str(install_dir),
|
||||
"install_location": str(install_root),
|
||||
"install_source": "C:/Installers",
|
||||
"uninstall_string": "msiexec /x ...",
|
||||
"publisher": "NVIDIA Corporation",
|
||||
@@ -322,6 +323,58 @@ class TestCommandBuilders:
|
||||
assert summary["top_events"][1]["event"] == "DirectLighting"
|
||||
assert summary["highlights"]
|
||||
|
||||
def test_gpu_trace_summary_prefers_newest_complete_export_dir(self, tmp_path):
|
||||
old_export = tmp_path / "A_old_export"
|
||||
new_export = tmp_path / "B_new_export"
|
||||
old_export.mkdir()
|
||||
new_export.mkdir()
|
||||
|
||||
old_files = {
|
||||
"frame": old_export / "FRAME.xls",
|
||||
"trace": old_export / "GPUTRACE_FRAME.xls",
|
||||
"events": old_export / "D3DPERF_EVENTS.xls",
|
||||
}
|
||||
old_files["frame"].write_text("GPU frame time\t40.0\n", encoding="utf-8")
|
||||
old_files["trace"].write_text(
|
||||
"FE_B.TriageAC.fe__draw_count.sum\t10\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
old_files["events"].write_text(
|
||||
"event_text\ttime_ms\nFrame 1\t40.0\nOldPass\t30.0\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
new_files = {
|
||||
"frame": new_export / "FRAME.xls",
|
||||
"trace": new_export / "GPUTRACE_FRAME.xls",
|
||||
"events": new_export / "D3DPERF_EVENTS.xls",
|
||||
}
|
||||
new_files["frame"].write_text("GPU frame time\t12.5\n", encoding="utf-8")
|
||||
new_files["trace"].write_text(
|
||||
"FE_B.TriageAC.fe__draw_count.sum\t123\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
new_files["events"].write_text(
|
||||
"event_text\ttime_ms\nFrame 2\t12.5\nNewPass\t8.5\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
for path in old_files.values():
|
||||
os.utime(path, ns=(1_000_000_000, 1_000_000_000))
|
||||
for path in new_files.values():
|
||||
os.utime(path, ns=(2_000_000_000, 2_000_000_000))
|
||||
|
||||
summary = gpu_trace.summarize_export_dir(str(tmp_path), top_n=3)
|
||||
|
||||
assert summary["output_dir"] == str(new_export.resolve())
|
||||
assert summary["search_root"] == str(tmp_path.resolve())
|
||||
assert summary["frame_time_ms"] == pytest.approx(12.5)
|
||||
assert summary["metrics"]["draw_count"] == 123
|
||||
assert summary["top_events"][0]["event"] == "NewPass"
|
||||
assert Path(summary["files"]["frame"]).parent == new_export.resolve()
|
||||
assert Path(summary["files"]["trace_frame"]).parent == new_export.resolve()
|
||||
assert Path(summary["files"]["events"]).parent == new_export.resolve()
|
||||
|
||||
|
||||
class TestCoreModules:
|
||||
@patch("cli_anything.nsight_graphics.core.frame.backend.run_with_artifacts")
|
||||
|
||||
+15
-2
@@ -50,6 +50,12 @@ def _dedupe(values: Iterable[str]) -> list[str]:
|
||||
return result
|
||||
|
||||
|
||||
def _normalized_install_key(path: str) -> str:
|
||||
"""Normalize a path for install-root comparisons across slash styles."""
|
||||
normalized = os.path.normcase(os.path.normpath(path))
|
||||
return re.sub(r"[\\/]+", "/", normalized).rstrip("/")
|
||||
|
||||
|
||||
def _scan_directory(directory: str) -> dict[str, str]:
|
||||
"""Scan a directory for known Nsight binaries."""
|
||||
found: dict[str, str] = {}
|
||||
@@ -331,7 +337,7 @@ def list_installations(
|
||||
install_location = record.get("install_location")
|
||||
normalized_location = None
|
||||
if install_location:
|
||||
normalized_location = os.path.normcase(os.path.normpath(install_location))
|
||||
normalized_location = _normalized_install_key(install_location)
|
||||
guessed_version = (
|
||||
_extract_version_from_text(record.get("display_name") or "")
|
||||
or _extract_version_from_text(record.get("display_version") or "")
|
||||
@@ -341,7 +347,14 @@ def list_installations(
|
||||
if normalized_location:
|
||||
for item in installations:
|
||||
item_root = item.get("install_root")
|
||||
if item_root and os.path.normcase(os.path.normpath(item_root)) == normalized_location:
|
||||
if not item_root:
|
||||
continue
|
||||
normalized_root = _normalized_install_key(item_root)
|
||||
if (
|
||||
normalized_root == normalized_location
|
||||
or normalized_root.startswith(normalized_location + "/")
|
||||
or normalized_location.startswith(normalized_root + "/")
|
||||
):
|
||||
matched_entry = item
|
||||
break
|
||||
elif guessed_version:
|
||||
|
||||
Reference in New Issue
Block a user