use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::SystemTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum CostTier {
Hot,
Standard,
InfrequentAccess,
Archive,
Glacier,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum CloudProvider {
AWS,
Azure,
GCP,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TierCostModel {
pub storage_cost_per_gb_month: f64,
pub write_request_cost_per_1k: f64,
pub read_request_cost_per_1k: f64,
pub retrieval_cost_per_gb: f64,
pub transfer_cost_per_gb: f64,
pub min_storage_days: u32,
pub early_deletion_cost_per_gb: f64,
}
impl TierCostModel {
pub fn aws_s3_standard() -> Self {
Self {
storage_cost_per_gb_month: 0.023,
write_request_cost_per_1k: 0.005,
read_request_cost_per_1k: 0.0004,
retrieval_cost_per_gb: 0.0,
transfer_cost_per_gb: 0.09,
min_storage_days: 0,
early_deletion_cost_per_gb: 0.0,
}
}
pub fn aws_s3_infrequent() -> Self {
Self {
storage_cost_per_gb_month: 0.0125,
write_request_cost_per_1k: 0.01,
read_request_cost_per_1k: 0.001,
retrieval_cost_per_gb: 0.01,
transfer_cost_per_gb: 0.09,
min_storage_days: 30,
early_deletion_cost_per_gb: 0.0125,
}
}
pub fn aws_s3_glacier() -> Self {
Self {
storage_cost_per_gb_month: 0.004,
write_request_cost_per_1k: 0.03,
read_request_cost_per_1k: 0.0004,
retrieval_cost_per_gb: 0.02, transfer_cost_per_gb: 0.09,
min_storage_days: 90,
early_deletion_cost_per_gb: 0.012,
}
}
pub fn azure_hot() -> Self {
Self {
storage_cost_per_gb_month: 0.018,
write_request_cost_per_1k: 0.0055,
read_request_cost_per_1k: 0.00044,
retrieval_cost_per_gb: 0.0,
transfer_cost_per_gb: 0.087,
min_storage_days: 0,
early_deletion_cost_per_gb: 0.0,
}
}
pub fn azure_cool() -> Self {
Self {
storage_cost_per_gb_month: 0.01,
write_request_cost_per_1k: 0.01,
read_request_cost_per_1k: 0.001,
retrieval_cost_per_gb: 0.01,
transfer_cost_per_gb: 0.087,
min_storage_days: 30,
early_deletion_cost_per_gb: 0.01,
}
}
pub fn azure_archive() -> Self {
Self {
storage_cost_per_gb_month: 0.002,
write_request_cost_per_1k: 0.011,
read_request_cost_per_1k: 0.0055,
retrieval_cost_per_gb: 0.02,
transfer_cost_per_gb: 0.087,
min_storage_days: 180,
early_deletion_cost_per_gb: 0.006,
}
}
pub fn gcp_standard() -> Self {
Self {
storage_cost_per_gb_month: 0.020,
write_request_cost_per_1k: 0.005,
read_request_cost_per_1k: 0.0004,
retrieval_cost_per_gb: 0.0,
transfer_cost_per_gb: 0.12,
min_storage_days: 0,
early_deletion_cost_per_gb: 0.0,
}
}
pub fn gcp_nearline() -> Self {
Self {
storage_cost_per_gb_month: 0.010,
write_request_cost_per_1k: 0.01,
read_request_cost_per_1k: 0.001,
retrieval_cost_per_gb: 0.01,
transfer_cost_per_gb: 0.12,
min_storage_days: 30,
early_deletion_cost_per_gb: 0.010,
}
}
pub fn gcp_coldline() -> Self {
Self {
storage_cost_per_gb_month: 0.004,
write_request_cost_per_1k: 0.01,
read_request_cost_per_1k: 0.005,
retrieval_cost_per_gb: 0.02,
transfer_cost_per_gb: 0.12,
min_storage_days: 90,
early_deletion_cost_per_gb: 0.012,
}
}
}
#[derive(Debug)]
pub struct UsageMetrics {
pub storage_bytes: AtomicU64,
pub write_requests: AtomicU64,
pub read_requests: AtomicU64,
pub bytes_retrieved: AtomicU64,
pub bytes_transferred: AtomicU64,
pub period_start: parking_lot::Mutex<SystemTime>,
}
impl UsageMetrics {
fn new() -> Self {
Self {
storage_bytes: AtomicU64::new(0),
write_requests: AtomicU64::new(0),
read_requests: AtomicU64::new(0),
bytes_retrieved: AtomicU64::new(0),
bytes_transferred: AtomicU64::new(0),
period_start: parking_lot::Mutex::new(SystemTime::now()),
}
}
fn record_write(&self, bytes: u64) {
self.storage_bytes.fetch_add(bytes, Ordering::Relaxed);
self.write_requests.fetch_add(1, Ordering::Relaxed);
}
fn record_read(&self, bytes: u64) {
self.read_requests.fetch_add(1, Ordering::Relaxed);
self.bytes_retrieved.fetch_add(bytes, Ordering::Relaxed);
}
fn record_delete(&self, bytes: u64) {
self.storage_bytes.fetch_sub(bytes, Ordering::Relaxed);
}
fn record_transfer(&self, bytes: u64) {
self.bytes_transferred.fetch_add(bytes, Ordering::Relaxed);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostBreakdown {
pub storage_cost: f64,
pub write_request_cost: f64,
pub read_request_cost: f64,
pub retrieval_cost: f64,
pub transfer_cost: f64,
pub early_deletion_cost: f64,
pub total_cost: f64,
}
impl CostBreakdown {
fn calculate_total(&mut self) {
self.total_cost = self.storage_cost
+ self.write_request_cost
+ self.read_request_cost
+ self.retrieval_cost
+ self.transfer_cost
+ self.early_deletion_cost;
}
}
pub struct CostAnalyzer {
cost_models: HashMap<CostTier, TierCostModel>,
usage_metrics: HashMap<CostTier, UsageMetrics>,
#[allow(dead_code)]
provider: CloudProvider,
}
impl CostAnalyzer {
pub fn new(provider: CloudProvider) -> Self {
let mut cost_models = HashMap::new();
match provider {
CloudProvider::AWS => {
cost_models.insert(CostTier::Standard, TierCostModel::aws_s3_standard());
cost_models.insert(
CostTier::InfrequentAccess,
TierCostModel::aws_s3_infrequent(),
);
cost_models.insert(CostTier::Glacier, TierCostModel::aws_s3_glacier());
}
CloudProvider::Azure => {
cost_models.insert(CostTier::Hot, TierCostModel::azure_hot());
cost_models.insert(CostTier::InfrequentAccess, TierCostModel::azure_cool());
cost_models.insert(CostTier::Archive, TierCostModel::azure_archive());
}
CloudProvider::GCP => {
cost_models.insert(CostTier::Standard, TierCostModel::gcp_standard());
cost_models.insert(CostTier::InfrequentAccess, TierCostModel::gcp_nearline());
cost_models.insert(CostTier::Archive, TierCostModel::gcp_coldline());
}
CloudProvider::Custom => {}
}
Self {
cost_models,
usage_metrics: HashMap::new(),
provider,
}
}
pub fn set_cost_model(&mut self, tier: CostTier, model: TierCostModel) {
self.cost_models.insert(tier, model);
}
pub fn record_write(&mut self, tier: CostTier, bytes: u64) {
self.usage_metrics
.entry(tier)
.or_insert_with(UsageMetrics::new)
.record_write(bytes);
}
pub fn record_read(&mut self, tier: CostTier, bytes: u64) {
self.usage_metrics
.entry(tier)
.or_insert_with(UsageMetrics::new)
.record_read(bytes);
}
pub fn record_delete(&mut self, tier: CostTier, bytes: u64) {
self.usage_metrics
.entry(tier)
.or_insert_with(UsageMetrics::new)
.record_delete(bytes);
}
pub fn record_transfer(&mut self, tier: CostTier, bytes: u64) {
self.usage_metrics
.entry(tier)
.or_insert_with(UsageMetrics::new)
.record_transfer(bytes);
}
pub fn calculate_tier_cost(&self, tier: CostTier, days: u32) -> Option<CostBreakdown> {
let model = self.cost_models.get(&tier)?;
let metrics = self.usage_metrics.get(&tier)?;
let storage_gb = metrics.storage_bytes.load(Ordering::Relaxed) as f64 / 1_073_741_824.0;
let write_requests = metrics.write_requests.load(Ordering::Relaxed) as f64;
let read_requests = metrics.read_requests.load(Ordering::Relaxed) as f64;
let retrieved_gb = metrics.bytes_retrieved.load(Ordering::Relaxed) as f64 / 1_073_741_824.0;
let transferred_gb =
metrics.bytes_transferred.load(Ordering::Relaxed) as f64 / 1_073_741_824.0;
let months = days as f64 / 30.0;
let mut breakdown = CostBreakdown {
storage_cost: storage_gb * model.storage_cost_per_gb_month * months,
write_request_cost: (write_requests / 1000.0) * model.write_request_cost_per_1k,
read_request_cost: (read_requests / 1000.0) * model.read_request_cost_per_1k,
retrieval_cost: retrieved_gb * model.retrieval_cost_per_gb,
transfer_cost: transferred_gb * model.transfer_cost_per_gb,
early_deletion_cost: 0.0, total_cost: 0.0,
};
breakdown.calculate_total();
Some(breakdown)
}
pub fn calculate_total_cost(&self, days: u32) -> CostBreakdown {
let mut total = CostBreakdown {
storage_cost: 0.0,
write_request_cost: 0.0,
read_request_cost: 0.0,
retrieval_cost: 0.0,
transfer_cost: 0.0,
early_deletion_cost: 0.0,
total_cost: 0.0,
};
for tier in self.usage_metrics.keys() {
if let Some(breakdown) = self.calculate_tier_cost(*tier, days) {
total.storage_cost += breakdown.storage_cost;
total.write_request_cost += breakdown.write_request_cost;
total.read_request_cost += breakdown.read_request_cost;
total.retrieval_cost += breakdown.retrieval_cost;
total.transfer_cost += breakdown.transfer_cost;
total.early_deletion_cost += breakdown.early_deletion_cost;
}
}
total.calculate_total();
total
}
pub fn recommend_tier(&self, access_frequency: f64, data_size_gb: f64) -> TierRecommendation {
let mut recommendations = Vec::new();
for (tier, model) in &self.cost_models {
let storage_cost = data_size_gb * model.storage_cost_per_gb_month;
let monthly_accesses = access_frequency * 30.0;
let read_cost = (monthly_accesses / 1000.0) * model.read_request_cost_per_1k;
let retrieval_cost = monthly_accesses * data_size_gb * model.retrieval_cost_per_gb;
let total_cost = storage_cost + read_cost + retrieval_cost;
recommendations.push(TierOption {
tier: *tier,
estimated_monthly_cost: total_cost,
storage_cost,
access_cost: read_cost + retrieval_cost,
});
}
recommendations.sort_by(|a, b| {
a.estimated_monthly_cost
.partial_cmp(&b.estimated_monthly_cost)
.unwrap()
});
TierRecommendation {
recommended_tier: recommendations[0].tier,
options: recommendations,
access_frequency,
data_size_gb,
}
}
pub fn project_costs(&self, days: u32) -> CostProjection {
let current_cost = self.calculate_total_cost(days);
let daily_rate = current_cost.total_cost / days as f64;
CostProjection {
current_total: current_cost.total_cost,
daily_rate,
projected_monthly: daily_rate * 30.0,
projected_yearly: daily_rate * 365.0,
breakdown: current_cost,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TierRecommendation {
pub recommended_tier: CostTier,
pub options: Vec<TierOption>,
pub access_frequency: f64,
pub data_size_gb: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TierOption {
pub tier: CostTier,
pub estimated_monthly_cost: f64,
pub storage_cost: f64,
pub access_cost: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostProjection {
pub current_total: f64,
pub daily_rate: f64,
pub projected_monthly: f64,
pub projected_yearly: f64,
pub breakdown: CostBreakdown,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cost_calculation() {
let mut analyzer = CostAnalyzer::new(CloudProvider::AWS);
analyzer.record_write(CostTier::Standard, 100 * 1024 * 1024 * 1024);
let breakdown = analyzer
.calculate_tier_cost(CostTier::Standard, 30)
.unwrap();
assert!(breakdown.storage_cost > 2.0 && breakdown.storage_cost < 2.5);
}
#[test]
fn test_tier_recommendation() {
let analyzer = CostAnalyzer::new(CloudProvider::AWS);
let rec = analyzer.recommend_tier(100.0, 10.0);
assert_eq!(rec.recommended_tier, CostTier::Standard);
let rec = analyzer.recommend_tier(0.01, 10.0);
assert_ne!(rec.recommended_tier, CostTier::Standard);
}
#[test]
fn test_cost_models() {
let model = TierCostModel::aws_s3_standard();
assert_eq!(model.storage_cost_per_gb_month, 0.023);
let model = TierCostModel::azure_hot();
assert_eq!(model.storage_cost_per_gb_month, 0.018);
let model = TierCostModel::gcp_standard();
assert_eq!(model.storage_cost_per_gb_month, 0.020);
}
#[test]
fn test_cost_projection() {
let mut analyzer = CostAnalyzer::new(CloudProvider::AWS);
analyzer.record_write(CostTier::Standard, 100 * 1024 * 1024 * 1024);
let projection = analyzer.project_costs(30);
assert!(projection.projected_monthly > 0.0);
assert!(projection.projected_yearly > projection.projected_monthly);
}
}