Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
418 changes: 418 additions & 0 deletions codecarbon/core/Modules/perfmodule.c

Large diffs are not rendered by default.

78 changes: 76 additions & 2 deletions codecarbon/core/cpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Intel Power Gadget
https://software.intel.com/content/www/us/en/develop/articles/intel-power-gadget.html
"""
import abc
import os
import shutil
import subprocess
Expand All @@ -15,6 +16,10 @@
with warnings.catch_warnings(record=True) as w:
from fuzzywuzzy import fuzz

try:
from codecarbon.core.perf import Perf
except ImportError:
Perf = None
from codecarbon.core.rapl import RAPLFile
from codecarbon.core.units import Time
from codecarbon.core.util import detect_cpu_model
Expand Down Expand Up @@ -46,7 +51,32 @@ def is_rapl_available():
return False


class IntelPowerGadget:
def is_perf_available():
try:
if Perf is not None:
Perf(["energy-pkg"])
return True
else:
return False
except Exception as e:
logger.debug(
"Not using the Perf interface, an exception occurred while instantiating "
+ f"Perf : {e}",
)
return False


class BaseHardwareMeasurement(abc.ABC):
@abc.abstractmethod
def start(self) -> None:
pass

@abc.abstractmethod
def get_cpu_details(self, **kwargs) -> Dict:
pass


class IntelPowerGadget(BaseHardwareMeasurement):
_osx_exec = "PowerLog"
_osx_exec_backup = "/Applications/Intel Power Gadget/PowerLog"
_windows_exec = "PowerLog3.0.exe"
Expand Down Expand Up @@ -156,8 +186,45 @@ def start(self):
# TODO: Read energy
pass

def stop(self):
# TODO: Read energy
pass


class PerfCPUWrapper(BaseHardwareMeasurement):
def __init__(self) -> None:
self._perfinterface = Perf(["energy-pkg"])
self.cpu_details: Dict = dict()

def start(self):
self._perfinterface.start()

def stop(self):
self._perfinterface.stop()

def get_cpu_details(self, duration: Time, **kwargs) -> Dict:
"""
Fetches the CPU Energy Deltas by fetching values from RAPL files
"""
cpu_details = dict()

cpu_details["Processor Energy Delta_0"] = self._perfinterface.delta(
duration.seconds
).kWh
self.cpu_details = cpu_details
logger.debug(f"get_cpu_details {self.cpu_details}")
return cpu_details

def get_static_cpu_details(self) -> Dict:
"""
Return CPU details without computing them.
"""
logger.debug(f"get_static_cpu_details {self.cpu_details}")

return self.cpu_details

class IntelRAPL:

class IntelRAPL(BaseHardwareMeasurement):
def __init__(self, rapl_dir="/sys/class/powercap/intel-rapl"):
self._lin_rapl_dir = rapl_dir
self._system = sys.platform.lower()
Expand Down Expand Up @@ -260,6 +327,10 @@ def start(self):
for rapl_file in self._rapl_files:
rapl_file.start()

def stop(self):
for rapl_file in self._rapl_files:
rapl_file.stop()


class TDP:
def __init__(self):
Expand Down Expand Up @@ -388,3 +459,6 @@ def _main(self) -> Tuple[str, int]:

def start(self):
pass

def stop(self):
pass
3 changes: 3 additions & 0 deletions codecarbon/core/rapl.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ def start(self) -> None:
self.last_energy = self._get_value()
return

def stop(self) -> None:
pass

def delta(self, duration: Time) -> None:
"""
Compute the energy used since last call.
Expand Down
5 changes: 5 additions & 0 deletions codecarbon/data/hardware/cpu_power.csv
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,9 @@ AMD Turion X2 Ultra ZM-88,35
AMD Z-01,6
AMD Z-60,5
Apple M1,10
AWS Graviton,95
AWS Graviton2,110
AWS Graviton3,100
Intel A100,3
Intel A110,3
Intel Atom 230,4
Expand Down Expand Up @@ -2020,6 +2023,7 @@ Intel Xeon E5-2680 v2,115
Intel Xeon E5-2680 v4,120
Intel Xeon E5-2687W v2,130
Intel Xeon E5-2690 v2,130
Intel Xeon E5-2690 v4,135
Intel Xeon E5-2692 v2,100
Intel Xeon E5-2695 v2,115
Intel Xeon E5-2697 v2,130
Expand Down Expand Up @@ -2158,3 +2162,4 @@ Intel Xeon X5672,95
Intel Xeon X5677,130
Intel Xeon X5680,130
Intel Xeon X5687,130
Neoverse-N1,110
9 changes: 9 additions & 0 deletions codecarbon/emissions_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,11 @@ def __init__(
hardware = CPU.from_utils(self._output_dir, "intel_power_gadget")
self._hardware.append(hardware)
self._conf["cpu_model"] = hardware.get_model()
elif cpu.is_perf_available():
logger.info("Tracking CPU via Linux Perf interface")
hardware = CPU.from_utils(self._output_dir, "linux_perf")
self._hardware.append(hardware)
self._conf["cpu_model"] = hardware.get_model()
elif cpu.is_rapl_available():
logger.info("Tracking Intel CPU via RAPL interface")
hardware = CPU.from_utils(self._output_dir, "intel_rapl")
Expand Down Expand Up @@ -523,6 +528,10 @@ def stop(self) -> Optional[float]:
# or if scheduler interval was longer than the run
self._measure_power_and_energy()

# Shutdown tracking for hardware after taking last measurement
for hardware in self._hardware:
hardware.stop()

emissions_data = self._prepare_emissions_data()

self._persist_data(emissions_data, experiment_name=self._experiment_name)
Expand Down
32 changes: 22 additions & 10 deletions codecarbon/external/hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import psutil

from codecarbon.core.cpu import IntelPowerGadget, IntelRAPL
from codecarbon.core.cpu import IntelPowerGadget, IntelRAPL, PerfCPUWrapper
from codecarbon.core.gpu import AllGPUDevices
from codecarbon.core.units import Energy, Power, Time
from codecarbon.core.util import detect_cpu_model
Expand Down Expand Up @@ -49,6 +49,9 @@ def measure_power_and_energy(self, last_duration: float) -> Tuple[Power, Energy]
def start(self) -> None:
pass

def stop(self) -> None:
pass


@dataclass
class GPU(BaseHardware):
Expand Down Expand Up @@ -137,9 +140,11 @@ def __init__(
self._tdp = tdp
self._is_generic_tdp = False
if self._mode == "intel_power_gadget":
self._intel_interface = IntelPowerGadget(self._output_dir)
self._hw_measurement_interface = IntelPowerGadget(self._output_dir)
elif self._mode == "intel_rapl":
self._intel_interface = IntelRAPL(rapl_dir=rapl_dir)
self._hw_measurement_interface = IntelRAPL(rapl_dir=rapl_dir)
elif self._mode == "linux_perf":
self._hw_measurement_interface = PerfCPUWrapper()

def __repr__(self) -> str:
if self._mode != "constant":
Expand All @@ -160,11 +165,13 @@ def _get_power_from_cpus(self) -> Power:
if self._mode == "constant":
power = self._tdp * CONSUMPTION_PERCENTAGE_CONSTANT
return Power.from_watts(power)
if self._mode == "intel_rapl":
if self._mode in ["intel_rapl", "linux_perf"]:
# Don't call get_cpu_details to avoid computing energy twice and losing data.
all_cpu_details: Dict = self._intel_interface.get_static_cpu_details()
all_cpu_details: Dict = (
self._hw_measurement_interface.get_static_cpu_details()
)
else:
all_cpu_details: Dict = self._intel_interface.get_cpu_details()
all_cpu_details: Dict = self._hw_measurement_interface.get_cpu_details()

power = 0
for metric, value in all_cpu_details.items():
Expand All @@ -182,7 +189,7 @@ def _get_energy_from_cpus(self, delay: Time) -> Energy:
Get CPU energy deltas from RAPL files
:return: energy in kWh
"""
all_cpu_details: Dict = self._intel_interface.get_cpu_details(delay)
all_cpu_details: Dict = self._hw_measurement_interface.get_cpu_details(delay)

energy = 0
for metric, value in all_cpu_details.items():
Expand All @@ -196,16 +203,21 @@ def total_power(self) -> Power:
return cpu_power

def measure_power_and_energy(self, last_duration: float) -> Tuple[Power, Energy]:
if self._mode == "intel_rapl":
if self._mode in ["intel_rapl", "linux_perf"]:
energy = self._get_energy_from_cpus(delay=Time(seconds=last_duration))
power = self.total_power()
return power, energy
# If not intel_rapl
return super().measure_power_and_energy(last_duration=last_duration)

def start(self):
if self._mode in ["intel_power_gadget", "intel_rapl"]:
self._intel_interface.start()
if self._mode in ["intel_power_gadget", "intel_rapl", "linux_perf"]:
self._hw_measurement_interface.start()
pass

def stop(self):
if self._mode in ["intel_power_gadget", "intel_rapl", "linux_perf"]:
self._hw_measurement_interface.stop()
pass

def get_model(self):
Expand Down
7 changes: 7 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,11 @@
"codecarbon = codecarbon.cli.main:codecarbon",
]
},
ext_modules=[
setuptools.Extension(
name="codecarbon.core.perf",
sources=["codecarbon/core/Modules/perfmodule.c"],
optional=True,
)
],
)
20 changes: 20 additions & 0 deletions tests/test_core_perf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import time

import codecarbon.core.cpu

try:
from codecarbon.core.perf import Perf
except ImportError:
Perf = None


def test_perf():
if codecarbon.core.cpu.is_perf_available() is False:
return
x = Perf(["energy-pkg"])
x.start()
time.sleep(20)
x.delta(20.0)
time.sleep(10)
x.delta(10.0)
x.stop()