Signed-off-by: junxiang Mu <1948535941@qq.com>
This commit is contained in:
junxiang Mu
2025-07-24 11:08:32 +08:00
parent 2a8c46874d
commit 4fefd63a5b
12 changed files with 408 additions and 1398 deletions
Generated
+6
View File
@@ -7933,6 +7933,7 @@ dependencies = [
"chrono", "chrono",
"futures", "futures",
"lazy_static", "lazy_static",
"once_cell",
"rmp-serde", "rmp-serde",
"rustfs-common", "rustfs-common",
"rustfs-ecstore", "rustfs-ecstore",
@@ -7942,14 +7943,18 @@ dependencies = [
"rustfs-utils", "rustfs-utils",
"serde", "serde",
"serde_json", "serde_json",
"serial_test",
"tempfile",
"thiserror 2.0.12", "thiserror 2.0.12",
"time", "time",
"tokio", "tokio",
"tokio-test", "tokio-test",
"tokio-util", "tokio-util",
"tracing", "tracing",
"tracing-subscriber",
"url", "url",
"uuid", "uuid",
"walkdir",
] ]
[[package]] [[package]]
@@ -7968,6 +7973,7 @@ version = "0.0.5"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"chrono", "chrono",
"lazy_static",
"path-clean", "path-clean",
"rmp-serde", "rmp-serde",
"rustfs-filemeta", "rustfs-filemeta",
+9 -10
View File
@@ -1,16 +1,10 @@
[package] [package]
name = "rustfs-ahm" name = "rustfs-ahm"
version.workspace = true version = "0.0.5"
edition.workspace = true edition = "2021"
authors = ["RustFS Team"] authors = ["RustFS Team"]
license.workspace = true license = "Apache-2.0"
description = "RustFS AHM (Automatic Health Management) Scanner" description = "RustFS AHM (Automatic Health Management) Scanner"
repository.workspace = true
rust-version.workspace = true
homepage.workspace = true
documentation = "https://docs.rs/rustfs-ahm/latest/rustfs_ahm/"
keywords = ["RustFS", "AHM", "health-management", "scanner", "Minio"]
categories = ["web-programming", "development-tools", "filesystem"]
[dependencies] [dependencies]
rustfs-ecstore = { workspace = true } rustfs-ecstore = { workspace = true }
@@ -38,5 +32,10 @@ chrono = { workspace = true }
[dev-dependencies] [dev-dependencies]
rmp-serde = { workspace = true } rmp-serde = { workspace = true }
tokio-test = { workspace = true } tokio-test = "0.4"
serde_json = { workspace = true } serde_json = { workspace = true }
serial_test = "3.2.0"
once_cell = { workspace = true }
tracing-subscriber = { workspace = true }
walkdir = "2.5.0"
tempfile = "3.10"
+1 -4
View File
@@ -22,10 +22,7 @@ pub mod scanner;
pub use error::{Error, Result}; pub use error::{Error, Result};
pub use heal::{channel::HealChannelProcessor, HealManager, HealOptions, HealPriority, HealRequest, HealType}; pub use heal::{channel::HealChannelProcessor, HealManager, HealOptions, HealPriority, HealRequest, HealType};
pub use scanner::{ pub use scanner::Scanner;
BucketTargetUsageInfo, BucketUsageInfo, DataUsageInfo, Scanner, ScannerMetrics, load_data_usage_from_backend,
store_data_usage_in_backend,
};
// Global cancellation token for AHM services (scanner and other background tasks) // Global cancellation token for AHM services (scanner and other background tasks)
static GLOBAL_AHM_SERVICES_CANCEL_TOKEN: OnceLock<CancellationToken> = OnceLock::new(); static GLOBAL_AHM_SERVICES_CANCEL_TOKEN: OnceLock<CancellationToken> = OnceLock::new();
+2 -1
View File
@@ -527,6 +527,7 @@ impl Scanner {
mod_time: p.mod_time, mod_time: p.mod_time,
index: p.index.clone(), index: p.index.clone(),
checksums: p.checksums.clone(), checksums: p.checksums.clone(),
error: None,
}) })
.collect(), .collect(),
erasure: rustfs_filemeta::ErasureInfo { erasure: rustfs_filemeta::ErasureInfo {
@@ -1392,8 +1393,8 @@ mod tests {
use rustfs_ecstore::endpoints::{EndpointServerPools, Endpoints, PoolEndpoints}; use rustfs_ecstore::endpoints::{EndpointServerPools, Endpoints, PoolEndpoints};
use rustfs_ecstore::store::ECStore; use rustfs_ecstore::store::ECStore;
use rustfs_ecstore::{ use rustfs_ecstore::{
StorageAPI,
store_api::{MakeBucketOptions, ObjectIO, PutObjReader}, store_api::{MakeBucketOptions, ObjectIO, PutObjReader},
StorageAPI,
}; };
use serial_test::serial; use serial_test::serial;
use std::fs; use std::fs;
+251 -222
View File
@@ -12,197 +12,258 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::collections::HashMap; use std::{
collections::HashMap,
sync::atomic::{AtomicU64, Ordering},
time::{Duration, SystemTime},
};
/// Size interval for object size histogram use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)] use tracing::info;
pub struct SizeInterval {
pub start: u64, /// Scanner metrics
pub end: u64, #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub name: &'static str, pub struct ScannerMetrics {
/// Total objects scanned since server start
pub objects_scanned: u64,
/// Total object versions scanned since server start
pub versions_scanned: u64,
/// Total directories scanned since server start
pub directories_scanned: u64,
/// Total bucket scans started since server start
pub bucket_scans_started: u64,
/// Total bucket scans finished since server start
pub bucket_scans_finished: u64,
/// Total objects with health issues found
pub objects_with_issues: u64,
/// Total heal tasks queued
pub heal_tasks_queued: u64,
/// Total heal tasks completed
pub heal_tasks_completed: u64,
/// Total heal tasks failed
pub heal_tasks_failed: u64,
/// Total healthy objects found
pub healthy_objects: u64,
/// Total corrupted objects found
pub corrupted_objects: u64,
/// Last scan activity time
pub last_activity: Option<SystemTime>,
/// Current scan cycle
pub current_cycle: u64,
/// Total scan cycles completed
pub total_cycles: u64,
/// Current scan duration
pub current_scan_duration: Option<Duration>,
/// Average scan duration
pub avg_scan_duration: Duration,
/// Objects scanned per second
pub objects_per_second: f64,
/// Buckets scanned per second
pub buckets_per_second: f64,
/// Storage metrics by bucket
pub bucket_metrics: HashMap<String, BucketMetrics>,
/// Disk metrics
pub disk_metrics: HashMap<String, DiskMetrics>,
} }
/// Version interval for object versions histogram /// Bucket-specific metrics
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct VersionInterval { pub struct BucketMetrics {
pub start: u64, /// Bucket name
pub end: u64, pub bucket: String,
pub name: &'static str, /// Total objects in bucket
pub total_objects: u64,
/// Total size of objects in bucket (bytes)
pub total_size: u64,
/// Objects with health issues
pub objects_with_issues: u64,
/// Last scan time
pub last_scan_time: Option<SystemTime>,
/// Scan duration
pub scan_duration: Option<Duration>,
/// Heal tasks queued for this bucket
pub heal_tasks_queued: u64,
/// Heal tasks completed for this bucket
pub heal_tasks_completed: u64,
/// Heal tasks failed for this bucket
pub heal_tasks_failed: u64,
} }
/// Object size histogram intervals /// Disk-specific metrics
pub const OBJECTS_HISTOGRAM_INTERVALS: &[SizeInterval] = &[ #[derive(Debug, Clone, Default, Serialize, Deserialize)]
SizeInterval { pub struct DiskMetrics {
start: 0, /// Disk path
end: 1024 - 1, pub disk_path: String,
name: "LESS_THAN_1_KiB", /// Total disk space (bytes)
}, pub total_space: u64,
SizeInterval { /// Used disk space (bytes)
start: 1024, pub used_space: u64,
end: 1024 * 1024 - 1, /// Free disk space (bytes)
name: "1_KiB_TO_1_MiB", pub free_space: u64,
}, /// Objects scanned on this disk
SizeInterval { pub objects_scanned: u64,
start: 1024 * 1024, /// Objects with issues on this disk
end: 10 * 1024 * 1024 - 1, pub objects_with_issues: u64,
name: "1_MiB_TO_10_MiB", /// Last scan time
}, pub last_scan_time: Option<SystemTime>,
SizeInterval { /// Whether disk is online
start: 10 * 1024 * 1024, pub is_online: bool,
end: 64 * 1024 * 1024 - 1, /// Whether disk is being scanned
name: "10_MiB_TO_64_MiB", pub is_scanning: bool,
},
SizeInterval {
start: 64 * 1024 * 1024,
end: 128 * 1024 * 1024 - 1,
name: "64_MiB_TO_128_MiB",
},
SizeInterval {
start: 128 * 1024 * 1024,
end: 512 * 1024 * 1024 - 1,
name: "128_MiB_TO_512_MiB",
},
SizeInterval {
start: 512 * 1024 * 1024,
end: u64::MAX,
name: "MORE_THAN_512_MiB",
},
];
/// Object version count histogram intervals
pub const OBJECTS_VERSION_COUNT_INTERVALS: &[VersionInterval] = &[
VersionInterval {
start: 1,
end: 1,
name: "1_VERSION",
},
VersionInterval {
start: 2,
end: 10,
name: "2_TO_10_VERSIONS",
},
VersionInterval {
start: 11,
end: 100,
name: "11_TO_100_VERSIONS",
},
VersionInterval {
start: 101,
end: 1000,
name: "101_TO_1000_VERSIONS",
},
VersionInterval {
start: 1001,
end: u64::MAX,
name: "MORE_THAN_1000_VERSIONS",
},
];
/// Size histogram for object size distribution
#[derive(Debug, Clone, Default)]
pub struct SizeHistogram {
counts: Vec<u64>,
} }
/// Versions histogram for object version count distribution /// Thread-safe metrics collector
#[derive(Debug, Clone, Default)] pub struct MetricsCollector {
pub struct VersionsHistogram { /// Atomic counters for real-time metrics
counts: Vec<u64>, objects_scanned: AtomicU64,
versions_scanned: AtomicU64,
directories_scanned: AtomicU64,
bucket_scans_started: AtomicU64,
bucket_scans_finished: AtomicU64,
objects_with_issues: AtomicU64,
heal_tasks_queued: AtomicU64,
heal_tasks_completed: AtomicU64,
heal_tasks_failed: AtomicU64,
current_cycle: AtomicU64,
total_cycles: AtomicU64,
healthy_objects: AtomicU64,
corrupted_objects: AtomicU64,
} }
impl SizeHistogram { impl MetricsCollector {
/// Create a new size histogram /// Create a new metrics collector
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
counts: vec![0; OBJECTS_HISTOGRAM_INTERVALS.len()], objects_scanned: AtomicU64::new(0),
versions_scanned: AtomicU64::new(0),
directories_scanned: AtomicU64::new(0),
bucket_scans_started: AtomicU64::new(0),
bucket_scans_finished: AtomicU64::new(0),
objects_with_issues: AtomicU64::new(0),
heal_tasks_queued: AtomicU64::new(0),
heal_tasks_completed: AtomicU64::new(0),
heal_tasks_failed: AtomicU64::new(0),
current_cycle: AtomicU64::new(0),
total_cycles: AtomicU64::new(0),
healthy_objects: AtomicU64::new(0),
corrupted_objects: AtomicU64::new(0),
} }
} }
/// Add a size to the histogram /// Increment objects scanned count
pub fn add(&mut self, size: u64) { pub fn increment_objects_scanned(&self, count: u64) {
for (idx, interval) in OBJECTS_HISTOGRAM_INTERVALS.iter().enumerate() { self.objects_scanned.fetch_add(count, Ordering::Relaxed);
if size >= interval.start && size <= interval.end { }
self.counts[idx] += 1;
break; /// Increment versions scanned count
} pub fn increment_versions_scanned(&self, count: u64) {
self.versions_scanned.fetch_add(count, Ordering::Relaxed);
}
/// Increment directories scanned count
pub fn increment_directories_scanned(&self, count: u64) {
self.directories_scanned.fetch_add(count, Ordering::Relaxed);
}
/// Increment bucket scans started count
pub fn increment_bucket_scans_started(&self, count: u64) {
self.bucket_scans_started.fetch_add(count, Ordering::Relaxed);
}
/// Increment bucket scans finished count
pub fn increment_bucket_scans_finished(&self, count: u64) {
self.bucket_scans_finished.fetch_add(count, Ordering::Relaxed);
}
/// Increment objects with issues count
pub fn increment_objects_with_issues(&self, count: u64) {
self.objects_with_issues.fetch_add(count, Ordering::Relaxed);
}
/// Increment heal tasks queued count
pub fn increment_heal_tasks_queued(&self, count: u64) {
self.heal_tasks_queued.fetch_add(count, Ordering::Relaxed);
}
/// Increment heal tasks completed count
pub fn increment_heal_tasks_completed(&self, count: u64) {
self.heal_tasks_completed.fetch_add(count, Ordering::Relaxed);
}
/// Increment heal tasks failed count
pub fn increment_heal_tasks_failed(&self, count: u64) {
self.heal_tasks_failed.fetch_add(count, Ordering::Relaxed);
}
/// Set current cycle
pub fn set_current_cycle(&self, cycle: u64) {
self.current_cycle.store(cycle, Ordering::Relaxed);
}
/// Increment total cycles
pub fn increment_total_cycles(&self) {
self.total_cycles.fetch_add(1, Ordering::Relaxed);
}
/// Increment healthy objects count
pub fn increment_healthy_objects(&self) {
self.healthy_objects.fetch_add(1, Ordering::Relaxed);
}
/// Increment corrupted objects count
pub fn increment_corrupted_objects(&self) {
self.corrupted_objects.fetch_add(1, Ordering::Relaxed);
}
/// Get current metrics snapshot
pub fn get_metrics(&self) -> ScannerMetrics {
ScannerMetrics {
objects_scanned: self.objects_scanned.load(Ordering::Relaxed),
versions_scanned: self.versions_scanned.load(Ordering::Relaxed),
directories_scanned: self.directories_scanned.load(Ordering::Relaxed),
bucket_scans_started: self.bucket_scans_started.load(Ordering::Relaxed),
bucket_scans_finished: self.bucket_scans_finished.load(Ordering::Relaxed),
objects_with_issues: self.objects_with_issues.load(Ordering::Relaxed),
heal_tasks_queued: self.heal_tasks_queued.load(Ordering::Relaxed),
heal_tasks_completed: self.heal_tasks_completed.load(Ordering::Relaxed),
heal_tasks_failed: self.heal_tasks_failed.load(Ordering::Relaxed),
healthy_objects: self.healthy_objects.load(Ordering::Relaxed),
corrupted_objects: self.corrupted_objects.load(Ordering::Relaxed),
last_activity: Some(SystemTime::now()),
current_cycle: self.current_cycle.load(Ordering::Relaxed),
total_cycles: self.total_cycles.load(Ordering::Relaxed),
current_scan_duration: None, // Will be set by scanner
avg_scan_duration: Duration::ZERO, // Will be calculated
objects_per_second: 0.0, // Will be calculated
buckets_per_second: 0.0, // Will be calculated
bucket_metrics: HashMap::new(), // Will be populated by scanner
disk_metrics: HashMap::new(), // Will be populated by scanner
} }
} }
/// Get the histogram as a map /// Reset all metrics
pub fn to_map(&self) -> HashMap<String, u64> { pub fn reset(&self) {
let mut result = HashMap::new(); self.objects_scanned.store(0, Ordering::Relaxed);
for (idx, count) in self.counts.iter().enumerate() { self.versions_scanned.store(0, Ordering::Relaxed);
let interval = &OBJECTS_HISTOGRAM_INTERVALS[idx]; self.directories_scanned.store(0, Ordering::Relaxed);
result.insert(interval.name.to_string(), *count); self.bucket_scans_started.store(0, Ordering::Relaxed);
} self.bucket_scans_finished.store(0, Ordering::Relaxed);
result self.objects_with_issues.store(0, Ordering::Relaxed);
} self.heal_tasks_queued.store(0, Ordering::Relaxed);
self.heal_tasks_completed.store(0, Ordering::Relaxed);
self.heal_tasks_failed.store(0, Ordering::Relaxed);
self.current_cycle.store(0, Ordering::Relaxed);
self.total_cycles.store(0, Ordering::Relaxed);
self.healthy_objects.store(0, Ordering::Relaxed);
self.corrupted_objects.store(0, Ordering::Relaxed);
/// Merge another histogram into this one info!("Scanner metrics reset");
pub fn merge(&mut self, other: &SizeHistogram) {
for (idx, count) in other.counts.iter().enumerate() {
self.counts[idx] += count;
}
}
/// Get total count
pub fn total_count(&self) -> u64 {
self.counts.iter().sum()
}
/// Reset the histogram
pub fn reset(&mut self) {
for count in &mut self.counts {
*count = 0;
}
} }
} }
impl VersionsHistogram { impl Default for MetricsCollector {
/// Create a new versions histogram fn default() -> Self {
pub fn new() -> Self { Self::new()
Self {
counts: vec![0; OBJECTS_VERSION_COUNT_INTERVALS.len()],
}
}
/// Add a version count to the histogram
pub fn add(&mut self, versions: u64) {
for (idx, interval) in OBJECTS_VERSION_COUNT_INTERVALS.iter().enumerate() {
if versions >= interval.start && versions <= interval.end {
self.counts[idx] += 1;
break;
}
}
}
/// Get the histogram as a map
pub fn to_map(&self) -> HashMap<String, u64> {
let mut result = HashMap::new();
for (idx, count) in self.counts.iter().enumerate() {
let interval = &OBJECTS_VERSION_COUNT_INTERVALS[idx];
result.insert(interval.name.to_string(), *count);
}
result
}
/// Merge another histogram into this one
pub fn merge(&mut self, other: &VersionsHistogram) {
for (idx, count) in other.counts.iter().enumerate() {
self.counts[idx] += count;
}
}
/// Get total count
pub fn total_count(&self) -> u64 {
self.counts.iter().sum()
}
/// Reset the histogram
pub fn reset(&mut self) {
for count in &mut self.counts {
*count = 0;
}
} }
} }
@@ -211,67 +272,35 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_size_histogram() { fn test_metrics_collector_creation() {
let mut histogram = SizeHistogram::new(); let collector = MetricsCollector::new();
let metrics = collector.get_metrics();
// Add some sizes assert_eq!(metrics.objects_scanned, 0);
histogram.add(512); // LESS_THAN_1_KiB assert_eq!(metrics.versions_scanned, 0);
histogram.add(1024); // 1_KiB_TO_1_MiB
histogram.add(1024 * 1024); // 1_MiB_TO_10_MiB
histogram.add(5 * 1024 * 1024); // 1_MiB_TO_10_MiB
let map = histogram.to_map();
assert_eq!(map.get("LESS_THAN_1_KiB"), Some(&1));
assert_eq!(map.get("1_KiB_TO_1_MiB"), Some(&1));
assert_eq!(map.get("1_MiB_TO_10_MiB"), Some(&2));
assert_eq!(map.get("10_MiB_TO_64_MiB"), Some(&0));
} }
#[test] #[test]
fn test_versions_histogram() { fn test_metrics_increment() {
let mut histogram = VersionsHistogram::new(); let collector = MetricsCollector::new();
// Add some version counts collector.increment_objects_scanned(10);
histogram.add(1); // 1_VERSION collector.increment_versions_scanned(5);
histogram.add(5); // 2_TO_10_VERSIONS collector.increment_objects_with_issues(2);
histogram.add(50); // 11_TO_100_VERSIONS
histogram.add(500); // 101_TO_1000_VERSIONS
let map = histogram.to_map(); let metrics = collector.get_metrics();
assert_eq!(metrics.objects_scanned, 10);
assert_eq!(map.get("1_VERSION"), Some(&1)); assert_eq!(metrics.versions_scanned, 5);
assert_eq!(map.get("2_TO_10_VERSIONS"), Some(&1)); assert_eq!(metrics.objects_with_issues, 2);
assert_eq!(map.get("11_TO_100_VERSIONS"), Some(&1));
assert_eq!(map.get("101_TO_1000_VERSIONS"), Some(&1));
} }
#[test] #[test]
fn test_histogram_merge() { fn test_metrics_reset() {
let mut histogram1 = SizeHistogram::new(); let collector = MetricsCollector::new();
histogram1.add(1024);
histogram1.add(1024 * 1024);
let mut histogram2 = SizeHistogram::new(); collector.increment_objects_scanned(10);
histogram2.add(1024); collector.reset();
histogram2.add(5 * 1024 * 1024);
histogram1.merge(&histogram2); let metrics = collector.get_metrics();
assert_eq!(metrics.objects_scanned, 0);
let map = histogram1.to_map();
assert_eq!(map.get("1_KiB_TO_1_MiB"), Some(&2)); // 1 from histogram1 + 1 from histogram2
assert_eq!(map.get("1_MiB_TO_10_MiB"), Some(&2)); // 1 from histogram1 + 1 from histogram2
}
#[test]
fn test_histogram_reset() {
let mut histogram = SizeHistogram::new();
histogram.add(1024);
histogram.add(1024 * 1024);
assert_eq!(histogram.total_count(), 2);
histogram.reset();
assert_eq!(histogram.total_count(), 0);
} }
} }
-5
View File
@@ -13,13 +13,8 @@
// limitations under the License. // limitations under the License.
pub mod data_scanner; pub mod data_scanner;
pub mod data_usage;
pub mod histogram; pub mod histogram;
pub mod metrics; pub mod metrics;
// Re-export main types for convenience
pub use data_scanner::Scanner; pub use data_scanner::Scanner;
pub use data_usage::{
BucketTargetUsageInfo, BucketUsageInfo, DataUsageInfo, load_data_usage_from_backend, store_data_usage_in_backend,
};
pub use metrics::ScannerMetrics; pub use metrics::ScannerMetrics;
+2 -1
View File
@@ -28,7 +28,8 @@ categories = ["web-programming", "development-tools", "data-structures"]
workspace = true workspace = true
[dependencies] [dependencies]
tokio.workspace = true lazy_static = { workspace = true}
tokio = { workspace = true }
tonic = { workspace = true } tonic = { workspace = true }
uuid = { workspace = true } uuid = { workspace = true }
chrono = { workspace = true } chrono = { workspace = true }
+2 -195
View File
@@ -21,9 +21,6 @@ use super::{
}; };
use super::{endpoint::Endpoint, error::DiskError, format::FormatV3}; use super::{endpoint::Endpoint, error::DiskError, format::FormatV3};
use crate::bucket::metadata_sys::{self};
use crate::bucket::versioning::VersioningApi;
use crate::bucket::versioning_sys::BucketVersioningSys;
use crate::disk::error::FileAccessDeniedWithContext; use crate::disk::error::FileAccessDeniedWithContext;
use crate::disk::error_conv::{to_access_error, to_file_error, to_unformatted_disk_error, to_volume_error}; use crate::disk::error_conv::{to_access_error, to_file_error, to_unformatted_disk_error, to_volume_error};
use crate::disk::fs::{ use crate::disk::fs::{
@@ -36,16 +33,6 @@ use crate::disk::{
}; };
use crate::disk::{FileWriter, STORAGE_FORMAT_FILE}; use crate::disk::{FileWriter, STORAGE_FORMAT_FILE};
use crate::global::{GLOBAL_IsErasureSD, GLOBAL_RootDiskThreshold}; use crate::global::{GLOBAL_IsErasureSD, GLOBAL_RootDiskThreshold};
use crate::heal::data_scanner::{
ScannerItem, ShouldSleepFn, SizeSummary, lc_has_active_rules, rep_has_active_rules, scan_data_folder,
};
use crate::heal::data_usage_cache::{DataUsageCache, DataUsageEntry};
use crate::heal::error::{ERR_IGNORE_FILE_CONTRIB, ERR_SKIP_FILE};
use crate::heal::heal_commands::{HealScanMode, HealingTracker};
use crate::heal::heal_ops::HEALING_TRACKER_FILENAME;
use crate::new_object_layer_fn;
use crate::store_api::{ObjectInfo, StorageAPI};
use rustfs_common::metrics::{Metric, Metrics};
use rustfs_utils::path::{ use rustfs_utils::path::{
GLOBAL_DIR_SUFFIX, GLOBAL_DIR_SUFFIX_WITH_SLASH, SLASH_SEPARATOR, clean, decode_dir_object, encode_dir_object, has_suffix, GLOBAL_DIR_SUFFIX, GLOBAL_DIR_SUFFIX_WITH_SLASH, SLASH_SEPARATOR, clean, decode_dir_object, encode_dir_object, has_suffix,
path_join, path_join_buf, path_join, path_join_buf,
@@ -55,19 +42,18 @@ use tokio::time::interval;
use crate::erasure_coding::bitrot_verify; use crate::erasure_coding::bitrot_verify;
use bytes::Bytes; use bytes::Bytes;
use path_absolutize::Absolutize; use path_absolutize::Absolutize;
use rustfs_common::defer;
use rustfs_filemeta::{ use rustfs_filemeta::{
Cache, FileInfo, FileInfoOpts, FileMeta, MetaCacheEntry, MetacacheWriter, ObjectPartInfo, Opts, RawFileInfo, UpdateFn, Cache, FileInfo, FileInfoOpts, FileMeta, MetaCacheEntry, MetacacheWriter, ObjectPartInfo, Opts, RawFileInfo, UpdateFn,
get_file_info, read_xl_meta_no_data, get_file_info, read_xl_meta_no_data,
}; };
use rustfs_utils::HashAlgorithm; use rustfs_utils::HashAlgorithm;
use rustfs_utils::os::get_info; use rustfs_utils::os::get_info;
use std::collections::{HashMap, HashSet}; use std::collections::HashSet;
use std::fmt::Debug; use std::fmt::Debug;
use std::io::SeekFrom; use std::io::SeekFrom;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
use std::time::{Duration, SystemTime}; use std::time::Duration;
use std::{ use std::{
fs::Metadata, fs::Metadata,
path::{Path, PathBuf}, path::{Path, PathBuf},
@@ -76,7 +62,6 @@ use time::OffsetDateTime;
use tokio::fs::{self, File}; use tokio::fs::{self, File};
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWrite, AsyncWriteExt, ErrorKind}; use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWrite, AsyncWriteExt, ErrorKind};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tokio::sync::mpsc::Sender;
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use uuid::Uuid; use uuid::Uuid;
@@ -2268,184 +2253,6 @@ impl DiskAPI for LocalDisk {
Ok(info) Ok(info)
} }
#[tracing::instrument(level = "info", skip_all)]
async fn ns_scanner(
&self,
cache: &DataUsageCache,
updates: Sender<DataUsageEntry>,
scan_mode: HealScanMode,
we_sleep: ShouldSleepFn,
) -> Result<DataUsageCache> {
self.scanning.fetch_add(1, Ordering::SeqCst);
defer!(|| { self.scanning.fetch_sub(1, Ordering::SeqCst) });
// must before metadata_sys
let Some(store) = new_object_layer_fn() else {
return Err(Error::other("errServerNotInitialized"));
};
let mut cache = cache.clone();
// Check if the current bucket has a configured lifecycle policy
if let Ok((lc, _)) = metadata_sys::get_lifecycle_config(&cache.info.name).await {
if lc_has_active_rules(&lc, "") {
cache.info.lifecycle = Some(lc);
}
}
// Check if the current bucket has replication configuration
if let Ok((rcfg, _)) = metadata_sys::get_replication_config(&cache.info.name).await {
if rep_has_active_rules(&rcfg, "", true) {
// TODO: globalBucketTargetSys
}
}
let vcfg = BucketVersioningSys::get(&cache.info.name).await.ok();
let loc = self.get_disk_location();
// TODO: 这里需要处理错误
let disks = store
.get_disks(loc.pool_idx.unwrap(), loc.disk_idx.unwrap())
.await
.map_err(|e| Error::other(e.to_string()))?;
let disk = Arc::new(LocalDisk::new(&self.endpoint(), false).await?);
let disk_clone = disk.clone();
cache.info.updates = Some(updates.clone());
let mut data_usage_info = scan_data_folder(
&disks,
disk,
&cache,
Box::new(move |item: &ScannerItem| {
let mut item = item.clone();
let disk = disk_clone.clone();
let vcfg = vcfg.clone();
Box::pin(async move {
if !item.path.ends_with(&format!("{SLASH_SEPARATOR}{STORAGE_FORMAT_FILE}")) {
return Err(Error::other(ERR_SKIP_FILE).into());
}
let stop_fn = Metrics::log(Metric::ScanObject);
let mut res = HashMap::new();
let done_sz = Metrics::time_size(Metric::ReadMetadata);
let buf = match disk.read_metadata(item.path.clone()).await {
Ok(buf) => buf,
Err(err) => {
res.insert("err".to_string(), err.to_string());
stop_fn(&res);
return Err(Error::other(ERR_SKIP_FILE).into());
}
};
done_sz(buf.len() as u64);
res.insert("metasize".to_string(), buf.len().to_string());
item.transform_meta_dir();
let meta_cache = MetaCacheEntry {
name: item.object_path().to_string_lossy().to_string(),
metadata: buf,
..Default::default()
};
let fivs = match meta_cache.file_info_versions(&item.bucket) {
Ok(fivs) => fivs,
Err(err) => {
res.insert("err".to_string(), err.to_string());
stop_fn(&res);
return Err(Error::other(ERR_SKIP_FILE).into());
}
};
let mut size_s = SizeSummary::default();
let done = Metrics::time(Metric::ApplyAll);
let obj_infos = match item.apply_versions_actions(&fivs.versions).await {
Ok(obj_infos) => obj_infos,
Err(err) => {
res.insert("err".to_string(), err.to_string());
stop_fn(&res);
return Err(Error::other(ERR_SKIP_FILE).into());
}
};
let versioned = if let Some(vcfg) = vcfg.as_ref() {
vcfg.versioned(item.object_path().to_str().unwrap_or_default())
} else {
false
};
let mut obj_deleted = false;
for info in obj_infos.iter() {
let done = Metrics::time(Metric::ApplyVersion);
let sz: i64;
(obj_deleted, sz) = item.apply_actions(info, &mut size_s).await;
done();
if obj_deleted {
break;
}
let actual_sz = match info.get_actual_size() {
Ok(size) => size,
Err(_) => continue,
};
if info.delete_marker {
size_s.delete_markers += 1;
}
if info.version_id.is_some() && sz == actual_sz {
size_s.versions += 1;
}
size_s.total_size += sz as usize;
if info.delete_marker {
continue;
}
}
for free_version in fivs.free_versions.iter() {
let _obj_info = ObjectInfo::from_file_info(
free_version,
&item.bucket,
&item.object_path().to_string_lossy(),
versioned,
);
let done = Metrics::time(Metric::TierObjSweep);
done();
}
// todo: global trace
if obj_deleted {
return Err(Error::other(ERR_IGNORE_FILE_CONTRIB).into());
}
done();
Ok(size_s)
})
}),
scan_mode,
we_sleep,
)
.await?;
data_usage_info.info.last_update = Some(SystemTime::now());
debug!("ns_scanner completed: {data_usage_info:?}");
Ok(data_usage_info)
}
#[tracing::instrument(skip(self))]
async fn healing(&self) -> Option<HealingTracker> {
let healing_file = path_join(&[
self.path(),
PathBuf::from(RUSTFS_META_BUCKET),
PathBuf::from(BUCKET_META_PREFIX),
PathBuf::from(HEALING_TRACKER_FILENAME),
]);
let b = match fs::read(healing_file).await {
Ok(b) => b,
Err(_) => return None,
};
if b.is_empty() {
return None;
}
match HealingTracker::unmarshal_msg(&b) {
Ok(h) => Some(h),
Err(_) => Some(HealingTracker::default()),
}
}
} }
async fn get_disk_info(drive_path: PathBuf) -> Result<(rustfs_utils::os::DiskInfo, bool)> { async fn get_disk_info(drive_path: PathBuf) -> Result<(rustfs_utils::os::DiskInfo, bool)> {
+5 -65
View File
@@ -21,9 +21,9 @@ use rustfs_protos::{
node_service_time_out_client, node_service_time_out_client,
proto_gen::node_service::{ proto_gen::node_service::{
CheckPartsRequest, DeletePathsRequest, DeleteRequest, DeleteVersionRequest, DeleteVersionsRequest, DeleteVolumeRequest, CheckPartsRequest, DeletePathsRequest, DeleteRequest, DeleteVersionRequest, DeleteVersionsRequest, DeleteVolumeRequest,
DiskInfoRequest, ListDirRequest, ListVolumesRequest, MakeVolumeRequest, MakeVolumesRequest, NsScannerRequest, DiskInfoRequest, ListDirRequest, ListVolumesRequest, MakeVolumeRequest, MakeVolumesRequest, ReadAllRequest,
ReadAllRequest, ReadMultipleRequest, ReadPartsRequest, ReadVersionRequest, ReadXlRequest, RenameDataRequest, ReadMultipleRequest, ReadPartsRequest, ReadVersionRequest, ReadXlRequest, RenameDataRequest, RenameFileRequest,
RenameFileRequest, StatVolumeRequest, UpdateMetadataRequest, VerifyFileRequest, WriteAllRequest, WriteMetadataRequest, StatVolumeRequest, UpdateMetadataRequest, VerifyFileRequest, WriteAllRequest, WriteMetadataRequest,
}, },
}; };
@@ -32,26 +32,15 @@ use crate::disk::{
ReadMultipleReq, ReadMultipleResp, ReadOptions, RenameDataResp, UpdateMetadataOpts, VolumeInfo, WalkDirOptions, ReadMultipleReq, ReadMultipleResp, ReadOptions, RenameDataResp, UpdateMetadataOpts, VolumeInfo, WalkDirOptions,
endpoint::Endpoint, endpoint::Endpoint,
}; };
use crate::disk::{FileReader, FileWriter};
use crate::{ use crate::{
disk::error::{Error, Result}, disk::error::{Error, Result},
rpc::build_auth_headers, rpc::build_auth_headers,
}; };
use crate::{
disk::{FileReader, FileWriter},
heal::{
data_scanner::ShouldSleepFn,
data_usage_cache::{DataUsageCache, DataUsageEntry},
heal_commands::{HealScanMode, HealingTracker},
},
};
use rustfs_filemeta::{FileInfo, ObjectPartInfo, RawFileInfo}; use rustfs_filemeta::{FileInfo, ObjectPartInfo, RawFileInfo};
use rustfs_protos::proto_gen::node_service::RenamePartRequest; use rustfs_protos::proto_gen::node_service::RenamePartRequest;
use rustfs_rio::{HttpReader, HttpWriter}; use rustfs_rio::{HttpReader, HttpWriter};
use tokio::{ use tokio::io::AsyncWrite;
io::AsyncWrite,
sync::mpsc::{self, Sender},
};
use tokio_stream::{StreamExt, wrappers::ReceiverStream};
use tonic::Request; use tonic::Request;
use tracing::info; use tracing::info;
use uuid::Uuid; use uuid::Uuid;
@@ -927,55 +916,6 @@ impl DiskAPI for RemoteDisk {
Ok(disk_info) Ok(disk_info)
} }
#[tracing::instrument(skip(self, cache, scan_mode, _we_sleep))]
async fn ns_scanner(
&self,
cache: &DataUsageCache,
updates: Sender<DataUsageEntry>,
scan_mode: HealScanMode,
_we_sleep: ShouldSleepFn,
) -> Result<DataUsageCache> {
info!("ns_scanner");
let cache = serde_json::to_string(cache)?;
let mut client = node_service_time_out_client(&self.addr)
.await
.map_err(|err| Error::other(format!("can not get client, err: {err}")))?;
let (tx, rx) = mpsc::channel(10);
let in_stream = ReceiverStream::new(rx);
let mut response = client.ns_scanner(in_stream).await?.into_inner();
let request = NsScannerRequest {
disk: self.endpoint.to_string(),
cache,
scan_mode: scan_mode as u64,
};
tx.send(request)
.await
.map_err(|err| Error::other(format!("can not send request, err: {err}")))?;
loop {
match response.next().await {
Some(Ok(resp)) => {
if !resp.update.is_empty() {
let data_usage_cache = serde_json::from_str::<DataUsageEntry>(&resp.update)?;
let _ = updates.send(data_usage_cache).await;
} else if !resp.data_usage_cache.is_empty() {
let data_usage_cache = serde_json::from_str::<DataUsageCache>(&resp.data_usage_cache)?;
return Ok(data_usage_cache);
} else {
return Err(Error::other("scan was interrupted"));
}
}
_ => return Err(Error::other("scan was interrupted")),
}
}
}
#[tracing::instrument(skip(self))]
async fn healing(&self) -> Option<HealingTracker> {
None
}
} }
#[cfg(test)] #[cfg(test)]
File diff suppressed because it is too large Load Diff
+1
View File
@@ -970,6 +970,7 @@ pub trait StorageAPI: ObjectIO {
// Walk TODO: // Walk TODO:
async fn get_object_info(&self, bucket: &str, object: &str, opts: &ObjectOptions) -> Result<ObjectInfo>; async fn get_object_info(&self, bucket: &str, object: &str, opts: &ObjectOptions) -> Result<ObjectInfo>;
async fn verify_object_integrity(&self, bucket: &str, object: &str, opts: &ObjectOptions) -> Result<()>;
async fn copy_object( async fn copy_object(
&self, &self,
src_bucket: &str, src_bucket: &str,
+1
View File
@@ -54,6 +54,7 @@ use rustfs_iam::init_iam_sys;
use rustfs_obs::{init_obs, set_global_guard}; use rustfs_obs::{init_obs, set_global_guard};
use rustfs_utils::net::parse_and_resolve_address; use rustfs_utils::net::parse_and_resolve_address;
use std::io::{Error, Result}; use std::io::{Error, Result};
use std::sync::Arc;
use tracing::{debug, error, info, instrument, warn}; use tracing::{debug, error, info, instrument, warn};
#[cfg(all(target_os = "linux", target_env = "gnu"))] #[cfg(all(target_os = "linux", target_env = "gnu"))]