mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-31 17:28:12 +00:00
Expose replication backlog gauges (#5557)
* fix(replication): count backlog at queue admission Co-Authored-By: heihutu <heihutu@gmail.com> * feat(obs): expose bucket replication backlog gauges Co-Authored-By: heihutu <heihutu@gmail.com> * fix(obs): report recent backlog from queued work Co-Authored-By: heihutu <heihutu@gmail.com> * fix(obs): preserve legacy backlog metric semantics Co-Authored-By: heihutu <heihutu@gmail.com> * feat(obs): expose durable MRF backlog gauges Co-Authored-By: heihutu <heihutu@gmail.com> * test(obs): cover replication backlog metric scope Co-Authored-By: heihutu <heihutu@gmail.com> * fix(obs): keep backlog metrics API-compatible Co-Authored-By: heihutu <heihutu@gmail.com> * refactor(obs): streamline replication backlog metrics Keep MRF backlog accounting and OBS metric collection on a single, cheaper path. Co-Authored-By: heihutu <heihutu@gmail.com> * test(kms): update aws capability snapshot Co-Authored-By: heihutu <heihutu@gmail.com> --------- Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -12,11 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
pub(crate) use rustfs_ecstore::api::bucket::bandwidth::monitor::Monitor as ObsBucketBandwidthMonitor;
|
||||
pub(crate) use rustfs_ecstore::api::bucket::metadata_sys::get_quota_config as obs_get_quota_config;
|
||||
use rustfs_ecstore::api::bucket::replication::get_global_replication_stats;
|
||||
use rustfs_ecstore::api::bucket::replication::{
|
||||
DurableMrfBucketBacklog, durable_mrf_backlog_summary_snapshot, get_global_replication_stats,
|
||||
};
|
||||
pub(crate) use rustfs_ecstore::api::capacity::{
|
||||
get_total_usable_capacity as obs_get_total_usable_capacity,
|
||||
get_total_usable_capacity_free as obs_get_total_usable_capacity_free,
|
||||
@@ -68,9 +71,50 @@ pub(crate) struct ObsBucketReplicationStatsSnapshot {
|
||||
pub(crate) resync_failed_count: u64,
|
||||
pub(crate) resync_canceled_count: u64,
|
||||
pub(crate) resync_duration_ms: u64,
|
||||
pub(crate) current_backlog_count: u64,
|
||||
pub(crate) current_backlog_bytes: u64,
|
||||
pub(crate) durable_mrf_available: bool,
|
||||
pub(crate) durable_mrf_backlog_count: u64,
|
||||
pub(crate) durable_mrf_backlog_bytes: u64,
|
||||
pub(crate) targets: Vec<ObsBucketReplicationTargetStatsSnapshot>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct ObsBucketReplicationRuntimeSnapshot {
|
||||
total_failed_bytes: u64,
|
||||
total_failed_count: u64,
|
||||
last_min_failed_bytes: u64,
|
||||
last_min_failed_count: u64,
|
||||
last_hour_failed_bytes: u64,
|
||||
last_hour_failed_count: u64,
|
||||
sent_bytes: u64,
|
||||
sent_count: u64,
|
||||
resync_started_count: u64,
|
||||
resync_completed_count: u64,
|
||||
resync_failed_count: u64,
|
||||
resync_canceled_count: u64,
|
||||
resync_duration_ms: u64,
|
||||
current_backlog_count: u64,
|
||||
current_backlog_bytes: u64,
|
||||
targets: Vec<ObsBucketReplicationTargetStatsSnapshot>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct ObsBucketReplicationProxySnapshot {
|
||||
proxied_get_requests_total: u64,
|
||||
proxied_get_requests_failures: u64,
|
||||
proxied_head_requests_total: u64,
|
||||
proxied_head_requests_failures: u64,
|
||||
proxied_put_requests_total: u64,
|
||||
proxied_put_requests_failures: u64,
|
||||
proxied_put_tagging_requests_total: u64,
|
||||
proxied_put_tagging_requests_failures: u64,
|
||||
proxied_get_tagging_requests_total: u64,
|
||||
proxied_get_tagging_requests_failures: u64,
|
||||
proxied_delete_tagging_requests_total: u64,
|
||||
proxied_delete_tagging_requests_failures: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub(crate) struct ObsReplicationSiteStatsSnapshot {
|
||||
pub(crate) average_active_workers: f64,
|
||||
@@ -102,59 +146,85 @@ fn replication_backlog_count(failed_counts: impl Iterator<Item = i64>, queued_co
|
||||
failed_backlog.saturating_add(i64_to_u64_floor_zero(queued_count))
|
||||
}
|
||||
|
||||
fn bucket_replication_stats_snapshot_from_parts(
|
||||
bucket: String,
|
||||
runtime: ObsBucketReplicationRuntimeSnapshot,
|
||||
proxy: ObsBucketReplicationProxySnapshot,
|
||||
durable_mrf_available: bool,
|
||||
durable_bucket: DurableMrfBucketBacklog,
|
||||
) -> ObsBucketReplicationStatsSnapshot {
|
||||
ObsBucketReplicationStatsSnapshot {
|
||||
bucket,
|
||||
total_failed_bytes: runtime.total_failed_bytes,
|
||||
total_failed_count: runtime.total_failed_count,
|
||||
last_min_failed_bytes: runtime.last_min_failed_bytes,
|
||||
last_min_failed_count: runtime.last_min_failed_count,
|
||||
last_hour_failed_bytes: runtime.last_hour_failed_bytes,
|
||||
last_hour_failed_count: runtime.last_hour_failed_count,
|
||||
sent_bytes: runtime.sent_bytes,
|
||||
sent_count: runtime.sent_count,
|
||||
proxied_get_requests_total: proxy.proxied_get_requests_total,
|
||||
proxied_get_requests_failures: proxy.proxied_get_requests_failures,
|
||||
proxied_head_requests_total: proxy.proxied_head_requests_total,
|
||||
proxied_head_requests_failures: proxy.proxied_head_requests_failures,
|
||||
proxied_put_requests_total: proxy.proxied_put_requests_total,
|
||||
proxied_put_requests_failures: proxy.proxied_put_requests_failures,
|
||||
proxied_put_tagging_requests_total: proxy.proxied_put_tagging_requests_total,
|
||||
proxied_put_tagging_requests_failures: proxy.proxied_put_tagging_requests_failures,
|
||||
proxied_get_tagging_requests_total: proxy.proxied_get_tagging_requests_total,
|
||||
proxied_get_tagging_requests_failures: proxy.proxied_get_tagging_requests_failures,
|
||||
proxied_delete_tagging_requests_total: proxy.proxied_delete_tagging_requests_total,
|
||||
proxied_delete_tagging_requests_failures: proxy.proxied_delete_tagging_requests_failures,
|
||||
resync_started_count: runtime.resync_started_count,
|
||||
resync_completed_count: runtime.resync_completed_count,
|
||||
resync_failed_count: runtime.resync_failed_count,
|
||||
resync_canceled_count: runtime.resync_canceled_count,
|
||||
resync_duration_ms: runtime.resync_duration_ms,
|
||||
current_backlog_count: runtime.current_backlog_count,
|
||||
current_backlog_bytes: runtime.current_backlog_bytes,
|
||||
durable_mrf_available,
|
||||
durable_mrf_backlog_count: durable_bucket.count,
|
||||
durable_mrf_backlog_bytes: durable_bucket.bytes,
|
||||
targets: runtime.targets,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn obs_bucket_replication_stats_snapshot() -> Vec<ObsBucketReplicationStatsSnapshot> {
|
||||
let Some(stats) = get_global_replication_stats() else {
|
||||
return Vec::new();
|
||||
let stats = get_global_replication_stats();
|
||||
let all_bucket_stats = if let Some(stats) = &stats {
|
||||
stats.get_all().await
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
let durable_mrf_summary = if obs_resolve_object_store_handle().is_some() {
|
||||
durable_mrf_backlog_summary_snapshot()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let durable_mrf_available = durable_mrf_summary.available;
|
||||
let durable_buckets = durable_mrf_summary
|
||||
.buckets
|
||||
.into_iter()
|
||||
.map(|bucket| (bucket.bucket.clone(), bucket))
|
||||
.collect::<HashMap<String, DurableMrfBucketBacklog>>();
|
||||
let mut bucket_names = Vec::with_capacity(all_bucket_stats.len().saturating_add(durable_buckets.len()));
|
||||
bucket_names.extend(all_bucket_stats.keys().cloned());
|
||||
bucket_names.extend(
|
||||
durable_buckets
|
||||
.keys()
|
||||
.filter(|bucket| !all_bucket_stats.contains_key(*bucket))
|
||||
.cloned(),
|
||||
);
|
||||
let mut buckets = Vec::with_capacity(bucket_names.len());
|
||||
|
||||
let all_bucket_stats = stats.get_all().await;
|
||||
let mut buckets = Vec::with_capacity(all_bucket_stats.len());
|
||||
|
||||
for (bucket, bucket_stats) in all_bucket_stats {
|
||||
let proxy = stats.get_proxy_stats(&bucket).await;
|
||||
let mut total_failed_bytes = 0u64;
|
||||
let mut total_failed_count = 0u64;
|
||||
let mut last_min_failed_bytes = 0u64;
|
||||
let mut last_min_failed_count = 0u64;
|
||||
let mut last_hour_failed_bytes = 0u64;
|
||||
let mut last_hour_failed_count = 0u64;
|
||||
let mut sent_bytes = 0u64;
|
||||
let mut sent_count = 0u64;
|
||||
let mut targets = Vec::with_capacity(bucket_stats.stats.len());
|
||||
|
||||
for (target_arn, target_stats) in bucket_stats.stats {
|
||||
total_failed_bytes = total_failed_bytes.saturating_add(i64_to_u64_floor_zero(target_stats.fail_stats.size));
|
||||
total_failed_count = total_failed_count.saturating_add(i64_to_u64_floor_zero(target_stats.fail_stats.count));
|
||||
|
||||
let last_min = target_stats.fail_stats.recent_since(Duration::from_secs(60));
|
||||
last_min_failed_bytes = last_min_failed_bytes.saturating_add(i64_to_u64_floor_zero(last_min.size));
|
||||
last_min_failed_count = last_min_failed_count.saturating_add(i64_to_u64_floor_zero(last_min.count));
|
||||
|
||||
let last_hour = target_stats.fail_stats.recent_since(Duration::from_secs(60 * 60));
|
||||
last_hour_failed_bytes = last_hour_failed_bytes.saturating_add(i64_to_u64_floor_zero(last_hour.size));
|
||||
last_hour_failed_count = last_hour_failed_count.saturating_add(i64_to_u64_floor_zero(last_hour.count));
|
||||
|
||||
sent_bytes = sent_bytes.saturating_add(i64_to_u64_floor_zero(target_stats.replicated_size));
|
||||
sent_count = sent_count.saturating_add(i64_to_u64_floor_zero(target_stats.replicated_count));
|
||||
|
||||
targets.push(ObsBucketReplicationTargetStatsSnapshot {
|
||||
target_arn,
|
||||
bandwidth_limit_bytes_per_sec: i64_to_u64_floor_zero(target_stats.bandwidth_limit_bytes_per_sec),
|
||||
current_bandwidth_bytes_per_sec: target_stats.current_bandwidth_bytes_per_sec,
|
||||
latency_ms: target_stats.latency.curr,
|
||||
});
|
||||
}
|
||||
|
||||
buckets.push(ObsBucketReplicationStatsSnapshot {
|
||||
bucket,
|
||||
total_failed_bytes,
|
||||
total_failed_count,
|
||||
last_min_failed_bytes,
|
||||
last_min_failed_count,
|
||||
last_hour_failed_bytes,
|
||||
last_hour_failed_count,
|
||||
sent_bytes,
|
||||
sent_count,
|
||||
for bucket in bucket_names {
|
||||
let bucket_stats = all_bucket_stats.get(&bucket);
|
||||
let proxy = if let Some(stats) = &stats {
|
||||
stats.get_proxy_stats(&bucket).await
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let proxy = ObsBucketReplicationProxySnapshot {
|
||||
proxied_get_requests_total: i64_to_u64_floor_zero(proxy.get_total),
|
||||
proxied_get_requests_failures: i64_to_u64_floor_zero(proxy.get_failed),
|
||||
proxied_head_requests_total: i64_to_u64_floor_zero(proxy.head_total),
|
||||
@@ -167,13 +237,67 @@ pub(crate) async fn obs_bucket_replication_stats_snapshot() -> Vec<ObsBucketRepl
|
||||
proxied_get_tagging_requests_failures: i64_to_u64_floor_zero(proxy.get_tag_failed),
|
||||
proxied_delete_tagging_requests_total: i64_to_u64_floor_zero(proxy.delete_tag_total),
|
||||
proxied_delete_tagging_requests_failures: i64_to_u64_floor_zero(proxy.delete_tag_failed),
|
||||
resync_started_count: i64_to_u64_floor_zero(bucket_stats.resync_started_count),
|
||||
resync_completed_count: i64_to_u64_floor_zero(bucket_stats.resync_completed_count),
|
||||
resync_failed_count: i64_to_u64_floor_zero(bucket_stats.resync_failed_count),
|
||||
resync_canceled_count: i64_to_u64_floor_zero(bucket_stats.resync_canceled_count),
|
||||
resync_duration_ms: i64_to_u64_floor_zero(bucket_stats.resync_duration_ms),
|
||||
targets,
|
||||
});
|
||||
};
|
||||
let mut runtime = ObsBucketReplicationRuntimeSnapshot {
|
||||
targets: Vec::with_capacity(bucket_stats.map(|stats| stats.stats.len()).unwrap_or(0)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(bucket_stats) = bucket_stats {
|
||||
for (target_arn, target_stats) in &bucket_stats.stats {
|
||||
runtime.total_failed_bytes = runtime
|
||||
.total_failed_bytes
|
||||
.saturating_add(i64_to_u64_floor_zero(target_stats.fail_stats.size));
|
||||
runtime.total_failed_count = runtime
|
||||
.total_failed_count
|
||||
.saturating_add(i64_to_u64_floor_zero(target_stats.fail_stats.count));
|
||||
|
||||
let last_min = target_stats.fail_stats.recent_since(Duration::from_secs(60));
|
||||
runtime.last_min_failed_bytes = runtime
|
||||
.last_min_failed_bytes
|
||||
.saturating_add(i64_to_u64_floor_zero(last_min.size));
|
||||
runtime.last_min_failed_count = runtime
|
||||
.last_min_failed_count
|
||||
.saturating_add(i64_to_u64_floor_zero(last_min.count));
|
||||
|
||||
let last_hour = target_stats.fail_stats.recent_since(Duration::from_secs(60 * 60));
|
||||
runtime.last_hour_failed_bytes = runtime
|
||||
.last_hour_failed_bytes
|
||||
.saturating_add(i64_to_u64_floor_zero(last_hour.size));
|
||||
runtime.last_hour_failed_count = runtime
|
||||
.last_hour_failed_count
|
||||
.saturating_add(i64_to_u64_floor_zero(last_hour.count));
|
||||
|
||||
runtime.sent_bytes = runtime
|
||||
.sent_bytes
|
||||
.saturating_add(i64_to_u64_floor_zero(target_stats.replicated_size));
|
||||
runtime.sent_count = runtime
|
||||
.sent_count
|
||||
.saturating_add(i64_to_u64_floor_zero(target_stats.replicated_count));
|
||||
|
||||
runtime.targets.push(ObsBucketReplicationTargetStatsSnapshot {
|
||||
target_arn: target_arn.clone(),
|
||||
bandwidth_limit_bytes_per_sec: i64_to_u64_floor_zero(target_stats.bandwidth_limit_bytes_per_sec),
|
||||
current_bandwidth_bytes_per_sec: target_stats.current_bandwidth_bytes_per_sec,
|
||||
latency_ms: target_stats.latency.curr,
|
||||
});
|
||||
}
|
||||
runtime.resync_started_count = i64_to_u64_floor_zero(bucket_stats.resync_started_count);
|
||||
runtime.resync_completed_count = i64_to_u64_floor_zero(bucket_stats.resync_completed_count);
|
||||
runtime.resync_failed_count = i64_to_u64_floor_zero(bucket_stats.resync_failed_count);
|
||||
runtime.resync_canceled_count = i64_to_u64_floor_zero(bucket_stats.resync_canceled_count);
|
||||
runtime.resync_duration_ms = i64_to_u64_floor_zero(bucket_stats.resync_duration_ms);
|
||||
runtime.current_backlog_count = i64_to_u64_floor_zero(bucket_stats.q_stat.curr.count);
|
||||
runtime.current_backlog_bytes = i64_to_u64_floor_zero(bucket_stats.q_stat.curr.bytes);
|
||||
}
|
||||
let durable_bucket = durable_buckets.get(&bucket).cloned().unwrap_or_default();
|
||||
buckets.push(bucket_replication_stats_snapshot_from_parts(
|
||||
bucket,
|
||||
runtime,
|
||||
proxy,
|
||||
durable_mrf_available,
|
||||
durable_bucket,
|
||||
));
|
||||
}
|
||||
|
||||
buckets
|
||||
@@ -243,15 +367,97 @@ mod tests {
|
||||
fn replication_backlog_count_floors_negative_failed_and_queue_values() {
|
||||
assert_eq!(replication_backlog_count([-3, 4].into_iter(), -2), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replication_backlog_count_keeps_legacy_failed_backlog_semantics() {
|
||||
assert_eq!(replication_backlog_count([9].into_iter(), 0), 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bucket_replication_snapshot_maps_runtime_and_durable_backlog() {
|
||||
let snapshot = bucket_replication_stats_snapshot_from_parts(
|
||||
"runtime-bucket".to_string(),
|
||||
ObsBucketReplicationRuntimeSnapshot {
|
||||
current_backlog_count: 3,
|
||||
current_backlog_bytes: 4096,
|
||||
resync_failed_count: 2,
|
||||
..Default::default()
|
||||
},
|
||||
ObsBucketReplicationProxySnapshot {
|
||||
proxied_get_requests_total: 7,
|
||||
proxied_get_requests_failures: 1,
|
||||
..Default::default()
|
||||
},
|
||||
true,
|
||||
DurableMrfBucketBacklog {
|
||||
bucket: "runtime-bucket".to_string(),
|
||||
count: 5,
|
||||
bytes: 8192,
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(snapshot.bucket, "runtime-bucket");
|
||||
assert_eq!(snapshot.current_backlog_count, 3);
|
||||
assert_eq!(snapshot.current_backlog_bytes, 4096);
|
||||
assert!(snapshot.durable_mrf_available);
|
||||
assert_eq!(snapshot.durable_mrf_backlog_count, 5);
|
||||
assert_eq!(snapshot.durable_mrf_backlog_bytes, 8192);
|
||||
assert_eq!(snapshot.resync_failed_count, 2);
|
||||
assert_eq!(snapshot.proxied_get_requests_total, 7);
|
||||
assert_eq!(snapshot.proxied_get_requests_failures, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bucket_replication_snapshot_reports_durable_only_bucket() {
|
||||
let snapshot = bucket_replication_stats_snapshot_from_parts(
|
||||
"durable-only".to_string(),
|
||||
ObsBucketReplicationRuntimeSnapshot::default(),
|
||||
ObsBucketReplicationProxySnapshot::default(),
|
||||
true,
|
||||
DurableMrfBucketBacklog {
|
||||
bucket: "durable-only".to_string(),
|
||||
count: 11,
|
||||
bytes: 2048,
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(snapshot.bucket, "durable-only");
|
||||
assert_eq!(snapshot.current_backlog_count, 0);
|
||||
assert!(snapshot.durable_mrf_available);
|
||||
assert_eq!(snapshot.durable_mrf_backlog_count, 11);
|
||||
assert_eq!(snapshot.durable_mrf_backlog_bytes, 2048);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bucket_replication_snapshot_preserves_durable_mrf_unavailable_state() {
|
||||
let snapshot = bucket_replication_stats_snapshot_from_parts(
|
||||
"runtime-only".to_string(),
|
||||
ObsBucketReplicationRuntimeSnapshot {
|
||||
current_backlog_count: 1,
|
||||
current_backlog_bytes: 512,
|
||||
..Default::default()
|
||||
},
|
||||
ObsBucketReplicationProxySnapshot::default(),
|
||||
false,
|
||||
DurableMrfBucketBacklog::default(),
|
||||
);
|
||||
|
||||
assert_eq!(snapshot.current_backlog_count, 1);
|
||||
assert_eq!(snapshot.current_backlog_bytes, 512);
|
||||
assert!(!snapshot.durable_mrf_available);
|
||||
assert_eq!(snapshot.durable_mrf_backlog_count, 0);
|
||||
assert_eq!(snapshot.durable_mrf_backlog_bytes, 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod metrics {
|
||||
pub(crate) use super::storage_contracts::{BucketOperations, BucketOptions, StorageAdminApi};
|
||||
|
||||
pub(crate) use super::{
|
||||
ObsBucketBandwidthMonitor, ObsEcstoreResult, ObsStore, obs_bucket_replication_stats_snapshot, obs_expiry_state_handle,
|
||||
obs_get_global_bucket_monitor, obs_get_quota_config, obs_get_total_usable_capacity, obs_get_total_usable_capacity_free,
|
||||
obs_is_disk_compression_enabled, obs_load_compression_total_from_memory, obs_load_data_usage_from_backend,
|
||||
obs_replication_site_stats_snapshot, obs_resolve_object_store_handle, obs_transition_state_handle,
|
||||
ObsBucketBandwidthMonitor, ObsBucketReplicationStatsSnapshot, ObsEcstoreResult, ObsStore,
|
||||
obs_bucket_replication_stats_snapshot, obs_expiry_state_handle, obs_get_global_bucket_monitor, obs_get_quota_config,
|
||||
obs_get_total_usable_capacity, obs_get_total_usable_capacity_free, obs_is_disk_compression_enabled,
|
||||
obs_load_compression_total_from_memory, obs_load_data_usage_from_backend, obs_replication_site_stats_snapshot,
|
||||
obs_resolve_object_store_handle, obs_transition_state_handle,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user