#[cfg(feature = "api")]
use crate::api;
use crate::energy::{CPUEnergy, GPUEnergy, PlatformEnergy};
#[cfg(feature = "vm")]
use crate::vm;
use crate::{Component, args::Args, platform, ringbuffer::RingBufferWriter};
#[cfg(feature = "api")]
use std::{sync::mpsc, thread};
#[cfg(feature = "api")]
use tokio::sync::broadcast::Sender;
#[cfg(feature = "api")]
pub type ApiSender = Option<Sender<api::ApiData>>;
#[cfg(not(feature = "api"))]
pub type ApiSender = Option<()>;
#[cfg(feature = "api")]
pub type ApiShutdownTx = Option<tokio::sync::oneshot::Sender<()>>;
#[cfg(not(feature = "api"))]
pub type ApiShutdownTx = Option<()>;
pub struct JoularContext {
pub cpu_energy: Box<dyn CPUEnergy>,
pub gpu_energy: Box<dyn GPUEnergy>,
pub platform: Box<dyn PlatformEnergy>,
pub ringbuffer: Option<RingBufferWriter>,
pub api_sender: ApiSender,
pub api_shutdown_tx: ApiShutdownTx,
}
struct DisabledCpuEnergy;
impl CPUEnergy for DisabledCpuEnergy {
fn get_power(&self) -> f64 {
0.0
}
}
struct DisabledGpuEnergy;
impl GPUEnergy for DisabledGpuEnergy {
fn get_power(&self) -> f64 {
0.0
}
}
pub fn setup_joularcore(args: &Args) -> JoularContext {
let platform = platform::current(args.gui);
let rb = if args.ringbuffer {
match RingBufferWriter::new(true) {
Ok(writer) => Some(writer),
Err(e) => {
crate::logging::print_warning(&format!(
"Ring buffer unavailable: {}. Continuing without ring buffer output",
e
));
None
}
}
} else {
None
};
#[cfg(feature = "api")]
let (api_tx, _api_rx) = if args.api_port.is_some() {
let (tx, rx) = tokio::sync::broadcast::channel(16);
(Some(tx), Some(rx))
} else {
(None, None)
};
#[cfg(feature = "vm")]
let (use_vm_cpu, use_vm_gpu) = {
let vm_cpu_enabled = std::env::var("VM_CPU_POWER_FILE").is_ok();
let vm_gpu_enabled = std::env::var("VM_GPU_POWER_FILE").is_ok();
if vm_cpu_enabled && !args.numeric_only && !args.gui {
println!("\x1b[1;32m✓ VM CPU monitoring\x1b[0m");
}
if vm_gpu_enabled && !args.numeric_only && !args.gui {
println!("\x1b[1;32m✓ VM GPU monitoring\x1b[0m");
}
(vm_cpu_enabled, vm_gpu_enabled)
};
#[cfg(feature = "vm")]
let cpu_energy: Box<dyn CPUEnergy> = if matches!(args.component, Some(Component::Gpu)) {
Box::new(DisabledCpuEnergy)
} else if use_vm_cpu {
match vm::VmCpu::from_env() {
Ok(vm_cpu) => Box::new(vm_cpu),
Err(e) => {
crate::logging::print_warning(&format!(
"VM CPU monitoring failed ({}); falling back to platform CPU monitoring",
e
));
platform.cpu()
}
}
} else {
platform.cpu()
};
#[cfg(not(feature = "vm"))]
let cpu_energy: Box<dyn CPUEnergy> = if matches!(args.component, Some(Component::Gpu)) {
Box::new(DisabledCpuEnergy)
} else {
platform.cpu()
};
#[cfg(feature = "vm")]
let gpu_energy: Box<dyn GPUEnergy> = if matches!(args.component, Some(Component::Cpu)) {
Box::new(DisabledGpuEnergy)
} else if use_vm_gpu {
match vm::VmGpu::from_env() {
Ok(vm_gpu) => Box::new(vm_gpu),
Err(e) => {
crate::logging::print_warning(&format!(
"VM GPU monitoring failed ({}); falling back to platform GPU monitoring",
e
));
platform.gpu()
}
}
} else {
platform.gpu()
};
#[cfg(not(feature = "vm"))]
let gpu_energy: Box<dyn GPUEnergy> = if matches!(args.component, Some(Component::Cpu)) {
Box::new(DisabledGpuEnergy)
} else {
platform.gpu()
};
#[cfg(feature = "api")]
let (api_sender, api_shutdown_tx) =
if let (Some(port), Some(tx)) = (args.api_port, api_tx.clone()) {
match spawn_api_server(port, args.api_allowed_origins.clone(), tx) {
Ok(server) => server,
Err(e) => {
crate::logging::print_error(&format!(
"Failed to start API server on port {}: {}. Continuing without API output",
port, e
));
(None, None)
}
}
} else {
(None, None)
};
#[cfg(not(feature = "api"))]
let api_sender: Option<()> = None;
#[cfg(not(feature = "api"))]
let api_shutdown_tx: ApiShutdownTx = None;
JoularContext {
cpu_energy,
gpu_energy,
platform,
ringbuffer: rb,
api_sender,
api_shutdown_tx,
}
}
#[cfg(feature = "api")]
pub fn spawn_api_server(
port: u16,
allowed_origins: Vec<String>,
tx: tokio::sync::broadcast::Sender<api::ApiData>,
) -> Result<(ApiSender, ApiShutdownTx), String> {
let tx_clone = tx.clone();
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
let (startup_tx, startup_rx) = mpsc::channel();
thread::spawn(move || {
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(e) => {
let _ = startup_tx.send(Err(format!("failed to start Tokio runtime: {e}")));
return;
}
};
rt.block_on(async {
let listener = match tokio::net::TcpListener::bind(format!("127.0.0.1:{port}")).await {
Ok(listener) => listener,
Err(e) => {
let _ = startup_tx.send(Err(format!("failed to bind 127.0.0.1:{port}: {e}")));
return;
}
};
if startup_tx.send(Ok(())).is_err() {
return;
}
if let Err(e) =
api::start_api_server(port, allowed_origins, tx, shutdown_rx, listener).await
{
crate::logging::print_error(&format!("API server error on port {}: {}", port, e));
}
});
});
match startup_rx.recv() {
Ok(Ok(())) => Ok((Some(tx_clone), Some(shutdown_tx))),
Ok(Err(e)) => Err(e),
Err(e) => Err(format!(
"API server startup thread ended before reporting status: {e}"
)),
}
}