"""Defines Intel RAPL related classes."""
import logging
import pprint as pp
import re
from io import IOBase
from pathlib import Path
from typing import Callable
import cpuinfo
import numpy as np
from perun.backend.backend import Backend
from perun.data_model.measurement_type import Magnitude, MetricMetaData, Number, Unit
from perun.data_model.sensor import DeviceType, Sensor
log = logging.getLogger(__name__)
RAPL_PATH = "/sys/class/powercap/"
DIR_RGX = r"intel-rapl:(\d)$"
SUBDIR_RGX = r"intel-rapl:\d:\d$"
[docs]
class PowercapRAPLBackend(Backend):
"""Powercap RAPL as a source of cpu and memory devices.
Uses the powercap filesystem files to gather device information and creates metrics for each available device
"""
id = "powercap_rapl"
name = "Powercap RAPL"
description = "Reads energy usage from CPUs and DRAM using Powercap RAPL"
[docs]
def setup(self) -> None:
"""Check Intel RAPL access."""
self._files = []
cpuInfo = cpuinfo.get_cpu_info()
self._metadata = {}
for key, value in cpuInfo.items():
if value is not None and value != "":
self._metadata[key] = str(value)
self.devices: dict[str, Sensor] = {}
log.debug(f"CPU info metadata: {pp.pformat(self._metadata)}")
raplPath = Path(RAPL_PATH)
def getCallback(file: IOBase, file_path: str) -> Callable[[], Number]:
def func() -> Number:
try:
file.seek(0)
return np.uint64(file.readline().strip())
except Exception as e:
log.warning(f"Error reading file: {file_path}")
log.exception(e)
return np.uint64(0)
return func
if not raplPath.exists():
raise ImportWarning("No powercap interface")
packageDevices = []
packageFiles = []
foundPsys = False
for child in raplPath.iterdir():
log.debug(child)
match = re.match(DIR_RGX, child.name)
log.debug(match)
if match:
energy_path = child / "energy_uj"
log.debug(f"Path: {energy_path}")
log.debug(f"Exists: {energy_path.exists()}")
log.debug(f"Is file: {energy_path.is_file()}")
# Change os.access to Path.exists() and Path.is_file(), and open with Path.open(), as it is more secure and reliable according to https://docs.python.org/3/library/os.html#os.access
try:
energy_file = open(child / "energy_uj", "r")
except Exception as e:
log.debug(f"Error opening file: {child / 'energy_uj'}")
log.debug(e)
continue
log.debug(f"Opened file: {energy_file}")
socket = match.groups()[0]
with open(child / "name", "r") as file:
device_name = file.readline().strip()
log.debug(device_name)
if "dram" in device_name:
devType = DeviceType.RAM
elif "package" in device_name:
devType = DeviceType.CPU
# Ignoring psys interface until I get more data.
# This paper might have no clue : https://dl.acm.org/doi/10.1145/3177754
# elif "psys" in device_name:
# devType = DeviceType.CPU
# foundPsys = True
else:
devType = DeviceType.OTHER
if devType != DeviceType.OTHER:
with open(child / "max_energy_range_uj", "r") as file:
line = file.readline().strip()
max_energy = np.uint64(line)
dataType = MetricMetaData(
Unit.JOULE,
Magnitude.MICRO,
np.dtype("uint64"),
np.uint64(0),
max_energy,
max_energy,
)
log.debug(f"RAPL FILE OPENED: {energy_path}")
self._files.append(energy_file)
device = Sensor(
f"{devType.value}_{socket}_{device_name}",
devType,
self._metadata,
dataType,
getCallback(energy_file, str(energy_path)),
)
self.devices[device.id] = device
if "package" in device_name:
packageDevices.append(device)
packageFiles.append(energy_file)
for grandchild in child.iterdir():
match = re.match(SUBDIR_RGX, grandchild.name)
if match:
with open(grandchild / "name", "r") as file:
device_name = file.readline().strip()
if "dram" in device_name:
devType = DeviceType.RAM
elif "package" in device_name:
devType = DeviceType.CPU
# elif "psys" in device_name:
# devType = DeviceType.CPU
# foundPsys = True
else:
devType = DeviceType.OTHER
if devType != DeviceType.OTHER:
with open(
grandchild / "max_energy_range_uj", "r"
) as file:
line = file.readline().strip()
max_energy = np.uint64(line)
dataType = MetricMetaData(
Unit.JOULE,
Magnitude.MICRO,
np.dtype("uint64"),
np.uint64(0),
max_energy,
max_energy,
)
grandchild_energy_path = grandchild / "energy_uj"
energy_file = open(grandchild_energy_path, "r")
log.debug(f"RAPL FILE OPENED: {grandchild_energy_path}")
self._files.append(energy_file)
device = Sensor(
f"{devType.value}_{socket}_{device_name}",
devType,
self._metadata,
dataType,
getCallback(
energy_file, str(grandchild_energy_path)
),
)
log.debug(device)
self.devices[device.id] = device
if "package" in device_name:
packageDevices.append(device)
if foundPsys:
for pkg, file in zip(packageDevices, packageFiles):
log.info(f"Closing file: {file}")
file.close()
del self.devices[pkg.id]
log.debug(
f"Powercap RAPL devices {pp.pformat([deviceId for deviceId in self.devices])}"
)
[docs]
def close(self) -> None:
"""Backend shutdown code (does nothing for intel rapl)."""
log.debug("Closing files")
for file in self._files:
log.debug(f"Closing file: {file}")
file.close()
return
[docs]
def availableSensors(self) -> dict[str, tuple]:
"""Return string id set of visible devices.
Returns
-------
set[str]
Set with visible device ids.
"""
return {
sensor_id: (self.id, sensor.type, sensor.dataType.unit)
for sensor_id, sensor in self.devices.items()
}
[docs]
def getSensors(self, deviceList: set[str]) -> list[Sensor]:
"""Gather device objects based on a set of device ids.
Parameters
----------
deviceList : set[str]
Set of device ids.
Returns
-------
list[Sensor]
Device objects.
"""
return [self.devices[deviceId] for deviceId in deviceList]