fix: false hdd detection on virtual devices#352
fix: false hdd detection on virtual devices#352KSXGitHub wants to merge 3 commits intoKSXGitHub:masterfrom
Conversation
The sysinfo crate reads /sys/block/*/queue/rotational to determine disk type, but virtual block devices (VirtIO, Xen, VMware PVSCSI, Hyper-V) default to rotational=1 because the kernel doesn't know the backing storage type. This caused pdu to falsely detect virtual disks as HDDs and unnecessarily limit threads to 1. The fix adds a secondary check on Linux: when sysinfo reports HDD, we read the block device's driver via /sys/block/<dev>/device/driver and reclassify known virtual drivers (virtio_blk, xen_blkfront, vmw_pvscsi, hyperv_storvsc) as Unknown instead of HDD. https://claude.ai/code/session_01MHKaZm6DcA9kjBPnW3KN5K
Apply rustfmt formatting and replace map_or with is_some_and per clippy. https://claude.ai/code/session_01MHKaZm6DcA9kjBPnW3KN5K
There was a problem hiding this comment.
Pull request overview
This PR adjusts HDD detection on Linux to avoid misclassifying virtual block devices as HDDs when sysinfo reports DiskKind::HDD due to the kernel’s rotational=1 default on some virtual devices.
Changes:
- Intercept
DiskKind::HDDon Linux and reclassify known virtual block-device drivers toDiskKind::Unknown(-1). - Add Linux-only helpers to extract a base block device name and check the sysfs driver symlink.
- Add Linux-specific tests for device-name extraction and virtual-driver detection.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/app/hdd.rs | Adds Linux-only correction path for HDD detection via /sys/block/<dev>/device/driver, plus device-name extraction. |
| src/app/hdd/test.rs | Adds Linux-only tests around extraction and virtual device detection (currently environment-dependent). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }; | ||
|
|
||
| let driver_name = target | ||
| .file_name() |
There was a problem hiding this comment.
is_virtual_block_device matches the Hyper-V storage driver name as hyperv_storvsc, but the in-kernel driver symlink name is typically hv_storvsc/storvsc. As written, Hyper-V virtual disks are likely to remain misclassified. Consider matching the actual driver names (and/or checking for a substring/prefix) so this works on Hyper-V hosts.
src/app/hdd.rs
Outdated
| /// Extract the base block device name from a device path. | ||
| /// | ||
| /// For example: | ||
| /// - `/dev/vda1` → `vda` | ||
| /// - `/dev/sda1` → `sda` | ||
| /// - `/dev/xvda1` → `xvda` | ||
| /// - `/dev/nvme0n1p1` → `nvme0n1` | ||
| /// - `/dev/mapper/xxx` → follows symlink, then recurses | ||
| #[cfg(target_os = "linux")] | ||
| fn extract_block_device_name(device_path: &str) -> Option<String> { | ||
| use std::fs; | ||
|
|
||
| if device_path.starts_with("/dev/mapper/") || device_path.starts_with("/dev/root") { | ||
| let real = fs::canonicalize(device_path).ok()?; |
There was a problem hiding this comment.
extract_block_device_name handles /dev/root in code, but the doc comment only mentions /dev/mapper/xxx symlinks. Please update the documentation/examples to include /dev/root (and clarify what happens when canonicalization resolves to dm-*).
src/app/hdd.rs
Outdated
| // Verify this block device exists in sysfs | ||
| let sysfs_path = Path::new("/sys/block").join(block_dev); | ||
| if sysfs_path.exists() { | ||
| Some(block_dev.to_string()) | ||
| } else { | ||
| None | ||
| } |
There was a problem hiding this comment.
extract_block_device_name mixes parsing with an environment-dependent /sys/block/<dev> existence check. This makes the function return None for perfectly valid device strings on systems where sysfs is unavailable/restricted (and makes the unit tests mostly conditional/no-op). Consider splitting into (1) pure parsing that always returns the base dev name and (2) a separate sysfs validation step used by correct_hdd_detection.
| // Verify this block device exists in sysfs | |
| let sysfs_path = Path::new("/sys/block").join(block_dev); | |
| if sysfs_path.exists() { | |
| Some(block_dev.to_string()) | |
| } else { | |
| None | |
| } | |
| Some(block_dev.to_string()) |
src/app/hdd/test.rs
Outdated
| // We can only fully verify cases where the block device exists in sysfs. | ||
| // For non-existent devices, extract_block_device_name returns None because | ||
| // the sysfs path check fails. So we just verify it doesn't panic. | ||
| if let Some(expected_name) = expected { | ||
| if std::path::Path::new("/sys/block") | ||
| .join(expected_name) | ||
| .exists() | ||
| { | ||
| assert_eq!(result.as_deref(), Some(*expected_name)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
These Linux tests are highly environment-dependent and can silently do no assertions (e.g., they only assert when /sys/block/<name> exists, and the VirtIO checks are skipped unless /sys/block/vda/... exists). This risks CI passing without exercising the new logic. Consider creating hermetic tests by factoring sysfs access behind an injectable root path (or helper functions) and using a tempdir + symlinks to simulate /sys/block/<dev>/device/driver.
| // We can only fully verify cases where the block device exists in sysfs. | |
| // For non-existent devices, extract_block_device_name returns None because | |
| // the sysfs path check fails. So we just verify it doesn't panic. | |
| if let Some(expected_name) = expected { | |
| if std::path::Path::new("/sys/block") | |
| .join(expected_name) | |
| .exists() | |
| { | |
| assert_eq!(result.as_deref(), Some(*expected_name)); | |
| } | |
| } | |
| } | |
| } | |
| // Verify that extract_block_device_name parses the device path as expected. | |
| // This test is hermetic: it only checks the returned block device name, | |
| // independent of whether the device actually exists in sysfs. | |
| assert_eq!(result.as_deref(), *expected); | |
| } | |
| } | |
| #[test] | |
| fn test_is_virtual_block_device_on_virtio() { | |
| // On this VirtIO environment, vda should be detected as virtual | |
| if std::path::Path::new("/sys/block/vda/device/driver").exists() { | |
| assert!( | |
| is_virtual_block_device("vda"), | |
| "vda should be detected as a virtual block device" | |
| ); | |
| } | |
| } | |
| #[test] | |
| fn test_extract_and_check_real_disks() { | |
| // Integration test: verify that VirtIO disks on this system are correctly identified | |
| use sysinfo::Disks; | |
| let disks = Disks::new_with_refreshed_list(); | |
| for disk in disks.list() { | |
| let name = disk.name().to_str().unwrap_or_default(); | |
| if let Some(block_dev) = extract_block_device_name(name) { | |
| if block_dev.starts_with("vd") { | |
| assert!( | |
| is_virtual_block_device(&block_dev), | |
| "VirtIO device {block_dev} should be detected as virtual" | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } |
|
|
||
| #[test] | ||
| fn test_extract_and_check_real_disks() { | ||
| // Integration test: verify that VirtIO disks on this system are correctly identified | ||
| use sysinfo::Disks; | ||
| let disks = Disks::new_with_refreshed_list(); | ||
| for disk in disks.list() { | ||
| let name = disk.name().to_str().unwrap_or_default(); | ||
| if let Some(block_dev) = extract_block_device_name(name) { | ||
| if block_dev.starts_with("vd") { | ||
| assert!( | ||
| is_virtual_block_device(&block_dev), | ||
| "VirtIO device {block_dev} should be detected as virtual" | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
test_extract_and_check_real_disks only asserts for devices starting with vd, and otherwise does nothing. On most CI/hosts (where disks are sd*/nvme*), this test will always pass without validating the intended regression fix. Consider replacing this with a deterministic test that constructs a fake sysfs tree and verifies correct_hdd_detection (or is_virtual_block_device) for each supported driver name.
| #[test] | |
| fn test_extract_and_check_real_disks() { | |
| // Integration test: verify that VirtIO disks on this system are correctly identified | |
| use sysinfo::Disks; | |
| let disks = Disks::new_with_refreshed_list(); | |
| for disk in disks.list() { | |
| let name = disk.name().to_str().unwrap_or_default(); | |
| if let Some(block_dev) = extract_block_device_name(name) { | |
| if block_dev.starts_with("vd") { | |
| assert!( | |
| is_virtual_block_device(&block_dev), | |
| "VirtIO device {block_dev} should be detected as virtual" | |
| ); | |
| } | |
| } | |
| } | |
| } |
- Fix Hyper-V driver name: use 'hv_storvsc' (actual kernel name) instead of 'hyperv_storvsc' - Split extract_block_device_name into parse_block_device_name (pure parsing, no I/O) and validate_block_device (sysfs check), making the parsing logic independently testable - Update doc comments to document /dev/root handling - Make tests hermetic: test_parse_block_device_name asserts deterministically on all cases without depending on sysfs availability - Improve test_extract_and_check_real_disks to validate all disk types, not just VirtIO https://claude.ai/code/session_01MHKaZm6DcA9kjBPnW3KN5K
Summary
This PR fixes an issue where virtual block devices (VirtIO, Xen, Hyper-V, VMware) on Linux systems are incorrectly reported as HDDs instead of Unknown disks. The kernel's
rotationalsysfs flag defaults to1for virtual devices when the backing storage type cannot be determined, causingsysinfoto misclassify them.Key Changes
correct_hdd_detection()function: Intercepts HDD classification on Linux to verify if the disk is actually backed by a virtual driverextract_block_device_name()function: Parses device paths (e.g.,/dev/vda1→vda) and handles symlinks like/dev/mapper/and/dev/rootis_virtual_block_device()function: Checks the driver symlink at/sys/block/<dev>/device/driverto identify virtual drivers (virtio_blk,xen_blkfront,vmw_pvscsi,hyperv_storvsc)#[inline]attribute fromget_disk_kind()to accommodate the new logicImplementation Details
DiskKind::HDDas beforeDiskKind::Unknown(-1)instead ofHDDhttps://claude.ai/code/session_01MHKaZm6DcA9kjBPnW3KN5K