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.
- {codecarbon-3.2.7 → codecarbon-3.2.8}/PKG-INFO +5 -4
- {codecarbon-3.2.7 → codecarbon-3.2.8}/README.md +2 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/__init__.py +7 -1
- codecarbon-3.2.8/codecarbon/_version.py +1 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/cli/auth.py +13 -6
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/cli/main.py +1 -1
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/cli/monitor.py +4 -3
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/api_client.py +4 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/cpu.py +8 -7
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/emissions.py +26 -16
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/gpu_amd.py +1 -1
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/schemas.py +4 -0
- codecarbon-3.2.8/codecarbon/core/telemetry_client.py +61 -0
- codecarbon-3.2.8/codecarbon/core/telemetry_schemas.py +156 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/emissions_tracker.py +266 -162
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output.py +1 -1
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/base_output.py +27 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon.egg-info/PKG-INFO +5 -4
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon.egg-info/SOURCES.txt +3 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon.egg-info/requires.txt +2 -4
- {codecarbon-3.2.7 → codecarbon-3.2.8}/pyproject.toml +3 -6
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_api_call.py +9 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_cpu.py +34 -3
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_emissions.py +72 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_emissions_tracker.py +145 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_gpu_amd.py +73 -0
- codecarbon-3.2.8/tests/test_telemetry_client.py +104 -0
- codecarbon-3.2.7/codecarbon/_version.py +0 -1
- {codecarbon-3.2.7 → codecarbon-3.2.8}/LICENSE +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/cli/__init__.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/cli/cli_utils.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/__init__.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/cloud.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/config.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/electricitymaps_api.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/gpu.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/gpu_device.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/gpu_nvidia.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/measure.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/powermetrics.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/rapl.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/resource_tracker.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/units.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/core/util.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/canada_provinces.geojson +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/cloud/impact.csv +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/hardware/cpu_dataset_builder/amd_cpu_scrapper.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/hardware/cpu_dataset_builder/intel_cpu_scrapper.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/hardware/cpu_dataset_builder/merge_scrapped_cpu_power.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/hardware/cpu_power.csv +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/private_infra/2016/usa_emissions.json +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/private_infra/2023/canada_energy_mix.json +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/private_infra/carbon_intensity_per_source.json +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/private_infra/global_energy_mix.json +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/data/private_infra/nordic_emissions.json +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/__init__.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/geography.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/hardware.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/logger.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/ram.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/scheduler.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/external/task.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/input.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/lock.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/__init__.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/boamps/__init__.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/boamps/mapper.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/boamps/models.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/boamps/output.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/emissions_data.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/file.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/http.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/logger.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/metrics/__init__.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/metrics/logfire.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/metrics/metric_docs.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/output_methods/metrics/prometheus.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/py.typed +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/__init__.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/assets/__init__.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/assets/car_icon.png +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/assets/house_icon.png +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/assets/tv_icon.png +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/carbonboard.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/carbonboard_on_api.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/components.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/data.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon/viz/units.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon.egg-info/dependency_links.txt +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon.egg-info/entry_points.txt +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/codecarbon.egg-info/top_level.txt +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/setup.cfg +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_boamps_output.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_cloud.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_config.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_core_util.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_cpu_load.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_custom_handler.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_docs_examples.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_electricitymaps_api.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_electricitymaps_backward_compatibility.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_electricitymaps_config_backward_compatibility.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_emissions_tracker_constant.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_emissions_tracker_flush.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_energy.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_geography.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_gpu.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_gpu_nvidia.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_input.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_lock.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_logger.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_logging_output.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_offline_emissions_tracker.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_package_integrity.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_powermetrics.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_ram.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_rapl_mmio_scanning.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_rapl_parameters.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_rapl_permissions.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_resource_tracker.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_tracking_inference.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_unsupported_gpu.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_utilization_tracking.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_viz_data.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/test_viz_units.py +0 -0
- {codecarbon-3.2.7 → codecarbon-3.2.8}/tests/testdata.py +0 -0
- {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.
|
|
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
|

|
|
@@ -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
|

|
|
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
|

|
|
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__ = [
|
|
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 =
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
67
|
-
tracker =
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|