-
-
Notifications
You must be signed in to change notification settings - Fork 312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add disk I/O stats #1376
Add disk I/O stats #1376
Conversation
5fa5dc0
to
0876661
Compare
d97c515
to
d3b8b0c
Compare
src/unix/apple/macos/disk.rs
Outdated
// Many external drive vendors do not advertise their device's storage medium. | ||
// | ||
// In these cases, assuming that there were _any_ properties about them registered, we fallback | ||
// to `HDD` when no storage medium is provided by the device instead of `Unknown`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not Unknown
? USB storage and other equivalents are not HDD after all, so I think Unknown
would fit better in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was existing code that was just moved due to refactoring; I was hoping git blame -C
would preserve the original author, but it doesn't seem to be doing so.
I do agree Unknown
makes the most sense here and can make the change!
@@ -58,6 +58,11 @@ impl DiskInner { | |||
refresh_disk(self, &mut vfs) | |||
} | |||
} | |||
|
|||
pub(crate) fn usage(&self) -> DiskUsage { | |||
// TODO: Until disk i/o stats are added, return the default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I'll take a look at adding support for it once this PR is merged.
src/unknown/disk.rs
Outdated
@@ -42,6 +42,10 @@ impl DiskInner { | |||
pub(crate) fn refresh(&mut self) -> bool { | |||
true | |||
} | |||
|
|||
pub(crate) fn usage(&self) -> DiskUsage { | |||
unreachable!() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer if it returned an empty DiskUsage
if you don't mind. Like that we can eventually instantiate a Disk
on all platforms and use its methods without worrying about whether or not it might panic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I will update that along with the kind
and name
functions to not panic
src/windows/disk.rs
Outdated
unsafe { | ||
// See <https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-ioctl_disk_performance> for reference | ||
DeviceIoControl( | ||
handle.0, | ||
IOCTL_DISK_PERFORMANCE, | ||
None, // Must be None as per docs | ||
0, | ||
Some(&mut disk_perf as *mut _ as _), | ||
size_of::<DISK_PERFORMANCE>() as u32, | ||
Some(&mut bytes_returned), | ||
None, | ||
) | ||
} | ||
.map_err(|err| { | ||
sysinfo_debug!("Error: DeviceIoControl(IOCTL_DISK_PERFORMANCE) = {:?}", err); | ||
err | ||
}) | ||
.ok() | ||
.and_then(|_| { | ||
disk_perf | ||
.BytesRead | ||
.try_into() | ||
.ok() | ||
.zip(disk_perf.BytesWritten.try_into().ok()) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems a bit more complicated than needed. What do you think about:
unsafe { | |
// See <https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-ioctl_disk_performance> for reference | |
DeviceIoControl( | |
handle.0, | |
IOCTL_DISK_PERFORMANCE, | |
None, // Must be None as per docs | |
0, | |
Some(&mut disk_perf as *mut _ as _), | |
size_of::<DISK_PERFORMANCE>() as u32, | |
Some(&mut bytes_returned), | |
None, | |
) | |
} | |
.map_err(|err| { | |
sysinfo_debug!("Error: DeviceIoControl(IOCTL_DISK_PERFORMANCE) = {:?}", err); | |
err | |
}) | |
.ok() | |
.and_then(|_| { | |
disk_perf | |
.BytesRead | |
.try_into() | |
.ok() | |
.zip(disk_perf.BytesWritten.try_into().ok()) | |
}) | |
unsafe { | |
// See <https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-ioctl_disk_performance> for reference | |
DeviceIoControl( | |
handle.0, | |
IOCTL_DISK_PERFORMANCE, | |
None, // Must be None as per docs | |
0, | |
Some(&mut disk_perf as *mut _ as _), | |
size_of::<DISK_PERFORMANCE>() as u32, | |
Some(&mut bytes_returned), | |
None, | |
) | |
} | |
.map_err(|err| { | |
sysinfo_debug!("Error: DeviceIoControl(IOCTL_DISK_PERFORMANCE) = {:?}", err); | |
err | |
}) | |
.ok()?; | |
Some((disk_perf.BytesRead.try_into().ok()?, disk_perf.BytesWritten.try_into().ok()?)) |
Looks very promising! A few things to change and then I'll merge. |
Thanks! Any idea when the next release (after this change is merged) might be? We are looking to make use of the disk I/O functionality in the next week or so, and it would be great to have this available. |
src/unix/apple/macos/disk.rs
Outdated
let disk_type = unsafe { | ||
super::disk::get_str_value( | ||
properties.inner(), | ||
DictKey::Defined(ffi::kIOPropertyMediumTypeKey), | ||
) | ||
}; | ||
|
||
if let Some(disk_type) = disk_type.and_then(|medium| match medium.as_str() { | ||
_ if medium == ffi::kIOPropertyMediumTypeSolidStateKey => Some(DiskKind::SSD), | ||
_ if medium == ffi::kIOPropertyMediumTypeRotationalKey => Some(DiskKind::HDD), | ||
_ => None, | ||
}) { | ||
Some(disk_type) | ||
} else { | ||
Some(DiskKind::Unknown(-1)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about:
let disk_type = unsafe { | |
super::disk::get_str_value( | |
properties.inner(), | |
DictKey::Defined(ffi::kIOPropertyMediumTypeKey), | |
) | |
}; | |
if let Some(disk_type) = disk_type.and_then(|medium| match medium.as_str() { | |
_ if medium == ffi::kIOPropertyMediumTypeSolidStateKey => Some(DiskKind::SSD), | |
_ if medium == ffi::kIOPropertyMediumTypeRotationalKey => Some(DiskKind::HDD), | |
_ => None, | |
}) { | |
Some(disk_type) | |
} else { | |
Some(DiskKind::Unknown(-1)) | |
} | |
let disk_type = unsafe { | |
super::disk::get_str_value( | |
properties.inner(), | |
DictKey::Defined(ffi::kIOPropertyMediumTypeKey), | |
) | |
}?; | |
match medium.as_str() { | |
_ if medium == ffi::kIOPropertyMediumTypeSolidStateKey => Some(DiskKind::SSD), | |
_ if medium == ffi::kIOPropertyMediumTypeRotationalKey => Some(DiskKind::HDD), | |
_ => Some(DiskKind::Unknown(-1)), | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much easier to reason with, thanks. I will update!
src/unix/apple/macos/disk.rs
Outdated
super::disk::get_int_value( | ||
properties.inner(), | ||
DictKey::Defined(ffi::kIOBlockStorageDriverStatisticsBytesReadKey), | ||
) | ||
.zip(super::disk::get_int_value( | ||
properties.inner(), | ||
DictKey::Defined(ffi::kIOBlockStorageDriverStatisticsBytesWrittenKey), | ||
)) | ||
} | ||
.and_then(|(read_bytes, written_bytes)| { | ||
read_bytes | ||
.try_into() | ||
.ok() | ||
.zip(written_bytes.try_into().ok()) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
super::disk::get_int_value( | |
properties.inner(), | |
DictKey::Defined(ffi::kIOBlockStorageDriverStatisticsBytesReadKey), | |
) | |
.zip(super::disk::get_int_value( | |
properties.inner(), | |
DictKey::Defined(ffi::kIOBlockStorageDriverStatisticsBytesWrittenKey), | |
)) | |
} | |
.and_then(|(read_bytes, written_bytes)| { | |
read_bytes | |
.try_into() | |
.ok() | |
.zip(written_bytes.try_into().ok()) | |
}) | |
let read_bytes = super::disk::get_int_value( | |
properties.inner(), | |
DictKey::Defined(ffi::kIOBlockStorageDriverStatisticsBytesReadKey), | |
)?; | |
let written_bytes = super::disk::get_int_value( | |
properties.inner(), | |
DictKey::Defined(ffi::kIOBlockStorageDriverStatisticsBytesWrittenKey), | |
)?; | |
Some((read_bytes.try_into().ok()?, written_bytes.try_into().ok()?)) |
/// * <https://github.com/torvalds/linux/blob/4f671fe2f9523a1ea206f63fe60a7c7b3a56d5c7/include/linux/bio.h#L99> | ||
/// * <https://lkml.org/lkml/2015/8/17/234> | ||
/// | ||
/// [`psutil`]: <https://github.com/giampaolo/psutil/blob/master/psutil/_pslinux.py#L103> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explanation is VERY appreciated, thanks a lot for that!
One thing missing: a test. For this test, please just check before/after creating a temporary file (with the |
Also I see you used "we". If it's part of your work in a company, please don't forget to try to make them sponsor this crate as it might allow me to free more time to work on it. :) |
faa8db8
to
c45758c
Compare
I have added a test, but it always seems to fail in CI on Ubuntu. Here is one of the test runs on a Any idea what might be going on here? |
|
||
let mut disks = sysinfo::Disks::new_with_refreshed_list(); | ||
|
||
let mut file = NamedTempFile::new().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this test is failing because temp files on linux are actually not written on disk. Maybe try with a temp folder within which you add a file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It works fine locally on my Ubuntu 22.04 machine, but I did try out your suggestion and unfortunately, still the same result. I am wondering if this is some limitation with how the Ubuntu CI runner is set up (by Github)...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use the trick used above:
// 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 {
Like that, the test will only run locally. I'll try to figure out later on what's wrong.
tests/disk.rs
Outdated
// Write 10mb worth of data to the temp file. Repeat this 10 times to increase the chances | ||
// of the OS registering the disk writes. | ||
for _ in 0..10 { | ||
let data = vec![1u8; 10 * 1024 * 1024]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be moved outside of the loop I think, no need to reallocate 10MB of memory at every turn.
tests/disk.rs
Outdated
|
||
// Write 10mb worth of data to the temp file. Repeat this 10 times to increase the chances | ||
// of the OS registering the disk writes. | ||
for _ in 0..10 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
100MB seems like a lot no? ^^'
Maybe try 1MB 10 times?
ebc5d3b
to
7a7d204
Compare
|
||
let s = sysinfo::System::new_all(); | ||
|
||
if !sysinfo::IS_SUPPORTED_SYSTEM || s.physical_core_count().unwrap_or_default() == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like it's not enough...
Just had an idea though. Can you create file in Path::new(std::env::var_os("CARGO_TARGET_DIR").expect("missing CARGO_TARGET_DIR env var")).join("disk_usage_test.tmp")
and write into it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, seems like the CI runner has 2 cores.
I tried it with my test branch and CARGO_TARGET_DIR doesn't seem to be defined for some odd reason: https://github.com/samin-cf/sysinfo/actions/runs/11672513236/job/32501288423
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try CARGO_TARGET_TMPDIR
as fallback?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird. No CARGO
environment variables are defined: https://github.com/samin-cf/sysinfo/actions/runs/11672628189/job/32501653467
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few, but not the ones we're looking for. Let's try at compile time with env!
then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CARGO_TARGET_DIR
isn't defined during compile time, but CARGO_TARGET_TMPDIR
is. Still no luck: https://github.com/samin-cf/sysinfo/actions/runs/11672913856/job/32502524730
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't expect it to be such a nightmare. ^^'
Then just put the file in target/test_file.tmp
and let's hope for the best this time...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😢 https://github.com/samin-cf/sysinfo/actions/runs/11673224654/job/32503482984#step:4:124
With the build just before the one I linked, the file was created in was target/tmp
and that also failed.
I am finding it surprising no one has run into this. Either that or something is up with the implementation (but it works locally 🙃)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the best course of action here is disabling in CI. Since we saw what env variables were set, we can disable it on linux if we're in github CI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the help along the way! Quite the journey lol
tests/disk.rs
Outdated
if std::env::var("RUNNER_OS").unwrap_or_default() == "Linux" | ||
&& std::env::var("CI").unwrap_or_default() == "true" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if std::env::var("RUNNER_OS").unwrap_or_default() == "Linux" | |
&& std::env::var("CI").unwrap_or_default() == "true" | |
if cfg!(target_os = "linux") && std::env::var("CI").is_ok() |
3605000
to
c90c5a2
Compare
Thanks a lot! |
This PR adds support to retrieve
DiskUsage
for any givenDisk
on Windows, Linux, and macOS. The implementation was manually tested on all three platforms by verifying the collected values against:psutil
iostat
on LinuxPart of #304