systemprompt_analytics/repository/tools/
detail_queries.rs1use crate::Result;
8use chrono::{DateTime, Utc};
9
10use super::ToolAnalyticsRepository;
11use crate::models::cli::{
12 ToolAgentUsageRow, ToolErrorRow, ToolExecutionRow, ToolStatsRow, ToolStatusBreakdownRow,
13 ToolSummaryRow,
14};
15
16impl ToolAnalyticsRepository {
17 pub async fn get_stats(
18 &self,
19 start: DateTime<Utc>,
20 end: DateTime<Utc>,
21 tool_filter: Option<&str>,
22 ) -> Result<ToolStatsRow> {
23 if let Some(tool) = tool_filter {
24 let pattern = format!("%{}%", tool);
25 sqlx::query_as!(
26 ToolStatsRow,
27 r#"
28 SELECT
29 COUNT(DISTINCT tool_name)::bigint as "total_tools!",
30 COUNT(*)::bigint as "total_executions!",
31 COUNT(*) FILTER (WHERE status = 'success')::bigint as "successful!",
32 COUNT(*) FILTER (WHERE status = 'failed')::bigint as "failed!",
33 COUNT(*) FILTER (WHERE status = 'timeout')::bigint as "timeout!",
34 COALESCE(AVG(execution_time_ms)::float8, 0) as "avg_time!",
35 COALESCE(PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY execution_time_ms)::float8, 0) as "p95_time!"
36 FROM mcp_tool_executions
37 WHERE created_at >= $1 AND created_at < $2 AND tool_name ILIKE $3
38 "#,
39 start, end, pattern
40 )
41 .fetch_one(&*self.pool)
42 .await
43 .map_err(Into::into)
44 } else {
45 sqlx::query_as!(
46 ToolStatsRow,
47 r#"
48 SELECT
49 COUNT(DISTINCT tool_name)::bigint as "total_tools!",
50 COUNT(*)::bigint as "total_executions!",
51 COUNT(*) FILTER (WHERE status = 'success')::bigint as "successful!",
52 COUNT(*) FILTER (WHERE status = 'failed')::bigint as "failed!",
53 COUNT(*) FILTER (WHERE status = 'timeout')::bigint as "timeout!",
54 COALESCE(AVG(execution_time_ms)::float8, 0) as "avg_time!",
55 COALESCE(PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY execution_time_ms)::float8, 0) as "p95_time!"
56 FROM mcp_tool_executions
57 WHERE created_at >= $1 AND created_at < $2
58 "#,
59 start, end
60 )
61 .fetch_one(&*self.pool)
62 .await
63 .map_err(Into::into)
64 }
65 }
66
67 pub async fn tool_exists(
68 &self,
69 tool_name: &str,
70 start: DateTime<Utc>,
71 end: DateTime<Utc>,
72 ) -> Result<i64> {
73 let pattern = format!("%{}%", tool_name);
74 let count = sqlx::query_scalar!(
75 r#"SELECT COUNT(*)::bigint as "count!" FROM mcp_tool_executions WHERE tool_name ILIKE $1 AND created_at >= $2 AND created_at < $3"#,
76 pattern,
77 start,
78 end
79 )
80 .fetch_one(&*self.pool)
81 .await?;
82 Ok(count)
83 }
84
85 pub async fn get_tool_summary(
86 &self,
87 tool_name: &str,
88 start: DateTime<Utc>,
89 end: DateTime<Utc>,
90 ) -> Result<ToolSummaryRow> {
91 let pattern = format!("%{}%", tool_name);
92 sqlx::query_as!(
93 ToolSummaryRow,
94 r#"
95 SELECT
96 COUNT(*)::bigint as "total!",
97 COUNT(*) FILTER (WHERE status = 'success')::bigint as "successful!",
98 COUNT(*) FILTER (WHERE status = 'failed')::bigint as "failed!",
99 COUNT(*) FILTER (WHERE status = 'timeout')::bigint as "timeout!",
100 COALESCE(AVG(execution_time_ms)::float8, 0) as "avg_time!",
101 COALESCE(PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY execution_time_ms)::float8, 0) as "p95_time!"
102 FROM mcp_tool_executions
103 WHERE tool_name ILIKE $1 AND created_at >= $2 AND created_at < $3
104 "#,
105 pattern, start, end
106 )
107 .fetch_one(&*self.pool)
108 .await
109 .map_err(Into::into)
110 }
111
112 pub async fn get_status_breakdown(
113 &self,
114 tool_name: &str,
115 start: DateTime<Utc>,
116 end: DateTime<Utc>,
117 ) -> Result<Vec<ToolStatusBreakdownRow>> {
118 let pattern = format!("%{}%", tool_name);
119 sqlx::query_as!(
120 ToolStatusBreakdownRow,
121 r#"
122 SELECT status as "status!", COUNT(*)::bigint as "status_count!"
123 FROM mcp_tool_executions
124 WHERE tool_name ILIKE $1 AND created_at >= $2 AND created_at < $3
125 GROUP BY status
126 ORDER BY 2 DESC
127 "#,
128 pattern,
129 start,
130 end
131 )
132 .fetch_all(&*self.pool)
133 .await
134 .map_err(Into::into)
135 }
136
137 pub async fn get_top_errors(
138 &self,
139 tool_name: &str,
140 start: DateTime<Utc>,
141 end: DateTime<Utc>,
142 ) -> Result<Vec<ToolErrorRow>> {
143 let pattern = format!("%{}%", tool_name);
144 sqlx::query_as!(
145 ToolErrorRow,
146 r#"
147 SELECT
148 COALESCE(SUBSTRING(error_message FROM 1 FOR 100), 'Unknown error') as "error_msg",
149 COUNT(*)::bigint as "error_count!"
150 FROM mcp_tool_executions
151 WHERE tool_name ILIKE $1 AND created_at >= $2 AND created_at < $3 AND status = 'failed'
152 GROUP BY SUBSTRING(error_message FROM 1 FOR 100)
153 ORDER BY 2 DESC
154 LIMIT 10
155 "#,
156 pattern,
157 start,
158 end
159 )
160 .fetch_all(&*self.pool)
161 .await
162 .map_err(Into::into)
163 }
164
165 pub async fn get_usage_by_agent(
166 &self,
167 tool_name: &str,
168 start: DateTime<Utc>,
169 end: DateTime<Utc>,
170 ) -> Result<Vec<ToolAgentUsageRow>> {
171 let pattern = format!("%{}%", tool_name);
172 sqlx::query_as!(
173 ToolAgentUsageRow,
174 r#"
175 SELECT
176 COALESCE(at.agent_name, CASE WHEN mte.task_id IS NULL THEN 'Direct Call' ELSE 'Unlinked Task' END) as "agent_name",
177 COUNT(*)::bigint as "usage_count!"
178 FROM mcp_tool_executions mte
179 LEFT JOIN agent_tasks at ON at.task_id = mte.task_id
180 WHERE mte.tool_name ILIKE $1 AND mte.created_at >= $2 AND mte.created_at < $3
181 GROUP BY COALESCE(at.agent_name, CASE WHEN mte.task_id IS NULL THEN 'Direct Call' ELSE 'Unlinked Task' END)
182 ORDER BY 2 DESC
183 LIMIT 10
184 "#,
185 pattern, start, end
186 )
187 .fetch_all(&*self.pool)
188 .await
189 .map_err(Into::into)
190 }
191
192 pub async fn get_executions_for_trends(
193 &self,
194 start: DateTime<Utc>,
195 end: DateTime<Utc>,
196 tool_filter: Option<&str>,
197 ) -> Result<Vec<ToolExecutionRow>> {
198 if let Some(tool) = tool_filter {
199 let pattern = format!("%{}%", tool);
200 sqlx::query_as!(
201 ToolExecutionRow,
202 r#"
203 SELECT
204 created_at as "created_at!",
205 status,
206 execution_time_ms
207 FROM mcp_tool_executions
208 WHERE created_at >= $1 AND created_at < $2 AND tool_name ILIKE $3
209 ORDER BY created_at
210 "#,
211 start,
212 end,
213 pattern
214 )
215 .fetch_all(&*self.pool)
216 .await
217 .map_err(Into::into)
218 } else {
219 sqlx::query_as!(
220 ToolExecutionRow,
221 r#"
222 SELECT
223 created_at as "created_at!",
224 status,
225 execution_time_ms
226 FROM mcp_tool_executions
227 WHERE created_at >= $1 AND created_at < $2
228 ORDER BY created_at
229 "#,
230 start,
231 end
232 )
233 .fetch_all(&*self.pool)
234 .await
235 .map_err(Into::into)
236 }
237 }
238}