1#![allow(dead_code)]
10
11pub mod carbon_tracking;
12pub mod config;
13pub mod efficiency_analysis;
14pub mod energy_monitoring;
15pub mod reporting;
16pub mod sustainability;
17pub mod types;
18
19pub use carbon_tracking::CarbonFootprintTracker;
20pub use config::EnvironmentalConfig;
21pub use efficiency_analysis::EfficiencyAnalyzer;
22pub use reporting::EnvironmentalReportingEngine;
23pub use sustainability::SustainabilityAdvisor;
24pub use types::*;
25
26use anyhow::Result;
27use std::time::{Duration, Instant};
28use tracing::{info, warn};
29
30#[derive(Debug, thiserror::Error)]
32pub enum EnvironmentalMonitorError {
33 #[error(
40 "no ForecastSource configured on this EnvironmentalMonitor (see \
41 EnvironmentalMonitor::set_forecast_source); cannot forecast carbon \
42 intensity or energy prices without one"
43 )]
44 NotConfigured,
45 #[error(
50 "no carbon intensity registered for region '{region}' (see \
51 CarbonFootprintTracker::update_carbon_intensity); refusing to invent one"
52 )]
53 UnknownRegion { region: String },
54}
55
56pub trait ForecastSource: std::fmt::Debug + Send + Sync {
68 fn carbon_intensity_forecast(&self, region: &str, hours: usize) -> Result<Vec<CarbonForecast>>;
71 fn energy_price_forecast(&self, region: &str, hours: usize)
74 -> Result<Vec<EnergyPriceForecast>>;
75}
76
77#[derive(Debug)]
79pub struct EnvironmentalMonitor {
80 config: EnvironmentalConfig,
81 carbon_tracker: CarbonFootprintTracker,
82 energy_monitor: energy_monitoring::EnergyConsumptionMonitor,
83 efficiency_analyzer: EfficiencyAnalyzer,
84 sustainability_advisor: SustainabilityAdvisor,
85 reporting_engine: EnvironmentalReportingEngine,
86 forecast_source: Option<Box<dyn ForecastSource>>,
90}
91
92impl EnvironmentalMonitor {
93 pub fn new(config: EnvironmentalConfig) -> Self {
95 Self {
96 config: config.clone(),
97 carbon_tracker: CarbonFootprintTracker::new(&config),
98 energy_monitor: energy_monitoring::EnergyConsumptionMonitor::new(),
99 efficiency_analyzer: EfficiencyAnalyzer::new(),
100 sustainability_advisor: SustainabilityAdvisor::new(),
101 reporting_engine: EnvironmentalReportingEngine::new(),
102 forecast_source: None,
103 }
104 }
105
106 pub fn set_forecast_source(&mut self, source: Box<dyn ForecastSource>) {
109 self.forecast_source = Some(source);
110 }
111
112 pub fn clear_forecast_source(&mut self) {
114 self.forecast_source = None;
115 }
116
117 pub fn has_forecast_source(&self) -> bool {
119 self.forecast_source.is_some()
120 }
121
122 pub async fn start_monitoring(&mut self) -> Result<()> {
124 info!(
125 "Starting environmental impact monitoring for region: {}",
126 self.config.region
127 );
128
129 self.start_monitoring_loops().await?;
133
134 self.sustainability_advisor.initialize_sustainability_goals().await?;
136
137 Ok(())
138 }
139
140 pub async fn record_session(
142 &mut self,
143 session_info: SessionInfo,
144 ) -> Result<SessionImpactReport> {
145 info!(
146 "Recording environmental impact for {:?} session",
147 session_info.session_type
148 );
149
150 let _start_time = Instant::now();
151
152 let predicted_energy_kwh = self
154 .energy_monitor
155 .predict_energy_consumption(session_info.duration_hours as u32);
156
157 let energy_kwh = if predicted_energy_kwh > 0.0 {
159 predicted_energy_kwh
160 } else {
161 session_info.estimated_energy_kwh
162 };
163
164 let energy_measurement = EnergyMeasurement {
166 timestamp: std::time::SystemTime::now(),
167 device_id: "session".to_string(),
168 power_watts: energy_kwh * 1000.0 / session_info.duration_hours, energy_kwh,
170 utilization: None,
174 temperature: None,
175 efficiency_ratio: None,
176 };
177
178 let carbon_measurement = self.carbon_tracker.record_emissions(
180 energy_measurement.energy_kwh,
181 &session_info.region,
182 session_info.session_type.clone(),
183 )?;
184
185 self.update_cumulative_metrics(&energy_measurement, &carbon_measurement).await?;
187
188 let efficiency_analysis = self
190 .efficiency_analyzer
191 .analyze_session_efficiency(&session_info, &energy_measurement)
192 .await?;
193
194 let cost_analysis = self.calculate_cost_impact(&energy_measurement).await?;
196 let recommendations = self.generate_session_recommendations(&efficiency_analysis).await?;
197
198 let impact_report = SessionImpactReport {
199 session_info,
200 carbon_emissions: CarbonEmissions {
201 total_co2_kg: carbon_measurement.co2_emissions_kg,
202 scope1_emissions_kg: 0.0, scope2_emissions_kg: carbon_measurement.scope2_emissions_kg,
204 scope3_emissions_kg: carbon_measurement.scope3_emissions_kg.unwrap_or(0.0),
205 training_emissions_kg: carbon_measurement.co2_emissions_kg,
206 inference_emissions_kg: 0.0,
207 equivalent_metrics: EquivalentMetrics {
208 car_miles_equivalent: carbon_measurement.co2_emissions_kg * 2.31, tree_months_to_offset: carbon_measurement.co2_emissions_kg * 0.039, coal_pounds_equivalent: carbon_measurement.co2_emissions_kg * 2.2, households_daily_energy: carbon_measurement.co2_emissions_kg * 0.123, },
213 },
214 energy_consumption: energy_measurement.energy_kwh,
215 cost_usd: cost_analysis.total_cost_usd,
216 efficiency_metrics: EnergyEfficiencyMetrics {
217 operations_per_kwh: None,
222 flops_per_watt: None,
223 model_energy_efficiency: efficiency_analysis.efficiency_score,
224 training_energy_efficiency: efficiency_analysis.efficiency_score,
225 inference_energy_efficiency: efficiency_analysis.efficiency_score,
226 comparative_efficiency: ComparativeEfficiency {
230 vs_cpu_only: None,
231 vs_previous_generation: None,
232 vs_cloud_baseline: None,
233 efficiency_percentile: None,
234 },
235 },
236 recommendations,
237 energy_measurement,
238 carbon_measurement,
239 efficiency_analysis,
240 cost_analysis,
241 };
242
243 self.check_environmental_alerts(&impact_report).await?;
245
246 Ok(impact_report)
247 }
248
249 pub async fn get_real_time_metrics(&self) -> Result<RealTimeEnvironmentalMetrics> {
262 let current_power = self.energy_monitor.get_current_consumption();
263 let carbon_intensity = self.carbon_tracker.get_carbon_intensity(&self.config.region);
264
265 let latest_measurement = self.energy_monitor.get_consumption_history().last();
266 let efficiency_ratio = latest_measurement.and_then(|m| m.efficiency_ratio);
267 let temperature_celsius = latest_measurement.and_then(|m| m.temperature);
268
269 Ok(RealTimeEnvironmentalMetrics {
270 timestamp: std::time::SystemTime::now(),
271 current_power_watts: current_power,
272 energy_consumed_kwh: current_power / 1000.0, co2_emissions_kg: carbon_intensity
274 .map(|intensity| (current_power / 1000.0) * intensity / 1000.0),
275 efficiency_ratio,
276 temperature_celsius,
277 })
278 }
279
280 pub async fn optimize_scheduling(
287 &self,
288 workload: WorkloadDescription,
289 ) -> Result<OptimalSchedule> {
290 info!("Optimizing schedule for minimum environmental impact");
291
292 let carbon_forecasts = self.get_carbon_intensity_forecasts().await?;
294
295 let price_forecasts = self.get_energy_price_forecasts().await?;
297
298 let optimal_time = self
300 .find_optimal_execution_time(&workload, &carbon_forecasts, &price_forecasts)
301 .await?;
302
303 let savings = self.calculate_projected_savings(&workload, &optimal_time).await?;
305
306 let confidence = if carbon_forecasts.is_empty() {
310 0.0
311 } else {
312 carbon_forecasts.iter().map(|f| f.confidence).sum::<f64>()
313 / carbon_forecasts.len() as f64
314 };
315
316 Ok(OptimalSchedule {
317 schedule_type: ScheduleType::LowCarbon,
318 start_time: optimal_time,
319 duration_hours: workload.estimated_duration_hours,
320 projected_savings: savings,
321 carbon_intensity_forecast: carbon_forecasts
322 .iter()
323 .map(|f| f.predicted_carbon_intensity)
324 .collect(),
325 confidence,
326 })
327 }
328
329 pub async fn generate_environmental_report(
331 &mut self,
332 report_type: ReportType,
333 ) -> Result<EnvironmentalReport> {
334 self.reporting_engine.generate_environmental_report(report_type).await
335 }
336
337 pub async fn get_sustainability_recommendations(
339 &self,
340 ) -> Result<Vec<SustainabilityRecommendation>> {
341 self.sustainability_advisor.get_sustainability_recommendations().await
342 }
343
344 pub async fn get_efficiency_opportunities(&self) -> Result<Vec<EfficiencyOpportunity>> {
346 self.efficiency_analyzer.analyze_efficiency_opportunities().await
347 }
348
349 pub fn get_cumulative_emissions(&self) -> &CarbonEmissions {
351 self.carbon_tracker.get_cumulative_emissions()
352 }
353
354 pub fn get_measurement_history(&self) -> &[CarbonMeasurement] {
356 self.carbon_tracker.get_measurement_history()
357 }
358
359 async fn start_monitoring_loops(&self) -> Result<()> {
373 let interval = Duration::from_secs(self.config.monitoring_interval_secs);
374
375 info!(
376 "Environmental monitoring configured with interval {:?}; call record_session() / \
377 get_real_time_metrics() to sample -- no autonomous background polling runs \
378 automatically",
379 interval
380 );
381
382 Ok(())
383 }
384
385 async fn update_cumulative_metrics(
386 &mut self,
387 _energy: &EnergyMeasurement,
388 _carbon: &CarbonMeasurement,
389 ) -> Result<()> {
390 Ok(())
392 }
393
394 async fn calculate_cost_impact(&self, energy: &EnergyMeasurement) -> Result<CostAnalysis> {
395 let energy_cost = energy.energy_kwh * self.config.energy_price_per_kwh;
396 let carbon_cost = self.calculate_carbon_cost(energy.energy_kwh).await?;
397
398 Ok(CostAnalysis {
399 energy_cost_usd: energy_cost,
400 carbon_cost_usd: Some(carbon_cost),
401 infrastructure_cost_usd: energy_cost * 0.1, total_cost_usd: energy_cost + carbon_cost,
403 cost_per_operation: (energy_cost + carbon_cost) / 1000.0, })
405 }
406
407 async fn calculate_carbon_cost(&self, energy_kwh: f64) -> Result<f64> {
413 let carbon_price_per_ton = 50.0; let carbon_intensity = self
417 .carbon_tracker
418 .get_carbon_intensity(&self.config.region)
419 .ok_or_else(|| EnvironmentalMonitorError::UnknownRegion {
420 region: self.config.region.clone(),
421 })?;
422 let co2_tons = (energy_kwh * carbon_intensity / 1000.0) / 1000.0;
423
424 Ok(co2_tons * carbon_price_per_ton)
425 }
426
427 async fn generate_session_recommendations(
428 &self,
429 efficiency: &SessionEfficiencyAnalysis,
430 ) -> Result<Vec<String>> {
431 let mut recommendations = Vec::new();
432
433 if efficiency.efficiency_score < 0.7 {
434 recommendations
435 .push("Consider optimizing batch size for better GPU utilization".to_string());
436 }
437
438 if efficiency.waste_percentage > 30.0 {
439 recommendations
440 .push("Implement gradient accumulation to reduce memory overhead".to_string());
441 }
442
443 recommendations.push("Schedule training during low carbon intensity periods".to_string());
444 recommendations
445 .push("Consider mixed precision training to reduce energy consumption".to_string());
446
447 Ok(recommendations)
448 }
449
450 async fn check_environmental_alerts(&self, report: &SessionImpactReport) -> Result<()> {
451 if report.carbon_measurement.co2_emissions_kg > self.config.carbon_alert_threshold {
452 warn!(
453 "Carbon emission alert: {:.2} kg CO2 exceeds threshold of {:.2} kg",
454 report.carbon_measurement.co2_emissions_kg, self.config.carbon_alert_threshold
455 );
456 }
457
458 if report.energy_measurement.energy_kwh > self.config.energy_alert_threshold {
459 warn!(
460 "Energy consumption alert: {:.2} kWh exceeds threshold of {:.2} kWh",
461 report.energy_measurement.energy_kwh, self.config.energy_alert_threshold
462 );
463 }
464
465 Ok(())
466 }
467
468 async fn get_carbon_intensity_forecasts(&self) -> Result<Vec<CarbonForecast>> {
473 let source = self
474 .forecast_source
475 .as_deref()
476 .ok_or(EnvironmentalMonitorError::NotConfigured)?;
477 source.carbon_intensity_forecast(&self.config.region, 24)
478 }
479
480 async fn get_energy_price_forecasts(&self) -> Result<Vec<EnergyPriceForecast>> {
483 let source = self
484 .forecast_source
485 .as_deref()
486 .ok_or(EnvironmentalMonitorError::NotConfigured)?;
487 source.energy_price_forecast(&self.config.region, 24)
488 }
489
490 async fn find_optimal_execution_time(
491 &self,
492 workload: &WorkloadDescription,
493 carbon_forecasts: &[CarbonForecast],
494 price_forecasts: &[EnergyPriceForecast],
495 ) -> Result<std::time::SystemTime> {
496 let mut best_time = std::time::SystemTime::now();
497 let mut best_score = f64::INFINITY;
498
499 for (carbon_forecast, price_forecast) in carbon_forecasts.iter().zip(price_forecasts.iter())
500 {
501 let carbon_score =
503 carbon_forecast.predicted_carbon_intensity * workload.estimated_energy_kwh;
504 let cost_score =
505 price_forecast.predicted_price_per_kwh * workload.estimated_energy_kwh * 100.0;
506 let combined_score = carbon_score + cost_score;
507
508 if combined_score < best_score {
509 best_score = combined_score;
510 best_time = carbon_forecast.timestamp;
511 }
512 }
513
514 Ok(best_time)
515 }
516
517 async fn calculate_projected_savings(
518 &self,
519 workload: &WorkloadDescription,
520 _optimal_time: &std::time::SystemTime,
521 ) -> Result<ProjectedSavings> {
522 Ok(ProjectedSavings {
523 energy_savings_kwh: 0.0, cost_savings_usd: workload.estimated_energy_kwh
525 * self.config.energy_price_per_kwh
526 * 0.2, carbon_reduction_kg: workload.estimated_energy_kwh * 0.15, efficiency_improvement_percent: 0.0, })
530 }
531}
532
533#[derive(Debug, Clone)]
536pub struct CarbonForecast {
537 pub timestamp: std::time::SystemTime,
538 pub predicted_carbon_intensity: f64,
539 pub renewable_percentage: f64,
540 pub confidence: f64,
544}
545
546#[derive(Debug, Clone)]
547pub struct EnergyPriceForecast {
548 pub timestamp: std::time::SystemTime,
549 pub predicted_price_per_kwh: f64,
550 pub confidence: f64,
551}
552
553pub fn create_environmental_monitor() -> EnvironmentalMonitor {
557 EnvironmentalMonitor::new(EnvironmentalConfig::default())
558}
559
560pub fn create_regional_environmental_monitor(region: String) -> EnvironmentalMonitor {
562 let mut config = EnvironmentalConfig::default();
563 config.region = region;
564 EnvironmentalMonitor::new(config)
565}
566
567#[macro_export]
569macro_rules! record_environmental_impact {
570 ($monitor:expr, $session_type:expr, $duration:expr, $energy:expr) => {{
571 let session_info = SessionInfo {
572 session_id: uuid::Uuid::new_v4().to_string(),
573 session_type: $session_type,
574 duration_hours: $duration,
575 workload_description: "default".to_string(),
576 region: "US-West".to_string(),
577 estimated_energy_kwh: $energy,
578 };
579 $monitor.record_session(session_info).await
580 }};
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586
587 #[tokio::test]
588 async fn test_environmental_monitor_creation() {
589 let monitor = EnvironmentalMonitor::new(EnvironmentalConfig::default());
590 assert_eq!(monitor.config.region, "US-West");
591 assert!(monitor.config.enable_carbon_tracking);
592 }
593
594 #[tokio::test]
595 async fn test_session_recording() {
596 let mut monitor = EnvironmentalMonitor::new(EnvironmentalConfig::default());
597
598 let session_info = SessionInfo {
599 session_id: "test-session".to_string(),
600 start_time: std::time::SystemTime::now(),
601 session_type: MeasurementType::Training,
602 duration_hours: 1.0,
603 workload_description: "test training".to_string(),
604 region: "US-West".to_string(),
605 estimated_energy_kwh: 2.5,
606 };
607
608 let result = monitor.record_session(session_info).await;
609 assert!(result.is_ok());
610
611 let report = result.expect("operation failed in test");
612 assert!(report.carbon_measurement.co2_emissions_kg > 0.0);
613 assert!(report.energy_measurement.energy_kwh > 0.0);
614 }
615
616 #[tokio::test]
617 async fn test_real_time_metrics() {
618 let mut monitor = EnvironmentalMonitor::new(EnvironmentalConfig::default());
619
620 use crate::environmental_monitor::types::{DeviceType, PowerMeasurementMethod};
622 monitor
623 .energy_monitor
624 .add_device(
625 "gpu-0".to_string(),
626 DeviceType::GPU,
627 PowerMeasurementMethod::Estimated,
628 )
629 .expect("operation failed in test");
630
631 let _ = monitor.energy_monitor.record_measurement("gpu-0", 250.0, 0.8, Some(70.0));
633
634 let metrics = monitor.get_real_time_metrics().await.expect("async operation failed");
635 assert!(metrics.current_power_watts >= 0.0); assert!(
637 metrics.efficiency_ratio.is_some_and(|r| r > 0.0),
638 "a recorded measurement with a real utilization reading yields a real ratio"
639 );
640
641 assert_eq!(
645 metrics.temperature_celsius,
646 Some(70.0),
647 "must reflect the real recorded temperature, not the old hardcoded Some(75.0)"
648 );
649 }
650
651 #[tokio::test]
656 async fn test_real_time_metrics_honest_before_any_measurement() {
657 let monitor = EnvironmentalMonitor::new(EnvironmentalConfig::default());
658 let metrics = monitor.get_real_time_metrics().await.expect("async operation failed");
659 assert_eq!(metrics.efficiency_ratio, None);
660 assert_eq!(metrics.temperature_celsius, None);
661 }
662
663 #[tokio::test]
669 async fn test_recorded_session_reports_no_utilization_rather_than_assuming_one() {
670 let mut monitor = EnvironmentalMonitor::new(EnvironmentalConfig::default());
671 let report = monitor
672 .record_session(SessionInfo {
673 session_id: "s1".to_string(),
674 session_type: MeasurementType::Training,
675 start_time: std::time::SystemTime::now(),
676 duration_hours: 1.0,
677 workload_description: "test".to_string(),
678 region: "US-West".to_string(),
679 estimated_energy_kwh: 2.5,
680 })
681 .await
682 .expect("US-West has a registered carbon intensity");
683
684 assert_eq!(report.energy_measurement.utilization, None);
685 assert_eq!(report.energy_measurement.efficiency_ratio, None);
686 let bottlenecks = monitor
687 .efficiency_analyzer
688 .identify_efficiency_bottlenecks(&report.energy_measurement)
689 .await
690 .expect("bottleneck analysis should succeed");
691 assert!(
692 !bottlenecks.iter().any(|b| b.contains("underutilization")),
693 "a bottleneck must not be derived from a utilization nothing measured: {bottlenecks:?}"
694 );
695 }
696
697 #[tokio::test]
700 async fn test_unknown_region_is_refused_rather_than_given_a_fallback_intensity() {
701 let mut monitor = EnvironmentalMonitor::new(EnvironmentalConfig {
702 region: "Atlantis".to_string(),
703 ..EnvironmentalConfig::default()
704 });
705 let err = monitor
706 .record_session(SessionInfo {
707 session_id: "s2".to_string(),
708 session_type: MeasurementType::Inference,
709 start_time: std::time::SystemTime::now(),
710 duration_hours: 1.0,
711 workload_description: "test".to_string(),
712 region: "Atlantis".to_string(),
713 estimated_energy_kwh: 1.0,
714 })
715 .await
716 .expect_err("no intensity is registered for 'Atlantis'");
717 assert!(
718 err.to_string().contains("Atlantis"),
719 "the refusal must name the region: {err}"
720 );
721 }
722
723 #[derive(Debug)]
727 struct FixedForecastSource;
728
729 impl ForecastSource for FixedForecastSource {
730 fn carbon_intensity_forecast(
731 &self,
732 _region: &str,
733 hours: usize,
734 ) -> Result<Vec<CarbonForecast>> {
735 let now = std::time::SystemTime::now();
736 Ok((0..hours)
737 .map(|h| CarbonForecast {
738 timestamp: now + Duration::from_secs(h as u64 * 3600),
739 predicted_carbon_intensity: 100.0,
740 renewable_percentage: 60.0,
741 confidence: 0.42,
742 })
743 .collect())
744 }
745
746 fn energy_price_forecast(
747 &self,
748 _region: &str,
749 hours: usize,
750 ) -> Result<Vec<EnergyPriceForecast>> {
751 let now = std::time::SystemTime::now();
752 Ok((0..hours)
753 .map(|h| EnergyPriceForecast {
754 timestamp: now + Duration::from_secs(h as u64 * 3600),
755 predicted_price_per_kwh: 0.1,
756 confidence: 0.42,
757 })
758 .collect())
759 }
760 }
761
762 #[tokio::test]
766 async fn test_scheduling_optimization_without_forecast_source_errors() {
767 let monitor = EnvironmentalMonitor::new(EnvironmentalConfig::default());
768 assert!(!monitor.has_forecast_source());
769
770 let workload = WorkloadDescription {
771 workload_name: "test workload".to_string(),
772 workload_type: "training".to_string(),
773 priority: WorkloadPriority::Medium,
774 estimated_duration_hours: 2.0,
775 resource_requirements: std::collections::HashMap::new(),
776 estimated_energy_kwh: 5.0,
777 };
778
779 let error = monitor
780 .optimize_scheduling(workload)
781 .await
782 .expect_err("must fail honestly without a ForecastSource");
783 assert!(error.to_string().contains("ForecastSource"));
784 }
785
786 #[tokio::test]
791 async fn test_scheduling_optimization_uses_real_forecast_source() {
792 let mut monitor = EnvironmentalMonitor::new(EnvironmentalConfig::default());
793 monitor.set_forecast_source(Box::new(FixedForecastSource));
794 assert!(monitor.has_forecast_source());
795
796 let workload = WorkloadDescription {
797 workload_name: "test workload".to_string(),
798 workload_type: "training".to_string(),
799 priority: WorkloadPriority::Medium,
800 estimated_duration_hours: 2.0,
801 resource_requirements: std::collections::HashMap::new(),
802 estimated_energy_kwh: 5.0,
803 };
804
805 let schedule = monitor.optimize_scheduling(workload).await.expect("async operation failed");
806 assert!(schedule.projected_savings.carbon_reduction_kg >= 0.0);
807 assert!(
808 schedule.carbon_intensity_forecast.iter().all(|&v| v == 100.0),
809 "must reflect the real ForecastSource data, not a sine wave"
810 );
811 assert_eq!(
812 schedule.confidence, 0.42,
813 "must be derived from the real ForecastSource confidence, not the old hardcoded 0.85"
814 );
815 }
816
817 #[tokio::test]
818 async fn test_environmental_report_generation() {
819 let mut monitor = EnvironmentalMonitor::new(EnvironmentalConfig::default());
820
821 let report = monitor
822 .generate_environmental_report(ReportType::Summary)
823 .await
824 .expect("async operation failed");
825 assert!(!report.report_id.is_empty());
826 assert!(!report.recommendations.is_empty());
827 }
828
829 #[test]
830 fn test_convenience_functions() {
831 let monitor = create_environmental_monitor();
832 assert_eq!(monitor.config.region, "US-West");
833
834 let regional_monitor = create_regional_environmental_monitor("EU-North".to_string());
835 assert_eq!(regional_monitor.config.region, "EU-North");
836 }
837}