Skip to main content

systemprompt_cli/commands/analytics/tools/
mod.rs

1//! Tool usage analytics: aggregate stats, listings, trends, and per-tool deep
2//! dives.
3//!
4//! Defines the [`ToolsCommands`] subcommand tree and the typed output shapes
5//! ([`ToolStatsOutput`], [`ToolListOutput`], [`ToolTrendsOutput`],
6//! [`ToolShowOutput`]) rendered by the `analytics tools` commands.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11mod list;
12mod show;
13mod stats;
14mod trends;
15
16use anyhow::Result;
17use clap::Subcommand;
18use schemars::JsonSchema;
19use serde::{Deserialize, Serialize};
20use systemprompt_identifiers::{AgentName, McpServerId, McpToolName};
21
22use crate::context::CommandContext;
23use crate::shared::render_result;
24
25#[derive(Debug, Subcommand)]
26pub enum ToolsCommands {
27    #[command(about = "Aggregate tool statistics")]
28    Stats(stats::StatsArgs),
29
30    #[command(about = "List tools with metrics")]
31    List(list::ListArgs),
32
33    #[command(about = "Tool usage trends over time")]
34    Trends(trends::TrendsArgs),
35
36    #[command(about = "Deep dive into specific tool")]
37    Show(show::ShowArgs),
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
41pub struct ToolStatsOutput {
42    pub period: String,
43    pub total_tools: i64,
44    pub total_executions: i64,
45    pub successful: i64,
46    pub failed: i64,
47    pub timeout: i64,
48    pub success_rate: f64,
49    pub avg_execution_time_ms: i64,
50    pub p95_execution_time_ms: i64,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
54pub struct ToolListRow {
55    pub tool_name: McpToolName,
56    pub server_name: McpServerId,
57    pub execution_count: i64,
58    pub success_rate: f64,
59    pub avg_execution_time_ms: i64,
60    pub last_used: String,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
64pub struct ToolListOutput {
65    pub tools: Vec<ToolListRow>,
66    pub total: i64,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
70pub struct ToolTrendPoint {
71    pub timestamp: String,
72    pub execution_count: i64,
73    pub success_rate: f64,
74    pub avg_execution_time_ms: i64,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
78pub struct ToolTrendsOutput {
79    pub tool: Option<String>,
80    pub period: String,
81    pub group_by: String,
82    pub points: Vec<ToolTrendPoint>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
86pub struct ToolShowOutput {
87    pub tool_name: McpToolName,
88    pub period: String,
89    pub summary: ToolStatsOutput,
90    pub status_breakdown: Vec<StatusBreakdownItem>,
91    pub top_errors: Vec<ErrorItem>,
92    pub usage_by_agent: Vec<AgentUsageItem>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
96pub struct StatusBreakdownItem {
97    pub status: String,
98    pub count: i64,
99    pub percentage: f64,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
103pub struct ErrorItem {
104    pub error_message: String,
105    pub count: i64,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
109pub struct AgentUsageItem {
110    pub agent_name: Option<AgentName>,
111    pub count: i64,
112    pub percentage: f64,
113}
114
115pub async fn execute(command: ToolsCommands, ctx: &CommandContext) -> Result<()> {
116    let db_ctx = ctx.database().await?;
117    match command {
118        ToolsCommands::Stats(args) => {
119            let result = stats::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
120            render_result(&result, &ctx.cli);
121            Ok(())
122        },
123        ToolsCommands::List(args) => {
124            let result = list::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
125            render_result(&result, &ctx.cli);
126            Ok(())
127        },
128        ToolsCommands::Trends(args) => {
129            let result = trends::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
130            render_result(&result, &ctx.cli);
131            Ok(())
132        },
133        ToolsCommands::Show(args) => {
134            let result = show::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
135            render_result(&result, &ctx.cli);
136            Ok(())
137        },
138    }
139}