mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-06 12:09:12 +00:00
0e6ee3bf62
* test(scanner): wire usage and heal rebuild gates * docs(scanner): define usage authority protocol * docs(heal): clarify scanner and ecstore boundaries * refactor(scanner): split metrics from contracts * feat(scanner): use shared workload snapshots * fix(ecstore): recheck capacity before decommission drain
166 lines
5.7 KiB
Rust
166 lines
5.7 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.
|
|
|
|
use std::sync::{Arc, LazyLock, RwLock};
|
|
|
|
use rustfs_concurrency::{AdmissionState, WorkloadAdmissionRegistrySnapshot, WorkloadAdmissionSnapshotProvider, WorkloadClass};
|
|
|
|
type WorkloadSnapshotProviderRef = Arc<dyn WorkloadAdmissionSnapshotProvider + Send + Sync>;
|
|
|
|
static SCANNER_WORKLOAD_ADMISSION_PROVIDER: LazyLock<RwLock<Option<WorkloadSnapshotProviderRef>>> =
|
|
LazyLock::new(|| RwLock::new(None));
|
|
|
|
pub fn set_scanner_workload_admission_snapshot_provider(provider: WorkloadSnapshotProviderRef) {
|
|
*SCANNER_WORKLOAD_ADMISSION_PROVIDER
|
|
.write()
|
|
.unwrap_or_else(|err| err.into_inner()) = Some(provider);
|
|
}
|
|
|
|
fn scanner_workload_admission_snapshot_provider() -> Option<WorkloadSnapshotProviderRef> {
|
|
SCANNER_WORKLOAD_ADMISSION_PROVIDER
|
|
.read()
|
|
.unwrap_or_else(|err| err.into_inner())
|
|
.clone()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn clear_scanner_workload_admission_snapshot_provider_for_test() {
|
|
*SCANNER_WORKLOAD_ADMISSION_PROVIDER
|
|
.write()
|
|
.unwrap_or_else(|err| err.into_inner()) = None;
|
|
}
|
|
|
|
pub(crate) fn foreground_workload_activity() -> u64 {
|
|
let local_activity = crate::current_foreground_read_activity();
|
|
let Some(provider) = scanner_workload_admission_snapshot_provider() else {
|
|
return local_activity;
|
|
};
|
|
|
|
local_activity.max(foreground_activity_from_snapshot(&provider.workload_admission_snapshot()))
|
|
}
|
|
|
|
fn foreground_activity_from_snapshot(snapshot: &WorkloadAdmissionRegistrySnapshot) -> u64 {
|
|
[WorkloadClass::ForegroundRead, WorkloadClass::ForegroundWrite]
|
|
.into_iter()
|
|
.filter_map(|class| snapshot.get(class))
|
|
.map(|entry| {
|
|
entry
|
|
.active
|
|
.or_else(|| {
|
|
matches!(entry.state, AdmissionState::Saturated).then(|| entry.limit.filter(|limit| *limit > 0).unwrap_or(1))
|
|
})
|
|
.map(usize_to_u64_saturated)
|
|
.unwrap_or(0)
|
|
})
|
|
.max()
|
|
.unwrap_or(0)
|
|
}
|
|
|
|
fn usize_to_u64_saturated(value: usize) -> u64 {
|
|
u64::try_from(value).unwrap_or(u64::MAX)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use rustfs_concurrency::{AdmissionState, WorkloadAdmissionSnapshot};
|
|
use serial_test::serial;
|
|
|
|
#[derive(Clone)]
|
|
struct FixedWorkloadProvider {
|
|
snapshot: WorkloadAdmissionRegistrySnapshot,
|
|
}
|
|
|
|
impl WorkloadAdmissionSnapshotProvider for FixedWorkloadProvider {
|
|
fn workload_admission_snapshot(&self) -> WorkloadAdmissionRegistrySnapshot {
|
|
self.snapshot.clone()
|
|
}
|
|
}
|
|
|
|
fn install_provider(snapshot: WorkloadAdmissionRegistrySnapshot) {
|
|
set_scanner_workload_admission_snapshot_provider(Arc::new(FixedWorkloadProvider { snapshot }));
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn foreground_workload_activity_falls_back_to_local_read_activity() {
|
|
clear_scanner_workload_admission_snapshot_provider_for_test();
|
|
crate::reset_foreground_read_activity_for_test();
|
|
crate::set_foreground_read_activity(3);
|
|
|
|
assert_eq!(foreground_workload_activity(), 3);
|
|
|
|
crate::reset_foreground_read_activity_for_test();
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn foreground_workload_activity_uses_shared_provider_counts() {
|
|
clear_scanner_workload_admission_snapshot_provider_for_test();
|
|
crate::reset_foreground_read_activity_for_test();
|
|
install_provider(WorkloadAdmissionRegistrySnapshot::new(vec![
|
|
WorkloadAdmissionSnapshot::new(WorkloadClass::ForegroundRead, AdmissionState::Open).with_counts(
|
|
Some(5),
|
|
None,
|
|
Some(8),
|
|
),
|
|
WorkloadAdmissionSnapshot::new(WorkloadClass::ForegroundWrite, AdmissionState::Open).with_counts(
|
|
Some(2),
|
|
None,
|
|
Some(4),
|
|
),
|
|
]));
|
|
|
|
assert_eq!(foreground_workload_activity(), 5);
|
|
|
|
clear_scanner_workload_admission_snapshot_provider_for_test();
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn foreground_workload_activity_treats_saturation_without_counts_as_pressure() {
|
|
clear_scanner_workload_admission_snapshot_provider_for_test();
|
|
crate::reset_foreground_read_activity_for_test();
|
|
install_provider(WorkloadAdmissionRegistrySnapshot::new(vec![
|
|
WorkloadAdmissionSnapshot::new(WorkloadClass::ForegroundRead, AdmissionState::Saturated).with_counts(
|
|
None,
|
|
None,
|
|
Some(7),
|
|
),
|
|
]));
|
|
|
|
assert_eq!(foreground_workload_activity(), 7);
|
|
|
|
clear_scanner_workload_admission_snapshot_provider_for_test();
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn foreground_workload_activity_treats_zero_limit_saturation_as_pressure() {
|
|
clear_scanner_workload_admission_snapshot_provider_for_test();
|
|
crate::reset_foreground_read_activity_for_test();
|
|
install_provider(WorkloadAdmissionRegistrySnapshot::new(vec![
|
|
WorkloadAdmissionSnapshot::new(WorkloadClass::ForegroundWrite, AdmissionState::Saturated).with_counts(
|
|
None,
|
|
None,
|
|
Some(0),
|
|
),
|
|
]));
|
|
|
|
assert_eq!(foreground_workload_activity(), 1);
|
|
|
|
clear_scanner_workload_admission_snapshot_provider_for_test();
|
|
}
|
|
}
|