1#[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#[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
18static 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
23const MAX_PERSISTED_CONTEXTS: usize = 10; pub fn add_persisted_context(context_status: ContextStatus) {
28 if let Ok(mut contexts) = PERSISTED_CONTEXTS.write() {
29 contexts.push_front(context_status.clone());
31
32 if contexts.len() > MAX_PERSISTED_CONTEXTS {
34 contexts.pop_back();
35 }
36 }
37
38 println!(
40 "TRACE: {} completed in {:?}",
41 context_status.name, context_status.duration
42 );
43}
44
45pub 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
54pub 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 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}