fastn_context/
status.rs

1/// Status snapshot of the context tree
2#[derive(Debug, Clone)]
3pub struct Status {
4    pub global_context: ContextStatus,
5    pub persisted_contexts: Option<Vec<ContextStatus>>,
6    pub timestamp: std::time::SystemTime,
7}
8
9/// Status information for a single context
10#[derive(Debug, Clone)]
11pub struct ContextStatus {
12    pub name: String,
13    pub is_cancelled: bool,
14    pub duration: std::time::Duration,
15    pub children: Vec<ContextStatus>,
16}
17
18/// Global storage for persisted contexts (circular buffer)
19static PERSISTED_CONTEXTS: std::sync::LazyLock<
20    std::sync::RwLock<std::collections::VecDeque<ContextStatus>>,
21> = std::sync::LazyLock::new(|| std::sync::RwLock::new(std::collections::VecDeque::new()));
22
23/// Maximum number of persisted contexts to keep (configurable via env)
24const MAX_PERSISTED_CONTEXTS: usize = 10; // TODO: Make configurable via env var
25
26/// Add a context to the persisted contexts circular buffer
27pub fn add_persisted_context(context_status: ContextStatus) {
28    if let Ok(mut contexts) = PERSISTED_CONTEXTS.write() {
29        // Add to front
30        contexts.push_front(context_status.clone());
31
32        // Keep only max number
33        if contexts.len() > MAX_PERSISTED_CONTEXTS {
34            contexts.pop_back();
35        }
36    }
37
38    // Log as trace event
39    println!(
40        "TRACE: {} completed in {:?}",
41        context_status.name, context_status.duration
42    );
43}
44
45/// Get current status snapshot of entire context tree
46pub fn status() -> Status {
47    Status {
48        global_context: crate::context::global().status(),
49        persisted_contexts: None,
50        timestamp: std::time::SystemTime::now(),
51    }
52}
53
54/// Get status including recent completed contexts (distributed tracing)
55pub fn status_with_latest() -> Status {
56    let persisted = if let Ok(contexts) = PERSISTED_CONTEXTS.read() {
57        Some(contexts.iter().cloned().collect())
58    } else {
59        None
60    };
61
62    Status {
63        global_context: crate::context::global().status(),
64        persisted_contexts: persisted,
65        timestamp: std::time::SystemTime::now(),
66    }
67}
68
69impl std::fmt::Display for Status {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        writeln!(f, "fastn Context Status")?;
72        writeln!(f, "Snapshot: {:?}", self.timestamp)?;
73        writeln!(f)?;
74
75        Self::display_context(&self.global_context, f, 0)?;
76
77        // Show persisted contexts if included
78        if let Some(persisted) = &self.persisted_contexts {
79            if !persisted.is_empty() {
80            writeln!(f, "\nRecent completed contexts (last {}):", persisted.len())?;
81            for ctx in persisted {
82                let duration_str = if ctx.duration.as_secs() > 60 {
83                    format!(
84                        "{}m {}s",
85                        ctx.duration.as_secs() / 60,
86                        ctx.duration.as_secs() % 60
87                    )
88                } else {
89                    format!("{:.1}s", ctx.duration.as_secs_f64())
90                };
91
92                let status_str = if ctx.is_cancelled {
93                    "cancelled"
94                } else {
95                    "completed"
96                };
97                writeln!(f, "- {} ({}, {})", ctx.name, duration_str, status_str)?;
98            }
99            }
100        }
101
102        Ok(())
103    }
104}
105
106impl Status {
107    fn display_context(
108        ctx: &ContextStatus,
109        f: &mut std::fmt::Formatter<'_>,
110        depth: usize,
111    ) -> std::fmt::Result {
112        let indent = "  ".repeat(depth);
113        let status_icon = if ctx.is_cancelled { "❌" } else { "✅" };
114
115        let duration_str = if ctx.duration.as_secs() > 60 {
116            format!(
117                "{}m {}s",
118                ctx.duration.as_secs() / 60,
119                ctx.duration.as_secs() % 60
120            )
121        } else {
122            format!("{:.1}s", ctx.duration.as_secs_f64())
123        };
124
125        writeln!(
126            f,
127            "{}{} {} ({}, {})",
128            indent,
129            status_icon,
130            ctx.name,
131            duration_str,
132            if ctx.is_cancelled {
133                "cancelled"
134            } else {
135                "active"
136            }
137        )?;
138
139        for child in &ctx.children {
140            Self::display_context(child, f, depth + 1)?;
141        }
142
143        Ok(())
144    }
145}