miyabi_worktree/
telemetry.rs

1//! テレメトリ・監視モジュール - Worktree実行の記録・監視
2//!
3//! 実行メトリクス、ログ、トレースを管理します。
4
5use std::time::Duration;
6use tracing::{debug, error, info, warn};
7
8/// Worktree実行イベント
9#[derive(Debug, Clone)]
10pub enum WorktreeEvent {
11    /// 作成開始
12    CreateStart {
13        worktree_id: String,
14        branch_name: String,
15    },
16    /// 作成完了
17    CreateComplete {
18        worktree_id: String,
19        duration: Duration,
20    },
21    /// 実行開始
22    ExecuteStart {
23        worktree_id: String,
24        agent_type: String,
25    },
26    /// 実行完了
27    ExecuteComplete {
28        worktree_id: String,
29        duration: Duration,
30        success: bool,
31    },
32    /// クリーンアップ開始
33    CleanupStart { worktree_id: String },
34    /// クリーンアップ完了
35    CleanupComplete {
36        worktree_id: String,
37        duration: Duration,
38    },
39    /// エラー発生
40    Error {
41        worktree_id: String,
42        error_message: String,
43    },
44}
45
46/// テレメトリコレクター
47pub struct TelemetryCollector {
48    events: Vec<WorktreeEvent>,
49}
50
51impl TelemetryCollector {
52    /// 新しいコレクターを作成
53    pub fn new() -> Self {
54        Self { events: Vec::new() }
55    }
56
57    /// イベントを記録
58    pub fn record(&mut self, event: WorktreeEvent) {
59        // 構造化ログ出力
60        match &event {
61            WorktreeEvent::CreateStart {
62                worktree_id,
63                branch_name,
64            } => {
65                info!(
66                    worktree_id = %worktree_id,
67                    branch = %branch_name,
68                    "Worktree作成開始"
69                );
70            },
71            WorktreeEvent::CreateComplete {
72                worktree_id,
73                duration,
74            } => {
75                info!(
76                    worktree_id = %worktree_id,
77                    duration_ms = duration.as_millis(),
78                    "Worktree作成完了"
79                );
80            },
81            WorktreeEvent::ExecuteStart {
82                worktree_id,
83                agent_type,
84            } => {
85                info!(
86                    worktree_id = %worktree_id,
87                    agent = %agent_type,
88                    "Agent実行開始"
89                );
90            },
91            WorktreeEvent::ExecuteComplete {
92                worktree_id,
93                duration,
94                success,
95            } => {
96                if *success {
97                    info!(
98                        worktree_id = %worktree_id,
99                        duration_ms = duration.as_millis(),
100                        "Agent実行成功"
101                    );
102                } else {
103                    warn!(
104                        worktree_id = %worktree_id,
105                        duration_ms = duration.as_millis(),
106                        "Agent実行失敗"
107                    );
108                }
109            },
110            WorktreeEvent::CleanupStart { worktree_id } => {
111                debug!(worktree_id = %worktree_id, "クリーンアップ開始");
112            },
113            WorktreeEvent::CleanupComplete {
114                worktree_id,
115                duration,
116            } => {
117                debug!(
118                    worktree_id = %worktree_id,
119                    duration_ms = duration.as_millis(),
120                    "クリーンアップ完了"
121                );
122            },
123            WorktreeEvent::Error {
124                worktree_id,
125                error_message,
126            } => {
127                error!(
128                    worktree_id = %worktree_id,
129                    error = %error_message,
130                    "Worktreeエラー"
131                );
132            },
133        }
134
135        self.events.push(event);
136    }
137
138    /// 全イベントを取得
139    pub fn events(&self) -> &[WorktreeEvent] {
140        &self.events
141    }
142
143    /// 統計を生成
144    pub fn generate_stats(&self) -> TelemetryStats {
145        let mut stats = TelemetryStats::default();
146
147        for event in &self.events {
148            match event {
149                WorktreeEvent::CreateComplete { .. } => stats.creates += 1,
150                WorktreeEvent::ExecuteComplete {
151                    success, duration, ..
152                } => {
153                    stats.executions += 1;
154                    if *success {
155                        stats.successful_executions += 1;
156                    } else {
157                        stats.failed_executions += 1;
158                    }
159                    stats.total_execution_time += *duration;
160                },
161                WorktreeEvent::CleanupComplete { .. } => stats.cleanups += 1,
162                WorktreeEvent::Error { .. } => stats.errors += 1,
163                _ => {},
164            }
165        }
166
167        stats
168    }
169
170    /// レポートを生成(人間可読形式)
171    pub fn generate_report(&self) -> String {
172        let stats = self.generate_stats();
173        format!(
174            "📊 Worktree実行レポート\n\
175             - 作成: {}回\n\
176             - 実行: {}回(成功: {}, 失敗: {})\n\
177             - クリーンアップ: {}回\n\
178             - エラー: {}回\n\
179             - 平均実行時間: {:.2}秒\n\
180             - 成功率: {:.1}%",
181            stats.creates,
182            stats.executions,
183            stats.successful_executions,
184            stats.failed_executions,
185            stats.cleanups,
186            stats.errors,
187            stats.average_execution_time().as_secs_f64(),
188            stats.success_rate()
189        )
190    }
191}
192
193impl Default for TelemetryCollector {
194    fn default() -> Self {
195        Self::new()
196    }
197}
198
199/// テレメトリ統計
200#[derive(Debug, Clone, Default)]
201pub struct TelemetryStats {
202    /// Creates
203    pub creates: usize,
204    pub executions: usize,
205    /// Successful executions
206    pub successful_executions: usize,
207    pub failed_executions: usize,
208    /// Cleanups
209    pub cleanups: usize,
210    pub errors: usize,
211    /// Total execution time
212    pub total_execution_time: Duration,
213}
214
215impl TelemetryStats {
216    /// 平均実行時間を計算
217    pub fn average_execution_time(&self) -> Duration {
218        if self.executions == 0 {
219            Duration::ZERO
220        } else {
221            self.total_execution_time / self.executions as u32
222        }
223    }
224
225    /// 成功率を計算(パーセント)
226    pub fn success_rate(&self) -> f64 {
227        if self.executions == 0 {
228            0.0
229        } else {
230            (self.successful_executions as f64 / self.executions as f64) * 100.0
231        }
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn test_telemetry_collector_creation() {
241        let collector = TelemetryCollector::new();
242        assert_eq!(collector.events().len(), 0);
243    }
244
245    #[test]
246    fn test_record_event() {
247        let mut collector = TelemetryCollector::new();
248        collector.record(WorktreeEvent::CreateStart {
249            worktree_id: "test-1".to_string(),
250            branch_name: "feature/test".to_string(),
251        });
252        assert_eq!(collector.events().len(), 1);
253    }
254
255    #[test]
256    fn test_generate_stats() {
257        let mut collector = TelemetryCollector::new();
258
259        collector.record(WorktreeEvent::CreateComplete {
260            worktree_id: "test-1".to_string(),
261            duration: Duration::from_secs(1),
262        });
263
264        collector.record(WorktreeEvent::ExecuteComplete {
265            worktree_id: "test-1".to_string(),
266            duration: Duration::from_secs(2),
267            success: true,
268        });
269
270        let stats = collector.generate_stats();
271        assert_eq!(stats.creates, 1);
272        assert_eq!(stats.executions, 1);
273        assert_eq!(stats.successful_executions, 1);
274        assert_eq!(stats.success_rate(), 100.0);
275    }
276
277    #[test]
278    fn test_generate_report() {
279        let mut collector = TelemetryCollector::new();
280
281        collector.record(WorktreeEvent::CreateComplete {
282            worktree_id: "test-1".to_string(),
283            duration: Duration::from_secs(1),
284        });
285
286        let report = collector.generate_report();
287        assert!(report.contains("作成: 1回"));
288    }
289
290    #[test]
291    fn test_telemetry_stats_average() {
292        let stats = TelemetryStats {
293            executions: 2,
294            total_execution_time: Duration::from_secs(10),
295            ..Default::default()
296        };
297
298        assert_eq!(stats.average_execution_time(), Duration::from_secs(5));
299    }
300}