systemprompt_cli/commands/infrastructure/logs/trace/
summary.rs1use chrono::{DateTime, Utc};
2use std::collections::HashMap;
3use systemprompt_logging::{
4 AiRequestSummary, CliService, ExecutionStepSummary, McpExecutionSummary, TraceEvent,
5};
6
7#[derive(Debug)]
8pub struct SummaryContext<'a> {
9 pub events: &'a [TraceEvent],
10 pub first: Option<DateTime<Utc>>,
11 pub last: Option<DateTime<Utc>>,
12 pub task_id: Option<&'a str>,
13 pub ai_summary: &'a AiRequestSummary,
14 pub mcp_summary: &'a McpExecutionSummary,
15 pub step_summary: &'a ExecutionStepSummary,
16}
17
18pub fn print_summary(ctx: &SummaryContext<'_>) {
19 CliService::section("Summary");
20
21 if let (Some(first), Some(last)) = (ctx.first, ctx.last) {
22 let duration = last.signed_duration_since(first);
23 CliService::key_value(" Duration", &format!("{}ms", duration.num_milliseconds()));
24 }
25
26 print_event_counts(ctx.events);
27 print_ai_summary(ctx.ai_summary);
28 print_mcp_summary(ctx.mcp_summary);
29 print_step_summary(ctx.step_summary);
30 print_trace_context(ctx.events, ctx.task_id);
31 print_status(ctx.events);
32}
33
34fn print_event_counts(events: &[TraceEvent]) {
35 let mut event_counts: HashMap<String, usize> = HashMap::new();
36 for event in events {
37 *event_counts.entry(event.event_type.clone()).or_insert(0) += 1;
38 }
39
40 let mut count_vec: Vec<_> = event_counts.iter().collect();
41 count_vec.sort_by_key(|&(k, _)| k);
42
43 let event_parts: Vec<String> = count_vec
44 .iter()
45 .map(|(k, v)| format!("{} {}", v, k))
46 .collect();
47 CliService::key_value(
48 " Events",
49 &format!("{} ({})", events.len(), event_parts.join(", ")),
50 );
51}
52
53fn print_ai_summary(ai_summary: &AiRequestSummary) {
54 if ai_summary.request_count > 0 {
55 CliService::info(" AI Requests:");
56 CliService::key_value(" Requests", &ai_summary.request_count.to_string());
57 CliService::key_value(
58 " Tokens",
59 &format!(
60 "{} (in: {}, out: {})",
61 ai_summary.total_tokens,
62 ai_summary.total_input_tokens,
63 ai_summary.total_output_tokens
64 ),
65 );
66 let dollars = ai_summary.total_cost_microdollars as f64 / 1_000_000.0;
67 CliService::key_value(" Cost", &format!("${dollars:.6}"));
68 CliService::key_value(
69 " Total Latency",
70 &format!("{}ms", ai_summary.total_latency_ms),
71 );
72 if ai_summary.request_count > 0 {
73 let avg_latency = ai_summary.total_latency_ms / ai_summary.request_count;
74 CliService::key_value(" Avg Latency", &format!("{avg_latency}ms"));
75 }
76 }
77}
78
79fn print_mcp_summary(mcp_summary: &McpExecutionSummary) {
80 if mcp_summary.execution_count > 0 {
81 CliService::info(" MCP Tool Executions:");
82 CliService::key_value(" Executions", &mcp_summary.execution_count.to_string());
83 CliService::key_value(
84 " Total Time",
85 &format!("{}ms", mcp_summary.total_execution_time_ms),
86 );
87 if mcp_summary.execution_count > 0 {
88 let avg_time = mcp_summary.total_execution_time_ms / mcp_summary.execution_count;
89 CliService::key_value(" Avg Time", &format!("{avg_time}ms"));
90 }
91 }
92}
93
94fn print_step_summary(step_summary: &ExecutionStepSummary) {
95 if step_summary.total > 0 {
96 CliService::info(" Execution Steps:");
97 CliService::key_value(
98 " Steps",
99 &format!(
100 "{} ({} completed, {} failed, {} pending)",
101 step_summary.total,
102 step_summary.completed,
103 step_summary.failed,
104 step_summary.pending
105 ),
106 );
107 }
108}
109
110fn print_trace_context(events: &[TraceEvent], task_id: Option<&str>) {
111 if let Some(task_id) = task_id {
112 CliService::key_value(
113 " Task",
114 &format!("{task_id} (use: just ai-trace <task_id>)"),
115 );
116 }
117
118 if let Some(session_id) = events.first().and_then(|e| e.session_id.as_ref()) {
119 CliService::key_value(" Session", session_id);
120 }
121
122 if let Some(user_id) = events.first().and_then(|e| e.user_id.as_ref()) {
123 CliService::key_value(" User", user_id);
124 }
125}
126
127fn print_status(events: &[TraceEvent]) {
128 let has_errors = events.iter().any(|e| {
129 e.details.contains("ERROR")
130 || e.details.contains("failed")
131 || e.details.contains("(failed)")
132 });
133
134 if has_errors {
135 CliService::error(" Status: [FAILED]");
136 } else {
137 CliService::success(" Status: [OK]");
138 }
139}