codecarbon 3.2.0__tar.gz → 3.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codecarbon-3.2.0 → codecarbon-3.2.2}/PKG-INFO +18 -16
- {codecarbon-3.2.0 → codecarbon-3.2.2}/README.md +12 -5
- codecarbon-3.2.2/codecarbon/_version.py +1 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/cli/main.py +25 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/gpu.py +5 -1
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/util.py +6 -4
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/emissions_tracker.py +101 -12
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/input.py +80 -26
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/base_output.py +3 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/emissions_data.py +8 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/file.py +5 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/metrics/metric_docs.py +9 -4
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/metrics/prometheus.py +34 -3
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/carbonboard.py +29 -3
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon.egg-info/PKG-INFO +18 -16
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon.egg-info/SOURCES.txt +2 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon.egg-info/requires.txt +7 -14
- {codecarbon-3.2.0 → codecarbon-3.2.2}/pyproject.toml +9 -14
- codecarbon-3.2.2/tests/test_core_util.py +44 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_emissions_tracker.py +19 -0
- codecarbon-3.2.2/tests/test_input.py +98 -0
- codecarbon-3.2.2/tests/test_utilization_tracking.py +206 -0
- codecarbon-3.2.0/codecarbon/_version.py +0 -1
- codecarbon-3.2.0/tests/test_core_util.py +0 -17
- {codecarbon-3.2.0 → codecarbon-3.2.2}/LICENSE +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/__init__.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/cli/__init__.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/cli/cli_utils.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/__init__.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/api_client.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/cloud.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/config.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/cpu.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/electricitymaps_api.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/emissions.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/measure.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/powermetrics.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/rapl.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/resource_tracker.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/schemas.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/units.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/canada_provinces.geojson +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/cloud/impact.csv +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/hardware/cpu_dataset_builder/amd_cpu_scrapper.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/hardware/cpu_dataset_builder/intel_cpu_scrapper.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/hardware/cpu_dataset_builder/merge_scrapped_cpu_power.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/hardware/cpu_power.csv +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/private_infra/2016/usa_emissions.json +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/private_infra/2023/canada_energy_mix.json +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/private_infra/carbon_intensity_per_source.json +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/private_infra/global_energy_mix.json +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/__init__.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/geography.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/hardware.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/logger.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/ram.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/scheduler.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/task.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/lock.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/__init__.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/http.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/logger.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/metrics/__init__.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/metrics/logfire.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/__init__.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/assets/__init__.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/assets/car_icon.png +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/assets/house_icon.png +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/assets/tv_icon.png +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/carbonboard_on_api.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/components.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/data.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/units.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon.egg-info/dependency_links.txt +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon.egg-info/entry_points.txt +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon.egg-info/top_level.txt +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/setup.cfg +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_api_call.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_cli.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_cloud.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_config.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_cpu.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_cpu_load.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_custom_handler.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_electricitymaps_api.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_electricitymaps_backward_compatibility.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_electricitymaps_config_backward_compatibility.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_emissions.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_emissions_tracker_constant.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_emissions_tracker_flush.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_energy.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_geography.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_gpu.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_lock.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_logging_output.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_offline_emissions_tracker.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_package_integrity.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_powermetrics.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_ram.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_rapl_mmio_scanning.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_rapl_parameters.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_rapl_permissions.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_tracking_inference.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_unsupported_gpu.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_viz_data.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_viz_units.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/testdata.py +0 -0
- {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/testutils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecarbon
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Author: Mila, DataForGood, BCG GAMMA, Comet.ml, Haverford College
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://codecarbon.io/
|
|
@@ -36,20 +36,14 @@ Requires-Dist: requests
|
|
|
36
36
|
Requires-Dist: questionary
|
|
37
37
|
Requires-Dist: rich
|
|
38
38
|
Requires-Dist: typer
|
|
39
|
+
Provides-Extra: carbonboard
|
|
40
|
+
Requires-Dist: dash; extra == "carbonboard"
|
|
41
|
+
Requires-Dist: dash_bootstrap_components>1.0.0; extra == "carbonboard"
|
|
42
|
+
Requires-Dist: fire; extra == "carbonboard"
|
|
39
43
|
Provides-Extra: viz-legacy
|
|
40
44
|
Requires-Dist: dash; extra == "viz-legacy"
|
|
41
45
|
Requires-Dist: dash_bootstrap_components>1.0.0; extra == "viz-legacy"
|
|
42
46
|
Requires-Dist: fire; extra == "viz-legacy"
|
|
43
|
-
Provides-Extra: viz
|
|
44
|
-
Requires-Dist: mock; extra == "viz"
|
|
45
|
-
Requires-Dist: pytest; extra == "viz"
|
|
46
|
-
Requires-Dist: responses; extra == "viz"
|
|
47
|
-
Requires-Dist: numpy<2.0.0; python_version < "3.9" and extra == "viz"
|
|
48
|
-
Requires-Dist: numpy; python_version >= "3.9" and extra == "viz"
|
|
49
|
-
Requires-Dist: psutil; extra == "viz"
|
|
50
|
-
Requires-Dist: requests-mock; extra == "viz"
|
|
51
|
-
Requires-Dist: rapidfuzz; extra == "viz"
|
|
52
|
-
Requires-Dist: importlib_resources; python_version < "3.9" and extra == "viz"
|
|
53
47
|
Provides-Extra: api
|
|
54
48
|
Requires-Dist: alembic<2.0.0; extra == "api"
|
|
55
49
|
Requires-Dist: bcrypt<5.0.0; extra == "api"
|
|
@@ -67,6 +61,7 @@ Requires-Dist: fastapi-pagination<1.0.0; extra == "api"
|
|
|
67
61
|
Requires-Dist: pytest; extra == "api"
|
|
68
62
|
Requires-Dist: mock; extra == "api"
|
|
69
63
|
Requires-Dist: responses; extra == "api"
|
|
64
|
+
Requires-Dist: fastapi-oidc; python_version >= "3.10" and extra == "api"
|
|
70
65
|
Requires-Dist: numpy; extra == "api"
|
|
71
66
|
Requires-Dist: psutil; extra == "api"
|
|
72
67
|
Requires-Dist: requests-mock; extra == "api"
|
|
@@ -90,8 +85,6 @@ CodeCarbon websites:
|
|
|
90
85
|
|
|
91
86
|
<br/>
|
|
92
87
|
|
|
93
|
-
[](https://anaconda.org/conda-forge/codecarbon)
|
|
94
|
-
[](https://anaconda.org/codecarbon/codecarbon)
|
|
95
88
|
[](https://pypi.org/project/codecarbon/)
|
|
96
89
|
[](https://zenodo.org/badge/latestdoi/263364731)
|
|
97
90
|
<!-- [](https://pepy.tech/project/codecarbon) -->
|
|
@@ -140,10 +133,13 @@ Our hope is that this package will be used widely for estimating the carbon foot
|
|
|
140
133
|
pip install codecarbon
|
|
141
134
|
```
|
|
142
135
|
|
|
143
|
-
**
|
|
144
|
-
|
|
145
|
-
|
|
136
|
+
**Using Conda environments**
|
|
137
|
+
If you're using Conda, you can install CodeCarbon with pip in your Conda environment:
|
|
138
|
+
```bash
|
|
139
|
+
conda activate your_env
|
|
140
|
+
pip install codecarbon
|
|
146
141
|
```
|
|
142
|
+
|
|
147
143
|
To see more installation options please refer to the documentation: [**Installation**](https://mlco2.github.io/codecarbon/installation.html#)
|
|
148
144
|
|
|
149
145
|
## Start to estimate your impact 📏
|
|
@@ -183,6 +179,12 @@ In your command prompt use:
|
|
|
183
179
|
```codecarbon monitor```
|
|
184
180
|
The package will track your emissions independently from your code.
|
|
185
181
|
|
|
182
|
+
### Detecting your hardware 🔍
|
|
183
|
+
|
|
184
|
+
In your command prompt use:
|
|
185
|
+
```codecarbon detect```
|
|
186
|
+
The package will detect and print your hardware information (RAM, CPU, GPU).
|
|
187
|
+
|
|
186
188
|
### In your Python code 🐍
|
|
187
189
|
```python
|
|
188
190
|
from codecarbon import track_emissions
|
|
@@ -13,8 +13,6 @@ CodeCarbon websites:
|
|
|
13
13
|
|
|
14
14
|
<br/>
|
|
15
15
|
|
|
16
|
-
[](https://anaconda.org/conda-forge/codecarbon)
|
|
17
|
-
[](https://anaconda.org/codecarbon/codecarbon)
|
|
18
16
|
[](https://pypi.org/project/codecarbon/)
|
|
19
17
|
[](https://zenodo.org/badge/latestdoi/263364731)
|
|
20
18
|
<!-- [](https://pepy.tech/project/codecarbon) -->
|
|
@@ -63,10 +61,13 @@ Our hope is that this package will be used widely for estimating the carbon foot
|
|
|
63
61
|
pip install codecarbon
|
|
64
62
|
```
|
|
65
63
|
|
|
66
|
-
**
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
**Using Conda environments**
|
|
65
|
+
If you're using Conda, you can install CodeCarbon with pip in your Conda environment:
|
|
66
|
+
```bash
|
|
67
|
+
conda activate your_env
|
|
68
|
+
pip install codecarbon
|
|
69
69
|
```
|
|
70
|
+
|
|
70
71
|
To see more installation options please refer to the documentation: [**Installation**](https://mlco2.github.io/codecarbon/installation.html#)
|
|
71
72
|
|
|
72
73
|
## Start to estimate your impact 📏
|
|
@@ -106,6 +107,12 @@ In your command prompt use:
|
|
|
106
107
|
```codecarbon monitor```
|
|
107
108
|
The package will track your emissions independently from your code.
|
|
108
109
|
|
|
110
|
+
### Detecting your hardware 🔍
|
|
111
|
+
|
|
112
|
+
In your command prompt use:
|
|
113
|
+
```codecarbon detect```
|
|
114
|
+
The package will detect and print your hardware information (RAM, CPU, GPU).
|
|
115
|
+
|
|
109
116
|
### In your Python code 🐍
|
|
110
117
|
```python
|
|
111
118
|
from codecarbon import track_emissions
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.2.2"
|
|
@@ -413,6 +413,31 @@ def monitor(
|
|
|
413
413
|
raise e
|
|
414
414
|
|
|
415
415
|
|
|
416
|
+
@codecarbon.command("detect", short_help="Detect hardware and print information.")
|
|
417
|
+
def detect():
|
|
418
|
+
"""
|
|
419
|
+
Detects hardware and prints information without running any measurements.
|
|
420
|
+
"""
|
|
421
|
+
print("Detecting hardware...")
|
|
422
|
+
tracker = EmissionsTracker(save_to_file=False)
|
|
423
|
+
hardware_info = tracker.get_detected_hardware()
|
|
424
|
+
|
|
425
|
+
print("\nDetected Hardware and System Information:")
|
|
426
|
+
print(f"- Available RAM: {hardware_info['ram_total_size']:.3f} GB")
|
|
427
|
+
print(
|
|
428
|
+
f"- CPU count: {hardware_info['cpu_count']} thread(s) in {hardware_info['cpu_physical_count']} physical CPU(s)"
|
|
429
|
+
)
|
|
430
|
+
print(f"- CPU model: {hardware_info['cpu_model']}")
|
|
431
|
+
print(f"- GPU count: {hardware_info['gpu_count']}")
|
|
432
|
+
|
|
433
|
+
gpu_model_str = hardware_info["gpu_model"]
|
|
434
|
+
if hardware_info.get("gpu_ids"):
|
|
435
|
+
gpu_model_str += (
|
|
436
|
+
f" BUT only tracking these GPU ids : {hardware_info['gpu_ids']}"
|
|
437
|
+
)
|
|
438
|
+
print(f"- GPU model: {gpu_model_str}")
|
|
439
|
+
|
|
440
|
+
|
|
416
441
|
def questionary_prompt(prompt, list_options, default):
|
|
417
442
|
value = questionary.select(
|
|
418
443
|
prompt,
|
|
@@ -141,7 +141,11 @@ class GPUDevice:
|
|
|
141
141
|
"""Returns memory info in bytes
|
|
142
142
|
https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceQueries.html#group__nvmlDeviceQueries_1g2dfeb1db82aa1de91aa6edf941c85ca8
|
|
143
143
|
"""
|
|
144
|
-
|
|
144
|
+
try:
|
|
145
|
+
return pynvml.nvmlDeviceGetMemoryInfo(self.handle)
|
|
146
|
+
except pynvml.NVMLError_NotSupported:
|
|
147
|
+
# error thrown for the NVIDIA Blackwell GPU of DGX Spark, due to memory sharing -> return defaults instead
|
|
148
|
+
return pynvml.c_nvmlMemory_t(-1, -1, -1)
|
|
145
149
|
|
|
146
150
|
def _get_temperature(self) -> int:
|
|
147
151
|
"""Returns degrees in the Celsius scale
|
|
@@ -3,6 +3,7 @@ import re
|
|
|
3
3
|
import subprocess
|
|
4
4
|
import sys
|
|
5
5
|
from contextlib import contextmanager
|
|
6
|
+
from functools import lru_cache
|
|
6
7
|
from os.path import expandvars
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Optional, Union
|
|
@@ -73,7 +74,8 @@ def backup(file_path: Union[str, Path], ext: Optional[str] = ".bak") -> None:
|
|
|
73
74
|
file_path.rename(backup_path)
|
|
74
75
|
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
@lru_cache(maxsize=1)
|
|
78
|
+
def detect_cpu_model() -> Optional[str]:
|
|
77
79
|
cpu_info = cpuinfo.get_cpu_info()
|
|
78
80
|
if cpu_info:
|
|
79
81
|
cpu_model_detected = cpu_info.get("brand_raw", "")
|
|
@@ -81,17 +83,17 @@ def detect_cpu_model() -> str:
|
|
|
81
83
|
return None
|
|
82
84
|
|
|
83
85
|
|
|
84
|
-
def is_mac_os() ->
|
|
86
|
+
def is_mac_os() -> bool:
|
|
85
87
|
system = sys.platform.lower()
|
|
86
88
|
return system.startswith("dar")
|
|
87
89
|
|
|
88
90
|
|
|
89
|
-
def is_windows_os() ->
|
|
91
|
+
def is_windows_os() -> bool:
|
|
90
92
|
system = sys.platform.lower()
|
|
91
93
|
return system.startswith("win")
|
|
92
94
|
|
|
93
95
|
|
|
94
|
-
def is_linux_os() ->
|
|
96
|
+
def is_linux_os() -> bool:
|
|
95
97
|
system = sys.platform.lower()
|
|
96
98
|
return system.startswith("lin")
|
|
97
99
|
|
|
@@ -6,6 +6,7 @@ OfflineEmissionsTracker, context manager and decorator @track_emissions
|
|
|
6
6
|
import dataclasses
|
|
7
7
|
import os
|
|
8
8
|
import platform
|
|
9
|
+
import re
|
|
9
10
|
import time
|
|
10
11
|
import uuid
|
|
11
12
|
from abc import ABC, abstractmethod
|
|
@@ -13,6 +14,8 @@ from datetime import datetime
|
|
|
13
14
|
from functools import wraps
|
|
14
15
|
from typing import Any, Callable, Dict, List, Optional, Union
|
|
15
16
|
|
|
17
|
+
import psutil
|
|
18
|
+
|
|
16
19
|
from codecarbon._version import __version__
|
|
17
20
|
from codecarbon.core.config import get_hierarchical_config
|
|
18
21
|
from codecarbon.core.emissions import Emissions
|
|
@@ -181,8 +184,8 @@ class BaseEmissionsTracker(ABC):
|
|
|
181
184
|
logger_preamble: Optional[str] = _sentinel,
|
|
182
185
|
force_cpu_power: Optional[int] = _sentinel,
|
|
183
186
|
force_ram_power: Optional[int] = _sentinel,
|
|
184
|
-
pue: Optional[
|
|
185
|
-
wue: Optional[
|
|
187
|
+
pue: Optional[float] = _sentinel,
|
|
188
|
+
wue: Optional[float] = _sentinel,
|
|
186
189
|
force_mode_cpu_load: Optional[bool] = _sentinel,
|
|
187
190
|
allow_multiple_runs: Optional[bool] = _sentinel,
|
|
188
191
|
rapl_include_dram: Optional[bool] = _sentinel,
|
|
@@ -335,6 +338,11 @@ class BaseEmissionsTracker(ABC):
|
|
|
335
338
|
self._last_measured_time: float = time.perf_counter()
|
|
336
339
|
self._total_energy: Energy = Energy.from_energy(kWh=0)
|
|
337
340
|
self._total_water: Water = Water.from_litres(litres=0)
|
|
341
|
+
# CPU and RAM utilization tracking
|
|
342
|
+
self._cpu_utilization_history: List[float] = []
|
|
343
|
+
self._gpu_utilization_history: List[float] = []
|
|
344
|
+
self._ram_utilization_history: List[float] = []
|
|
345
|
+
self._ram_used_history: List[float] = []
|
|
338
346
|
self._total_cpu_energy: Energy = Energy.from_energy(kWh=0)
|
|
339
347
|
self._total_gpu_energy: Energy = Energy.from_energy(kWh=0)
|
|
340
348
|
self._total_ram_energy: Energy = Energy.from_energy(kWh=0)
|
|
@@ -361,8 +369,9 @@ class BaseEmissionsTracker(ABC):
|
|
|
361
369
|
self._active_task_emissions_at_start: Optional[EmissionsData] = None
|
|
362
370
|
|
|
363
371
|
# Tracking mode detection
|
|
364
|
-
|
|
365
|
-
|
|
372
|
+
self._hardware = []
|
|
373
|
+
resource_tracker = ResourceTracker(self)
|
|
374
|
+
resource_tracker.set_CPU_GPU_ram_tracking()
|
|
366
375
|
|
|
367
376
|
self._conf["hardware"] = list(map(lambda x: x.description(), self._hardware))
|
|
368
377
|
|
|
@@ -370,18 +379,20 @@ class BaseEmissionsTracker(ABC):
|
|
|
370
379
|
logger.info(f" Platform system: {self._conf.get('os')}")
|
|
371
380
|
logger.info(f" Python version: {self._conf.get('python_version')}")
|
|
372
381
|
logger.info(f" CodeCarbon version: {self._conf.get('codecarbon_version')}")
|
|
373
|
-
|
|
382
|
+
|
|
383
|
+
hardware_info = self.get_detected_hardware()
|
|
384
|
+
logger.info(f" Available RAM : {hardware_info['ram_total_size']:.3f} GB")
|
|
374
385
|
logger.info(
|
|
375
|
-
f" CPU count: {
|
|
386
|
+
f" CPU count: {hardware_info['cpu_count']} thread(s) in {hardware_info['cpu_physical_count']} physical CPU(s)"
|
|
376
387
|
)
|
|
377
|
-
logger.info(f" CPU model: {
|
|
378
|
-
logger.info(f" GPU count: {
|
|
388
|
+
logger.info(f" CPU model: {hardware_info['cpu_model']}")
|
|
389
|
+
logger.info(f" GPU count: {hardware_info['gpu_count']}")
|
|
379
390
|
if self._gpu_ids:
|
|
380
391
|
logger.info(
|
|
381
|
-
f" GPU model: {
|
|
392
|
+
f" GPU model: {hardware_info['gpu_model']} BUT only tracking these GPU ids : {hardware_info['gpu_ids']}"
|
|
382
393
|
)
|
|
383
394
|
else:
|
|
384
|
-
logger.info(f" GPU model: {
|
|
395
|
+
logger.info(f" GPU model: {hardware_info['gpu_model']}")
|
|
385
396
|
|
|
386
397
|
# Run `self._measure_power_and_energy` every `measure_power_secs` seconds in a
|
|
387
398
|
# background thread
|
|
@@ -447,11 +458,36 @@ class BaseEmissionsTracker(ABC):
|
|
|
447
458
|
self.run_id = uuid.uuid4()
|
|
448
459
|
|
|
449
460
|
if self._save_to_prometheus:
|
|
450
|
-
self._output_handlers.append(
|
|
461
|
+
self._output_handlers.append(
|
|
462
|
+
PrometheusOutput(
|
|
463
|
+
self._prometheus_url,
|
|
464
|
+
job_name=re.sub(
|
|
465
|
+
r"[^a-zA-Z0-9_-]",
|
|
466
|
+
"_",
|
|
467
|
+
f"{self._project_name}_{self._experiment_name}",
|
|
468
|
+
),
|
|
469
|
+
)
|
|
470
|
+
)
|
|
451
471
|
|
|
452
472
|
if self._save_to_logfire:
|
|
453
473
|
self._output_handlers.append(LogfireOutput())
|
|
454
474
|
|
|
475
|
+
def get_detected_hardware(self) -> Dict[str, Any]:
|
|
476
|
+
"""
|
|
477
|
+
Get the detected hardware.
|
|
478
|
+
:return: A dictionary containing hardware data.
|
|
479
|
+
"""
|
|
480
|
+
hardware_info = {
|
|
481
|
+
"ram_total_size": self._conf.get("ram_total_size"),
|
|
482
|
+
"cpu_count": self._conf.get("cpu_count"),
|
|
483
|
+
"cpu_physical_count": self._conf.get("cpu_physical_count"),
|
|
484
|
+
"cpu_model": self._conf.get("cpu_model"),
|
|
485
|
+
"gpu_count": self._conf.get("gpu_count"),
|
|
486
|
+
"gpu_model": self._conf.get("gpu_model"),
|
|
487
|
+
"gpu_ids": self._conf.get("gpu_ids"),
|
|
488
|
+
}
|
|
489
|
+
return hardware_info
|
|
490
|
+
|
|
455
491
|
def service_shutdown(self, signum, frame):
|
|
456
492
|
logger.warning("service_shutdown - Caught signal %d" % signum)
|
|
457
493
|
self.stop()
|
|
@@ -482,6 +518,13 @@ class BaseEmissionsTracker(ABC):
|
|
|
482
518
|
return
|
|
483
519
|
|
|
484
520
|
self._last_measured_time = self._start_time = time.perf_counter()
|
|
521
|
+
|
|
522
|
+
# Clear utilization history for fresh measurements
|
|
523
|
+
self._cpu_utilization_history.clear()
|
|
524
|
+
self._ram_utilization_history.clear()
|
|
525
|
+
self._ram_used_history.clear()
|
|
526
|
+
self._gpu_utilization_history.clear()
|
|
527
|
+
|
|
485
528
|
# Read initial energy for hardware
|
|
486
529
|
for hardware in self._hardware:
|
|
487
530
|
hardware.start()
|
|
@@ -525,6 +568,13 @@ class BaseEmissionsTracker(ABC):
|
|
|
525
568
|
if task_name in self._tasks.keys():
|
|
526
569
|
task_name += "_" + uuid.uuid4().__str__()
|
|
527
570
|
self._last_measured_time = self._start_time = time.perf_counter()
|
|
571
|
+
|
|
572
|
+
# Clear utilization history for fresh measurements
|
|
573
|
+
self._cpu_utilization_history.clear()
|
|
574
|
+
self._ram_utilization_history.clear()
|
|
575
|
+
self._ram_used_history.clear()
|
|
576
|
+
self._gpu_utilization_history.clear()
|
|
577
|
+
|
|
528
578
|
# Read initial energy for hardware
|
|
529
579
|
for hardware in self._hardware:
|
|
530
580
|
hardware.start()
|
|
@@ -686,6 +736,10 @@ class BaseEmissionsTracker(ABC):
|
|
|
686
736
|
|
|
687
737
|
self.final_emissions_data = emissions_data
|
|
688
738
|
self.final_emissions = emissions_data.emissions
|
|
739
|
+
|
|
740
|
+
for handler in self._output_handlers:
|
|
741
|
+
handler.exit()
|
|
742
|
+
|
|
689
743
|
return emissions_data.emissions
|
|
690
744
|
|
|
691
745
|
def _persist_data(
|
|
@@ -782,6 +836,26 @@ class BaseEmissionsTracker(ABC):
|
|
|
782
836
|
duration=duration.seconds,
|
|
783
837
|
emissions=emissions, # kg
|
|
784
838
|
emissions_rate=emissions / duration.seconds, # kg/s
|
|
839
|
+
cpu_utilization_percent=(
|
|
840
|
+
sum(self._cpu_utilization_history) / len(self._cpu_utilization_history)
|
|
841
|
+
if self._cpu_utilization_history
|
|
842
|
+
else 0
|
|
843
|
+
),
|
|
844
|
+
gpu_utilization_percent=(
|
|
845
|
+
sum(self._gpu_utilization_history) / len(self._gpu_utilization_history)
|
|
846
|
+
if self._gpu_utilization_history
|
|
847
|
+
else 0
|
|
848
|
+
),
|
|
849
|
+
ram_utilization_percent=(
|
|
850
|
+
sum(self._ram_utilization_history) / len(self._ram_utilization_history)
|
|
851
|
+
if self._ram_utilization_history
|
|
852
|
+
else 0
|
|
853
|
+
),
|
|
854
|
+
ram_used_gb=(
|
|
855
|
+
sum(self._ram_used_history) / len(self._ram_used_history)
|
|
856
|
+
if self._ram_used_history
|
|
857
|
+
else 0
|
|
858
|
+
),
|
|
785
859
|
cpu_power=avg_cpu_power,
|
|
786
860
|
gpu_power=avg_gpu_power,
|
|
787
861
|
ram_power=avg_ram_power,
|
|
@@ -855,6 +929,21 @@ class BaseEmissionsTracker(ABC):
|
|
|
855
929
|
if isinstance(hardware, CPU):
|
|
856
930
|
hardware.monitor_power()
|
|
857
931
|
|
|
932
|
+
# Collect CPU and RAM utilization metrics
|
|
933
|
+
self._cpu_utilization_history.append(psutil.cpu_percent())
|
|
934
|
+
self._ram_utilization_history.append(psutil.virtual_memory().percent)
|
|
935
|
+
self._ram_used_history.append(psutil.virtual_memory().used / (1024**3))
|
|
936
|
+
|
|
937
|
+
# Collect GPU utilization metrics
|
|
938
|
+
for hardware in self._hardware:
|
|
939
|
+
if isinstance(hardware, GPU):
|
|
940
|
+
gpu_details = hardware.devices.get_gpu_details()
|
|
941
|
+
for gpu_detail in gpu_details:
|
|
942
|
+
if "gpu_utilization" in gpu_detail:
|
|
943
|
+
self._gpu_utilization_history.append(
|
|
944
|
+
gpu_detail["gpu_utilization"]
|
|
945
|
+
)
|
|
946
|
+
|
|
858
947
|
def _do_measurements(self) -> None:
|
|
859
948
|
for hardware in self._hardware:
|
|
860
949
|
h_time = time.perf_counter()
|
|
@@ -1174,7 +1263,7 @@ def track_emissions(
|
|
|
1174
1263
|
country_2letter_iso_code: Optional[str] = _sentinel,
|
|
1175
1264
|
force_cpu_power: Optional[int] = _sentinel,
|
|
1176
1265
|
force_ram_power: Optional[int] = _sentinel,
|
|
1177
|
-
pue: Optional[
|
|
1266
|
+
pue: Optional[float] = _sentinel,
|
|
1178
1267
|
wue: Optional[float] = _sentinel,
|
|
1179
1268
|
allow_multiple_runs: Optional[bool] = _sentinel,
|
|
1180
1269
|
rapl_include_dram: Optional[bool] = _sentinel,
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
"""
|
|
2
|
-
App configuration
|
|
2
|
+
App configuration and static reference data loading.
|
|
3
|
+
|
|
4
|
+
Data files are static reference data that never change during runtime.
|
|
5
|
+
They are loaded once at module import to avoid repeated file I/O on the hot path
|
|
6
|
+
(start_task/stop_task calls for instance).
|
|
3
7
|
"""
|
|
4
8
|
|
|
5
9
|
import atexit
|
|
6
10
|
import json
|
|
7
11
|
import sys
|
|
8
12
|
from contextlib import ExitStack
|
|
9
|
-
from typing import Dict
|
|
13
|
+
from typing import Any, Dict
|
|
10
14
|
|
|
11
15
|
import pandas as pd
|
|
12
16
|
|
|
@@ -18,6 +22,49 @@ else:
|
|
|
18
22
|
from importlib_resources import files as importlib_resources_files
|
|
19
23
|
|
|
20
24
|
|
|
25
|
+
_CACHE: Dict[str, Any] = {}
|
|
26
|
+
_MODULE_NAME = "codecarbon"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_resource_path(filepath: str):
|
|
30
|
+
"""Get filesystem path to a package resource file."""
|
|
31
|
+
file_manager = ExitStack()
|
|
32
|
+
atexit.register(file_manager.close)
|
|
33
|
+
ref = importlib_resources_files(_MODULE_NAME).joinpath(filepath)
|
|
34
|
+
path = file_manager.enter_context(importlib_resources_as_file(ref))
|
|
35
|
+
return path
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _load_static_data() -> None:
|
|
39
|
+
"""
|
|
40
|
+
Load all static reference data at module import.
|
|
41
|
+
|
|
42
|
+
Called once when codecarbon is imported. All data loaded here
|
|
43
|
+
is immutable and shared across all tracker instances.
|
|
44
|
+
"""
|
|
45
|
+
# Global energy mix - used for emissions calculations
|
|
46
|
+
path = _get_resource_path("data/private_infra/global_energy_mix.json")
|
|
47
|
+
with open(path) as f:
|
|
48
|
+
_CACHE["global_energy_mix"] = json.load(f)
|
|
49
|
+
|
|
50
|
+
# Cloud emissions data
|
|
51
|
+
path = _get_resource_path("data/cloud/impact.csv")
|
|
52
|
+
_CACHE["cloud_emissions"] = pd.read_csv(path)
|
|
53
|
+
|
|
54
|
+
# Carbon intensity per source
|
|
55
|
+
path = _get_resource_path("data/private_infra/carbon_intensity_per_source.json")
|
|
56
|
+
with open(path) as f:
|
|
57
|
+
_CACHE["carbon_intensity_per_source"] = json.load(f)
|
|
58
|
+
|
|
59
|
+
# CPU power data
|
|
60
|
+
path = _get_resource_path("data/hardware/cpu_power.csv")
|
|
61
|
+
_CACHE["cpu_power"] = pd.read_csv(path)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Load static data at module import
|
|
65
|
+
_load_static_data()
|
|
66
|
+
|
|
67
|
+
|
|
21
68
|
class DataSource:
|
|
22
69
|
def __init__(self):
|
|
23
70
|
self.config = {
|
|
@@ -84,56 +131,63 @@ class DataSource:
|
|
|
84
131
|
|
|
85
132
|
def get_global_energy_mix_data(self) -> Dict:
|
|
86
133
|
"""
|
|
87
|
-
Returns Global Energy Mix Data
|
|
134
|
+
Returns Global Energy Mix Data.
|
|
135
|
+
Data is pre-loaded at module import for performance.
|
|
88
136
|
"""
|
|
89
|
-
|
|
90
|
-
global_energy_mix: Dict = json.load(f)
|
|
91
|
-
return global_energy_mix
|
|
137
|
+
return _CACHE["global_energy_mix"]
|
|
92
138
|
|
|
93
139
|
def get_cloud_emissions_data(self) -> pd.DataFrame:
|
|
94
140
|
"""
|
|
95
|
-
Returns Cloud Regions Impact Data
|
|
141
|
+
Returns Cloud Regions Impact Data.
|
|
142
|
+
Data is pre-loaded at module import for performance.
|
|
96
143
|
"""
|
|
97
|
-
return
|
|
144
|
+
return _CACHE["cloud_emissions"]
|
|
98
145
|
|
|
99
146
|
def get_country_emissions_data(self, country_iso_code: str) -> Dict:
|
|
100
147
|
"""
|
|
101
|
-
Returns Emissions Across Regions in a country
|
|
148
|
+
Returns Emissions Across Regions in a country.
|
|
149
|
+
Data is cached on first access per country.
|
|
150
|
+
|
|
102
151
|
:param country_iso_code: ISO code similar to one used in file names
|
|
103
152
|
:return: emissions in lbs/MWh and region code
|
|
104
153
|
"""
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
154
|
+
cache_key = f"country_emissions_{country_iso_code}"
|
|
155
|
+
if cache_key not in _CACHE:
|
|
156
|
+
try:
|
|
157
|
+
with open(self.country_emissions_data_path(country_iso_code)) as f:
|
|
158
|
+
_CACHE[cache_key] = json.load(f)
|
|
159
|
+
except KeyError:
|
|
160
|
+
# KeyError raised when there is no data path specified for the country
|
|
161
|
+
raise DataSourceException
|
|
162
|
+
return _CACHE[cache_key]
|
|
113
163
|
|
|
114
164
|
def get_country_energy_mix_data(self, country_iso_code: str) -> Dict:
|
|
115
165
|
"""
|
|
116
|
-
Returns Energy Mix Across Regions in a country
|
|
166
|
+
Returns Energy Mix Across Regions in a country.
|
|
167
|
+
Data is cached on first access per country.
|
|
168
|
+
|
|
117
169
|
:param country_iso_code: ISO code similar to one used in file names
|
|
118
170
|
:return: energy mix by region code
|
|
119
171
|
"""
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
172
|
+
cache_key = f"country_energy_mix_{country_iso_code}"
|
|
173
|
+
if cache_key not in _CACHE:
|
|
174
|
+
with open(self.country_energy_mix_data_path(country_iso_code)) as f:
|
|
175
|
+
_CACHE[cache_key] = json.load(f)
|
|
176
|
+
return _CACHE[cache_key]
|
|
123
177
|
|
|
124
178
|
def get_carbon_intensity_per_source_data(self) -> Dict:
|
|
125
179
|
"""
|
|
126
180
|
Returns Carbon intensity per source. In gCO2.eq/kWh.
|
|
181
|
+
Data is pre-loaded at module import for performance.
|
|
127
182
|
"""
|
|
128
|
-
|
|
129
|
-
carbon_intensity_per_source: Dict = json.load(f)
|
|
130
|
-
return carbon_intensity_per_source
|
|
183
|
+
return _CACHE["carbon_intensity_per_source"]
|
|
131
184
|
|
|
132
185
|
def get_cpu_power_data(self) -> pd.DataFrame:
|
|
133
186
|
"""
|
|
134
|
-
Returns CPU power Data
|
|
187
|
+
Returns CPU power Data.
|
|
188
|
+
Data is pre-loaded at module import for performance.
|
|
135
189
|
"""
|
|
136
|
-
return
|
|
190
|
+
return _CACHE["cpu_power"]
|
|
137
191
|
|
|
138
192
|
|
|
139
193
|
class DataSourceException(Exception):
|
|
@@ -40,6 +40,10 @@ class EmissionsData:
|
|
|
40
40
|
latitude: float
|
|
41
41
|
ram_total_size: float
|
|
42
42
|
tracking_mode: str
|
|
43
|
+
cpu_utilization_percent: float = 0.0
|
|
44
|
+
gpu_utilization_percent: float = 0.0
|
|
45
|
+
ram_utilization_percent: float = 0.0
|
|
46
|
+
ram_used_gb: float = 0.0
|
|
43
47
|
on_cloud: str = "N"
|
|
44
48
|
pue: float = 1
|
|
45
49
|
wue: float = 0
|
|
@@ -101,6 +105,10 @@ class TaskEmissionsData:
|
|
|
101
105
|
latitude: float
|
|
102
106
|
ram_total_size: float
|
|
103
107
|
tracking_mode: str
|
|
108
|
+
cpu_utilization_percent: float = 0.0
|
|
109
|
+
gpu_utilization_percent: float = 0.0
|
|
110
|
+
ram_utilization_percent: float = 0.0
|
|
111
|
+
ram_used_gb: float = 0.0
|
|
104
112
|
on_cloud: str = "N"
|
|
105
113
|
|
|
106
114
|
@property
|
|
@@ -88,6 +88,11 @@ class FileOutput(BaseOutput):
|
|
|
88
88
|
|
|
89
89
|
"""
|
|
90
90
|
file_exists: bool = os.path.isfile(self.save_file_path)
|
|
91
|
+
if file_exists and os.path.getsize(self.save_file_path) == 0:
|
|
92
|
+
logger.warning(
|
|
93
|
+
f"File {self.save_file_path} exists but is empty. Treating as new file."
|
|
94
|
+
)
|
|
95
|
+
file_exists = False
|
|
91
96
|
if file_exists and not self.has_valid_headers(total):
|
|
92
97
|
logger.warning("The CSV format has changed, backing up old emission file.")
|
|
93
98
|
backup(self.save_file_path)
|
|
@@ -50,17 +50,22 @@ ram_power_doc = MetricDocumentation(
|
|
|
50
50
|
)
|
|
51
51
|
cpu_energy_doc = MetricDocumentation(
|
|
52
52
|
"codecarbon_cpu_energy",
|
|
53
|
-
description="Energy used per CPU (kWh)",
|
|
53
|
+
description="Energy used per CPU since last reading (kWh)",
|
|
54
54
|
)
|
|
55
55
|
gpu_energy_doc = MetricDocumentation(
|
|
56
56
|
"codecarbon_gpu_energy",
|
|
57
|
-
description="Energy used per GPU (kWh)",
|
|
57
|
+
description="Energy used per GPU since last reading (kWh)",
|
|
58
58
|
)
|
|
59
59
|
ram_energy_doc = MetricDocumentation(
|
|
60
60
|
"codecarbon_ram_energy",
|
|
61
|
-
description="Energy used per RAM (kWh)",
|
|
61
|
+
description="Energy used per RAM since last reading (kWh)",
|
|
62
62
|
)
|
|
63
63
|
energy_consumed_doc = MetricDocumentation(
|
|
64
64
|
"codecarbon_energy_consumed",
|
|
65
|
-
description="Sum of cpu_energy, gpu_energy and ram_energy (
|
|
65
|
+
description="Sum of cpu_energy, gpu_energy and ram_energy (kWh)",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
energy_consumed_total_doc = MetricDocumentation(
|
|
69
|
+
"codecarbon_energy_total",
|
|
70
|
+
description="Accumulated cpu_energy, gpu_energy and ram_energy (kWh) since the start of the run",
|
|
66
71
|
)
|