Skip to main content

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

1use 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}