chie_shared/types/stats.rs
1//! Statistics and metrics types for CHIE Protocol.
2//!
3//! This module contains types for tracking and reporting statistics:
4//! - Node statistics and performance metrics
5//! - Bandwidth usage statistics
6//! - Platform-wide statistics
7//! - Network health metrics
8//! - Time-series data for monitoring
9
10#[cfg(feature = "schema")]
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13
14use super::core::{Bytes, PeerIdString, Points};
15use super::enums::NodeStatus;
16
17/// Node statistics.
18///
19/// # Examples
20///
21/// ```
22/// use chie_shared::{NodeStats, NodeStatus};
23///
24/// let stats = NodeStats {
25/// peer_id: "12D3KooWExample".to_string(),
26/// status: NodeStatus::Online,
27/// total_bandwidth_bytes: 1024 * 1024 * 1024 * 50, // 50 GB
28/// total_earnings: 5000,
29/// uptime_seconds: 86400 * 30, // 30 days
30/// pinned_content_count: 100,
31/// pinned_storage_bytes: 1024 * 1024 * 1024 * 10, // 10 GB
32/// last_seen_at: chrono::Utc::now(),
33/// };
34///
35/// // Convert to display-friendly units
36/// assert_eq!(stats.bandwidth_gb() as u64, 50);
37/// assert_eq!(stats.storage_gb() as u64, 10);
38/// assert_eq!(stats.uptime_days() as u64, 30);
39///
40/// // Check node status
41/// assert!(stats.is_online());
42/// assert!(stats.is_recently_active());
43/// ```
44#[derive(Debug, Clone, Serialize, Deserialize)]
45#[cfg_attr(feature = "schema", derive(JsonSchema))]
46pub struct NodeStats {
47 pub peer_id: PeerIdString,
48 pub status: NodeStatus,
49 pub total_bandwidth_bytes: Bytes,
50 pub total_earnings: Points,
51 pub uptime_seconds: u64,
52 pub pinned_content_count: u64,
53 pub pinned_storage_bytes: Bytes,
54 pub last_seen_at: chrono::DateTime<chrono::Utc>,
55}
56
57impl NodeStats {
58 /// Get total bandwidth in gigabytes.
59 pub fn bandwidth_gb(&self) -> f64 {
60 self.total_bandwidth_bytes as f64 / (1024.0 * 1024.0 * 1024.0)
61 }
62
63 /// Get total bandwidth in terabytes.
64 pub fn bandwidth_tb(&self) -> f64 {
65 self.total_bandwidth_bytes as f64 / (1024.0 * 1024.0 * 1024.0 * 1024.0)
66 }
67
68 /// Get pinned storage in gigabytes.
69 pub fn storage_gb(&self) -> f64 {
70 self.pinned_storage_bytes as f64 / (1024.0 * 1024.0 * 1024.0)
71 }
72
73 /// Get uptime in days.
74 pub fn uptime_days(&self) -> f64 {
75 self.uptime_seconds as f64 / 86400.0
76 }
77
78 /// Check if the node is currently online.
79 pub fn is_online(&self) -> bool {
80 self.status == NodeStatus::Online
81 }
82
83 /// Check if the node has been seen recently (within 5 minutes).
84 pub fn is_recently_active(&self) -> bool {
85 let now = chrono::Utc::now();
86 let diff = now.signed_duration_since(self.last_seen_at);
87 diff.num_seconds() <= 300
88 }
89}
90
91/// Bandwidth statistics over a time period.
92///
93/// # Examples
94///
95/// ```
96/// use chie_shared::BandwidthStats;
97///
98/// let now = chrono::Utc::now();
99/// let hour_ago = now - chrono::Duration::hours(1);
100///
101/// let stats = BandwidthStats {
102/// bytes_uploaded: 1024 * 1024 * 100, // 100 MB
103/// bytes_downloaded: 1024 * 1024 * 50, // 50 MB
104/// chunks_served: 400,
105/// chunks_requested: 200,
106/// avg_upload_speed_bps: 1024 * 1024, // 1 MB/s
107/// avg_download_speed_bps: 512 * 1024, // 512 KB/s
108/// peak_upload_speed_bps: 5 * 1024 * 1024, // 5 MB/s
109/// peak_download_speed_bps: 2 * 1024 * 1024, // 2 MB/s
110/// period_start: hour_ago,
111/// period_end: now,
112/// };
113///
114/// // Calculate throughput
115/// let total_transfer = stats.bytes_uploaded + stats.bytes_downloaded;
116/// assert!(total_transfer > 0);
117///
118/// // Check chunk efficiency
119/// assert!(stats.chunks_served > stats.chunks_requested);
120/// ```
121#[derive(Debug, Clone, Serialize, Deserialize)]
122#[cfg_attr(feature = "schema", derive(JsonSchema))]
123pub struct BandwidthStats {
124 /// Total bytes uploaded.
125 pub bytes_uploaded: Bytes,
126 /// Total bytes downloaded.
127 pub bytes_downloaded: Bytes,
128 /// Number of chunks served.
129 pub chunks_served: u64,
130 /// Number of chunks requested.
131 pub chunks_requested: u64,
132 /// Average upload speed (bytes per second).
133 pub avg_upload_speed_bps: u64,
134 /// Average download speed (bytes per second).
135 pub avg_download_speed_bps: u64,
136 /// Peak upload speed (bytes per second).
137 pub peak_upload_speed_bps: u64,
138 /// Peak download speed (bytes per second).
139 pub peak_download_speed_bps: u64,
140 /// Statistics time range start.
141 pub period_start: chrono::DateTime<chrono::Utc>,
142 /// Statistics time range end.
143 pub period_end: chrono::DateTime<chrono::Utc>,
144}
145
146/// Platform-wide statistics.
147///
148/// # Examples
149///
150/// ```
151/// use chie_shared::PlatformStats;
152///
153/// let stats = PlatformStats {
154/// total_users: 10_000,
155/// total_creators: 1_500,
156/// active_nodes: 500,
157/// total_content: 5_000,
158/// total_storage_bytes: 1024 * 1024 * 1024 * 1024 * 5, // 5 TB
159/// total_bandwidth_bytes: 1024 * 1024 * 1024 * 1024 * 50, // 50 TB
160/// total_points_distributed: 1_000_000,
161/// total_transactions: 50_000,
162/// timestamp: chrono::Utc::now(),
163/// };
164///
165/// // Calculate ratios
166/// let creator_ratio = stats.total_creators as f64 / stats.total_users as f64;
167/// assert!(creator_ratio > 0.0 && creator_ratio < 1.0);
168///
169/// // Average content per creator
170/// let avg_content = stats.total_content / stats.total_creators;
171/// assert!(avg_content > 0);
172/// ```
173#[derive(Debug, Clone, Serialize, Deserialize)]
174#[cfg_attr(feature = "schema", derive(JsonSchema))]
175pub struct PlatformStats {
176 /// Total number of users.
177 pub total_users: u64,
178 /// Total number of creators.
179 pub total_creators: u64,
180 /// Total number of active nodes.
181 pub active_nodes: u64,
182 /// Total content items.
183 pub total_content: u64,
184 /// Total storage used (bytes).
185 pub total_storage_bytes: Bytes,
186 /// Total bandwidth served (bytes).
187 pub total_bandwidth_bytes: Bytes,
188 /// Total points distributed.
189 pub total_points_distributed: Points,
190 /// Total transactions.
191 pub total_transactions: u64,
192 /// Statistics timestamp.
193 pub timestamp: chrono::DateTime<chrono::Utc>,
194}
195
196/// Network health metrics.
197///
198/// # Examples
199///
200/// ```
201/// use chie_shared::NetworkHealth;
202///
203/// let health = NetworkHealth {
204/// online_nodes: 500,
205/// avg_uptime_percent: 99.5,
206/// avg_latency_ms: 45.0,
207/// avg_replication_factor: 4.2,
208/// content_availability_percent: 98.5,
209/// failed_proofs_24h: 50,
210/// successful_proofs_24h: 10_000,
211/// timestamp: chrono::Utc::now(),
212/// };
213///
214/// // Assess network health
215/// let is_healthy = health.avg_uptime_percent > 95.0
216/// && health.avg_latency_ms < 100.0
217/// && health.content_availability_percent > 95.0;
218/// assert!(is_healthy);
219///
220/// // Calculate success rate
221/// let total_proofs = health.successful_proofs_24h + health.failed_proofs_24h;
222/// let success_rate = health.successful_proofs_24h as f64 / total_proofs as f64;
223/// assert!(success_rate > 0.99);
224/// ```
225#[derive(Debug, Clone, Serialize, Deserialize)]
226#[cfg_attr(feature = "schema", derive(JsonSchema))]
227pub struct NetworkHealth {
228 /// Number of online nodes.
229 pub online_nodes: u64,
230 /// Average node uptime (percentage).
231 pub avg_uptime_percent: f64,
232 /// Average latency (milliseconds).
233 pub avg_latency_ms: f64,
234 /// Network replication factor (average copies per content).
235 pub avg_replication_factor: f64,
236 /// Percentage of content with at least 3 seeders.
237 pub content_availability_percent: f64,
238 /// Failed proof submissions (last 24h).
239 pub failed_proofs_24h: u64,
240 /// Successful proof submissions (last 24h).
241 pub successful_proofs_24h: u64,
242 /// Health check timestamp.
243 pub timestamp: chrono::DateTime<chrono::Utc>,
244}
245
246/// Time-series data point for metrics.
247#[derive(Debug, Clone, Serialize, Deserialize)]
248#[cfg_attr(feature = "schema", derive(JsonSchema))]
249pub struct TimeSeriesPoint {
250 /// Timestamp of the data point.
251 pub timestamp: chrono::DateTime<chrono::Utc>,
252 /// Metric value.
253 pub value: f64,
254}
255
256/// Time-series metric data.
257#[derive(Debug, Clone, Serialize, Deserialize)]
258#[cfg_attr(feature = "schema", derive(JsonSchema))]
259pub struct TimeSeriesMetric {
260 /// Metric name.
261 pub metric_name: String,
262 /// Data points.
263 pub data_points: Vec<TimeSeriesPoint>,
264 /// Unit of measurement.
265 pub unit: String,
266}
267
268impl TimeSeriesMetric {
269 /// Create a new time-series metric.
270 pub fn new(metric_name: impl Into<String>, unit: impl Into<String>) -> Self {
271 Self {
272 metric_name: metric_name.into(),
273 unit: unit.into(),
274 data_points: Vec::new(),
275 }
276 }
277
278 /// Add a data point.
279 pub fn add_point(&mut self, timestamp: chrono::DateTime<chrono::Utc>, value: f64) {
280 self.data_points.push(TimeSeriesPoint { timestamp, value });
281 }
282
283 /// Get the latest value.
284 pub fn latest_value(&self) -> Option<f64> {
285 self.data_points.last().map(|p| p.value)
286 }
287
288 /// Calculate average value.
289 pub fn average(&self) -> f64 {
290 if self.data_points.is_empty() {
291 return 0.0;
292 }
293 let sum: f64 = self.data_points.iter().map(|p| p.value).sum();
294 sum / self.data_points.len() as f64
295 }
296
297 /// Calculate maximum value.
298 pub fn max(&self) -> Option<f64> {
299 self.data_points
300 .iter()
301 .map(|p| p.value)
302 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
303 }
304
305 /// Calculate minimum value.
306 pub fn min(&self) -> Option<f64> {
307 self.data_points
308 .iter()
309 .map(|p| p.value)
310 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317
318 #[test]
319 fn test_node_stats_bandwidth_conversions() {
320 let stats = NodeStats {
321 peer_id: "12D3KooTest".to_string(),
322 status: NodeStatus::Online,
323 total_bandwidth_bytes: 5 * 1024 * 1024 * 1024, // 5 GB
324 total_earnings: 1000,
325 uptime_seconds: 86400, // 1 day
326 pinned_content_count: 10,
327 pinned_storage_bytes: 2 * 1024 * 1024 * 1024, // 2 GB
328 last_seen_at: chrono::Utc::now(),
329 };
330
331 assert!((stats.bandwidth_gb() - 5.0).abs() < 0.001);
332 assert!((stats.storage_gb() - 2.0).abs() < 0.001);
333 assert!((stats.uptime_days() - 1.0).abs() < 0.001);
334 assert!(stats.is_online());
335 assert!(stats.is_recently_active());
336 }
337
338 #[test]
339 fn test_time_series_metric_new() {
340 let metric = TimeSeriesMetric::new("test_metric", "units");
341 assert_eq!(metric.metric_name, "test_metric");
342 assert_eq!(metric.unit, "units");
343 assert!(metric.data_points.is_empty());
344 }
345
346 #[test]
347 fn test_time_series_metric_add_point() {
348 let mut metric = TimeSeriesMetric::new("test", "units");
349 let now = chrono::Utc::now();
350
351 metric.add_point(now, 10.0);
352 metric.add_point(now, 20.0);
353 metric.add_point(now, 30.0);
354
355 assert_eq!(metric.data_points.len(), 3);
356 assert_eq!(metric.latest_value(), Some(30.0));
357 }
358
359 #[test]
360 fn test_time_series_metric_average() {
361 let mut metric = TimeSeriesMetric::new("test", "units");
362 let now = chrono::Utc::now();
363
364 metric.add_point(now, 10.0);
365 metric.add_point(now, 20.0);
366 metric.add_point(now, 30.0);
367
368 assert!((metric.average() - 20.0).abs() < 0.001);
369 }
370
371 #[test]
372 fn test_time_series_metric_max_min() {
373 let mut metric = TimeSeriesMetric::new("test", "units");
374 let now = chrono::Utc::now();
375
376 metric.add_point(now, 10.0);
377 metric.add_point(now, 30.0);
378 metric.add_point(now, 20.0);
379
380 assert_eq!(metric.max(), Some(30.0));
381 assert_eq!(metric.min(), Some(10.0));
382 }
383
384 #[test]
385 fn test_time_series_metric_empty() {
386 let metric = TimeSeriesMetric::new("test", "units");
387
388 assert_eq!(metric.latest_value(), None);
389 assert_eq!(metric.average(), 0.0);
390 assert_eq!(metric.max(), None);
391 assert_eq!(metric.min(), None);
392 }
393}