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