codecarbon 3.2.8__tar.gz → 3.2.9__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.8 → codecarbon-3.2.9}/PKG-INFO +1 -1
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/__init__.py +1 -1
- codecarbon-3.2.9/codecarbon/_version.py +1 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/cli/main.py +33 -7
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/cli/monitor.py +5 -4
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/api_client.py +3 -5
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/cpu.py +16 -3
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/emissions.py +4 -3
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/gpu.py +17 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/gpu_amd.py +6 -1
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/gpu_device.py +14 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/gpu_nvidia.py +6 -1
- codecarbon-3.2.9/codecarbon/core/hardware_cache.py +249 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/powermetrics.py +15 -2
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/resource_tracker.py +71 -25
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/util.py +2 -1
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/emissions_tracker.py +124 -69
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/hardware.py +21 -5
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/task.py +1 -1
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/input.py +29 -12
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/file.py +15 -19
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/http.py +13 -8
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon.egg-info/PKG-INFO +1 -1
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon.egg-info/SOURCES.txt +2 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/pyproject.toml +2 -2
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_config.py +4 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_cpu.py +45 -7
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_cpu_load.py +5 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_custom_handler.py +4 -2
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_emissions_tracker.py +175 -10
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_emissions_tracker_constant.py +10 -15
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_gpu.py +7 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_gpu_nvidia.py +35 -0
- codecarbon-3.2.9/tests/test_hardware_cache.py +288 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_input.py +6 -3
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_offline_emissions_tracker.py +11 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_powermetrics.py +83 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_resource_tracker.py +246 -2
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/testdata.py +7 -0
- codecarbon-3.2.8/codecarbon/_version.py +0 -1
- {codecarbon-3.2.8 → codecarbon-3.2.9}/LICENSE +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/README.md +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/cli/__init__.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/cli/auth.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/cli/cli_utils.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/__init__.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/cloud.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/config.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/electricitymaps_api.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/measure.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/rapl.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/schemas.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/telemetry_client.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/telemetry_schemas.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/units.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/canada_provinces.geojson +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/cloud/impact.csv +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/hardware/cpu_dataset_builder/amd_cpu_scrapper.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/hardware/cpu_dataset_builder/intel_cpu_scrapper.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/hardware/cpu_dataset_builder/merge_scrapped_cpu_power.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/hardware/cpu_power.csv +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/private_infra/2016/usa_emissions.json +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/private_infra/2023/canada_energy_mix.json +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/private_infra/carbon_intensity_per_source.json +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/private_infra/global_energy_mix.json +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/private_infra/nordic_emissions.json +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/__init__.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/geography.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/logger.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/ram.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/scheduler.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/lock.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/__init__.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/base_output.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/boamps/__init__.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/boamps/mapper.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/boamps/models.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/boamps/output.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/emissions_data.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/logger.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/metrics/__init__.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/metrics/logfire.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/metrics/metric_docs.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/metrics/prometheus.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/py.typed +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/__init__.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/assets/__init__.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/assets/car_icon.png +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/assets/house_icon.png +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/assets/tv_icon.png +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/carbonboard.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/carbonboard_on_api.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/components.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/data.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/units.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon.egg-info/dependency_links.txt +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon.egg-info/entry_points.txt +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon.egg-info/requires.txt +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon.egg-info/top_level.txt +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/setup.cfg +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_api_call.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_boamps_output.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_cloud.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_core_util.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_docs_examples.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_electricitymaps_api.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_electricitymaps_backward_compatibility.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_electricitymaps_config_backward_compatibility.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_emissions.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_emissions_tracker_flush.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_energy.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_geography.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_gpu_amd.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_lock.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_logger.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_logging_output.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_package_integrity.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_ram.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_rapl_mmio_scanning.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_rapl_parameters.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_rapl_permissions.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_telemetry_client.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_tracking_inference.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_unsupported_gpu.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_utilization_tracking.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_viz_data.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_viz_units.py +0 -0
- {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/testutils.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.2.9"
|
|
@@ -5,15 +5,12 @@ import time
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Optional
|
|
7
7
|
|
|
8
|
-
import questionary
|
|
9
|
-
import requests
|
|
10
8
|
import typer
|
|
11
9
|
from rich import print
|
|
12
10
|
from rich.prompt import Confirm
|
|
13
11
|
from typing_extensions import Annotated
|
|
14
12
|
|
|
15
13
|
from codecarbon import __app_name__, __version__
|
|
16
|
-
from codecarbon.cli.auth import authorize, get_access_token
|
|
17
14
|
from codecarbon.cli.cli_utils import (
|
|
18
15
|
create_new_config_file,
|
|
19
16
|
get_api_endpoint,
|
|
@@ -21,10 +18,6 @@ from codecarbon.cli.cli_utils import (
|
|
|
21
18
|
get_existing_exp_id,
|
|
22
19
|
overwrite_local_config,
|
|
23
20
|
)
|
|
24
|
-
from codecarbon.cli.monitor import run_and_monitor
|
|
25
|
-
from codecarbon.core.api_client import ApiClient, get_datetime_with_timezone
|
|
26
|
-
from codecarbon.core.schemas import ExperimentCreate, OrganizationCreate, ProjectCreate
|
|
27
|
-
from codecarbon.emissions_tracker import EmissionsTracker, OfflineEmissionsTracker
|
|
28
21
|
|
|
29
22
|
API_URL = os.environ.get("API_URL", "https://dashboard.codecarbon.io/api")
|
|
30
23
|
|
|
@@ -68,6 +61,9 @@ def version(
|
|
|
68
61
|
|
|
69
62
|
|
|
70
63
|
def show_config(path: Path = Path("./.codecarbon.config")) -> None:
|
|
64
|
+
from codecarbon.cli.auth import get_access_token
|
|
65
|
+
from codecarbon.core.api_client import ApiClient
|
|
66
|
+
|
|
71
67
|
d = get_config(path)
|
|
72
68
|
print("Current configuration : \n")
|
|
73
69
|
print("Config file content : ")
|
|
@@ -114,6 +110,9 @@ def api_get():
|
|
|
114
110
|
"""
|
|
115
111
|
ex: test-api
|
|
116
112
|
"""
|
|
113
|
+
from codecarbon.cli.auth import get_access_token
|
|
114
|
+
from codecarbon.core.api_client import ApiClient
|
|
115
|
+
|
|
117
116
|
api_endpoint = get_api_endpoint()
|
|
118
117
|
api = ApiClient(endpoint_url=api_endpoint)
|
|
119
118
|
api.set_access_token(get_access_token())
|
|
@@ -123,6 +122,9 @@ def api_get():
|
|
|
123
122
|
|
|
124
123
|
@codecarbon.command("login", short_help="Login to CodeCarbon")
|
|
125
124
|
def login():
|
|
125
|
+
from codecarbon.cli.auth import authorize, get_access_token
|
|
126
|
+
from codecarbon.core.api_client import ApiClient
|
|
127
|
+
|
|
126
128
|
authorize()
|
|
127
129
|
api_endpoint = get_api_endpoint()
|
|
128
130
|
api = ApiClient(endpoint_url=api_endpoint)
|
|
@@ -132,6 +134,10 @@ def login():
|
|
|
132
134
|
|
|
133
135
|
|
|
134
136
|
def get_api_key(project_id: str):
|
|
137
|
+
import requests
|
|
138
|
+
|
|
139
|
+
from codecarbon.cli.auth import get_access_token
|
|
140
|
+
|
|
135
141
|
api_endpoint = get_api_endpoint()
|
|
136
142
|
api_endpoint = api_endpoint.rstrip("/")
|
|
137
143
|
req = requests.post(
|
|
@@ -161,6 +167,13 @@ def config():
|
|
|
161
167
|
"""
|
|
162
168
|
Initialize CodeCarbon, this will prompt you for configuration of Organisation/Team/Project/Experiment.
|
|
163
169
|
"""
|
|
170
|
+
from codecarbon.cli.auth import get_access_token
|
|
171
|
+
from codecarbon.core.api_client import ApiClient, get_datetime_with_timezone
|
|
172
|
+
from codecarbon.core.schemas import (
|
|
173
|
+
ExperimentCreate,
|
|
174
|
+
OrganizationCreate,
|
|
175
|
+
ProjectCreate,
|
|
176
|
+
)
|
|
164
177
|
|
|
165
178
|
print("Welcome to CodeCarbon configuration wizard")
|
|
166
179
|
home = Path.home()
|
|
@@ -342,6 +355,10 @@ def monitor(
|
|
|
342
355
|
str,
|
|
343
356
|
typer.Option(help="Region/province for offline mode"),
|
|
344
357
|
] = None,
|
|
358
|
+
log_level: Annotated[
|
|
359
|
+
str,
|
|
360
|
+
typer.Option(help="Log level (critical, error, warning, info, debug)"),
|
|
361
|
+
] = "error",
|
|
345
362
|
):
|
|
346
363
|
"""Monitor your machine's carbon emissions."""
|
|
347
364
|
|
|
@@ -349,6 +366,7 @@ def monitor(
|
|
|
349
366
|
tracker_args = {
|
|
350
367
|
"measure_power_secs": measure_power_secs,
|
|
351
368
|
"api_call_interval": api_call_interval,
|
|
369
|
+
"log_level": log_level,
|
|
352
370
|
}
|
|
353
371
|
# Set up the tracker arguments based on mode (offline vs online) and validate required args for each mode
|
|
354
372
|
if offline:
|
|
@@ -375,8 +393,12 @@ def monitor(
|
|
|
375
393
|
|
|
376
394
|
tracker_args = {**tracker_args, "save_to_api": api}
|
|
377
395
|
|
|
396
|
+
from codecarbon.emissions_tracker import EmissionsTracker, OfflineEmissionsTracker
|
|
397
|
+
|
|
378
398
|
# If extra args are provided (e.g. `codecarbon monitor -- my_script.py`), delegate to `run_and_monitor`
|
|
379
399
|
if getattr(ctx, "args", None):
|
|
400
|
+
from codecarbon.cli.monitor import run_and_monitor
|
|
401
|
+
|
|
380
402
|
return run_and_monitor(ctx, offline=offline, **tracker_args)
|
|
381
403
|
|
|
382
404
|
# Instantiate the tracker
|
|
@@ -417,6 +439,8 @@ def detect():
|
|
|
417
439
|
"""
|
|
418
440
|
Detects hardware and prints information without running any measurements.
|
|
419
441
|
"""
|
|
442
|
+
from codecarbon.emissions_tracker import EmissionsTracker
|
|
443
|
+
|
|
420
444
|
print("Detecting hardware...")
|
|
421
445
|
tracker = EmissionsTracker(save_to_file=False)
|
|
422
446
|
hardware_info = tracker.get_detected_hardware()
|
|
@@ -438,6 +462,8 @@ def detect():
|
|
|
438
462
|
|
|
439
463
|
|
|
440
464
|
def questionary_prompt(prompt, list_options, default):
|
|
465
|
+
import questionary
|
|
466
|
+
|
|
441
467
|
value = questionary.select(
|
|
442
468
|
prompt,
|
|
443
469
|
list_options,
|
|
@@ -8,8 +8,6 @@ import typer
|
|
|
8
8
|
from rich import print
|
|
9
9
|
from typing_extensions import Annotated
|
|
10
10
|
|
|
11
|
-
from codecarbon.emissions_tracker import EmissionsTracker, OfflineEmissionsTracker
|
|
12
|
-
|
|
13
11
|
|
|
14
12
|
def run_and_monitor(
|
|
15
13
|
ctx: typer.Context,
|
|
@@ -50,12 +48,15 @@ def run_and_monitor(
|
|
|
50
48
|
directory. The file path is shown in the final report.
|
|
51
49
|
"""
|
|
52
50
|
# Suppress all CodeCarbon logs during execution
|
|
51
|
+
from codecarbon.emissions_tracker import EmissionsTracker, OfflineEmissionsTracker
|
|
53
52
|
from codecarbon.external.logger import set_logger_level
|
|
54
53
|
|
|
55
54
|
set_logger_level(log_level)
|
|
56
55
|
|
|
57
|
-
# Get the command from remaining args
|
|
58
|
-
command = ctx
|
|
56
|
+
# Get the command from remaining args (strip nested subcommand / `--` leftovers)
|
|
57
|
+
command = list(getattr(ctx, "args", None) or [])
|
|
58
|
+
while command and command[0] in ("monitor", "--"):
|
|
59
|
+
command.pop(0)
|
|
59
60
|
|
|
60
61
|
if not command:
|
|
61
62
|
print(
|
|
@@ -10,7 +10,6 @@ import dataclasses
|
|
|
10
10
|
import json
|
|
11
11
|
from datetime import timedelta, tzinfo
|
|
12
12
|
|
|
13
|
-
import arrow
|
|
14
13
|
import requests
|
|
15
14
|
|
|
16
15
|
from codecarbon.core.schemas import (
|
|
@@ -22,12 +21,11 @@ from codecarbon.core.schemas import (
|
|
|
22
21
|
)
|
|
23
22
|
from codecarbon.external.logger import logger
|
|
24
23
|
|
|
25
|
-
# from codecarbon.output import EmissionsData
|
|
26
|
-
|
|
27
24
|
|
|
28
25
|
def get_datetime_with_timezone():
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
import arrow
|
|
27
|
+
|
|
28
|
+
return str(arrow.now().isoformat())
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
class ApiClient: # (AsyncClient)
|
|
@@ -4,14 +4,16 @@ using Intel Power Gadget
|
|
|
4
4
|
https://software.intel.com/content/www/us/en/develop/articles/intel-power-gadget.html
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
7
9
|
import os
|
|
8
10
|
import re
|
|
9
11
|
import shutil
|
|
10
12
|
import subprocess
|
|
11
13
|
import sys
|
|
12
|
-
from
|
|
14
|
+
from functools import lru_cache
|
|
15
|
+
from typing import TYPE_CHECKING, Dict, Optional, Tuple
|
|
13
16
|
|
|
14
|
-
import pandas as pd
|
|
15
17
|
import psutil
|
|
16
18
|
from rapidfuzz import fuzz, process, utils
|
|
17
19
|
|
|
@@ -19,12 +21,15 @@ from codecarbon.core.rapl import RAPLFile
|
|
|
19
21
|
from codecarbon.core.units import Time
|
|
20
22
|
from codecarbon.core.util import count_cpus, detect_cpu_model
|
|
21
23
|
from codecarbon.external.logger import logger
|
|
22
|
-
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
import pandas as pd
|
|
23
27
|
|
|
24
28
|
# default W value per core for a CPU if no model is found in the ref csv
|
|
25
29
|
DEFAULT_POWER_PER_CORE = 4
|
|
26
30
|
|
|
27
31
|
|
|
32
|
+
@lru_cache(maxsize=1)
|
|
28
33
|
def is_powergadget_available() -> bool:
|
|
29
34
|
"""
|
|
30
35
|
Checks if Intel Power Gadget is available on the system.
|
|
@@ -44,6 +49,10 @@ def is_powergadget_available() -> bool:
|
|
|
44
49
|
return False
|
|
45
50
|
|
|
46
51
|
|
|
52
|
+
def clear_powergadget_cache() -> None:
|
|
53
|
+
is_powergadget_available.cache_clear()
|
|
54
|
+
|
|
55
|
+
|
|
47
56
|
def _get_candidate_bases(rapl_dir: str) -> list:
|
|
48
57
|
"""Get list of directories to scan for RAPL files."""
|
|
49
58
|
default_rapl_dir = "/sys/class/powercap/intel-rapl/subsystem"
|
|
@@ -366,6 +375,8 @@ class IntelPowerGadget:
|
|
|
366
375
|
self._log_values()
|
|
367
376
|
cpu_details = {}
|
|
368
377
|
try:
|
|
378
|
+
import pandas as pd
|
|
379
|
+
|
|
369
380
|
cpu_data = pd.read_csv(self._log_file_path).dropna()
|
|
370
381
|
for col_name in cpu_data.columns:
|
|
371
382
|
if col_name in ["System Time", "Elapsed Time (sec)", "RDTSC"]:
|
|
@@ -892,6 +903,8 @@ class TDP:
|
|
|
892
903
|
return float(cpu_power_df[cpu_power_df["Name"] == match]["TDP"].values[0])
|
|
893
904
|
|
|
894
905
|
def _get_cpu_power_from_registry(self, cpu_model_raw: str) -> Optional[int]:
|
|
906
|
+
from codecarbon.input import DataSource
|
|
907
|
+
|
|
895
908
|
cpu_power_df = DataSource().get_cpu_power_data()
|
|
896
909
|
cpu_matching = self._get_matching_cpu(cpu_model_raw, cpu_power_df)
|
|
897
910
|
if cpu_matching:
|
|
@@ -6,9 +6,7 @@ https://github.com/mlco2/impact
|
|
|
6
6
|
https://github.com/responsibleproblemsolving/energy-usage
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from typing import Dict, Optional
|
|
10
|
-
|
|
11
|
-
import pandas as pd
|
|
9
|
+
from typing import TYPE_CHECKING, Dict, Optional
|
|
12
10
|
|
|
13
11
|
from codecarbon.core import electricitymaps_api
|
|
14
12
|
from codecarbon.core.units import EmissionsPerKWh, Energy
|
|
@@ -16,6 +14,9 @@ from codecarbon.external.geography import CloudMetadata, GeoMetadata
|
|
|
16
14
|
from codecarbon.external.logger import logger
|
|
17
15
|
from codecarbon.input import DataSource, DataSourceException
|
|
18
16
|
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
import pandas as pd
|
|
19
|
+
|
|
19
20
|
_NORDIC_REGIONS_BY_COUNTRY = {
|
|
20
21
|
"SWE": {"SE1", "SE2", "SE3", "SE4"},
|
|
21
22
|
"NOR": {"NO1", "NO2", "NO3", "NO4", "NO5"},
|
|
@@ -137,6 +137,23 @@ class AllGPUDevices:
|
|
|
137
137
|
logger.warning("Failed to retrieve gpu information", exc_info=True)
|
|
138
138
|
return []
|
|
139
139
|
|
|
140
|
+
def get_gpu_utilization_list(self) -> List:
|
|
141
|
+
"""Lightweight alternative to :meth:`get_gpu_details` for the 1s
|
|
142
|
+
monitoring hot path. Returns only ``gpu_index`` and
|
|
143
|
+
``gpu_utilization`` per device, skipping heavyweight queries
|
|
144
|
+
(memory, temperature, compute mode, process lists).
|
|
145
|
+
|
|
146
|
+
>>> get_gpu_utilization_list()
|
|
147
|
+
[
|
|
148
|
+
{"gpu_index": 0, "gpu_utilization": 0},
|
|
149
|
+
]
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
return [d.get_gpu_utilization_lightweight() for d in self.devices]
|
|
153
|
+
except Exception:
|
|
154
|
+
logger.warning("Failed to retrieve gpu utilization", exc_info=True)
|
|
155
|
+
return []
|
|
156
|
+
|
|
140
157
|
def get_delta(self, last_duration: Time) -> List:
|
|
141
158
|
"""Get difference since last time this function was called
|
|
142
159
|
>>> get_delta()
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
from collections import namedtuple
|
|
3
|
+
from functools import lru_cache
|
|
3
4
|
from typing import Callable
|
|
4
5
|
|
|
5
6
|
from codecarbon.core.gpu_device import GPUDevice
|
|
6
7
|
from codecarbon.external.logger import logger
|
|
7
8
|
|
|
8
9
|
|
|
10
|
+
@lru_cache(maxsize=1)
|
|
9
11
|
def is_rocm_system():
|
|
10
12
|
"""Returns True if the system has an rocm-smi interface."""
|
|
11
13
|
try:
|
|
12
|
-
# Check if rocm-smi is available
|
|
13
14
|
subprocess.check_output(["rocm-smi", "--help"])
|
|
14
15
|
return True
|
|
15
16
|
except (subprocess.CalledProcessError, OSError):
|
|
16
17
|
return False
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def clear_rocm_system_cache() -> None:
|
|
21
|
+
is_rocm_system.cache_clear()
|
|
22
|
+
|
|
23
|
+
|
|
19
24
|
try:
|
|
20
25
|
import amdsmi
|
|
21
26
|
|
|
@@ -101,6 +101,20 @@ class GPUDevice:
|
|
|
101
101
|
}
|
|
102
102
|
return device_details
|
|
103
103
|
|
|
104
|
+
def get_gpu_utilization_lightweight(self) -> Dict[str, Any]:
|
|
105
|
+
"""
|
|
106
|
+
Lightweight alternative to :meth:`get_gpu_details` for the hot path
|
|
107
|
+
(``_monitor_power`` which runs every 1s).
|
|
108
|
+
|
|
109
|
+
Only queries the GPU utilization — avoids heavyweight calls like
|
|
110
|
+
memory info, temperature, compute mode, and process lists which are
|
|
111
|
+
not consumed by the tracker's monitoring loop.
|
|
112
|
+
"""
|
|
113
|
+
return {
|
|
114
|
+
"gpu_index": self.gpu_index,
|
|
115
|
+
"gpu_utilization": self._get_gpu_utilization(),
|
|
116
|
+
}
|
|
117
|
+
|
|
104
118
|
def _to_utf8(self, str_or_bytes) -> Any:
|
|
105
119
|
if hasattr(str_or_bytes, "decode"):
|
|
106
120
|
return str_or_bytes.decode("utf-8", errors="replace")
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
+
from functools import lru_cache
|
|
3
4
|
from typing import Any, Union
|
|
4
5
|
|
|
5
6
|
from codecarbon.core.gpu_device import GPUDevice
|
|
6
7
|
from codecarbon.external.logger import logger
|
|
7
8
|
|
|
8
9
|
|
|
10
|
+
@lru_cache(maxsize=1)
|
|
9
11
|
def is_nvidia_system():
|
|
10
12
|
"""Returns True if the system has an nvidia-smi interface."""
|
|
11
13
|
try:
|
|
12
|
-
# Check if nvidia-smi is available
|
|
13
14
|
subprocess.check_output(["nvidia-smi", "--help"])
|
|
14
15
|
return True
|
|
15
16
|
except Exception:
|
|
16
17
|
return False
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def clear_nvidia_system_cache() -> None:
|
|
21
|
+
is_nvidia_system.cache_clear()
|
|
22
|
+
|
|
23
|
+
|
|
19
24
|
try:
|
|
20
25
|
import pynvml
|
|
21
26
|
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Process-level cache for hardware detection and setup.
|
|
3
|
+
|
|
4
|
+
Reuses the outcome of the first tracker hardware probe so additional runs on
|
|
5
|
+
the same device (same process) skip repeated powermetrics, cpuinfo, and GPU
|
|
6
|
+
detection work.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import threading
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
|
|
15
|
+
|
|
16
|
+
from codecarbon.core.config import normalize_gpu_ids
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from codecarbon.core.resource_tracker import ResourceTracker
|
|
20
|
+
|
|
21
|
+
DEFAULT_RAPL_DIR = "/sys/class/powercap/intel-rapl/subsystem"
|
|
22
|
+
|
|
23
|
+
CONF_KEYS = (
|
|
24
|
+
"ram_total_size",
|
|
25
|
+
"cpu_count",
|
|
26
|
+
"cpu_physical_count",
|
|
27
|
+
"cpu_model",
|
|
28
|
+
"gpu_count",
|
|
29
|
+
"gpu_model",
|
|
30
|
+
"gpu_ids",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class HardwareKind(str, Enum):
|
|
35
|
+
RAM = "ram"
|
|
36
|
+
CPU = "cpu"
|
|
37
|
+
APPLE_CHIP = "apple_chip"
|
|
38
|
+
GPU = "gpu"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_cache_lock = threading.Lock()
|
|
42
|
+
_plans: Dict["_HardwareCacheKey", "_HardwarePlan"] = {}
|
|
43
|
+
_tdp = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class _HardwareCacheKey:
|
|
48
|
+
tracking_mode: str
|
|
49
|
+
force_cpu_power: Any
|
|
50
|
+
force_ram_power: Any
|
|
51
|
+
force_mode_cpu_load: bool
|
|
52
|
+
gpu_ids: Any
|
|
53
|
+
rapl_include_dram: bool
|
|
54
|
+
rapl_prefer_psys: bool
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class _HardwarePlan:
|
|
59
|
+
ram_tracker: str
|
|
60
|
+
cpu_tracker: str
|
|
61
|
+
gpu_tracker: str
|
|
62
|
+
conf: Dict[str, Any] = field(default_factory=dict)
|
|
63
|
+
hardware_specs: List[Dict[str, Any]] = field(default_factory=list)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _canonical_gpu_ids(
|
|
67
|
+
gpu_ids: Optional[List],
|
|
68
|
+
) -> Optional[Tuple[str, ...]]:
|
|
69
|
+
"""Normalize GPU ids to a stable cache-key form (tuple of strings)."""
|
|
70
|
+
if gpu_ids is None:
|
|
71
|
+
return None
|
|
72
|
+
if not isinstance(gpu_ids, (list, tuple)):
|
|
73
|
+
gpu_ids = [gpu_ids]
|
|
74
|
+
normalized = normalize_gpu_ids(list(gpu_ids))
|
|
75
|
+
if not normalized:
|
|
76
|
+
return None
|
|
77
|
+
return tuple(str(gpu_id) for gpu_id in normalized)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def make_key(tracker) -> _HardwareCacheKey:
|
|
81
|
+
return _HardwareCacheKey(
|
|
82
|
+
tracking_mode=tracker._tracking_mode,
|
|
83
|
+
force_cpu_power=tracker._force_cpu_power,
|
|
84
|
+
force_ram_power=tracker._force_ram_power,
|
|
85
|
+
force_mode_cpu_load=bool(tracker._conf.get("force_mode_cpu_load", False)),
|
|
86
|
+
gpu_ids=_canonical_gpu_ids(tracker._gpu_ids),
|
|
87
|
+
rapl_include_dram=bool(tracker._rapl_include_dram),
|
|
88
|
+
rapl_prefer_psys=bool(tracker._rapl_prefer_psys),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_cached_tdp(cpu_module):
|
|
93
|
+
"""Return a shared cpu.TDP() instance for this process."""
|
|
94
|
+
global _tdp
|
|
95
|
+
if _tdp is None:
|
|
96
|
+
_tdp = cpu_module.TDP()
|
|
97
|
+
return _tdp
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _hardware_kind(hw) -> HardwareKind:
|
|
101
|
+
"""Classify hardware without isinstance (safe if modules were reloaded)."""
|
|
102
|
+
name = type(hw).__name__
|
|
103
|
+
if name == "RAM":
|
|
104
|
+
return HardwareKind.RAM
|
|
105
|
+
if name == "CPU":
|
|
106
|
+
return HardwareKind.CPU
|
|
107
|
+
if name == "AppleSiliconChip":
|
|
108
|
+
return HardwareKind.APPLE_CHIP
|
|
109
|
+
if name == "GPU":
|
|
110
|
+
return HardwareKind.GPU
|
|
111
|
+
raise TypeError(f"Unsupported hardware type for cache: {type(hw)}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _spec_from_hardware(hw) -> Dict[str, Any]:
|
|
115
|
+
kind = _hardware_kind(hw)
|
|
116
|
+
if kind == HardwareKind.RAM:
|
|
117
|
+
return {
|
|
118
|
+
"kind": kind.value,
|
|
119
|
+
"tracking_mode": hw._tracking_mode,
|
|
120
|
+
"force_ram_power": hw._force_ram_power,
|
|
121
|
+
}
|
|
122
|
+
if kind == HardwareKind.CPU:
|
|
123
|
+
spec: Dict[str, Any] = {
|
|
124
|
+
"kind": kind.value,
|
|
125
|
+
"mode": hw._mode,
|
|
126
|
+
"model": hw._model,
|
|
127
|
+
"tdp": hw._tdp,
|
|
128
|
+
"tracking_mode": hw._tracking_mode,
|
|
129
|
+
"rapl_include_dram": False,
|
|
130
|
+
"rapl_prefer_psys": False,
|
|
131
|
+
}
|
|
132
|
+
if hw._mode == "intel_rapl" and hasattr(hw, "_intel_interface"):
|
|
133
|
+
intel = hw._intel_interface
|
|
134
|
+
spec["rapl_include_dram"] = getattr(intel, "rapl_include_dram", False)
|
|
135
|
+
spec["rapl_prefer_psys"] = getattr(intel, "rapl_prefer_psys", False)
|
|
136
|
+
spec["rapl_dir"] = getattr(intel, "_lin_rapl_dir", DEFAULT_RAPL_DIR)
|
|
137
|
+
return spec
|
|
138
|
+
if kind == HardwareKind.APPLE_CHIP:
|
|
139
|
+
return {
|
|
140
|
+
"kind": kind.value,
|
|
141
|
+
"model": hw._model,
|
|
142
|
+
"chip_part": hw.chip_part,
|
|
143
|
+
}
|
|
144
|
+
if kind == HardwareKind.GPU:
|
|
145
|
+
gpu_ids = _canonical_gpu_ids(hw.gpu_ids)
|
|
146
|
+
return {"kind": kind.value, "gpu_ids": list(gpu_ids) if gpu_ids else None}
|
|
147
|
+
raise TypeError(f"Unsupported hardware type for cache: {type(hw)}")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _hardware_from_spec(spec: Dict[str, Any], output_dir: str):
|
|
151
|
+
from codecarbon.external.hardware import CPU, GPU, AppleSiliconChip
|
|
152
|
+
from codecarbon.external.ram import RAM
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
kind = HardwareKind(spec["kind"])
|
|
156
|
+
except ValueError as exc:
|
|
157
|
+
raise ValueError(f"Unknown hardware spec kind: {spec['kind']}") from exc
|
|
158
|
+
|
|
159
|
+
if kind == HardwareKind.RAM:
|
|
160
|
+
return RAM(
|
|
161
|
+
tracking_mode=spec["tracking_mode"],
|
|
162
|
+
force_ram_power=spec.get("force_ram_power"),
|
|
163
|
+
)
|
|
164
|
+
if kind == HardwareKind.CPU:
|
|
165
|
+
return CPU(
|
|
166
|
+
output_dir=output_dir,
|
|
167
|
+
mode=spec["mode"],
|
|
168
|
+
model=spec["model"],
|
|
169
|
+
tdp=spec["tdp"],
|
|
170
|
+
tracking_mode=spec["tracking_mode"],
|
|
171
|
+
rapl_dir=spec.get("rapl_dir", DEFAULT_RAPL_DIR),
|
|
172
|
+
rapl_include_dram=spec.get("rapl_include_dram", False),
|
|
173
|
+
rapl_prefer_psys=spec.get("rapl_prefer_psys", False),
|
|
174
|
+
)
|
|
175
|
+
if kind == HardwareKind.APPLE_CHIP:
|
|
176
|
+
return AppleSiliconChip(
|
|
177
|
+
output_dir=output_dir,
|
|
178
|
+
model=spec["model"],
|
|
179
|
+
chip_part=spec["chip_part"],
|
|
180
|
+
)
|
|
181
|
+
if kind == HardwareKind.GPU:
|
|
182
|
+
gpu_ids = _canonical_gpu_ids(spec.get("gpu_ids"))
|
|
183
|
+
return GPU.from_utils(gpu_ids=list(gpu_ids) if gpu_ids else None)
|
|
184
|
+
raise ValueError(f"Unknown hardware spec kind: {kind}")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def capture(resource_tracker: "ResourceTracker") -> _HardwarePlan:
|
|
188
|
+
tracker = resource_tracker.tracker
|
|
189
|
+
conf = {k: tracker._conf[k] for k in CONF_KEYS if k in tracker._conf}
|
|
190
|
+
return _HardwarePlan(
|
|
191
|
+
ram_tracker=resource_tracker.ram_tracker,
|
|
192
|
+
cpu_tracker=resource_tracker.cpu_tracker,
|
|
193
|
+
gpu_tracker=resource_tracker.gpu_tracker,
|
|
194
|
+
conf=conf,
|
|
195
|
+
hardware_specs=[_spec_from_hardware(hw) for hw in tracker._hardware],
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def apply(resource_tracker: "ResourceTracker", plan: _HardwarePlan) -> None:
|
|
200
|
+
tracker = resource_tracker.tracker
|
|
201
|
+
resource_tracker.ram_tracker = plan.ram_tracker
|
|
202
|
+
resource_tracker.cpu_tracker = plan.cpu_tracker
|
|
203
|
+
resource_tracker.gpu_tracker = plan.gpu_tracker
|
|
204
|
+
tracker._conf.update(plan.conf)
|
|
205
|
+
if "gpu_ids" in plan.conf:
|
|
206
|
+
tracker._gpu_ids = plan.conf["gpu_ids"]
|
|
207
|
+
tracker._hardware = [
|
|
208
|
+
_hardware_from_spec(spec, tracker._output_dir) for spec in plan.hardware_specs
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def get_or_run_setup(
|
|
213
|
+
resource_tracker: "ResourceTracker",
|
|
214
|
+
setup_fn,
|
|
215
|
+
) -> None:
|
|
216
|
+
"""Apply cached hardware plan or run full setup once per cache key."""
|
|
217
|
+
key = make_key(resource_tracker.tracker)
|
|
218
|
+
with _cache_lock:
|
|
219
|
+
plan = _plans.get(key)
|
|
220
|
+
if plan is not None:
|
|
221
|
+
apply(resource_tracker, plan)
|
|
222
|
+
return
|
|
223
|
+
setup_fn()
|
|
224
|
+
_plans[key] = capture(resource_tracker)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def clear_cache() -> None:
|
|
228
|
+
"""Clear cached plans (for tests)."""
|
|
229
|
+
global _tdp
|
|
230
|
+
import sys
|
|
231
|
+
|
|
232
|
+
with _cache_lock:
|
|
233
|
+
_plans.clear()
|
|
234
|
+
_tdp = None
|
|
235
|
+
|
|
236
|
+
for mod_name, clear_fn in (
|
|
237
|
+
("codecarbon.core.gpu_nvidia", "clear_nvidia_system_cache"),
|
|
238
|
+
("codecarbon.core.gpu_amd", "clear_rocm_system_cache"),
|
|
239
|
+
("codecarbon.core.cpu", "clear_powergadget_cache"),
|
|
240
|
+
("codecarbon.core.powermetrics", "clear_powermetrics_cache"),
|
|
241
|
+
):
|
|
242
|
+
mod = sys.modules.get(mod_name)
|
|
243
|
+
if mod is not None:
|
|
244
|
+
getattr(mod, clear_fn)()
|
|
245
|
+
|
|
246
|
+
if "codecarbon.external.hardware" in sys.modules:
|
|
247
|
+
from codecarbon.external.hardware import clear_cpu_load_prime_cache
|
|
248
|
+
|
|
249
|
+
clear_cpu_load_prime_cache()
|
|
@@ -3,6 +3,8 @@ import re
|
|
|
3
3
|
import shutil
|
|
4
4
|
import subprocess
|
|
5
5
|
import sys
|
|
6
|
+
import time
|
|
7
|
+
from functools import lru_cache
|
|
6
8
|
from typing import Dict
|
|
7
9
|
|
|
8
10
|
import numpy as np
|
|
@@ -11,11 +13,11 @@ from codecarbon.core.util import detect_cpu_model
|
|
|
11
13
|
from codecarbon.external.logger import logger
|
|
12
14
|
|
|
13
15
|
|
|
16
|
+
@lru_cache(maxsize=1)
|
|
14
17
|
def is_powermetrics_available() -> bool:
|
|
15
18
|
try:
|
|
16
19
|
ApplePowermetrics()
|
|
17
|
-
|
|
18
|
-
return response
|
|
20
|
+
return _has_powermetrics_sudo()
|
|
19
21
|
except Exception as e:
|
|
20
22
|
logger.debug(
|
|
21
23
|
"Not using PowerMetrics, an exception occurred while instantiating"
|
|
@@ -24,6 +26,10 @@ def is_powermetrics_available() -> bool:
|
|
|
24
26
|
return False
|
|
25
27
|
|
|
26
28
|
|
|
29
|
+
def clear_powermetrics_cache() -> None:
|
|
30
|
+
is_powermetrics_available.cache_clear()
|
|
31
|
+
|
|
32
|
+
|
|
27
33
|
def _has_powermetrics_sudo() -> bool:
|
|
28
34
|
if shutil.which("sudo") is None:
|
|
29
35
|
logger.debug("sudo not available, we won't use Apple PowerMetrics.")
|
|
@@ -51,6 +57,13 @@ def _has_powermetrics_sudo() -> bool:
|
|
|
51
57
|
stderr=subprocess.PIPE,
|
|
52
58
|
text=True,
|
|
53
59
|
) as process:
|
|
60
|
+
deadline = time.time() + 3
|
|
61
|
+
while process.poll() is None and time.time() < deadline:
|
|
62
|
+
time.sleep(0.05)
|
|
63
|
+
if process.poll() is None:
|
|
64
|
+
process.kill()
|
|
65
|
+
logger.debug("PowerMetrics sudo check timed out.")
|
|
66
|
+
return False
|
|
54
67
|
_, stderr = process.communicate()
|
|
55
68
|
|
|
56
69
|
if re.search(r"[sudo].*password", stderr):
|