feat(ecstore): expose rename sync tail metrics (#6257)

Add default-off PUT stage helpers for fdatasync batch shape and rename quorum fanout shape so #925 follow-up probes can distinguish shard sync batching opportunities from fanout convergence.

Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
houseme
2026-08-19 14:26:43 +08:00
committed by GitHub
parent 05e6dc5f4a
commit 07cef6789b
3 changed files with 137 additions and 0 deletions
+8
View File
@@ -1098,6 +1098,10 @@ pub(crate) async fn sync_dir_files_with_limiter(dir: impl AsRef<Path>, disk_perm
let files = run_file_sync_blocking(disk_permits.clone(), move || { let files = run_file_sync_blocking(disk_permits.clone(), move || {
let files = regular_files(&scan_dir)?; let files = regular_files(&scan_dir)?;
if files.len() < PARALLEL_FILE_SYNC_THRESHOLD { if files.len() < PARALLEL_FILE_SYNC_THRESHOLD {
rustfs_io_metrics::record_put_rename_fdatasync_batch(
rustfs_io_metrics::PUT_RENAME_FDATASYNC_BATCH_MODE_SERIAL,
files.len(),
);
sync_files(&files)?; sync_files(&files)?;
let fsync_started = rustfs_io_metrics::put_stage_timer(); let fsync_started = rustfs_io_metrics::put_stage_timer();
let result = fsync_dir_std(scan_dir); let result = fsync_dir_std(scan_dir);
@@ -1115,6 +1119,10 @@ pub(crate) async fn sync_dir_files_with_limiter(dir: impl AsRef<Path>, disk_perm
let Some(files) = files else { let Some(files) = files else {
return Ok(()); return Ok(());
}; };
rustfs_io_metrics::record_put_rename_fdatasync_batch(
rustfs_io_metrics::PUT_RENAME_FDATASYNC_BATCH_MODE_PARALLEL,
files.len(),
);
futures::stream::iter(files.into_iter().map(Ok::<_, io::Error>)) futures::stream::iter(files.into_iter().map(Ok::<_, io::Error>))
.try_for_each_concurrent(MAX_PARALLEL_FILE_SYNCS, |path| { .try_for_each_concurrent(MAX_PARALLEL_FILE_SYNCS, |path| {
let disk_permits = disk_permits.clone(); let disk_permits = disk_permits.clone();
@@ -3417,6 +3417,25 @@ impl SetDisks {
quorum_wait_started, quorum_wait_started,
); );
let (results, mut file_infos) = fanout_result.map_err(|_| DiskError::Unexpected)?; let (results, mut file_infos) = fanout_result.map_err(|_| DiskError::Unexpected)?;
if rustfs_io_metrics::put_stage_metrics_enabled() {
let mut fanout_success = 0;
let mut fanout_error = 0;
let mut fanout_panic = 0;
for result in &results {
match result {
Ok(Ok(_)) => fanout_success += 1,
Ok(Err(_)) => fanout_error += 1,
Err(_) => fanout_panic += 1,
}
}
rustfs_io_metrics::record_put_rename_quorum_wait_fanout(
results.len(),
write_quorum,
fanout_success,
fanout_error,
fanout_panic,
);
}
for (idx, result) in results.iter().enumerate() { for (idx, result) in results.iter().enumerate() {
match result { match result {
+110
View File
@@ -120,6 +120,14 @@ pub const PUT_STAGE_SET_DISK_RENAME_BACKUP_DIR_FSYNC: &str = "set_disk_rename_ba
pub const PUT_STAGE_SET_DISK_RENAME_ANCESTOR_DIR_FSYNC: &str = "set_disk_rename_ancestor_dir_fsync"; pub const PUT_STAGE_SET_DISK_RENAME_ANCESTOR_DIR_FSYNC: &str = "set_disk_rename_ancestor_dir_fsync";
pub const PUT_STAGE_SET_DISK_RENAME_RENAME_SYSCALL: &str = "set_disk_rename_rename_syscall"; pub const PUT_STAGE_SET_DISK_RENAME_RENAME_SYSCALL: &str = "set_disk_rename_rename_syscall";
pub const PUT_RENAME_FDATASYNC_BATCH_MODE_SERIAL: &str = "serial";
pub const PUT_RENAME_FDATASYNC_BATCH_MODE_PARALLEL: &str = "parallel";
pub const PUT_RENAME_QUORUM_FANOUT_STATE_SCHEDULED: &str = "scheduled";
pub const PUT_RENAME_QUORUM_FANOUT_STATE_WRITE_QUORUM: &str = "write_quorum";
pub const PUT_RENAME_QUORUM_FANOUT_STATE_SUCCESS: &str = "success";
pub const PUT_RENAME_QUORUM_FANOUT_STATE_ERROR: &str = "error";
pub const PUT_RENAME_QUORUM_FANOUT_STATE_PANIC: &str = "panic";
#[inline(always)] #[inline(always)]
pub fn get_stage_metrics_enabled() -> bool { pub fn get_stage_metrics_enabled() -> bool {
GET_STAGE_METRICS_ENABLED.load(Ordering::Relaxed) GET_STAGE_METRICS_ENABLED.load(Ordering::Relaxed)
@@ -2042,6 +2050,44 @@ pub fn record_put_object_stage_duration_from(stage: &'static str, started_at: Op
} }
} }
#[inline(always)]
fn put_stage_count_value(value: usize) -> f64 {
match u32::try_from(value) {
Ok(value) => f64::from(value),
Err(_) => f64::from(u32::MAX),
}
}
#[inline(always)]
pub fn record_put_rename_fdatasync_batch(mode: &'static str, files: usize) {
if !put_stage_metrics_enabled() {
return;
}
histogram!("rustfs_s3_put_object_rename_fdatasync_batch_files", "mode" => mode).record(put_stage_count_value(files));
}
#[inline(always)]
pub fn record_put_rename_quorum_wait_fanout(
scheduled: usize,
write_quorum: usize,
success: usize,
error: usize,
panicked: usize,
) {
if !put_stage_metrics_enabled() {
return;
}
for (state, count) in [
(PUT_RENAME_QUORUM_FANOUT_STATE_SCHEDULED, scheduled),
(PUT_RENAME_QUORUM_FANOUT_STATE_WRITE_QUORUM, write_quorum),
(PUT_RENAME_QUORUM_FANOUT_STATE_SUCCESS, success),
(PUT_RENAME_QUORUM_FANOUT_STATE_ERROR, error),
(PUT_RENAME_QUORUM_FANOUT_STATE_PANIC, panicked),
] {
histogram!("rustfs_s3_put_object_rename_quorum_wait_fanout_disks", "state" => state).record(put_stage_count_value(count));
}
}
/// Record generic internal operation stage duration (non-PUT paths). /// Record generic internal operation stage duration (non-PUT paths).
/// Use this for metacache walks, listing, lifecycle, and other background /// Use this for metacache walks, listing, lifecycle, and other background
/// operations that are NOT part of the PUT object hot path. /// operations that are NOT part of the PUT object hot path.
@@ -3122,6 +3168,70 @@ mod tests {
assert!(stages.iter().all(|stage| recorded.contains(*stage))); assert!(stages.iter().all(|stage| recorded.contains(*stage)));
} }
#[test]
fn put_rename_code_level_metrics_are_static_and_gated() {
let _guard = METRICS_FLAG_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let recorder = DebuggingRecorder::new();
let snapshotter = recorder.snapshotter();
metrics::with_local_recorder(&recorder, || {
set_put_stage_metrics_enabled(false);
record_put_rename_fdatasync_batch(PUT_RENAME_FDATASYNC_BATCH_MODE_SERIAL, 2);
record_put_rename_quorum_wait_fanout(4, 3, 3, 1, 0);
set_put_stage_metrics_enabled(true);
record_put_rename_fdatasync_batch(PUT_RENAME_FDATASYNC_BATCH_MODE_PARALLEL, 9);
record_put_rename_quorum_wait_fanout(4, 3, 3, 1, 0);
set_put_stage_metrics_enabled(false);
});
let rows = snapshotter.snapshot().into_vec();
assert_eq!(histogram_samples(&rows, "rustfs_s3_put_object_rename_fdatasync_batch_files"), vec![9.0]);
let batch_modes = rows
.iter()
.filter(|(composite, _, _, _)| {
composite.kind() == MetricKind::Histogram
&& composite.key().name() == "rustfs_s3_put_object_rename_fdatasync_batch_files"
})
.flat_map(|(composite, _, _, _)| {
composite
.key()
.labels()
.filter(|label| label.key() == "mode")
.map(|label| label.value().to_string())
.collect::<Vec<_>>()
})
.collect::<HashSet<_>>();
assert_eq!(batch_modes, HashSet::from([PUT_RENAME_FDATASYNC_BATCH_MODE_PARALLEL.to_string()]));
let quorum_samples = histogram_samples(&rows, "rustfs_s3_put_object_rename_quorum_wait_fanout_disks");
assert_eq!(quorum_samples, vec![0.0, 1.0, 3.0, 3.0, 4.0]);
let quorum_states = rows
.iter()
.filter(|(composite, _, _, _)| {
composite.kind() == MetricKind::Histogram
&& composite.key().name() == "rustfs_s3_put_object_rename_quorum_wait_fanout_disks"
})
.flat_map(|(composite, _, _, _)| {
composite
.key()
.labels()
.filter(|label| label.key() == "state")
.map(|label| label.value().to_string())
.collect::<Vec<_>>()
})
.collect::<HashSet<_>>();
assert_eq!(
quorum_states,
HashSet::from([
PUT_RENAME_QUORUM_FANOUT_STATE_SCHEDULED.to_string(),
PUT_RENAME_QUORUM_FANOUT_STATE_WRITE_QUORUM.to_string(),
PUT_RENAME_QUORUM_FANOUT_STATE_SUCCESS.to_string(),
PUT_RENAME_QUORUM_FANOUT_STATE_ERROR.to_string(),
PUT_RENAME_QUORUM_FANOUT_STATE_PANIC.to_string(),
])
);
}
#[test] #[test]
fn test_put_object_diagnostic_buckets() { fn test_put_object_diagnostic_buckets() {
assert_eq!(put_object_size_bucket(0), "unknown"); assert_eq!(put_object_size_bucket(0), "unknown");