obs(get): add GET read pipeline metrics (#3868)

This commit is contained in:
houseme
2026-06-26 04:02:02 +08:00
committed by GitHub
parent 33b7f48f37
commit 7bf411f465
16 changed files with 3268 additions and 82 deletions
+233 -2
View File
@@ -182,6 +182,16 @@ fn saturating_sub_atomic(counter: &AtomicU64, bytes: u64) -> u64 {
}
}
#[inline(always)]
fn usize_to_f64(value: usize) -> f64 {
value as f64
}
#[inline(always)]
fn i64_non_negative_to_f64(value: i64) -> f64 {
value.max(0) as f64
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum TrackedMemoryGauge {
GetObjectBufferedBytes,
@@ -266,9 +276,44 @@ pub fn record_get_object_completion(total_duration_secs: f64, response_size_byte
pub fn record_get_object_stream_strategy(strategy: &str, buffer_size_bytes: usize, response_size_bytes: i64) {
counter!("rustfs_io_get_object_stream_strategy_total", "strategy" => strategy.to_string()).increment(1);
histogram!("rustfs_io_get_object_stream_buffer_size_bytes", "strategy" => strategy.to_string())
.record(buffer_size_bytes as f64);
.record(usize_to_f64(buffer_size_bytes));
histogram!("rustfs_io_get_object_stream_response_size_bytes", "strategy" => strategy.to_string())
.record(response_size_bytes.max(0) as f64);
.record(i64_non_negative_to_f64(response_size_bytes));
}
/// Record the response-body handoff shape from a GetObject reader into the S3 streaming body.
#[inline(always)]
pub fn record_get_object_response_handoff(
strategy: &str,
buffer_source: &str,
buffer_size_bytes: usize,
response_size_bytes: i64,
duration_secs: f64,
) {
counter!(
"rustfs_io_get_object_response_handoff_total",
"strategy" => strategy.to_string(),
"buffer_source" => buffer_source.to_string()
)
.increment(1);
histogram!(
"rustfs_io_get_object_response_handoff_buffer_size_bytes",
"strategy" => strategy.to_string(),
"buffer_source" => buffer_source.to_string()
)
.record(usize_to_f64(buffer_size_bytes));
histogram!(
"rustfs_io_get_object_response_handoff_response_size_bytes",
"strategy" => strategy.to_string(),
"buffer_source" => buffer_source.to_string()
)
.record(i64_non_negative_to_f64(response_size_bytes));
histogram!(
"rustfs_io_get_object_response_handoff_duration_seconds",
"strategy" => strategy.to_string(),
"buffer_source" => buffer_source.to_string()
)
.record(duration_secs);
}
/// Record I/O queue congestion observation.
@@ -301,6 +346,169 @@ pub fn record_get_object_io_state(
counter!("rustfs_io_strategy_selected_total", "level" => load_level.to_string()).increment(1);
}
/// Record GetObject phase duration for the current read path.
#[inline(always)]
pub fn record_get_object_stage_duration(path: &'static str, stage: &'static str, duration_secs: f64) {
histogram!("rustfs_io_get_object_stage_duration_seconds", "path" => path, "stage" => stage).record(duration_secs);
}
/// Record the selected GetObject reader path.
#[inline(always)]
pub fn record_get_object_reader_path(path: &'static str) {
counter!("rustfs_io_get_object_reader_path_total", "path" => path).increment(1);
}
/// Record why the codec streaming reader was not selected.
#[inline(always)]
pub fn record_get_object_codec_streaming_fallback(reason: &'static str) {
counter!("rustfs_io_get_object_codec_streaming_fallback_total", "reason" => reason).increment(1);
}
/// Record one decoded reader stripe processed by a GetObject read path.
#[inline(always)]
pub fn record_get_object_reader_stripe(path: &'static str) {
counter!("rustfs_io_get_object_reader_stripes_total", "path" => path).increment(1);
}
/// Record bytes emitted by a GetObject reader path.
#[inline(always)]
pub fn record_get_object_reader_bytes(path: &'static str, bytes: usize) {
let bytes = u64::try_from(bytes).unwrap_or(u64::MAX);
counter!("rustfs_io_get_object_reader_bytes_total", "path" => path).increment(bytes);
}
/// Record one reader buffer produced by a GetObject read path.
#[inline(always)]
pub fn record_get_object_reader_buffer(path: &'static str, role: &'static str, bytes: usize) {
histogram!("rustfs_io_get_object_reader_buffer_bytes", "path" => path, "role" => role).record(usize_to_f64(bytes));
}
/// Record one copy from a GetObject reader's internal buffer into the downstream read buffer.
#[inline(always)]
pub fn record_get_object_reader_copy(
path: &'static str,
bytes: usize,
read_buf_remaining_before: usize,
output_remaining_before: usize,
duration_secs: f64,
) {
let bytes_counter = u64::try_from(bytes).unwrap_or(u64::MAX);
counter!("rustfs_io_get_object_reader_copy_chunks_total", "path" => path).increment(1);
counter!("rustfs_io_get_object_reader_copy_bytes_total", "path" => path).increment(bytes_counter);
histogram!("rustfs_io_get_object_reader_copy_bytes", "path" => path).record(usize_to_f64(bytes));
histogram!("rustfs_io_get_object_reader_copy_read_buf_remaining_bytes", "path" => path)
.record(usize_to_f64(read_buf_remaining_before));
histogram!("rustfs_io_get_object_reader_copy_output_remaining_bytes", "path" => path)
.record(usize_to_f64(output_remaining_before));
histogram!("rustfs_io_get_object_reader_copy_duration_seconds", "path" => path).record(duration_secs);
}
/// Record one downstream poll of the response-facing GetObject reader.
#[inline(always)]
pub fn record_get_object_reader_poll(
path: &'static str,
outcome: &'static str,
read_buf_remaining_before: usize,
filled_bytes: usize,
duration_secs: f64,
) {
let filled_bytes_counter = u64::try_from(filled_bytes).unwrap_or(u64::MAX);
counter!("rustfs_io_get_object_reader_poll_total", "path" => path, "outcome" => outcome).increment(1);
counter!("rustfs_io_get_object_reader_poll_filled_bytes_total", "path" => path, "outcome" => outcome)
.increment(filled_bytes_counter);
histogram!("rustfs_io_get_object_reader_poll_read_buf_remaining_bytes", "path" => path, "outcome" => outcome)
.record(usize_to_f64(read_buf_remaining_before));
histogram!("rustfs_io_get_object_reader_poll_filled_bytes", "path" => path, "outcome" => outcome)
.record(usize_to_f64(filled_bytes));
histogram!("rustfs_io_get_object_reader_poll_duration_seconds", "path" => path, "outcome" => outcome).record(duration_secs);
}
/// Record a bounded prefetch outcome for a GetObject reader path.
#[inline(always)]
pub fn record_get_object_reader_prefetch(path: &'static str, outcome: &'static str) {
counter!("rustfs_io_get_object_reader_prefetch_total", "path" => path, "outcome" => outcome).increment(1);
}
/// Record how long a GetObject reader spent waiting for a prefetch/fill result.
#[inline(always)]
pub fn record_get_object_reader_prefetch_wait(path: &'static str, duration_secs: f64) {
histogram!("rustfs_io_get_object_reader_prefetch_wait_seconds", "path" => path).record(duration_secs);
}
/// Record one underlying shard read attempt for GetObject read-path attribution.
#[inline(always)]
pub fn record_get_object_shard_read(
path: &'static str,
role: &'static str,
outcome: &'static str,
bytes: usize,
duration_secs: f64,
) {
let bytes = u64::try_from(bytes).unwrap_or(u64::MAX);
counter!("rustfs_io_get_object_shard_read_total", "path" => path, "role" => role, "outcome" => outcome).increment(1);
counter!("rustfs_io_get_object_shard_read_bytes_total", "path" => path, "role" => role, "outcome" => outcome)
.increment(bytes);
histogram!("rustfs_io_get_object_shard_read_duration_seconds", "path" => path, "role" => role, "outcome" => outcome)
.record(duration_secs);
}
#[inline(always)]
fn shard_read_fanout_to_f64(value: usize) -> f64 {
u32::try_from(value).map(f64::from).unwrap_or(f64::from(u32::MAX))
}
/// Record per-stripe shard-read fanout shape for GetObject read-path attribution.
#[inline(always)]
pub fn record_get_object_shard_read_fanout(
path: &'static str,
scheduled: usize,
completed: usize,
successful: usize,
failed: usize,
) {
histogram!("rustfs_io_get_object_shard_read_scheduled", "path" => path).record(shard_read_fanout_to_f64(scheduled));
histogram!("rustfs_io_get_object_shard_read_completed", "path" => path).record(shard_read_fanout_to_f64(completed));
histogram!("rustfs_io_get_object_shard_read_successful", "path" => path).record(shard_read_fanout_to_f64(successful));
histogram!("rustfs_io_get_object_shard_read_failed", "path" => path).record(shard_read_fanout_to_f64(failed));
}
/// Record GetObject metadata resolution duration.
#[inline(always)]
pub fn record_get_object_metadata_phase_duration(duration_secs: f64) {
record_get_object_stage_duration("legacy_duplex", "metadata", duration_secs);
}
/// Record GetObject shard reader setup duration.
#[inline(always)]
pub fn record_get_object_shard_reader_setup_duration(duration_secs: f64) {
record_get_object_stage_duration("legacy_duplex", "reader_setup", duration_secs);
}
/// Record GetObject erasure decode duration.
#[inline(always)]
pub fn record_get_object_decode_duration(duration_secs: f64) {
record_get_object_stage_duration("legacy_duplex", "decode", duration_secs);
}
/// Record GetObject downstream write wait while emitting decoded data.
#[inline(always)]
pub fn record_get_object_duplex_backpressure_duration(duration_secs: f64) {
record_get_object_stage_duration("legacy_duplex", "duplex_backpressure", duration_secs);
}
/// Record GetObject read pipeline failures using bounded labels.
#[inline(always)]
pub fn record_get_object_pipeline_failure(stage: &'static str, reason: &'static str) {
counter!("rustfs_io_get_object_pipeline_failures_total", "path" => "legacy_duplex", "stage" => stage, "reason" => reason)
.increment(1);
}
/// Record GetObject read pipeline failures for an explicit bounded path label.
#[inline(always)]
pub fn record_get_object_pipeline_failure_for_path(path: &'static str, stage: &'static str, reason: &'static str) {
counter!("rustfs_io_get_object_pipeline_failures_total", "path" => path, "stage" => stage, "reason" => reason).increment(1);
}
/// Record a zero-copy read operation.
///
/// # Arguments
@@ -895,6 +1103,29 @@ mod tests {
record_get_object(50.0, 2048);
}
#[test]
fn test_record_get_object_stage_metrics() {
record_get_object_stage_duration("s3_handler", "request_context", 0.001);
record_get_object_reader_path("codec_streaming");
record_get_object_codec_streaming_fallback("range");
record_get_object_reader_stripe("codec_streaming");
record_get_object_reader_bytes("codec_streaming", 1024);
record_get_object_reader_buffer("codec_streaming", "output", 1024);
record_get_object_reader_copy("codec_streaming", 512, 8192, 1024, 0.0001);
record_get_object_reader_poll("codec_streaming", "ready_data", 8192, 512, 0.0002);
record_get_object_reader_prefetch("codec_streaming", "stored");
record_get_object_reader_prefetch_wait("codec_streaming", 0.0002);
record_get_object_response_handoff("standard", "selected", 8192, 1024, 0.0001);
record_get_object_metadata_phase_duration(0.002);
record_get_object_shard_reader_setup_duration(0.003);
record_get_object_decode_duration(0.004);
record_get_object_duplex_backpressure_duration(0.005);
record_get_object_pipeline_failure("decode", "read_quorum");
record_get_object_pipeline_failure_for_path("codec_streaming", "decode", "read_quorum");
assert!(0.005_f64.is_sign_positive());
}
#[test]
fn test_record_put_object() {
record_put_object(200.0, 1024 * 1024, true);