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.
Files changed (109) hide show
  1. {codecarbon-3.2.0 → codecarbon-3.2.2}/PKG-INFO +18 -16
  2. {codecarbon-3.2.0 → codecarbon-3.2.2}/README.md +12 -5
  3. codecarbon-3.2.2/codecarbon/_version.py +1 -0
  4. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/cli/main.py +25 -0
  5. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/gpu.py +5 -1
  6. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/util.py +6 -4
  7. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/emissions_tracker.py +101 -12
  8. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/input.py +80 -26
  9. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/base_output.py +3 -0
  10. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/emissions_data.py +8 -0
  11. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/file.py +5 -0
  12. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/metrics/metric_docs.py +9 -4
  13. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/metrics/prometheus.py +34 -3
  14. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/carbonboard.py +29 -3
  15. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon.egg-info/PKG-INFO +18 -16
  16. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon.egg-info/SOURCES.txt +2 -0
  17. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon.egg-info/requires.txt +7 -14
  18. {codecarbon-3.2.0 → codecarbon-3.2.2}/pyproject.toml +9 -14
  19. codecarbon-3.2.2/tests/test_core_util.py +44 -0
  20. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_emissions_tracker.py +19 -0
  21. codecarbon-3.2.2/tests/test_input.py +98 -0
  22. codecarbon-3.2.2/tests/test_utilization_tracking.py +206 -0
  23. codecarbon-3.2.0/codecarbon/_version.py +0 -1
  24. codecarbon-3.2.0/tests/test_core_util.py +0 -17
  25. {codecarbon-3.2.0 → codecarbon-3.2.2}/LICENSE +0 -0
  26. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/__init__.py +0 -0
  27. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/cli/__init__.py +0 -0
  28. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/cli/cli_utils.py +0 -0
  29. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/__init__.py +0 -0
  30. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/api_client.py +0 -0
  31. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/cloud.py +0 -0
  32. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/config.py +0 -0
  33. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/cpu.py +0 -0
  34. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/electricitymaps_api.py +0 -0
  35. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/emissions.py +0 -0
  36. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/measure.py +0 -0
  37. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/powermetrics.py +0 -0
  38. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/rapl.py +0 -0
  39. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/resource_tracker.py +0 -0
  40. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/schemas.py +0 -0
  41. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/core/units.py +0 -0
  42. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/canada_provinces.geojson +0 -0
  43. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/cloud/impact.csv +0 -0
  44. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/hardware/cpu_dataset_builder/amd_cpu_scrapper.py +0 -0
  45. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/hardware/cpu_dataset_builder/intel_cpu_scrapper.py +0 -0
  46. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/hardware/cpu_dataset_builder/merge_scrapped_cpu_power.py +0 -0
  47. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/hardware/cpu_power.csv +0 -0
  48. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/private_infra/2016/usa_emissions.json +0 -0
  49. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/private_infra/2023/canada_energy_mix.json +0 -0
  50. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/private_infra/carbon_intensity_per_source.json +0 -0
  51. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/data/private_infra/global_energy_mix.json +0 -0
  52. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/__init__.py +0 -0
  53. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/geography.py +0 -0
  54. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/hardware.py +0 -0
  55. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/logger.py +0 -0
  56. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/ram.py +0 -0
  57. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/scheduler.py +0 -0
  58. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/external/task.py +0 -0
  59. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/lock.py +0 -0
  60. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output.py +0 -0
  61. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/__init__.py +0 -0
  62. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/http.py +0 -0
  63. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/logger.py +0 -0
  64. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/metrics/__init__.py +0 -0
  65. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/output_methods/metrics/logfire.py +0 -0
  66. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/__init__.py +0 -0
  67. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/assets/__init__.py +0 -0
  68. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/assets/car_icon.png +0 -0
  69. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/assets/house_icon.png +0 -0
  70. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/assets/tv_icon.png +0 -0
  71. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/carbonboard_on_api.py +0 -0
  72. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/components.py +0 -0
  73. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/data.py +0 -0
  74. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon/viz/units.py +0 -0
  75. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon.egg-info/dependency_links.txt +0 -0
  76. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon.egg-info/entry_points.txt +0 -0
  77. {codecarbon-3.2.0 → codecarbon-3.2.2}/codecarbon.egg-info/top_level.txt +0 -0
  78. {codecarbon-3.2.0 → codecarbon-3.2.2}/setup.cfg +0 -0
  79. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_api_call.py +0 -0
  80. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_cli.py +0 -0
  81. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_cloud.py +0 -0
  82. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_config.py +0 -0
  83. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_cpu.py +0 -0
  84. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_cpu_load.py +0 -0
  85. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_custom_handler.py +0 -0
  86. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_electricitymaps_api.py +0 -0
  87. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_electricitymaps_backward_compatibility.py +0 -0
  88. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_electricitymaps_config_backward_compatibility.py +0 -0
  89. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_emissions.py +0 -0
  90. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_emissions_tracker_constant.py +0 -0
  91. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_emissions_tracker_flush.py +0 -0
  92. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_energy.py +0 -0
  93. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_geography.py +0 -0
  94. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_gpu.py +0 -0
  95. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_lock.py +0 -0
  96. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_logging_output.py +0 -0
  97. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_offline_emissions_tracker.py +0 -0
  98. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_package_integrity.py +0 -0
  99. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_powermetrics.py +0 -0
  100. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_ram.py +0 -0
  101. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_rapl_mmio_scanning.py +0 -0
  102. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_rapl_parameters.py +0 -0
  103. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_rapl_permissions.py +0 -0
  104. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_tracking_inference.py +0 -0
  105. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_unsupported_gpu.py +0 -0
  106. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_viz_data.py +0 -0
  107. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/test_viz_units.py +0 -0
  108. {codecarbon-3.2.0 → codecarbon-3.2.2}/tests/testdata.py +0 -0
  109. {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.0
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/badges/version.svg)](https://anaconda.org/conda-forge/codecarbon)
94
- [![](https://anaconda.org/codecarbon/codecarbon/badges/version.svg)](https://anaconda.org/codecarbon/codecarbon)
95
88
  [![](https://img.shields.io/pypi/v/codecarbon?color=024758)](https://pypi.org/project/codecarbon/)
96
89
  [![DOI](https://zenodo.org/badge/263364731.svg)](https://zenodo.org/badge/latestdoi/263364731)
97
90
  <!-- [![Downloads](https://static.pepy.tech/badge/codecarbon/month)](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
- **From Conda repository**
144
- ```python
145
- conda install -c codecarbon codecarbon
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/badges/version.svg)](https://anaconda.org/conda-forge/codecarbon)
17
- [![](https://anaconda.org/codecarbon/codecarbon/badges/version.svg)](https://anaconda.org/codecarbon/codecarbon)
18
16
  [![](https://img.shields.io/pypi/v/codecarbon?color=024758)](https://pypi.org/project/codecarbon/)
19
17
  [![DOI](https://zenodo.org/badge/263364731.svg)](https://zenodo.org/badge/latestdoi/263364731)
20
18
  <!-- [![Downloads](https://static.pepy.tech/badge/codecarbon/month)](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
- **From Conda repository**
67
- ```python
68
- conda install -c codecarbon codecarbon
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
- return pynvml.nvmlDeviceGetMemoryInfo(self.handle)
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
- def detect_cpu_model() -> str:
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() -> str:
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() -> str:
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() -> str:
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[int] = _sentinel,
185
- wue: Optional[bool] = _sentinel,
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
- ressource_tracker = ResourceTracker(self)
365
- ressource_tracker.set_CPU_GPU_ram_tracking()
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
- logger.info(f" Available RAM : {self._conf.get('ram_total_size'):.3f} GB")
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: {self._conf.get('cpu_count')} thread(s) in {self._conf.get('cpu_physical_count')} physical CPU(s)"
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: {self._conf.get('cpu_model')}")
378
- logger.info(f" GPU count: {self._conf.get('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: {self._conf.get('gpu_model')} BUT only tracking these GPU ids : {self._conf.get('gpu_ids')}"
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: {self._conf.get('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(PrometheusOutput(self._prometheus_url))
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[int] = _sentinel,
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: This will likely change when we have a common location for data files
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
- with open(self.global_energy_mix_data_path) as f:
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 pd.read_csv(self.cloud_emissions_path)
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
- try:
106
- with open(self.country_emissions_data_path(country_iso_code)) as f:
107
- country_emissions_data: Dict = json.load(f)
108
- return country_emissions_data
109
- except KeyError:
110
- # KeyError raised from line 39, when there is no data path specified for
111
- # the given country
112
- raise DataSourceException
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
- with open(self.country_energy_mix_data_path(country_iso_code)) as f:
121
- country_energy_mix_data: Dict = json.load(f)
122
- return country_energy_mix_data
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
- with open(self.carbon_intensity_per_source_path) as f:
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 pd.read_csv(self.cpu_power_path)
190
+ return _CACHE["cpu_power"]
137
191
 
138
192
 
139
193
  class DataSourceException(Exception):
@@ -22,3 +22,6 @@ class BaseOutput:
22
22
 
23
23
  def task_out(self, data: List[TaskEmissionsData], experiment_name: str):
24
24
  pass
25
+
26
+ def exit(self):
27
+ pass
@@ -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 (kW)",
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
  )