codecarbon 3.2.7__tar.gz → 3.2.8__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 (127) hide show
  1. {codecarbon-3.2.7 → codecarbon-3.2.8}/PKG-INFO +5 -4
  2. {codecarbon-3.2.7 → codecarbon-3.2.8}/README.md +2 -0
  3. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/__init__.py +7 -1
  4. codecarbon-3.2.8/codecarbon/_version.py +1 -0
  5. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/cli/auth.py +13 -6
  6. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/cli/main.py +1 -1
  7. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/cli/monitor.py +4 -3
  8. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/api_client.py +4 -0
  9. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/cpu.py +8 -7
  10. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/emissions.py +26 -16
  11. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/gpu_amd.py +1 -1
  12. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/schemas.py +4 -0
  13. codecarbon-3.2.8/codecarbon/core/telemetry_client.py +61 -0
  14. codecarbon-3.2.8/codecarbon/core/telemetry_schemas.py +156 -0
  15. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/emissions_tracker.py +266 -162
  16. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output.py +1 -1
  17. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/base_output.py +27 -0
  18. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon.egg-info/PKG-INFO +5 -4
  19. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon.egg-info/SOURCES.txt +3 -0
  20. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon.egg-info/requires.txt +2 -4
  21. {codecarbon-3.2.7 → codecarbon-3.2.8}/pyproject.toml +3 -6
  22. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_api_call.py +9 -0
  23. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_cpu.py +34 -3
  24. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_emissions.py +72 -0
  25. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_emissions_tracker.py +145 -0
  26. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_gpu_amd.py +73 -0
  27. codecarbon-3.2.8/tests/test_telemetry_client.py +104 -0
  28. codecarbon-3.2.7/codecarbon/_version.py +0 -1
  29. {codecarbon-3.2.7 → codecarbon-3.2.8}/LICENSE +0 -0
  30. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/cli/__init__.py +0 -0
  31. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/cli/cli_utils.py +0 -0
  32. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/__init__.py +0 -0
  33. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/cloud.py +0 -0
  34. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/config.py +0 -0
  35. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/electricitymaps_api.py +0 -0
  36. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/gpu.py +0 -0
  37. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/gpu_device.py +0 -0
  38. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/gpu_nvidia.py +0 -0
  39. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/measure.py +0 -0
  40. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/powermetrics.py +0 -0
  41. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/rapl.py +0 -0
  42. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/resource_tracker.py +0 -0
  43. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/units.py +0 -0
  44. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/util.py +0 -0
  45. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/canada_provinces.geojson +0 -0
  46. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/cloud/impact.csv +0 -0
  47. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/hardware/cpu_dataset_builder/amd_cpu_scrapper.py +0 -0
  48. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/hardware/cpu_dataset_builder/intel_cpu_scrapper.py +0 -0
  49. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/hardware/cpu_dataset_builder/merge_scrapped_cpu_power.py +0 -0
  50. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/hardware/cpu_power.csv +0 -0
  51. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/private_infra/2016/usa_emissions.json +0 -0
  52. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/private_infra/2023/canada_energy_mix.json +0 -0
  53. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/private_infra/carbon_intensity_per_source.json +0 -0
  54. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/private_infra/global_energy_mix.json +0 -0
  55. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/private_infra/nordic_emissions.json +0 -0
  56. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/__init__.py +0 -0
  57. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/geography.py +0 -0
  58. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/hardware.py +0 -0
  59. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/logger.py +0 -0
  60. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/ram.py +0 -0
  61. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/scheduler.py +0 -0
  62. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/task.py +0 -0
  63. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/input.py +0 -0
  64. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/lock.py +0 -0
  65. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/__init__.py +0 -0
  66. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/boamps/__init__.py +0 -0
  67. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/boamps/mapper.py +0 -0
  68. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/boamps/models.py +0 -0
  69. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/boamps/output.py +0 -0
  70. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/emissions_data.py +0 -0
  71. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/file.py +0 -0
  72. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/http.py +0 -0
  73. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/logger.py +0 -0
  74. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/metrics/__init__.py +0 -0
  75. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/metrics/logfire.py +0 -0
  76. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/metrics/metric_docs.py +0 -0
  77. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/metrics/prometheus.py +0 -0
  78. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/py.typed +0 -0
  79. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/__init__.py +0 -0
  80. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/assets/__init__.py +0 -0
  81. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/assets/car_icon.png +0 -0
  82. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/assets/house_icon.png +0 -0
  83. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/assets/tv_icon.png +0 -0
  84. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/carbonboard.py +0 -0
  85. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/carbonboard_on_api.py +0 -0
  86. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/components.py +0 -0
  87. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/data.py +0 -0
  88. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/units.py +0 -0
  89. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon.egg-info/dependency_links.txt +0 -0
  90. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon.egg-info/entry_points.txt +0 -0
  91. {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon.egg-info/top_level.txt +0 -0
  92. {codecarbon-3.2.7 → codecarbon-3.2.8}/setup.cfg +0 -0
  93. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_boamps_output.py +0 -0
  94. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_cloud.py +0 -0
  95. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_config.py +0 -0
  96. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_core_util.py +0 -0
  97. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_cpu_load.py +0 -0
  98. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_custom_handler.py +0 -0
  99. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_docs_examples.py +0 -0
  100. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_electricitymaps_api.py +0 -0
  101. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_electricitymaps_backward_compatibility.py +0 -0
  102. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_electricitymaps_config_backward_compatibility.py +0 -0
  103. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_emissions_tracker_constant.py +0 -0
  104. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_emissions_tracker_flush.py +0 -0
  105. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_energy.py +0 -0
  106. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_geography.py +0 -0
  107. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_gpu.py +0 -0
  108. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_gpu_nvidia.py +0 -0
  109. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_input.py +0 -0
  110. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_lock.py +0 -0
  111. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_logger.py +0 -0
  112. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_logging_output.py +0 -0
  113. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_offline_emissions_tracker.py +0 -0
  114. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_package_integrity.py +0 -0
  115. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_powermetrics.py +0 -0
  116. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_ram.py +0 -0
  117. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_rapl_mmio_scanning.py +0 -0
  118. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_rapl_parameters.py +0 -0
  119. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_rapl_permissions.py +0 -0
  120. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_resource_tracker.py +0 -0
  121. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_tracking_inference.py +0 -0
  122. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_unsupported_gpu.py +0 -0
  123. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_utilization_tracking.py +0 -0
  124. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_viz_data.py +0 -0
  125. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_viz_units.py +0 -0
  126. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/testdata.py +0 -0
  127. {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/testutils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecarbon
3
- Version: 3.2.7
3
+ Version: 3.2.8
4
4
  Author: Mila, DataForGood, BCG GAMMA, Comet.ml, Haverford College
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://codecarbon.io/
@@ -20,13 +20,14 @@ Description-Content-Type: text/markdown
20
20
  License-File: LICENSE
21
21
  Requires-Dist: arrow
22
22
  Requires-Dist: authlib>=1.2.1
23
+ Requires-Dist: joserfc>=1.0.0
23
24
  Requires-Dist: click
24
25
  Requires-Dist: pandas>=2.3.3; python_version >= "3.14"
25
26
  Requires-Dist: pandas; python_version < "3.14"
26
27
  Requires-Dist: prometheus_client
27
28
  Requires-Dist: psutil>=6.0.0
28
29
  Requires-Dist: py-cpuinfo
29
- Requires-Dist: pydantic
30
+ Requires-Dist: pydantic<3.0.0,>=2.0.0
30
31
  Requires-Dist: nvidia-ml-py
31
32
  Requires-Dist: rapidfuzz
32
33
  Requires-Dist: requests
@@ -42,8 +43,6 @@ Provides-Extra: viz-legacy
42
43
  Requires-Dist: dash; extra == "viz-legacy"
43
44
  Requires-Dist: dash_bootstrap_components>1.0.0; extra == "viz-legacy"
44
45
  Requires-Dist: fire; extra == "viz-legacy"
45
- Provides-Extra: amdsmi
46
- Requires-Dist: amdsmi>=6.0.0; extra == "amdsmi"
47
46
  Dynamic: license-file
48
47
 
49
48
  ![banner](docs/images/banner.png)
@@ -124,6 +123,8 @@ Configuration precedence and examples: [configuration guide](https://docs.codeca
124
123
 
125
124
  We created a Python package that estimates your hardware electricity power consumption (GPU + CPU + RAM) and we apply to it the carbon intensity of the region where the computing is done.
126
125
 
126
+ CodeCarbon focuses on the main compute components it can measure or estimate directly: CPU, GPU, and RAM. It does not separately model disk I/O, network transfers, displays, cooling, or other peripherals because those sources are usually much smaller for local code-level experiments and are not exposed through the same low-overhead measurement interfaces.
127
+
127
128
  ![calculation Summary](docs/images/calculation.png)
128
129
 
129
130
  We explain more about this calculation in the [**Methodology**](https://docs.codecarbon.io/latest/explanation/methodology/) section of the documentation.
@@ -76,6 +76,8 @@ Configuration precedence and examples: [configuration guide](https://docs.codeca
76
76
 
77
77
  We created a Python package that estimates your hardware electricity power consumption (GPU + CPU + RAM) and we apply to it the carbon intensity of the region where the computing is done.
78
78
 
79
+ CodeCarbon focuses on the main compute components it can measure or estimate directly: CPU, GPU, and RAM. It does not separately model disk I/O, network transfers, displays, cooling, or other peripherals because those sources are usually much smaller for local code-level experiments and are not exposed through the same low-overhead measurement interfaces.
80
+
79
81
  ![calculation Summary](docs/images/calculation.png)
80
82
 
81
83
  We explain more about this calculation in the [**Methodology**](https://docs.codecarbon.io/latest/explanation/methodology/) section of the documentation.
@@ -8,6 +8,12 @@ from .emissions_tracker import (
8
8
  OfflineEmissionsTracker,
9
9
  track_emissions,
10
10
  )
11
+ from .output import OutputMethod
11
12
 
12
- __all__ = ["EmissionsTracker", "OfflineEmissionsTracker", "track_emissions"]
13
+ __all__ = [
14
+ "EmissionsTracker",
15
+ "OfflineEmissionsTracker",
16
+ "OutputMethod",
17
+ "track_emissions",
18
+ ]
13
19
  __app_name__ = "codecarbon"
@@ -0,0 +1 @@
1
+ __version__ = "3.2.8"
@@ -6,6 +6,7 @@ PKCE), credential storage, JWKS validation, and transparent refresh.
6
6
  """
7
7
 
8
8
  import json
9
+ import logging
9
10
  import os
10
11
  import webbrowser
11
12
  from http.server import BaseHTTPRequestHandler, HTTPServer
@@ -15,9 +16,9 @@ from urllib.parse import parse_qs, urlparse
15
16
  import requests
16
17
  from authlib.common.security import generate_token
17
18
  from authlib.integrations.requests_client import OAuth2Session
18
- from authlib.jose import JsonWebKey
19
- from authlib.jose import jwt as jose_jwt
20
19
  from authlib.oauth2.rfc7636 import create_s256_code_challenge
20
+ from joserfc import jwt as jose_jwt
21
+ from joserfc.jwk import KeySet
21
22
 
22
23
  AUTH_CLIENT_ID = os.environ.get(
23
24
  "AUTH_CLIENT_ID",
@@ -31,6 +32,7 @@ AUTH_SERVER_WELL_KNOWN = os.environ.get(
31
32
  _REDIRECT_PORT = 8090
32
33
  _REDIRECT_URI = f"http://localhost:{_REDIRECT_PORT}/callback"
33
34
  _CREDENTIALS_FILE = Path("./credentials.json")
35
+ logger = logging.getLogger(__name__)
34
36
 
35
37
 
36
38
  # ── OAuth callback server ───────────────────────────────────────────
@@ -108,11 +110,16 @@ def _validate_access_token(access_token: str) -> bool:
108
110
  discovery = _discover_endpoints()
109
111
  jwks_resp = requests.get(discovery["jwks_uri"])
110
112
  jwks_resp.raise_for_status()
111
- keyset = JsonWebKey.import_key_set(jwks_resp.json())
112
- claims = jose_jwt.decode(access_token, keyset)
113
- claims.validate()
113
+ keyset = KeySet.import_key_set(jwks_resp.json())
114
+ token = jose_jwt.decode(access_token, keyset)
115
+ jose_jwt.JWTClaimsRegistry().validate(token.claims)
114
116
  return True
115
- except requests.RequestException:
117
+ except requests.RequestException as exc:
118
+ logger.warning(
119
+ "Skipping local access token validation because the auth server is "
120
+ "unreachable; the API will validate the token. Error: %s",
121
+ exc,
122
+ )
116
123
  return True # Can't reach auth server — let the API handle it
117
124
  except Exception:
118
125
  return False
@@ -377,7 +377,7 @@ def monitor(
377
377
 
378
378
  # If extra args are provided (e.g. `codecarbon monitor -- my_script.py`), delegate to `run_and_monitor`
379
379
  if getattr(ctx, "args", None):
380
- return run_and_monitor(ctx, **tracker_args)
380
+ return run_and_monitor(ctx, offline=offline, **tracker_args)
381
381
 
382
382
  # Instantiate the tracker
383
383
  if offline:
@@ -8,7 +8,7 @@ import typer
8
8
  from rich import print
9
9
  from typing_extensions import Annotated
10
10
 
11
- from codecarbon.emissions_tracker import EmissionsTracker
11
+ from codecarbon.emissions_tracker import EmissionsTracker, OfflineEmissionsTracker
12
12
 
13
13
 
14
14
  def run_and_monitor(
@@ -17,6 +17,7 @@ def run_and_monitor(
17
17
  str,
18
18
  typer.Option(help="Log level (critical, error, warning, info, debug)"),
19
19
  ] = "error",
20
+ offline: bool = False,
20
21
  **tracker_args,
21
22
  ):
22
23
  """
@@ -63,8 +64,8 @@ def run_and_monitor(
63
64
  )
64
65
  raise typer.Exit(1)
65
66
 
66
- # Initialize tracker with specified logging level and shared args
67
- tracker = EmissionsTracker(
67
+ tracker_cls = OfflineEmissionsTracker if offline else EmissionsTracker
68
+ tracker = tracker_cls(
68
69
  log_level=log_level,
69
70
  save_to_logger=False,
70
71
  tracking_mode="process",
@@ -227,6 +227,10 @@ class ApiClient: # (AsyncClient)
227
227
  gpu_energy=carbon_emission["gpu_energy"],
228
228
  ram_energy=carbon_emission["ram_energy"],
229
229
  energy_consumed=carbon_emission["energy_consumed"],
230
+ cpu_utilization_percent=carbon_emission.get("cpu_utilization_percent"),
231
+ gpu_utilization_percent=carbon_emission.get("gpu_utilization_percent"),
232
+ ram_utilization_percent=carbon_emission.get("ram_utilization_percent"),
233
+ wue=carbon_emission.get("wue", 0),
230
234
  )
231
235
  try:
232
236
  payload = dataclasses.asdict(emission)
@@ -217,19 +217,19 @@ def is_psutil_available():
217
217
  return True
218
218
  else:
219
219
  logger.debug(
220
- f"is_psutil_available(): psutil.cpu_times().nice is too small: {nice}"
220
+ f"is_psutil_available(): psutil.cpu_times().nice is too small: {nice}; "
221
+ "using fallback check."
221
222
  )
222
- return False
223
223
 
224
224
  else:
225
- # Fallback: check if psutil works by calling cpu_percent
226
225
  logger.debug(
227
226
  "is_psutil_available(): no 'nice' attribute, using fallback check."
228
227
  )
229
228
 
230
- # check CPU utilization usable
231
- psutil.cpu_percent(interval=0.0, percpu=False)
232
- return True
229
+ # Fallback: check if psutil CPU utilization is usable. This covers
230
+ # platforms like Windows and macOS where cpu_times().nice is absent or 0.
231
+ psutil.cpu_percent(interval=0.0, percpu=False)
232
+ return True
233
233
 
234
234
  except Exception as e:
235
235
  logger.debug(
@@ -828,7 +828,8 @@ class IntelRAPL:
828
828
  """
829
829
  cpu_details = {}
830
830
  try:
831
- list(map(lambda rapl_file: rapl_file.delta(duration), self._rapl_files))
831
+ for rapl_file in self._rapl_files:
832
+ rapl_file.delta(duration)
832
833
 
833
834
  for rapl_file in self._rapl_files:
834
835
  logger.debug(rapl_file)
@@ -16,6 +16,12 @@ from codecarbon.external.geography import CloudMetadata, GeoMetadata
16
16
  from codecarbon.external.logger import logger
17
17
  from codecarbon.input import DataSource, DataSourceException
18
18
 
19
+ _NORDIC_REGIONS_BY_COUNTRY = {
20
+ "SWE": {"SE1", "SE2", "SE3", "SE4"},
21
+ "NOR": {"NO1", "NO2", "NO3", "NO4", "NO5"},
22
+ "FIN": {"FI"},
23
+ }
24
+
19
25
 
20
26
  class Emissions:
21
27
  def __init__(
@@ -169,8 +175,11 @@ class Emissions:
169
175
  + " >>> Using CodeCarbon's data."
170
176
  )
171
177
 
178
+ country_iso_code = (
179
+ geo.country_iso_code.upper() if geo.country_iso_code is not None else None
180
+ )
172
181
  compute_with_regional_data: bool = (geo.region is not None) and (
173
- geo.country_iso_code.upper() in ["USA", "CAN", "SWE", "NOR", "FIN"]
182
+ country_iso_code in ["USA", "CAN"] or self._is_supported_nordic_region(geo)
174
183
  )
175
184
 
176
185
  if compute_with_regional_data:
@@ -187,24 +196,10 @@ class Emissions:
187
196
  def _try_get_nordic_region_emissions(
188
197
  self, energy: Energy, geo: GeoMetadata
189
198
  ) -> Optional[float]:
190
- nordic_regions = {
191
- "SE1",
192
- "SE2",
193
- "SE3",
194
- "SE4",
195
- "NO1",
196
- "NO2",
197
- "NO3",
198
- "NO4",
199
- "NO5",
200
- "FI",
201
- }
202
- if geo.region is None:
199
+ if not self._is_supported_nordic_region(geo):
203
200
  return None
204
201
 
205
202
  region_upper = geo.region.upper()
206
- if region_upper not in nordic_regions:
207
- return None
208
203
 
209
204
  try:
210
205
  nordic_data = self._data_source.get_nordic_country_energy_mix_data()
@@ -225,6 +220,16 @@ class Emissions:
225
220
  )
226
221
  return None
227
222
 
223
+ @staticmethod
224
+ def _is_supported_nordic_region(geo: GeoMetadata) -> bool:
225
+ if geo.country_iso_code is None or geo.region is None:
226
+ return False
227
+
228
+ country_regions = _NORDIC_REGIONS_BY_COUNTRY.get(
229
+ geo.country_iso_code.upper(), set()
230
+ )
231
+ return geo.region.upper() in country_regions
232
+
228
233
  def get_region_emissions(self, energy: Energy, geo: GeoMetadata) -> float:
229
234
  """
230
235
  Computes emissions for a region on private infra.
@@ -248,6 +253,11 @@ class Emissions:
248
253
  nordic_emissions = self._try_get_nordic_region_emissions(energy, geo)
249
254
  if nordic_emissions is not None:
250
255
  return nordic_emissions
256
+ if (
257
+ geo.country_iso_code is not None
258
+ and geo.country_iso_code.upper() in _NORDIC_REGIONS_BY_COUNTRY
259
+ ):
260
+ return self.get_country_emissions(energy, geo)
251
261
 
252
262
  # Handle USA and Canada regional data
253
263
  try:
@@ -28,7 +28,7 @@ except ImportError:
28
28
  "Please install amdsmi to get GPU metrics."
29
29
  )
30
30
  AMDSMI_AVAILABLE = False
31
- except AttributeError as e:
31
+ except (AttributeError, OSError, KeyError) as e:
32
32
  amdsmi = None
33
33
  # In some environments, amdsmi may be present but not properly configured, leading to AttributeError when importing
34
34
  logger.warning(
@@ -22,6 +22,10 @@ class EmissionBase:
22
22
  gpu_energy: float
23
23
  ram_energy: float
24
24
  energy_consumed: float
25
+ cpu_utilization_percent: Optional[float] = None
26
+ gpu_utilization_percent: Optional[float] = None
27
+ ram_utilization_percent: Optional[float] = None
28
+ wue: Optional[float] = 0
25
29
 
26
30
 
27
31
  class EmissionCreate(EmissionBase):
@@ -0,0 +1,61 @@
1
+ import json
2
+ from typing import Optional, Union
3
+
4
+ import requests
5
+
6
+ from codecarbon.core.telemetry_schemas import TelemetryCreate
7
+ from codecarbon.external.logger import logger
8
+
9
+
10
+ class TelemetryClient:
11
+ """
12
+ Client dedicated to sending CodeCarbon telemetry payloads.
13
+ """
14
+
15
+ def __init__(
16
+ self,
17
+ endpoint_url="https://api.codecarbon.io",
18
+ telemetry: Optional[Union[TelemetryCreate, dict]] = None,
19
+ ):
20
+ self.endpoint_url = endpoint_url.rstrip("/")
21
+ self.telemetry_url = self.endpoint_url + "/telemetry"
22
+ self.headers = {"Content-Type": "application/json"}
23
+ self.telemetry = self._validate_telemetry(telemetry) if telemetry else None
24
+
25
+ def add_telemetry(self, telemetry: Optional[Union[TelemetryCreate, dict]] = None):
26
+ telemetry_payload = (
27
+ self._validate_telemetry(telemetry) if telemetry else self.telemetry
28
+ )
29
+ if telemetry_payload is None:
30
+ logger.error("TelemetryClient.add_telemetry() needs a telemetry payload")
31
+ return None
32
+ payload = telemetry_payload.model_dump(mode="json", exclude_none=True)
33
+
34
+ try:
35
+ response = requests.post(
36
+ url=self.telemetry_url,
37
+ json=payload,
38
+ timeout=2,
39
+ headers=self.headers,
40
+ )
41
+ if response.status_code != 201:
42
+ self._log_error(payload, response)
43
+ return None
44
+ return response.json()
45
+ except Exception as e:
46
+ logger.error(e, exc_info=True)
47
+ return None
48
+
49
+ @staticmethod
50
+ def _validate_telemetry(telemetry: Union[TelemetryCreate, dict]) -> TelemetryCreate:
51
+ if isinstance(telemetry, TelemetryCreate):
52
+ return telemetry
53
+ return TelemetryCreate(**telemetry)
54
+
55
+ def _log_error(self, payload, response):
56
+ logger.error(
57
+ f"TelemetryClient Error when calling the API on {self.telemetry_url} with : {json.dumps(payload)}"
58
+ )
59
+ logger.error(
60
+ f"TelemetryClient API return http code {response.status_code} and answer : {response.text}"
61
+ )
@@ -0,0 +1,156 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+ from typing import List, Optional
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
6
+
7
+
8
+ class TelemetryLevel(str, Enum):
9
+ disabled = "disabled"
10
+ minimal = "minimal"
11
+ extensive = "extensive"
12
+
13
+
14
+ class TelemetryBase(BaseModel):
15
+ model_config = ConfigDict(extra="forbid", use_enum_values=True)
16
+
17
+ timestamp: datetime
18
+ telemetry_level: TelemetryLevel
19
+
20
+ os: Optional[str] = None
21
+ country_name: Optional[str] = None
22
+ country_iso_code: Optional[str] = Field(default=None, min_length=2, max_length=3)
23
+ region: Optional[str] = None
24
+ cloud_provider: Optional[str] = None
25
+ cloud_region: Optional[str] = None
26
+ longitude: Optional[float] = Field(default=None, ge=-180, le=180)
27
+ latitude: Optional[float] = Field(default=None, ge=-90, le=90)
28
+
29
+ cpu_count: Optional[int] = Field(default=None, ge=0)
30
+ cpu_physical_count: Optional[int] = Field(default=None, ge=0)
31
+ cpu_model: Optional[str] = None
32
+ cpu_architecture: Optional[str] = None
33
+ gpu_count: Optional[int] = Field(default=None, ge=0)
34
+ gpu_model: Optional[str] = None
35
+ gpu_driver_version: Optional[str] = None
36
+ gpu_memory_total_gb: Optional[float] = Field(default=None, ge=0)
37
+ ram_total_size_gb: Optional[float] = Field(default=None, ge=0)
38
+ cuda_version: Optional[str] = None
39
+ cudnn_version: Optional[str] = None
40
+
41
+ python_version: Optional[str] = None
42
+ python_implementation: Optional[str] = None
43
+ python_executable_hash: Optional[str] = Field(
44
+ default=None, min_length=64, max_length=64
45
+ )
46
+ python_env_type: Optional[str] = None
47
+ codecarbon_version: Optional[str] = None
48
+ codecarbon_install_method: Optional[str] = None
49
+
50
+ total_emissions_kg: Optional[float] = Field(default=None, ge=0)
51
+ emissions_rate_kg_per_sec: Optional[float] = Field(default=None, ge=0)
52
+ energy_consumed_kwh: Optional[float] = Field(default=None, ge=0)
53
+ cpu_energy_kwh: Optional[float] = Field(default=None, ge=0)
54
+ gpu_energy_kwh: Optional[float] = Field(default=None, ge=0)
55
+ ram_energy_kwh: Optional[float] = Field(default=None, ge=0)
56
+ duration_seconds: Optional[float] = Field(default=None, ge=0)
57
+ cpu_utilization_avg: Optional[float] = Field(default=None, ge=0, le=100)
58
+ gpu_utilization_avg: Optional[float] = Field(default=None, ge=0, le=100)
59
+ ram_utilization_avg: Optional[float] = Field(default=None, ge=0, le=100)
60
+
61
+ tracking_mode: Optional[str] = None
62
+ api_mode: Optional[str] = None
63
+ output_methods: Optional[List[str]] = None
64
+ hardware_tracked: Optional[List[str]] = None
65
+ task_tracking_used: Optional[bool] = None
66
+ decorator_vs_context: Optional[str] = None
67
+ measure_power_interval_secs: Optional[float] = Field(default=None, ge=0)
68
+
69
+ hardware_detection_success: Optional[bool] = None
70
+ rapl_available: Optional[bool] = None
71
+ gpu_detection_method: Optional[str] = None
72
+ first_measurement_time_ms: Optional[float] = Field(default=None, ge=0)
73
+ tracking_overhead_percent: Optional[float] = Field(default=None, ge=0)
74
+ errors_encountered: Optional[List[str]] = None
75
+ warning_count: Optional[int] = Field(default=None, ge=0)
76
+
77
+ ide_used: Optional[str] = None
78
+ notebook_environment: Optional[str] = None
79
+ ci_environment: Optional[str] = None
80
+ python_package_manager: Optional[str] = None
81
+ framework_detected: Optional[str] = None
82
+
83
+ has_torch: Optional[bool] = None
84
+ torch_version: Optional[str] = None
85
+ has_transformers: Optional[bool] = None
86
+ transformers_version: Optional[str] = None
87
+ has_diffusers: Optional[bool] = None
88
+ diffusers_version: Optional[str] = None
89
+ has_tensorflow: Optional[bool] = None
90
+ tensorflow_version: Optional[str] = None
91
+ has_keras: Optional[bool] = None
92
+ keras_version: Optional[str] = None
93
+ has_pytorch_lightning: Optional[bool] = None
94
+ pytorch_lightning_version: Optional[str] = None
95
+ has_fastai: Optional[bool] = None
96
+ fastai_version: Optional[str] = None
97
+ ml_framework_primary: Optional[str] = None
98
+
99
+ container_runtime: Optional[str] = None
100
+ in_container: Optional[bool] = None
101
+ host_machine_hash: Optional[str] = None
102
+
103
+ @model_validator(mode="after")
104
+ def validate_telemetry_level(self):
105
+ if self.telemetry_level == TelemetryLevel.disabled:
106
+ raise ValueError("Disabled telemetry must not be submitted")
107
+
108
+ if self.telemetry_level == TelemetryLevel.minimal:
109
+ extensive_fields = set(type(self).model_fields) - MINIMAL_TELEMETRY_FIELDS
110
+ submitted_extensive_fields = [
111
+ field
112
+ for field in extensive_fields
113
+ if getattr(self, field) not in (None, [], {})
114
+ ]
115
+ if submitted_extensive_fields:
116
+ fields = ", ".join(sorted(submitted_extensive_fields))
117
+ raise ValueError(
118
+ f"Minimal telemetry cannot include extensive fields: {fields}"
119
+ )
120
+
121
+ return self
122
+
123
+
124
+ MINIMAL_TELEMETRY_FIELDS = {
125
+ "timestamp",
126
+ "telemetry_level",
127
+ "os",
128
+ "country_name",
129
+ "country_iso_code",
130
+ "region",
131
+ "cloud_provider",
132
+ "cloud_region",
133
+ "longitude",
134
+ "latitude",
135
+ "cpu_count",
136
+ "cpu_physical_count",
137
+ "cpu_model",
138
+ "cpu_architecture",
139
+ "gpu_count",
140
+ "gpu_model",
141
+ "gpu_driver_version",
142
+ "gpu_memory_total_gb",
143
+ "ram_total_size_gb",
144
+ "cuda_version",
145
+ "cudnn_version",
146
+ "python_version",
147
+ "python_implementation",
148
+ "python_executable_hash",
149
+ "python_env_type",
150
+ "codecarbon_version",
151
+ "codecarbon_install_method",
152
+ }
153
+
154
+
155
+ class TelemetryCreate(TelemetryBase):
156
+ pass