1use crate::inspector::{Inspector, InspectorStats};
6use crate::task::{TaskInfo, TaskState};
7use crate::timeline::Event;
8use std::fmt::Write as FmtWrite;
9
10pub mod html;
11
12pub struct Reporter {
14 inspector: Inspector,
15}
16
17impl Reporter {
18 #[must_use]
20 pub fn new(inspector: Inspector) -> Self {
21 Self { inspector }
22 }
23
24 #[must_use]
26 pub fn global() -> Self {
27 Self::new(Inspector::global().clone())
28 }
29
30 pub fn print_summary(&self) {
32 let stats = self.inspector.stats();
33 let tasks = self.inspector.get_all_tasks();
34
35 println!("┌─────────────────────────────────────────────────────────────┐");
36 println!("│ async-inspect - Task Summary │");
37 println!("├─────────────────────────────────────────────────────────────┤");
38 println!("│ │");
39
40 self.print_stats(&stats);
41
42 println!("│ │");
43 println!("├─────────────────────────────────────────────────────────────┤");
44 println!("│ Tasks │");
45 println!("├─────────────────────────────────────────────────────────────┤");
46
47 if tasks.is_empty() {
48 println!("│ No tasks tracked │");
49 } else {
50 for task in &tasks {
51 self.print_task_line(task);
52 }
53 }
54
55 println!("└─────────────────────────────────────────────────────────────┘");
56 }
57
58 fn print_stats(&self, stats: &InspectorStats) {
60 println!(
61 "│ Total Tasks: {:>3} │",
62 stats.total_tasks
63 );
64 println!(
65 "│ Active: {:>3} (Running: {}, Blocked: {}) │",
66 stats.running_tasks + stats.blocked_tasks,
67 stats.running_tasks,
68 stats.blocked_tasks
69 );
70 println!(
71 "│ Completed: {:>3} │",
72 stats.completed_tasks
73 );
74 println!(
75 "│ Failed: {:>3} │",
76 stats.failed_tasks
77 );
78 println!(
79 "│ Total Events: {:>3} │",
80 stats.total_events
81 );
82 println!(
83 "│ Duration: {:.2}s │",
84 stats.timeline_duration.as_secs_f64()
85 );
86 }
87
88 fn print_task_line(&self, task: &TaskInfo) {
90 let state_icon = match task.state {
91 TaskState::Pending => "[PEND]",
92 TaskState::Running => "[RUN] ",
93 TaskState::Blocked { .. } => "[WAIT]",
94 TaskState::Completed => "[OK] ",
95 TaskState::Failed => "[FAIL]",
96 };
97
98 let status = format!("{} {} {}", task.id, state_icon, task.name);
99 println!("│ {status:<59} │");
100
101 if let TaskState::Blocked { ref await_point } = task.state {
103 let detail = format!(
104 " └─> Waiting: {} ({:.2}s)",
105 await_point,
106 task.time_since_update().as_secs_f64()
107 );
108 println!("│ {detail:<59} │");
109 }
110 }
111
112 pub fn print_task_details(&self, task_id: crate::task::TaskId) {
114 let Some(task) = self.inspector.get_task(task_id) else {
115 println!("Task {task_id} not found");
116 return;
117 };
118
119 println!("┌─────────────────────────────────────────────────────────────┐");
120 println!(
121 "│ Task Details: {} │",
122 task.id
123 );
124 println!("├─────────────────────────────────────────────────────────────┤");
125 println!("│ │");
126 println!("│ Name: {:<44}│", task.name);
127 println!("│ State: {:<44}│", task.state.to_string());
128 println!(
129 "│ Age: {:.2}s{:<38}│",
130 task.age().as_secs_f64(),
131 ""
132 );
133 println!("│ Poll Count: {:<44}│", task.poll_count);
134 println!(
135 "│ Total Runtime: {:.2}s{:<38}│",
136 task.total_run_time.as_secs_f64(),
137 ""
138 );
139
140 if let Some(parent) = task.parent {
141 println!("│ Parent: {:<44}│", parent.to_string());
142 }
143
144 if let Some(location) = &task.location {
145 println!("│ Location: {location:<44}│");
146 }
147
148 println!("│ │");
149 println!("├─────────────────────────────────────────────────────────────┤");
150 println!("│ Events │");
151 println!("├─────────────────────────────────────────────────────────────┤");
152
153 let events = self.inspector.get_task_events(task_id);
154 if events.is_empty() {
155 println!("│ No events recorded │");
156 } else {
157 for event in events.iter().take(20) {
158 let event_str = format!("{}", event.kind);
159 println!("│ {event_str:<59} │");
160 }
161
162 if events.len() > 20 {
163 println!(
164 "│ ... and {} more events │",
165 events.len() - 20
166 );
167 }
168 }
169
170 println!("└─────────────────────────────────────────────────────────────┘");
171 }
172
173 pub fn print_timeline(&self) {
175 let events = self.inspector.get_events();
176
177 println!("┌─────────────────────────────────────────────────────────────┐");
178 println!("│ async-inspect - Timeline │");
179 println!("├─────────────────────────────────────────────────────────────┤");
180
181 if events.is_empty() {
182 println!("│ No events recorded │");
183 } else {
184 for event in events.iter().take(50) {
185 self.print_event_line(event);
186 }
187
188 if events.len() > 50 {
189 println!("│ │");
190 println!(
191 "│ ... and {} more events │",
192 events.len() - 50
193 );
194 }
195 }
196
197 println!("└─────────────────────────────────────────────────────────────┘");
198 }
199
200 fn print_event_line(&self, event: &Event) {
202 let time_str = format!("[{:.3}s]", event.age().as_secs_f64());
203 let event_str = format!("{} {}: {}", time_str, event.task_id, event.kind);
204
205 let truncated = if event_str.len() > 59 {
207 format!("{}...", &event_str[..56])
208 } else {
209 event_str
210 };
211
212 println!("│ {truncated:<59} │");
213 }
214
215 #[must_use]
217 pub fn generate_report(&self) -> String {
218 let mut report = String::new();
219 let stats = self.inspector.stats();
220 let tasks = self.inspector.get_all_tasks();
221
222 writeln!(report, "[async-inspect] Report").unwrap();
223 writeln!(report, "=======================").unwrap();
224 writeln!(report).unwrap();
225 writeln!(report, "Statistics:").unwrap();
226 writeln!(report, " Total Tasks: {}", stats.total_tasks).unwrap();
227 writeln!(report, " Pending: {}", stats.pending_tasks).unwrap();
228 writeln!(report, " Running: {}", stats.running_tasks).unwrap();
229 writeln!(report, " Blocked: {}", stats.blocked_tasks).unwrap();
230 writeln!(report, " Completed: {}", stats.completed_tasks).unwrap();
231 writeln!(report, " Failed: {}", stats.failed_tasks).unwrap();
232 writeln!(report, " Total Events: {}", stats.total_events).unwrap();
233 writeln!(
234 report,
235 " Duration: {:.2}s",
236 stats.timeline_duration.as_secs_f64()
237 )
238 .unwrap();
239 writeln!(report).unwrap();
240
241 writeln!(report, "Tasks:").unwrap();
242 for task in &tasks {
243 writeln!(report, " {task}").unwrap();
244 }
245
246 report
247 }
248
249 pub fn print_compact_summary(&self) {
251 let stats = self.inspector.stats();
252 println!(
253 "async-inspect: {} tasks ({} active, {} completed, {} failed) | {} events | {:.2}s",
254 stats.total_tasks,
255 stats.running_tasks + stats.blocked_tasks,
256 stats.completed_tasks,
257 stats.failed_tasks,
258 stats.total_events,
259 stats.timeline_duration.as_secs_f64()
260 );
261 }
262
263 pub fn print_gantt_timeline(&self) {
265 let tasks = self.inspector.get_all_tasks();
266
267 if tasks.is_empty() {
268 println!("No tasks to display");
269 return;
270 }
271
272 let start_time = tasks
274 .iter()
275 .map(|t| t.created_at)
276 .min()
277 .expect("At least one task");
278
279 let end_time = tasks
280 .iter()
281 .map(|t| t.created_at + t.age())
282 .max()
283 .expect("At least one task");
284
285 let total_duration = end_time.duration_since(start_time);
286
287 const TIMELINE_WIDTH: usize = 50;
289
290 println!("┌────────────────────────────────────────────────────────────────────────────┐");
291 println!("│ Concurrency Timeline (Gantt View) │");
292 println!("├────────────────────────────────────────────────────────────────────────────┤");
293 println!("│ │");
294
295 let time_markers = self.generate_time_markers(total_duration, TIMELINE_WIDTH);
297 println!("│ Time: {time_markers}│");
298 println!("│ {}│", self.generate_timeline_ruler(TIMELINE_WIDTH));
299 println!("│ │");
300
301 for task in &tasks {
303 let task_line =
304 self.generate_task_timeline(task, start_time, total_duration, TIMELINE_WIDTH);
305 println!("│ {task_line}│");
306 }
307
308 println!("│ │");
309 println!("│ Legend: █ Running ░ Blocked ─ Waiting ✓ Completed ✗ Failed │");
310 println!("└────────────────────────────────────────────────────────────────────────────┘");
311 }
312
313 fn generate_time_markers(&self, total_duration: std::time::Duration, width: usize) -> String {
315 let mut markers = String::new();
316 let millis = total_duration.as_millis();
317
318 let positions = [0, width / 4, width / 2, 3 * width / 4, width];
320 let mut last_end = 0;
321
322 for &pos in &positions {
323 let time_ms = (millis as f64 * pos as f64 / width as f64) as u128;
324 let marker = format!("{time_ms}ms");
325
326 if pos > last_end {
328 let spaces = pos.saturating_sub(last_end);
329 markers.push_str(&" ".repeat(spaces));
330 }
331
332 markers.push_str(&marker);
333 last_end = pos + marker.len();
334 }
335
336 if markers.len() < width {
338 markers.push_str(&" ".repeat(width - markers.len()));
339 }
340
341 markers
342 }
343
344 fn generate_timeline_ruler(&self, width: usize) -> String {
346 let mut ruler = String::new();
347 for i in 0..width {
348 if i % 10 == 0 {
349 ruler.push('|');
350 } else if i % 5 == 0 {
351 ruler.push('·');
352 } else {
353 ruler.push('─');
354 }
355 }
356 ruler
357 }
358
359 fn generate_task_timeline(
361 &self,
362 task: &TaskInfo,
363 start_time: std::time::Instant,
364 total_duration: std::time::Duration,
365 width: usize,
366 ) -> String {
367 let mut line = String::new();
368
369 let name = if task.name.len() > 12 {
371 format!("{:.9}...", task.name)
372 } else {
373 format!("{:<12}", task.name)
374 };
375 line.push_str(&name);
376 line.push_str(": ");
377
378 let task_start = task.created_at.duration_since(start_time);
380 let task_duration = task.age();
381
382 let start_pos = ((task_start.as_millis() as f64 / total_duration.as_millis() as f64)
383 * width as f64) as usize;
384 let task_len = ((task_duration.as_millis() as f64 / total_duration.as_millis() as f64)
385 * width as f64)
386 .max(1.0) as usize;
387
388 for i in 0..width {
390 if i < start_pos {
391 line.push(' ');
392 } else if i < start_pos + task_len {
393 let ch = match task.state {
395 TaskState::Running => '█',
396 TaskState::Blocked { .. } => '░',
397 TaskState::Completed => '█',
398 TaskState::Failed => '▓',
399 TaskState::Pending => '─',
400 };
401 line.push(ch);
402 } else {
403 line.push(' ');
404 }
405 }
406
407 let indicator = match task.state {
409 TaskState::Completed => " ✓",
410 TaskState::Failed => " ✗",
411 TaskState::Running => " →",
412 TaskState::Blocked { .. } => " ⏸",
413 TaskState::Pending => " ○",
414 };
415 line.push_str(indicator);
416
417 while line.len() < 74 {
419 line.push(' ');
420 }
421
422 line
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429
430 #[test]
431 fn test_reporter_creation() {
432 let inspector = Inspector::new();
433 let reporter = Reporter::new(inspector);
434 reporter.print_compact_summary();
436 }
437
438 #[test]
439 fn test_generate_report() {
440 let inspector = Inspector::new();
441 inspector.register_task("test".to_string());
442
443 let reporter = Reporter::new(inspector);
444 let report = reporter.generate_report();
445
446 assert!(report.contains("[async-inspect] Report"));
447 assert!(report.contains("Total Tasks: 1"));
448 }
449}