systemprompt_cli/commands/infrastructure/logs/request/
stats.rs1use anyhow::Result;
8use clap::Args;
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11use std::sync::Arc;
12use systemprompt_logging::TraceQueryService;
13
14use crate::CliConfig;
15use crate::commands::infrastructure::logs::duration::parse_since;
16use crate::shared::{CommandOutput, render_result};
17
18#[derive(Debug, Args)]
19pub struct StatsArgs {
20 #[arg(
21 long,
22 help = "Only include requests since this duration (e.g., '1h', '24h', '7d')"
23 )]
24 pub since: Option<String>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
28pub struct RequestStatsOutput {
29 pub total_requests: i64,
30 pub total_tokens: TokenStats,
31 pub total_cost_dollars: f64,
32 pub average_latency_ms: i64,
33 pub by_provider: Vec<ProviderStats>,
34 pub by_model: Vec<ModelStats>,
35}
36
37#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
38pub struct TokenStats {
39 pub input: i64,
40 pub output: i64,
41 pub total: i64,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
45pub struct ProviderStats {
46 pub provider: String,
47 pub request_count: i64,
48 pub total_tokens: i64,
49 pub total_cost_dollars: f64,
50 pub avg_latency_ms: i64,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
54pub struct ModelStats {
55 pub model: String,
56 pub provider: String,
57 pub request_count: i64,
58 pub total_tokens: i64,
59 pub total_cost_dollars: f64,
60 pub avg_latency_ms: i64,
61}
62
63crate::define_pool_command!(StatsArgs => (), with_config);
64
65async fn execute_with_pool_inner(
66 args: StatsArgs,
67 pool: &Arc<sqlx::PgPool>,
68 config: &CliConfig,
69) -> Result<()> {
70 let since_timestamp = parse_since(args.since.as_ref())?;
71
72 let service = TraceQueryService::new(Arc::clone(pool));
73 let stats = service.get_ai_request_stats(since_timestamp).await?;
74
75 let input_tokens = stats.total_input_tokens;
76 let output_tokens = stats.total_output_tokens;
77
78 let output = RequestStatsOutput {
79 total_requests: stats.total_requests,
80 total_tokens: TokenStats {
81 input: input_tokens,
82 output: output_tokens,
83 total: input_tokens + output_tokens,
84 },
85 total_cost_dollars: f64::from(stats.total_cost_microdollars as i32) / 1_000_000.0,
86 average_latency_ms: stats.avg_latency_ms,
87 by_provider: stats
88 .by_provider
89 .into_iter()
90 .map(|r| ProviderStats {
91 provider: r.provider,
92 request_count: r.request_count,
93 total_tokens: r.total_tokens,
94 total_cost_dollars: f64::from(r.total_cost_microdollars as i32) / 1_000_000.0,
95 avg_latency_ms: r.avg_latency_ms,
96 })
97 .collect(),
98 by_model: stats
99 .by_model
100 .into_iter()
101 .map(|r| ModelStats {
102 model: r.model,
103 provider: r.provider,
104 request_count: r.request_count,
105 total_tokens: r.total_tokens,
106 total_cost_dollars: f64::from(r.total_cost_microdollars as i32) / 1_000_000.0,
107 avg_latency_ms: r.avg_latency_ms,
108 })
109 .collect(),
110 };
111
112 render_result(&build_request_stats(&output), config);
113
114 Ok(())
115}
116
117#[must_use]
118pub fn build_request_stats(output: &RequestStatsOutput) -> CommandOutput {
119 CommandOutput::card_value("AI Request Statistics", output)
120}