joularcore 0.1.0

Joular Core is a platform to measure power and energy across all systems, OSes and devices
Documentation
/*
 * Copyright (c) 2025-2026, Adel Noureddine.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the
 * GNU Lesser General Public License v3.0 only (LGPL-3.0-only)
 * which accompanies this distribution, and is available at
 * https://www.gnu.org/licenses/lgpl-3.0.en.html
 *
 * Author : Adel Noureddine
 */

use crate::cpu::{AppCPUUtilization, CPUUtilization, ProcessCPUUtilization};
use sysinfo::System;

pub trait CPUEnergy {
    fn get_power(&self) -> f64;
}

pub trait GPUEnergy {
    fn get_power(&self) -> f64;
}

pub trait PlatformEnergy {
    fn name(&self) -> String {
        let name = System::name().unwrap_or_else(|| "Unknown OS".to_string());
        let version = System::os_version().unwrap_or_default();
        if version.is_empty() {
            name
        } else {
            format!("{name} {version}")
        }
    }

    fn cpu(&self) -> Box<dyn CPUEnergy>;
    fn gpu(&self) -> Box<dyn GPUEnergy>;

    fn cpu_usage(&self) -> Box<dyn CPUUtilization>;

    /// Create a process CPU utilization tracker
    /// Returns None if platform doesn't support process monitoring
    fn process_cpu_usage(&self) -> Option<Box<dyn ProcessCPUUtilization>> {
        None
    }

    /// Create an application CPU utilization tracker
    /// Returns None if platform doesn't support application monitoring
    fn app_cpu_usage(
        &self,
        _refresh_interval: std::time::Duration,
    ) -> Option<Box<dyn AppCPUUtilization>> {
        None
    }

    /// Calculate process energy from process utilization
    /// This is platform-independent and shared across all platforms
    fn process_energy(
        &self,
        process_utilization: f64,
        cpu_power: f64,
        cpu_utilization: f64,
    ) -> f64 {
        // Epsilon (0.01% of CPU) avoids dividing by near-zero noise on a
        // nearly-idle machine, which would otherwise blow up the attribution.
        if cpu_utilization < 0.01 {
            return 0.0;
        }
        let attributed = 100.0 * ((process_utilization * cpu_power) / cpu_utilization);
        // A single process can never draw more than the whole CPU budget.
        attributed.clamp(0.0, cpu_power)
    }

    /// Calculate application energy from application utilization
    /// Same formula as process energy, but for multiple PIDs summed
    fn app_energy(&self, app_utilization: f64, cpu_power: f64, cpu_utilization: f64) -> f64 {
        if cpu_utilization < 0.01 {
            return 0.0;
        }
        let attributed = 100.0 * ((app_utilization * cpu_power) / cpu_utilization);
        attributed.clamp(0.0, cpu_power)
    }
}