1use std::collections::HashMap;
3use std::io::Write;
4
5use anyhow::Result;
6use serde::Serialize;
7
8use crate::output::Emitter;
9use crate::util::discover::SessionFile;
10
11pub struct StatsOpts {
14 pub max_tokens: usize,
15}
16
17#[derive(Serialize, Debug)]
20struct StatsRecord {
21 #[serde(rename = "type")]
22 record_type: &'static str,
23 total_sessions: usize,
24 total_size_bytes: u64,
25 total_size_human: String,
26 project_count: usize,
27 projects: Vec<ProjectStat>,
28}
29
30#[derive(Serialize, Debug)]
31struct ProjectStat {
32 name: String,
33 sessions: usize,
34 size_bytes: u64,
35 size_human: String,
36}
37
38pub fn run<W: Write>(_opts: &StatsOpts, files: &[SessionFile], em: &mut Emitter<W>) -> Result<()> {
41 let total_size: u64 = files.iter().map(|f| f.size_bytes).sum();
42
43 let mut projects: HashMap<String, (usize, u64)> = HashMap::new();
44 for f in files {
45 let entry = projects.entry(f.project_name.clone()).or_default();
46 entry.0 += 1;
47 entry.1 += f.size_bytes;
48 }
49
50 let mut sorted: Vec<_> = projects.into_iter().collect();
51 sorted.sort_by(|a, b| b.1 .1.cmp(&a.1 .1));
52
53 let project_stats: Vec<ProjectStat> = sorted
54 .iter()
55 .take(15)
56 .map(|(name, (count, size))| ProjectStat {
57 name: name.clone(),
58 sessions: *count,
59 size_bytes: *size,
60 size_human: format_bytes(*size),
61 })
62 .collect();
63
64 let rec = StatsRecord {
65 record_type: "stats",
66 total_sessions: files.len(),
67 total_size_bytes: total_size,
68 total_size_human: format_bytes(total_size),
69 project_count: sorted.len(),
70 projects: project_stats,
71 };
72
73 em.emit(&rec)?;
74 em.flush()?;
75 Ok(())
76}
77
78pub fn format_bytes(bytes: u64) -> String {
81 if bytes < 1024 {
82 format!("{}B", bytes)
83 } else if bytes < 1024 * 1024 {
84 format!("{:.1}KB", bytes as f64 / 1024.0)
85 } else if bytes < 1024 * 1024 * 1024 {
86 format!("{:.1}MB", bytes as f64 / (1024.0 * 1024.0))
87 } else {
88 format!("{:.2}GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
89 }
90}