codecarbon 3.3.0__tar.gz → 3.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codecarbon-3.3.0 → codecarbon-3.3.2}/PKG-INFO +15 -1
- codecarbon-3.3.2/codecarbon/_version.py +1 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/cli/auth.py +10 -2
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/cli/main.py +53 -7
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/cli/monitor.py +14 -11
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/api_client.py +7 -2
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/config.py +85 -30
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/cpu.py +120 -14
- codecarbon-3.3.2/codecarbon/core/electricitymaps_api.py +152 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/emissions.py +49 -4
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/gpu.py +13 -1
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/gpu_amd.py +11 -3
- codecarbon-3.3.2/codecarbon/core/gpu_device.py +298 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/gpu_nvidia.py +3 -2
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/hardware_cache.py +16 -6
- codecarbon-3.3.2/codecarbon/core/powermetrics.py +242 -0
- codecarbon-3.3.2/codecarbon/core/rapl.py +157 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/resource_tracker.py +27 -2
- codecarbon-3.3.2/codecarbon/core/slurm.py +30 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/windows_emi.py +4 -23
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/data/hardware/cpu_power.csv +15 -0
- codecarbon-3.3.2/codecarbon/data/private_infra/2023/china_provincial_emissions.json +292 -0
- {codecarbon-3.3.0/codecarbon/data/private_infra/2016 → codecarbon-3.3.2/codecarbon/data/private_infra/2023}/usa_emissions.json +157 -151
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/data/private_infra/carbon_intensity_per_source.json +7 -1
- codecarbon-3.3.2/codecarbon/data/private_infra/nordic_emissions.json +89 -0
- codecarbon-3.3.2/codecarbon/data/private_infra/update_usa_emissions.py +145 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/emissions_tracker.py +162 -46
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/external/geography.py +1 -1
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/external/hardware.py +209 -50
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/external/ram.py +4 -3
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/input.py +14 -1
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/boamps/output.py +29 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/file.py +31 -19
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/viz/carbonboard.py +2 -2
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon.egg-info/PKG-INFO +15 -1
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon.egg-info/SOURCES.txt +10 -1
- {codecarbon-3.3.0 → codecarbon-3.3.2}/pyproject.toml +38 -7
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_api_call.py +33 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_boamps_output.py +6 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_config.py +61 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_cpu.py +55 -0
- codecarbon-3.3.2/tests/test_cpu_load.py +384 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_electricitymaps_api.py +13 -0
- codecarbon-3.3.2/tests/test_electricitymaps_cache.py +183 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_emissions.py +235 -4
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_emissions_tracker.py +290 -1
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_emissions_tracker_constant.py +36 -1
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_emissions_tracker_flush.py +13 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_geography.py +15 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_gpu_amd.py +35 -6
- codecarbon-3.3.2/tests/test_gpu_nvidia.py +938 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_hardware_cache.py +34 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_powermetrics.py +167 -8
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_ram.py +6 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_rapl_mmio_scanning.py +229 -0
- codecarbon-3.3.2/tests/test_rapl_permissions.py +278 -0
- codecarbon-3.3.2/tests/test_raspberry.py +253 -0
- codecarbon-3.3.2/tests/test_readme_links.py +26 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_resource_tracker.py +73 -0
- codecarbon-3.3.2/tests/test_slurm.py +40 -0
- codecarbon-3.3.2/tests/test_update_usa_emissions.py +102 -0
- codecarbon-3.3.2/tests/test_zero_carbon_intensity.py +34 -0
- codecarbon-3.3.0/codecarbon/_version.py +0 -1
- codecarbon-3.3.0/codecarbon/core/electricitymaps_api.py +0 -69
- codecarbon-3.3.0/codecarbon/core/gpu_device.py +0 -130
- codecarbon-3.3.0/codecarbon/core/powermetrics.py +0 -211
- codecarbon-3.3.0/codecarbon/core/rapl.py +0 -84
- codecarbon-3.3.0/codecarbon/data/private_infra/nordic_emissions.json +0 -69
- codecarbon-3.3.0/tests/test_cpu_load.py +0 -195
- codecarbon-3.3.0/tests/test_gpu_nvidia.py +0 -469
- codecarbon-3.3.0/tests/test_rapl_permissions.py +0 -112
- {codecarbon-3.3.0 → codecarbon-3.3.2}/LICENSE +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/README.md +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/__init__.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/cli/__init__.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/cli/cli_utils.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/__init__.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/cloud.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/measure.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/schemas.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/telemetry_client.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/telemetry_schemas.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/units.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/core/util.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/data/canada_provinces.geojson +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/data/cloud/impact.csv +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/data/hardware/cpu_dataset_builder/amd_cpu_scrapper.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/data/hardware/cpu_dataset_builder/intel_cpu_scrapper.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/data/hardware/cpu_dataset_builder/merge_scrapped_cpu_power.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/data/private_infra/2023/canada_energy_mix.json +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/data/private_infra/global_energy_mix.json +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/external/__init__.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/external/logger.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/external/scheduler.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/external/task.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/lock.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/__init__.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/base_output.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/boamps/__init__.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/boamps/mapper.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/boamps/models.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/emissions_data.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/http.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/logger.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/metrics/__init__.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/metrics/logfire.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/metrics/metric_docs.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/output_methods/metrics/prometheus.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/py.typed +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/viz/__init__.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/viz/assets/__init__.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/viz/assets/car_icon.png +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/viz/assets/house_icon.png +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/viz/assets/tv_icon.png +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/viz/carbonboard_on_api.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/viz/components.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/viz/data.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon/viz/units.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon.egg-info/dependency_links.txt +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon.egg-info/entry_points.txt +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon.egg-info/requires.txt +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/codecarbon.egg-info/top_level.txt +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/setup.cfg +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_cloud.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_core_util.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_custom_handler.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_docs_examples.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_electricitymaps_backward_compatibility.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_electricitymaps_config_backward_compatibility.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_energy.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_gpu.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_input.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_lock.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_logger.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_logging_output.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_offline_emissions_tracker.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_package_integrity.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_rapl_parameters.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_telemetry_client.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_tracking_inference.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_unsupported_gpu.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_utilization_tracking.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_viz_data.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_viz_units.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/test_windows_emi.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/testdata.py +0 -0
- {codecarbon-3.3.0 → codecarbon-3.3.2}/tests/testutils.py +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecarbon
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.2
|
|
4
|
+
Summary: Track and reduce the CO2 emissions of your computing
|
|
4
5
|
Author: Mila, DataForGood, BCG GAMMA, Comet.ml, Haverford College
|
|
5
6
|
License-Expression: MIT
|
|
6
7
|
Project-URL: Homepage, https://codecarbon.io/
|
|
@@ -9,12 +10,25 @@ Project-URL: Dashboard, http://dashboard.codecarbon.io/
|
|
|
9
10
|
Project-URL: Documentation, https://docs.codecarbon.io/
|
|
10
11
|
Project-URL: Issues, https://github.com/mlco2/codecarbon/issues
|
|
11
12
|
Project-URL: Changelog, https://github.com/mlco2/codecarbon/releases
|
|
13
|
+
Keywords: carbon,co2,emissions,carbon-footprint,sustainability,green-ai,green-computing,energy,energy-consumption,power-consumption,rapl,gpu,machine-learning,mlops,monitoring
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Intended Audience :: Science/Research
|
|
18
|
+
Classifier: Intended Audience :: System Administrators
|
|
12
19
|
Classifier: Natural Language :: English
|
|
20
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
21
|
+
Classifier: Operating System :: MacOS
|
|
22
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
13
23
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
24
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
25
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
26
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
27
|
Classifier: Programming Language :: Python :: 3.14
|
|
28
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
29
|
+
Classifier: Topic :: System :: Monitoring
|
|
30
|
+
Classifier: Topic :: System :: Benchmark
|
|
31
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
32
|
Requires-Python: >=3.10
|
|
19
33
|
Description-Content-Type: text/markdown
|
|
20
34
|
License-File: LICENSE
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.3.2"
|
|
@@ -86,8 +86,15 @@ def _discover_endpoints():
|
|
|
86
86
|
|
|
87
87
|
def _save_credentials(tokens):
|
|
88
88
|
"""Save OAuth tokens to the local credentials file."""
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
try:
|
|
90
|
+
with open(_CREDENTIALS_FILE, "w") as f:
|
|
91
|
+
json.dump(tokens, f)
|
|
92
|
+
except OSError as e:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"Could not write the credentials file {_CREDENTIALS_FILE.resolve()} "
|
|
95
|
+
f"(error: {e}). Please run the command from a directory you can "
|
|
96
|
+
"write to."
|
|
97
|
+
)
|
|
91
98
|
|
|
92
99
|
|
|
93
100
|
def _load_credentials():
|
|
@@ -169,6 +176,7 @@ def authorize():
|
|
|
169
176
|
server = HTTPServer(("localhost", _REDIRECT_PORT), _CallbackHandler)
|
|
170
177
|
|
|
171
178
|
print("Opening browser for authentication...")
|
|
179
|
+
print(f"If no browser opens, copy this URL into your browser:\n{uri}")
|
|
172
180
|
webbrowser.open(uri)
|
|
173
181
|
|
|
174
182
|
server.handle_request()
|
|
@@ -358,6 +358,34 @@ def config():
|
|
|
358
358
|
)
|
|
359
359
|
|
|
360
360
|
|
|
361
|
+
def _cli_provided(ctx, name: str) -> bool:
|
|
362
|
+
"""
|
|
363
|
+
Whether the option `name` was typed on the command line (or set through its
|
|
364
|
+
environment variable) rather than left at its Typer default.
|
|
365
|
+
|
|
366
|
+
Options left at their default must not be forwarded to the tracker: doing so
|
|
367
|
+
would silently override the values coming from `.codecarbon.config` and the
|
|
368
|
+
`CODECARBON_*` environment variables, which the tracker reads itself.
|
|
369
|
+
"""
|
|
370
|
+
get_source = getattr(ctx, "get_parameter_source", None)
|
|
371
|
+
if get_source is None:
|
|
372
|
+
# `monitor` called directly from Python, not through Click: every value
|
|
373
|
+
# given is explicit.
|
|
374
|
+
return True
|
|
375
|
+
source = get_source(name)
|
|
376
|
+
return source is None or source.name in ("COMMANDLINE", "ENVIRONMENT")
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _external_config() -> dict:
|
|
380
|
+
"""The configuration files and CODECARBON_* variables, as a plain dict."""
|
|
381
|
+
from codecarbon.core.config import get_hierarchical_config
|
|
382
|
+
|
|
383
|
+
try:
|
|
384
|
+
return dict(get_hierarchical_config())
|
|
385
|
+
except Exception:
|
|
386
|
+
return {}
|
|
387
|
+
|
|
388
|
+
|
|
361
389
|
@codecarbon.command(
|
|
362
390
|
"monitor",
|
|
363
391
|
short_help="Monitor your machine's carbon emissions.",
|
|
@@ -365,6 +393,10 @@ def config():
|
|
|
365
393
|
)
|
|
366
394
|
def monitor(
|
|
367
395
|
ctx: typer.Context,
|
|
396
|
+
project_name: Annotated[
|
|
397
|
+
str,
|
|
398
|
+
typer.Option(help="Project name for the current experiment."),
|
|
399
|
+
] = None,
|
|
368
400
|
measure_power_secs: Annotated[
|
|
369
401
|
int,
|
|
370
402
|
typer.Option(help="Interval between two measures."),
|
|
@@ -393,15 +425,29 @@ def monitor(
|
|
|
393
425
|
):
|
|
394
426
|
"""Monitor your machine's carbon emissions."""
|
|
395
427
|
|
|
396
|
-
|
|
428
|
+
external_conf = _external_config()
|
|
429
|
+
|
|
430
|
+
# Shared tracker args so monitor and run_and_monitor behave the same.
|
|
431
|
+
# Only the options actually given are forwarded: the others are left to the
|
|
432
|
+
# tracker, which resolves them from the configuration file and environment.
|
|
433
|
+
cli_defaults = (
|
|
434
|
+
("project_name", project_name),
|
|
435
|
+
("measure_power_secs", measure_power_secs),
|
|
436
|
+
("api_call_interval", api_call_interval),
|
|
437
|
+
("log_level", log_level),
|
|
438
|
+
)
|
|
397
439
|
tracker_args = {
|
|
398
|
-
|
|
399
|
-
"api_call_interval": api_call_interval,
|
|
400
|
-
"log_level": log_level,
|
|
440
|
+
name: value for name, value in cli_defaults if _cli_provided(ctx, name)
|
|
401
441
|
}
|
|
442
|
+
for name, value in cli_defaults:
|
|
443
|
+
if name not in tracker_args and name not in external_conf:
|
|
444
|
+
# Nothing configures it: keep the defaults advertised by `--help`
|
|
445
|
+
# (and an unattended monitor quiet) instead of the tracker's own.
|
|
446
|
+
tracker_args[name] = value
|
|
447
|
+
|
|
402
448
|
# Set up the tracker arguments based on mode (offline vs online) and validate required args for each mode
|
|
403
449
|
if offline:
|
|
404
|
-
if not country_iso_code:
|
|
450
|
+
if not country_iso_code and not external_conf.get("country_iso_code"):
|
|
405
451
|
print(
|
|
406
452
|
"ERROR: Country ISO code is required for offline mode. Add it to your configuration or provide it via the command line: `--country-iso-code FRA`",
|
|
407
453
|
file=sys.stderr,
|
|
@@ -410,8 +456,8 @@ def monitor(
|
|
|
410
456
|
|
|
411
457
|
tracker_args = {
|
|
412
458
|
**tracker_args,
|
|
413
|
-
"country_iso_code": country_iso_code,
|
|
414
|
-
"region": region,
|
|
459
|
+
**({"country_iso_code": country_iso_code} if country_iso_code else {}),
|
|
460
|
+
**({"region": region} if region else {}),
|
|
415
461
|
}
|
|
416
462
|
else:
|
|
417
463
|
experiment_id = get_existing_exp_id()
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import os
|
|
4
4
|
import subprocess
|
|
5
5
|
import sys
|
|
6
|
+
from typing import Optional
|
|
6
7
|
|
|
7
8
|
import typer
|
|
8
9
|
from rich import print
|
|
@@ -12,9 +13,9 @@ from typing_extensions import Annotated
|
|
|
12
13
|
def run_and_monitor(
|
|
13
14
|
ctx: typer.Context,
|
|
14
15
|
log_level: Annotated[
|
|
15
|
-
str,
|
|
16
|
+
Optional[str],
|
|
16
17
|
typer.Option(help="Log level (critical, error, warning, info, debug)"),
|
|
17
|
-
] =
|
|
18
|
+
] = None,
|
|
18
19
|
offline: bool = False,
|
|
19
20
|
**tracker_args,
|
|
20
21
|
):
|
|
@@ -51,7 +52,11 @@ def run_and_monitor(
|
|
|
51
52
|
from codecarbon.emissions_tracker import EmissionsTracker, OfflineEmissionsTracker
|
|
52
53
|
from codecarbon.external.logger import set_logger_level
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
# `log_level` is None when nothing set it: leave it to the tracker, which
|
|
56
|
+
# resolves it from the configuration file and the environment.
|
|
57
|
+
if log_level is not None:
|
|
58
|
+
set_logger_level(log_level)
|
|
59
|
+
tracker_args["log_level"] = log_level
|
|
55
60
|
|
|
56
61
|
# Get the command from remaining args (strip nested subcommand / `--` leftovers)
|
|
57
62
|
command = list(getattr(ctx, "args", None) or [])
|
|
@@ -67,7 +72,6 @@ def run_and_monitor(
|
|
|
67
72
|
|
|
68
73
|
tracker_cls = OfflineEmissionsTracker if offline else EmissionsTracker
|
|
69
74
|
tracker = tracker_cls(
|
|
70
|
-
log_level=log_level,
|
|
71
75
|
save_to_logger=False,
|
|
72
76
|
tracking_mode="process",
|
|
73
77
|
**tracker_args,
|
|
@@ -116,13 +120,12 @@ def run_and_monitor(
|
|
|
116
120
|
else:
|
|
117
121
|
print(" Emissions: N/A")
|
|
118
122
|
|
|
119
|
-
# Show where the data was saved
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if
|
|
124
|
-
|
|
125
|
-
print(f" Saved to: {output_path}")
|
|
123
|
+
# Show where the data was saved, asking the output handlers themselves
|
|
124
|
+
# so the path stays right whatever `output_dir` / `output_file` are.
|
|
125
|
+
for handler in getattr(tracker, "_output_handlers", []):
|
|
126
|
+
save_file_path = getattr(handler, "save_file_path", None)
|
|
127
|
+
if save_file_path:
|
|
128
|
+
print(f" Saved to: {os.path.abspath(save_file_path)}")
|
|
126
129
|
|
|
127
130
|
print(" ⚠️ Note: Tracked the command process and its children")
|
|
128
131
|
print("=" * 60)
|
|
@@ -22,6 +22,11 @@ from codecarbon.core.schemas import (
|
|
|
22
22
|
from codecarbon.external.logger import logger
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def _round_or_none(value: float | None) -> float | None:
|
|
26
|
+
"""Round a coordinate, keeping None when it is unknown."""
|
|
27
|
+
return None if value is None else round(value, 1)
|
|
28
|
+
|
|
29
|
+
|
|
25
30
|
def get_datetime_with_timezone():
|
|
26
31
|
import arrow
|
|
27
32
|
|
|
@@ -242,8 +247,8 @@ class ApiClient: # (AsyncClient)
|
|
|
242
247
|
gpu_count=self.conf.get("gpu_count"),
|
|
243
248
|
gpu_model=self.conf.get("gpu_model"),
|
|
244
249
|
# Reduce precision for Privacy
|
|
245
|
-
longitude=
|
|
246
|
-
latitude=
|
|
250
|
+
longitude=_round_or_none(self.conf.get("longitude")),
|
|
251
|
+
latitude=_round_or_none(self.conf.get("latitude")),
|
|
247
252
|
region=self.conf.get("region"),
|
|
248
253
|
provider=self.conf.get("provider"),
|
|
249
254
|
ram_total_size=self.conf.get("ram_total_size"),
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import configparser
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import List, Optional, Union
|
|
4
|
+
from typing import Dict, List, Optional, Tuple, Union
|
|
5
5
|
|
|
6
6
|
from codecarbon.external.logger import logger
|
|
7
7
|
|
|
8
|
+
CONFIG_FILE_NAME = ".codecarbon.config"
|
|
9
|
+
CONFIG_SECTION = "codecarbon"
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
def clean_env_key(k: str) -> str:
|
|
10
13
|
"""
|
|
@@ -36,7 +39,7 @@ def parse_env_config() -> dict:
|
|
|
36
39
|
dict: existing relevant environment variables mapped into a dict
|
|
37
40
|
"""
|
|
38
41
|
return {
|
|
39
|
-
|
|
42
|
+
CONFIG_SECTION: {
|
|
40
43
|
clean_env_key(k): v
|
|
41
44
|
for k, v in os.environ.items()
|
|
42
45
|
if k.lower().startswith("codecarbon_")
|
|
@@ -110,10 +113,81 @@ def normalize_gpu_ids(
|
|
|
110
113
|
return None
|
|
111
114
|
|
|
112
115
|
|
|
116
|
+
def read_config_file(path: str) -> dict:
|
|
117
|
+
"""
|
|
118
|
+
Read the `[codecarbon]` section of a single configuration file.
|
|
119
|
+
|
|
120
|
+
A file that does not exist, or that cannot be read, is ignored, like
|
|
121
|
+
configparser.ConfigParser.read does.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
path (str): Path of the configuration file to read.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
dict: The keys defined in this file only, empty if there is nothing to read.
|
|
128
|
+
"""
|
|
129
|
+
config = configparser.ConfigParser()
|
|
130
|
+
config.read(path)
|
|
131
|
+
if not config.has_section(CONFIG_SECTION):
|
|
132
|
+
return {}
|
|
133
|
+
return dict(config[CONFIG_SECTION])
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def log_config_sources(sources: Dict[str, str]) -> None:
|
|
137
|
+
"""
|
|
138
|
+
Log a single line telling, for every configuration key, which layer set it.
|
|
139
|
+
|
|
140
|
+
Only the key names and their source are logged, never the values, because
|
|
141
|
+
this configuration holds secrets such as `api_key` and `experiment_id`.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
sources (dict): The key to source mapping built by
|
|
145
|
+
get_hierarchical_config_with_sources.
|
|
146
|
+
"""
|
|
147
|
+
if not sources:
|
|
148
|
+
return
|
|
149
|
+
logger.info(
|
|
150
|
+
"Codecarbon configuration: "
|
|
151
|
+
+ ", ".join(f"{key} from {source}" for key, source in sorted(sources.items()))
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_hierarchical_config_with_sources() -> Tuple[dict, Dict[str, str]]:
|
|
156
|
+
"""
|
|
157
|
+
Same as get_hierarchical_config, but also tells where each key comes from.
|
|
158
|
+
|
|
159
|
+
The layers are read from the lowest to the highest priority: the global
|
|
160
|
+
config file, then the local config file, then the environment variables.
|
|
161
|
+
The last layer defining a key wins, and is the one reported as its source.
|
|
162
|
+
A source is either the path of the config file that set the key, or the
|
|
163
|
+
name of the environment variable, e.g. `CODECARBON_PROJECT_NAME`.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
tuple: The (config, sources) dicts, which share the same keys. `config`
|
|
167
|
+
maps a key to its value (**all values are strings**) and `sources` maps
|
|
168
|
+
a key to the layer it was read from.
|
|
169
|
+
"""
|
|
170
|
+
global_path = str((Path.home() / CONFIG_FILE_NAME).expanduser().resolve())
|
|
171
|
+
local_path = str((Path.cwd() / CONFIG_FILE_NAME).expanduser().resolve())
|
|
172
|
+
|
|
173
|
+
config = {}
|
|
174
|
+
sources = {}
|
|
175
|
+
|
|
176
|
+
for path in (global_path, local_path):
|
|
177
|
+
for key, value in read_config_file(path).items():
|
|
178
|
+
config[key] = value
|
|
179
|
+
sources[key] = path
|
|
180
|
+
|
|
181
|
+
for key, value in parse_env_config()[CONFIG_SECTION].items():
|
|
182
|
+
config[key] = value
|
|
183
|
+
sources[key] = f"CODECARBON_{key.upper()}"
|
|
184
|
+
|
|
185
|
+
return config, sources
|
|
186
|
+
|
|
187
|
+
|
|
113
188
|
def get_hierarchical_config():
|
|
114
189
|
"""
|
|
115
|
-
Get the user-defined codecarbon configuration
|
|
116
|
-
(actually a configparser.SectionProxy instance).
|
|
190
|
+
Get the user-defined codecarbon configuration as a dict.
|
|
117
191
|
|
|
118
192
|
```
|
|
119
193
|
>>> from codecarbon.core.config import get_hierarchical_config
|
|
@@ -121,10 +195,7 @@ def get_hierarchical_config():
|
|
|
121
195
|
>>> print(conf)
|
|
122
196
|
```
|
|
123
197
|
|
|
124
|
-
|
|
125
|
-
and getboolean(key) to automatically parse strings into those types.
|
|
126
|
-
|
|
127
|
-
All values (outputs of get(key)) are strings.
|
|
198
|
+
All values are strings.
|
|
128
199
|
|
|
129
200
|
It looks for, and reads, a config file .codecarbon.config in the user's $HOME.
|
|
130
201
|
It then looks for, reads, and updates the previous configuration from a config
|
|
@@ -133,29 +204,13 @@ def get_hierarchical_config():
|
|
|
133
204
|
with `CODECARBON_` (for instance if `CODECARBON_PROJECT_NAME` is `your-project`
|
|
134
205
|
then the resulting configuration key `project_name` will have value `your-project`)
|
|
135
206
|
|
|
207
|
+
The source of every resolved key is logged, so that a surprising value can be
|
|
208
|
+
traced back to the file or the environment variable that set it.
|
|
209
|
+
|
|
136
210
|
Returns:
|
|
137
211
|
dict: The final configuration dict parsed from global,
|
|
138
212
|
local and environment configurations. **All values are strings**.
|
|
139
213
|
"""
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
cwd = Path.cwd()
|
|
144
|
-
home = Path.home()
|
|
145
|
-
global_path = str((home / ".codecarbon.config").expanduser().resolve())
|
|
146
|
-
local_path = str((cwd / ".codecarbon.config").expanduser().resolve())
|
|
147
|
-
if Path(global_path).exists():
|
|
148
|
-
logger.info(
|
|
149
|
-
f"Codecarbon is taking the configuration from global file: {global_path}"
|
|
150
|
-
)
|
|
151
|
-
if Path(local_path).exists():
|
|
152
|
-
logger.info(f"Some variables are overriden by the local file: {local_path}")
|
|
153
|
-
elif Path(local_path).exists():
|
|
154
|
-
logger.info(
|
|
155
|
-
f"Codecarbon is taking the configuration from the local file {local_path}"
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
config.read([global_path, local_path])
|
|
159
|
-
config.read_dict(parse_env_config())
|
|
160
|
-
|
|
161
|
-
return dict(config["codecarbon"])
|
|
214
|
+
config, sources = get_hierarchical_config_with_sources()
|
|
215
|
+
log_config_sources(sources)
|
|
216
|
+
return config
|
|
@@ -17,7 +17,12 @@ from typing import TYPE_CHECKING, Dict, Optional, Tuple
|
|
|
17
17
|
import psutil
|
|
18
18
|
from rapidfuzz import fuzz, process, utils
|
|
19
19
|
|
|
20
|
-
from codecarbon.core.rapl import
|
|
20
|
+
from codecarbon.core.rapl import (
|
|
21
|
+
SEQUENTIAL_READ_TOLERANCE_KWH,
|
|
22
|
+
RAPLFile,
|
|
23
|
+
counters_match,
|
|
24
|
+
find_mirrored_counters,
|
|
25
|
+
)
|
|
21
26
|
from codecarbon.core.units import Time
|
|
22
27
|
from codecarbon.core.util import count_cpus, detect_cpu_model
|
|
23
28
|
from codecarbon.external.logger import logger
|
|
@@ -28,6 +33,13 @@ if TYPE_CHECKING:
|
|
|
28
33
|
# default W value per core for a CPU if no model is found in the ref csv
|
|
29
34
|
DEFAULT_POWER_PER_CORE = 4
|
|
30
35
|
|
|
36
|
+
_TROUBLESHOOTING_URL = "https://docs.codecarbon.io/latest/how-to/troubleshooting/"
|
|
37
|
+
RAPL_PERMISSION_HELP = (
|
|
38
|
+
"You can grant read permission with: sudo chmod -R a+r /sys/class/powercap/* "
|
|
39
|
+
"(this does not persist across reboots, see "
|
|
40
|
+
f"{_TROUBLESHOOTING_URL}#rapl-permission-denied for a permanent setup)"
|
|
41
|
+
)
|
|
42
|
+
|
|
31
43
|
|
|
32
44
|
@lru_cache(maxsize=1)
|
|
33
45
|
def is_powergadget_available() -> bool:
|
|
@@ -175,18 +187,16 @@ def _create_warn_function():
|
|
|
175
187
|
nonlocal already_warned
|
|
176
188
|
if not already_warned:
|
|
177
189
|
logger.warning(
|
|
178
|
-
"\tRAPL - Permission denied reading RAPL file %s. "
|
|
179
|
-
"You can grant read permission with: "
|
|
180
|
-
"sudo chmod -R a+r /sys/class/powercap/*",
|
|
190
|
+
"\tRAPL - Permission denied reading RAPL file %s. %s",
|
|
181
191
|
energy_path,
|
|
192
|
+
RAPL_PERMISSION_HELP,
|
|
182
193
|
)
|
|
183
194
|
already_warned = True
|
|
184
195
|
else:
|
|
185
196
|
logger.debug(
|
|
186
|
-
"\tRAPL - Permission denied reading RAPL file %s. "
|
|
187
|
-
"You can grant read permission with: "
|
|
188
|
-
"sudo chmod -R a+r /sys/class/powercap/*",
|
|
197
|
+
"\tRAPL - Permission denied reading RAPL file %s. %s",
|
|
189
198
|
energy_path,
|
|
199
|
+
RAPL_PERMISSION_HELP,
|
|
190
200
|
)
|
|
191
201
|
|
|
192
202
|
return warn_permission_denied
|
|
@@ -248,6 +258,24 @@ def is_psutil_available():
|
|
|
248
258
|
return False
|
|
249
259
|
|
|
250
260
|
|
|
261
|
+
def is_raspberry() -> bool:
|
|
262
|
+
"""
|
|
263
|
+
Check if the Raspberry Pi PMIC power readings are available.
|
|
264
|
+
|
|
265
|
+
`vcgencmd` ships with every Raspberry Pi OS, but only the Pi 5 answers
|
|
266
|
+
`pmic_read_adc`: older boards fall back to the TDP of their CPU.
|
|
267
|
+
"""
|
|
268
|
+
if not os.path.exists("/usr/bin/vcgencmd"):
|
|
269
|
+
return False
|
|
270
|
+
try:
|
|
271
|
+
res = subprocess.run(
|
|
272
|
+
["vcgencmd", "pmic_read_adc"], capture_output=True, timeout=5
|
|
273
|
+
)
|
|
274
|
+
except (OSError, subprocess.SubprocessError):
|
|
275
|
+
return False
|
|
276
|
+
return res.returncode == 0 and b"volt(" in res.stdout
|
|
277
|
+
|
|
278
|
+
|
|
251
279
|
class IntelPowerGadget:
|
|
252
280
|
"""
|
|
253
281
|
A class to interface with Intel Power Gadget for monitoring CPU power consumption on Windows and (non-Apple Silicon) macOS.
|
|
@@ -450,6 +478,10 @@ class IntelRAPL:
|
|
|
450
478
|
self._lin_rapl_dir = rapl_dir
|
|
451
479
|
self._system = sys.platform.lower()
|
|
452
480
|
self._rapl_files = []
|
|
481
|
+
# Files that look like a duplicate of another counter, but whose
|
|
482
|
+
# energy deltas have not confirmed it yet. They are left out of the
|
|
483
|
+
# measurement while pending. Maps file path -> mirrored file path.
|
|
484
|
+
self._mirrored_candidates: Dict[str, str] = {}
|
|
453
485
|
self.rapl_include_dram = rapl_include_dram
|
|
454
486
|
self.rapl_prefer_psys = rapl_prefer_psys
|
|
455
487
|
self._setup_rapl()
|
|
@@ -480,7 +512,7 @@ class IntelRAPL:
|
|
|
480
512
|
"""Scan subdirectories of a RAPL entry for domain directories."""
|
|
481
513
|
subdirs = []
|
|
482
514
|
try:
|
|
483
|
-
for sub in os.listdir(entry_path):
|
|
515
|
+
for sub in sorted(os.listdir(entry_path)):
|
|
484
516
|
sub_path = os.path.join(entry_path, sub)
|
|
485
517
|
if ":" in sub and os.path.isdir(sub_path):
|
|
486
518
|
if os.path.exists(os.path.join(sub_path, "energy_uj")):
|
|
@@ -499,7 +531,7 @@ class IntelRAPL:
|
|
|
499
531
|
"""Scan a base directory for intel-rapl entries."""
|
|
500
532
|
domain_dirs = []
|
|
501
533
|
try:
|
|
502
|
-
for entry in os.listdir(base):
|
|
534
|
+
for entry in sorted(os.listdir(base)):
|
|
503
535
|
if not entry.startswith("intel-rapl"):
|
|
504
536
|
continue
|
|
505
537
|
entry_path = os.path.join(base, entry)
|
|
@@ -528,7 +560,7 @@ class IntelRAPL:
|
|
|
528
560
|
if domain_dirs:
|
|
529
561
|
return domain_dirs
|
|
530
562
|
try:
|
|
531
|
-
for item in os.listdir(self._lin_rapl_dir):
|
|
563
|
+
for item in sorted(os.listdir(self._lin_rapl_dir)):
|
|
532
564
|
if ":" in item:
|
|
533
565
|
path = os.path.join(self._lin_rapl_dir, item)
|
|
534
566
|
if os.path.isdir(path) and os.path.exists(
|
|
@@ -583,8 +615,7 @@ class IntelRAPL:
|
|
|
583
615
|
return True, is_required_main
|
|
584
616
|
except PermissionError:
|
|
585
617
|
msg = f"\tRAPL - Permission denied reading RAPL file {rapl_file}."
|
|
586
|
-
|
|
587
|
-
logger.warning("%s %s; skipping.", msg, suggestion)
|
|
618
|
+
logger.warning("%s %s; skipping.", msg, RAPL_PERMISSION_HELP)
|
|
588
619
|
return False, False
|
|
589
620
|
except Exception as e:
|
|
590
621
|
logger.debug(
|
|
@@ -842,7 +873,12 @@ class IntelRAPL:
|
|
|
842
873
|
for rapl_file in self._rapl_files:
|
|
843
874
|
rapl_file.delta(duration)
|
|
844
875
|
|
|
876
|
+
self._confirm_mirrored_files()
|
|
877
|
+
|
|
845
878
|
for rapl_file in self._rapl_files:
|
|
879
|
+
if rapl_file.path in self._mirrored_candidates:
|
|
880
|
+
# Still suspected of duplicating another counter
|
|
881
|
+
continue
|
|
846
882
|
logger.debug(rapl_file)
|
|
847
883
|
cpu_details[rapl_file.name] = rapl_file.energy_delta.kWh
|
|
848
884
|
# We fake the name used by Power Gadget when using RAPL
|
|
@@ -874,6 +910,75 @@ class IntelRAPL:
|
|
|
874
910
|
"""
|
|
875
911
|
for rapl_file in self._rapl_files:
|
|
876
912
|
rapl_file.start()
|
|
913
|
+
# DRAM domains are a different measurement, never a duplicate
|
|
914
|
+
counters = [
|
|
915
|
+
(rapl_file.path, float(rapl_file.last_energy))
|
|
916
|
+
for rapl_file in self._rapl_files
|
|
917
|
+
if "dram" not in rapl_file.name.lower()
|
|
918
|
+
and rapl_file.last_energy is not None
|
|
919
|
+
]
|
|
920
|
+
self._mirrored_candidates = find_mirrored_counters(
|
|
921
|
+
counters, SEQUENTIAL_READ_TOLERANCE_KWH
|
|
922
|
+
)
|
|
923
|
+
for path, reference_path in self._mirrored_candidates.items():
|
|
924
|
+
logger.warning(
|
|
925
|
+
"\tRAPL - Energy counter at %s looks like a mirror of %s, it is "
|
|
926
|
+
"left out of the measurement to avoid double-counting",
|
|
927
|
+
path,
|
|
928
|
+
reference_path,
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
def _confirm_mirrored_files(self) -> None:
|
|
932
|
+
"""
|
|
933
|
+
Decide the fate of the files flagged as duplicates at ``start()``.
|
|
934
|
+
|
|
935
|
+
Two independent meters could hold indistinguishable counters at the
|
|
936
|
+
single instant of the initial reading, so a candidate is only dropped
|
|
937
|
+
for good once it has also accumulated the very same energy over a
|
|
938
|
+
measurement interval. A candidate whose energy delta differs is a real
|
|
939
|
+
meter and is restored, and a candidate that has not accumulated
|
|
940
|
+
anything yet stays pending until an interval is conclusive.
|
|
941
|
+
"""
|
|
942
|
+
if not self._mirrored_candidates:
|
|
943
|
+
return
|
|
944
|
+
files_by_path = {rapl_file.path: rapl_file for rapl_file in self._rapl_files}
|
|
945
|
+
pending = {}
|
|
946
|
+
confirmed = set()
|
|
947
|
+
for path, reference_path in self._mirrored_candidates.items():
|
|
948
|
+
rapl_file = files_by_path.get(path)
|
|
949
|
+
reference = files_by_path.get(reference_path)
|
|
950
|
+
if rapl_file is None or reference is None:
|
|
951
|
+
continue
|
|
952
|
+
delta = float(rapl_file.energy_delta)
|
|
953
|
+
reference_delta = float(reference.energy_delta)
|
|
954
|
+
if rapl_file.sample_skipped or reference.sample_skipped:
|
|
955
|
+
# A skipped sample reports no energy: the interval is not conclusive
|
|
956
|
+
pending[path] = reference_path
|
|
957
|
+
elif delta <= 0 and reference_delta <= 0:
|
|
958
|
+
# Nothing was accumulated: the interval is not conclusive
|
|
959
|
+
pending[path] = reference_path
|
|
960
|
+
elif counters_match(delta, reference_delta, SEQUENTIAL_READ_TOLERANCE_KWH):
|
|
961
|
+
confirmed.add(path)
|
|
962
|
+
logger.warning(
|
|
963
|
+
"\tRAPL - Energy counter at %s mirrors the one at %s, it is "
|
|
964
|
+
"dropped to avoid double-counting the CPU power (multi-die "
|
|
965
|
+
"CPUs mirror the package counter on each die)",
|
|
966
|
+
path,
|
|
967
|
+
reference_path,
|
|
968
|
+
)
|
|
969
|
+
else:
|
|
970
|
+
logger.info(
|
|
971
|
+
"\tRAPL - Energy counter at %s accumulates energy on its own, "
|
|
972
|
+
"it is an independent meter and is measured again",
|
|
973
|
+
path,
|
|
974
|
+
)
|
|
975
|
+
self._mirrored_candidates = pending
|
|
976
|
+
if confirmed:
|
|
977
|
+
self._rapl_files = [
|
|
978
|
+
rapl_file
|
|
979
|
+
for rapl_file in self._rapl_files
|
|
980
|
+
if rapl_file.path not in confirmed
|
|
981
|
+
]
|
|
877
982
|
|
|
878
983
|
|
|
879
984
|
class TDP:
|
|
@@ -1009,9 +1114,10 @@ class TDP:
|
|
|
1009
1114
|
)
|
|
1010
1115
|
return cpu_model_detected, power
|
|
1011
1116
|
logger.warning(
|
|
1012
|
-
"We saw that you have a %s but we don't know it."
|
|
1013
|
-
|
|
1117
|
+
"We saw that you have a %s but we don't know it. "
|
|
1118
|
+
"Please help us add it, see %s#unknown-cpu-model",
|
|
1014
1119
|
cpu_model_detected,
|
|
1120
|
+
_TROUBLESHOOTING_URL,
|
|
1015
1121
|
)
|
|
1016
1122
|
if is_psutil_available():
|
|
1017
1123
|
# Count thread of the CPU
|