Spec
機能仕様書: Unity Profilerパフォーマンス計測
機能ID: SPEC-1591581c
作成日: 2025-01-17
ステータス: 下書き
入力: ユーザー説明: "Unity Profilerパフォーマンス計測機能をMCPツールとして提供"
ユーザーシナリオ&テスト (必須)
ユーザーストーリー1 - パフォーマンス計測の記録と保存 (優先度: P1)
開発者やQAエンジニアが、Unityアプリケーションの実行中にパフォーマンスデータを記録し、後から詳細に分析できるようにしたい。記録されたデータはUnityの標準Profiler Windowで開くことができ、CPU使用率、メモリ使用量、レンダリング性能などを時系列で確認できる。
この優先度の理由: パフォーマンス問題の特定と解決には、詳細なプロファイリングデータの記録が不可欠。これがなければパフォーマンス最適化作業そのものが成立しない。
独立テスト: MCPツールでプロファイリングを開始し、数秒間実行してから停止する。保存された.dataファイルがUnity Profiler Windowで正常に開けることを確認することで完全にテスト可能。
受け入れシナリオ:
- 前提 Unityエディタが起動しており、MCPサーバーと接続している、実行 LLMがパフォーマンス計測開始を指示、結果 プロファイリングが開始され、セッションIDが返却される
- 前提 プロファイリングが実行中、実行 LLMがパフォーマンス計測停止を指示、結果 プロファイリングが停止し、.dataファイルが指定された場所に保存される
- 前提 .dataファイルが保存されている、実行 Unity Profiler Windowでファイルを開く、結果 記録された期間のCPU、メモリ、レンダリングなどのプロファイリングデータが時系列グラフで表示される
- 前提 既にプロファイリングが実行中、実行 LLMが再度パフォーマンス計測開始を指示、結果 エラーメッセージが返却され、既存のセッションIDが通知される
ユーザーストーリー2 - リアルタイムメトリクスの取得 (優先度: P2)
開発者が、Unityアプリケーションの実行中にリアルタイムでパフォーマンスメトリクス(CPU時間、メモリ使用量、GC回数、描画コール数など)を取得し、LLMがその場で分析・アドバイスできるようにしたい。
この優先度の理由: .dataファイル保存だけでなく、即座にメトリクスを取得できることで、LLMによる自動診断や、長時間実行中の監視が可能になる。P1で基本機能が確立されているため、P2として価値を追加する。
独立テスト: MCPツールでプロファイリングを開始し、リアルタイムメトリクス取得を要求すると、現在のCPU時間、メモリ使用量、GC回数などのメトリクスがJSON形式で即座に返却されることを確認することでテスト可能。
受け入れシナリオ:
- 前提 Unityエディタが起動している、実行 LLMが利用可能なメトリクス一覧の取得を指示、結果 CPU、メモリ、レンダリング、GCなどのカテゴリ別にメトリクス名の一覧が返却される
- 前提 プロファイリングが実行中、実行 LLMが現在のメトリクス取得を指示、結果 最新のCPU時間、メモリ使用量、GC回数、描画コール数などの数値がJSON形式で返却される
- 前提 プロファイリングが実行中、実行 LLMが特定のメトリクス(例: System Used Memory、Draw Calls Count)のみを指定して取得を指示、結果 指定されたメトリクスの値のみが返却される
ユーザーストーリー3 - プロファイリング状態の確認 (優先度: P3)
開発者が、現在プロファイリングが実行中かどうか、いつ開始されたか、どのくらいの時間記録しているかを確認できるようにしたい。特に長時間実行する場合や、複数のLLMセッションで作業する場合に有用。
この優先度の理由: 状態確認は便利だが、P1とP2の機能だけでも十分に価値を提供できる。長時間監視や複数セッション対応のための補助機能として位置付ける。
独立テスト: プロファイリングを開始した後、状態確認を要求すると、実行中であること、セッションID、開始時刻、経過時間が返却されることを確認することでテスト可能。
受け入れシナリオ:
- 前提 プロファイリングが実行中でない、実行 LLMがプロファイリング状態確認を指示、結果 実行中でないことを示す情報が返却される
- 前提 プロファイリングが実行中、実行 LLMがプロファイリング状態確認を指示、結果 実行中であること、セッションID、開始時刻、経過時間が返却される
- 前提 プロファイリングが実行中、実行 自動停止時間(maxDurationSec)が設定されている場合、結果 状態確認時に残り時間も返却される
エッジケース
- プロファイリング中にUnityエディタがクラッシュした場合、次回起動時に状態がリセットされるか?
- 保存先ディレクトリが存在しない、または書き込み権限がない場合、どのようなエラーメッセージが返されるか?
- プロファイリングを開始せずに停止を要求した場合、適切なエラーメッセージが返されるか?
- 長時間(数時間)のプロファイリングでメモリ使用量が過剰にならないか、自動的に制限されるか?
- 同一セッションIDで複数回停止を要求した場合、どのように処理されるか?
要件 (必須)
機能要件
- FR-001: システムはUnity Profilerによるパフォーマンスデータの記録を開始できる必要がある
- FR-002: システムはパフォーマンスデータの記録を停止し、.data形式でファイルに保存できる必要がある
- FR-003: 保存された.dataファイルはUnity Profiler Windowで開くことができる標準形式である必要がある
- FR-004: システムは現在のプロファイリング状態(実行中/停止中、セッションID、経過時間)を返却できる必要がある
- FR-005: システムは利用可能なパフォーマンスメトリクスの一覧を返却できる必要がある
- FR-006: システムは指定されたメトリクス(CPU時間、メモリ使用量、GC回数、描画コール数など)のリアルタイム値を取得できる必要がある
- FR-007: システムは既にプロファイリングが実行中の場合、新しい記録開始を拒否し、適切なエラーメッセージを返す必要がある
- FR-008: システムはプロファイリングの自動停止時間(maxDurationSec)を設定できる必要がある
- FR-009: 保存されたファイルは既存のスクリーンショット・動画と同じ場所(.unity/capture/)に統一的に保存される必要がある
- FR-010: システムはセッションごとに一意のIDを生成し、停止時にそのIDで特定できる必要がある
主要エンティティ
- プロファイリングセッション: パフォーマンス記録の単位。セッションID、開始時刻、ステータス(実行中/停止)、保存先パスを持つ
- メトリクス: パフォーマンスの測定値。カテゴリ(CPU、メモリ、レンダリング、GCなど)、名前、現在値、単位を持つ
- プロファイルデータファイル: 記録されたパフォーマンスデータを保存する.data形式のファイル。Unity Profiler Windowで開ける
スコープ外 (オプション)
以下の機能は本仕様のスコープ外とし、将来のバージョンで対応予定:
- ディーププロファイリング(Deep Profiling)の自動有効化
- フレーム範囲の指定(特定のフレーム区間のみ記録)
- メトリクスの統計サマリー生成(平均値、最大値、最小値、標準偏差)
- グラフ画像の自動生成
- 複数の.dataファイルの比較機能
- プロファイリングデータのCSVエクスポート
技術制約 (該当する場合)
- Unity Profiler APIはUnityエディタ環境でのみ動作し、ビルド済みアプリケーションでは使用できない
- .dataファイルの保存にはUnityEditorInternal.ProfilerDriver APIを使用する(エディタ専用)
- リアルタイムメトリクス取得にはUnity.Profiling.ProfilerRecorder APIを使用する
- 保存先は.unity/capture/配下に固定され、他の場所への保存はサポートしない
前提条件 (該当する場合)
この機能は以下を前提とします:
- Unityエディタが起動しており、MCPサーバーと正常に接続されている
- .unity/capture/ディレクトリが存在するか、システムが自動作成できる
- ユーザーがプロファイリングを実行する権限を持っている
依存関係 (該当する場合)
この機能は以下に依存します:
- MCPサーバーとUnityエディタ間の双方向通信機能
- ワークスペースルート解決機能(Nodeからの
workspaceRoot 優先/未受領時は .unity/ 探索)
- 既存のハンドラーアーキテクチャ(VideoCapture、Screenshotと同様のパターン)
成功基準 (必須)
以下の成功基準を満たす必要があります:
- LLMがパフォーマンス計測を開始・停止でき、保存された.dataファイルがUnity Profiler Windowで正常に開けること
- リアルタイムメトリクスの取得が1秒以内に完了し、正確な数値が返却されること
- 長時間(10分以上)のプロファイリングでもメモリリークやクラッシュが発生しないこと
- 自動テスト(CI/CD)環境でパフォーマンスリグレッションを検出できること(記録→保存→分析のフローが完全に自動化可能)
- 既存のMCPツール(Screenshot、Video)と同じ操作感で使用でき、保存先も統一されていること
⚡ クイックガイドライン
- ✅ ユーザーが「何を」必要とし「なぜ」必要なのかに焦点を当てる
- ❌ 「どのように」実装するかを避ける (技術スタック、API、コード構造なし)
- 👥 ビジネス関係者向けに記述 (開発者向けではない)
Plan
実装計画: Unity Profilerパフォーマンス計測
機能ID: SPEC-1591581c | 日付: 2025-01-17 | 仕様: spec.md
入力: /specs/SPEC-1591581c/spec.mdの機能仕様
実行フロー (/speckit.plan コマンドのスコープ)
1. 入力パスから機能仕様を読み込み ✅
2. 技術コンテキストを記入 ✅
3. 憲章チェックセクションを評価 → 次のステップ
4. Phase 0 を実行 → research.md
5. Phase 1 を実行 → contracts, data-model.md, quickstart.md
6. 憲章チェックセクションを再評価
7. Phase 2 を計画 → タスク生成アプローチを記述
8. 停止 - /speckit.tasks コマンドの準備完了
概要
Unity Profilerパフォーマンス計測機能をMCPツールとして提供する。LLMがUnityアプリケーションの実行中にプロファイリングを開始/停止し、.dataファイルとして保存したり、リアルタイムメトリクス(CPU、メモリ、GC、描画コール数など)を取得できるようにする。既存のVideoCapture/Screenshot機能と同様のアーキテクチャで実装し、.unity/capture/配下に統一的に保存する。
技術アプローチ:
- Unity側:
UnityEditorInternal.ProfilerDriver(.data保存)+ Unity.Profiling.ProfilerRecorder(リアルタイムメトリクス)
- Node側: BaseToolHandler継承の標準パターン
- 4つのMCPツール:
profiler_start, profiler_stop, profiler_status, profiler_get_metrics
技術コンテキスト
言語/バージョン:
- Unity C# (Unity 2021.3 LTS以上)
- Node.js 18+ (unity-cli)
主要依存関係:
- Unity:
UnityEditorInternal.ProfilerDriver, Unity.Profiling.ProfilerRecorder
- Node:
@modelcontextprotocol/sdk, tcp-jsonrpc-client
ストレージ:
- ファイルシステム(
.unity/capture/*.data および .json)
テスト:
- Node: Vitest (既存テストインフラ)
- Unity: NUnit (Unity Test Framework)
対象プラットフォーム:
プロジェクトタイプ:
- 既存プロジェクト拡張(Unity CLI Bridge)
パフォーマンス目標:
- リアルタイムメトリクス取得: <1秒
- プロファイリング開始/停止: <500ms
- 長時間記録(10分以上)でメモリリークなし
制約:
- Unity Editor環境でのみ動作(ビルド済みアプリ不可)
- 保存先は
.unity/capture/固定
- 既存のVideoCaptureHandler/ScreenshotHandlerと同様のパターン遵守
スケール/スコープ:
- 4つの新規MCPツール
- Unity側1つのHandler(ProfilerHandler.cs)
- Node側4つのToolHandler
憲章チェック
ゲート: Phase 0 research前に合格必須。Phase 1 design後に再チェック。
シンプルさ:
- プロジェクト数: 既存プロジェクトに統合(追加なし)✅
- フレームワークを直接使用? Unity API直接使用、Node SDK直接使用 ✅
- 単一データモデル? セッション状態とメトリクスのみ ✅
- パターン回避? 既存のHandlerパターン踏襲(実証済み) ✅
アーキテクチャ:
- すべての機能をライブラリとして? Unity CLI Bridge Serverアーキテクチャ遵守 ✅
- ライブラリリスト:
unity-cli: MCPツール公開
unity-cli: Unity Editor拡張
- ライブラリごとのCLI: 既存のUnity CLI Bridge Server CLI ✅
- ライブラリドキュメント: 既存のREADME.md/CLAUDE.md拡張 ✅
テスト (妥協不可):
- RED-GREEN-Refactorサイクルを強制? ✅ TDD必須
- Gitコミットはテストが実装より先に表示? ✅ コミット順序遵守
- 順序: Contract→Integration→E2E→Unitを厳密に遵守? ✅
- 実依存関係を使用? ✅ 実Unity接続使用
- Integration testの対象: 新しいProfilerHandler、MCPツール統合 ✅
- 禁止: テスト前の実装、REDフェーズのスキップ ✅
可観測性:
- 構造化ロギング含む? ✅ 既存のログインフラ使用
- エラーコンテキスト十分? ✅ エラーコード、メッセージ、推奨解決策
バージョニング:
- バージョン番号割り当て済み? 既存のsemantic-release使用 ✅
- 変更ごとにBUILDインクリメント?
npm versionコマンド使用 ✅
- 破壊的変更を処理? 新機能追加のみ(非破壊的) ✅
憲章チェック結果: ✅ 合格(違反なし)
プロジェクト構造
ドキュメント (この機能)
specs/SPEC-1591581c/
├── spec.md # 機能仕様書 ✅
├── plan.md # このファイル (進行中)
├── research.md # Phase 0 出力
├── data-model.md # Phase 1 出力
├── quickstart.md # Phase 1 出力
├── contracts/ # Phase 1 出力
│ ├── profiler-start.json
│ ├── profiler-stop.json
│ ├── profiler-status.json
│ └── profiler-get-metrics.json
└── tasks.md # Phase 2 出力 (/speckit.tasks)
ソースコード (リポジトリルート)
unity-cli/
├── src/
│ └── handlers/
│ └── profiler/
│ ├── ProfilerStartToolHandler.js
│ ├── ProfilerStopToolHandler.js
│ ├── ProfilerStatusToolHandler.js
│ └── ProfilerGetMetricsToolHandler.js
└── tests/
├── integration/
│ └── profiler.test.js
└── unit/
└── handlers/
└── profiler/
├── ProfilerStartToolHandler.test.js
├── ProfilerStopToolHandler.test.js
├── ProfilerStatusToolHandler.test.js
└── ProfilerGetMetricsToolHandler.test.js
UnityCliBridge/
└── Packages/
└── unity-cli/
└── Editor/
└── Handlers/
└── ProfilerHandler.cs
構造決定: 既存プロジェクト拡張のため、既存ディレクトリ構造に従う
Phase 0: アウトライン&リサーチ
リサーチタスク
-
ProfilerDriver API詳細調査:
- 決定:
ProfilerDriver.enabled, ProfilerDriver.SaveProfile() を使用
- 理由: Unity標準API、.data形式で保存可能
- 代替案: カスタムプロファイリング実装 → 却下(車輪の再発明)
-
ProfilerRecorder API詳細調査:
- 決定:
ProfilerRecorder.StartNew() でメトリクス取得
- 理由: Unity 2020.2以降の新API、リアルタイム取得に最適
- 代替案: Profiler.GetRuntimeMemorySizeLong等の個別API → 却下(メトリクス種類が限定的)
-
利用可能メトリクス一覧取得方法:
- 決定:
ProfilerRecorderHandle.GetAvailable() を使用
- 理由: すべての利用可能メトリクスを動的に取得可能
- 代替案: ハードコードされたメトリクスリスト → 却下(Unityバージョン間で差異あり)
-
EditorApplication.updateでの定期処理:
- 決定: VideoCapture同様にEditorApplication.updateで定期処理
- 理由: 既存パターン、自動停止機能に必要
- 代替案: コルーチン → 却下(EditorApplication.updateが標準)
-
セッション状態管理:
- 決定: 静的フィールドでセッション状態保持
- 理由: VideoCaptureHandlerと同じパターン、シンプル
- 代替案: ScriptableObject → 却下(過剰設計)
出力: research.md にすべての技術決定を文書化
Phase 1: 設計&契約
1. データモデル (data-model.md)
エンティティ:
-
ProfilerSession (Unity側):
- sessionId: string (GUID)
- isRecording: bool
- startedAt: DateTime
- outputPath: string
- maxDurationSec: double
- recorders: Dictionary<string, ProfilerRecorder>
-
ProfilerMetric (Node/Unity共通):
- category: string (CPU, Memory, Rendering, GC)
- name: string (System Used Memory, Draw Calls Count)
- value: long
- unit: string (bytes, count, milliseconds)
-
ProfilerStartRequest (Node側):
- mode: string (normal, deep) - デフォルト: normal
- recordToFile: bool - デフォルト: true
- metrics: string[] - 記録対象メトリクス(空=すべて)
- maxDurationSec: number - 自動停止時間(0=無制限)
-
ProfilerStartResponse (Unity側):
- sessionId: string
- startedAt: string (ISO 8601)
- isRecording: bool
- outputPath: string | null
-
ProfilerStopResponse (Unity側):
- sessionId: string
- outputPath: string | null
- duration: number (秒)
- frameCount: number
- metrics: ProfilerMetric[] | null
-
ProfilerStatusResponse (Unity側):
- isRecording: bool
- sessionId: string | null
- startedAt: string | null
- elapsedSec: number
- remainingSec: number | null
-
ProfilerMetricsResponse (Unity側):
- categories: {
[categoryName]: {
name: string,
metrics: ProfilerMetric[]
}
}
2. API契約 (/contracts/)
profiler-start.json (MCPツール):
{
"name": "profiler_start",
"description": "Start Unity Profiler recording session",
"inputSchema": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": ["normal", "deep"],
"default": "normal",
"description": "Profiling mode (normal or deep profiling)"
},
"recordToFile": {
"type": "boolean",
"default": true,
"description": "Save profiling data to .data file"
},
"metrics": {
"type": "array",
"items": {"type": "string"},
"description": "Specific metrics to record (empty = all)"
},
"maxDurationSec": {
"type": "number",
"minimum": 0,
"description": "Auto-stop after N seconds (0 = unlimited)"
}
}
}
}
profiler-stop.json (MCPツール):
{
"name": "profiler_stop",
"description": "Stop Unity Profiler recording and save data",
"inputSchema": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"description": "Optional session ID to stop (defaults to current)"
}
}
}
}
profiler-status.json (MCPツール):
{
"name": "profiler_status",
"description": "Get current profiling session status",
"inputSchema": {
"type": "object",
"properties": {}
}
}
profiler-get-metrics.json (MCPツール):
{
"name": "profiler_get_metrics",
"description": "Get available profiler metrics or current values",
"inputSchema": {
"type": "object",
"properties": {
"listAvailable": {
"type": "boolean",
"default": false,
"description": "Return list of available metrics instead of current values"
},
"metrics": {
"type": "array",
"items": {"type": "string"},
"description": "Specific metrics to query (empty = all)"
}
}
}
}
3. 契約テスト (TDD - RED phase)
tests/integration/profiler.test.js:
describe('Profiler Integration Tests', () => {
describe('profiler_start', () => {
it('should start profiling and return session ID', async () => {
// RED: この時点では実装なし、テスト失敗
const result = await mcpClient.callTool('profiler_start', {
mode: 'normal',
recordToFile: true
});
expect(result).toHaveProperty('sessionId');
expect(result).toHaveProperty('isRecording', true);
expect(result).toHaveProperty('startedAt');
});
it('should reject duplicate start', async () => {
await mcpClient.callTool('profiler_start', {});
const result = await mcpClient.callTool('profiler_start', {});
expect(result).toHaveProperty('error');
expect(result.error).toContain('already running');
});
});
describe('profiler_stop', () => {
it('should stop profiling and save .data file', async () => {
await mcpClient.callTool('profiler_start', {recordToFile: true});
const result = await mcpClient.callTool('profiler_stop', {});
expect(result).toHaveProperty('outputPath');
expect(result.outputPath).toMatch(/\.data$/);
expect(fs.existsSync(result.outputPath)).toBe(true);
});
});
describe('profiler_status', () => {
it('should return idle status when not recording', async () => {
const result = await mcpClient.callTool('profiler_status', {});
expect(result).toHaveProperty('isRecording', false);
});
it('should return active status when recording', async () => {
await mcpClient.callTool('profiler_start', {});
const result = await mcpClient.callTool('profiler_status', {});
expect(result).toHaveProperty('isRecording', true);
expect(result).toHaveProperty('sessionId');
expect(result).toHaveProperty('elapsedSec');
});
});
describe('profiler_get_metrics', () => {
it('should return available metrics list', async () => {
const result = await mcpClient.callTool('profiler_get_metrics', {
listAvailable: true
});
expect(result).toHaveProperty('categories');
expect(result.categories).toHaveProperty('Memory');
expect(result.categories).toHaveProperty('Rendering');
});
it('should return current metric values', async () => {
await mcpClient.callTool('profiler_start', {});
const result = await mcpClient.callTool('profiler_get_metrics', {
metrics: ['System Used Memory', 'Draw Calls Count']
});
expect(result.metrics).toHaveLength(2);
expect(result.metrics[0]).toHaveProperty('name');
expect(result.metrics[0]).toHaveProperty('value');
});
});
});
4. Quickstart (quickstart.md)
# Quickstart: Unity Profilerパフォーマンス計測
## 前提条件
- Unity Editor起動中
- Unity CLI Bridge Server接続済み
## 基本的な使い方
### 1. プロファイリング開始
\`\`\`bash
# LLMに指示
"パフォーマンス計測を開始してください"
# 内部的に実行されるMCPツール
profiler_start({
mode: "normal",
recordToFile: true
})
# → {sessionId: "abc123", isRecording: true, startedAt: "2025-01-17T10:00:00Z"}
\`\`\`
### 2. プロファイリング停止と保存
\`\`\`bash
# LLMに指示
"パフォーマンス計測を停止してください"
# 内部的に実行されるMCPツール
profiler_stop({})
# → {sessionId: "abc123", outputPath: ".unity/capture/profile_2025-01-17_10-05-00.data", duration: 300}
\`\`\`
### 3. .dataファイルをUnity Profiler Windowで開く
1. Unity Editor: Window > Analysis > Profiler
2. Profiler Window: Load > `.unity/capture/profile_2025-01-17_10-05-00.data`
3. CPU、メモリ、レンダリング等のグラフを時系列で確認
### 4. リアルタイムメトリクス取得
\`\`\`bash
# LLMに指示
"現在のメモリ使用量と描画コール数を教えてください"
# 内部的に実行されるMCPツール
profiler_get_metrics({
metrics: ["System Used Memory", "Draw Calls Count"]
})
# → {metrics: [{name: "System Used Memory", value: 524288000, unit: "bytes"}, ...]}
\`\`\`
## 検証ステップ
### ユーザーストーリー1検証 (P1)
1. ✅ プロファイリング開始 → セッションID返却
2. ✅ プロファイリング停止 → .dataファイル保存
3. ✅ Unity Profiler Windowで.dataファイルを開く
4. ✅ 重複開始 → エラーメッセージ返却
### ユーザーストーリー2検証 (P2)
1. ✅ 利用可能メトリクス一覧取得
2. ✅ リアルタイムメトリクス取得(JSON形式)
3. ✅ 特定メトリクスのみ取得
### ユーザーストーリー3検証 (P3)
1. ✅ 非記録中の状態確認
2. ✅ 記録中の状態確認(セッションID、経過時間)
3. ✅ 残り時間の確認(maxDurationSec設定時)
\`\`\`
**出力**: data-model.md, /contracts/*, 失敗するテスト, quickstart.md
## Phase 2: タスク計画アプローチ
*このセクションは/speckit.tasksコマンドが実行することを記述*
**タスク生成戦略**:
1. **Setup Tasks**:
- Unity ProfilerHandler.cs作成(空実装)
- Node ProfilerToolHandler 4ファイル作成(空実装)
- ハンドラ登録(unity-cli/src/handlers/index.js、UnityCliBridge.cs)
2. **Contract Test Tasks** [P]:
- profiler_start contract test作成(RED)
- profiler_stop contract test作成(RED)
- profiler_status contract test作成(RED)
- profiler_get_metrics contract test作成(RED)
3. **Core Implementation Tasks**:
- Unity ProfilerHandler.Start実装(GREEN)
- Unity ProfilerHandler.Stop実装(GREEN)
- Unity ProfilerHandler.Status実装(GREEN)
- Unity ProfilerHandler.GetAvailableMetrics実装(GREEN)
- Node ProfilerStartToolHandler実装(GREEN)
- Node ProfilerStopToolHandler実装(GREEN)
- Node ProfilerStatusToolHandler実装(GREEN)
- Node ProfilerGetMetricsToolHandler実装(GREEN)
4. **Integration Tasks**:
- ワークスペースルート解決機能統合
- EditorApplication.update定期処理実装
- ProfilerRecorderによるメトリクス取得実装
5. **Polish Tasks**:
- エラーハンドリング強化
- ログ追加
- README.md更新
**順序戦略**:
- TDD順序: Contract Tests → Implementation
- 並列実行: Contract Tests はすべて [P]
- 依存関係: Unity Handler → Node Handler
**推定出力**: tasks.mdに約25-30個のタスク
## Phase 3+: 今後の実装
*これらのフェーズは/planコマンドのスコープ外*
**Phase 3**: タスク実行 (/speckit.tasksコマンドがtasks.mdを作成)
**Phase 4**: 実装 (TDDサイクルでtasks.mdを実行)
**Phase 5**: 検証 (統合テスト実行、quickstart.md実行、.dataファイル検証)
## 複雑さトラッキング
*憲章チェックに正当化が必要な違反がある場合のみ記入*
| 違反 | 必要な理由 | より単純な代替案が却下された理由 |
|------|-----------|--------------------------------|
| なし | N/A | N/A |
## 進捗トラッキング
*このチェックリストは実行フロー中に更新される*
**フェーズステータス**:
- [ ] Phase 0: Research完了
- [ ] Phase 1: Design完了
- [ ] Phase 2: Task planning完了(アプローチのみ記述)
- [ ] Phase 3: Tasks生成済み (/speckit.tasks)
- [ ] Phase 4: 実装完了
- [ ] Phase 5: 検証合格
**ゲートステータス**:
- [x] 初期憲章チェック: 合格
- [ ] 設計後憲章チェック: 合格
- [ ] すべての要明確化解決済み
- [x] 複雑さの逸脱を文書化済み(違反なし)
---
*憲章 v1.0.0 に基づく - `/docs/constitution.md` 参照*
## Tasks
# タスク: Unity Profilerパフォーマンス計測
**機能ID**: `SPEC-1591581c` | **入力**: `/specs/SPEC-1591581c/`の設計ドキュメント
**前提条件**: plan.md, research.md, data-model.md, contracts/, quickstart.md
## 実行フロー概要
- Setup (T001-T003): プロジェクト構造作成、ハンドラ登録
- Contract Tests (T004-T007): 4つのMCPツールのcontract tests作成 [RED]
- Core Implementation (T008-T015): Unity Handler + Node Handlers実装 [GREEN]
- Integration (T016-T018): EditorUpdate、メトリクス取得、ワークスペース解決
- Polish (T019-T023): Unit tests、エラーハンドリング、ドキュメント
**TDD厳守**: Contract Tests (T004-T007) がすべて失敗することを確認してから Core Implementation (T008-T015) を開始
---
## Phase 3.1: セットアップ
### T001 Unity ProfilerHandler.cs作成(空実装)
**ファイル**: `UnityCliBridge/Packages/unity-cli-bridge/Editor/Handlers/ProfilerHandler.cs`
**タスク**:
- ProfilerHandler.csファイルを作成
- BaseHandlerを継承
- 4つの空メソッドを定義:
- `Start(string mode, bool recordToFile, string[] metrics, double maxDurationSec)`
- `Stop(string sessionId)`
- `GetStatus()`
- `GetAvailableMetrics(bool listAvailable, string[] metrics)`
- コンパイルエラーがないことを確認
**検証**:
- Unity Editorでコンパイルエラーが発生しない
- ProfilerHandlerクラスがBaseHandlerを継承している
**依存関係**: なし
---
### T002 [P] Node ProfilerStartToolHandler.js作成(空実装)
**ファイル**: `unity-cli/src/handlers/profiler/ProfilerStartToolHandler.js`
**タスク**:
- ProfilerStartToolHandler.jsファイルを作成
- BaseToolHandlerを継承
- `handle(args)`メソッドを空実装
- `contracts/profiler-start.json`をスキーマとして参照
**検証**:
- `npm run lint --workspace=unity-cli`がエラーなく完了
- BaseToolHandlerを正しく継承している
**依存関係**: なし [P]
---
### T003 [P] Node ProfilerStopToolHandler.js作成(空実装)
**ファイル**: `unity-cli/src/handlers/profiler/ProfilerStopToolHandler.js`
**タスク**:
- ProfilerStopToolHandler.jsファイルを作成
- BaseToolHandlerを継承
- `handle(args)`メソッドを空実装
- `contracts/profiler-stop.json`をスキーマとして参照
**検証**:
- `npm run lint --workspace=unity-cli`がエラーなく完了
- BaseToolHandlerを正しく継承している
**依存関係**: なし [P]
---
### T004 [P] Node ProfilerStatusToolHandler.js作成(空実装)
**ファイル**: `unity-cli/src/handlers/profiler/ProfilerStatusToolHandler.js`
**タスク**:
- ProfilerStatusToolHandler.jsファイルを作成
- BaseToolHandlerを継承
- `handle(args)`メソッドを空実装
- `contracts/profiler-status.json`をスキーマとして参照
**検証**:
- `npm run lint --workspace=unity-cli`がエラーなく完了
- BaseToolHandlerを正しく継承している
**依存関係**: なし [P]
---
### T005 [P] Node ProfilerGetMetricsToolHandler.js作成(空実装)
**ファイル**: `unity-cli/src/handlers/profiler/ProfilerGetMetricsToolHandler.js`
**タスク**:
- ProfilerGetMetricsToolHandler.jsファイルを作成
- BaseToolHandlerを継承
- `handle(args)`メソッドを空実装
- `contracts/profiler-get-metrics.json`をスキーマとして参照
**検証**:
- `npm run lint --workspace=unity-cli`がエラーなく完了
- BaseToolHandlerを正しく継承している
**依存関係**: なし [P]
---
### T006 ハンドラ登録(Node側)
**ファイル**: `unity-cli/src/handlers/index.js`
**タスク**:
- ProfilerStartToolHandler, ProfilerStopToolHandler, ProfilerStatusToolHandler, ProfilerGetMetricsToolHandlerをインポート
- 4つのhandlerをMCPサーバーに登録
- 既存のVideoCapture/Screenshotハンドラと同じパターンで実装
**検証**:
- MCPサーバー起動時に4つのツールが登録される
- `profiler_start`, `profiler_stop`, `profiler_status`, `profiler_get_metrics`がツールリストに表示される
**依存関係**: T002, T003, T004, T005
---
### T007 ハンドラ登録(Unity側)
**ファイル**: `UnityCliBridge/Packages/unity-cli-bridge/Editor/Core/UnityCliBridge.cs`
**タスク**:
- ProfilerHandlerをインスタンス化
- Unityコマンドハンドラーに`profiler_start`, `profiler_stop`, `profiler_status`, `profiler_get_metrics`を登録
- 既存のVideoCapture/Screenshotハンドラと同じパターンで実装
**検証**:
- Unity Editor起動時にProfilerHandlerが初期化される
- TCPサーバーが4つのプロファイラーコマンドを受信できる
**依存関係**: T001
---
## Phase 3.2: Contract Tests (TDD - RED) ⚠️ Phase 3.3の前に完了必須
**重要**: これらのテストは記述され、実装前に失敗する必要があります。すべてのContract Testsが失敗することを確認してからCore Implementationに進んでください。
---
### T008 [P] profiler_start contract test作成
**ファイル**: `unity-cli/tests/integration/profiler/profiler-start.test.js`
**タスク**:
- `profiler_start`のcontract testを作成
- `contracts/profiler-start.json`に基づいてテストケースを作成:
1. 正常ケース: mode=normal, recordToFile=true → sessionId, isRecording, startedAt, outputPathを返却
2. 正常ケース: mode=deep, recordToFile=false → outputPath=null
3. エラーケース: 既にプロファイリング実行中 → E_ALREADY_RUNNING
4. エラーケース: 無効なmode → E_INVALID_MODE
5. エラーケース: 無効なメトリクス名 → E_INVALID_METRICS
**検証**:
- テストを実行すると5つすべてが失敗する(RED)
- `npm run test:integration --workspace=unity-cli`で実行可能
**依存関係**: T006, T007 [P]
---
### T009 [P] profiler_stop contract test作成
**ファイル**: `unity-cli/tests/integration/profiler/profiler-stop.test.js`
**タスク**:
- `profiler_stop`のcontract testを作成
- `contracts/profiler-stop.json`に基づいてテストケースを作成:
1. 正常ケース: recordToFile=true → sessionId, outputPath, duration, frameCountを返却
2. 正常ケース: recordToFile=false → outputPath=null, metricsが返却される
3. エラーケース: プロファイリング未実行 → E_NOT_RECORDING
4. エラーケース: 無効なsessionId → E_INVALID_SESSION
5. エラーケース: ファイル保存失敗 → E_FILE_IO
**検証**:
- テストを実行すると5つすべてが失敗する(RED)
- `npm run test:integration --workspace=unity-cli`で実行可能
**依存関係**: T006, T007 [P]
---
### T010 [P] profiler_status contract test作成
**ファイル**: `unity-cli/tests/integration/profiler/profiler-status.test.js`
**タスク**:
- `profiler_status`のcontract testを作成
- `contracts/profiler-status.json`に基づいてテストケースを作成:
1. 正常ケース: 未記録 → isRecording=false, sessionId=null, startedAt=null, elapsedSec=0
2. 正常ケース: 記録中 → isRecording=true, sessionId, startedAt, elapsedSec>0
3. 正常ケース: 自動停止設定あり → remainingSec>0
**検証**:
- テストを実行すると3つすべてが失敗する(RED)
- `npm run test:integration --workspace=unity-cli`で実行可能
**依存関係**: T006, T007 [P]
---
### T011 [P] profiler_get_metrics contract test作成
**ファイル**: `unity-cli/tests/integration/profiler/profiler-get-metrics.test.js`
**タスク**:
- `profiler_get_metrics`のcontract testを作成
- `contracts/profiler-get-metrics.json`に基づいてテストケースを作成:
1. 正常ケース: listAvailable=true → categoriesオブジェクトを返却(Memory, Rendering, CPU, GC等)
2. 正常ケース: listAvailable=false, metrics=[] → 全メトリクスの現在値を返却
3. 正常ケース: listAvailable=false, metrics=["System Used Memory", "Draw Calls Count"] → 指定メトリクスのみ返却
4. エラーケース: 無効なメトリクス名 → E_INVALID_METRICS
**検証**:
- テストを実行すると4つすべてが失敗する(RED)
- `npm run test:integration --workspace=unity-cli`で実行可能
**依存関係**: T006, T007 [P]
---
### T012 すべてのContract Testsが失敗することを確認(RED確認)
**タスク**:
- T008-T011のすべてのテストを実行
- すべてのテストが失敗することを確認(RED)
- テスト失敗の原因が「実装がない」ことであることを確認
**検証**:
- `npm run test:integration --workspace=unity-cli -- --profiler`で17個のテストすべてが失敗
- エラーメッセージが「機能未実装」または「ハンドラーが空」であることを確認
**依存関係**: T008, T009, T010, T011
**⚠️ 重要**: このタスクが完了するまでPhase 3.3に進んではいけません
---
## Phase 3.3: Core Implementation (TDD - GREEN) ⚠️ T012完了後のみ
**前提条件**: T012ですべてのContract Testsが失敗していることを確認済み
---
### T013 Unity ProfilerHandler.Start実装
**ファイル**: `UnityCliBridge/Packages/unity-cli-bridge/Editor/Handlers/ProfilerHandler.cs`
**タスク**:
- `Start()`メソッドを実装:
1. 既にプロファイリング実行中の場合 → E_ALREADY_RUNNINGエラーを返却
2. ProfilerDriver.enabledをtrueに設定
3. ProfilerDriver.deepProfilingを設定(mode="deep"の場合)
4. セッションID生成(GUID形式、ハイフンなし)
5. 開始時刻記録(DateTime.UtcNow)
6. recordToFile=trueの場合、outputPath生成(`.unity/capture/profiler_{sessionId}_{timestamp}.data`)
7. maxDurationSec>0の場合、EditorApplication.updateに自動停止処理を登録
8. ProfilerRecorderを初期化(metricsが指定されている場合はそれらのみ、未指定なら全メトリクス)
9. セッション情報を静的フィールドに保存
10. レスポンスJSON生成(sessionId, startedAt, isRecording, outputPath)
**検証**:
- T008の`profiler_start` contract testが合格する(GREEN)
- Unity EditorでProfiler.enabledがtrueになる
- セッションIDがGUID形式で生成される
**依存関係**: T001, T012
---
### T014 Unity ProfilerHandler.Stop実装
**ファイル**: `UnityCliBridge/Packages/unity-cli-bridge/Editor/Handlers/ProfilerHandler.cs`
**タスク**:
- `Stop()`メソッドを実装:
1. プロファイリング未実行の場合 → E_NOT_RECORDINGエラーを返却
2. sessionIdが指定されている場合、現在のセッションIDと一致するか確認(不一致なら→ E_INVALID_SESSION)
3. ProfilerDriver.enabledをfalseに設定
4. recordToFile=trueの場合、ProfilerDriver.SaveProfile(outputPath)で.dataファイル保存
5. recordToFile=falseの場合、ProfilerRecorderから全メトリクス値を取得してJSON化
6. 記録時間計算(duration = DateTime.UtcNow - startedAt)
7. フレーム数カウント取得(ProfilerDriver.lastFrameIndex)
8. EditorApplication.updateから自動停止処理を解除
9. ProfilerRecorderをすべてDispose
10. セッション情報をクリア
11. レスポンスJSON生成(sessionId, outputPath, duration, frameCount, metrics)
**検証**:
- T009の`profiler_stop` contract testが合格する(GREEN)
- .dataファイルが`.unity/capture/`配下に保存される
- Unity Profiler Windowでファイルが正常に開ける
**依存関係**: T013
---
### T015 Unity ProfilerHandler.GetStatus実装
**ファイル**: `UnityCliBridge/Packages/unity-cli-bridge/Editor/Handlers/ProfilerHandler.cs`
**タスク**:
- `GetStatus()`メソッドを実装:
1. 現在のセッション情報を読み取る
2. isRecording=trueの場合:
- sessionId、startedAtを返却
- elapsedSec計算(DateTime.UtcNow - startedAt)
- maxDurationSec>0の場合、remainingSec計算(maxDurationSec - elapsedSec)
3. isRecording=falseの場合:
- sessionId=null, startedAt=null, elapsedSec=0, remainingSec=null
4. レスポンスJSON生成(isRecording, sessionId, startedAt, elapsedSec, remainingSec)
**検証**:
- T010の`profiler_status` contract testが合格する(GREEN)
- 記録中/未記録で正しい値が返却される
- 経過時間が実時間と一致する
**依存関係**: T013
---
### T016 Unity ProfilerHandler.GetAvailableMetrics実装
**ファイル**: `UnityCliBridge/Packages/unity-cli-bridge/Editor/Handlers/ProfilerHandler.cs`
**タスク**:
- `GetAvailableMetrics()`メソッドを実装:
1. listAvailable=trueの場合:
- ProfilerRecorderHandle.GetAvailable()で全メトリクスを取得
- ProfilerCategoryごとにメトリクスをグループ化
- カテゴリオブジェクト生成(categories: {Memory: {name, metrics[]}, Rendering: {name, metrics[]}, ...})
2. listAvailable=falseの場合:
- metricsが指定されている場合はそれらのみ、未指定なら現在記録中の全メトリクスを取得
- ProfilerRecorder.GetSample(0).Valueで現在値を取得
- メトリクス配列生成(metrics: [{category, name, value, unit}, ...])
3. 無効なメトリクス名が指定された場合 → E_INVALID_METRICSエラーを返却
4. レスポンスJSON生成(categories または metrics)
**検証**:
- T011の`profiler_get_metrics` contract testが合格する(GREEN)
- 利用可能なメトリクス一覧が返却される(Memory、Rendering、CPU、GC等のカテゴリ)
- 現在のメトリクス値が正確に返却される
**依存関係**: T013
---
### T017 [P] Node ProfilerStartToolHandler実装
**ファイル**: `unity-cli/src/handlers/profiler/ProfilerStartToolHandler.js`
**タスク**:
- `handle(args)`メソッドを実装:
1. Unityコマンド`profiler_start`を送信
2. 引数にworkspaceRootを追加(既存のVideoCaptureHandlerと同様)
3. args.mode, args.recordToFile, args.metrics, args.maxDurationSecをUnityに転送
4. Unityからのレスポンスをそのまま返却(sessionId, startedAt, isRecording, outputPath)
5. エラーレスポンス処理(E_ALREADY_RUNNING, E_INVALID_MODE, E_INVALID_METRICS)
**検証**:
- MCPクライアントから`profiler_start`ツールを呼び出すと、Unityでプロファイリングが開始される
- workspaceRootが正しく解決される(未受領時は親ディレクトリを遡って `.unity/` を探索)
**依存関係**: T002, T013 [P]
---
### T018 [P] Node ProfilerStopToolHandler実装
**ファイル**: `unity-cli/src/handlers/profiler/ProfilerStopToolHandler.js`
**タスク**:
- `handle(args)`メソッドを実装:
1. Unityコマンド`profiler_stop`を送信
2. 引数にworkspaceRootを追加
3. args.sessionIdをUnityに転送(省略時は現在のセッション)
4. Unityからのレスポンスをそのまま返却(sessionId, outputPath, duration, frameCount, metrics)
5. エラーレスポンス処理(E_NOT_RECORDING, E_INVALID_SESSION, E_FILE_IO)
**検証**:
- MCPクライアントから`profiler_stop`ツールを呼び出すと、Unityでプロファイリングが停止される
- .dataファイルが正しく保存される
**依存関係**: T003, T014 [P]
---
### T019 [P] Node ProfilerStatusToolHandler実装
**ファイル**: `unity-cli/src/handlers/profiler/ProfilerStatusToolHandler.js`
**タスク**:
- `handle(args)`メソッドを実装:
1. Unityコマンド`profiler_status`を送信
2. Unityからのレスポンスをそのまま返却(isRecording, sessionId, startedAt, elapsedSec, remainingSec)
**検証**:
- MCPクライアントから`profiler_status`ツールを呼び出すと、現在の状態が返却される
- 記録中/未記録で正しい値が返る
**依存関係**: T004, T015 [P]
---
### T020 [P] Node ProfilerGetMetricsToolHandler実装
**ファイル**: `unity-cli/src/handlers/profiler/ProfilerGetMetricsToolHandler.js`
**タスク**:
- `handle(args)`メソッドを実装:
1. Unityコマンド`profiler_get_metrics`を送信
2. args.listAvailable, args.metricsをUnityに転送
3. Unityからのレスポンスをそのまま返却(categories または metrics)
4. エラーレスポンス処理(E_INVALID_METRICS)
**検証**:
- MCPクライアントから`profiler_get_metrics`ツールを呼び出すと、メトリクス一覧または現在値が返却される
- カテゴリ別のグループ化が正しく機能する
**依存関係**: T005, T016 [P]
---
### T021 すべてのContract Testsが合格することを確認(GREEN確認)
**タスク**:
- T008-T011のすべてのテストを再実行
- すべてのテストが合格することを確認(GREEN)
- カバレッジが80%以上であることを確認
**検証**:
- `npm run test:integration --workspace=unity-cli -- --profiler`で17個のテストすべてが合格
- `npm run test:coverage --workspace=unity-cli`でカバレッジレポート生成
**依存関係**: T017, T018, T019, T020
**⚠️ 重要**: このタスクが完了するまでPhase 3.4に進んではいけません
---
## Phase 3.4: Integration
---
### T022 EditorApplication.update定期処理実装
**ファイル**: `UnityCliBridge/Packages/unity-cli-bridge/Editor/Handlers/ProfilerHandler.cs`
**タスク**:
- EditorApplication.updateに自動停止処理を登録:
1. maxDurationSec>0の場合、経過時間を監視
2. 経過時間がmaxDurationSecを超えたら自動的にStop()を呼び出す
3. ProfilerRecorderの定期サンプリング(必要に応じて)
4. EditorApplication.updateからの登録解除処理を実装
**検証**:
- maxDurationSec=10で開始し、10秒後に自動停止することを確認
- `profiler_status`でremainingSecがカウントダウンされることを確認
**依存関係**: T013, T014
---
### T023 ProfilerRecorderによるメトリクス取得実装
**ファイル**: `UnityCliBridge/Packages/unity-cli-bridge/Editor/Handlers/ProfilerHandler.cs`
**タスク**:
- ProfilerRecorderを使用したリアルタイムメトリクス取得:
1. Start()時にProfilerRecorder.StartNew()で各メトリクスのRecorderを作成
2. GetAvailableMetrics()でProfilerRecorder.GetSample(0).Valueで現在値を取得
3. Stop()時にすべてのRecorderをDispose
4. メトリクスのカテゴリ分類実装(ProfilerCategory)
**検証**:
- `profiler_get_metrics`で正確なメトリクス値が返却される
- メモリリークが発生しない(Recorder適切にDispose)
**依存関係**: T016
---
### T024 ワークスペースルート解決機能統合
**ファイル**: `UnityCliBridge/Packages/unity-cli-bridge/Editor/Handlers/ProfilerHandler.cs`
**タスク**:
- 既存のWorkspaceRootResolver機能を統合:
1. Node側から受け取ったworkspaceRootを優先使用
2. 未受領時は親ディレクトリを遡って `.unity/` を探索してフォールバック解決
3. outputPath生成時にワークスペースルート + `.unity/capture/`を使用
**検証**:
- .dataファイルが正しいワークスペースルートの`.unity/capture/`配下に保存される
- 既存のVideoCapture/Screenshotと同じ場所に保存される
**依存関係**: T013, T014
---
## Phase 3.5: Polish
---
### T025 [P] Unit Tests作成
**ファイル**: `unity-cli/tests/unit/handlers/profiler/*.test.js`
**タスク**:
- 各ToolHandlerのunit testを作成:
1. ProfilerStartToolHandler.test.js: 引数変換、バリデーション
2. ProfilerStopToolHandler.test.js: sessionId省略時のデフォルト動作
3. ProfilerStatusToolHandler.test.js: レスポンス形式検証
4. ProfilerGetMetricsToolHandler.test.js: メトリクス配列の検証
- モックUnity接続を使用してハンドラーロジックのみテスト
**検証**:
- `npm run test:unit --workspace=unity-cli`ですべてのunit testsが合格
- カバレッジ80%以上を維持
**依存関係**: T017, T018, T019, T020 [P]
---
### T026 エラーハンドリング強化
**ファイル**: `UnityCliBridge/Packages/unity-cli-bridge/Editor/Handlers/ProfilerHandler.cs`
**タスク**:
- 包括的なエラーハンドリング追加:
1. ProfilerDriver.SaveProfile()失敗時 → E_FILE_IO
2. 無効なメトリクス名指定時 → E_INVALID_METRICS(無効な名前のリスト付き)
3. Unity Editorクラッシュ時の状態リセット処理
4. ファイル書き込み権限エラー処理
5. メモリ不足時の対処
**検証**:
- すべてのエラーケースで適切なエラーコードとメッセージが返却される
- Unity Editorクラッシュ後の再起動で状態がクリアされる
**依存関係**: T013, T014, T016
---
### T027 構造化ロギング追加
**ファイル**:
- `UnityCliBridge/Packages/unity-cli-bridge/Editor/Handlers/ProfilerHandler.cs`
- `unity-cli/src/handlers/profiler/*.js`
**タスク**:
- 既存のロギングインフラを使用してログ追加:
1. プロファイリング開始/停止時のINFOログ
2. .dataファイル保存成功時のINFOログ
3. エラー発生時のERRORログ(スタックトレース付き)
4. メトリクス取得時のDEBUGログ(メトリクス名、値)
**検証**:
- Unity Console/Node consoleでログが正しく出力される
- ログレベルが適切(DEBUG/INFO/ERROR)
**依存関係**: T013, T014, T015, T016
---
### T028 [P] README.md更新
**ファイル**: `README.md`
**タスク**:
- Unity Profilerパフォーマンス計測機能のドキュメント追加:
1. 機能概要セクション追加
2. 4つのMCPツールの説明追加
3. quickstart.mdへのリンク追加
4. 既存のVideoCapture/Screenshot機能と同じフォーマットで記載
**検証**:
- `npm run lint:md --workspace=unity-cli`がエラーなく完了
- README.mdがGitHub上で正しくレンダリングされる
**依存関係**: T021 [P]
---
### T029 [P] quickstart.md検証実行
**ファイル**: `specs/SPEC-1591581c/quickstart.md`
**タスク**:
- quickstart.mdに記載されたすべての手順を手動実行:
1. プロファイリング開始
2. プロファイリング停止と.dataファイル保存
3. Unity Profiler Windowでファイルを開く
4. リアルタイムメトリクス取得
5. プロファイリング状態確認
6. エラーケースの検証
**検証**:
- すべての手順が正常に動作する
- Unity Profiler Windowで.dataファイルが正常に開ける
**依存関係**: T021 [P]
---
### T030 リファクタリング(重複削除)
**ファイル**:
- `UnityCliBridge/Packages/unity-cli-bridge/Editor/Handlers/ProfilerHandler.cs`
- `unity-cli/src/handlers/profiler/*.js`
**タスク**:
- コードの重複を削除:
1. セッション状態管理の共通化
2. ワークスペースルート解決ロジックの共通化
3. エラーレスポンス生成の共通化
4. メトリクス名バリデーションの共通化
**検証**:
- すべてのテストが引き続き合格する
- コードカバレッジが維持される
- `npm run lint --workspace=unity-cli`がエラーなく完了
**依存関係**: T021
---
## 依存関係グラフ
Setup (T001-T007)
↓
Contract Tests (T008-T012) [RED確認必須]
↓
Core Implementation (T013-T021) [GREEN確認必須]
↓
Integration (T022-T024)
↓
Polish (T025-T030)
**詳細な依存関係**:
- T001 → T007
- T002, T003, T004, T005 → T006
- T006, T007 → T008, T009, T010, T011
- T008, T009, T010, T011 → T012
- T012 → T013, T014, T015, T016(T012完了まで実装禁止)
- T013 → T014, T015, T016, T022, T024
- T014 → T024
- T016 → T023
- T002, T013 → T017
- T003, T014 → T018
- T004, T015 → T019
- T005, T016 → T020
- T017, T018, T019, T020 → T021
- T021 → T022, T023, T024, T025, T026, T027, T028, T029, T030(T021完了まで統合/仕上げ禁止)
---
## 並列実行例
### Phase 3.1: Setup
T002-T005 を並列実行(すべて異なるファイル)
Task: "Node ProfilerStartToolHandler.js作成(空実装)"
Task: "Node ProfilerStopToolHandler.js作成(空実装)"
Task: "Node ProfilerStatusToolHandler.js作成(空実装)"
Task: "Node ProfilerGetMetricsToolHandler.js作成(空実装)"
### Phase 3.2: Contract Tests
T008-T011 を並列実行(すべて異なるファイル)
Task: "profiler_start contract test作成"
Task: "profiler_stop contract test作成"
Task: "profiler_status contract test作成"
Task: "profiler_get_metrics contract test作成"
### Phase 3.3: Core Implementation (Node側)
T017-T020 を並列実行(すべて異なるファイル)
Task: "Node ProfilerStartToolHandler実装"
Task: "Node ProfilerStopToolHandler実装"
Task: "Node ProfilerStatusToolHandler実装"
Task: "Node ProfilerGetMetricsToolHandler実装"
T025, T028, T029 を並列実行(すべて異なるファイル)
Task: "Unit Tests作成"
Task: "README.md更新"
Task: "quickstart.md検証実行"
---
## 注意事項
### TDD厳守
- **絶対ルール**: T012(すべてのContract Testsが失敗)が完了するまでT013-T021(Core Implementation)を開始しない
- **絶対ルール**: T021(すべてのContract Testsが合格)が完了するまでT022-T030(Integration/Polish)を開始しない
- **コミット順序**: テストコミットが実装コミットより先
### 並列実行
- [P]マーク付きタスクは並列実行可能(異なるファイル、依存関係なし)
- 同じファイルを変更するタスクは順次実行必須
### 各タスク後のアクション
- 実装後、該当するテストを実行して確認
- コミットメッセージはConventional Commits形式
- `npm run lint --workspace=unity-cli`と`npm run test --workspace=unity-cli`を実行してエラーがないことを確認
### エラー対処
- テスト失敗時は実装をロールバックして再度RED-GREEN-Refactorサイクル
- Unity Editorクラッシュ時は状態リセット処理を実装
---
## 検証チェックリスト
**タスク完全性**:
- [x] すべてのcontracts(4つ)に対応するcontract testがある(T008-T011)
- [x] すべてのentities(ProfilerSession, ProfilerMetric)にデータモデルがある(data-model.md)
- [x] すべてのMCPツール(4つ)が実装されている(T013-T020)
- [x] すべてのテストが実装より先にある(T008-T012 → T013-T021)
- [x] 並列タスクは本当に独立している([P]マークのタスクはすべて異なるファイル)
- [x] 各タスクは正確なファイルパスを指定している
- [x] 同じファイルを変更する[P]タスクがない
**TDD検証**:
- [ ] T012完了時点ですべてのContract Testsが失敗している(RED)
- [ ] T021完了時点ですべてのContract Testsが合格している(GREEN)
- [ ] カバレッジが80%以上である
**統合検証**:
- [ ] .dataファイルがUnity Profiler Windowで正常に開ける
- [ ] リアルタイムメトリクスが正確に取得できる
- [ ] 既存のVideoCapture/Screenshot機能と同じ操作感で使用できる
---
**総タスク数**: 30タスク
**並列実行可能タスク数**: 13タスク([P]マーク付き)
**推定実装時間**: Phase 3.1-3.2: 2-3時間、Phase 3.3: 4-6時間、Phase 3.4-3.5: 2-3時間
---
*TDD厳守 - 憲章 v1.0.0 に基づく - `/docs/constitution.md` 参照*
## TDD
_TODO_
## Research
# Research: Unity Profilerパフォーマンス計測
**機能ID**: `SPEC-1591581c` | **日付**: 2025-01-17
## 技術調査結果
### 1. ProfilerDriver API詳細調査
**決定**: `UnityEditorInternal.ProfilerDriver`を使用して.dataファイル保存を実現
**理由**:
- Unity標準APIでエディタ専用のプロファイリング制御が可能
- `.data`形式で保存することでUnity Profiler Windowで直接開ける
- `ProfilerDriver.enabled`で記録開始/停止をシンプルに制御
- `ProfilerDriver.SaveProfile(string path)`で指定パスに保存
**主要API**:
```csharp
using UnityEditorInternal;
// 記録開始
ProfilerDriver.ClearAllFrames();
ProfilerDriver.enabled = true;
// 記録停止と保存
ProfilerDriver.enabled = false;
string outputPath = ".unity/capture/profile_timestamp.data";
ProfilerDriver.SaveProfile(outputPath);
検討した代替案:
- カスタムプロファイリング実装 → 却下理由: 車輪の再発明、Unity標準形式との互換性なし
- Profiler.BeginSample/EndSample → 却下理由: 手動計装が必要、既存コードへの侵入的変更
参考: Unity Documentation - ProfilerDriver
2. ProfilerRecorder API詳細調査
決定: Unity.Profiling.ProfilerRecorderを使用してリアルタイムメトリクス取得
理由:
- Unity 2020.2以降の新しいProfiler APIで、リアルタイムメトリクス取得に最適化
ProfilerRecorder.StartNew()で特定のメトリクスを記録開始
ProfilerRecorder.LastValueで最新の値を即座に取得可能
- オーバーヘッドが小さく、長時間実行に適している
主要API:
using Unity.Profiling;
// メトリクス記録開始
var memoryRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "System Used Memory");
var drawCallsRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Draw Calls Count");
// 値取得
long memoryUsed = memoryRecorder.LastValue; // bytes
long drawCalls = drawCallsRecorder.LastValue; // count
// 記録停止
memoryRecorder.Dispose();
drawCallsRecorder.Dispose();
検討した代替案:
Profiler.GetRuntimeMemorySizeLong()等の個別API → 却下理由: メトリクス種類が限定的(メモリのみ)
Profiler.GetTotalAllocatedMemoryLong()等の旧API → 却下理由: ProfilerRecorderの方が統一的で拡張性が高い
参考: Unity Documentation - ProfilerRecorder
3. 利用可能メトリクス一覧取得方法
決定: ProfilerRecorderHandle.GetAvailable()を使用して動的にメトリクス一覧取得
理由:
- すべての利用可能なProfilerメトリクスを実行時に取得可能
- Unityバージョン間の差異を吸収(ハードコードされたリスト不要)
- カテゴリ別に分類して返却可能(Memory、Rendering、GC等)
主要API:
using Unity.Profiling;
// すべての利用可能メトリクスを取得
var handles = ProfilerRecorderHandle.GetAvailable();
foreach (var handle in handles)
{
var description = ProfilerRecorderHandle.GetDescription(handle);
Debug.Log($"{description.Category}: {description.Name}");
}
主要メトリクス例:
- Memory: System Used Memory, GC Reserved Memory, Texture Memory, Mesh Memory
- Rendering: SetPass Calls Count, Draw Calls Count, Vertices Count, Triangles Count
- CPU/Frame: Main Thread Frame Time, Render Thread Frame Time
- GC: GC Allocated In Frame
検討した代替案:
- ハードコードされたメトリクスリスト → 却下理由: Unityバージョン間で差異があり、メンテナンスコストが高い
参考: Unity Documentation - ProfilerRecorderHandle
4. EditorApplication.updateでの定期処理
決定: EditorApplication.updateイベントを使用して定期処理を実装
理由:
- VideoCaptureHandlerで既に使用されている実証済みパターン
- 自動停止機能(maxDurationSec)の実装に必要
- リアルタイムメトリクスのサンプリングに使用可能
- Unity Editorのメインスレッドで実行され、安全
実装パターン:
using UnityEditor;
private static bool s_IsRecording = false;
private static DateTime s_StartedAt;
private static double s_MaxDurationSec = 0;
public static object Start(JObject parameters)
{
s_MaxDurationSec = parameters["maxDurationSec"]?.ToObject<double>() ?? 0;
// 定期処理登録
EditorApplication.update += OnUpdate;
s_IsRecording = true;
s_StartedAt = DateTime.UtcNow;
return new { sessionId = Guid.NewGuid().ToString("N"), isRecording = true };
}
private static void OnUpdate()
{
if (!s_IsRecording) return;
var elapsed = (DateTime.UtcNow - s_StartedAt).TotalSeconds;
// 自動停止チェック
if (s_MaxDurationSec > 0 && elapsed >= s_MaxDurationSec)
{
Stop(new JObject());
}
}
public static object Stop(JObject parameters)
{
// 定期処理解除
EditorApplication.update -= OnUpdate;
s_IsRecording = false;
return new { sessionId = "...", duration = (DateTime.UtcNow - s_StartedAt).TotalSeconds };
}
検討した代替案:
- コルーチン → 却下理由: EditorApplicationでコルーチンは非標準、EditorApplication.updateの方が一般的
- async/await → 却下理由: 定期処理には不向き、EditorApplication.updateの方がシンプル
参考: Unity Documentation - EditorApplication.update
5. セッション状態管理
決定: 静的フィールドでセッション状態を保持
理由:
- VideoCaptureHandlerと同じパターンでシンプル
- Unityエディタのドメインリロード間で状態をリセット(意図した挙動)
- プロファイリングはエディタセッション内で完結する一時的な操作
状態管理フィールド:
public static class ProfilerHandler
{
private static bool s_IsRecording = false;
private static string s_SessionId = null;
private static DateTime s_StartedAt;
private static string s_OutputPath = null;
private static double s_MaxDurationSec = 0;
private static Dictionary<string, ProfilerRecorder> s_Recorders = new Dictionary<string, ProfilerRecorder>();
}
検討した代替案:
- ScriptableObject → 却下理由: 過剰設計、ファイルI/Oが不要
- EditorPrefs → 却下理由: セッション永続化は不要、エディタ再起動後は状態リセットが正しい挙動
参考: VideoCaptureHandler.csの実装パターン
ベストプラクティス
1. ワークスペースルート解決
既存のResolveWorkspaceRoot()ユーティリティを使用:
private static string ResolveWorkspaceRoot(string projectRoot)
{
string dir = projectRoot;
for (int i = 0; i < 3 && !string.IsNullOrEmpty(dir); i++)
{
if (Directory.Exists(Path.Combine(dir, ".unity")))
{
return dir;
}
var parent = Directory.GetParent(dir);
if (parent == null) break;
dir = parent.FullName;
}
return projectRoot;
}
2. エラーハンドリング
VideoCaptureHandlerと同様のエラーハンドリングパターン:
try
{
if (s_IsRecording)
{
return new { error = "A profiling session is already running.", sessionId = s_SessionId };
}
// 実装...
return new { sessionId = Guid.NewGuid().ToString("N"), isRecording = true };
}
catch (Exception ex)
{
Debug.LogError($"[ProfilerHandler] Start error: {ex.Message}");
return new { error = $"Failed to start profiling: {ex.Message}", code = "E_UNKNOWN" };
}
3. ファイル命名規則
既存パターンに従った命名:
string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
string outputPath = Path.Combine(workspaceRoot, ".unity", "capture", $"profile_{timestamp}.data");
統合ポイント
Node側ハンドラ
既存のBaseToolHandlerパターンを踏襲:
export class ProfilerStartToolHandler extends BaseToolHandler {
constructor(unityConnection) {
super('profiler_start', 'Start Unity Profiler recording', {
type: 'object',
properties: {
mode: { type: 'string', enum: ['normal', 'deep'], default: 'normal' },
recordToFile: { type: 'boolean', default: true },
metrics: { type: 'array', items: { type: 'string' } },
maxDurationSec: { type: 'number', minimum: 0 }
}
});
this.unityConnection = unityConnection;
}
async execute(params) {
const { WORKSPACE_ROOT } = await import('../../core/config.js');
return await this.unityConnection.sendCommand('profiler_start', {
...params,
workspaceRoot: WORKSPACE_ROOT
});
}
}
Unity側コマンドルーティング
UnityCliBridge.csに追加:
case "profiler_start":
var pStart = ProfilerHandler.Start(command.Parameters);
response = Response.SuccessResult(command.Id, pStart);
break;
case "profiler_stop":
var pStop = ProfilerHandler.Stop(command.Parameters);
response = Response.SuccessResult(command.Id, pStop);
break;
case "profiler_status":
var pStatus = ProfilerHandler.Status(command.Parameters);
response = Response.SuccessResult(command.Id, pStatus);
break;
case "profiler_get_metrics":
var pMetrics = ProfilerHandler.GetAvailableMetrics(command.Parameters);
response = Response.SuccessResult(command.Id, pMetrics);
break;
結論
すべての技術的不明点を解決し、既存のVideoCaptureHandler/ScreenshotHandlerと同様のアーキテクチャで実装可能であることを確認しました。Unity標準APIを使用することで、シンプルかつ保守性の高い実装が可能です。
次のステップ: Phase 1(設計&契約)に進む準備完了
Data Model
Data Model: Unity Profilerパフォーマンス計測
機能ID: SPEC-1591581c | 日付: 2025-01-17
エンティティ概要
本機能では3つの主要エンティティを定義します:
- ProfilerSession: プロファイリングセッションの状態管理
- ProfilerMetric: 個別のパフォーマンスメトリクス
- ProfilerMetricsCategory: メトリクスのカテゴリ分類
1. ProfilerSession (Unity側)
プロファイリングセッションの状態を保持するエンティティ。
フィールド
| フィールド名 |
型 |
説明 |
検証ルール |
| sessionId |
string |
セッションの一意識別子(GUID形式) |
必須、32文字のhex文字列 |
| isRecording |
bool |
記録中かどうか |
必須 |
| startedAt |
DateTime |
記録開始時刻(UTC) |
必須 |
| outputPath |
string |
保存先ファイルパス(.data) |
recordToFile=trueの場合必須 |
| maxDurationSec |
double |
自動停止時間(秒、0=無制限) |
≥0 |
| recorders |
Dictionary<string, ProfilerRecorder> |
アクティブなProfilerRecorderインスタンス |
内部使用のみ |
状態遷移
[Idle] --Start--> [Recording] --Stop--> [Completed]
|
+--Auto Stop (maxDurationSec)--> [Completed]
検証ルール
sessionId: GUID形式(ハイフンなし、32文字hex)
startedAt: 過去の時刻でなければならない
maxDurationSec: 0以上(0は無制限)
outputPath: .unity/capture/配下、.data拡張子
C#実装例
public class ProfilerSession
{
public string SessionId { get; set; }
public bool IsRecording { get; set; }
public DateTime StartedAt { get; set; }
public string OutputPath { get; set; }
public double MaxDurationSec { get; set; }
public Dictionary<string, ProfilerRecorder> Recorders { get; set; }
public double ElapsedSeconds => (DateTime.UtcNow - StartedAt).TotalSeconds;
public double? RemainingSeconds => MaxDurationSec > 0 ? Math.Max(0, MaxDurationSec - ElapsedSeconds) : null;
}
2. ProfilerMetric (Node/Unity共通)
個別のパフォーマンスメトリクスを表すエンティティ。
フィールド
| フィールド名 |
型 |
説明 |
検証ルール |
| category |
string |
カテゴリ名(Memory、Rendering、GC等) |
必須、非空文字列 |
| name |
string |
メトリクス名(System Used Memory等) |
必須、非空文字列 |
| value |
long |
メトリクス値 |
必須 |
| unit |
string |
単位(bytes、count、milliseconds等) |
必須、非空文字列 |
主要カテゴリとメトリクス
Memory:
- System Used Memory (bytes)
- GC Reserved Memory (bytes)
- Texture Memory (bytes)
- Mesh Memory (bytes)
Rendering:
- SetPass Calls Count (count)
- Draw Calls Count (count)
- Vertices Count (count)
- Triangles Count (count)
CPU/Frame:
- Main Thread Frame Time (milliseconds)
- Render Thread Frame Time (milliseconds)
GC:
- GC Allocated In Frame (bytes)
検証ルール
category: ProfilerCategoryの有効な値
name: ProfilerRecorderHandle.GetAvailable()で取得可能な名前
value: 負数不可(ただし一部メトリクスでは0可)
unit: 標準単位(bytes, count, milliseconds, seconds, percentage)
JSON形式例
{
"category": "Memory",
"name": "System Used Memory",
"value": 524288000,
"unit": "bytes"
}
3. ProfilerMetricsCategory (Unity側)
メトリクスをカテゴリ別に分類して返却するためのエンティティ。
フィールド
| フィールド名 |
型 |
説明 |
検証ルール |
| name |
string |
カテゴリ名 |
必須、非空文字列 |
| metrics |
ProfilerMetric[] |
カテゴリ内のメトリクスリスト |
必須、空配列可 |
JSON形式例
{
"categories": {
"Memory": {
"name": "Memory",
"metrics": [
{"category": "Memory", "name": "System Used Memory", "value": 524288000, "unit": "bytes"},
{"category": "Memory", "name": "GC Reserved Memory", "value": 104857600, "unit": "bytes"}
]
},
"Rendering": {
"name": "Rendering",
"metrics": [
{"category": "Rendering", "name": "Draw Calls Count", "value": 128, "unit": "count"}
]
}
}
}
リクエスト/レスポンスモデル
ProfilerStartRequest (Node → Unity)
interface ProfilerStartRequest {
mode?: 'normal' | 'deep'; // デフォルト: 'normal'
recordToFile?: boolean; // デフォルト: true
metrics?: string[]; // 空=すべて
maxDurationSec?: number; // デフォルト: 0 (無制限)
workspaceRoot: string; // 自動付与
}
検証ルール:
mode: 'normal' または 'deep'のみ
recordToFile: boolean
metrics: 有効なメトリクス名の配列(ProfilerRecorderHandle.GetAvailable()で検証)
maxDurationSec: 0以上
ProfilerStartResponse (Unity → Node)
interface ProfilerStartResponse {
sessionId: string;
startedAt: string; // ISO 8601形式
isRecording: boolean;
outputPath: string | null; // recordToFile=falseの場合null
}
検証ルール:
sessionId: GUID形式
startedAt: ISO 8601形式(例: "2025-01-17T10:00:00.000Z")
isRecording: 常にtrue(成功時)
outputPath: recordToFile=trueの場合は非null、.data拡張子
ProfilerStopRequest (Node → Unity)
interface ProfilerStopRequest {
sessionId?: string; // 省略時は現在のセッション
workspaceRoot: string; // 自動付与
}
検証ルール:
sessionId: 存在する場合は有効なGUID形式
ProfilerStopResponse (Unity → Node)
interface ProfilerStopResponse {
sessionId: string;
outputPath: string | null;
duration: number; // 秒
frameCount: number;
metrics: ProfilerMetric[] | null; // recordToFile=falseの場合のみ返却
}
検証ルール:
sessionId: GUID形式
outputPath: recordToFile=trueの場合は非null
duration: 0以上
frameCount: 0以上
metrics: recordToFile=falseの場合のみ配列、それ以外はnull
ProfilerStatusRequest (Node → Unity)
interface ProfilerStatusRequest {
// パラメータなし
}
ProfilerStatusResponse (Unity → Node)
interface ProfilerStatusResponse {
isRecording: boolean;
sessionId: string | null;
startedAt: string | null; // ISO 8601形式
elapsedSec: number;
remainingSec: number | null; // maxDurationSec=0の場合null
}
検証ルール:
isRecording: 記録中はtrue、それ以外はfalse
sessionId: 記録中の場合は非null、GUID形式
startedAt: 記録中の場合は非null、ISO 8601形式
elapsedSec: 0以上
remainingSec: maxDurationSec>0の場合は0以上、それ以外はnull
ProfilerGetMetricsRequest (Node → Unity)
interface ProfilerGetMetricsRequest {
listAvailable?: boolean; // デフォルト: false
metrics?: string[]; // 空=すべて
workspaceRoot: string; // 自動付与
}
検証ルール:
listAvailable: boolean
metrics: 有効なメトリクス名の配列
ProfilerGetMetricsResponse (Unity → Node)
listAvailable=trueの場合:
interface ProfilerGetMetricsResponse {
categories: {
[categoryName: string]: {
name: string;
metrics: ProfilerMetric[];
}
}
}
listAvailable=falseの場合:
interface ProfilerGetMetricsResponse {
metrics: ProfilerMetric[];
}
エラーレスポンス
すべてのリクエストで以下の形式のエラーレスポンスを返却可能:
interface ErrorResponse {
error: string; // エラーメッセージ
code?: string; // エラーコード(例: "E_ALREADY_RUNNING")
sessionId?: string; // 既存セッションID(重複開始時)
}
エラーコード一覧:
E_ALREADY_RUNNING: 既にプロファイリング実行中
E_NOT_RECORDING: プロファイリング未実行
E_INVALID_MODE: 無効なmode指定
E_INVALID_METRICS: 無効なメトリクス名
E_FILE_IO: ファイル保存エラー
E_UNKNOWN: その他のエラー
関係図
ProfilerSession (1) --- (*) ProfilerMetric
|
+--- (1) ProfilerMetricsCategory
|
+--- (*) ProfilerMetric
- ProfilerSessionは複数のProfilerMetricを記録
- ProfilerMetricsCategoryは複数のProfilerMetricをカテゴリ別にグループ化
永続化
- ProfilerSession: 静的フィールドで一時保持(エディタセッション内のみ)
- ProfilerMetric: メモリ内のみ(リアルタイム取得)
- .dataファイル: Unity標準形式で永続化(Unity Profiler Windowで開ける)
結論
すべてのエンティティは既存の機能仕様の要件を満たし、Unity標準APIと整合性があります。シンプルで拡張性のあるデータモデルです。
Quickstart
Profiler Performance Measurement - Quick Start Guide
This guide helps you get started with Unity Profiler performance measurement via MCP tools.
Prerequisites
- Unity Editor with
unity-cli package installed
- MCP client connected to Unity Editor
- Unity project running (Play Mode or Edit Mode)
Basic Workflow
1. Start Profiling Session
// Start profiling in normal mode (standard profiling)
const startResult = await client.callTool('profiler_start', {
mode: 'normal', // 'normal' or 'deep' (deep = more detailed but higher overhead)
recordToFile: true, // Save .data file for analysis in Unity Profiler Window
maxDurationSec: 60 // Auto-stop after 60 seconds (optional, 0 = unlimited)
});
console.log(startResult);
// {
// sessionId: "a1b2c3d4...",
// startedAt: "2025-11-17T15:00:00.000Z",
// isRecording: true,
// outputPath: ".unity/capture/profiler_a1b2c3d4..._2025-11-17_15-00-00.data"
// }
2. Check Status (Optional)
// Get current profiling status
const status = await client.callTool('profiler_status', {});
console.log(status);
// {
// isRecording: true,
// sessionId: "a1b2c3d4...",
// startedAt: "2025-11-17T15:00:00.000Z",
// elapsedSec: 15.5,
// remainingSec: 44.5 // null if no maxDurationSec set
// }
3. Stop Profiling
// Stop profiling session
const stopResult = await client.callTool('profiler_stop', {
sessionId: "a1b2c3d4..." // Optional: specify session to stop (current session if omitted)
});
console.log(stopResult);
// {
// sessionId: "a1b2c3d4...",
// outputPath: ".unity/capture/profiler_a1b2c3d4..._2025-11-17_15-00-00.data",
// duration: 60.2,
// frameCount: 1805,
// metrics: null // Non-null if recordToFile=false
// }
4. Analyze Results
- Open Unity Editor
- Window → Analysis → Profiler
- Click "Load" button
- Select
.unity/capture/profiler_*.data file
- Analyze CPU, memory, rendering, and GC metrics
Advanced Usage
Real-time Metrics (No File Output)
// Start profiling without saving .data file
const startResult = await client.callTool('profiler_start', {
mode: 'normal',
recordToFile: false, // Skip file output
metrics: [ // Optional: specific metrics to record
'System Used Memory',
'Draw Calls Count',
'GC Allocated In Frame'
]
});
// Stop and get metrics
const stopResult = await client.callTool('profiler_stop', {});
console.log(stopResult.metrics);
// [
// { name: "System Used Memory", value: 524288000, unit: "bytes" },
// { name: "Draw Calls Count", value: 120, unit: "count" },
// { name: "GC Allocated In Frame", value: 8192, unit: "bytes" }
// ]
List Available Metrics
// Get all available metrics grouped by category
const metricsResult = await client.callTool('profiler_get_metrics', {
listAvailable: true
});
console.log(metricsResult.categories);
// {
// "Memory": ["System Used Memory", "GC Allocated In Frame", ...],
// "Rendering": ["Draw Calls Count", "Triangles Count", ...],
// "Scripts": ["Scripts Update Time", ...],
// ...
// }
Query Current Metric Values
// Query specific metrics (current frame values)
const metricsResult = await client.callTool('profiler_get_metrics', {
listAvailable: false,
metrics: ['System Used Memory', 'Draw Calls Count']
});
console.log(metricsResult.metrics);
// [
// { name: "System Used Memory", value: 524288000, unit: "bytes" },
// { name: "Draw Calls Count", value: 120, unit: "count" }
// ]
Deep Profiling
// Start deep profiling (more detailed analysis, higher overhead)
const startResult = await client.callTool('profiler_start', {
mode: 'deep', // Enable deep profiling
recordToFile: true,
maxDurationSec: 30 // Shorter duration recommended for deep profiling
});
// Note: Deep profiling records all function calls (including internal Unity calls)
// This provides more detailed analysis but significantly impacts performance
Error Handling
Common Errors
-
E_ALREADY_RUNNING: Another profiling session is already running
// Solution: Stop current session first
await client.callTool('profiler_stop', {});
-
E_INVALID_MODE: Invalid mode parameter
// Valid modes: 'normal' or 'deep'
await client.callTool('profiler_start', { mode: 'normal' });
-
E_INVALID_METRICS: Invalid metric names
// Solution: Query available metrics first
const available = await client.callTool('profiler_get_metrics', { listAvailable: true });
-
E_NOT_RECORDING: No profiling session is running
// Solution: Start profiling first
await client.callTool('profiler_start', {});
-
E_INVALID_SESSION: Session ID does not match current session
// Solution: Use correct session ID or omit parameter
const status = await client.callTool('profiler_status', {});
await client.callTool('profiler_stop', { sessionId: status.sessionId });
-
E_FILE_IO: Failed to save .data file
// Solution: Check disk space and .unity/capture/ directory permissions
Best Practices
-
Use Normal Mode First: Start with mode: 'normal' for general profiling. Only use mode: 'deep' when you need detailed function-level analysis.
-
Set Auto-stop Duration: Always set maxDurationSec to prevent unlimited recording that may consume disk space.
-
Profile During Gameplay: Start profiling during actual gameplay scenarios for realistic performance data.
-
Analyze .data Files: Use Unity Profiler Window to visualize and analyze .data files. The MCP tools provide raw data collection, but Unity's built-in tools offer better visualization.
-
Real-time Metrics for Automation: Use recordToFile: false with specific metrics for automated performance testing or CI/CD pipelines.
-
Check Status Before Stopping: Query profiler_status to confirm session is running before calling profiler_stop.
Troubleshooting
Profiling Session Won't Start
- Check Unity Editor: Ensure Unity Editor is running and connected
- Check Existing Session: Another session may be running. Call
profiler_status and profiler_stop if needed.
.data File Not Saved
- Check Output Path: Verify
.unity/capture/ directory exists and is writable
- Check Disk Space: Ensure sufficient disk space for profiling data
- Check recordToFile: Confirm
recordToFile: true was set in profiler_start
Metrics Not Available
- Platform-Specific: Some metrics are platform-specific (e.g., mobile-only metrics)
- Query Available: Use
profiler_get_metrics with listAvailable: true to check available metrics
Performance Impact
- Deep Profiling Overhead:
mode: 'deep' has significant performance overhead. Use short durations (10-30 seconds).
- Metric Count: Recording many metrics increases overhead. Limit to essential metrics for real-time profiling.
Next Steps
Contracts
Migrated from local files. See artifact comments below.
Checklists
Artifact files under checklists/ are managed in issue comments with checklist:<name> entries.
Acceptance Checklist
Spec
機能仕様書: Unity Profilerパフォーマンス計測
機能ID:
SPEC-1591581c作成日: 2025-01-17
ステータス: 下書き
入力: ユーザー説明: "Unity Profilerパフォーマンス計測機能をMCPツールとして提供"
ユーザーシナリオ&テスト (必須)
ユーザーストーリー1 - パフォーマンス計測の記録と保存 (優先度: P1)
開発者やQAエンジニアが、Unityアプリケーションの実行中にパフォーマンスデータを記録し、後から詳細に分析できるようにしたい。記録されたデータはUnityの標準Profiler Windowで開くことができ、CPU使用率、メモリ使用量、レンダリング性能などを時系列で確認できる。
この優先度の理由: パフォーマンス問題の特定と解決には、詳細なプロファイリングデータの記録が不可欠。これがなければパフォーマンス最適化作業そのものが成立しない。
独立テスト: MCPツールでプロファイリングを開始し、数秒間実行してから停止する。保存された.dataファイルがUnity Profiler Windowで正常に開けることを確認することで完全にテスト可能。
受け入れシナリオ:
ユーザーストーリー2 - リアルタイムメトリクスの取得 (優先度: P2)
開発者が、Unityアプリケーションの実行中にリアルタイムでパフォーマンスメトリクス(CPU時間、メモリ使用量、GC回数、描画コール数など)を取得し、LLMがその場で分析・アドバイスできるようにしたい。
この優先度の理由: .dataファイル保存だけでなく、即座にメトリクスを取得できることで、LLMによる自動診断や、長時間実行中の監視が可能になる。P1で基本機能が確立されているため、P2として価値を追加する。
独立テスト: MCPツールでプロファイリングを開始し、リアルタイムメトリクス取得を要求すると、現在のCPU時間、メモリ使用量、GC回数などのメトリクスがJSON形式で即座に返却されることを確認することでテスト可能。
受け入れシナリオ:
ユーザーストーリー3 - プロファイリング状態の確認 (優先度: P3)
開発者が、現在プロファイリングが実行中かどうか、いつ開始されたか、どのくらいの時間記録しているかを確認できるようにしたい。特に長時間実行する場合や、複数のLLMセッションで作業する場合に有用。
この優先度の理由: 状態確認は便利だが、P1とP2の機能だけでも十分に価値を提供できる。長時間監視や複数セッション対応のための補助機能として位置付ける。
独立テスト: プロファイリングを開始した後、状態確認を要求すると、実行中であること、セッションID、開始時刻、経過時間が返却されることを確認することでテスト可能。
受け入れシナリオ:
エッジケース
要件 (必須)
機能要件
主要エンティティ
スコープ外 (オプション)
以下の機能は本仕様のスコープ外とし、将来のバージョンで対応予定:
技術制約 (該当する場合)
前提条件 (該当する場合)
この機能は以下を前提とします:
依存関係 (該当する場合)
この機能は以下に依存します:
workspaceRoot優先/未受領時は.unity/探索)成功基準 (必須)
以下の成功基準を満たす必要があります:
⚡ クイックガイドライン
Plan
実装計画: Unity Profilerパフォーマンス計測
機能ID:
SPEC-1591581c| 日付: 2025-01-17 | 仕様: spec.md入力:
/specs/SPEC-1591581c/spec.mdの機能仕様実行フロー (/speckit.plan コマンドのスコープ)
概要
Unity Profilerパフォーマンス計測機能をMCPツールとして提供する。LLMがUnityアプリケーションの実行中にプロファイリングを開始/停止し、.dataファイルとして保存したり、リアルタイムメトリクス(CPU、メモリ、GC、描画コール数など)を取得できるようにする。既存のVideoCapture/Screenshot機能と同様のアーキテクチャで実装し、.unity/capture/配下に統一的に保存する。
技術アプローチ:
UnityEditorInternal.ProfilerDriver(.data保存)+Unity.Profiling.ProfilerRecorder(リアルタイムメトリクス)profiler_start,profiler_stop,profiler_status,profiler_get_metrics技術コンテキスト
言語/バージョン:
主要依存関係:
UnityEditorInternal.ProfilerDriver,Unity.Profiling.ProfilerRecorder@modelcontextprotocol/sdk,tcp-jsonrpc-clientストレージ:
.unity/capture/*.dataおよび.json)テスト:
対象プラットフォーム:
プロジェクトタイプ:
パフォーマンス目標:
制約:
.unity/capture/固定スケール/スコープ:
憲章チェック
ゲート: Phase 0 research前に合格必須。Phase 1 design後に再チェック。
シンプルさ:
アーキテクチャ:
unity-cli: MCPツール公開unity-cli: Unity Editor拡張テスト (妥協不可):
可観測性:
バージョニング:
npm versionコマンド使用 ✅憲章チェック結果: ✅ 合格(違反なし)
プロジェクト構造
ドキュメント (この機能)
ソースコード (リポジトリルート)
構造決定: 既存プロジェクト拡張のため、既存ディレクトリ構造に従う
Phase 0: アウトライン&リサーチ
リサーチタスク
ProfilerDriver API詳細調査:
ProfilerDriver.enabled,ProfilerDriver.SaveProfile()を使用ProfilerRecorder API詳細調査:
ProfilerRecorder.StartNew()でメトリクス取得利用可能メトリクス一覧取得方法:
ProfilerRecorderHandle.GetAvailable()を使用EditorApplication.updateでの定期処理:
セッション状態管理:
出力:
research.mdにすべての技術決定を文書化Phase 1: 設計&契約
1. データモデル (
data-model.md)エンティティ:
ProfilerSession (Unity側):
ProfilerMetric (Node/Unity共通):
ProfilerStartRequest (Node側):
ProfilerStartResponse (Unity側):
ProfilerStopResponse (Unity側):
ProfilerStatusResponse (Unity側):
ProfilerMetricsResponse (Unity側):
2. API契約 (
/contracts/)profiler-start.json (MCPツール):
{ "name": "profiler_start", "description": "Start Unity Profiler recording session", "inputSchema": { "type": "object", "properties": { "mode": { "type": "string", "enum": ["normal", "deep"], "default": "normal", "description": "Profiling mode (normal or deep profiling)" }, "recordToFile": { "type": "boolean", "default": true, "description": "Save profiling data to .data file" }, "metrics": { "type": "array", "items": {"type": "string"}, "description": "Specific metrics to record (empty = all)" }, "maxDurationSec": { "type": "number", "minimum": 0, "description": "Auto-stop after N seconds (0 = unlimited)" } } } }profiler-stop.json (MCPツール):
{ "name": "profiler_stop", "description": "Stop Unity Profiler recording and save data", "inputSchema": { "type": "object", "properties": { "sessionId": { "type": "string", "description": "Optional session ID to stop (defaults to current)" } } } }profiler-status.json (MCPツール):
{ "name": "profiler_status", "description": "Get current profiling session status", "inputSchema": { "type": "object", "properties": {} } }profiler-get-metrics.json (MCPツール):
{ "name": "profiler_get_metrics", "description": "Get available profiler metrics or current values", "inputSchema": { "type": "object", "properties": { "listAvailable": { "type": "boolean", "default": false, "description": "Return list of available metrics instead of current values" }, "metrics": { "type": "array", "items": {"type": "string"}, "description": "Specific metrics to query (empty = all)" } } } }3. 契約テスト (TDD - RED phase)
tests/integration/profiler.test.js:
4. Quickstart (
quickstart.md)Setup (T001-T007)
↓
Contract Tests (T008-T012) [RED確認必須]
↓
Core Implementation (T013-T021) [GREEN確認必須]
↓
Integration (T022-T024)
↓
Polish (T025-T030)
T002-T005 を並列実行(すべて異なるファイル)
Task: "Node ProfilerStartToolHandler.js作成(空実装)"
Task: "Node ProfilerStopToolHandler.js作成(空実装)"
Task: "Node ProfilerStatusToolHandler.js作成(空実装)"
Task: "Node ProfilerGetMetricsToolHandler.js作成(空実装)"
T008-T011 を並列実行(すべて異なるファイル)
Task: "profiler_start contract test作成"
Task: "profiler_stop contract test作成"
Task: "profiler_status contract test作成"
Task: "profiler_get_metrics contract test作成"
T017-T020 を並列実行(すべて異なるファイル)
Task: "Node ProfilerStartToolHandler実装"
Task: "Node ProfilerStopToolHandler実装"
Task: "Node ProfilerStatusToolHandler実装"
Task: "Node ProfilerGetMetricsToolHandler実装"
T025, T028, T029 を並列実行(すべて異なるファイル)
Task: "Unit Tests作成"
Task: "README.md更新"
Task: "quickstart.md検証実行"
検討した代替案:
参考: Unity Documentation - ProfilerDriver
2. ProfilerRecorder API詳細調査
決定:
Unity.Profiling.ProfilerRecorderを使用してリアルタイムメトリクス取得理由:
ProfilerRecorder.StartNew()で特定のメトリクスを記録開始ProfilerRecorder.LastValueで最新の値を即座に取得可能主要API:
検討した代替案:
Profiler.GetRuntimeMemorySizeLong()等の個別API → 却下理由: メトリクス種類が限定的(メモリのみ)Profiler.GetTotalAllocatedMemoryLong()等の旧API → 却下理由: ProfilerRecorderの方が統一的で拡張性が高い参考: Unity Documentation - ProfilerRecorder
3. 利用可能メトリクス一覧取得方法
決定:
ProfilerRecorderHandle.GetAvailable()を使用して動的にメトリクス一覧取得理由:
主要API:
主要メトリクス例:
検討した代替案:
参考: Unity Documentation - ProfilerRecorderHandle
4. EditorApplication.updateでの定期処理
決定:
EditorApplication.updateイベントを使用して定期処理を実装理由:
実装パターン:
検討した代替案:
参考: Unity Documentation - EditorApplication.update
5. セッション状態管理
決定: 静的フィールドでセッション状態を保持
理由:
状態管理フィールド:
検討した代替案:
参考: VideoCaptureHandler.csの実装パターン
ベストプラクティス
1. ワークスペースルート解決
既存の
ResolveWorkspaceRoot()ユーティリティを使用:2. エラーハンドリング
VideoCaptureHandlerと同様のエラーハンドリングパターン:
3. ファイル命名規則
既存パターンに従った命名:
統合ポイント
Node側ハンドラ
既存のBaseToolHandlerパターンを踏襲:
Unity側コマンドルーティング
UnityCliBridge.csに追加:
結論
すべての技術的不明点を解決し、既存のVideoCaptureHandler/ScreenshotHandlerと同様のアーキテクチャで実装可能であることを確認しました。Unity標準APIを使用することで、シンプルかつ保守性の高い実装が可能です。
次のステップ: Phase 1(設計&契約)に進む準備完了
Data Model
Data Model: Unity Profilerパフォーマンス計測
機能ID:
SPEC-1591581c| 日付: 2025-01-17エンティティ概要
本機能では3つの主要エンティティを定義します:
1. ProfilerSession (Unity側)
プロファイリングセッションの状態を保持するエンティティ。
フィールド
状態遷移
検証ルール
sessionId: GUID形式(ハイフンなし、32文字hex)startedAt: 過去の時刻でなければならないmaxDurationSec: 0以上(0は無制限)outputPath:.unity/capture/配下、.data拡張子C#実装例
2. ProfilerMetric (Node/Unity共通)
個別のパフォーマンスメトリクスを表すエンティティ。
フィールド
主要カテゴリとメトリクス
Memory:
Rendering:
CPU/Frame:
GC:
検証ルール
category: ProfilerCategoryの有効な値name: ProfilerRecorderHandle.GetAvailable()で取得可能な名前value: 負数不可(ただし一部メトリクスでは0可)unit: 標準単位(bytes, count, milliseconds, seconds, percentage)JSON形式例
{ "category": "Memory", "name": "System Used Memory", "value": 524288000, "unit": "bytes" }3. ProfilerMetricsCategory (Unity側)
メトリクスをカテゴリ別に分類して返却するためのエンティティ。
フィールド
JSON形式例
{ "categories": { "Memory": { "name": "Memory", "metrics": [ {"category": "Memory", "name": "System Used Memory", "value": 524288000, "unit": "bytes"}, {"category": "Memory", "name": "GC Reserved Memory", "value": 104857600, "unit": "bytes"} ] }, "Rendering": { "name": "Rendering", "metrics": [ {"category": "Rendering", "name": "Draw Calls Count", "value": 128, "unit": "count"} ] } } }リクエスト/レスポンスモデル
ProfilerStartRequest (Node → Unity)
検証ルール:
mode: 'normal' または 'deep'のみrecordToFile: booleanmetrics: 有効なメトリクス名の配列(ProfilerRecorderHandle.GetAvailable()で検証)maxDurationSec: 0以上ProfilerStartResponse (Unity → Node)
検証ルール:
sessionId: GUID形式startedAt: ISO 8601形式(例: "2025-01-17T10:00:00.000Z")isRecording: 常にtrue(成功時)outputPath: recordToFile=trueの場合は非null、.data拡張子ProfilerStopRequest (Node → Unity)
検証ルール:
sessionId: 存在する場合は有効なGUID形式ProfilerStopResponse (Unity → Node)
検証ルール:
sessionId: GUID形式outputPath: recordToFile=trueの場合は非nullduration: 0以上frameCount: 0以上metrics: recordToFile=falseの場合のみ配列、それ以外はnullProfilerStatusRequest (Node → Unity)
ProfilerStatusResponse (Unity → Node)
検証ルール:
isRecording: 記録中はtrue、それ以外はfalsesessionId: 記録中の場合は非null、GUID形式startedAt: 記録中の場合は非null、ISO 8601形式elapsedSec: 0以上remainingSec: maxDurationSec>0の場合は0以上、それ以外はnullProfilerGetMetricsRequest (Node → Unity)
検証ルール:
listAvailable: booleanmetrics: 有効なメトリクス名の配列ProfilerGetMetricsResponse (Unity → Node)
listAvailable=trueの場合:
listAvailable=falseの場合:
エラーレスポンス
すべてのリクエストで以下の形式のエラーレスポンスを返却可能:
エラーコード一覧:
E_ALREADY_RUNNING: 既にプロファイリング実行中E_NOT_RECORDING: プロファイリング未実行E_INVALID_MODE: 無効なmode指定E_INVALID_METRICS: 無効なメトリクス名E_FILE_IO: ファイル保存エラーE_UNKNOWN: その他のエラー関係図
永続化
結論
すべてのエンティティは既存の機能仕様の要件を満たし、Unity標準APIと整合性があります。シンプルで拡張性のあるデータモデルです。
Quickstart
Profiler Performance Measurement - Quick Start Guide
This guide helps you get started with Unity Profiler performance measurement via MCP tools.
Prerequisites
unity-clipackage installedBasic Workflow
1. Start Profiling Session
2. Check Status (Optional)
3. Stop Profiling
4. Analyze Results
.unity/capture/profiler_*.datafileAdvanced Usage
Real-time Metrics (No File Output)
List Available Metrics
Query Current Metric Values
Deep Profiling
Error Handling
Common Errors
E_ALREADY_RUNNING: Another profiling session is already running
E_INVALID_MODE: Invalid mode parameter
E_INVALID_METRICS: Invalid metric names
E_NOT_RECORDING: No profiling session is running
E_INVALID_SESSION: Session ID does not match current session
E_FILE_IO: Failed to save .data file
// Solution: Check disk space and .unity/capture/ directory permissionsBest Practices
Use Normal Mode First: Start with
mode: 'normal'for general profiling. Only usemode: 'deep'when you need detailed function-level analysis.Set Auto-stop Duration: Always set
maxDurationSecto prevent unlimited recording that may consume disk space.Profile During Gameplay: Start profiling during actual gameplay scenarios for realistic performance data.
Analyze .data Files: Use Unity Profiler Window to visualize and analyze
.datafiles. The MCP tools provide raw data collection, but Unity's built-in tools offer better visualization.Real-time Metrics for Automation: Use
recordToFile: falsewith specificmetricsfor automated performance testing or CI/CD pipelines.Check Status Before Stopping: Query
profiler_statusto confirm session is running before callingprofiler_stop.Troubleshooting
Profiling Session Won't Start
profiler_statusandprofiler_stopif needed..data File Not Saved
.unity/capture/directory exists and is writablerecordToFile: truewas set inprofiler_startMetrics Not Available
profiler_get_metricswithlistAvailable: trueto check available metricsPerformance Impact
mode: 'deep'has significant performance overhead. Use short durations (10-30 seconds).Next Steps
Contracts
Migrated from local files. See artifact comments below.
Checklists
Artifact files under
checklists/are managed in issue comments withchecklist:<name>entries.Acceptance Checklist