radicle_cli/commands/
stats.rs1mod args;
2
3use std::path::Path;
4
5use localtime::LocalDuration;
6use localtime::LocalTime;
7use radicle::git;
8use radicle::issue::cache::Issues as _;
9use radicle::node::address;
10use radicle::node::routing;
11use radicle::patch::cache::Patches as _;
12use radicle::storage::{ReadRepository, ReadStorage, WriteRepository};
13use radicle_term::Element;
14use serde::Serialize;
15
16use crate::terminal as term;
17
18pub use args::Args;
19pub(crate) use args::ABOUT;
20
21#[derive(Default, Serialize)]
22#[serde(rename_all = "camelCase")]
23struct NodeStats {
24 all: usize,
25 public_daily: usize,
26 online_daily: usize,
27 online_weekly: usize,
28 seeding_weekly: usize,
29}
30
31#[derive(Default, Serialize)]
32#[serde(rename_all = "camelCase")]
33struct LocalStats {
34 repos: usize,
35 issues: usize,
36 patches: usize,
37 pushes: usize,
38 forks: usize,
39}
40
41#[derive(Default, Serialize)]
42#[serde(rename_all = "camelCase")]
43struct RepoStats {
44 unique: usize,
45 replicas: usize,
46}
47
48#[derive(Default, Serialize)]
49#[serde(rename_all = "camelCase")]
50struct Stats {
51 local: LocalStats,
52 repos: RepoStats,
53 nodes: NodeStats,
54}
55
56pub fn run(_args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
57 let profile = ctx.profile()?;
58 let storage = &profile.storage;
59 let mut stats = Stats::default();
60
61 for repo in storage.repositories()? {
62 let repo = storage.repository(repo.rid)?;
63 let issues = term::cob::issues(&profile, &repo)?.counts()?;
64 let patches = term::cob::patches(&profile, &repo)?.counts()?;
65
66 stats.local.issues += issues.total();
67 stats.local.patches += patches.total();
68 stats.local.repos += 1;
69
70 for remote in repo.remote_ids()? {
71 let remote = remote?;
72 let sigrefs = repo.reference_oid(&remote, &git::refs::storage::SIGREFS_BRANCH)?;
73 let mut walk = repo.raw().revwalk()?;
74 walk.push(*sigrefs)?;
75
76 stats.local.pushes += walk.count();
77 stats.local.forks += 1;
78 }
79 }
80
81 let now = LocalTime::now();
82 let db = profile.database()?;
83 stats.nodes.all = address::Store::nodes(&db)?;
84 stats.repos.replicas = routing::Store::len(&db)?;
85
86 {
87 let row = db
88 .db
89 .prepare("SELECT COUNT(DISTINCT repo) FROM routing")?
90 .into_iter()
92 .next()
93 .unwrap()?;
94 let count = row.try_read::<i64, _>(0)? as usize;
95
96 stats.repos.unique = count;
97 }
98
99 {
100 let since = now - LocalDuration::from_mins(60 * 24); let mut stmt = db.db.prepare(
102 "SELECT COUNT(DISTINCT node) FROM announcements WHERE timestamp >= ?1 AND timestamp < ?2",
103 )?;
104 stmt.bind((1, since.as_millis() as i64))?;
105 stmt.bind((2, now.as_millis() as i64))?;
106
107 let row = stmt.iter().next().unwrap()?;
109 stats.nodes.online_daily = row.try_read::<i64, _>(0)? as usize;
110
111 let since = now - LocalDuration::from_mins(60 * 24 * 7); stmt.reset()?;
113 stmt.bind((1, since.as_millis() as i64))?;
114 stmt.bind((2, now.as_millis() as i64))?;
115
116 let row = stmt.iter().next().unwrap()?;
117 stats.nodes.online_weekly = row.try_read::<i64, _>(0)? as usize;
118 }
119
120 {
121 let since = now - LocalDuration::from_mins(60 * 24); let mut stmt = db.db.prepare(
123 "SELECT COUNT(DISTINCT ann.node) FROM announcements as ann
124 JOIN addresses AS addr
125 ON ann.node == addr.node
126 WHERE ann.timestamp >= ?1 AND ann.timestamp < ?2",
127 )?;
128 stmt.bind((1, since.as_millis() as i64))?;
129 stmt.bind((2, now.as_millis() as i64))?;
130
131 let row = stmt
132 .into_iter()
133 .next()
134 .unwrap()?;
136 let count = row.try_read::<i64, _>(0)? as usize;
137
138 stats.nodes.public_daily = count;
139 }
140
141 {
142 let since = now - LocalDuration::from_mins(60 * 24 * 7); let mut stmt = db.db.prepare(
144 "SELECT COUNT(DISTINCT node) FROM routing
145 WHERE timestamp >= ?1 AND timestamp < ?2",
146 )?;
147 stmt.bind((1, since.as_millis() as i64))?;
148 stmt.bind((2, now.as_millis() as i64))?;
149
150 let row = stmt
151 .into_iter()
152 .next()
153 .unwrap()?;
155 let count = row.try_read::<i64, _>(0)? as usize;
156
157 stats.nodes.seeding_weekly = count;
158 }
159
160 let output = term::json::to_pretty(&stats, Path::new("stats.json"))?;
161 output.print();
162
163 Ok(())
164}