ipfrs_storage/
cost_analytics.rs

1//! Cost Analytics for storage optimization
2//!
3//! This module provides cost tracking and optimization for storage:
4//! - Per-tier cost tracking (hot/warm/cold/archive)
5//! - Storage operation cost analysis (reads, writes, retrievals)
6//! - Cloud storage cost modeling (AWS S3, Azure, GCP)
7//! - Cost-aware data placement recommendations
8//! - Budget tracking and alerting
9//! - Cost projection and forecasting
10
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::time::SystemTime;
15
16/// Storage tier for cost calculation
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub enum CostTier {
19    /// Hot storage (frequently accessed, expensive)
20    Hot,
21    /// Standard storage (balanced cost/performance)
22    Standard,
23    /// Infrequent access (cheaper storage, retrieval fees)
24    InfrequentAccess,
25    /// Archive (very cheap storage, high retrieval cost)
26    Archive,
27    /// Glacier (coldest tier, highest retrieval cost)
28    Glacier,
29}
30
31/// Cloud provider
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub enum CloudProvider {
34    AWS,
35    Azure,
36    GCP,
37    Custom,
38}
39
40/// Cost model for a storage tier
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct TierCostModel {
43    /// Storage cost per GB per month
44    pub storage_cost_per_gb_month: f64,
45    /// PUT/POST/LIST request cost per 1000 requests
46    pub write_request_cost_per_1k: f64,
47    /// GET/HEAD request cost per 1000 requests
48    pub read_request_cost_per_1k: f64,
49    /// Data retrieval cost per GB (for cold tiers)
50    pub retrieval_cost_per_gb: f64,
51    /// Data transfer out cost per GB
52    pub transfer_cost_per_gb: f64,
53    /// Minimum storage duration in days
54    pub min_storage_days: u32,
55    /// Early deletion fee per GB
56    pub early_deletion_cost_per_gb: f64,
57}
58
59impl TierCostModel {
60    /// AWS S3 Standard tier pricing (approximate)
61    pub fn aws_s3_standard() -> Self {
62        Self {
63            storage_cost_per_gb_month: 0.023,
64            write_request_cost_per_1k: 0.005,
65            read_request_cost_per_1k: 0.0004,
66            retrieval_cost_per_gb: 0.0,
67            transfer_cost_per_gb: 0.09,
68            min_storage_days: 0,
69            early_deletion_cost_per_gb: 0.0,
70        }
71    }
72
73    /// AWS S3 Infrequent Access tier pricing
74    pub fn aws_s3_infrequent() -> Self {
75        Self {
76            storage_cost_per_gb_month: 0.0125,
77            write_request_cost_per_1k: 0.01,
78            read_request_cost_per_1k: 0.001,
79            retrieval_cost_per_gb: 0.01,
80            transfer_cost_per_gb: 0.09,
81            min_storage_days: 30,
82            early_deletion_cost_per_gb: 0.0125,
83        }
84    }
85
86    /// AWS S3 Glacier tier pricing
87    pub fn aws_s3_glacier() -> Self {
88        Self {
89            storage_cost_per_gb_month: 0.004,
90            write_request_cost_per_1k: 0.03,
91            read_request_cost_per_1k: 0.0004,
92            retrieval_cost_per_gb: 0.02, // Standard retrieval
93            transfer_cost_per_gb: 0.09,
94            min_storage_days: 90,
95            early_deletion_cost_per_gb: 0.012,
96        }
97    }
98
99    /// Azure Blob Hot tier pricing
100    pub fn azure_hot() -> Self {
101        Self {
102            storage_cost_per_gb_month: 0.018,
103            write_request_cost_per_1k: 0.0055,
104            read_request_cost_per_1k: 0.00044,
105            retrieval_cost_per_gb: 0.0,
106            transfer_cost_per_gb: 0.087,
107            min_storage_days: 0,
108            early_deletion_cost_per_gb: 0.0,
109        }
110    }
111
112    /// Azure Blob Cool tier pricing
113    pub fn azure_cool() -> Self {
114        Self {
115            storage_cost_per_gb_month: 0.01,
116            write_request_cost_per_1k: 0.01,
117            read_request_cost_per_1k: 0.001,
118            retrieval_cost_per_gb: 0.01,
119            transfer_cost_per_gb: 0.087,
120            min_storage_days: 30,
121            early_deletion_cost_per_gb: 0.01,
122        }
123    }
124
125    /// Azure Blob Archive tier pricing
126    pub fn azure_archive() -> Self {
127        Self {
128            storage_cost_per_gb_month: 0.002,
129            write_request_cost_per_1k: 0.011,
130            read_request_cost_per_1k: 0.0055,
131            retrieval_cost_per_gb: 0.02,
132            transfer_cost_per_gb: 0.087,
133            min_storage_days: 180,
134            early_deletion_cost_per_gb: 0.006,
135        }
136    }
137
138    /// GCP Standard storage pricing
139    pub fn gcp_standard() -> Self {
140        Self {
141            storage_cost_per_gb_month: 0.020,
142            write_request_cost_per_1k: 0.005,
143            read_request_cost_per_1k: 0.0004,
144            retrieval_cost_per_gb: 0.0,
145            transfer_cost_per_gb: 0.12,
146            min_storage_days: 0,
147            early_deletion_cost_per_gb: 0.0,
148        }
149    }
150
151    /// GCP Nearline storage pricing
152    pub fn gcp_nearline() -> Self {
153        Self {
154            storage_cost_per_gb_month: 0.010,
155            write_request_cost_per_1k: 0.01,
156            read_request_cost_per_1k: 0.001,
157            retrieval_cost_per_gb: 0.01,
158            transfer_cost_per_gb: 0.12,
159            min_storage_days: 30,
160            early_deletion_cost_per_gb: 0.010,
161        }
162    }
163
164    /// GCP Coldline storage pricing
165    pub fn gcp_coldline() -> Self {
166        Self {
167            storage_cost_per_gb_month: 0.004,
168            write_request_cost_per_1k: 0.01,
169            read_request_cost_per_1k: 0.005,
170            retrieval_cost_per_gb: 0.02,
171            transfer_cost_per_gb: 0.12,
172            min_storage_days: 90,
173            early_deletion_cost_per_gb: 0.012,
174        }
175    }
176}
177
178/// Usage tracking for cost calculation
179#[derive(Debug)]
180pub struct UsageMetrics {
181    /// Total storage in bytes
182    pub storage_bytes: AtomicU64,
183    /// Total write requests
184    pub write_requests: AtomicU64,
185    /// Total read requests
186    pub read_requests: AtomicU64,
187    /// Total bytes retrieved
188    pub bytes_retrieved: AtomicU64,
189    /// Total bytes transferred out
190    pub bytes_transferred: AtomicU64,
191    /// Start time for this period
192    pub period_start: parking_lot::Mutex<SystemTime>,
193}
194
195impl UsageMetrics {
196    fn new() -> Self {
197        Self {
198            storage_bytes: AtomicU64::new(0),
199            write_requests: AtomicU64::new(0),
200            read_requests: AtomicU64::new(0),
201            bytes_retrieved: AtomicU64::new(0),
202            bytes_transferred: AtomicU64::new(0),
203            period_start: parking_lot::Mutex::new(SystemTime::now()),
204        }
205    }
206
207    fn record_write(&self, bytes: u64) {
208        self.storage_bytes.fetch_add(bytes, Ordering::Relaxed);
209        self.write_requests.fetch_add(1, Ordering::Relaxed);
210    }
211
212    fn record_read(&self, bytes: u64) {
213        self.read_requests.fetch_add(1, Ordering::Relaxed);
214        self.bytes_retrieved.fetch_add(bytes, Ordering::Relaxed);
215    }
216
217    fn record_delete(&self, bytes: u64) {
218        self.storage_bytes.fetch_sub(bytes, Ordering::Relaxed);
219    }
220
221    fn record_transfer(&self, bytes: u64) {
222        self.bytes_transferred.fetch_add(bytes, Ordering::Relaxed);
223    }
224}
225
226/// Cost breakdown
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct CostBreakdown {
229    /// Storage costs
230    pub storage_cost: f64,
231    /// Write request costs
232    pub write_request_cost: f64,
233    /// Read request costs
234    pub read_request_cost: f64,
235    /// Data retrieval costs
236    pub retrieval_cost: f64,
237    /// Data transfer costs
238    pub transfer_cost: f64,
239    /// Early deletion costs
240    pub early_deletion_cost: f64,
241    /// Total cost
242    pub total_cost: f64,
243}
244
245impl CostBreakdown {
246    fn calculate_total(&mut self) {
247        self.total_cost = self.storage_cost
248            + self.write_request_cost
249            + self.read_request_cost
250            + self.retrieval_cost
251            + self.transfer_cost
252            + self.early_deletion_cost;
253    }
254}
255
256/// Cost optimizer
257pub struct CostAnalyzer {
258    /// Cost models by tier
259    cost_models: HashMap<CostTier, TierCostModel>,
260    /// Usage metrics by tier
261    usage_metrics: HashMap<CostTier, UsageMetrics>,
262    /// Cloud provider
263    #[allow(dead_code)]
264    provider: CloudProvider,
265}
266
267impl CostAnalyzer {
268    /// Create a new cost analyzer for a specific provider
269    pub fn new(provider: CloudProvider) -> Self {
270        let mut cost_models = HashMap::new();
271
272        match provider {
273            CloudProvider::AWS => {
274                cost_models.insert(CostTier::Standard, TierCostModel::aws_s3_standard());
275                cost_models.insert(
276                    CostTier::InfrequentAccess,
277                    TierCostModel::aws_s3_infrequent(),
278                );
279                cost_models.insert(CostTier::Glacier, TierCostModel::aws_s3_glacier());
280            }
281            CloudProvider::Azure => {
282                cost_models.insert(CostTier::Hot, TierCostModel::azure_hot());
283                cost_models.insert(CostTier::InfrequentAccess, TierCostModel::azure_cool());
284                cost_models.insert(CostTier::Archive, TierCostModel::azure_archive());
285            }
286            CloudProvider::GCP => {
287                cost_models.insert(CostTier::Standard, TierCostModel::gcp_standard());
288                cost_models.insert(CostTier::InfrequentAccess, TierCostModel::gcp_nearline());
289                cost_models.insert(CostTier::Archive, TierCostModel::gcp_coldline());
290            }
291            CloudProvider::Custom => {}
292        }
293
294        Self {
295            cost_models,
296            usage_metrics: HashMap::new(),
297            provider,
298        }
299    }
300
301    /// Set custom cost model for a tier
302    pub fn set_cost_model(&mut self, tier: CostTier, model: TierCostModel) {
303        self.cost_models.insert(tier, model);
304    }
305
306    /// Record a write operation
307    pub fn record_write(&mut self, tier: CostTier, bytes: u64) {
308        self.usage_metrics
309            .entry(tier)
310            .or_insert_with(UsageMetrics::new)
311            .record_write(bytes);
312    }
313
314    /// Record a read operation
315    pub fn record_read(&mut self, tier: CostTier, bytes: u64) {
316        self.usage_metrics
317            .entry(tier)
318            .or_insert_with(UsageMetrics::new)
319            .record_read(bytes);
320    }
321
322    /// Record a delete operation
323    pub fn record_delete(&mut self, tier: CostTier, bytes: u64) {
324        self.usage_metrics
325            .entry(tier)
326            .or_insert_with(UsageMetrics::new)
327            .record_delete(bytes);
328    }
329
330    /// Record data transfer
331    pub fn record_transfer(&mut self, tier: CostTier, bytes: u64) {
332        self.usage_metrics
333            .entry(tier)
334            .or_insert_with(UsageMetrics::new)
335            .record_transfer(bytes);
336    }
337
338    /// Calculate cost for a specific tier
339    pub fn calculate_tier_cost(&self, tier: CostTier, days: u32) -> Option<CostBreakdown> {
340        let model = self.cost_models.get(&tier)?;
341        let metrics = self.usage_metrics.get(&tier)?;
342
343        let storage_gb = metrics.storage_bytes.load(Ordering::Relaxed) as f64 / 1_073_741_824.0;
344        let write_requests = metrics.write_requests.load(Ordering::Relaxed) as f64;
345        let read_requests = metrics.read_requests.load(Ordering::Relaxed) as f64;
346        let retrieved_gb = metrics.bytes_retrieved.load(Ordering::Relaxed) as f64 / 1_073_741_824.0;
347        let transferred_gb =
348            metrics.bytes_transferred.load(Ordering::Relaxed) as f64 / 1_073_741_824.0;
349
350        let months = days as f64 / 30.0;
351
352        let mut breakdown = CostBreakdown {
353            storage_cost: storage_gb * model.storage_cost_per_gb_month * months,
354            write_request_cost: (write_requests / 1000.0) * model.write_request_cost_per_1k,
355            read_request_cost: (read_requests / 1000.0) * model.read_request_cost_per_1k,
356            retrieval_cost: retrieved_gb * model.retrieval_cost_per_gb,
357            transfer_cost: transferred_gb * model.transfer_cost_per_gb,
358            early_deletion_cost: 0.0, // Would need deletion tracking
359            total_cost: 0.0,
360        };
361
362        breakdown.calculate_total();
363        Some(breakdown)
364    }
365
366    /// Calculate total cost across all tiers
367    pub fn calculate_total_cost(&self, days: u32) -> CostBreakdown {
368        let mut total = CostBreakdown {
369            storage_cost: 0.0,
370            write_request_cost: 0.0,
371            read_request_cost: 0.0,
372            retrieval_cost: 0.0,
373            transfer_cost: 0.0,
374            early_deletion_cost: 0.0,
375            total_cost: 0.0,
376        };
377
378        for tier in self.usage_metrics.keys() {
379            if let Some(breakdown) = self.calculate_tier_cost(*tier, days) {
380                total.storage_cost += breakdown.storage_cost;
381                total.write_request_cost += breakdown.write_request_cost;
382                total.read_request_cost += breakdown.read_request_cost;
383                total.retrieval_cost += breakdown.retrieval_cost;
384                total.transfer_cost += breakdown.transfer_cost;
385                total.early_deletion_cost += breakdown.early_deletion_cost;
386            }
387        }
388
389        total.calculate_total();
390        total
391    }
392
393    /// Get tier recommendation based on access pattern
394    pub fn recommend_tier(&self, access_frequency: f64, data_size_gb: f64) -> TierRecommendation {
395        let mut recommendations = Vec::new();
396
397        for (tier, model) in &self.cost_models {
398            // Calculate cost for this tier over 30 days
399            let storage_cost = data_size_gb * model.storage_cost_per_gb_month;
400
401            // Estimate request costs based on access frequency
402            let monthly_accesses = access_frequency * 30.0;
403            let read_cost = (monthly_accesses / 1000.0) * model.read_request_cost_per_1k;
404            let retrieval_cost = monthly_accesses * data_size_gb * model.retrieval_cost_per_gb;
405
406            let total_cost = storage_cost + read_cost + retrieval_cost;
407
408            recommendations.push(TierOption {
409                tier: *tier,
410                estimated_monthly_cost: total_cost,
411                storage_cost,
412                access_cost: read_cost + retrieval_cost,
413            });
414        }
415
416        // Sort by cost
417        recommendations.sort_by(|a, b| {
418            a.estimated_monthly_cost
419                .partial_cmp(&b.estimated_monthly_cost)
420                .unwrap()
421        });
422
423        TierRecommendation {
424            recommended_tier: recommendations[0].tier,
425            options: recommendations,
426            access_frequency,
427            data_size_gb,
428        }
429    }
430
431    /// Project costs for next N days
432    pub fn project_costs(&self, days: u32) -> CostProjection {
433        let current_cost = self.calculate_total_cost(days);
434        let daily_rate = current_cost.total_cost / days as f64;
435
436        CostProjection {
437            current_total: current_cost.total_cost,
438            daily_rate,
439            projected_monthly: daily_rate * 30.0,
440            projected_yearly: daily_rate * 365.0,
441            breakdown: current_cost,
442        }
443    }
444}
445
446/// Tier recommendation
447#[derive(Debug, Clone, Serialize, Deserialize)]
448pub struct TierRecommendation {
449    pub recommended_tier: CostTier,
450    pub options: Vec<TierOption>,
451    pub access_frequency: f64,
452    pub data_size_gb: f64,
453}
454
455/// Tier option
456#[derive(Debug, Clone, Serialize, Deserialize)]
457pub struct TierOption {
458    pub tier: CostTier,
459    pub estimated_monthly_cost: f64,
460    pub storage_cost: f64,
461    pub access_cost: f64,
462}
463
464/// Cost projection
465#[derive(Debug, Clone, Serialize, Deserialize)]
466pub struct CostProjection {
467    pub current_total: f64,
468    pub daily_rate: f64,
469    pub projected_monthly: f64,
470    pub projected_yearly: f64,
471    pub breakdown: CostBreakdown,
472}
473
474#[cfg(test)]
475mod tests {
476    use super::*;
477
478    #[test]
479    fn test_cost_calculation() {
480        let mut analyzer = CostAnalyzer::new(CloudProvider::AWS);
481
482        // Simulate 100 GB in standard tier
483        analyzer.record_write(CostTier::Standard, 100 * 1024 * 1024 * 1024);
484
485        // Calculate monthly cost
486        let breakdown = analyzer
487            .calculate_tier_cost(CostTier::Standard, 30)
488            .unwrap();
489
490        // Should be approximately $2.30 for 100GB at $0.023/GB/month
491        assert!(breakdown.storage_cost > 2.0 && breakdown.storage_cost < 2.5);
492    }
493
494    #[test]
495    fn test_tier_recommendation() {
496        let analyzer = CostAnalyzer::new(CloudProvider::AWS);
497
498        // High access frequency - should recommend standard
499        let rec = analyzer.recommend_tier(100.0, 10.0);
500        assert_eq!(rec.recommended_tier, CostTier::Standard);
501
502        // Very low access frequency - should recommend cheaper tier
503        // Glacier retrieval = monthly_accesses * data_gb * $0.02/GB
504        // Storage savings vs Standard = data_gb * ($0.023 - $0.004)/month
505        // For Glacier to be cheaper: monthly_accesses < 0.95
506        // With access_freq=0.01, monthly_accesses=0.3 < 0.95, so Glacier wins
507        let rec = analyzer.recommend_tier(0.01, 10.0);
508        // With very low access, Glacier or IA should be cheaper
509        assert_ne!(rec.recommended_tier, CostTier::Standard);
510    }
511
512    #[test]
513    fn test_cost_models() {
514        let model = TierCostModel::aws_s3_standard();
515        assert_eq!(model.storage_cost_per_gb_month, 0.023);
516
517        let model = TierCostModel::azure_hot();
518        assert_eq!(model.storage_cost_per_gb_month, 0.018);
519
520        let model = TierCostModel::gcp_standard();
521        assert_eq!(model.storage_cost_per_gb_month, 0.020);
522    }
523
524    #[test]
525    fn test_cost_projection() {
526        let mut analyzer = CostAnalyzer::new(CloudProvider::AWS);
527        analyzer.record_write(CostTier::Standard, 100 * 1024 * 1024 * 1024);
528
529        let projection = analyzer.project_costs(30);
530        assert!(projection.projected_monthly > 0.0);
531        assert!(projection.projected_yearly > projection.projected_monthly);
532    }
533}