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.
Files changed (129) hide show
  1. {codecarbon-3.2.8 → codecarbon-3.2.9}/PKG-INFO +1 -1
  2. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/__init__.py +1 -1
  3. codecarbon-3.2.9/codecarbon/_version.py +1 -0
  4. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/cli/main.py +33 -7
  5. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/cli/monitor.py +5 -4
  6. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/api_client.py +3 -5
  7. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/cpu.py +16 -3
  8. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/emissions.py +4 -3
  9. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/gpu.py +17 -0
  10. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/gpu_amd.py +6 -1
  11. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/gpu_device.py +14 -0
  12. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/gpu_nvidia.py +6 -1
  13. codecarbon-3.2.9/codecarbon/core/hardware_cache.py +249 -0
  14. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/powermetrics.py +15 -2
  15. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/resource_tracker.py +71 -25
  16. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/util.py +2 -1
  17. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/emissions_tracker.py +124 -69
  18. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/hardware.py +21 -5
  19. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/task.py +1 -1
  20. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/input.py +29 -12
  21. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/file.py +15 -19
  22. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/http.py +13 -8
  23. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon.egg-info/PKG-INFO +1 -1
  24. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon.egg-info/SOURCES.txt +2 -0
  25. {codecarbon-3.2.8 → codecarbon-3.2.9}/pyproject.toml +2 -2
  26. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_config.py +4 -0
  27. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_cpu.py +45 -7
  28. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_cpu_load.py +5 -0
  29. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_custom_handler.py +4 -2
  30. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_emissions_tracker.py +175 -10
  31. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_emissions_tracker_constant.py +10 -15
  32. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_gpu.py +7 -0
  33. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_gpu_nvidia.py +35 -0
  34. codecarbon-3.2.9/tests/test_hardware_cache.py +288 -0
  35. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_input.py +6 -3
  36. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_offline_emissions_tracker.py +11 -0
  37. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_powermetrics.py +83 -0
  38. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_resource_tracker.py +246 -2
  39. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/testdata.py +7 -0
  40. codecarbon-3.2.8/codecarbon/_version.py +0 -1
  41. {codecarbon-3.2.8 → codecarbon-3.2.9}/LICENSE +0 -0
  42. {codecarbon-3.2.8 → codecarbon-3.2.9}/README.md +0 -0
  43. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/cli/__init__.py +0 -0
  44. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/cli/auth.py +0 -0
  45. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/cli/cli_utils.py +0 -0
  46. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/__init__.py +0 -0
  47. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/cloud.py +0 -0
  48. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/config.py +0 -0
  49. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/electricitymaps_api.py +0 -0
  50. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/measure.py +0 -0
  51. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/rapl.py +0 -0
  52. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/schemas.py +0 -0
  53. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/telemetry_client.py +0 -0
  54. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/telemetry_schemas.py +0 -0
  55. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/core/units.py +0 -0
  56. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/canada_provinces.geojson +0 -0
  57. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/cloud/impact.csv +0 -0
  58. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/hardware/cpu_dataset_builder/amd_cpu_scrapper.py +0 -0
  59. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/hardware/cpu_dataset_builder/intel_cpu_scrapper.py +0 -0
  60. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/hardware/cpu_dataset_builder/merge_scrapped_cpu_power.py +0 -0
  61. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/hardware/cpu_power.csv +0 -0
  62. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/private_infra/2016/usa_emissions.json +0 -0
  63. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/private_infra/2023/canada_energy_mix.json +0 -0
  64. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/private_infra/carbon_intensity_per_source.json +0 -0
  65. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/private_infra/global_energy_mix.json +0 -0
  66. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/data/private_infra/nordic_emissions.json +0 -0
  67. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/__init__.py +0 -0
  68. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/geography.py +0 -0
  69. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/logger.py +0 -0
  70. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/ram.py +0 -0
  71. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/external/scheduler.py +0 -0
  72. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/lock.py +0 -0
  73. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output.py +0 -0
  74. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/__init__.py +0 -0
  75. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/base_output.py +0 -0
  76. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/boamps/__init__.py +0 -0
  77. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/boamps/mapper.py +0 -0
  78. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/boamps/models.py +0 -0
  79. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/boamps/output.py +0 -0
  80. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/emissions_data.py +0 -0
  81. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/logger.py +0 -0
  82. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/metrics/__init__.py +0 -0
  83. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/metrics/logfire.py +0 -0
  84. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/metrics/metric_docs.py +0 -0
  85. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/output_methods/metrics/prometheus.py +0 -0
  86. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/py.typed +0 -0
  87. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/__init__.py +0 -0
  88. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/assets/__init__.py +0 -0
  89. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/assets/car_icon.png +0 -0
  90. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/assets/house_icon.png +0 -0
  91. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/assets/tv_icon.png +0 -0
  92. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/carbonboard.py +0 -0
  93. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/carbonboard_on_api.py +0 -0
  94. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/components.py +0 -0
  95. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/data.py +0 -0
  96. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon/viz/units.py +0 -0
  97. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon.egg-info/dependency_links.txt +0 -0
  98. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon.egg-info/entry_points.txt +0 -0
  99. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon.egg-info/requires.txt +0 -0
  100. {codecarbon-3.2.8 → codecarbon-3.2.9}/codecarbon.egg-info/top_level.txt +0 -0
  101. {codecarbon-3.2.8 → codecarbon-3.2.9}/setup.cfg +0 -0
  102. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_api_call.py +0 -0
  103. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_boamps_output.py +0 -0
  104. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_cloud.py +0 -0
  105. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_core_util.py +0 -0
  106. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_docs_examples.py +0 -0
  107. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_electricitymaps_api.py +0 -0
  108. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_electricitymaps_backward_compatibility.py +0 -0
  109. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_electricitymaps_config_backward_compatibility.py +0 -0
  110. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_emissions.py +0 -0
  111. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_emissions_tracker_flush.py +0 -0
  112. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_energy.py +0 -0
  113. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_geography.py +0 -0
  114. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_gpu_amd.py +0 -0
  115. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_lock.py +0 -0
  116. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_logger.py +0 -0
  117. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_logging_output.py +0 -0
  118. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_package_integrity.py +0 -0
  119. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_ram.py +0 -0
  120. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_rapl_mmio_scanning.py +0 -0
  121. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_rapl_parameters.py +0 -0
  122. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_rapl_permissions.py +0 -0
  123. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_telemetry_client.py +0 -0
  124. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_tracking_inference.py +0 -0
  125. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_unsupported_gpu.py +0 -0
  126. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_utilization_tracking.py +0 -0
  127. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_viz_data.py +0 -0
  128. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/test_viz_units.py +0 -0
  129. {codecarbon-3.2.8 → codecarbon-3.2.9}/tests/testutils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecarbon
3
- Version: 3.2.8
3
+ Version: 3.2.9
4
4
  Author: Mila, DataForGood, BCG GAMMA, Comet.ml, Haverford College
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://codecarbon.io/
@@ -8,7 +8,7 @@ from .emissions_tracker import (
8
8
  OfflineEmissionsTracker,
9
9
  track_emissions,
10
10
  )
11
- from .output import OutputMethod
11
+ from .output_methods.base_output import OutputMethod
12
12
 
13
13
  __all__ = [
14
14
  "EmissionsTracker",
@@ -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.args
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
- timestamp = str(arrow.now().isoformat())
30
- return timestamp
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 typing import Dict, Optional, Tuple
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
- from codecarbon.input import DataSource
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
- response = _has_powermetrics_sudo()
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):