diff --git a/Cargo.toml b/Cargo.toml index 473037305..a682f23b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,9 @@ libc = "^0.2.112" [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] core-foundation-sys = "0.8" +[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] +procfs = "0.12" + [target.'cfg(all(target_os = "linux", not(target_os = "android")))'.dev-dependencies] tempfile = "3.2" diff --git a/src/apple/disk.rs b/src/apple/disk.rs index 4e5890004..61ad01fb6 100644 --- a/src/apple/disk.rs +++ b/src/apple/disk.rs @@ -1,7 +1,7 @@ // Take a look at the license at the top of the repository in the LICENSE file. use crate::utils::to_cpath; -use crate::{DiskExt, DiskType}; +use crate::{DiskExt, DiskType, DiskUsageExt}; #[cfg(target_os = "macos")] pub(crate) use crate::sys::inner::disk::*; @@ -51,6 +51,14 @@ impl DiskExt for Disk { self.is_removable } + fn usage(&self) -> DiskUsageExt { + todo!() + } + + fn refresh_usage(&mut self) -> bool { + todo!() + } + fn refresh(&mut self) -> bool { unsafe { let mut stat: statfs = mem::zeroed(); diff --git a/src/common.rs b/src/common.rs index 229229771..83d9f105b 100644 --- a/src/common.rs +++ b/src/common.rs @@ -688,6 +688,60 @@ pub struct DiskUsage { pub read_bytes: u64, } +/// Type containing read and written bytes plus read and written number of operations. +/// +/// It is returned by [`DiskExt::usage`][crate::DiskExt::usage]. +/// +/// ```no_run +/// use sysinfo::{DiskExt, System, SystemExt}; +/// +/// let mut s = System::new_all(); +/// s.refresh_disks_list(); +/// s.refresh_disks_usage(); +/// for disk in s.disks() { +/// let disk_usage = disk.usage(); +/// println!("[{:?}] read bytes : new/total => {}/{} B", +/// disk.name(), +/// disk_usage.read_bytes, +/// disk_usage.total_read_bytes, +/// ); +/// println!("[{:?}] written bytes: new/total => {}/{} B", +/// disk.name(), +/// disk_usage.written_bytes, +/// disk_usage.total_written_bytes, +/// ); +/// println!("[{:?}] read ops : new/total => {}/{}", +/// disk.name(), +/// disk_usage.read_ops, +/// disk_usage.total_read_ops, +/// ); +/// println!("[{:?}] written ops: new/total => {}/{}", +/// disk.name(), +/// disk_usage.written_ops, +/// disk_usage.total_written_ops, +/// ); +/// } +/// ``` +#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] +pub struct DiskUsageExt { + /// Total number of written bytes. + pub total_written_bytes: u64, + /// Number of written bytes since the last refresh. + pub written_bytes: u64, + /// Total number of read bytes. + pub total_read_bytes: u64, + /// Number of read bytes since the last refresh. + pub read_bytes: u64, + /// Total number of written ops. + pub total_written_ops: u64, + /// Number of written ops since the last refresh. + pub written_ops: u64, + /// Total number of read ops. + pub total_read_ops: u64, + /// Number of read ops since the last refresh. + pub read_ops: u64, +} + /// Enum describing the different status of a process. #[derive(Clone, Copy, Debug, PartialEq)] pub enum ProcessStatus { diff --git a/src/freebsd/disk.rs b/src/freebsd/disk.rs index 985209340..8364d7694 100644 --- a/src/freebsd/disk.rs +++ b/src/freebsd/disk.rs @@ -1,6 +1,6 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use crate::{DiskExt, DiskType}; +use crate::{DiskExt, DiskType, DiskUsageExt}; use std::ffi::{OsStr, OsString}; use std::path::{Path, PathBuf}; @@ -47,6 +47,14 @@ impl DiskExt for Disk { self.is_removable } + fn usage(&self) -> DiskUsageExt { + todo!() + } + + fn refresh_usage(&mut self) -> bool { + todo!() + } + fn refresh(&mut self) -> bool { unsafe { let mut vfs: libc::statvfs = std::mem::zeroed(); diff --git a/src/lib.rs b/src/lib.rs index c55d61b55..e46b888f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ cfg_if::cfg_if! { } pub use common::{ - get_current_pid, DiskType, DiskUsage, Gid, LoadAvg, NetworksIter, Pid, PidExt, + get_current_pid, DiskType, DiskUsage, DiskUsageExt, Gid, LoadAvg, NetworksIter, Pid, PidExt, ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, Uid, User, }; pub use sys::{Component, Disk, NetworkData, Networks, Process, Processor, System}; diff --git a/src/linux/disk.rs b/src/linux/disk.rs index 1fd1272f1..e1d19e18d 100644 --- a/src/linux/disk.rs +++ b/src/linux/disk.rs @@ -1,16 +1,19 @@ // Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::get_all_data; -use crate::{utils, DiskExt, DiskType}; +use crate::{utils, DiskExt, DiskType, DiskUsageExt}; use libc::statvfs; use std::ffi::{OsStr, OsString}; use std::fs; +use std::io::{BufRead, BufReader}; use std::mem; use std::num::Wrapping; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; +const SECTOR_SIZE: u64 = 512; + macro_rules! cast { ($x:expr) => { u64::from($x) @@ -21,12 +24,43 @@ macro_rules! cast { #[derive(PartialEq)] pub struct Disk { type_: DiskType, + device_name: OsString, + + #[doc(hidden)] + pub(crate) actual_device_name: String, + file_system: Vec, mount_point: PathBuf, total_space: u64, available_space: u64, is_removable: bool, + + old_read_bytes: u64, + old_written_bytes: u64, + read_bytes: u64, + written_bytes: u64, + + old_read_ops: u64, + old_written_ops: u64, + read_ops: u64, + written_ops: u64, +} + +impl Disk { + #[inline] + pub(crate) fn update_disk_stats(&mut self, stat: &procfs::DiskStat) { + self.old_read_bytes = self.read_bytes; + self.old_written_bytes = self.written_bytes; + self.old_read_ops = self.read_ops; + self.old_written_ops = self.written_ops; + + self.read_ops = stat.reads + stat.merged; + self.written_ops = stat.writes + stat.writes_merged; + + self.read_bytes = stat.sectors_read * SECTOR_SIZE; + self.written_bytes = stat.sectors_written * SECTOR_SIZE; + } } impl DiskExt for Disk { @@ -58,6 +92,47 @@ impl DiskExt for Disk { self.is_removable } + fn usage(&self) -> DiskUsageExt { + DiskUsageExt { + written_bytes: self.written_bytes - self.old_written_bytes, + total_written_bytes: self.written_bytes, + read_bytes: self.read_bytes - self.old_read_bytes, + total_read_bytes: self.read_bytes, + written_ops: self.written_ops - self.old_written_ops, + total_written_ops: self.written_ops, + read_ops: self.read_ops - self.old_read_ops, + total_read_ops: self.read_ops, + } + } + + fn refresh_usage(&mut self) -> bool { + #[inline] + fn refresh_usage(disk: &mut Disk) -> Result<(), std::io::Error> { + for line in BufReader::new(fs::File::open("/proc/diskstats")?).lines() { + let line = line?; + + let stat = match procfs::DiskStat::from_line(&line) { + Ok(stat) => stat, + Err(procfs::ProcError::Io(err, _)) => return Err(err), + Err(err) => return Err(std::io::Error::new(std::io::ErrorKind::Other, err)), + }; + + if stat.name != disk.actual_device_name { + continue; + } + + disk.update_disk_stats(&stat); + + return Ok(()); + } + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Device not found", + )) + } + refresh_usage(self).is_ok() + } + fn refresh(&mut self) -> bool { unsafe { let mut stat: statvfs = mem::zeroed(); @@ -73,6 +148,15 @@ impl DiskExt for Disk { } } +// FIXME: I think this may be completely incorrect, in many different ways. +fn find_device_name(device_name: &OsStr) -> String { + device_name + .to_string_lossy() + .strip_prefix("/dev/") + .map(|s| s.to_string()) + .unwrap_or_else(|| device_name.to_string_lossy().into_owned()) +} + fn new_disk( device_name: &OsStr, mount_point: &Path, @@ -102,12 +186,23 @@ fn new_disk( .any(|e| e.as_os_str() == device_name); Some(Disk { type_, + actual_device_name: find_device_name(device_name), device_name: device_name.to_owned(), file_system: file_system.to_owned(), mount_point, total_space: cast!(total), available_space: cast!(available), is_removable, + + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + + old_read_ops: 0, + old_written_ops: 0, + read_ops: 0, + written_ops: 0, }) } diff --git a/src/linux/system.rs b/src/linux/system.rs index fbd32b116..59264f240 100644 --- a/src/linux/system.rs +++ b/src/linux/system.rs @@ -529,6 +529,41 @@ impl SystemExt for System { &mut self.disks } + // We reimplement this for efficiency + // Refreshing each disk individually is an O(n ^ 2) operation, but this implementation is O(n) + fn refresh_disks_usage(&mut self) { + if self.disks.is_empty() { + return; + } + + #[inline] + fn refresh_usage(disk: &mut S) -> Result<(), std::io::Error> { + for line in BufReader::new(std::fs::File::open("/proc/diskstats")?).lines() { + let line = line?; + + let stat = match procfs::DiskStat::from_line(&line) { + Ok(stat) => stat, + Err(procfs::ProcError::Io(err, _)) => return Err(err), + Err(err) => return Err(std::io::Error::new(std::io::ErrorKind::Other, err)), + }; + + let disk = match disk + .disks_mut() + .iter_mut() + .find(|disk| disk.actual_device_name == stat.name) + { + Some(disk) => disk, + None => continue, + }; + + disk.update_disk_stats(&stat); + } + + Ok(()) + } + let _ = refresh_usage(self); + } + fn uptime(&self) -> u64 { let content = get_all_data("/proc/uptime", 50).unwrap_or_default(); content diff --git a/src/sysinfo.h b/src/sysinfo.h index 63fc56606..3ced67da4 100644 --- a/src/sysinfo.h +++ b/src/sysinfo.h @@ -18,7 +18,7 @@ void sysinfo_refresh_processes(CSystem system); void sysinfo_refresh_process(CSystem system, pid_t pid); #endif void sysinfo_refresh_disks(CSystem system); -void sysinfo_refresh_disk_list(CSystem system); +void sysinfo_refresh_disks_list(CSystem system); size_t sysinfo_get_total_memory(CSystem system); size_t sysinfo_get_free_memory(CSystem system); size_t sysinfo_get_used_memory(CSystem system); diff --git a/src/traits.rs b/src/traits.rs index d51bd50a0..c54d044f2 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -5,8 +5,8 @@ use crate::{ sys::{Component, Disk, Networks, Process, Processor}, }; use crate::{ - DiskType, DiskUsage, LoadAvg, NetworksIter, Pid, ProcessRefreshKind, ProcessStatus, - RefreshKind, Signal, User, + DiskType, DiskUsage, DiskUsageExt, LoadAvg, NetworksIter, Pid, ProcessRefreshKind, + ProcessStatus, RefreshKind, Signal, User, }; use std::collections::HashMap; @@ -109,6 +109,37 @@ pub trait DiskExt: Debug { /// ``` fn is_removable(&self) -> bool; + /// Returns number of bytes read and written to disk, system-wide. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// let disk_usage = disk.usage(); + /// println!("read bytes : new/total => {}/{}", + /// disk_usage.read_bytes, + /// disk_usage.total_read_bytes, + /// ); + /// println!("written bytes: new/total => {}/{}", + /// disk_usage.written_bytes, + /// disk_usage.total_written_bytes, + /// ); + /// } + fn usage(&self) -> DiskUsageExt; + + /// Updates the disk's I/O usage information. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// for disk in s.disks_mut() { + /// disk.refresh_usage(); + /// } + /// ``` + fn refresh_usage(&mut self) -> bool; + /// Updates the disk' information. /// /// ```no_run @@ -700,6 +731,20 @@ pub trait SystemExt: Sized + Debug + Default + Send + Sync { } } + /// Refreshes the listed disks I/O usage information. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_disks_usage(); + /// ``` + fn refresh_disks_usage(&mut self) { + for disk in self.disks_mut() { + disk.refresh_usage(); + } + } + /// The disk list will be emptied then completely recomputed. /// /// ```no_run diff --git a/src/unknown/disk.rs b/src/unknown/disk.rs index 8956da9f5..9deaf34c8 100644 --- a/src/unknown/disk.rs +++ b/src/unknown/disk.rs @@ -1,6 +1,6 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use crate::{DiskExt, DiskType}; +use crate::{DiskExt, DiskType, DiskUsageExt}; use std::{ffi::OsStr, path::Path}; @@ -36,6 +36,23 @@ impl DiskExt for Disk { false } + fn usage(&self) -> DiskUsageExt { + DiskUsageExt { + written_bytes: 0, + total_written_bytes: 0, + read_bytes: 0, + total_read_bytes: 0, + written_ops: 0, + total_written_ops: 0, + read_ops: 0, + total_read_ops: 0, + } + } + + fn refresh_usage(&mut self) -> bool { + true + } + fn refresh(&mut self) -> bool { true } diff --git a/src/windows/disk.rs b/src/windows/disk.rs index 3e8fdbe7f..65747766a 100644 --- a/src/windows/disk.rs +++ b/src/windows/disk.rs @@ -1,14 +1,24 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use crate::{DiskExt, DiskType}; +use crate::{DiskExt, DiskType, DiskUsageExt}; use std::ffi::{OsStr, OsString}; use std::path::Path; +use ntapi::ntrtl::RtlGetVersion; +use once_cell::sync::Lazy; +use winapi::shared::minwindef::FALSE; +use winapi::shared::ntdef::NT_SUCCESS; +use winapi::shared::winerror::ERROR_MORE_DATA; +use winapi::um::errhandlingapi::GetLastError; use winapi::um::fileapi::GetDiskFreeSpaceExW; -use winapi::um::winnt::ULARGE_INTEGER; +use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE}; +use winapi::um::ioapiset::DeviceIoControl; +use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS; +use winapi::um::winnt::{RTL_OSVERSIONINFOEXW, ULARGE_INTEGER}; pub(crate) fn new_disk( + disk_idx: u16, name: &OsStr, mount_point: &[u16], file_system: &[u8], @@ -20,6 +30,7 @@ pub(crate) fn new_disk( return None; } let mut d = Disk { + disk_idx, type_, name: name.to_owned(), file_system: file_system.to_vec(), @@ -28,6 +39,15 @@ pub(crate) fn new_disk( total_space, available_space: 0, is_removable, + + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + old_read_ops: 0, + old_written_ops: 0, + read_ops: 0, + written_ops: 0, }; d.refresh(); Some(d) @@ -35,6 +55,8 @@ pub(crate) fn new_disk( #[doc = include_str!("../../md_doc/disk.md")] pub struct Disk { + disk_idx: u16, + type_: DiskType, name: OsString, file_system: Vec, @@ -43,8 +65,33 @@ pub struct Disk { total_space: u64, available_space: u64, is_removable: bool, + + old_read_bytes: u64, + old_written_bytes: u64, + read_bytes: u64, + written_bytes: u64, + + old_read_ops: u64, + old_written_ops: u64, + read_ops: u64, + written_ops: u64, } +impl Disk { + // Yes, seriously, for whatever reason, the underlying APIs for getting I/O statistics requires the leading backslash + fn stats_id(&self) -> [u16; 8] { + [ + b'\\' as u16, + b'\\' as u16, + b'.' as u16, + b'\\' as u16, + b'A' as u16 + self.disk_idx as u16, + b':' as u16, + b'\\' as u16, + 0, + ] + } +} impl DiskExt for Disk { fn type_(&self) -> DiskType { self.type_ @@ -74,6 +121,148 @@ impl DiskExt for Disk { self.is_removable } + fn usage(&self) -> DiskUsageExt { + DiskUsageExt { + written_bytes: self.written_bytes - self.old_written_bytes, + total_written_bytes: self.written_bytes, + read_bytes: self.read_bytes - self.old_read_bytes, + total_read_bytes: self.read_bytes, + written_ops: self.written_ops - self.old_written_ops, + total_written_ops: self.written_ops, + read_ops: self.read_ops - self.old_read_ops, + total_read_ops: self.read_ops, + } + } + + fn refresh_usage(&mut self) -> bool { + // https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Ioctl/constant.FSCTL_FILESYSTEM_GET_STATISTICS_EX.html + const FSCTL_FILESYSTEM_GET_STATISTICS_EX: u32 = 590732u32; + + // https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Ioctl/type.FILESYSTEM_STATISTICS_TYPE.html + type FileSystemStatisticsType = u16; + + // https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Ioctl/struct.FILESYSTEM_STATISTICS.html + #[repr(C)] + #[derive(Debug)] + #[allow(non_snake_case)] + struct FILESYSTEM_STATISTICS { + FileSystemType: FileSystemStatisticsType, + Version: u16, + SizeOfCompleteStructure: u32, + UserFileReads: u32, + UserFileReadBytes: u32, + UserDiskReads: u32, + UserFileWrites: u32, + UserFileWriteBytes: u32, + UserDiskWrites: u32, + MetaDataReads: u32, + MetaDataReadBytes: u32, + MetaDataDiskReads: u32, + MetaDataWrites: u32, + MetaDataWriteBytes: u32, + MetaDataDiskWrites: u32, + } + + // https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Ioctl/struct.FILESYSTEM_STATISTICS_EX.html + #[repr(C)] + #[derive(Debug)] + #[allow(non_snake_case)] + struct FILESYSTEM_STATISTICS_EX { + FileSystemType: FileSystemStatisticsType, + Version: u16, + SizeOfCompleteStructure: u32, + UserFileReads: u64, + UserFileReadBytes: u64, + UserDiskReads: u64, + UserFileWrites: u64, + UserFileWriteBytes: u64, + UserDiskWrites: u64, + MetaDataReads: u64, + MetaDataReadBytes: u64, + MetaDataDiskReads: u64, + MetaDataWrites: u64, + MetaDataWriteBytes: u64, + MetaDataDiskWrites: u64, + } + + static WINDOWS_10_OR_NEWER: Lazy = Lazy::new(|| { + let mut version_info: RTL_OSVERSIONINFOEXW = unsafe { std::mem::zeroed() }; + + version_info.dwOSVersionInfoSize = std::mem::size_of::() as u32; + if !NT_SUCCESS(unsafe { + RtlGetVersion(&mut version_info as *mut RTL_OSVERSIONINFOEXW as *mut _) + }) { + return true; + } + + version_info.dwMajorVersion >= 10 + }); + + macro_rules! update_stats { + ($buffer:ident) => { + self.old_written_bytes = self.written_bytes; + self.old_read_bytes = self.read_bytes; + self.old_written_ops = self.written_ops; + self.old_read_ops = self.read_ops; + self.written_bytes = + ($buffer.UserFileWriteBytes + $buffer.MetaDataWriteBytes) as u64; + self.read_bytes = ($buffer.UserFileReadBytes + $buffer.MetaDataReadBytes) as u64; + self.written_ops = ($buffer.UserFileWrites + $buffer.MetaDataWrites) as u64; + self.read_ops = ($buffer.UserFileReads + $buffer.MetaDataReads) as u64; + }; + } + + unsafe { + let handle = super::tools::open_drive(&self.stats_id(), 0, FILE_FLAG_BACKUP_SEMANTICS); + if handle == INVALID_HANDLE_VALUE { + CloseHandle(handle); + return false; + } + + if *WINDOWS_10_OR_NEWER { + let mut buffer: FILESYSTEM_STATISTICS_EX = std::mem::zeroed(); + if DeviceIoControl( + handle, + FSCTL_FILESYSTEM_GET_STATISTICS_EX, + std::ptr::null_mut(), + 0, + &mut buffer as *mut _ as *mut _, + std::mem::size_of::() as _, + &mut 0, + std::ptr::null_mut(), + ) == FALSE + { + // Many drivers/filesystems will return a bit more data, but we can safely ignore it + if GetLastError() != ERROR_MORE_DATA { + return false; + } + } + update_stats!(buffer); + } else { + let mut buffer: FILESYSTEM_STATISTICS = std::mem::zeroed(); + if DeviceIoControl( + handle, + winapi::um::winioctl::FSCTL_FILESYSTEM_GET_STATISTICS, + std::ptr::null_mut(), + 0, + &mut buffer as *mut _ as *mut _, + std::mem::size_of::() as _, + &mut 0, + std::ptr::null_mut(), + ) == FALSE + { + // Many drivers/filesystems will return a bit more data, but we can safely ignore it + if GetLastError() != ERROR_MORE_DATA { + return false; + } + } + update_stats!(buffer); + } + } + + true + } + fn refresh(&mut self) -> bool { if self.total_space != 0 { unsafe { @@ -90,6 +279,7 @@ impl DiskExt for Disk { } } } + false } } diff --git a/src/windows/tools.rs b/src/windows/tools.rs index 9b9428e82..0b5cbb34f 100644 --- a/src/windows/tools.rs +++ b/src/windows/tools.rs @@ -54,14 +54,14 @@ pub(crate) fn init_processors() -> (Vec, String, String) { } } -pub unsafe fn open_drive(drive_name: &[u16], open_rights: DWORD) -> HANDLE { +pub unsafe fn open_drive(drive_name: &[u16], open_rights: DWORD, flags: DWORD) -> HANDLE { CreateFileW( drive_name.as_ptr(), open_rights, FILE_SHARE_READ | FILE_SHARE_WRITE, std::ptr::null_mut(), OPEN_EXISTING, - 0, + flags, std::ptr::null_mut(), ) } @@ -152,10 +152,12 @@ pub unsafe fn get_disks() -> Vec { b':' as u16, 0, ]; - let handle = open_drive(&drive_name, 0); + + let handle = open_drive(&drive_name, 0, 0); if handle == INVALID_HANDLE_VALUE { CloseHandle(handle); return new_disk( + x as _, name, &mount_point, &file_system, @@ -191,6 +193,7 @@ pub unsafe fn get_disks() -> Vec { { CloseHandle(handle); return new_disk( + x as _, name, &mount_point, &file_system, @@ -202,6 +205,7 @@ pub unsafe fn get_disks() -> Vec { let is_ssd = dtd.TrimEnabled != 0; CloseHandle(handle); new_disk( + x as _, name, &mount_point, &file_system, diff --git a/tests/disk_list.rs b/tests/disk_list.rs deleted file mode 100644 index 164d5b931..000000000 --- a/tests/disk_list.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Take a look at the license at the top of the repository in the LICENSE file. - -#[test] -fn test_disks() { - use sysinfo::SystemExt; - - if sysinfo::System::IS_SUPPORTED { - let s = sysinfo::System::new_all(); - // If we don't have any physical core present, it's very likely that we're inside a VM... - if s.physical_core_count().unwrap_or_default() > 0 { - assert!(!s.disks().is_empty()); - } - } -} diff --git a/tests/disks.rs b/tests/disks.rs new file mode 100644 index 000000000..d32213e6c --- /dev/null +++ b/tests/disks.rs @@ -0,0 +1,70 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#[test] +fn test_disks() { + use sysinfo::SystemExt; + + if sysinfo::System::IS_SUPPORTED { + let s = sysinfo::System::new_all(); + // If we don't have any physical core present, it's very likely that we're inside a VM... + if s.physical_core_count().unwrap_or_default() > 0 { + assert!(!s.disks().is_empty()); + } + } +} + +#[test] +fn test_system_disk_usage() { + use std::fs; + use std::fs::File; + use std::io::prelude::*; + use sysinfo::{DiskExt, SystemExt}; + + if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { + return; + } + if std::env::var("FREEBSD_CI").is_ok() { + // For an unknown reason, when running this test on Cirrus CI, it fails. It works perfectly + // locally though... Dark magic... + return; + } + + let mut system = sysinfo::System::new(); + system.refresh_disks_list(); + + if system.disks().is_empty() { + return; + } + + system.refresh_disks_usage(); + system.refresh_disks_usage(); + + { + let mut file = File::create("test.txt").expect("failed to create file"); + file.write_all(b"This is a test file\nwith test data.\n") + .expect("failed to write to file"); + } + fs::remove_file("test.txt").expect("failed to remove file"); + // Waiting a bit just in case... + std::thread::sleep(std::time::Duration::from_millis(5000)); + + system.refresh_disks_usage(); + + let mut total_written_since = 0; + let mut total_written = 0; + for disk in system.disks() { + let usage = disk.usage(); + total_written_since += usage.written_bytes; + total_written += usage.total_written_bytes; + } + assert!( + total_written > 0, + "found {} total written bytes...", + total_written + ); + assert!( + total_written_since > 0, + "found {} written bytes...", + total_written_since + ); +}