codecarbon 2.8.2__tar.gz → 3.0.0rc0__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 (70) hide show
  1. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/.gitignore +2 -3
  2. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/PKG-INFO +7 -1
  3. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/README.md +6 -0
  4. codecarbon-3.0.0rc0/codecarbon/_version.py +1 -0
  5. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/cli/main.py +11 -5
  6. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/api_client.py +2 -2
  7. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/cpu.py +30 -3
  8. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/emissions.py +2 -2
  9. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/resource_tracker.py +77 -12
  10. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/units.py +6 -0
  11. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/util.py +5 -3
  12. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/hardware/cpu_power.csv +4 -0
  13. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/emissions_tracker.py +44 -5
  14. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/external/hardware.py +89 -6
  15. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/output_methods/file.py +5 -7
  16. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/pyproject.toml +2 -2
  17. codecarbon-2.8.2/codecarbon/_version.py +0 -1
  18. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/LICENSE +0 -0
  19. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/__init__.py +0 -0
  20. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/cli/__init__.py +0 -0
  21. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/cli/cli_utils.py +0 -0
  22. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/__init__.py +0 -0
  23. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/cloud.py +0 -0
  24. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/co2_signal.py +0 -0
  25. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/config.py +0 -0
  26. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/gpu.py +0 -0
  27. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/measure.py +0 -0
  28. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/powermetrics.py +0 -0
  29. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/rapl.py +0 -0
  30. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/core/schemas.py +0 -0
  31. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/canada_provinces.geojson +0 -0
  32. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/cloud/impact.csv +0 -0
  33. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/private_infra/2016/canada_energy_mix.json +0 -0
  34. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/private_infra/2016/global_energy_mix-old.json +0 -0
  35. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/private_infra/2016/usa_emissions.json +0 -0
  36. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/private_infra/2020/01_get_world_carbon_intensity.ipynb +0 -0
  37. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/private_infra/2020/02_convert_csv_to_json.ipynb +0 -0
  38. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/private_infra/2020/03_add_eu_data.ipynb +0 -0
  39. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/private_infra/2020/eu-carbon-intensity-electricity.csv +0 -0
  40. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/private_infra/2023-07-07-22-40-48.png +0 -0
  41. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/private_infra/carbon_intensity_per_source.json +0 -0
  42. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/private_infra/global_energy_mix.json +0 -0
  43. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/private_infra/our_world_in_data.ipynb +0 -0
  44. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/data/private_infra/world_energy_mix.csv +0 -0
  45. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/external/__init__.py +0 -0
  46. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/external/geography.py +0 -0
  47. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/external/logger.py +0 -0
  48. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/external/scheduler.py +0 -0
  49. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/external/task.py +0 -0
  50. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/input.py +0 -0
  51. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/lock.py +0 -0
  52. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/output.py +0 -0
  53. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/output_methods/__init__.py +0 -0
  54. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/output_methods/base_output.py +0 -0
  55. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/output_methods/emissions_data.py +0 -0
  56. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/output_methods/http.py +0 -0
  57. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/output_methods/logger.py +0 -0
  58. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/output_methods/metrics/__init__.py +0 -0
  59. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/output_methods/metrics/logfire.py +0 -0
  60. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/output_methods/metrics/metric_docs.py +0 -0
  61. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/output_methods/metrics/prometheus.py +0 -0
  62. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/viz/__init__.py +0 -0
  63. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/viz/assets/__init__.py +0 -0
  64. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/viz/assets/car_icon.png +0 -0
  65. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/viz/assets/house_icon.png +0 -0
  66. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/viz/assets/tv_icon.png +0 -0
  67. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/viz/carbonboard.py +0 -0
  68. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/viz/carbonboard_on_api.py +0 -0
  69. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/viz/components.py +0 -0
  70. {codecarbon-2.8.2 → codecarbon-3.0.0rc0}/codecarbon/viz/data.py +0 -0
@@ -8,11 +8,9 @@ __pycache__/
8
8
  *intel_power_gadget_log.csv
9
9
  *powermetrics_log.txt
10
10
  !tests/test_data/mock*
11
- .codecarbon.config
12
11
 
13
12
  # C extensions
14
13
  *.so
15
- .codecarbon.config
16
14
 
17
15
  # Distribution / packaging
18
16
  .Python
@@ -132,4 +130,5 @@ tests/test_data/rapl/*
132
130
  *.cast
133
131
 
134
132
  # credentials
135
- credentials.json
133
+ credentials*
134
+ .codecarbon.config*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecarbon
3
- Version: 2.8.2
3
+ Version: 3.0.0rc0
4
4
  Project-URL: Homepage, https://codecarbon.io/
5
5
  Project-URL: Repository, https://github.com/mlco2/codecarbon
6
6
  Project-URL: Dashboard, http://dashboard.codecarbon.io/
@@ -212,3 +212,9 @@ Here is a sample for BibTeX:
212
212
  # Contact 📝
213
213
 
214
214
  Maintainers are [@vict0rsch](https://github.com/vict0rsch) [@benoit-cty](https://github.com/benoit-cty) and [@SaboniAmine](https://github.com/saboniamine). Codecarbon is developed by volunteers from [**Mila**](http://mila.quebec) and the [**DataForGoodFR**](https://twitter.com/dataforgood_fr) community alongside donated professional time of engineers at [**Comet.ml**](https://comet.ml) and [**BCG GAMMA**](https://www.bcg.com/en-nl/beyond-consulting/bcg-gamma/default).
215
+
216
+ ## Star History
217
+
218
+ Comparison of the number of stars accumulated by the different Python CO2 emissions projects:
219
+ [![Star History Chart](https://api.star-history.com/svg?repos=mlco2/codecarbon,lfwa/carbontracker,sb-ai-lab/Eco2AI,fvaleye/tracarbon,Breakend/experiment-impact-tracker&type=Date)](https://star-history.com/#mlco2/codecarbon&lfwa/carbontracker&sb-ai-lab/Eco2AI&fvaleye/tracarbon&Breakend/experiment-impact-tracker&Date)
220
+
@@ -172,3 +172,9 @@ Here is a sample for BibTeX:
172
172
  # Contact 📝
173
173
 
174
174
  Maintainers are [@vict0rsch](https://github.com/vict0rsch) [@benoit-cty](https://github.com/benoit-cty) and [@SaboniAmine](https://github.com/saboniamine). Codecarbon is developed by volunteers from [**Mila**](http://mila.quebec) and the [**DataForGoodFR**](https://twitter.com/dataforgood_fr) community alongside donated professional time of engineers at [**Comet.ml**](https://comet.ml) and [**BCG GAMMA**](https://www.bcg.com/en-nl/beyond-consulting/bcg-gamma/default).
175
+
176
+ ## Star History
177
+
178
+ Comparison of the number of stars accumulated by the different Python CO2 emissions projects:
179
+ [![Star History Chart](https://api.star-history.com/svg?repos=mlco2/codecarbon,lfwa/carbontracker,sb-ai-lab/Eco2AI,fvaleye/tracarbon,Breakend/experiment-impact-tracker&type=Date)](https://star-history.com/#mlco2/codecarbon&lfwa/carbontracker&sb-ai-lab/Eco2AI&fvaleye/tracarbon&Breakend/experiment-impact-tracker&Date)
180
+
@@ -0,0 +1 @@
1
+ __version__ = "3.0.0_rc0"
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import sys
2
3
  import time
3
4
  from pathlib import Path
4
5
  from typing import Optional
@@ -99,12 +100,14 @@ def show_config(path: Path = Path("./.codecarbon.config")) -> None:
99
100
  )
100
101
 
101
102
 
102
- fief = Fief(AUTH_SERVER_URL, AUTH_CLIENT_ID)
103
- fief_auth = FiefAuth(fief, "./credentials.json")
103
+ def get_fief_auth():
104
+ fief = Fief(AUTH_SERVER_URL, AUTH_CLIENT_ID)
105
+ fief_auth = FiefAuth(fief, "./credentials.json")
106
+ return fief_auth
104
107
 
105
108
 
106
109
  def _get_access_token():
107
- access_token_info = fief_auth.access_token_info()
110
+ access_token_info = get_fief_auth().access_token_info()
108
111
  access_token = access_token_info["access_token"]
109
112
  return access_token
110
113
 
@@ -124,7 +127,7 @@ def api_get():
124
127
 
125
128
  @codecarbon.command("login", short_help="Login to CodeCarbon")
126
129
  def login():
127
- fief_auth.authorize()
130
+ get_fief_auth().authorize()
128
131
 
129
132
 
130
133
  def get_api_key(project_id: str):
@@ -327,7 +330,10 @@ def monitor(
327
330
  experiment_id = get_existing_local_exp_id()
328
331
  if api:
329
332
  if experiment_id is None:
330
- print("ERROR: No experiment id, call 'codecarbon config' first.", err=True)
333
+ print(
334
+ "ERROR: No experiment id, call 'codecarbon config' first.",
335
+ file=sys.stderr,
336
+ )
331
337
  print("CodeCarbon is going in an infinite loop to monitor this machine.")
332
338
  with EmissionsTracker(
333
339
  measure_power_secs=measure_power_secs,
@@ -257,8 +257,8 @@ class ApiClient: # (AsyncClient)
257
257
  gpu_count=self.conf.get("gpu_count"),
258
258
  gpu_model=self.conf.get("gpu_model"),
259
259
  # Reduce precision for Privacy
260
- longitude=round(self.conf.get("longitude"), 1),
261
- latitude=round(self.conf.get("latitude"), 1),
260
+ longitude=round(self.conf.get("longitude", 0), 1),
261
+ latitude=round(self.conf.get("latitude", 0), 1),
262
262
  region=self.conf.get("region"),
263
263
  provider=self.conf.get("provider"),
264
264
  ram_total_size=self.conf.get("ram_total_size"),
@@ -5,12 +5,14 @@ https://software.intel.com/content/www/us/en/develop/articles/intel-power-gadget
5
5
  """
6
6
 
7
7
  import os
8
+ import re
8
9
  import shutil
9
10
  import subprocess
10
11
  import sys
11
12
  from typing import Dict, Optional, Tuple
12
13
 
13
14
  import pandas as pd
15
+ import psutil
14
16
  from rapidfuzz import fuzz, process, utils
15
17
 
16
18
  from codecarbon.core.rapl import RAPLFile
@@ -58,6 +60,21 @@ def is_rapl_available() -> bool:
58
60
  return False
59
61
 
60
62
 
63
+ def is_psutil_available():
64
+ try:
65
+ nice = psutil.cpu_times().nice
66
+ if nice > 0.1:
67
+ return True
68
+ else:
69
+ return False
70
+ except Exception as e:
71
+ logger.debug(
72
+ "Not using the psutil interface, an exception occurred while instantiating "
73
+ + f"psutil.cpu_percent : {e}",
74
+ )
75
+ return False
76
+
77
+
61
78
  class IntelPowerGadget:
62
79
  """
63
80
  A class to interface with Intel Power Gadget for monitoring CPU power consumption on Windows and (non-Apple Silicon) macOS.
@@ -294,7 +311,7 @@ class IntelRAPL:
294
311
  logger.debug("We will read Intel RAPL files at %s", rapl_file)
295
312
  except PermissionError as e:
296
313
  raise PermissionError(
297
- "Unable to read Intel RAPL files for CPU power, we will use a constant for your CPU power."
314
+ "PermissionError : Unable to read Intel RAPL files for CPU power, we will use a constant for your CPU power."
298
315
  + " Please view https://github.com/mlco2/codecarbon/issues/244"
299
316
  + " for workarounds : %s",
300
317
  e,
@@ -332,8 +349,6 @@ class IntelRAPL:
332
349
  """
333
350
  Return CPU details without computing them.
334
351
  """
335
- logger.debug("get_static_cpu_details %s", self._cpu_details)
336
-
337
352
  return self._cpu_details
338
353
 
339
354
  def start(self) -> None:
@@ -426,6 +441,18 @@ class TDP:
426
441
  start_cpu = model_raw.find(" CPU @ ")
427
442
  if start_cpu > 0:
428
443
  model_raw = model_raw[0:start_cpu]
444
+ model_raw = model_raw.replace(" CPU", "")
445
+ model_raw = re.sub(r" @\s*\d+\.\d+GHz", "", model_raw)
446
+ direct_match = process.extractOne(
447
+ model_raw,
448
+ cpu_df["Name"],
449
+ processor=lambda s: s.lower(),
450
+ scorer=fuzz.ratio,
451
+ score_cutoff=THRESHOLD_DIRECT,
452
+ )
453
+
454
+ if direct_match:
455
+ return direct_match[0]
429
456
  indirect_matches = process.extract(
430
457
  model_raw,
431
458
  cpu_df["Name"],
@@ -90,7 +90,7 @@ class Emissions:
90
90
  selected = df.loc[flags]
91
91
  if not len(selected):
92
92
  raise ValueError(
93
- "Unable to find country name for "
93
+ "Unable to find country ISO Code for "
94
94
  f"cloud_provider={cloud.provider}, "
95
95
  f"cloud_region={cloud.region}"
96
96
  )
@@ -105,7 +105,7 @@ class Emissions:
105
105
  selected = df.loc[flags]
106
106
  if not len(selected):
107
107
  raise ValueError(
108
- "Unable to find country name for "
108
+ "Unable to find State/City name for "
109
109
  f"cloud_provider={cloud.provider}, "
110
110
  f"cloud_region={cloud.region}"
111
111
  )
@@ -4,7 +4,7 @@ from typing import List, Union
4
4
  from codecarbon.core import cpu, gpu, powermetrics
5
5
  from codecarbon.core.config import parse_gpu_ids
6
6
  from codecarbon.core.util import detect_cpu_model, is_linux_os, is_mac_os, is_windows_os
7
- from codecarbon.external.hardware import CPU, GPU, RAM, AppleSiliconChip
7
+ from codecarbon.external.hardware import CPU, GPU, MODE_CPU_LOAD, RAM, AppleSiliconChip
8
8
  from codecarbon.external.logger import logger
9
9
 
10
10
 
@@ -23,6 +23,31 @@ class ResourceTracker:
23
23
 
24
24
  def set_CPU_tracking(self):
25
25
  logger.info("[setup] CPU Tracking...")
26
+ tdp = cpu.TDP()
27
+ if self.tracker._conf.get("force_mode_cpu_load", False) and tdp.tdp is not None:
28
+ if tdp.tdp is None:
29
+ logger.warning(
30
+ "Force CPU load mode requested but TDP could not be calculated. Falling back to another mode."
31
+ )
32
+ elif cpu.is_psutil_available():
33
+ # Register a CPU with MODE_CPU_LOAD
34
+ power = tdp.tdp
35
+ model = tdp.model
36
+ hardware = CPU.from_utils(
37
+ self.tracker._output_dir,
38
+ MODE_CPU_LOAD,
39
+ model,
40
+ power,
41
+ tracking_mode=self.tracker._tracking_mode,
42
+ )
43
+ self.cpu_tracker = MODE_CPU_LOAD
44
+ self.tracker._conf["cpu_model"] = hardware.get_model()
45
+ self.tracker._hardware.append(hardware)
46
+ return
47
+ else:
48
+ logger.warning(
49
+ "Force CPU load mode requested but psutil is not available."
50
+ )
26
51
  if cpu.is_powergadget_available() and self.tracker._default_cpu_power is None:
27
52
  logger.info("Tracking Intel CPU via Power Gadget")
28
53
  self.cpu_tracker = "Power Gadget"
@@ -32,9 +57,15 @@ class ResourceTracker:
32
57
  elif cpu.is_rapl_available():
33
58
  logger.info("Tracking Intel CPU via RAPL interface")
34
59
  self.cpu_tracker = "RAPL"
35
- hardware = CPU.from_utils(self.tracker._output_dir, "intel_rapl")
60
+ hardware = CPU.from_utils(
61
+ output_dir=self.tracker._output_dir, mode="intel_rapl"
62
+ )
36
63
  self.tracker._hardware.append(hardware)
37
64
  self.tracker._conf["cpu_model"] = hardware.get_model()
65
+ if "AMD Ryzen Threadripper" in self.tracker._conf["cpu_model"]:
66
+ logger.warning(
67
+ "The RAPL energy and power reported is divided by 2 for all 'AMD Ryzen Threadripper' as it seems to give better results."
68
+ )
38
69
  # change code to check if powermetrics needs to be installed or just sudo setup
39
70
  elif (
40
71
  powermetrics.is_powermetrics_available()
@@ -72,7 +103,7 @@ class ResourceTracker:
72
103
  elif is_windows_os():
73
104
  cpu_tracking_install_instructions = "Windows OS detected: Please install Intel Power Gadget to measure CPU"
74
105
  elif is_linux_os():
75
- cpu_tracking_install_instructions = "Linux OS detected: Please ensure RAPL files exist at \\sys\\class\\powercap\\intel-rapl to measure CPU"
106
+ cpu_tracking_install_instructions = "Linux OS detected: Please ensure RAPL files exist at /sys/class/powercap/intel-rapl to measure CPU"
76
107
  logger.warning(
77
108
  f"No CPU tracking mode found. Falling back on CPU constant mode. \n {cpu_tracking_install_instructions}\n"
78
109
  )
@@ -89,17 +120,46 @@ class ResourceTracker:
89
120
  logger.info(f"CPU Model on constant consumption mode: {model}")
90
121
  self.tracker._conf["cpu_model"] = model
91
122
  if tdp:
92
- hardware = CPU.from_utils(
93
- self.tracker._output_dir, "constant", model, power
94
- )
123
+ if cpu.is_psutil_available():
124
+ logger.warning(
125
+ "No CPU tracking mode found. Falling back on CPU load mode."
126
+ )
127
+ hardware = CPU.from_utils(
128
+ self.tracker._output_dir,
129
+ MODE_CPU_LOAD,
130
+ model,
131
+ power,
132
+ tracking_mode=self.tracker._tracking_mode,
133
+ )
134
+ self.cpu_tracker = MODE_CPU_LOAD
135
+ else:
136
+ logger.warning(
137
+ "No CPU tracking mode found. Falling back on CPU constant mode."
138
+ )
139
+ hardware = CPU.from_utils(
140
+ self.tracker._output_dir, "constant", model, power
141
+ )
142
+ self.cpu_tracker = "global constant"
95
143
  self.tracker._hardware.append(hardware)
96
144
  else:
97
- logger.warning(
98
- "Failed to match CPU TDP constant. "
99
- + "Falling back on a global constant."
100
- )
101
- self.cpu_tracker = "global constant"
102
- hardware = CPU.from_utils(self.tracker._output_dir, "constant")
145
+ if cpu.is_psutil_available():
146
+ logger.warning(
147
+ "Failed to match CPU TDP constant. Falling back on CPU load mode."
148
+ )
149
+ hardware = CPU.from_utils(
150
+ self.tracker._output_dir,
151
+ MODE_CPU_LOAD,
152
+ model,
153
+ power,
154
+ tracking_mode=self.tracker._tracking_mode,
155
+ )
156
+ self.cpu_tracker = MODE_CPU_LOAD
157
+ else:
158
+ logger.warning(
159
+ "Failed to match CPU TDP constant. Falling back on a global constant."
160
+ )
161
+ self.cpu_tracker = "global constant"
162
+ hardware = CPU.from_utils(self.tracker._output_dir, "constant")
103
163
  self.tracker._hardware.append(hardware)
104
164
 
105
165
  def set_GPU_tracking(self):
@@ -130,10 +190,15 @@ class ResourceTracker:
130
190
  self.tracker._conf["gpu_count"] = len(
131
191
  gpu_devices.devices.get_gpu_static_info()
132
192
  )
193
+ self.gpu_tracker = "pynvml"
133
194
  else:
134
195
  logger.info("No GPU found.")
135
196
 
136
197
  def set_CPU_GPU_ram_tracking(self):
198
+ """
199
+ Set up CPU, GPU and RAM tracking based on the user's configuration.
200
+ param tracker: BaseEmissionsTracker object
201
+ """
137
202
  self.set_RAM_tracking()
138
203
  self.set_CPU_tracking()
139
204
  self.set_GPU_tracking()
@@ -145,3 +145,9 @@ class Power:
145
145
 
146
146
  def __mul__(self, factor: float) -> "Power":
147
147
  return Power(self.kW * factor)
148
+
149
+ def __truediv__(self, divisor: float) -> "Power":
150
+ return Power(self.kW / divisor)
151
+
152
+ def __floordiv__(self, divisor: float) -> "Power":
153
+ return Power(self.kW // divisor)
@@ -48,6 +48,8 @@ def resolve_path(path: Union[str, Path]) -> Path:
48
48
  def backup(file_path: Union[str, Path], ext: Optional[str] = ".bak") -> None:
49
49
  """
50
50
  Resolves the path to a path then backs it up, adding the extension provided.
51
+ Warning : this function will rename the file in place, it's the calling function that will write a new file at the original path.
52
+ This function will not overwrite existing backups but add a number.
51
53
 
52
54
  Args:
53
55
  file_path (Union[str, Path]): Path to a file to backup.
@@ -111,7 +113,7 @@ def count_cpus() -> int:
111
113
  "Error running `scontrol show job $SLURM_JOB_ID` "
112
114
  + "to count SLURM-available cpus. Using the machine's cpu count."
113
115
  )
114
- return psutil.cpu_count()
116
+ return psutil.cpu_count(logical=True)
115
117
 
116
118
  num_cpus_matches = re.findall(r"NumCPUs=\d+", scontrol)
117
119
 
@@ -120,14 +122,14 @@ def count_cpus() -> int:
120
122
  "Could not find NumCPUs= after running `scontrol show job $SLURM_JOB_ID` "
121
123
  + "to count SLURM-available cpus. Using the machine's cpu count."
122
124
  )
123
- return psutil.cpu_count()
125
+ return psutil.cpu_count(logical=True)
124
126
 
125
127
  if len(num_cpus_matches) > 1:
126
128
  logger.warning(
127
129
  "Unexpected output after running `scontrol show job $SLURM_JOB_ID` "
128
130
  + "to count SLURM-available cpus. Using the machine's cpu count."
129
131
  )
130
- return psutil.cpu_count()
132
+ return psutil.cpu_count(logical=True)
131
133
 
132
134
  num_cpus = num_cpus_matches[0].replace("NumCPUs=", "")
133
135
  logger.debug(f"Detected {num_cpus} cpus available on SLURM.")
@@ -563,6 +563,7 @@ AMD EPYC 7F32,180
563
563
  AMD EPYC 7F52,240
564
564
  AMD EPYC 7F72,240
565
565
  AMD EPYC 7H12,280
566
+ AMD EPYC 8024P,90
566
567
  AMD EPYC Embedded 3251,50
567
568
  AMD FX-4100,95
568
569
  AMD FX-4120,95
@@ -2231,6 +2232,7 @@ Intel Core i7-1185G7E,28
2231
2232
  Intel Core i7-1185GRE,28
2232
2233
  Intel Core i7-1195G7,28
2233
2234
  Intel Core i7-1270P,64
2235
+ Intel Core i7-12700K,190
2234
2236
  Intel Core i7-1360P,28
2235
2237
  Intel Core i7-2600,95
2236
2238
  Intel Core i7-2600K,95
@@ -3188,6 +3190,7 @@ Intel Xeon E5-2618L v2,50
3188
3190
  Intel Xeon E5-2618L v4,75
3189
3191
  Intel Xeon E5-2620,95
3190
3192
  Intel Xeon E5-2620 v2,80
3193
+ Intel Xeon E5-2620 v3,85
3191
3194
  Intel Xeon E5-2620 v4,85
3192
3195
  Intel Xeon E5-2623 v4,85
3193
3196
  Intel Xeon E5-2628L,60
@@ -3751,6 +3754,7 @@ Intel Xeon Platinum 8376HL,205
3751
3754
  Intel Xeon Platinum 8380,270
3752
3755
  Intel Xeon Platinum 8380H,250
3753
3756
  Intel Xeon Platinum 8380HL,250
3757
+ Intel Xeon Platinum 8370C,205
3754
3758
  Intel Xeon Platinum 9221,250
3755
3759
  Intel Xeon Platinum 9222,250
3756
3760
  Intel Xeon Platinum 9242,350
@@ -64,6 +64,9 @@ class BaseEmissionsTracker(ABC):
64
64
  and `CarbonTracker.`
65
65
  """
66
66
 
67
+ _scheduler: Optional[PeriodicScheduler] = None
68
+ _scheduler_monitor_power: Optional[PeriodicScheduler] = None
69
+
67
70
  def _set_from_conf(
68
71
  self, var, name, default=None, return_type=None, prevent_setter=False
69
72
  ):
@@ -170,6 +173,7 @@ class BaseEmissionsTracker(ABC):
170
173
  logger_preamble: Optional[str] = _sentinel,
171
174
  default_cpu_power: Optional[int] = _sentinel,
172
175
  pue: Optional[int] = _sentinel,
176
+ force_mode_cpu_load: Optional[bool] = _sentinel,
173
177
  allow_multiple_runs: Optional[bool] = _sentinel,
174
178
  ):
175
179
  """
@@ -225,6 +229,7 @@ class BaseEmissionsTracker(ABC):
225
229
  messages. Defaults to "".
226
230
  :param default_cpu_power: cpu power to be used as default if the cpu is not known.
227
231
  :param pue: PUE (Power Usage Effectiveness) of the datacenter.
232
+ :param force_mode_cpu_load: Force the addition of a CPU in MODE_CPU_LOAD
228
233
  :param allow_multiple_runs: Allow multiple instances of codecarbon running in parallel. Defaults to False.
229
234
  """
230
235
 
@@ -274,6 +279,7 @@ class BaseEmissionsTracker(ABC):
274
279
  self._set_from_conf(logger_preamble, "logger_preamble", "")
275
280
  self._set_from_conf(default_cpu_power, "default_cpu_power")
276
281
  self._set_from_conf(pue, "pue", 1.0, float)
282
+ self._set_from_conf(force_mode_cpu_load, "force_mode_cpu_load", False)
277
283
  self._set_from_conf(
278
284
  experiment_id, "experiment_id", "5b0fa12a-3dd7-45bb-9766-cc326314d9f1"
279
285
  )
@@ -330,6 +336,10 @@ class BaseEmissionsTracker(ABC):
330
336
  function=self._measure_power_and_energy,
331
337
  interval=self._measure_power_secs,
332
338
  )
339
+ self._scheduler_monitor_power = PeriodicScheduler(
340
+ function=self._monitor_power,
341
+ interval=1,
342
+ )
333
343
 
334
344
  self._data_source = DataSource()
335
345
 
@@ -417,6 +427,7 @@ class BaseEmissionsTracker(ABC):
417
427
  hardware.start()
418
428
 
419
429
  self._scheduler.start()
430
+ self._scheduler_monitor_power.start()
420
431
 
421
432
  def start_task(self, task_name=None) -> None:
422
433
  """
@@ -428,6 +439,9 @@ class BaseEmissionsTracker(ABC):
428
439
  if self._scheduler:
429
440
  self._scheduler.stop()
430
441
 
442
+ # Task background thread for measuring power
443
+ self._scheduler_monitor_power.start()
444
+
431
445
  if self._active_task:
432
446
  logger.info("A task is already under measure")
433
447
  return
@@ -457,6 +471,9 @@ class BaseEmissionsTracker(ABC):
457
471
  emissions.
458
472
  :return: None
459
473
  """
474
+ if self._scheduler_monitor_power:
475
+ self._scheduler_monitor_power.stop()
476
+
460
477
  task_name = task_name if task_name else self._active_task
461
478
  self._measure_power_and_energy()
462
479
 
@@ -524,6 +541,9 @@ class BaseEmissionsTracker(ABC):
524
541
  if self._scheduler:
525
542
  self._scheduler.stop()
526
543
  self._scheduler = None
544
+ if self._scheduler_monitor_power:
545
+ self._scheduler_monitor_power.stop()
546
+ self._scheduler_monitor_power = None
527
547
  else:
528
548
  logger.warning("Tracker already stopped !")
529
549
  for task_name in self._tasks:
@@ -652,6 +672,17 @@ class BaseEmissionsTracker(ABC):
652
672
  :return: Metadata containing cloud info
653
673
  """
654
674
 
675
+ def _monitor_power(self) -> None:
676
+ """
677
+ Monitor the power consumption of the hardware.
678
+ We do this for hardware that does not support energy monitoring.
679
+ So we could average the power consumption.
680
+ This method is called every 1 second. Even if we are in Task mode.
681
+ """
682
+ for hardware in self._hardware:
683
+ if isinstance(hardware, CPU):
684
+ hardware.monitor_power()
685
+
655
686
  def _do_measurements(self) -> None:
656
687
  for hardware in self._hardware:
657
688
  h_time = time.perf_counter()
@@ -668,8 +699,11 @@ class BaseEmissionsTracker(ABC):
668
699
  self._total_cpu_energy += energy
669
700
  self._cpu_power = power
670
701
  logger.info(
671
- f"Energy consumed for all CPUs : {self._total_cpu_energy.kWh:.6f} kWh"
672
- + f". Total CPU Power : {self._cpu_power.W} W"
702
+ f"Delta energy consumed for CPU with {hardware._mode} : {energy.kWh:.6f} kWh"
703
+ + f", power : {self._cpu_power.W} W"
704
+ )
705
+ logger.info(
706
+ f"Energy consumed for All CPU : {self._total_cpu_energy.kWh:.6f} kWh"
673
707
  )
674
708
  elif isinstance(hardware, GPU):
675
709
  self._total_gpu_energy += energy
@@ -704,8 +738,7 @@ class BaseEmissionsTracker(ABC):
704
738
  logger.error(f"Unknown hardware type: {hardware} ({type(hardware)})")
705
739
  h_time = time.perf_counter() - h_time
706
740
  logger.debug(
707
- f"{hardware.__class__.__name__} : {hardware.total_power().W:,.2f} "
708
- + f"W during {last_duration:,.2f} s [measurement time: {h_time:,.4f}]"
741
+ f"Done measure for {hardware.__class__.__name__} - measurement time: {h_time:,.4f} s - last call {last_duration:,.2f} s"
709
742
  )
710
743
  logger.info(
711
744
  f"{self._total_energy.kWh:.6f} kWh of electricity used since the beginning."
@@ -717,7 +750,13 @@ class BaseEmissionsTracker(ABC):
717
750
  every `self._measure_power_secs` seconds.
718
751
  :return: None
719
752
  """
720
- last_duration = time.perf_counter() - self._last_measured_time
753
+ try:
754
+ last_duration = time.perf_counter() - self._last_measured_time
755
+ except AttributeError as e:
756
+ logger.debug(
757
+ f"You need to start the tracker first before measuring. Or maybe you do multiple run at the same time ? Error: {e}"
758
+ )
759
+ raise e
721
760
 
722
761
  warning_duration = self._measure_power_secs * 3
723
762
  if last_duration > warning_duration:
@@ -2,6 +2,7 @@
2
2
  Encapsulates external dependencies to retrieve hardware metadata
3
3
  """
4
4
 
5
+ import math
5
6
  import re
6
7
  import subprocess
7
8
  from abc import ABC, abstractmethod
@@ -14,7 +15,7 @@ from codecarbon.core.cpu import IntelPowerGadget, IntelRAPL
14
15
  from codecarbon.core.gpu import AllGPUDevices
15
16
  from codecarbon.core.powermetrics import ApplePowermetrics
16
17
  from codecarbon.core.units import Energy, Power, Time
17
- from codecarbon.core.util import SLURM_JOB_ID, detect_cpu_model
18
+ from codecarbon.core.util import SLURM_JOB_ID, count_cpus, detect_cpu_model
18
19
  from codecarbon.external.logger import logger
19
20
 
20
21
  # default W value for a CPU if no model is found in the ref csv
@@ -25,6 +26,8 @@ CONSUMPTION_PERCENTAGE_CONSTANT = 0.5
25
26
 
26
27
  B_TO_GB = 1024 * 1024 * 1024
27
28
 
29
+ MODE_CPU_LOAD = "cpu_load"
30
+
28
31
 
29
32
  @dataclass
30
33
  class BaseHardware(ABC):
@@ -147,12 +150,20 @@ class CPU(BaseHardware):
147
150
  model: str,
148
151
  tdp: int,
149
152
  rapl_dir: str = "/sys/class/powercap/intel-rapl",
153
+ tracking_mode: str = "machine",
150
154
  ):
155
+ assert tracking_mode in ["machine", "process"]
156
+ self._power_history: List[Power] = []
151
157
  self._output_dir = output_dir
152
158
  self._mode = mode
153
159
  self._model = model
154
160
  self._tdp = tdp
155
161
  self._is_generic_tdp = False
162
+ self._tracking_mode = tracking_mode
163
+ self._pid = psutil.Process().pid
164
+ self._cpu_count = count_cpus()
165
+ self._process = psutil.Process(self._pid)
166
+
156
167
  if self._mode == "intel_power_gadget":
157
168
  self._intel_interface = IntelPowerGadget(self._output_dir)
158
169
  elif self._mode == "intel_rapl":
@@ -169,12 +180,62 @@ class CPU(BaseHardware):
169
180
 
170
181
  return s + ")"
171
182
 
183
+ @staticmethod
184
+ def _calculate_power_from_cpu_load(tdp, cpu_load, model):
185
+ if "AMD Ryzen Threadripper" in model:
186
+ return CPU._calculate_power_from_cpu_load_treadripper(tdp, cpu_load)
187
+ else:
188
+ return tdp * (cpu_load / 100.0)
189
+
190
+ @staticmethod
191
+ def _calculate_power_from_cpu_load_treadripper(tdp, cpu_load):
192
+ load = cpu_load / 100.0
193
+
194
+ if load < 0.1: # Below 10% CPU load
195
+ return tdp * (0.05 * load * 10)
196
+ elif load <= 0.3: # 10-30% load - linear phase
197
+ return tdp * (0.05 + 1.8 * (load - 0.1))
198
+ elif load <= 0.5: # 30-50% load - adjusted coefficients
199
+ # Increased base power and adjusted curve
200
+ base_power = 0.45 # Increased from 0.41
201
+ power_range = 0.50 # Increased from 0.44
202
+ factor = ((load - 0.3) / 0.2) ** 1.8 # Reduced power from 2.0 to 1.8
203
+ return tdp * (base_power + power_range * factor)
204
+ else: # Above 50% - plateau phase
205
+ return tdp * (0.85 + 0.15 * (1 - math.exp(-(load - 0.5) * 5)))
206
+
207
+ def _get_power_from_cpu_load(self):
208
+ """
209
+ When in MODE_CPU_LOAD
210
+ """
211
+ if self._tracking_mode == "machine":
212
+ tdp = self._tdp
213
+ cpu_load = psutil.cpu_percent(interval=0.5, percpu=False)
214
+ power = self._calculate_power_from_cpu_load(tdp, cpu_load, self._model)
215
+ logger.debug(
216
+ f"A TDP of {self._tdp} W and a CPU load of {cpu_load:.1f}% give an estimation of {power} W for whole machine."
217
+ )
218
+ elif self._tracking_mode == "process":
219
+ cpu_load = (
220
+ self._process.cpu_percent(interval=0.5, percpu=False) / self._cpu_count
221
+ )
222
+ power = self._calculate_power_from_cpu_load(self.tdp, cpu_load, self._model)
223
+ logger.debug(
224
+ f"A TDP of {self._tdp} W and a CPU load of {cpu_load * 100:.1f}% give an estimation of {power} W for process {self._pid}."
225
+ )
226
+ else:
227
+ raise Exception(f"Unknown tracking_mode {self._tracking_mode}")
228
+ return Power.from_watts(power)
229
+
172
230
  def _get_power_from_cpus(self) -> Power:
173
231
  """
174
232
  Get CPU power
175
233
  :return: power in kW
176
234
  """
177
- if self._mode == "constant":
235
+ if self._mode == MODE_CPU_LOAD:
236
+ power = self._get_power_from_cpu_load()
237
+ return power
238
+ elif self._mode == "constant":
178
239
  power = self._tdp * CONSUMPTION_PERCENTAGE_CONSTANT
179
240
  return Power.from_watts(power)
180
241
  if self._mode == "intel_rapl":
@@ -208,20 +269,35 @@ class CPU(BaseHardware):
208
269
  return Energy.from_energy(energy)
209
270
 
210
271
  def total_power(self) -> Power:
211
- cpu_power = self._get_power_from_cpus()
212
- return cpu_power
272
+ self._power_history.append(self._get_power_from_cpus())
273
+ power_history_in_W = [power.W for power in self._power_history]
274
+ cpu_power = sum(power_history_in_W) / len(power_history_in_W)
275
+ self._power_history = []
276
+ return Power.from_watts(cpu_power)
213
277
 
214
278
  def measure_power_and_energy(self, last_duration: float) -> Tuple[Power, Energy]:
215
279
  if self._mode == "intel_rapl":
216
280
  energy = self._get_energy_from_cpus(delay=Time(seconds=last_duration))
217
281
  power = self.total_power()
282
+ # Patch AMD Threadripper that count 2x the power
283
+ if "AMD Ryzen Threadripper" in self._model:
284
+ power = power / 2
285
+ energy = energy / 2
218
286
  return power, energy
219
- # If not intel_rapl
287
+ # If not intel_rapl, we call the parent method from BaseHardware
288
+ # to compute energy from power and time
220
289
  return super().measure_power_and_energy(last_duration=last_duration)
221
290
 
222
291
  def start(self):
223
292
  if self._mode in ["intel_power_gadget", "intel_rapl", "apple_powermetrics"]:
224
293
  self._intel_interface.start()
294
+ if self._mode == MODE_CPU_LOAD:
295
+ # The first time this is called it will return a meaningless 0.0 value which you are supposed to ignore.
296
+ _ = self._get_power_from_cpu_load()
297
+
298
+ def monitor_power(self):
299
+ cpu_power = self._get_power_from_cpus()
300
+ self._power_history.append(cpu_power)
225
301
 
226
302
  def get_model(self):
227
303
  return self._model
@@ -233,6 +309,7 @@ class CPU(BaseHardware):
233
309
  mode: str,
234
310
  model: Optional[str] = None,
235
311
  tdp: Optional[int] = None,
312
+ tracking_mode: str = "machine",
236
313
  ) -> "CPU":
237
314
  if model is None:
238
315
  model = detect_cpu_model()
@@ -245,7 +322,13 @@ class CPU(BaseHardware):
245
322
  cpu._is_generic_tdp = True
246
323
  return cpu
247
324
 
248
- return cls(output_dir=output_dir, mode=mode, model=model, tdp=tdp)
325
+ return cls(
326
+ output_dir=output_dir,
327
+ mode=mode,
328
+ model=model,
329
+ tdp=tdp,
330
+ tracking_mode=tracking_mode,
331
+ )
249
332
 
250
333
 
251
334
  @dataclass
@@ -78,12 +78,10 @@ class FileOutput(BaseOutput):
78
78
  self.output_dir, "emissions_" + experiment_name + "_" + run_id + ".csv"
79
79
  )
80
80
  df = pd.DataFrame(columns=data[0].values.keys())
81
- df = pd.concat(
82
- [
83
- df,
84
- pd.DataFrame.from_records(
85
- [dict(data_point.values) for data_point in data]
86
- ),
87
- ]
81
+ new_df = pd.DataFrame.from_records(
82
+ [dict(data_point.values) for data_point in data]
88
83
  )
84
+ # Filter out empty or all-NA columns, to avoid warnings from Pandas
85
+ new_df = new_df.dropna(axis=1, how="all")
86
+ df = pd.concat([df, new_df], ignore_index=True)
89
87
  df.to_csv(save_task_file_path, index=False)
@@ -183,8 +183,8 @@ include = [
183
183
  ]
184
184
 
185
185
  [tool.bumpver]
186
- current_version = "2.8.2"
187
- version_pattern = "MAJOR.MINOR.PATCH"
186
+ current_version = "3.0.0_rc0"
187
+ version_pattern = "MAJOR.MINOR.PATCH[_TAGNUM]"
188
188
 
189
189
  [tool.bumpver.file_patterns]
190
190
  "codecarbon/_version.py" = [
@@ -1 +0,0 @@
1
- __version__ = "2.8.2"
File without changes