From c21f5960fa7adb724387b5b3e4917549dd2c2578 Mon Sep 17 00:00:00 2001 From: IamLRBA Date: Wed, 16 Jul 2025 21:43:15 +0300 Subject: [PATCH 01/11] Add CPU and RAM utilization tracking - Implement CPU and RAM utilization percentage tracking using psutil - Add cpu_utilization_percent, ram_utilization_percent, and ram_used_gb metrics - Include utilization metrics in emissions data output - Update IntelPowerGadget and IntelRAPL classes to collect utilization data Resolves #885 --- codecarbon/core/cpu.py | 12 ++++++++++-- codecarbon/emissions_tracker.py | 5 ++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/codecarbon/core/cpu.py b/codecarbon/core/cpu.py index 3ab71cfab..db3793d04 100644 --- a/codecarbon/core/cpu.py +++ b/codecarbon/core/cpu.py @@ -259,7 +259,7 @@ def __init__( self._windows_exec_backup = None self._setup_cli() - def _setup_cli(self) -> None: + def _setup_cli() -> None: """ Setup cli command to run Intel Power Gadget """ @@ -359,6 +359,10 @@ def get_cpu_details(self) -> Dict: cpu_details[col_name] = cpu_data[col_name].iloc[-1] else: cpu_details[col_name] = cpu_data[col_name].mean() + # CPU and RAM utilization + cpu_details["cpu_utilization_percent"] = psutil.cpu_percent() + cpu_details["ram_utilization_percent"] = psutil.virtual_memory().percent + cpu_details["ram_used_gb"] = psutil.virtual_memory().used / (1024 ** 3) except Exception as e: logger.info( f"Unable to read Intel Power Gadget logged file at {self._log_file_path}\n \ @@ -823,6 +827,10 @@ def get_cpu_details(self, duration: Time) -> Dict: cpu_details[rapl_file.name.replace("Energy", "Power")] = ( rapl_file.power.W ) + # CPU and RAM utilization + cpu_details["cpu_utilization_percent"] = psutil.cpu_percent() + cpu_details["ram_utilization_percent"] = psutil.virtual_memory().percent + cpu_details["ram_used_gb"] = psutil.virtual_memory().used / (1024 ** 3) except Exception as e: logger.info( "\tRAPL - Unable to read Intel RAPL files at %s\n \ @@ -1000,4 +1008,4 @@ def _main(self) -> Tuple[str, int]: return "Unknown", None def start(self): - pass + pass \ No newline at end of file diff --git a/codecarbon/emissions_tracker.py b/codecarbon/emissions_tracker.py index 073a969c7..ba26d04d0 100644 --- a/codecarbon/emissions_tracker.py +++ b/codecarbon/emissions_tracker.py @@ -758,6 +758,9 @@ def _prepare_emissions_data(self) -> EmissionsData: tracking_mode=self._conf.get("tracking_mode"), pue=self._pue, wue=self._wue, + cpu_utilization_percent=psutil.cpu_percent(), + ram_utilization_percent=psutil.virtual_memory().percent, + ram_used_gb=psutil.virtual_memory().used / (1024 ** 3), ) logger.debug(total_emissions) return total_emissions @@ -1327,4 +1330,4 @@ def wrapped_fn(*args, **kwargs): if fn: return _decorate(fn) - return _decorate + return _decorate \ No newline at end of file From fc9b3d81a4aea9f8ca453b3e381798fecfbbbde5 Mon Sep 17 00:00:00 2001 From: IamLRBA Date: Fri, 1 Aug 2025 11:39:32 +0300 Subject: [PATCH 02/11] style: fix linting issues --- codecarbon/core/cpu.py | 8 ++++---- codecarbon/emissions_tracker.py | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/codecarbon/core/cpu.py b/codecarbon/core/cpu.py index db3793d04..0e9371953 100644 --- a/codecarbon/core/cpu.py +++ b/codecarbon/core/cpu.py @@ -259,7 +259,7 @@ def __init__( self._windows_exec_backup = None self._setup_cli() - def _setup_cli() -> None: + def _setup_cli(self) -> None: """ Setup cli command to run Intel Power Gadget """ @@ -362,7 +362,7 @@ def get_cpu_details(self) -> Dict: # CPU and RAM utilization cpu_details["cpu_utilization_percent"] = psutil.cpu_percent() cpu_details["ram_utilization_percent"] = psutil.virtual_memory().percent - cpu_details["ram_used_gb"] = psutil.virtual_memory().used / (1024 ** 3) + cpu_details["ram_used_gb"] = psutil.virtual_memory().used / (1024**3) except Exception as e: logger.info( f"Unable to read Intel Power Gadget logged file at {self._log_file_path}\n \ @@ -830,7 +830,7 @@ def get_cpu_details(self, duration: Time) -> Dict: # CPU and RAM utilization cpu_details["cpu_utilization_percent"] = psutil.cpu_percent() cpu_details["ram_utilization_percent"] = psutil.virtual_memory().percent - cpu_details["ram_used_gb"] = psutil.virtual_memory().used / (1024 ** 3) + cpu_details["ram_used_gb"] = psutil.virtual_memory().used / (1024**3) except Exception as e: logger.info( "\tRAPL - Unable to read Intel RAPL files at %s\n \ @@ -1008,4 +1008,4 @@ def _main(self) -> Tuple[str, int]: return "Unknown", None def start(self): - pass \ No newline at end of file + pass diff --git a/codecarbon/emissions_tracker.py b/codecarbon/emissions_tracker.py index ba26d04d0..a018c217e 100644 --- a/codecarbon/emissions_tracker.py +++ b/codecarbon/emissions_tracker.py @@ -13,6 +13,8 @@ from functools import wraps from typing import Any, Callable, Dict, List, Optional, Union +import psutil + from codecarbon._version import __version__ from codecarbon.core.config import get_hierarchical_config from codecarbon.core.emissions import Emissions @@ -760,7 +762,7 @@ def _prepare_emissions_data(self) -> EmissionsData: wue=self._wue, cpu_utilization_percent=psutil.cpu_percent(), ram_utilization_percent=psutil.virtual_memory().percent, - ram_used_gb=psutil.virtual_memory().used / (1024 ** 3), + ram_used_gb=psutil.virtual_memory().used / (1024**3), ) logger.debug(total_emissions) return total_emissions @@ -1330,4 +1332,4 @@ def wrapped_fn(*args, **kwargs): if fn: return _decorate(fn) - return _decorate \ No newline at end of file + return _decorate From 0999d9c19705dc9518dc1d74d0eb83eac20f36f0 Mon Sep 17 00:00:00 2001 From: benoit-cty Date: Wed, 19 Nov 2025 11:35:37 +0100 Subject: [PATCH 03/11] Collect CPU and RAM utilization metrics --- codecarbon/core/cpu.py | 8 ------ codecarbon/emissions_tracker.py | 27 ++++++++++++++++++--- codecarbon/output_methods/emissions_data.py | 6 +++++ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/codecarbon/core/cpu.py b/codecarbon/core/cpu.py index 0e9371953..3ab71cfab 100644 --- a/codecarbon/core/cpu.py +++ b/codecarbon/core/cpu.py @@ -359,10 +359,6 @@ def get_cpu_details(self) -> Dict: cpu_details[col_name] = cpu_data[col_name].iloc[-1] else: cpu_details[col_name] = cpu_data[col_name].mean() - # CPU and RAM utilization - cpu_details["cpu_utilization_percent"] = psutil.cpu_percent() - cpu_details["ram_utilization_percent"] = psutil.virtual_memory().percent - cpu_details["ram_used_gb"] = psutil.virtual_memory().used / (1024**3) except Exception as e: logger.info( f"Unable to read Intel Power Gadget logged file at {self._log_file_path}\n \ @@ -827,10 +823,6 @@ def get_cpu_details(self, duration: Time) -> Dict: cpu_details[rapl_file.name.replace("Energy", "Power")] = ( rapl_file.power.W ) - # CPU and RAM utilization - cpu_details["cpu_utilization_percent"] = psutil.cpu_percent() - cpu_details["ram_utilization_percent"] = psutil.virtual_memory().percent - cpu_details["ram_used_gb"] = psutil.virtual_memory().used / (1024**3) except Exception as e: logger.info( "\tRAPL - Unable to read Intel RAPL files at %s\n \ diff --git a/codecarbon/emissions_tracker.py b/codecarbon/emissions_tracker.py index a018c217e..77a669f3f 100644 --- a/codecarbon/emissions_tracker.py +++ b/codecarbon/emissions_tracker.py @@ -309,6 +309,10 @@ def __init__( self._last_measured_time: float = time.perf_counter() self._total_energy: Energy = Energy.from_energy(kWh=0) self._total_water: Water = Water.from_litres(litres=0) + # CPU and RAM utilization tracking + self._cpu_utilization_history: List[float] = [] + self._ram_utilization_history: List[float] = [] + self._ram_used_history: List[float] = [] self._total_cpu_energy: Energy = Energy.from_energy(kWh=0) self._total_gpu_energy: Energy = Energy.from_energy(kWh=0) self._total_ram_energy: Energy = Energy.from_energy(kWh=0) @@ -451,6 +455,12 @@ def start(self) -> None: return self._last_measured_time = self._start_time = time.perf_counter() + + # Clear utilization history for fresh measurements + self._cpu_utilization_history.clear() + self._ram_utilization_history.clear() + self._ram_used_history.clear() + # Read initial energy for hardware for hardware in self._hardware: hardware.start() @@ -494,6 +504,12 @@ def start_task(self, task_name=None) -> None: if task_name in self._tasks.keys(): task_name += "_" + uuid.uuid4().__str__() self._last_measured_time = self._start_time = time.perf_counter() + + # Clear utilization history for fresh measurements + self._cpu_utilization_history.clear() + self._ram_utilization_history.clear() + self._ram_used_history.clear() + # Read initial energy for hardware for hardware in self._hardware: hardware.start() @@ -733,6 +749,9 @@ def _prepare_emissions_data(self) -> EmissionsData: duration=duration.seconds, emissions=emissions, # kg emissions_rate=emissions / duration.seconds, # kg/s + cpu_utilization_percent=sum(self._cpu_utilization_history) / len(self._cpu_utilization_history) if self._cpu_utilization_history else psutil.cpu_percent(), + ram_utilization_percent=sum(self._ram_utilization_history) / len(self._ram_utilization_history) if self._ram_utilization_history else psutil.virtual_memory().percent, + ram_used_gb=sum(self._ram_used_history) / len(self._ram_used_history) if self._ram_used_history else psutil.virtual_memory().used / (1024**3), cpu_power=self._cpu_power.W, gpu_power=self._gpu_power.W, ram_power=self._ram_power.W, @@ -760,9 +779,6 @@ def _prepare_emissions_data(self) -> EmissionsData: tracking_mode=self._conf.get("tracking_mode"), pue=self._pue, wue=self._wue, - cpu_utilization_percent=psutil.cpu_percent(), - ram_utilization_percent=psutil.virtual_memory().percent, - ram_used_gb=psutil.virtual_memory().used / (1024**3), ) logger.debug(total_emissions) return total_emissions @@ -808,6 +824,11 @@ def _monitor_power(self) -> None: for hardware in self._hardware: if isinstance(hardware, CPU): hardware.monitor_power() + + # Collect CPU and RAM utilization metrics + self._cpu_utilization_history.append(psutil.cpu_percent()) + self._ram_utilization_history.append(psutil.virtual_memory().percent) + self._ram_used_history.append(psutil.virtual_memory().used / (1024**3)) def _do_measurements(self) -> None: for hardware in self._hardware: diff --git a/codecarbon/output_methods/emissions_data.py b/codecarbon/output_methods/emissions_data.py index 8813a2679..832f19480 100644 --- a/codecarbon/output_methods/emissions_data.py +++ b/codecarbon/output_methods/emissions_data.py @@ -16,6 +16,9 @@ class EmissionsData: duration: float emissions: float emissions_rate: float + cpu_utilization_percent: float = 0 + ram_utilization_percent: float = 0 + ram_used_gb: float = 0 cpu_power: float gpu_power: float ram_power: float @@ -77,6 +80,9 @@ class TaskEmissionsData: duration: float emissions: float emissions_rate: float + cpu_utilization_percent: float = 0 + ram_utilization_percent: float = 0 + ram_used_gb: float = 0 cpu_power: float gpu_power: float ram_power: float From 55a1352dd83323f482c03eae66b787d3815f0089 Mon Sep 17 00:00:00 2001 From: benoit-cty Date: Wed, 19 Nov 2025 18:46:31 +0100 Subject: [PATCH 04/11] Add GPU laod tracking --- codecarbon/emissions_tracker.py | 18 +- codecarbon/output_methods/emissions_data.py | 14 +- docs/edit/output.rst | 10 +- test_gpu_monitoring.py | 51 +++++ test_utilization_tracking.py | 135 +++++++++++++ tests/test_utilization_tracking.py | 206 ++++++++++++++++++++ 6 files changed, 424 insertions(+), 10 deletions(-) create mode 100644 test_gpu_monitoring.py create mode 100644 test_utilization_tracking.py create mode 100644 tests/test_utilization_tracking.py diff --git a/codecarbon/emissions_tracker.py b/codecarbon/emissions_tracker.py index 77a669f3f..7001f8d04 100644 --- a/codecarbon/emissions_tracker.py +++ b/codecarbon/emissions_tracker.py @@ -311,6 +311,7 @@ def __init__( self._total_water: Water = Water.from_litres(litres=0) # CPU and RAM utilization tracking self._cpu_utilization_history: List[float] = [] + self._gpu_utilization_history: List[float] = [] self._ram_utilization_history: List[float] = [] self._ram_used_history: List[float] = [] self._total_cpu_energy: Energy = Energy.from_energy(kWh=0) @@ -460,6 +461,7 @@ def start(self) -> None: self._cpu_utilization_history.clear() self._ram_utilization_history.clear() self._ram_used_history.clear() + self._gpu_utilization_history.clear() # Read initial energy for hardware for hardware in self._hardware: @@ -509,6 +511,7 @@ def start_task(self, task_name=None) -> None: self._cpu_utilization_history.clear() self._ram_utilization_history.clear() self._ram_used_history.clear() + self._gpu_utilization_history.clear() # Read initial energy for hardware for hardware in self._hardware: @@ -749,9 +752,10 @@ def _prepare_emissions_data(self) -> EmissionsData: duration=duration.seconds, emissions=emissions, # kg emissions_rate=emissions / duration.seconds, # kg/s - cpu_utilization_percent=sum(self._cpu_utilization_history) / len(self._cpu_utilization_history) if self._cpu_utilization_history else psutil.cpu_percent(), - ram_utilization_percent=sum(self._ram_utilization_history) / len(self._ram_utilization_history) if self._ram_utilization_history else psutil.virtual_memory().percent, - ram_used_gb=sum(self._ram_used_history) / len(self._ram_used_history) if self._ram_used_history else psutil.virtual_memory().used / (1024**3), + cpu_utilization_percent=sum(self._cpu_utilization_history) / len(self._cpu_utilization_history) if self._cpu_utilization_history else 0, + gpu_utilization_percent=sum(self._gpu_utilization_history) / len(self._gpu_utilization_history) if self._gpu_utilization_history else 0, + ram_utilization_percent=sum(self._ram_utilization_history) / len(self._ram_utilization_history) if self._ram_utilization_history else 0, + ram_used_gb=sum(self._ram_used_history) / len(self._ram_used_history) if self._ram_used_history else 0, cpu_power=self._cpu_power.W, gpu_power=self._gpu_power.W, ram_power=self._ram_power.W, @@ -829,6 +833,14 @@ def _monitor_power(self) -> None: self._cpu_utilization_history.append(psutil.cpu_percent()) self._ram_utilization_history.append(psutil.virtual_memory().percent) self._ram_used_history.append(psutil.virtual_memory().used / (1024**3)) + + # Collect GPU utilization metrics + for hardware in self._hardware: + if isinstance(hardware, GPU): + gpu_details = hardware.devices.get_gpu_details() + for gpu_detail in gpu_details: + if 'gpu_utilization' in gpu_detail: + self._gpu_utilization_history.append(gpu_detail['gpu_utilization']) def _do_measurements(self) -> None: for hardware in self._hardware: diff --git a/codecarbon/output_methods/emissions_data.py b/codecarbon/output_methods/emissions_data.py index 832f19480..eec45df5c 100644 --- a/codecarbon/output_methods/emissions_data.py +++ b/codecarbon/output_methods/emissions_data.py @@ -16,9 +16,10 @@ class EmissionsData: duration: float emissions: float emissions_rate: float - cpu_utilization_percent: float = 0 - ram_utilization_percent: float = 0 - ram_used_gb: float = 0 + cpu_utilization_percent: float + gpu_utilization_percent: float + ram_utilization_percent: float + ram_used_gb: float cpu_power: float gpu_power: float ram_power: float @@ -80,9 +81,10 @@ class TaskEmissionsData: duration: float emissions: float emissions_rate: float - cpu_utilization_percent: float = 0 - ram_utilization_percent: float = 0 - ram_used_gb: float = 0 + cpu_utilization_percent: float + gpu_utilization_percent: float + ram_utilization_percent: float + ram_used_gb: float cpu_power: float gpu_power: float ram_power: float diff --git a/docs/edit/output.rst b/docs/edit/output.rst index 2a6a4a369..7c2e7817a 100644 --- a/docs/edit/output.rst +++ b/docs/edit/output.rst @@ -77,8 +77,16 @@ input parameter (defaults to the current directory), for each experiment tracked | This is done for privacy protection. * - ram_total_size - total RAM available (Go) - * - Tracking_mode: + * - tracking_mode: - ``machine`` or ``process``(default to ``machine``) + * - cpu_utilization_percent + - Average CPU utilization during tracking period (%) + * - gpu_utilization_percent + - Average GPU utilization during tracking period (%) + * - ram_utilization_percent + - Average RAM utilization during tracking period (%) + * - ram_used_gb + - Average RAM used during tracking period (GB) .. note:: diff --git a/test_gpu_monitoring.py b/test_gpu_monitoring.py new file mode 100644 index 000000000..5bb1ea091 --- /dev/null +++ b/test_gpu_monitoring.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +""" +Simple test script to verify GPU load monitoring functionality. +This script will run a simple workload and check if GPU utilization is being tracked. +""" + +import time +from codecarbon import EmissionsTracker + +def main(): + print("Starting GPU load monitoring test...") + print("=" * 60) + + # Initialize the tracker + tracker = EmissionsTracker( + project_name="gpu_load_test", + measure_power_secs=2, + save_to_file=True, + output_file="test_gpu_emissions.csv" + ) + + # Start tracking + tracker.start() + print("Tracker started. Running for 10 seconds...") + + # Run for a short duration to collect some metrics + time.sleep(10) + + # Stop tracking + emissions = tracker.stop() + + print("=" * 60) + print("Test completed!") + print(f"Total emissions: {emissions:.6f} kg CO2") + + # Check if GPU utilization was tracked + if hasattr(tracker, 'final_emissions_data'): + data = tracker.final_emissions_data + print(f"GPU utilization: {data.gpu_utilization_percent:.2f}%") + print(f"CPU utilization: {data.cpu_utilization_percent:.2f}%") + print(f"RAM utilization: {data.ram_utilization_percent:.2f}%") + + if data.gpu_utilization_percent > 0: + print("\n✓ GPU utilization tracking is working!") + else: + print("\n⚠ GPU utilization is 0% (may not have GPU or no GPU workload)") + + print("\nCheck test_gpu_emissions.csv for detailed results.") + +if __name__ == "__main__": + main() diff --git a/test_utilization_tracking.py b/test_utilization_tracking.py new file mode 100644 index 000000000..8a3bcb8f0 --- /dev/null +++ b/test_utilization_tracking.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +""" +Test script to verify CPU and RAM utilization tracking improvements. +This script tests that the metrics are collected and averaged correctly. +""" + +import time +from codecarbon import EmissionsTracker + +def test_basic_tracking(): + """Test basic emissions tracking with utilization metrics.""" + print("=" * 60) + print("Test 1: Basic Emissions Tracking") + print("=" * 60) + + tracker = EmissionsTracker() + tracker.start() + + # Run for a few seconds to collect multiple measurements + print("Running for 5 seconds to collect measurements...") + time.sleep(5) + + tracker.stop() + emissions = tracker.final_emissions_data + + print(f"\nResults:") + print(f" Duration: {emissions.duration:.2f} seconds") + print(f" CPU Utilization: {emissions.cpu_utilization_percent:.2f}%") + print(f" RAM Utilization: {emissions.ram_utilization_percent:.2f}%") + print(f" RAM Used: {emissions.ram_used_gb:.2f} GB") + print(f" Energy Consumed: {emissions.energy_consumed:.6f} kWh") + print(f" Emissions: {emissions.emissions:.6f} kg CO2eq") + + # Verify that metrics are reasonable + assert 0 <= emissions.cpu_utilization_percent <= 100, "CPU utilization out of range" + assert 0 <= emissions.ram_utilization_percent <= 100, "RAM utilization out of range" + assert emissions.ram_used_gb >= 0, "RAM used should be non-negative" + + print("\n✓ Test 1 passed!") + return emissions + + +def test_task_tracking(): + """Test task-based tracking with utilization metrics.""" + print("\n" + "=" * 60) + print("Test 2: Task-Based Tracking") + print("=" * 60) + + tracker = EmissionsTracker() + tracker.start() + + # Start a task + tracker.start_task("test_task") + print("Running task for 3 seconds...") + time.sleep(3) + + task_emissions = tracker.stop_task() + tracker.stop() + + print(f"\nTask Results:") + print(f" Duration: {task_emissions.duration:.2f} seconds") + print(f" CPU Utilization: {task_emissions.cpu_utilization_percent:.2f}%") + print(f" RAM Utilization: {task_emissions.ram_utilization_percent:.2f}%") + print(f" RAM Used: {task_emissions.ram_used_gb:.2f} GB") + print(f" Energy Consumed: {task_emissions.energy_consumed:.6f} kWh") + + # Verify that metrics are reasonable + assert 0 <= task_emissions.cpu_utilization_percent <= 100, "CPU utilization out of range" + assert 0 <= task_emissions.ram_utilization_percent <= 100, "RAM utilization out of range" + assert task_emissions.ram_used_gb >= 0, "RAM used should be non-negative" + + print("\n✓ Test 2 passed!") + return task_emissions + + +def test_averaging(): + """Test that averaging is working by comparing with instantaneous values.""" + print("\n" + "=" * 60) + print("Test 3: Verify Averaging vs Instantaneous") + print("=" * 60) + + import psutil + + tracker = EmissionsTracker() + tracker.start() + + # Collect instantaneous values at start + instant_cpu_start = psutil.cpu_percent() + instant_ram_start = psutil.virtual_memory().percent + + print(f"Instantaneous at start:") + print(f" CPU: {instant_cpu_start:.2f}%") + print(f" RAM: {instant_ram_start:.2f}%") + + # Run for several seconds + print("\nRunning for 5 seconds...") + time.sleep(5) + + # Collect instantaneous values at end + instant_cpu_end = psutil.cpu_percent() + instant_ram_end = psutil.virtual_memory().percent + + print(f"\nInstantaneous at end:") + print(f" CPU: {instant_cpu_end:.2f}%") + print(f" RAM: {instant_ram_end:.2f}%") + + tracker.stop() + emissions = tracker.final_emissions_data + + print(f"\nAveraged over period:") + print(f" CPU: {emissions.cpu_utilization_percent:.2f}%") + print(f" RAM: {emissions.ram_utilization_percent:.2f}%") + + # The averaged value should be between start and end (or close to them) + # This is a soft check since system load can vary + print("\n✓ Test 3 passed! (Averaging is working)") + return emissions + + +if __name__ == "__main__": + try: + # Run all tests + test_basic_tracking() + test_task_tracking() + test_averaging() + + print("\n" + "=" * 60) + print("All tests passed! ✓") + print("=" * 60) + + except Exception as e: + print(f"\n✗ Test failed with error: {e}") + import traceback + traceback.print_exc() + exit(1) diff --git a/tests/test_utilization_tracking.py b/tests/test_utilization_tracking.py new file mode 100644 index 000000000..3b0ab814f --- /dev/null +++ b/tests/test_utilization_tracking.py @@ -0,0 +1,206 @@ +""" +Tests for CPU, RAM, and GPU utilization tracking functionality. +""" +import time +import unittest +from pathlib import Path +from unittest import mock + +import pandas as pd + +from codecarbon.emissions_tracker import EmissionsTracker, OfflineEmissionsTracker +from tests.testutils import get_custom_mock_open + + +empty_conf = "[codecarbon]" + + +class TestUtilizationTracking(unittest.TestCase): + """Test suite for CPU and RAM utilization tracking features.""" + + def setUp(self) -> None: + """Set up test fixtures.""" + import tempfile + + self.temp_dir = tempfile.TemporaryDirectory() + self.temp_path = Path(self.temp_dir.name) + self.emissions_file_path = self.temp_path / "emissions.csv" + + # Patch config file access + patcher = mock.patch( + "builtins.open", new_callable=get_custom_mock_open(empty_conf, empty_conf) + ) + self.addCleanup(patcher.stop) + patcher.start() + + def tearDown(self) -> None: + """Clean up test fixtures.""" + self.temp_dir.cleanup() + + def test_utilization_fields_in_emissions_data(self): + """Test that EmissionsData contains utilization fields.""" + tracker = OfflineEmissionsTracker( + country_iso_code="USA", + output_dir=self.temp_path, + save_to_file=False, + ) + + tracker.start() + time.sleep(2) # Run for 2 seconds to collect measurements + tracker.stop() + + emissions_data = tracker.final_emissions_data + + # Verify utilization fields exist + self.assertTrue(hasattr(emissions_data, 'cpu_utilization_percent')) + self.assertTrue(hasattr(emissions_data, 'gpu_utilization_percent')) + self.assertTrue(hasattr(emissions_data, 'ram_utilization_percent')) + self.assertTrue(hasattr(emissions_data, 'ram_used_gb')) + + # Verify values are reasonable + self.assertGreaterEqual(emissions_data.cpu_utilization_percent, 0) + self.assertLessEqual(emissions_data.cpu_utilization_percent, 100) + self.assertGreaterEqual(emissions_data.gpu_utilization_percent, 0) + self.assertLessEqual(emissions_data.gpu_utilization_percent, 100) + self.assertGreaterEqual(emissions_data.ram_utilization_percent, 0) + self.assertLessEqual(emissions_data.ram_utilization_percent, 100) + self.assertGreaterEqual(emissions_data.ram_used_gb, 0) + + def test_utilization_fields_in_csv_output(self): + """Test that utilization metrics are saved to CSV file.""" + tracker = OfflineEmissionsTracker( + country_iso_code="USA", + output_dir=self.temp_path, + ) + + tracker.start() + time.sleep(2) # Run for 2 seconds to collect measurements + tracker.stop() + + # Read CSV and verify columns exist + emissions_df = pd.read_csv(self.emissions_file_path) + + self.assertIn('cpu_utilization_percent', emissions_df.columns) + self.assertIn('gpu_utilization_percent', emissions_df.columns) + self.assertIn('ram_utilization_percent', emissions_df.columns) + self.assertIn('ram_used_gb', emissions_df.columns) + + # Verify values are reasonable + cpu_util = emissions_df['cpu_utilization_percent'].values[0] + gpu_util = emissions_df['gpu_utilization_percent'].values[0] + ram_util = emissions_df['ram_utilization_percent'].values[0] + ram_used = emissions_df['ram_used_gb'].values[0] + + self.assertGreaterEqual(cpu_util, 0) + self.assertLessEqual(cpu_util, 100) + self.assertGreaterEqual(gpu_util, 0) + self.assertLessEqual(gpu_util, 100) + self.assertGreaterEqual(ram_util, 0) + self.assertLessEqual(ram_util, 100) + self.assertGreaterEqual(ram_used, 0) + + def test_utilization_history_cleared_on_start(self): + """Test that utilization history is cleared when tracker starts.""" + tracker = OfflineEmissionsTracker( + country_iso_code="USA", + output_dir=self.temp_path, + save_to_file=False, + ) + + # First run + tracker.start() + time.sleep(1) + tracker.stop() + first_cpu_util = tracker.final_emissions_data.cpu_utilization_percent + + # Second run - history should be cleared + tracker.start() + time.sleep(1) + tracker.stop() + second_cpu_util = tracker.final_emissions_data.cpu_utilization_percent + + # Both should have valid values (not necessarily equal) + self.assertGreaterEqual(first_cpu_util, 0) + self.assertGreaterEqual(second_cpu_util, 0) + + def test_utilization_averaging_over_time(self): + """Test that utilization values are averaged over the tracking period.""" + import psutil + + tracker = OfflineEmissionsTracker( + country_iso_code="USA", + output_dir=self.temp_path, + save_to_file=False, + ) + + # Get instantaneous value before starting + instant_before = psutil.cpu_percent() + + tracker.start() + time.sleep(3) # Run for 3 seconds to collect multiple measurements + tracker.stop() + + # Get instantaneous value after stopping + instant_after = psutil.cpu_percent() + + averaged = tracker.final_emissions_data.cpu_utilization_percent + + # Averaged value should be valid + self.assertGreaterEqual(averaged, 0) + self.assertLessEqual(averaged, 100) + + # The averaged value may differ from instantaneous values + # This is expected behavior - we're just verifying it's computed + + def test_task_utilization_tracking(self): + """Test that task-based tracking includes utilization metrics.""" + tracker = OfflineEmissionsTracker( + country_iso_code="USA", + output_dir=self.temp_path, + save_to_file=False, + ) + + tracker.start() + tracker.start_task("test_task") + time.sleep(2) + task_data = tracker.stop_task() + tracker.stop() + + # Verify task data has utilization fields + self.assertTrue(hasattr(task_data, 'cpu_utilization_percent')) + self.assertTrue(hasattr(task_data, 'ram_utilization_percent')) + self.assertTrue(hasattr(task_data, 'ram_used_gb')) + self.assertTrue(hasattr(task_data, 'gpu_utilization_percent')) + + # Verify values are reasonable + self.assertGreaterEqual(task_data.cpu_utilization_percent, 0) + self.assertLessEqual(task_data.cpu_utilization_percent, 100) + self.assertGreaterEqual(task_data.ram_utilization_percent, 0) + self.assertLessEqual(task_data.ram_utilization_percent, 100) + self.assertGreaterEqual(task_data.ram_used_gb, 0) + self.assertGreaterEqual(task_data.gpu_utilization_percent, 0) + self.assertLessEqual(task_data.gpu_utilization_percent, 100) + + def test_utilization_with_empty_history(self): + """Test that tracker handles empty history gracefully.""" + tracker = OfflineEmissionsTracker( + country_iso_code="USA", + output_dir=self.temp_path, + save_to_file=False, + ) + + # Start and stop immediately (minimal time for history collection) + tracker.start() + tracker.stop() + + emissions_data = tracker.final_emissions_data + + # Should still have valid values (fallback to instantaneous) + self.assertGreaterEqual(emissions_data.cpu_utilization_percent, 0) + self.assertLessEqual(emissions_data.cpu_utilization_percent, 100) + self.assertGreaterEqual(emissions_data.ram_utilization_percent, 0) + self.assertLessEqual(emissions_data.ram_utilization_percent, 100) + + +if __name__ == "__main__": + unittest.main() From 363b5c9762142f3a6f976fe2808baed077265d4a Mon Sep 17 00:00:00 2001 From: benoit-cty Date: Wed, 19 Nov 2025 19:05:28 +0100 Subject: [PATCH 05/11] Add CPU, GPU and RAM load to API --- .../api/infra/database/sql_models.py | 3 ++ .../repositories/repository_emissions.py | 6 +++ .../repositories/repository_experiments.py | 9 ++++ .../repositories/repository_organizations.py | 9 ++++ .../infra/repositories/repository_projects.py | 9 ++++ .../api/infra/repositories/repository_runs.py | 9 ++++ carbonserver/carbonserver/api/schemas.py | 21 +++++++++ .../20251119_add_utilization_metrics.py | 44 +++++++++++++++++++ 8 files changed, 110 insertions(+) create mode 100644 carbonserver/carbonserver/database/alembic/versions/20251119_add_utilization_metrics.py diff --git a/carbonserver/carbonserver/api/infra/database/sql_models.py b/carbonserver/carbonserver/api/infra/database/sql_models.py index 337771697..8872abb21 100644 --- a/carbonserver/carbonserver/api/infra/database/sql_models.py +++ b/carbonserver/carbonserver/api/infra/database/sql_models.py @@ -21,6 +21,9 @@ class Emission(Base): gpu_energy = Column(Float) ram_energy = Column(Float) energy_consumed = Column(Float) + cpu_utilization_percent = Column(Float, nullable=True) + gpu_utilization_percent = Column(Float, nullable=True) + ram_utilization_percent = Column(Float, nullable=True) wue = Column(Float, nullable=False, default=0) run_id = Column(UUID(as_uuid=True), ForeignKey("runs.id", ondelete="CASCADE")) run = relationship("Run", back_populates="emissions") diff --git a/carbonserver/carbonserver/api/infra/repositories/repository_emissions.py b/carbonserver/carbonserver/api/infra/repositories/repository_emissions.py index 70056495d..893ab86e1 100644 --- a/carbonserver/carbonserver/api/infra/repositories/repository_emissions.py +++ b/carbonserver/carbonserver/api/infra/repositories/repository_emissions.py @@ -41,6 +41,9 @@ def add_emission(self, emission: EmissionCreate) -> UUID: gpu_energy=emission.gpu_energy, ram_energy=emission.ram_energy, energy_consumed=emission.energy_consumed, + cpu_utilization_percent=emission.cpu_utilization_percent, + gpu_utilization_percent=emission.gpu_utilization_percent, + ram_utilization_percent=emission.ram_utilization_percent, wue=emission.wue, run_id=emission.run_id, ) @@ -105,6 +108,9 @@ def map_sql_to_schema(emission: sql_models.Emission) -> Emission: gpu_energy=emission.gpu_energy, ram_energy=emission.ram_energy, energy_consumed=emission.energy_consumed, + cpu_utilization_percent=emission.cpu_utilization_percent, + gpu_utilization_percent=emission.gpu_utilization_percent, + ram_utilization_percent=emission.ram_utilization_percent, wue=emission.wue, run_id=emission.run_id, ) diff --git a/carbonserver/carbonserver/api/infra/repositories/repository_experiments.py b/carbonserver/carbonserver/api/infra/repositories/repository_experiments.py index df8a2173c..db729e667 100644 --- a/carbonserver/carbonserver/api/infra/repositories/repository_experiments.py +++ b/carbonserver/carbonserver/api/infra/repositories/repository_experiments.py @@ -135,6 +135,15 @@ def get_project_detailed_sums_by_experiment( func.sum(SqlModelEmission.energy_consumed).label("energy_consumed"), func.sum(SqlModelEmission.duration).label("duration"), func.avg(SqlModelEmission.emissions_rate).label("emissions_rate"), + func.avg(SqlModelEmission.cpu_utilization_percent).label( + "cpu_utilization_percent" + ), + func.avg(SqlModelEmission.gpu_utilization_percent).label( + "gpu_utilization_percent" + ), + func.avg(SqlModelEmission.ram_utilization_percent).label( + "ram_utilization_percent" + ), func.count(SqlModelEmission.emissions_rate).label( "emissions_count" ), diff --git a/carbonserver/carbonserver/api/infra/repositories/repository_organizations.py b/carbonserver/carbonserver/api/infra/repositories/repository_organizations.py index 4ef9e9963..754655650 100644 --- a/carbonserver/carbonserver/api/infra/repositories/repository_organizations.py +++ b/carbonserver/carbonserver/api/infra/repositories/repository_organizations.py @@ -116,6 +116,15 @@ def get_organization_detailed_sums( func.sum(SqlModelEmission.energy_consumed).label("energy_consumed"), func.sum(SqlModelEmission.duration).label("duration"), func.avg(SqlModelEmission.emissions_rate).label("emissions_rate"), + func.avg(SqlModelEmission.cpu_utilization_percent).label( + "cpu_utilization_percent" + ), + func.avg(SqlModelEmission.gpu_utilization_percent).label( + "gpu_utilization_percent" + ), + func.avg(SqlModelEmission.ram_utilization_percent).label( + "ram_utilization_percent" + ), func.count(SqlModelEmission.emissions_rate).label( "emissions_count" ), diff --git a/carbonserver/carbonserver/api/infra/repositories/repository_projects.py b/carbonserver/carbonserver/api/infra/repositories/repository_projects.py index 77dc6dbf4..23a7181ca 100644 --- a/carbonserver/carbonserver/api/infra/repositories/repository_projects.py +++ b/carbonserver/carbonserver/api/infra/repositories/repository_projects.py @@ -130,6 +130,15 @@ def get_project_detailed_sums( func.sum(SqlModelEmission.energy_consumed).label("energy_consumed"), func.sum(SqlModelEmission.duration).label("duration"), func.avg(SqlModelEmission.emissions_rate).label("emissions_rate"), + func.avg(SqlModelEmission.cpu_utilization_percent).label( + "cpu_utilization_percent" + ), + func.avg(SqlModelEmission.gpu_utilization_percent).label( + "gpu_utilization_percent" + ), + func.avg(SqlModelEmission.ram_utilization_percent).label( + "ram_utilization_percent" + ), func.count(SqlModelEmission.emissions_rate).label( "emissions_count" ), diff --git a/carbonserver/carbonserver/api/infra/repositories/repository_runs.py b/carbonserver/carbonserver/api/infra/repositories/repository_runs.py index 84d2202b6..bef3fed48 100644 --- a/carbonserver/carbonserver/api/infra/repositories/repository_runs.py +++ b/carbonserver/carbonserver/api/infra/repositories/repository_runs.py @@ -144,6 +144,15 @@ def get_experiment_detailed_sums_by_run( func.sum(SqlModelEmission.energy_consumed).label("energy_consumed"), func.sum(SqlModelEmission.duration).label("duration"), func.avg(SqlModelEmission.emissions_rate).label("emissions_rate"), + func.avg(SqlModelEmission.cpu_utilization_percent).label( + "cpu_utilization_percent" + ), + func.avg(SqlModelEmission.gpu_utilization_percent).label( + "gpu_utilization_percent" + ), + func.avg(SqlModelEmission.ram_utilization_percent).label( + "ram_utilization_percent" + ), func.count(SqlModelEmission.emissions_rate).label( "emissions_count" ), diff --git a/carbonserver/carbonserver/api/schemas.py b/carbonserver/carbonserver/api/schemas.py index e2365f208..21e5bcc91 100644 --- a/carbonserver/carbonserver/api/schemas.py +++ b/carbonserver/carbonserver/api/schemas.py @@ -87,6 +87,15 @@ class EmissionBase(BaseModel): ram_energy: Optional[float] = Field( ..., ge=0, description="The ram_energy must be greater than zero" ) + cpu_utilization_percent: Optional[float] = Field( + None, ge=0, le=100, description="The CPU utilization must be between 0 and 100" + ) + gpu_utilization_percent: Optional[float] = Field( + None, ge=0, le=100, description="The GPU utilization must be between 0 and 100" + ) + ram_utilization_percent: Optional[float] = Field( + None, ge=0, le=100, description="The RAM utilization must be between 0 and 100" + ) wue: Optional[float] = Field( default=0, ge=0, @@ -183,6 +192,9 @@ class RunReport(RunBase): duration: float emissions_rate: float emissions_count: int + cpu_utilization_percent: Optional[float] = None + gpu_utilization_percent: Optional[float] = None + ram_utilization_percent: Optional[float] = None class ExperimentBase(BaseModel): @@ -246,6 +258,9 @@ class ExperimentReport(ExperimentBase): duration: int emissions_rate: float emissions_count: int + cpu_utilization_percent: Optional[float] = None + gpu_utilization_percent: Optional[float] = None + ram_utilization_percent: Optional[float] = None class Config: schema_extra = { @@ -377,6 +392,9 @@ class ProjectReport(ProjectBase): duration: int emissions_rate: float emissions_count: int + cpu_utilization_percent: Optional[float] = None + gpu_utilization_percent: Optional[float] = None + ram_utilization_percent: Optional[float] = None class OrganizationBase(BaseModel): @@ -420,6 +438,9 @@ class OrganizationReport(OrganizationBase): duration: int emissions_rate: float emissions_count: int + cpu_utilization_percent: Optional[float] = None + gpu_utilization_percent: Optional[float] = None + ram_utilization_percent: Optional[float] = None class Membership(BaseModel): diff --git a/carbonserver/carbonserver/database/alembic/versions/20251119_add_utilization_metrics.py b/carbonserver/carbonserver/database/alembic/versions/20251119_add_utilization_metrics.py new file mode 100644 index 000000000..ad96921ed --- /dev/null +++ b/carbonserver/carbonserver/database/alembic/versions/20251119_add_utilization_metrics.py @@ -0,0 +1,44 @@ +"""add_utilization_metrics_to_emissions + +Revision ID: 20251119_add_utilization +Revises: 202501_f3a10 +Create Date: 2025-11-19 18:52:00.000000 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "20251119_add_utilization" +down_revision = "202501_f3a10" +branch_labels = None +depends_on = None + + +def upgrade(): + """ + Add CPU, GPU, and RAM utilization percentage fields to emissions table. + These fields track the average utilization of resources during emission tracking. + """ + op.add_column( + "emissions", + sa.Column("cpu_utilization_percent", sa.Float, nullable=True), + ) + op.add_column( + "emissions", + sa.Column("gpu_utilization_percent", sa.Float, nullable=True), + ) + op.add_column( + "emissions", + sa.Column("ram_utilization_percent", sa.Float, nullable=True), + ) + + +def downgrade(): + """ + Remove CPU, GPU, and RAM utilization percentage fields from emissions table. + """ + op.drop_column("emissions", "ram_utilization_percent") + op.drop_column("emissions", "gpu_utilization_percent") + op.drop_column("emissions", "cpu_utilization_percent") From 05d9d4d0ddd581ac74ee1e1615f0b13422a03ece Mon Sep 17 00:00:00 2001 From: benoit-cty Date: Wed, 19 Nov 2025 22:02:07 +0100 Subject: [PATCH 06/11] wip: fix test --- .../versions/20251119_add_utilization_metrics.py | 2 +- codecarbon/output_methods/emissions_data.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/carbonserver/carbonserver/database/alembic/versions/20251119_add_utilization_metrics.py b/carbonserver/carbonserver/database/alembic/versions/20251119_add_utilization_metrics.py index ad96921ed..6cf37c6d4 100644 --- a/carbonserver/carbonserver/database/alembic/versions/20251119_add_utilization_metrics.py +++ b/carbonserver/carbonserver/database/alembic/versions/20251119_add_utilization_metrics.py @@ -11,7 +11,7 @@ # revision identifiers, used by Alembic. revision = "20251119_add_utilization" -down_revision = "202501_f3a10" +down_revision = "f3a10c95079f" branch_labels = None depends_on = None diff --git a/codecarbon/output_methods/emissions_data.py b/codecarbon/output_methods/emissions_data.py index eec45df5c..17544aa51 100644 --- a/codecarbon/output_methods/emissions_data.py +++ b/codecarbon/output_methods/emissions_data.py @@ -16,10 +16,6 @@ class EmissionsData: duration: float emissions: float emissions_rate: float - cpu_utilization_percent: float - gpu_utilization_percent: float - ram_utilization_percent: float - ram_used_gb: float cpu_power: float gpu_power: float ram_power: float @@ -44,6 +40,10 @@ class EmissionsData: latitude: float ram_total_size: float tracking_mode: str + cpu_utilization_percent: float = 0.0 + gpu_utilization_percent: float = 0.0 + ram_utilization_percent: float = 0.0 + ram_used_gb: float = 0.0 on_cloud: str = "N" pue: float = 1 wue: float = 0 @@ -81,10 +81,6 @@ class TaskEmissionsData: duration: float emissions: float emissions_rate: float - cpu_utilization_percent: float - gpu_utilization_percent: float - ram_utilization_percent: float - ram_used_gb: float cpu_power: float gpu_power: float ram_power: float @@ -109,6 +105,10 @@ class TaskEmissionsData: latitude: float ram_total_size: float tracking_mode: str + cpu_utilization_percent: float = 0.0 + gpu_utilization_percent: float = 0.0 + ram_utilization_percent: float = 0.0 + ram_used_gb: float = 0.0 on_cloud: str = "N" @property From 6db1f8beaa5e2d6b1dc7adfe1da31c14043cebd2 Mon Sep 17 00:00:00 2001 From: IamLRBA Date: Wed, 26 Nov 2025 01:46:06 +0300 Subject: [PATCH 07/11] test: update test data with new utilization columns - Add cpu_utilization_percent, gpu_utilization_percent, ram_utilization_percent, and ram_used_gb columns to test data - Fixes test_offline_tracker_valid_headers test failure - Ensures CSV validation works with new utilization tracking fields --- tests/test_data/emissions_valid_headers.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_data/emissions_valid_headers.csv b/tests/test_data/emissions_valid_headers.csv index 7743f6073..b7493c902 100644 --- a/tests/test_data/emissions_valid_headers.csv +++ b/tests/test_data/emissions_valid_headers.csv @@ -1,2 +1,2 @@ -timestamp,project_name,run_id,experiment_id,duration,emissions,emissions_rate,cpu_power,gpu_power,ram_power,cpu_energy,gpu_energy,ram_energy,energy_consumed,water_consumed,country_name,country_iso_code,region,cloud_provider,cloud_region,os,python_version,codecarbon_version,cpu_count,cpu_model,gpu_count,gpu_model,longitude,latitude,ram_total_size,tracking_mode,on_cloud,pue,wue -2021-09-23T15:04:51,codecarbon,0a578547-1d6b-4e2f-be0c-7ad10f2f7c97,test,161.20380687713623,0.0004490989249167,0.0027859076880178,0.269999999999999,0.0,12.884901888000002,0.0,0,0.00057442898176,0.00057442898176,0.1,Morocco,MAR,casablanca-settat,,,macOS-10.15.7-x86_64-i386-64bit,3.8.0,2.1.3,12,Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz,,,-7.9084,33.5932,,machine,N,1.0,0.0 +timestamp,project_name,run_id,experiment_id,duration,emissions,emissions_rate,cpu_power,gpu_power,ram_power,cpu_energy,gpu_energy,ram_energy,energy_consumed,water_consumed,country_name,country_iso_code,region,cloud_provider,cloud_region,os,python_version,codecarbon_version,cpu_count,cpu_model,gpu_count,gpu_model,longitude,latitude,ram_total_size,tracking_mode,cpu_utilization_percent,gpu_utilization_percent,ram_utilization_percent,ram_used_gb,on_cloud,pue,wue +2021-09-23T15:04:51,codecarbon,0a578547-1d6b-4e2f-be0c-7ad10f2f7c97,test,161.20380687713623,0.0004490989249167,0.0027859076880178,0.269999999999999,0.0,12.884901888000002,0.0,0,0.00057442898176,0.00057442898176,0.1,Morocco,MAR,casablanca-settat,,,macOS-10.15.7-x86_64-i386-64bit,3.8.0,2.1.3,12,Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz,,,-7.9084,33.5932,,machine,0.0,0.0,0.0,0.0,N,1.0,0.0 From d06433a88fa778e22b75ef92943757b47925fb07 Mon Sep 17 00:00:00 2001 From: benoit-cty <4-benoit-cty@users.noreply.git.leximpact.dev> Date: Sun, 28 Dec 2025 12:26:32 +0100 Subject: [PATCH 08/11] Fix pre-commit --- codecarbon/emissions_tracker.py | 42 ++++++++++---- test_gpu_monitoring.py | 23 ++++---- test_utilization_tracking.py | 59 +++++++++++--------- tests/test_utilization_tracking.py | 90 +++++++++++++++--------------- 4 files changed, 121 insertions(+), 93 deletions(-) diff --git a/codecarbon/emissions_tracker.py b/codecarbon/emissions_tracker.py index cc946a689..05cea21b7 100644 --- a/codecarbon/emissions_tracker.py +++ b/codecarbon/emissions_tracker.py @@ -489,13 +489,13 @@ def start(self) -> None: return self._last_measured_time = self._start_time = time.perf_counter() - + # Clear utilization history for fresh measurements self._cpu_utilization_history.clear() self._ram_utilization_history.clear() self._ram_used_history.clear() self._gpu_utilization_history.clear() - + # Read initial energy for hardware for hardware in self._hardware: hardware.start() @@ -539,13 +539,13 @@ def start_task(self, task_name=None) -> None: if task_name in self._tasks.keys(): task_name += "_" + uuid.uuid4().__str__() self._last_measured_time = self._start_time = time.perf_counter() - + # Clear utilization history for fresh measurements self._cpu_utilization_history.clear() self._ram_utilization_history.clear() self._ram_used_history.clear() self._gpu_utilization_history.clear() - + # Read initial energy for hardware for hardware in self._hardware: hardware.start() @@ -803,10 +803,26 @@ def _prepare_emissions_data(self) -> EmissionsData: duration=duration.seconds, emissions=emissions, # kg emissions_rate=emissions / duration.seconds, # kg/s - cpu_utilization_percent=sum(self._cpu_utilization_history) / len(self._cpu_utilization_history) if self._cpu_utilization_history else 0, - gpu_utilization_percent=sum(self._gpu_utilization_history) / len(self._gpu_utilization_history) if self._gpu_utilization_history else 0, - ram_utilization_percent=sum(self._ram_utilization_history) / len(self._ram_utilization_history) if self._ram_utilization_history else 0, - ram_used_gb=sum(self._ram_used_history) / len(self._ram_used_history) if self._ram_used_history else 0, + cpu_utilization_percent=( + sum(self._cpu_utilization_history) / len(self._cpu_utilization_history) + if self._cpu_utilization_history + else 0 + ), + gpu_utilization_percent=( + sum(self._gpu_utilization_history) / len(self._gpu_utilization_history) + if self._gpu_utilization_history + else 0 + ), + ram_utilization_percent=( + sum(self._ram_utilization_history) / len(self._ram_utilization_history) + if self._ram_utilization_history + else 0 + ), + ram_used_gb=( + sum(self._ram_used_history) / len(self._ram_used_history) + if self._ram_used_history + else 0 + ), cpu_power=avg_cpu_power, gpu_power=avg_gpu_power, ram_power=avg_ram_power, @@ -879,19 +895,21 @@ def _monitor_power(self) -> None: for hardware in self._hardware: if isinstance(hardware, CPU): hardware.monitor_power() - + # Collect CPU and RAM utilization metrics self._cpu_utilization_history.append(psutil.cpu_percent()) self._ram_utilization_history.append(psutil.virtual_memory().percent) self._ram_used_history.append(psutil.virtual_memory().used / (1024**3)) - + # Collect GPU utilization metrics for hardware in self._hardware: if isinstance(hardware, GPU): gpu_details = hardware.devices.get_gpu_details() for gpu_detail in gpu_details: - if 'gpu_utilization' in gpu_detail: - self._gpu_utilization_history.append(gpu_detail['gpu_utilization']) + if "gpu_utilization" in gpu_detail: + self._gpu_utilization_history.append( + gpu_detail["gpu_utilization"] + ) def _do_measurements(self) -> None: for hardware in self._hardware: diff --git a/test_gpu_monitoring.py b/test_gpu_monitoring.py index 5bb1ea091..da715c077 100644 --- a/test_gpu_monitoring.py +++ b/test_gpu_monitoring.py @@ -5,47 +5,50 @@ """ import time + from codecarbon import EmissionsTracker + def main(): print("Starting GPU load monitoring test...") print("=" * 60) - + # Initialize the tracker tracker = EmissionsTracker( project_name="gpu_load_test", measure_power_secs=2, save_to_file=True, - output_file="test_gpu_emissions.csv" + output_file="test_gpu_emissions.csv", ) - + # Start tracking tracker.start() print("Tracker started. Running for 10 seconds...") - + # Run for a short duration to collect some metrics time.sleep(10) - + # Stop tracking emissions = tracker.stop() - + print("=" * 60) print("Test completed!") print(f"Total emissions: {emissions:.6f} kg CO2") - + # Check if GPU utilization was tracked - if hasattr(tracker, 'final_emissions_data'): + if hasattr(tracker, "final_emissions_data"): data = tracker.final_emissions_data print(f"GPU utilization: {data.gpu_utilization_percent:.2f}%") print(f"CPU utilization: {data.cpu_utilization_percent:.2f}%") print(f"RAM utilization: {data.ram_utilization_percent:.2f}%") - + if data.gpu_utilization_percent > 0: print("\n✓ GPU utilization tracking is working!") else: print("\n⚠ GPU utilization is 0% (may not have GPU or no GPU workload)") - + print("\nCheck test_gpu_emissions.csv for detailed results.") + if __name__ == "__main__": main() diff --git a/test_utilization_tracking.py b/test_utilization_tracking.py index 8a3bcb8f0..8e82cced1 100644 --- a/test_utilization_tracking.py +++ b/test_utilization_tracking.py @@ -5,24 +5,26 @@ """ import time + from codecarbon import EmissionsTracker + def test_basic_tracking(): """Test basic emissions tracking with utilization metrics.""" print("=" * 60) print("Test 1: Basic Emissions Tracking") print("=" * 60) - + tracker = EmissionsTracker() tracker.start() - + # Run for a few seconds to collect multiple measurements print("Running for 5 seconds to collect measurements...") time.sleep(5) - + tracker.stop() emissions = tracker.final_emissions_data - + print(f"\nResults:") print(f" Duration: {emissions.duration:.2f} seconds") print(f" CPU Utilization: {emissions.cpu_utilization_percent:.2f}%") @@ -30,12 +32,12 @@ def test_basic_tracking(): print(f" RAM Used: {emissions.ram_used_gb:.2f} GB") print(f" Energy Consumed: {emissions.energy_consumed:.6f} kWh") print(f" Emissions: {emissions.emissions:.6f} kg CO2eq") - + # Verify that metrics are reasonable assert 0 <= emissions.cpu_utilization_percent <= 100, "CPU utilization out of range" assert 0 <= emissions.ram_utilization_percent <= 100, "RAM utilization out of range" assert emissions.ram_used_gb >= 0, "RAM used should be non-negative" - + print("\n✓ Test 1 passed!") return emissions @@ -45,30 +47,34 @@ def test_task_tracking(): print("\n" + "=" * 60) print("Test 2: Task-Based Tracking") print("=" * 60) - + tracker = EmissionsTracker() tracker.start() - + # Start a task tracker.start_task("test_task") print("Running task for 3 seconds...") time.sleep(3) - + task_emissions = tracker.stop_task() tracker.stop() - + print(f"\nTask Results:") print(f" Duration: {task_emissions.duration:.2f} seconds") print(f" CPU Utilization: {task_emissions.cpu_utilization_percent:.2f}%") print(f" RAM Utilization: {task_emissions.ram_utilization_percent:.2f}%") print(f" RAM Used: {task_emissions.ram_used_gb:.2f} GB") print(f" Energy Consumed: {task_emissions.energy_consumed:.6f} kWh") - + # Verify that metrics are reasonable - assert 0 <= task_emissions.cpu_utilization_percent <= 100, "CPU utilization out of range" - assert 0 <= task_emissions.ram_utilization_percent <= 100, "RAM utilization out of range" + assert ( + 0 <= task_emissions.cpu_utilization_percent <= 100 + ), "CPU utilization out of range" + assert ( + 0 <= task_emissions.ram_utilization_percent <= 100 + ), "RAM utilization out of range" assert task_emissions.ram_used_gb >= 0, "RAM used should be non-negative" - + print("\n✓ Test 2 passed!") return task_emissions @@ -78,39 +84,39 @@ def test_averaging(): print("\n" + "=" * 60) print("Test 3: Verify Averaging vs Instantaneous") print("=" * 60) - + import psutil - + tracker = EmissionsTracker() tracker.start() - + # Collect instantaneous values at start instant_cpu_start = psutil.cpu_percent() instant_ram_start = psutil.virtual_memory().percent - + print(f"Instantaneous at start:") print(f" CPU: {instant_cpu_start:.2f}%") print(f" RAM: {instant_ram_start:.2f}%") - + # Run for several seconds print("\nRunning for 5 seconds...") time.sleep(5) - + # Collect instantaneous values at end instant_cpu_end = psutil.cpu_percent() instant_ram_end = psutil.virtual_memory().percent - + print(f"\nInstantaneous at end:") print(f" CPU: {instant_cpu_end:.2f}%") print(f" RAM: {instant_ram_end:.2f}%") - + tracker.stop() emissions = tracker.final_emissions_data - + print(f"\nAveraged over period:") print(f" CPU: {emissions.cpu_utilization_percent:.2f}%") print(f" RAM: {emissions.ram_utilization_percent:.2f}%") - + # The averaged value should be between start and end (or close to them) # This is a soft check since system load can vary print("\n✓ Test 3 passed! (Averaging is working)") @@ -123,13 +129,14 @@ def test_averaging(): test_basic_tracking() test_task_tracking() test_averaging() - + print("\n" + "=" * 60) print("All tests passed! ✓") print("=" * 60) - + except Exception as e: print(f"\n✗ Test failed with error: {e}") import traceback + traceback.print_exc() exit(1) diff --git a/tests/test_utilization_tracking.py b/tests/test_utilization_tracking.py index 3b0ab814f..18ded27d3 100644 --- a/tests/test_utilization_tracking.py +++ b/tests/test_utilization_tracking.py @@ -1,6 +1,7 @@ """ Tests for CPU, RAM, and GPU utilization tracking functionality. """ + import time import unittest from pathlib import Path @@ -11,7 +12,6 @@ from codecarbon.emissions_tracker import EmissionsTracker, OfflineEmissionsTracker from tests.testutils import get_custom_mock_open - empty_conf = "[codecarbon]" @@ -25,7 +25,7 @@ def setUp(self) -> None: self.temp_dir = tempfile.TemporaryDirectory() self.temp_path = Path(self.temp_dir.name) self.emissions_file_path = self.temp_path / "emissions.csv" - + # Patch config file access patcher = mock.patch( "builtins.open", new_callable=get_custom_mock_open(empty_conf, empty_conf) @@ -44,19 +44,19 @@ def test_utilization_fields_in_emissions_data(self): output_dir=self.temp_path, save_to_file=False, ) - + tracker.start() time.sleep(2) # Run for 2 seconds to collect measurements tracker.stop() - + emissions_data = tracker.final_emissions_data - + # Verify utilization fields exist - self.assertTrue(hasattr(emissions_data, 'cpu_utilization_percent')) - self.assertTrue(hasattr(emissions_data, 'gpu_utilization_percent')) - self.assertTrue(hasattr(emissions_data, 'ram_utilization_percent')) - self.assertTrue(hasattr(emissions_data, 'ram_used_gb')) - + self.assertTrue(hasattr(emissions_data, "cpu_utilization_percent")) + self.assertTrue(hasattr(emissions_data, "gpu_utilization_percent")) + self.assertTrue(hasattr(emissions_data, "ram_utilization_percent")) + self.assertTrue(hasattr(emissions_data, "ram_used_gb")) + # Verify values are reasonable self.assertGreaterEqual(emissions_data.cpu_utilization_percent, 0) self.assertLessEqual(emissions_data.cpu_utilization_percent, 100) @@ -72,25 +72,25 @@ def test_utilization_fields_in_csv_output(self): country_iso_code="USA", output_dir=self.temp_path, ) - + tracker.start() time.sleep(2) # Run for 2 seconds to collect measurements tracker.stop() - + # Read CSV and verify columns exist emissions_df = pd.read_csv(self.emissions_file_path) - - self.assertIn('cpu_utilization_percent', emissions_df.columns) - self.assertIn('gpu_utilization_percent', emissions_df.columns) - self.assertIn('ram_utilization_percent', emissions_df.columns) - self.assertIn('ram_used_gb', emissions_df.columns) - + + self.assertIn("cpu_utilization_percent", emissions_df.columns) + self.assertIn("gpu_utilization_percent", emissions_df.columns) + self.assertIn("ram_utilization_percent", emissions_df.columns) + self.assertIn("ram_used_gb", emissions_df.columns) + # Verify values are reasonable - cpu_util = emissions_df['cpu_utilization_percent'].values[0] - gpu_util = emissions_df['gpu_utilization_percent'].values[0] - ram_util = emissions_df['ram_utilization_percent'].values[0] - ram_used = emissions_df['ram_used_gb'].values[0] - + cpu_util = emissions_df["cpu_utilization_percent"].values[0] + gpu_util = emissions_df["gpu_utilization_percent"].values[0] + ram_util = emissions_df["ram_utilization_percent"].values[0] + ram_used = emissions_df["ram_used_gb"].values[0] + self.assertGreaterEqual(cpu_util, 0) self.assertLessEqual(cpu_util, 100) self.assertGreaterEqual(gpu_util, 0) @@ -106,19 +106,19 @@ def test_utilization_history_cleared_on_start(self): output_dir=self.temp_path, save_to_file=False, ) - + # First run tracker.start() time.sleep(1) tracker.stop() first_cpu_util = tracker.final_emissions_data.cpu_utilization_percent - + # Second run - history should be cleared tracker.start() time.sleep(1) tracker.stop() second_cpu_util = tracker.final_emissions_data.cpu_utilization_percent - + # Both should have valid values (not necessarily equal) self.assertGreaterEqual(first_cpu_util, 0) self.assertGreaterEqual(second_cpu_util, 0) @@ -126,29 +126,29 @@ def test_utilization_history_cleared_on_start(self): def test_utilization_averaging_over_time(self): """Test that utilization values are averaged over the tracking period.""" import psutil - + tracker = OfflineEmissionsTracker( country_iso_code="USA", output_dir=self.temp_path, save_to_file=False, ) - + # Get instantaneous value before starting - instant_before = psutil.cpu_percent() - + psutil.cpu_percent() + tracker.start() time.sleep(3) # Run for 3 seconds to collect multiple measurements tracker.stop() - + # Get instantaneous value after stopping - instant_after = psutil.cpu_percent() - + psutil.cpu_percent() + averaged = tracker.final_emissions_data.cpu_utilization_percent - + # Averaged value should be valid self.assertGreaterEqual(averaged, 0) self.assertLessEqual(averaged, 100) - + # The averaged value may differ from instantaneous values # This is expected behavior - we're just verifying it's computed @@ -159,19 +159,19 @@ def test_task_utilization_tracking(self): output_dir=self.temp_path, save_to_file=False, ) - + tracker.start() tracker.start_task("test_task") time.sleep(2) task_data = tracker.stop_task() tracker.stop() - + # Verify task data has utilization fields - self.assertTrue(hasattr(task_data, 'cpu_utilization_percent')) - self.assertTrue(hasattr(task_data, 'ram_utilization_percent')) - self.assertTrue(hasattr(task_data, 'ram_used_gb')) - self.assertTrue(hasattr(task_data, 'gpu_utilization_percent')) - + self.assertTrue(hasattr(task_data, "cpu_utilization_percent")) + self.assertTrue(hasattr(task_data, "ram_utilization_percent")) + self.assertTrue(hasattr(task_data, "ram_used_gb")) + self.assertTrue(hasattr(task_data, "gpu_utilization_percent")) + # Verify values are reasonable self.assertGreaterEqual(task_data.cpu_utilization_percent, 0) self.assertLessEqual(task_data.cpu_utilization_percent, 100) @@ -188,13 +188,13 @@ def test_utilization_with_empty_history(self): output_dir=self.temp_path, save_to_file=False, ) - + # Start and stop immediately (minimal time for history collection) tracker.start() tracker.stop() - + emissions_data = tracker.final_emissions_data - + # Should still have valid values (fallback to instantaneous) self.assertGreaterEqual(emissions_data.cpu_utilization_percent, 0) self.assertLessEqual(emissions_data.cpu_utilization_percent, 100) From 613e495c6756db26e0e6a79da2e1361414e6ce3c Mon Sep 17 00:00:00 2001 From: benoit-cty <4-benoit-cty@users.noreply.git.leximpact.dev> Date: Sun, 28 Dec 2025 12:28:06 +0100 Subject: [PATCH 09/11] Fix pre-commit --- test_utilization_tracking.py | 10 +++++----- tests/test_utilization_tracking.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test_utilization_tracking.py b/test_utilization_tracking.py index 8e82cced1..4b7f7ed02 100644 --- a/test_utilization_tracking.py +++ b/test_utilization_tracking.py @@ -25,7 +25,7 @@ def test_basic_tracking(): tracker.stop() emissions = tracker.final_emissions_data - print(f"\nResults:") + print("\nResults:") print(f" Duration: {emissions.duration:.2f} seconds") print(f" CPU Utilization: {emissions.cpu_utilization_percent:.2f}%") print(f" RAM Utilization: {emissions.ram_utilization_percent:.2f}%") @@ -59,7 +59,7 @@ def test_task_tracking(): task_emissions = tracker.stop_task() tracker.stop() - print(f"\nTask Results:") + print("\nTask Results:") print(f" Duration: {task_emissions.duration:.2f} seconds") print(f" CPU Utilization: {task_emissions.cpu_utilization_percent:.2f}%") print(f" RAM Utilization: {task_emissions.ram_utilization_percent:.2f}%") @@ -94,7 +94,7 @@ def test_averaging(): instant_cpu_start = psutil.cpu_percent() instant_ram_start = psutil.virtual_memory().percent - print(f"Instantaneous at start:") + print("Instantaneous at start:") print(f" CPU: {instant_cpu_start:.2f}%") print(f" RAM: {instant_ram_start:.2f}%") @@ -106,14 +106,14 @@ def test_averaging(): instant_cpu_end = psutil.cpu_percent() instant_ram_end = psutil.virtual_memory().percent - print(f"\nInstantaneous at end:") + print("\nInstantaneous at end:") print(f" CPU: {instant_cpu_end:.2f}%") print(f" RAM: {instant_ram_end:.2f}%") tracker.stop() emissions = tracker.final_emissions_data - print(f"\nAveraged over period:") + print("\nAveraged over period:") print(f" CPU: {emissions.cpu_utilization_percent:.2f}%") print(f" RAM: {emissions.ram_utilization_percent:.2f}%") diff --git a/tests/test_utilization_tracking.py b/tests/test_utilization_tracking.py index 18ded27d3..f182f8273 100644 --- a/tests/test_utilization_tracking.py +++ b/tests/test_utilization_tracking.py @@ -9,7 +9,7 @@ import pandas as pd -from codecarbon.emissions_tracker import EmissionsTracker, OfflineEmissionsTracker +from codecarbon.emissions_tracker import OfflineEmissionsTracker from tests.testutils import get_custom_mock_open empty_conf = "[codecarbon]" From 13c9b0bb7b89dbbf168f0746a2c263d44013b80e Mon Sep 17 00:00:00 2001 From: benoit-cty <4-benoit-cty@users.noreply.git.leximpact.dev> Date: Sun, 28 Dec 2025 22:01:21 +0100 Subject: [PATCH 10/11] Fix alembic migration chain --- .../alembic/versions/20251119_add_utilization_metrics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/carbonserver/carbonserver/database/alembic/versions/20251119_add_utilization_metrics.py b/carbonserver/carbonserver/database/alembic/versions/20251119_add_utilization_metrics.py index 6cf37c6d4..42cca9d84 100644 --- a/carbonserver/carbonserver/database/alembic/versions/20251119_add_utilization_metrics.py +++ b/carbonserver/carbonserver/database/alembic/versions/20251119_add_utilization_metrics.py @@ -1,7 +1,7 @@ """add_utilization_metrics_to_emissions Revision ID: 20251119_add_utilization -Revises: 202501_f3a10 +Revises: 3212895acafd Create Date: 2025-11-19 18:52:00.000000 """ @@ -11,7 +11,7 @@ # revision identifiers, used by Alembic. revision = "20251119_add_utilization" -down_revision = "f3a10c95079f" +down_revision = "3212895acafd" branch_labels = None depends_on = None From a2921331c6fff3bb905bf0770bcc9747dbc4e86b Mon Sep 17 00:00:00 2001 From: benoit-cty <4-benoit-cty@users.noreply.git.leximpact.dev> Date: Tue, 30 Dec 2025 21:25:37 +0100 Subject: [PATCH 11/11] cleaning --- test_gpu_monitoring.py | 54 ------------- test_utilization_tracking.py | 142 ----------------------------------- 2 files changed, 196 deletions(-) delete mode 100644 test_gpu_monitoring.py delete mode 100644 test_utilization_tracking.py diff --git a/test_gpu_monitoring.py b/test_gpu_monitoring.py deleted file mode 100644 index da715c077..000000000 --- a/test_gpu_monitoring.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple test script to verify GPU load monitoring functionality. -This script will run a simple workload and check if GPU utilization is being tracked. -""" - -import time - -from codecarbon import EmissionsTracker - - -def main(): - print("Starting GPU load monitoring test...") - print("=" * 60) - - # Initialize the tracker - tracker = EmissionsTracker( - project_name="gpu_load_test", - measure_power_secs=2, - save_to_file=True, - output_file="test_gpu_emissions.csv", - ) - - # Start tracking - tracker.start() - print("Tracker started. Running for 10 seconds...") - - # Run for a short duration to collect some metrics - time.sleep(10) - - # Stop tracking - emissions = tracker.stop() - - print("=" * 60) - print("Test completed!") - print(f"Total emissions: {emissions:.6f} kg CO2") - - # Check if GPU utilization was tracked - if hasattr(tracker, "final_emissions_data"): - data = tracker.final_emissions_data - print(f"GPU utilization: {data.gpu_utilization_percent:.2f}%") - print(f"CPU utilization: {data.cpu_utilization_percent:.2f}%") - print(f"RAM utilization: {data.ram_utilization_percent:.2f}%") - - if data.gpu_utilization_percent > 0: - print("\n✓ GPU utilization tracking is working!") - else: - print("\n⚠ GPU utilization is 0% (may not have GPU or no GPU workload)") - - print("\nCheck test_gpu_emissions.csv for detailed results.") - - -if __name__ == "__main__": - main() diff --git a/test_utilization_tracking.py b/test_utilization_tracking.py deleted file mode 100644 index 4b7f7ed02..000000000 --- a/test_utilization_tracking.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify CPU and RAM utilization tracking improvements. -This script tests that the metrics are collected and averaged correctly. -""" - -import time - -from codecarbon import EmissionsTracker - - -def test_basic_tracking(): - """Test basic emissions tracking with utilization metrics.""" - print("=" * 60) - print("Test 1: Basic Emissions Tracking") - print("=" * 60) - - tracker = EmissionsTracker() - tracker.start() - - # Run for a few seconds to collect multiple measurements - print("Running for 5 seconds to collect measurements...") - time.sleep(5) - - tracker.stop() - emissions = tracker.final_emissions_data - - print("\nResults:") - print(f" Duration: {emissions.duration:.2f} seconds") - print(f" CPU Utilization: {emissions.cpu_utilization_percent:.2f}%") - print(f" RAM Utilization: {emissions.ram_utilization_percent:.2f}%") - print(f" RAM Used: {emissions.ram_used_gb:.2f} GB") - print(f" Energy Consumed: {emissions.energy_consumed:.6f} kWh") - print(f" Emissions: {emissions.emissions:.6f} kg CO2eq") - - # Verify that metrics are reasonable - assert 0 <= emissions.cpu_utilization_percent <= 100, "CPU utilization out of range" - assert 0 <= emissions.ram_utilization_percent <= 100, "RAM utilization out of range" - assert emissions.ram_used_gb >= 0, "RAM used should be non-negative" - - print("\n✓ Test 1 passed!") - return emissions - - -def test_task_tracking(): - """Test task-based tracking with utilization metrics.""" - print("\n" + "=" * 60) - print("Test 2: Task-Based Tracking") - print("=" * 60) - - tracker = EmissionsTracker() - tracker.start() - - # Start a task - tracker.start_task("test_task") - print("Running task for 3 seconds...") - time.sleep(3) - - task_emissions = tracker.stop_task() - tracker.stop() - - print("\nTask Results:") - print(f" Duration: {task_emissions.duration:.2f} seconds") - print(f" CPU Utilization: {task_emissions.cpu_utilization_percent:.2f}%") - print(f" RAM Utilization: {task_emissions.ram_utilization_percent:.2f}%") - print(f" RAM Used: {task_emissions.ram_used_gb:.2f} GB") - print(f" Energy Consumed: {task_emissions.energy_consumed:.6f} kWh") - - # Verify that metrics are reasonable - assert ( - 0 <= task_emissions.cpu_utilization_percent <= 100 - ), "CPU utilization out of range" - assert ( - 0 <= task_emissions.ram_utilization_percent <= 100 - ), "RAM utilization out of range" - assert task_emissions.ram_used_gb >= 0, "RAM used should be non-negative" - - print("\n✓ Test 2 passed!") - return task_emissions - - -def test_averaging(): - """Test that averaging is working by comparing with instantaneous values.""" - print("\n" + "=" * 60) - print("Test 3: Verify Averaging vs Instantaneous") - print("=" * 60) - - import psutil - - tracker = EmissionsTracker() - tracker.start() - - # Collect instantaneous values at start - instant_cpu_start = psutil.cpu_percent() - instant_ram_start = psutil.virtual_memory().percent - - print("Instantaneous at start:") - print(f" CPU: {instant_cpu_start:.2f}%") - print(f" RAM: {instant_ram_start:.2f}%") - - # Run for several seconds - print("\nRunning for 5 seconds...") - time.sleep(5) - - # Collect instantaneous values at end - instant_cpu_end = psutil.cpu_percent() - instant_ram_end = psutil.virtual_memory().percent - - print("\nInstantaneous at end:") - print(f" CPU: {instant_cpu_end:.2f}%") - print(f" RAM: {instant_ram_end:.2f}%") - - tracker.stop() - emissions = tracker.final_emissions_data - - print("\nAveraged over period:") - print(f" CPU: {emissions.cpu_utilization_percent:.2f}%") - print(f" RAM: {emissions.ram_utilization_percent:.2f}%") - - # The averaged value should be between start and end (or close to them) - # This is a soft check since system load can vary - print("\n✓ Test 3 passed! (Averaging is working)") - return emissions - - -if __name__ == "__main__": - try: - # Run all tests - test_basic_tracking() - test_task_tracking() - test_averaging() - - print("\n" + "=" * 60) - print("All tests passed! ✓") - print("=" * 60) - - except Exception as e: - print(f"\n✗ Test failed with error: {e}") - import traceback - - traceback.print_exc() - exit(1)