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}