Skip to main content

adk_acp/
usage.rs

1//! Usage tracking for ACP agent invocations.
2//!
3//! Records per-call metrics so you can monitor costs and performance
4//! across both your LLM provider (token costs) and ACP agent (credits/time).
5
6use std::sync::Arc;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::time::Duration;
9
10/// Usage metrics from a single ACP agent invocation.
11#[derive(Debug, Clone)]
12pub struct AcpUsage {
13    /// Name of the ACP agent tool that was invoked.
14    pub tool_name: String,
15    /// Length of the prompt sent (characters).
16    pub prompt_chars: usize,
17    /// Length of the response received (characters).
18    pub response_chars: usize,
19    /// Wall-clock duration of the invocation.
20    pub duration: Duration,
21    /// Whether the invocation succeeded.
22    pub success: bool,
23    /// Number of permission requests received during this invocation.
24    pub permission_requests: u32,
25    /// Number of permission requests that were denied.
26    pub permissions_denied: u32,
27}
28
29/// Aggregated usage statistics across all ACP invocations.
30#[derive(Debug, Clone)]
31pub struct AcpUsageStats {
32    /// Total number of invocations.
33    pub total_calls: u64,
34    /// Total successful invocations.
35    pub successful_calls: u64,
36    /// Total failed invocations.
37    pub failed_calls: u64,
38    /// Total prompt characters sent.
39    pub total_prompt_chars: u64,
40    /// Total response characters received.
41    pub total_response_chars: u64,
42    /// Total wall-clock time spent in ACP calls.
43    pub total_duration: Duration,
44    /// Total permission requests received.
45    pub total_permission_requests: u64,
46    /// Total permission requests denied.
47    pub total_permissions_denied: u64,
48}
49
50/// Thread-safe usage tracker for ACP invocations.
51///
52/// Attach to an `AcpAgentTool` or use standalone to aggregate metrics.
53///
54/// # Example
55///
56/// ```rust,ignore
57/// use adk_acp::usage::UsageTracker;
58///
59/// let tracker = UsageTracker::new();
60/// // ... tool invocations happen ...
61/// let stats = tracker.stats();
62/// println!("Total ACP calls: {}", stats.total_calls);
63/// println!("Avg response time: {:?}", stats.total_duration / stats.total_calls as u32);
64/// ```
65#[derive(Debug, Clone, Default)]
66pub struct UsageTracker {
67    inner: Arc<UsageTrackerInner>,
68}
69
70#[derive(Debug, Default)]
71struct UsageTrackerInner {
72    total_calls: AtomicU64,
73    successful_calls: AtomicU64,
74    failed_calls: AtomicU64,
75    total_prompt_chars: AtomicU64,
76    total_response_chars: AtomicU64,
77    total_duration_ms: AtomicU64,
78    total_permission_requests: AtomicU64,
79    total_permissions_denied: AtomicU64,
80}
81
82impl UsageTracker {
83    /// Create a new usage tracker.
84    pub fn new() -> Self {
85        Self::default()
86    }
87
88    /// Record a completed ACP invocation.
89    pub fn record(&self, usage: &AcpUsage) {
90        let inner = &self.inner;
91        inner.total_calls.fetch_add(1, Ordering::Relaxed);
92        if usage.success {
93            inner.successful_calls.fetch_add(1, Ordering::Relaxed);
94        } else {
95            inner.failed_calls.fetch_add(1, Ordering::Relaxed);
96        }
97        inner.total_prompt_chars.fetch_add(usage.prompt_chars as u64, Ordering::Relaxed);
98        inner.total_response_chars.fetch_add(usage.response_chars as u64, Ordering::Relaxed);
99        inner.total_duration_ms.fetch_add(usage.duration.as_millis() as u64, Ordering::Relaxed);
100        inner
101            .total_permission_requests
102            .fetch_add(u64::from(usage.permission_requests), Ordering::Relaxed);
103        inner
104            .total_permissions_denied
105            .fetch_add(u64::from(usage.permissions_denied), Ordering::Relaxed);
106    }
107
108    /// Get aggregated usage statistics.
109    pub fn stats(&self) -> AcpUsageStats {
110        let inner = &self.inner;
111        AcpUsageStats {
112            total_calls: inner.total_calls.load(Ordering::Relaxed),
113            successful_calls: inner.successful_calls.load(Ordering::Relaxed),
114            failed_calls: inner.failed_calls.load(Ordering::Relaxed),
115            total_prompt_chars: inner.total_prompt_chars.load(Ordering::Relaxed),
116            total_response_chars: inner.total_response_chars.load(Ordering::Relaxed),
117            total_duration: Duration::from_millis(inner.total_duration_ms.load(Ordering::Relaxed)),
118            total_permission_requests: inner.total_permission_requests.load(Ordering::Relaxed),
119            total_permissions_denied: inner.total_permissions_denied.load(Ordering::Relaxed),
120        }
121    }
122
123    /// Reset all counters to zero.
124    pub fn reset(&self) {
125        let inner = &self.inner;
126        inner.total_calls.store(0, Ordering::Relaxed);
127        inner.successful_calls.store(0, Ordering::Relaxed);
128        inner.failed_calls.store(0, Ordering::Relaxed);
129        inner.total_prompt_chars.store(0, Ordering::Relaxed);
130        inner.total_response_chars.store(0, Ordering::Relaxed);
131        inner.total_duration_ms.store(0, Ordering::Relaxed);
132        inner.total_permission_requests.store(0, Ordering::Relaxed);
133        inner.total_permissions_denied.store(0, Ordering::Relaxed);
134    }
135}