1use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::time::SystemTime;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub enum CostTier {
19 Hot,
21 Standard,
23 InfrequentAccess,
25 Archive,
27 Glacier,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub enum CloudProvider {
34 AWS,
35 Azure,
36 GCP,
37 Custom,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct TierCostModel {
43 pub storage_cost_per_gb_month: f64,
45 pub write_request_cost_per_1k: f64,
47 pub read_request_cost_per_1k: f64,
49 pub retrieval_cost_per_gb: f64,
51 pub transfer_cost_per_gb: f64,
53 pub min_storage_days: u32,
55 pub early_deletion_cost_per_gb: f64,
57}
58
59impl TierCostModel {
60 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 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 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, transfer_cost_per_gb: 0.09,
94 min_storage_days: 90,
95 early_deletion_cost_per_gb: 0.012,
96 }
97 }
98
99 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 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 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 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 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 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#[derive(Debug)]
180pub struct UsageMetrics {
181 pub storage_bytes: AtomicU64,
183 pub write_requests: AtomicU64,
185 pub read_requests: AtomicU64,
187 pub bytes_retrieved: AtomicU64,
189 pub bytes_transferred: AtomicU64,
191 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#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct CostBreakdown {
229 pub storage_cost: f64,
231 pub write_request_cost: f64,
233 pub read_request_cost: f64,
235 pub retrieval_cost: f64,
237 pub transfer_cost: f64,
239 pub early_deletion_cost: f64,
241 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
256pub struct CostAnalyzer {
258 cost_models: HashMap<CostTier, TierCostModel>,
260 usage_metrics: HashMap<CostTier, UsageMetrics>,
262 #[allow(dead_code)]
264 provider: CloudProvider,
265}
266
267impl CostAnalyzer {
268 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 pub fn set_cost_model(&mut self, tier: CostTier, model: TierCostModel) {
303 self.cost_models.insert(tier, model);
304 }
305
306 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 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 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 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 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, total_cost: 0.0,
360 };
361
362 breakdown.calculate_total();
363 Some(breakdown)
364 }
365
366 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 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 let storage_cost = data_size_gb * model.storage_cost_per_gb_month;
400
401 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 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 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#[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#[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#[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 analyzer.record_write(CostTier::Standard, 100 * 1024 * 1024 * 1024);
484
485 let breakdown = analyzer
487 .calculate_tier_cost(CostTier::Standard, 30)
488 .unwrap();
489
490 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 let rec = analyzer.recommend_tier(100.0, 10.0);
500 assert_eq!(rec.recommended_tier, CostTier::Standard);
501
502 let rec = analyzer.recommend_tier(0.01, 10.0);
508 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}