mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-19 11:06:17 +00:00
deb0edb7cc
Remove every bare `#[allow(dead_code)]` in io-core, object-capacity, targets, rio, and scanner. Each allow was stripped first and clippy was then asked which ones the compiler actually missed, so the verdicts rest on the diagnostic rather than on inspection. 23 were inert: they sat on `pub fn`s inside `pub mod`s, where `dead_code` does not apply, or on scanner integration-test helpers that the tests in the same file do call. The remaining 3 are in rio's private `compress_index` module and the code behind them is deleted rather than annotated. `remove_index_headers` is dead and also wrong — after skipping the 4-byte chunk header it matches against `S2_INDEX_TRAILER` where `S2_INDEX_HEADER` sits, so it returns `None` for every well-formed index; rio-v2 carries the correct equivalent that is actually in use. `restore_index_headers` is its unreachable counterpart, likewise duplicated live in rio-v2. `Index::reset` is a private method with no caller. Refs backlog#1823
466 lines
15 KiB
Rust
466 lines
15 KiB
Rust
// Copyright 2024 RustFS Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! I/O profile helpers for adaptive scheduling.
|
|
|
|
use std::collections::VecDeque;
|
|
use std::str::FromStr;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub enum StorageMedia {
|
|
Nvme,
|
|
Ssd,
|
|
Hdd,
|
|
Unknown,
|
|
}
|
|
|
|
impl StorageMedia {
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
Self::Nvme => "nvme",
|
|
Self::Ssd => "ssd",
|
|
Self::Hdd => "hdd",
|
|
Self::Unknown => "unknown",
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for StorageMedia {
|
|
type Err = ();
|
|
|
|
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
|
match value.trim().to_ascii_lowercase().as_str() {
|
|
"nvme" => Ok(Self::Nvme),
|
|
"ssd" => Ok(Self::Ssd),
|
|
"hdd" => Ok(Self::Hdd),
|
|
"unknown" => Ok(Self::Unknown),
|
|
_ => Err(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub enum AccessPattern {
|
|
Sequential,
|
|
Random,
|
|
Mixed,
|
|
Unknown,
|
|
}
|
|
|
|
impl AccessPattern {
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
Self::Sequential => "sequential",
|
|
Self::Random => "random",
|
|
Self::Mixed => "mixed",
|
|
Self::Unknown => "unknown",
|
|
}
|
|
}
|
|
|
|
/// Check if this is a sequential access pattern.
|
|
pub fn is_sequential(&self) -> bool {
|
|
matches!(self, Self::Sequential)
|
|
}
|
|
|
|
/// Check if this is a random access pattern.
|
|
pub fn is_random(&self) -> bool {
|
|
matches!(self, Self::Random)
|
|
}
|
|
|
|
/// Check if this is a mixed access pattern.
|
|
pub fn is_mixed(&self) -> bool {
|
|
matches!(self, Self::Mixed)
|
|
}
|
|
|
|
/// Check if this pattern is unknown.
|
|
pub fn is_unknown(&self) -> bool {
|
|
matches!(self, Self::Unknown)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub struct StorageProfile {
|
|
pub media: StorageMedia,
|
|
pub buffer_cap: usize,
|
|
pub sequential_boost_multiplier: f64,
|
|
pub random_penalty_multiplier: f64,
|
|
pub prefers_readahead: bool,
|
|
}
|
|
|
|
impl StorageProfile {
|
|
pub fn for_media(media: StorageMedia, nvme_buffer_cap: usize, ssd_buffer_cap: usize, hdd_buffer_cap: usize) -> Self {
|
|
match media {
|
|
StorageMedia::Nvme => Self {
|
|
media,
|
|
buffer_cap: nvme_buffer_cap,
|
|
sequential_boost_multiplier: 1.35,
|
|
random_penalty_multiplier: 0.9,
|
|
prefers_readahead: true,
|
|
},
|
|
StorageMedia::Ssd => Self {
|
|
media,
|
|
buffer_cap: ssd_buffer_cap,
|
|
sequential_boost_multiplier: 1.2,
|
|
random_penalty_multiplier: 0.8,
|
|
prefers_readahead: true,
|
|
},
|
|
StorageMedia::Hdd => Self {
|
|
media,
|
|
buffer_cap: hdd_buffer_cap,
|
|
sequential_boost_multiplier: 1.1,
|
|
random_penalty_multiplier: 0.65,
|
|
prefers_readahead: false,
|
|
},
|
|
StorageMedia::Unknown => Self {
|
|
media,
|
|
buffer_cap: ssd_buffer_cap,
|
|
sequential_boost_multiplier: 1.0,
|
|
random_penalty_multiplier: 0.8,
|
|
prefers_readahead: true,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct IoPatternDetector {
|
|
history_size: usize,
|
|
sequential_step_tolerance_bytes: u64,
|
|
history: VecDeque<(u64, u64)>,
|
|
}
|
|
|
|
impl IoPatternDetector {
|
|
pub fn new(history_size: usize, sequential_step_tolerance_bytes: u64) -> Self {
|
|
Self {
|
|
history_size: history_size.max(2),
|
|
sequential_step_tolerance_bytes,
|
|
history: VecDeque::with_capacity(history_size.max(2)),
|
|
}
|
|
}
|
|
|
|
pub fn record(&mut self, offset: u64, len: u64) {
|
|
if self.history.len() == self.history_size {
|
|
self.history.pop_front();
|
|
}
|
|
self.history.push_back((offset, len));
|
|
}
|
|
|
|
pub fn current_pattern(&self) -> AccessPattern {
|
|
if self.history.len() < 2 {
|
|
return AccessPattern::Unknown;
|
|
}
|
|
|
|
let history = self.history.iter().copied().collect::<Vec<_>>();
|
|
let mut sequential = 0usize;
|
|
let mut random = 0usize;
|
|
|
|
for window in history.windows(2) {
|
|
let (prev_offset, prev_len) = window[0];
|
|
let (curr_offset, _) = window[1];
|
|
let prev_end = prev_offset.saturating_add(prev_len);
|
|
if curr_offset.abs_diff(prev_end) <= self.sequential_step_tolerance_bytes {
|
|
sequential += 1;
|
|
} else {
|
|
random += 1;
|
|
}
|
|
}
|
|
|
|
match (sequential, random) {
|
|
(0, 0) => AccessPattern::Unknown,
|
|
(_, 0) => AccessPattern::Sequential,
|
|
(0, _) => AccessPattern::Random,
|
|
_ => AccessPattern::Mixed,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn detect_storage_media(storage_detection_enabled: bool, storage_media_override: &str) -> StorageMedia {
|
|
if let Ok(media) = StorageMedia::from_str(storage_media_override) {
|
|
return media;
|
|
}
|
|
|
|
if !storage_detection_enabled {
|
|
return StorageMedia::Unknown;
|
|
}
|
|
|
|
// Try platform-specific detection
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
if let Ok(media) = detect_linux_storage_media()
|
|
&& media != StorageMedia::Unknown
|
|
{
|
|
return media;
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
if let Ok(media) = detect_macos_storage_media()
|
|
&& media != StorageMedia::Unknown
|
|
{
|
|
return media;
|
|
}
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
|
{
|
|
if let Ok(media) = detect_platform_storage_media()
|
|
&& media != StorageMedia::Unknown
|
|
{
|
|
return media;
|
|
}
|
|
}
|
|
|
|
StorageMedia::Unknown
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn detect_linux_storage_media() -> Result<StorageMedia, std::io::Error> {
|
|
use std::path::Path;
|
|
|
|
// Try to detect NVMe devices first
|
|
if Path::new("/sys/class/nvme").exists() {
|
|
// Check if there are any NVMe devices
|
|
if let Ok(entries) = std::fs::read_dir("/sys/class/nvme") {
|
|
for entry in entries.flatten() {
|
|
let name = entry.file_name();
|
|
let name_str = name.to_string_lossy();
|
|
if name_str.starts_with("nvme") {
|
|
return Ok(StorageMedia::Nvme);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check rotational flag for common block devices (sda, sdb, etc.)
|
|
for device in &["sda", "sdb", "nvme0n1", "vda"] {
|
|
let rotational_path = format!("/sys/block/{}/queue/rotational", device);
|
|
if let Ok(content) = std::fs::read_to_string(&rotational_path) {
|
|
let rotational = content.trim().parse::<u32>().unwrap_or(1);
|
|
if rotational == 0 {
|
|
// Non-rotating = SSD/NVMe
|
|
// If device name starts with "nvme", it's NVMe
|
|
if device.starts_with("nvme") {
|
|
return Ok(StorageMedia::Nvme);
|
|
}
|
|
return Ok(StorageMedia::Ssd);
|
|
} else {
|
|
// Rotating = HDD
|
|
return Ok(StorageMedia::Hdd);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(StorageMedia::Unknown)
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
fn detect_macos_storage_media() -> Result<StorageMedia, std::io::Error> {
|
|
use std::process::Command;
|
|
|
|
// Use diskutil to get disk information
|
|
let output = Command::new("diskutil").args(["info", "/"]).output()?;
|
|
|
|
if !output.status.success() {
|
|
return Ok(StorageMedia::Unknown);
|
|
}
|
|
|
|
let info = String::from_utf8_lossy(&output.stdout);
|
|
|
|
// Check for NVMe
|
|
if info.contains("NVMe") || info.contains("nvme") {
|
|
return Ok(StorageMedia::Nvme);
|
|
}
|
|
|
|
// Check for SSD indicators
|
|
if info.contains("Solid State") || info.contains("SSD") || info.contains("Solid-State") {
|
|
return Ok(StorageMedia::Ssd);
|
|
}
|
|
|
|
// Check for HDD/rotational indicators
|
|
// Note: macOS typically doesn't explicitly say "HDD", so we assume HDD if not SSD/NVMe
|
|
// when detection is enabled
|
|
if info.contains("Rotational") || info.contains("HDD") {
|
|
return Ok(StorageMedia::Hdd);
|
|
}
|
|
|
|
// Default to SSD for modern Macs (most are SSD-based)
|
|
// This is a reasonable default for macOS systems
|
|
Ok(StorageMedia::Ssd)
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
|
fn detect_platform_storage_media() -> Result<StorageMedia, std::io::Error> {
|
|
Ok(StorageMedia::Unknown)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_storage_media_override() {
|
|
// Override should always take precedence
|
|
assert_eq!(detect_storage_media(true, "nvme"), StorageMedia::Nvme);
|
|
assert_eq!(detect_storage_media(false, "ssd"), StorageMedia::Ssd);
|
|
assert_eq!(detect_storage_media(true, "hdd"), StorageMedia::Hdd);
|
|
assert_eq!(detect_storage_media(false, "unknown"), StorageMedia::Unknown);
|
|
}
|
|
|
|
#[test]
|
|
fn test_storage_media_from_str() {
|
|
assert_eq!(StorageMedia::from_str("nvme"), Ok(StorageMedia::Nvme));
|
|
assert_eq!(StorageMedia::from_str("NVMe"), Ok(StorageMedia::Nvme));
|
|
assert_eq!(StorageMedia::from_str("ssd"), Ok(StorageMedia::Ssd));
|
|
assert_eq!(StorageMedia::from_str("SSD"), Ok(StorageMedia::Ssd));
|
|
assert_eq!(StorageMedia::from_str("hdd"), Ok(StorageMedia::Hdd));
|
|
assert_eq!(StorageMedia::from_str("HDD"), Ok(StorageMedia::Hdd));
|
|
assert_eq!(StorageMedia::from_str("unknown"), Ok(StorageMedia::Unknown));
|
|
assert_eq!(StorageMedia::from_str("invalid"), Err(()));
|
|
assert_eq!(StorageMedia::from_str(""), Err(()));
|
|
}
|
|
|
|
#[test]
|
|
fn test_storage_media_as_str() {
|
|
assert_eq!(StorageMedia::Nvme.as_str(), "nvme");
|
|
assert_eq!(StorageMedia::Ssd.as_str(), "ssd");
|
|
assert_eq!(StorageMedia::Hdd.as_str(), "hdd");
|
|
assert_eq!(StorageMedia::Unknown.as_str(), "unknown");
|
|
}
|
|
|
|
#[test]
|
|
fn test_storage_detection_disabled() {
|
|
// When detection is disabled and no override, should return Unknown
|
|
assert_eq!(detect_storage_media(false, ""), StorageMedia::Unknown);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pattern_detector_sequential() {
|
|
let mut detector = IoPatternDetector::new(4, 1024);
|
|
detector.record(0, 4096);
|
|
detector.record(4096, 4096);
|
|
detector.record(8192, 4096);
|
|
assert_eq!(detector.current_pattern(), AccessPattern::Sequential);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pattern_detector_random() {
|
|
let mut detector = IoPatternDetector::new(4, 1024);
|
|
detector.record(0, 4096);
|
|
detector.record(65536, 4096);
|
|
detector.record(4096, 4096);
|
|
assert_eq!(detector.current_pattern(), AccessPattern::Random);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pattern_detector_mixed() {
|
|
let mut detector = IoPatternDetector::new(10, 1024);
|
|
detector.record(0, 4096); // Sequential to 4096
|
|
detector.record(4096, 4096); // Sequential to 8192
|
|
detector.record(65536, 4096); // Random jump
|
|
detector.record(98304, 4096); // Sequential from random position
|
|
assert_eq!(detector.current_pattern(), AccessPattern::Mixed);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pattern_detector_insufficient_history() {
|
|
let detector = IoPatternDetector::new(10, 1024);
|
|
// No records yet
|
|
assert_eq!(detector.current_pattern(), AccessPattern::Unknown);
|
|
|
|
// Only one record
|
|
let mut detector = IoPatternDetector::new(10, 1024);
|
|
detector.record(0, 4096);
|
|
assert_eq!(detector.current_pattern(), AccessPattern::Unknown);
|
|
}
|
|
|
|
#[test]
|
|
fn test_access_pattern_helpers() {
|
|
assert!(AccessPattern::Sequential.is_sequential());
|
|
assert!(!AccessPattern::Sequential.is_random());
|
|
assert!(!AccessPattern::Sequential.is_mixed());
|
|
assert!(!AccessPattern::Sequential.is_unknown());
|
|
|
|
assert!(AccessPattern::Random.is_random());
|
|
assert!(!AccessPattern::Random.is_sequential());
|
|
|
|
assert!(AccessPattern::Mixed.is_mixed());
|
|
assert!(!AccessPattern::Mixed.is_sequential());
|
|
assert!(!AccessPattern::Mixed.is_random());
|
|
|
|
assert!(AccessPattern::Unknown.is_unknown());
|
|
assert!(!AccessPattern::Unknown.is_sequential());
|
|
}
|
|
|
|
#[test]
|
|
fn test_storage_profile_for_media() {
|
|
let nvme_cap = 2 * 1024 * 1024;
|
|
let ssd_cap = 1024 * 1024;
|
|
let hdd_cap = 512 * 1024;
|
|
|
|
let nvme_profile = StorageProfile::for_media(StorageMedia::Nvme, nvme_cap, ssd_cap, hdd_cap);
|
|
assert_eq!(nvme_profile.media, StorageMedia::Nvme);
|
|
assert_eq!(nvme_profile.buffer_cap, nvme_cap);
|
|
assert_eq!(nvme_profile.sequential_boost_multiplier, 1.35);
|
|
assert_eq!(nvme_profile.random_penalty_multiplier, 0.9);
|
|
assert!(nvme_profile.prefers_readahead);
|
|
|
|
let ssd_profile = StorageProfile::for_media(StorageMedia::Ssd, nvme_cap, ssd_cap, hdd_cap);
|
|
assert_eq!(ssd_profile.media, StorageMedia::Ssd);
|
|
assert_eq!(ssd_profile.buffer_cap, ssd_cap);
|
|
assert_eq!(ssd_profile.sequential_boost_multiplier, 1.2);
|
|
assert_eq!(ssd_profile.random_penalty_multiplier, 0.8);
|
|
|
|
let hdd_profile = StorageProfile::for_media(StorageMedia::Hdd, nvme_cap, ssd_cap, hdd_cap);
|
|
assert_eq!(hdd_profile.media, StorageMedia::Hdd);
|
|
assert_eq!(hdd_profile.buffer_cap, hdd_cap);
|
|
assert_eq!(hdd_profile.sequential_boost_multiplier, 1.1);
|
|
assert_eq!(hdd_profile.random_penalty_multiplier, 0.65);
|
|
assert!(!hdd_profile.prefers_readahead);
|
|
|
|
let unknown_profile = StorageProfile::for_media(StorageMedia::Unknown, nvme_cap, ssd_cap, hdd_cap);
|
|
assert_eq!(unknown_profile.media, StorageMedia::Unknown);
|
|
// Unknown media uses SSD cap
|
|
assert_eq!(unknown_profile.buffer_cap, ssd_cap);
|
|
assert_eq!(unknown_profile.sequential_boost_multiplier, 1.0);
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
#[test]
|
|
fn test_linux_storage_detection_exists() {
|
|
// This test just verifies the detection function exists and doesn't panic
|
|
// The actual result depends on the system it's running on
|
|
let result = detect_storage_media(true, "");
|
|
// We should get some result (not panic)
|
|
match result {
|
|
StorageMedia::Nvme | StorageMedia::Ssd | StorageMedia::Hdd | StorageMedia::Unknown => {
|
|
// All valid results
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
#[test]
|
|
fn test_macos_storage_detection_exists() {
|
|
// This test just verifies the detection function exists and doesn't panic
|
|
let result = detect_storage_media(true, "");
|
|
// We should get some result (not panic)
|
|
match result {
|
|
StorageMedia::Nvme | StorageMedia::Ssd | StorageMedia::Hdd | StorageMedia::Unknown => {
|
|
// All valid results
|
|
}
|
|
}
|
|
}
|
|
}
|