#[cfg(target_family = "unix")]
use {
memmap2::MmapMut,
std::fs::OpenOptions,
std::io,
std::mem::size_of,
std::os::unix::fs::{MetadataExt, OpenOptionsExt, PermissionsExt},
std::path::Path,
std::sync::{Arc, Mutex},
};
#[cfg(target_os = "windows")]
use {
std::io,
std::mem::size_of,
windows::Win32::Foundation::INVALID_HANDLE_VALUE,
windows::Win32::System::Memory::{
CreateFileMappingW, FILE_MAP_ALL_ACCESS, MapViewOfFile, PAGE_READWRITE,
},
windows::core::PCWSTR,
};
const BUFFER_SIZE: usize = 5;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "api", derive(serde::Serialize))]
pub struct RingBufferStruct {
pub timestamp: u64,
pub cpu_power: f64,
pub gpu_power: f64,
pub total_power: f64,
pub cpu_usage: f64,
pub pid_app_power: f64,
}
impl From<&crate::monitor::MonitorSample> for RingBufferStruct {
fn from(sample: &crate::monitor::MonitorSample) -> Self {
Self {
timestamp: sample.timestamp,
cpu_power: sample.cpu_power,
gpu_power: sample.gpu_power,
total_power: sample.total_power,
cpu_usage: sample.cpu_usage,
pid_app_power: sample.pid_app_power(),
}
}
}
pub struct RingBufferWriter {
enabled: bool,
#[cfg(target_family = "unix")]
mmap: Option<Arc<Mutex<MmapMut>>>,
#[cfg(target_os = "windows")]
ptr: *mut u8,
#[cfg(target_os = "windows")]
handle: Option<windows::Win32::Foundation::HANDLE>,
}
impl RingBufferWriter {
pub fn shared_path() -> &'static str {
if cfg!(target_os = "macos") {
"/tmp/joularcorering"
} else if cfg!(target_os = "windows") {
"Local\\JoularCoreRing"
} else {
"/dev/shm/joularcorering"
}
}
pub fn new(enabled: bool) -> io::Result<Self> {
if !enabled {
return Ok(Self {
enabled: false,
#[cfg(target_family = "unix")]
mmap: None,
#[cfg(target_os = "windows")]
ptr: std::ptr::null_mut(),
#[cfg(target_os = "windows")]
handle: None,
});
}
#[cfg(target_family = "unix")]
{
let path = if cfg!(target_os = "macos") {
"/tmp/joularcorering"
} else {
"/dev/shm/joularcorering"
};
let entry_size = size_of::<RingBufferStruct>();
let file_size = 8 + BUFFER_SIZE * entry_size;
if let Ok(meta) = std::fs::metadata(path) {
let our_uid = unsafe { libc::geteuid() };
if meta.uid() != our_uid {
return Err(io::Error::other(format!(
"Ring buffer file {path} is owned by another user; \
remove it or run as the owner"
)));
}
}
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.mode(0o600)
.open(Path::new(path))
.or_else(|_| {
OpenOptions::new()
.read(true)
.write(true)
.open(Path::new(path))
})
.map_err(|e| io::Error::other(format!("Failed to open shared memory file: {e}")))?;
let _ = file.set_permissions(std::fs::Permissions::from_mode(0o600));
file.set_len(file_size as u64)?;
let mmap = unsafe {
MmapMut::map_mut(&file)
.map_err(|e| io::Error::other(format!("Failed to mmap shared memory: {e}")))?
};
let mmap = Arc::new(Mutex::new(mmap));
Ok(Self {
enabled,
mmap: Some(mmap),
})
}
#[cfg(target_os = "windows")]
{
let entry_size = size_of::<RingBufferStruct>();
let file_size = 8 + BUFFER_SIZE * entry_size;
let local_name: Vec<u16> = "Local\\JoularCoreRing"
.encode_utf16()
.chain(std::iter::once(0))
.collect();
unsafe {
let mapping = match CreateFileMappingW(
INVALID_HANDLE_VALUE,
None,
PAGE_READWRITE,
0,
file_size as u32,
PCWSTR::from_raw(local_name.as_ptr()),
) {
Ok(h) => h,
Err(e) => {
return Err(io::Error::other(format!("CreateFileMappingW failed: {e}")));
}
};
let result_ptr = MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if result_ptr.Value.is_null() {
let _ = windows::Win32::Foundation::CloseHandle(mapping);
return Err(io::Error::other("MapViewOfFile failed"));
}
let ringbuffer_ptr = result_ptr.Value as *mut u8;
Ok(Self {
enabled: true,
ptr: ringbuffer_ptr,
handle: Some(mapping),
})
}
}
}
}
impl Drop for RingBufferWriter {
#[allow(clippy::needless_return)]
fn drop(&mut self) {
if !self.enabled {
return;
}
#[cfg(target_os = "windows")]
unsafe {
if !self.ptr.is_null() {
let _ = windows::Win32::System::Memory::UnmapViewOfFile(
windows::Win32::System::Memory::MEMORY_MAPPED_VIEW_ADDRESS {
Value: self.ptr as *mut _,
},
);
}
if let Some(handle) = self.handle {
let _ = windows::Win32::Foundation::CloseHandle(handle);
}
}
}
}
impl RingBufferWriter {
pub fn write(&self, ringbufferstruct: RingBufferStruct) {
if !self.enabled {
return;
}
#[cfg(target_family = "unix")]
if let Some(ref mmap_mutex) = self.mmap {
let mut mmap = mmap_mutex.lock().unwrap_or_else(|e| e.into_inner());
let entry_size = size_of::<RingBufferStruct>();
let required = 8 + BUFFER_SIZE * entry_size;
if mmap.len() < required {
crate::logging::print_error(
"Ring buffer mmap too small; skipping write (buffer may be corrupted)",
);
return;
}
let head_bytes: [u8; 8] = match mmap[0..8].try_into() {
Ok(b) => b,
Err(_) => return,
};
let head = u64::from_ne_bytes(head_bytes);
let idx = (head as usize) % BUFFER_SIZE;
let offset = 8 + idx * entry_size;
let bytes: &[u8] = unsafe {
std::slice::from_raw_parts(
&ringbufferstruct as *const RingBufferStruct as *const u8,
entry_size,
)
};
mmap[offset..offset + bytes.len()].copy_from_slice(bytes);
mmap[0..8].copy_from_slice(&head.wrapping_add(1).to_ne_bytes());
}
#[cfg(target_os = "windows")]
unsafe {
let head_ptr = self.ptr as *mut u64;
let head = *head_ptr;
let idx = (head as usize) % BUFFER_SIZE;
let offset = 8 + idx * std::mem::size_of::<RingBufferStruct>();
let ringbufferstruct_ptr = self.ptr.add(offset) as *mut RingBufferStruct;
*ringbufferstruct_ptr = ringbufferstruct;
*head_ptr = head + 1;
}
}
}