Skip to content

Commit 07de74f

Browse files
committed
feat(codecarbon) add raspberry support
1 parent 6a81c29 commit 07de74f

4 files changed

Lines changed: 136 additions & 3 deletions

File tree

codecarbon/core/cpu.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ def is_rapl_available() -> bool:
5858
return False
5959

6060

61+
def is_raspberry() -> bool:
62+
"""
63+
Check if raspberry power util is present
64+
"""
65+
return os.path.exists("/usr/bin/vcgencmd")
66+
67+
6168
class IntelPowerGadget:
6269
"""
6370
A class to interface with Intel Power Gadget for monitoring CPU power consumption on Windows and (non-Apple Silicon) macOS.

codecarbon/core/resource_tracker.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from codecarbon.core import cpu, gpu, powermetrics
55
from codecarbon.core.config import parse_gpu_ids
66
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, RAM, AppleSiliconChip, Raspberry
88
from codecarbon.external.logger import logger
99

1010

@@ -19,7 +19,9 @@ def set_RAM_tracking(self):
1919
self.ram_tracker = "3 Watts for 8 GB ratio constant"
2020
ram = RAM(tracking_mode=self.tracker._tracking_mode)
2121
self.tracker._conf["ram_total_size"] = ram.machine_memory_GB
22-
self.tracker._hardware: List[Union[RAM, CPU, GPU, AppleSiliconChip]] = [ram]
22+
self.tracker._hardware: List[
23+
Union[RAM, CPU, GPU, AppleSiliconChip, Raspberry]
24+
] = [ram]
2325

2426
def set_CPU_tracking(self):
2527
logger.info("[setup] CPU Tracking...")
@@ -35,6 +37,18 @@ def set_CPU_tracking(self):
3537
hardware = CPU.from_utils(self.tracker._output_dir, "intel_rapl")
3638
self.tracker._hardware.append(hardware)
3739
self.tracker._conf["cpu_model"] = hardware.get_model()
40+
elif cpu.is_raspberry():
41+
logger.info("Tracking CPU via raspberry utils")
42+
self.cpu_tracker = "raspberry"
43+
hardware_cpu = Raspberry.from_utils(
44+
self.tracker._output_dir, chip_part="CPU"
45+
)
46+
self.tracker._hardware.append(hardware_cpu)
47+
hardware_ram = Raspberry.from_utils(
48+
self.tracker._output_dir, chip_part="RAM"
49+
)
50+
self.tracker._hardware.append(hardware_ram)
51+
self.tracker._conf["cpu_model"] = hardware_cpu.get_model()
3852
# change code to check if powermetrics needs to be installed or just sudo setup
3953
elif (
4054
powermetrics.is_powermetrics_available()

codecarbon/emissions_tracker.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from codecarbon.core.units import Energy, Power, Time
2121
from codecarbon.core.util import count_cpus, suppress
2222
from codecarbon.external.geography import CloudMetadata, GeoMetadata
23-
from codecarbon.external.hardware import CPU, GPU, RAM, AppleSiliconChip
23+
from codecarbon.external.hardware import CPU, GPU, RAM, AppleSiliconChip, Raspberry
2424
from codecarbon.external.logger import logger, set_logger_format, set_logger_level
2525
from codecarbon.external.scheduler import PeriodicScheduler
2626
from codecarbon.external.task import Task
@@ -705,6 +705,21 @@ def _do_measurements(self) -> None:
705705
f"Energy consumed for RAM : {self._total_ram_energy.kWh:.6f} kWh"
706706
+ f". RAM Power : {self._ram_power.W} W"
707707
)
708+
elif isinstance(hardware, Raspberry):
709+
if hardware.chip_part == "CPU":
710+
self._total_cpu_energy += energy
711+
self._cpu_power = power
712+
logger.info(
713+
f"Energy consumed for all CPUs : {self._total_cpu_energy.kWh:.6f} kWh"
714+
+ f". Total CPU Power : {self._cpu_power.W} W"
715+
)
716+
elif hardware.chip_part == "RAM":
717+
self._total_ram_energy += energy
718+
self._ram_power = power
719+
logger.info(
720+
f"Energy consumed for RAMs : {self._total_ram_energy.kWh:.6f} kWh"
721+
+ f". Total RAM Power : {self._ram_power.W} W"
722+
)
708723
elif isinstance(hardware, AppleSiliconChip):
709724
if hardware.chip_part == "CPU":
710725
self._total_cpu_energy += energy

codecarbon/external/hardware.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import subprocess
77
from abc import ABC, abstractmethod
88
from dataclasses import dataclass
9+
from subprocess import run
910
from typing import Dict, Iterable, List, Optional, Tuple
1011

1112
import psutil
@@ -487,3 +488,99 @@ def from_utils(
487488
logger.warning("Could not read AppleSiliconChip model.")
488489

489490
return cls(output_dir=output_dir, model=model, chip_part=chip_part)
491+
492+
493+
@dataclass
494+
class Raspberry(BaseHardware):
495+
def __init__(
496+
self,
497+
output_dir: str,
498+
model: str,
499+
chip_part: str = "CPU",
500+
):
501+
if chip_part == "CPU":
502+
self.WANTED_COMPONENTS = (
503+
"3V7_WL_SW",
504+
"3V3_SYS",
505+
"1V8_SYS",
506+
"1V1_SYS",
507+
"0V8_SW",
508+
"VDD_CORE",
509+
"3V3_DAC",
510+
"3V3_ADC",
511+
"0V8_AON",
512+
)
513+
elif chip_part == "RAM":
514+
self.WANTED_COMPONENTS = ("DDR_VDD2", "DDR_VDD2", "DDR_VDDQ")
515+
else:
516+
raise Exception("Unknown chip part", chip_part)
517+
518+
self._output_dir = output_dir
519+
self._model = model
520+
self.chip_part = chip_part
521+
522+
def __repr__(self) -> str:
523+
return f"Raspberry ({self._model} > {self.chip_part})"
524+
525+
def _get_power(self) -> Power:
526+
""" """
527+
measure: Dict = self.get_measure()
528+
return Power.from_watts(measure["power"])
529+
530+
def _get_energy(self, delay: Time) -> Energy:
531+
"""
532+
Get Chip part energy deltas
533+
Args:
534+
chip_part (str): Chip part to get power from (Processor, GPU, etc.)
535+
:return: energy in kWh
536+
"""
537+
energy = Energy.from_power_and_time(
538+
power=self._get_power(), time=Time.from_seconds(delay)
539+
)
540+
return energy
541+
542+
def total_power(self) -> Power:
543+
return self._get_power()
544+
545+
def start(self):
546+
# ...
547+
...
548+
549+
def get_model(self):
550+
return self._model
551+
552+
def get_measure(self):
553+
components = {}
554+
res = run(["vcgencmd", "pmic_read_adc"], capture_output=True)
555+
lines = res.stdout.decode("utf-8").splitlines()
556+
for line in lines:
557+
res = re.search(
558+
"([A-Z_0-9]+)_[VA] (current|volt)\(([0-9]+)\)=([0-9.]+)", # noqa: W605
559+
line,
560+
)
561+
component_name, measure_type, idx, value = res.groups()
562+
component = components[component_name] = components.get(component_name, {})
563+
component[measure_type] = float(value)
564+
pi_power = 0
565+
566+
for component_name, component in components.items():
567+
try:
568+
component["power"] = component["volt"] * component["current"]
569+
if component_name in self.WANTED_COMPONENTS:
570+
pi_power += component["power"]
571+
except Exception:
572+
...
573+
return {
574+
"power": pi_power,
575+
}
576+
577+
@classmethod
578+
def from_utils(
579+
cls, output_dir: str, model: Optional[str] = None, chip_part: str = "CPU"
580+
) -> "Raspberry":
581+
if model is None:
582+
model = detect_cpu_model()
583+
if model is None:
584+
logger.warning("Could not read Raspberry model.")
585+
586+
return cls(output_dir=output_dir, model=model, chip_part=chip_part)

0 commit comments

Comments
 (0)