Skip to main content

systemprompt_cli/commands/infrastructure/logs/trace/
summary.rs

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