use crate::cpu::{
AppCPUUtilization, AppMonitor, CPUUtilization, ProcStatCpuUsage, ProcStatProcessUtil,
ProcessCPUUtilization,
};
use crate::energy::{CPUEnergy, GPUEnergy, PlatformEnergy};
use serde::Deserialize;
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, BufRead, Read};
pub struct SBCPlatform;
impl SBCPlatform {
pub fn new() -> Self {
SBCPlatform
}
}
impl PlatformEnergy for SBCPlatform {
fn cpu(&self) -> Box<dyn CPUEnergy> {
Box::new(SBCCpu::new())
}
fn gpu(&self) -> Box<dyn GPUEnergy> {
Box::new(SBCGpu)
}
fn cpu_usage(&self) -> Box<dyn CPUUtilization> {
Box::new(ProcStatCpuUsage::new())
}
fn process_cpu_usage(&self) -> Option<Box<dyn ProcessCPUUtilization>> {
Some(Box::new(ProcStatProcessUtil::new()))
}
fn app_cpu_usage(
&self,
refresh_interval: std::time::Duration,
) -> Option<Box<dyn AppCPUUtilization>> {
Some(Box::new(AppMonitor::new(
ProcStatProcessUtil::new,
refresh_interval,
)))
}
}
pub struct SBCGpu;
impl GPUEnergy for SBCGpu {
fn get_power(&self) -> f64 {
0.0
}
}
pub struct SBCCpu {
sbc_device: SBCDevice,
cpu_usage: ProcStatCpuUsage,
}
impl SBCCpu {
pub fn new() -> Self {
Self {
sbc_device: SBCDevice::new().unwrap_or_default(),
cpu_usage: ProcStatCpuUsage::new(),
}
}
}
impl CPUEnergy for SBCCpu {
fn get_power(&self) -> f64 {
self.sbc_device
.calculate_cpu_power(self.cpu_usage.get_cpu_utilization())
.unwrap_or(0.0)
}
}
const PLATFORMS: &[(&str, &[f64])] = &[
(
"rbp5b1.0-64",
&[
8.71648242592654,
-186.5634686,
2676.08397456,
-18839.18873015,
75110.3949109,
-180373.73562042,
267380.23597063,
-240403.81341249,
120509.01405772,
-25877.41360901,
],
),
(
"rbp4001.0-64",
&[
2.6630056198236938,
0.82814554,
-112.17687631,
1753.99173239,
-10992.65341181,
35988.45610911,
-66254.20051068,
69071.21138567,
-38089.87171735,
8638.45610698,
],
),
(
"rbp4b1.2",
&[
2.58542069543335,
12.335449,
-248.010554,
2379.83232,
-11962.419149,
34444.268647,
-58455.266502,
57698.685016,
-30618.557703,
6752.265368,
],
),
(
"rbp4b1.2-64",
&[
3.039940056604439,
-3.074225,
47.753114,
-271.974551,
879.966571,
-1437.466442,
1133.325791,
-345.134888,
],
),
(
"rbp4b1.1",
&[
2.5718068562852086,
2.794871,
-58.954883,
838.875781,
-5371.428686,
18168.842874,
-34369.583554,
36585.681749,
-20501.30764,
4708.33149,
],
),
(
"rbp4b1.1-64",
&[
3.405685008777926,
-11.834416,
137.312822,
-775.891511,
2563.399671,
-4783.024354,
4974.960753,
-2691.923074,
590.355251,
],
),
(
"rbp3b+1.3",
&[
2.484396997449118,
2.933542,
-150.400134,
2278.69031,
-15008.559279,
51537.315529,
-98756.887779,
106478.929766,
-60432.910139,
14053.677709,
],
),
(
"rbp3b1.2",
&[
1.524116907651687,
10.053851,
-234.18693,
2516.322119,
-13733.555536,
41739.918887,
-73342.794259,
74062.644914,
-39909.425362,
8894.110508,
],
),
(
"rbp2b1.1",
&[
1.3596870187778196,
5.13509,
-103.296366,
1027.169748,
-5323.639404,
15592.036875,
-26675.601585,
26412.963366,
-14023.471809,
3089.7862,
],
),
(
"rbp1b+1.2",
&[
1.2513999338064061,
1.857815,
-18.109537,
101.531231,
-346.386617,
749.560352,
-1028.802514,
863.877618,
-403.270951,
79.925932,
],
),
(
"rbp1b2",
&[
2.826093843916506,
3.539891,
-43.586963,
282.48856,
-1074.116844,
2537.679443,
-3761.784242,
3391.045904,
-1692.84087,
357.800968,
],
),
(
"rbpzw1.1",
&[
0.8551610676717238,
7.207151,
-135.517893,
1254.808001,
-6329.450524,
18502.371291,
-32098.028941,
32554.67989,
-17824.350159,
4069.178175,
],
),
(
"asustbs",
&[
3.9146162374630173,
-19.85430796,
141.7306532,
-298.12713091,
-1115.76983141,
8238.27573132,
-20976.13898406,
27132.90930519,
-17741.01303757,
4640.69530931,
],
),
];
#[derive(Deserialize)]
struct PolynomialModel {
intercept: String,
coefficients: Vec<String>,
}
#[derive(Deserialize)]
struct PlatformModel {
polynomial: PolynomialModel,
}
#[derive(Debug, Default)]
pub struct SBCDevice {
device_model: String,
coefficients: Option<Vec<f64>>,
}
impl SBCDevice {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
let device_model = Self::detect_device()?;
if device_model.is_empty() {
return Err("Unsupported device model".into());
}
let coefficients = Self::load_json_coefficients(&device_model).ok();
Ok(Self {
device_model,
coefficients,
})
}
fn load_json_coefficients(device_model: &str) -> Result<Vec<f64>, Box<dyn std::error::Error>> {
let path = std::env::var("SBC_POWER_MODEL_JSON")?;
let mut contents = String::new();
File::open(path)?.read_to_string(&mut contents)?;
let models: HashMap<String, PlatformModel> = serde_json::from_str(&contents)?;
let model = models
.get(device_model)
.ok_or_else(|| format!("Device {} not found in JSON", device_model))?;
let mut coeffs = vec![model.polynomial.intercept.parse()?];
for c in &model.polynomial.coefficients {
coeffs.push(c.parse()?);
}
Ok(coeffs)
}
fn detect_device() -> Result<String, Box<dyn std::error::Error>> {
let models = [
("Raspberry Pi 5 Model B Rev 1.0", "rbp5b1.0", "rbp5b1.0-64"),
("Raspberry Pi 5 Model B", "rbp5b1.0", "rbp5b1.0-64"),
("Raspberry Pi 400 Rev 1.0", "rbp4001.0", "rbp4001.0-64"),
("Raspberry Pi 400", "rbp4001.0", "rbp4001.0-64"),
("Raspberry Pi 4 Model B Rev 1.2", "rbp4b1.2", "rbp4b1.2-64"),
("Raspberry Pi 4 Model B Rev 1.1", "rbp4b1.1", "rbp4b1.1-64"),
("Raspberry Pi 4 Model B", "rbp4b1.2", "rbp4b1.2-64"),
(
"Raspberry Pi 3 Model B Plus Rev 1.3",
"rbp3b+1.3",
"rbp3b+1.3",
),
("Raspberry Pi 3 Model B Plus", "rbp3b+1.3", "rbp3b+1.3"),
("Raspberry Pi 3 Model B Rev 1.2", "rbp3b1.2", "rbp3b1.2"),
("Raspberry Pi 3 Model B", "rbp3b1.2", "rbp3b1.2"),
("Raspberry Pi 2 Model B Rev 1.1", "rbp2b1.1", "rbp2b1.1"),
("Raspberry Pi 2 Model B", "rbp2b1.1", "rbp2b1.1"),
(
"Raspberry Pi Model B Plus Rev 1.2",
"rbp1b+1.2",
"rbp1b+1.2",
),
("Raspberry Pi Model B Plus", "rbp1b+1.2", "rbp1b+1.2"),
("Raspberry Pi Model B Rev 2", "rbp1b2", "rbp1b2"),
("Raspberry Pi Model B", "rbp1b2", "rbp1b2"),
("Raspberry Pi Zero W Rev 1.1", "rbpzw1.1", "rbpzw1.1"),
("Raspberry Pi Zero W", "rbpzw1.1", "rbpzw1.1"),
("ASUS Tinker Board (S)", "asustbs", "asustbs"),
("ASUS Tinker Board", "asustbs", "asustbs"),
];
let is_64bit = std::env::consts::ARCH == "aarch64";
let file = File::open("/proc/device-tree/model")?;
for line in io::BufReader::new(file).lines().flatten() {
for &(substring, name_32, name_64) in &models {
if line.contains(substring) {
return Ok((if is_64bit { name_64 } else { name_32 }).to_string());
}
}
}
Ok(String::new())
}
pub fn calculate_cpu_power(
&self,
cpu_utilization: f64,
) -> Result<f64, Box<dyn std::error::Error>> {
if self.device_model.is_empty() {
return Ok(0.0);
}
let coeffs = match &self.coefficients {
Some(c) => c.as_slice(),
None => PLATFORMS
.iter()
.find(|(name, _)| *name == self.device_model)
.map(|(_, c)| *c)
.ok_or_else(|| format!("Platform not supported: {}", self.device_model))?,
};
let power: f64 = coeffs
.iter()
.enumerate()
.map(|(i, c)| c * cpu_utilization.powi(i as i32))
.sum();
Ok(power.max(0.0))
}
}