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