Skip to content

Commit

Permalink
Use libgeom to get disk I/O information on FreeBSD
Browse files Browse the repository at this point in the history
  • Loading branch information
GuillaumeGomez committed Nov 24, 2024
1 parent 3079bc5 commit a92ff11
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 109 deletions.
2 changes: 0 additions & 2 deletions src/common/disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,6 @@ impl Disk {

/// Returns number of bytes read and written by the disk
///
/// ⚠️ Note that FreeBSD is not yet supported
///
/// ```no_run
/// use sysinfo::Disks;
///
Expand Down
257 changes: 175 additions & 82 deletions src/unix/freebsd/disk.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
// Take a look at the license at the top of the repository in the LICENSE file.

use crate::{Disk, DiskKind, DiskUsage};

use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
use std::marker::PhantomData;
use std::os::unix::ffi::OsStringExt;
use std::path::{Path, PathBuf};
use std::ptr::null_mut;
use std::ptr::{NonNull, null_mut};
use std::sync::OnceLock;

use libc::c_void;

use crate::{Disk, DiskKind, DiskUsage};
use super::ffi::{
devinfo,
devstat,
devstat_compute_statistics,
devstat_getdevs,
devstat_getversion,
statinfo,
DSM_NONE,
DSM_TOTAL_BYTES_READ,
DSM_TOTAL_BYTES_WRITE,
geom_stats_open,
geom_stats_snapshot_get,
geom_stats_snapshot_next,
geom_stats_snapshot_reset,
geom_stats_snapshot_free,
DEVSTAT_READ,
DEVSTAT_WRITE,
};

use super::utils::{c_buf_to_utf8_str, c_buf_to_utf8_string, get_sys_value_str_by_name};
use super::utils::{c_buf_to_utf8_str, get_sys_value_str_by_name};

#[derive(Debug)]
pub(crate) struct DiskInner {
Expand Down Expand Up @@ -180,81 +182,77 @@ fn refresh_disk(disk: &mut DiskInner) -> bool {
}
}

struct DevInfoWrapper {
info: statinfo,
unsafe fn initialize_geom() -> Result<(), ()> {
let version = devstat_getversion(null_mut());
if version != 6 {
// For now we only handle the devstat 6 version.
sysinfo_debug!("version {version} of devstat is not supported");
return Err(());
}
let r = unsafe { geom_stats_open() };
if r != 0 {
sysinfo_debug!("`geom_stats_open` failed: {r}");
Err(())
} else {
Ok(())
}
}

impl DevInfoWrapper {
fn new() -> Self {
Self {
info: unsafe { std::mem::zeroed() },
}
}
unsafe fn refresh_disk_io<T: GetValues>(disks: &mut [T]) {
static GEOM_STATS: OnceLock<Result<(), ()>> = OnceLock::new();

unsafe fn get_devs(&mut self) -> Option<&statinfo> {
let version = devstat_getversion(null_mut());
if version != 6 {
// For now we only handle the devstat 6 version.
sysinfo_debug!("version {version} of devstat is not supported");
return None;
}
if self.info.dinfo.is_null() {
self.info.dinfo = libc::calloc(1, std::mem::size_of::<devinfo>()) as *mut _;
if self.info.dinfo.is_null() {
return None;
}
}
if devstat_getdevs(null_mut(), &mut self.info as *mut _) != -1 {
Some(&self.info)
} else {
None
}
if GEOM_STATS.get_or_init(initialize_geom).is_err() {
return;
}
}
let Some(mut snap) = GeomSnapshot::new() else { return };
for device in snap.iter() {
let device = device.devstat.as_ref();
let Some(device_name) = c_buf_to_utf8_str(&device.device_name) else { continue };
let dev_stat_name = format!("{device_name}{}", device.unit_number);

impl Drop for DevInfoWrapper {
fn drop(&mut self) {
if !self.info.dinfo.is_null() {
unsafe { libc::free(self.info.dinfo as *mut _); }
for disk in disks.iter_mut().filter(|d| d.dev_id().is_some_and(|id| *id == dev_stat_name)) {
disk.update_old();
*disk.get_read() = device.bytes[DEVSTAT_READ];
*disk.get_written() = device.bytes[DEVSTAT_WRITE];
}
}
}

unsafe fn refresh_disk_io<T: GetValues>(disks: &mut [T]) {
thread_local! {
static DEV_INFO: RefCell<DevInfoWrapper> = RefCell::new(DevInfoWrapper::new());
}

DEV_INFO.with_borrow_mut(|dev_info| {
let Some(stat_info) = dev_info.get_devs() else { return };
let dinfo = (*stat_info).dinfo;

let numdevs = (*dinfo).numdevs;
if numdevs < 0 {
return;
}
let devices: &mut [devstat] = std::slice::from_raw_parts_mut((*dinfo).devices, numdevs as _);
for device in devices {
let Some(device_name) = c_buf_to_utf8_str(&device.device_name) else { continue };
let dev_stat_name = format!("{device_name}{}", device.unit_number);

for disk in disks.iter_mut().filter(|d| d.dev_id().is_some_and(|id| *id == dev_stat_name)) {
disk.update_old();
let mut read = 0u64;
devstat_compute_statistics(
device,
null_mut(),
0,
DSM_TOTAL_BYTES_READ,
&mut read,
DSM_TOTAL_BYTES_WRITE,
disk.get_written(),
DSM_NONE,
);
*disk.get_read() = read;
}
}
});
// thread_local! {
// static DEV_INFO: RefCell<DevInfoWrapper> = RefCell::new(DevInfoWrapper::new());
// }

// DEV_INFO.with_borrow_mut(|dev_info| {
// let Some(stat_info) = dev_info.get_devs() else { return };
// let dinfo = (*stat_info).dinfo;

// let numdevs = (*dinfo).numdevs;
// if numdevs < 0 {
// return;
// }
// let devices: &mut [devstat] = std::slice::from_raw_parts_mut((*dinfo).devices, numdevs as _);
// for device in devices {
// let Some(device_name) = c_buf_to_utf8_str(&device.device_name) else { continue };
// let dev_stat_name = format!("{device_name}{}", device.unit_number);

// for disk in disks.iter_mut().filter(|d| d.dev_id().is_some_and(|id| *id == dev_stat_name)) {
// disk.update_old();
// let mut read = 0u64;
// // This code cannot work because `devstat_compute_statistics` expects a
// // `long double` as 3rd argument, making it impossible for rust to call it...
// devstat_compute_statistics(
// device,
// null_mut(),
// 0,
// DSM_TOTAL_BYTES_READ,
// &mut read,
// DSM_TOTAL_BYTES_WRITE,
// disk.get_written(),
// DSM_NONE,
// );
// *disk.get_read() = read;
// }
// }
// });
}

fn get_disks_mapping() -> HashMap<String, String> {
Expand All @@ -281,7 +279,7 @@ fn get_disks_mapping() -> HashMap<String, String> {
}
}
}
return disk_mapping;
disk_mapping
}

pub unsafe fn get_all_list(container: &mut Vec<Disk>, add_new_disks: bool) {
Expand Down Expand Up @@ -393,9 +391,104 @@ pub unsafe fn get_all_list(container: &mut Vec<Disk>, add_new_disks: bool) {
true
});
} else {
for c in container {
for c in container.iter_mut() {
c.inner.updated = false;
}
}
refresh_disk_io(container);
refresh_disk_io(container.as_mut_slice());
}

// struct DevInfoWrapper {
// info: statinfo,
// }

// impl DevInfoWrapper {
// fn new() -> Self {
// Self {
// info: unsafe { std::mem::zeroed() },
// }
// }

// unsafe fn get_devs(&mut self) -> Option<&statinfo> {
// let version = devstat_getversion(null_mut());
// if version != 6 {
// // For now we only handle the devstat 6 version.
// sysinfo_debug!("version {version} of devstat is not supported");
// return None;
// }
// if self.info.dinfo.is_null() {
// self.info.dinfo = libc::calloc(1, std::mem::size_of::<devinfo>()) as *mut _;
// if self.info.dinfo.is_null() {
// return None;
// }
// }
// if devstat_getdevs(null_mut(), &mut self.info as *mut _) != -1 {
// Some(&self.info)
// } else {
// None
// }
// }
// }

// impl Drop for DevInfoWrapper {
// fn drop(&mut self) {
// if !self.info.dinfo.is_null() {
// unsafe { libc::free(self.info.dinfo as *mut _); }
// }
// }
// }

// Most of this code was adapted from `gstat-rs` (https://github.com/asomers/gstat-rs).
struct GeomSnapshot(NonNull<c_void>);

impl GeomSnapshot {
unsafe fn new() -> Option<Self> {
match NonNull::new(geom_stats_snapshot_get()) {
Some(n) => Some(Self(n)),
None => {
sysinfo_debug!("geom_stats_snapshot_get failed");
None
}
}
}

fn iter(&mut self) -> GeomSnapshotIter {
GeomSnapshotIter(self)
}

fn reset(&mut self) {
unsafe { geom_stats_snapshot_reset(self.0.as_mut()) }
}
}

impl Drop for GeomSnapshot {
fn drop(&mut self) {
unsafe { geom_stats_snapshot_free(self.0.as_mut()) };
}
}

#[repr(transparent)]
struct Devstat<'a> {
devstat: NonNull<devstat>,
phantom: PhantomData<&'a devstat>,
}

struct GeomSnapshotIter<'a>(&'a mut GeomSnapshot);

impl<'a> Iterator for GeomSnapshotIter<'a> {
type Item = Devstat<'a>;

fn next(&mut self) -> Option<Self::Item> {
let raw = unsafe { geom_stats_snapshot_next(self.0.0.as_mut()) };
NonNull::new(raw).map(|devstat| Devstat {
devstat,
phantom: PhantomData,
})
}
}

impl Drop for GeomSnapshotIter<'_> {
fn drop(&mut self) {
self.0.reset();
}
}
57 changes: 32 additions & 25 deletions src/unix/freebsd/ffi.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
#![allow(non_camel_case_types)]
// Because of long double.
#![allow(improper_ctypes)]

use libc::{c_char, c_int, c_long, c_uint, c_void, bintime, kvm_t, CPUSTATES};
use libc::{c_char, c_int, c_uint, c_void, bintime, kvm_t};

// definitions come from:
// https://github.com/freebsd/freebsd-src/blob/main/lib/libdevstat/devstat.h
// https://github.com/freebsd/freebsd-src/blob/main/sys/sys/devicestat.h

// 16 bytes says `sizeof` in C so let's use a type of the same size.
pub type c_long_double = u128;
pub type devstat_priority = c_int;
pub type devstat_support_flags = c_int;
pub type devstat_type_flags = c_int;
Expand Down Expand Up @@ -44,32 +40,43 @@ pub(crate) struct devstat {
pub(crate) sequence1: c_uint,
}

#[repr(C)]
pub(crate) struct devinfo {
pub(crate) devices: *mut devstat,
pub(crate) mem_ptr: *mut u8,
pub(crate) generation: c_long,
pub(crate) numdevs: c_int,
}
// #[repr(C)]
// pub(crate) struct devinfo {
// pub(crate) devices: *mut devstat,
// pub(crate) mem_ptr: *mut u8,
// pub(crate) generation: c_long,
// pub(crate) numdevs: c_int,
// }

#[repr(C)]
pub(crate) struct statinfo {
pub(crate) cp_time: [c_long; CPUSTATES as usize],
pub(crate) tk_nin: c_long,
pub(crate) tk_nout: c_long,
pub(crate) dinfo: *mut devinfo,
pub(crate) snap_time: c_long_double,
}
// #[repr(C)]
// pub(crate) struct statinfo {
// pub(crate) cp_time: [c_long; CPUSTATES as usize],
// pub(crate) tk_nin: c_long,
// pub(crate) tk_nout: c_long,
// pub(crate) dinfo: *mut devinfo,
// pub(crate) snap_time: c_long_double,
// }

pub(crate) const DEVSTAT_N_TRANS_FLAGS: usize = 4;
pub(crate) const DEVSTAT_NAME_LEN: usize = 16;
pub(crate) const DEVSTAT_READ: usize = 0x01;
pub(crate) const DEVSTAT_WRITE: usize = 0x02;

pub(crate) const DSM_NONE: c_int = 0;
pub(crate) const DSM_TOTAL_BYTES_READ: c_int = 2;
pub(crate) const DSM_TOTAL_BYTES_WRITE: c_int = 3;
// pub(crate) const DSM_NONE: c_int = 0;
// pub(crate) const DSM_TOTAL_BYTES_READ: c_int = 2;
// pub(crate) const DSM_TOTAL_BYTES_WRITE: c_int = 3;

extern "C" {
pub(crate) fn devstat_getversion(kd: *mut kvm_t) -> c_int;
pub(crate) fn devstat_getdevs(kd: *mut kvm_t, stats: *mut statinfo) -> c_int;
pub(crate) fn devstat_compute_statistics(current: *mut devstat, previous: *mut devstat, etime: c_long_double, ...) -> c_int;
// pub(crate) fn devstat_getdevs(kd: *mut kvm_t, stats: *mut statinfo) -> c_int;
// pub(crate) fn devstat_compute_statistics(current: *mut devstat, previous: *mut devstat, etime: c_long_double, ...) -> c_int;
}

#[link(name = "geom")]
extern "C" {
pub(crate) fn geom_stats_open() -> c_int;
pub(crate) fn geom_stats_snapshot_get() -> *mut c_void;
pub(crate) fn geom_stats_snapshot_next(arg: *mut c_void) -> *mut devstat;
pub(crate) fn geom_stats_snapshot_reset(arg: *mut c_void);
pub(crate) fn geom_stats_snapshot_free(arg: *mut c_void);
}

0 comments on commit a92ff11

Please sign in to comment.