mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-24 13:16:28 +00:00
refactor(metrics): separate console stream from exports (#2583)
This commit is contained in:
Generated
+1
@@ -8082,6 +8082,7 @@ dependencies = [
|
|||||||
name = "rustfs-io-metrics"
|
name = "rustfs-io-metrics"
|
||||||
version = "0.0.5"
|
version = "0.0.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"criterion",
|
||||||
"metrics",
|
"metrics",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ description = "Metrics collection and reporting for RustFS (using metrics crate
|
|||||||
keywords = ["metrics", "zero-copy", "rustfs", "otel", "performance"]
|
keywords = ["metrics", "zero-copy", "rustfs", "otel", "performance"]
|
||||||
categories = ["development-tools", "filesystem"]
|
categories = ["development-tools", "filesystem"]
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "metrics_pipeline"
|
||||||
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
metrics = { workspace = true }
|
metrics = { workspace = true }
|
||||||
num_cpus = { workspace = true }
|
num_cpus = { workspace = true }
|
||||||
@@ -32,6 +36,7 @@ tokio = { workspace = true, features = ["sync","rt"] }
|
|||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
criterion = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["test-util","rt","macros"] }
|
tokio = { workspace = true, features = ["test-util","rt","macros"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
- **Bandwidth Monitoring**: Real-time bandwidth observation and analysis
|
- **Bandwidth Monitoring**: Real-time bandwidth observation and analysis
|
||||||
- **Performance Metrics**: I/O performance metrics collection
|
- **Performance Metrics**: I/O performance metrics collection
|
||||||
- **Unified Configuration**: Centralized configuration management
|
- **Unified Configuration**: Centralized configuration management
|
||||||
|
- **Exporter Boundary**: Emit via `metrics`, export via `rustfs-obs`, no Prometheus HTTP endpoint
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -199,7 +200,7 @@ cargo test --package rustfs-io-metrics
|
|||||||
cargo test --package rustfs-io-metrics --lib adaptive_ttl
|
cargo test --package rustfs-io-metrics --lib adaptive_ttl
|
||||||
|
|
||||||
# Run benchmarks
|
# Run benchmarks
|
||||||
cargo bench --package rustfs-io-metrics
|
cargo bench --package rustfs-io-metrics --bench metrics_pipeline
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
- **带宽监控**:实时带宽观测和分析
|
- **带宽监控**:实时带宽观测和分析
|
||||||
- **性能指标**:I/O 性能指标收集
|
- **性能指标**:I/O 性能指标收集
|
||||||
- **统一配置**:集中式配置管理
|
- **统一配置**:集中式配置管理
|
||||||
|
- **导出边界**:通过 `metrics` 主动上报,由 `rustfs-obs` 负责 OTEL 导出,不提供 Prometheus HTTP 端点
|
||||||
|
|
||||||
## ✨ 核心功能
|
## ✨ 核心功能
|
||||||
|
|
||||||
@@ -280,7 +281,7 @@ cargo test --package rustfs-io-metrics
|
|||||||
cargo test --package rustfs-io-metrics --lib adaptive_ttl
|
cargo test --package rustfs-io-metrics --lib adaptive_ttl
|
||||||
|
|
||||||
# 运行基准测试
|
# 运行基准测试
|
||||||
cargo bench --package rustfs-io-metrics
|
cargo bench --package rustfs-io-metrics --bench metrics_pipeline
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📚 文档
|
## 📚 文档
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
use criterion::{Criterion, criterion_group, criterion_main};
|
||||||
|
use rustfs_io_metrics::{MetricsCollector, PerformanceMetrics, record_get_object_request_started};
|
||||||
|
use std::hint::black_box;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
fn bench_record_get_object_request_started(c: &mut Criterion) {
|
||||||
|
c.bench_function("record_get_object_request_started", |b| b.iter(record_get_object_request_started));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_update_concurrent_requests(c: &mut Criterion) {
|
||||||
|
let metrics = PerformanceMetrics::new();
|
||||||
|
|
||||||
|
c.bench_function("performance_metrics_update_concurrent_requests", |b| {
|
||||||
|
b.iter(|| metrics.update_concurrent_requests(black_box(64)))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_metrics_collector_record_io_operation(c: &mut Criterion) {
|
||||||
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("build tokio runtime for io-metrics benchmark");
|
||||||
|
let collector = MetricsCollector::new(Arc::new(PerformanceMetrics::new()), 256);
|
||||||
|
|
||||||
|
c.bench_function("metrics_collector_record_io_operation", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
runtime.block_on(collector.record_io_operation(
|
||||||
|
black_box(64 * 1024),
|
||||||
|
Duration::from_micros(black_box(250)),
|
||||||
|
black_box(true),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(
|
||||||
|
benches,
|
||||||
|
bench_record_get_object_request_started,
|
||||||
|
bench_update_concurrent_requests,
|
||||||
|
bench_metrics_collector_record_io_operation
|
||||||
|
);
|
||||||
|
criterion_main!(benches);
|
||||||
@@ -143,7 +143,7 @@ fn metrics_recording_example() {
|
|||||||
|
|
||||||
println!(" Recorded 10 cache operations (hits: 7, misses: 3)");
|
println!(" Recorded 10 cache operations (hits: 7, misses: 3)");
|
||||||
println!(" Metrics reported via metrics crate");
|
println!(" Metrics reported via metrics crate");
|
||||||
println!(" View via Prometheus/Grafana");
|
println!(" Export via rustfs-obs OTEL pipeline");
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
//! - **PerformanceMetrics**: Shared atomic counter struct for advanced use cases
|
//! - **PerformanceMetrics**: Shared atomic counter struct for advanced use cases
|
||||||
//! - **MetricsCollector**: I/O operation tracking with percentile calculation
|
//! - **MetricsCollector**: I/O operation tracking with percentile calculation
|
||||||
//! - **AutoTuner**: Automatic performance optimization based on metrics
|
//! - **AutoTuner**: Automatic performance optimization based on metrics
|
||||||
|
//! - **No HTTP metrics endpoint**: consumers emit metrics through the `metrics` crate;
|
||||||
|
//! `rustfs-obs` owns OTEL initialization and export
|
||||||
//!
|
//!
|
||||||
//! # Usage
|
//! # Usage
|
||||||
//!
|
//!
|
||||||
|
|||||||
@@ -12,22 +12,29 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
//! Console realtime metrics API.
|
||||||
|
//!
|
||||||
|
//! This preserves the console's fixed `/admin/v3/metrics` contract while
|
||||||
|
//! keeping the response format explicitly NDJSON. It is not a Prometheus text
|
||||||
|
//! exposition endpoint.
|
||||||
|
|
||||||
use crate::admin::router::Operation;
|
use crate::admin::router::Operation;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
use http::Uri;
|
use http::{HeaderMap, HeaderValue, Uri};
|
||||||
use hyper::StatusCode;
|
use hyper::StatusCode;
|
||||||
use matchit::Params;
|
use matchit::Params;
|
||||||
use rustfs_ecstore::metrics_realtime::{CollectMetricsOpts, MetricType, collect_local_metrics};
|
use rustfs_ecstore::metrics_realtime::{CollectMetricsOpts, MetricType, collect_local_metrics};
|
||||||
use rustfs_madmin::metrics::RealtimeMetrics;
|
use rustfs_madmin::metrics::RealtimeMetrics;
|
||||||
use rustfs_madmin::utils::parse_duration;
|
use rustfs_madmin::utils::parse_duration;
|
||||||
|
use s3s::header::CONTENT_TYPE;
|
||||||
use s3s::stream::{ByteStream, DynByteStream};
|
use s3s::stream::{ByteStream, DynByteStream};
|
||||||
use s3s::{Body, S3Request, S3Response, S3Result, StdError, s3_error};
|
use s3s::{Body, S3Request, S3Response, S3Result, StdError, s3_error};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::time::Duration as std_Duration;
|
use std::time::Duration as StdDuration;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::time::interval;
|
use tokio::time::interval;
|
||||||
use tokio::{select, spawn};
|
use tokio::{select, spawn};
|
||||||
@@ -36,6 +43,7 @@ use tracing::{debug, error, warn};
|
|||||||
|
|
||||||
const DEFAULT_METRICS_SAMPLES: u64 = 1;
|
const DEFAULT_METRICS_SAMPLES: u64 = 1;
|
||||||
const MAX_METRICS_SAMPLES: u64 = 120;
|
const MAX_METRICS_SAMPLES: u64 = 120;
|
||||||
|
const CONSOLE_METRICS_CONTENT_TYPE: &str = "application/x-ndjson";
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct MetricsParams {
|
struct MetricsParams {
|
||||||
@@ -173,14 +181,15 @@ pub struct MetricsHandler {}
|
|||||||
impl Operation for MetricsHandler {
|
impl Operation for MetricsHandler {
|
||||||
async fn call(&self, req: S3Request<Body>, params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
async fn call(&self, req: S3Request<Body>, params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
debug!("handle MetricsHandler, uri: {:?}, params: {:?}", req.uri, params);
|
debug!("handle MetricsHandler, uri: {:?}, params: {:?}", req.uri, params);
|
||||||
let Some(_cred) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) };
|
let Some(_cred) = req.credentials else {
|
||||||
debug!("validated metrics request credentials");
|
return Err(s3_error!(InvalidRequest, "get cred failed"));
|
||||||
|
};
|
||||||
|
debug!("validated console metrics request credentials");
|
||||||
|
|
||||||
let mp = extract_metrics_init_params(&req.uri);
|
let mp = extract_metrics_init_params(&req.uri);
|
||||||
debug!("mp: {:?}", mp);
|
debug!("mp: {:?}", mp);
|
||||||
|
|
||||||
let tick = parse_duration(&mp.tick).unwrap_or_else(|_| std_Duration::from_secs(3));
|
let tick = parse_duration(&mp.tick).unwrap_or_else(|_| StdDuration::from_secs(3));
|
||||||
|
|
||||||
let mut n = resolve_sample_count(&mp);
|
let mut n = resolve_sample_count(&mp);
|
||||||
|
|
||||||
let types = if mp.types != 0 {
|
let types = if mp.types != 0 {
|
||||||
@@ -193,53 +202,43 @@ impl Operation for MetricsHandler {
|
|||||||
s.split(',').filter(|part| !part.is_empty()).map(String::from).collect()
|
s.split(',').filter(|part| !part.is_empty()).map(String::from).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
let disks = parse_comma_separated(&mp.disks);
|
|
||||||
let by_disk = mp.by_disk == "true";
|
let by_disk = mp.by_disk == "true";
|
||||||
let disk_map = disks;
|
|
||||||
|
|
||||||
let job_id = mp.by_job_id;
|
|
||||||
let hosts = parse_comma_separated(&mp.hosts);
|
|
||||||
let by_host = mp.by_host == "true";
|
let by_host = mp.by_host == "true";
|
||||||
let host_map = hosts;
|
|
||||||
|
|
||||||
let d_id = mp.by_dep_id;
|
|
||||||
let mut interval = interval(tick);
|
let mut interval = interval(tick);
|
||||||
|
|
||||||
let opts = CollectMetricsOpts {
|
let opts = CollectMetricsOpts {
|
||||||
hosts: host_map,
|
hosts: parse_comma_separated(&mp.hosts),
|
||||||
disks: disk_map,
|
disks: parse_comma_separated(&mp.disks),
|
||||||
job_id,
|
job_id: mp.by_job_id,
|
||||||
dep_id: d_id,
|
dep_id: mp.by_dep_id,
|
||||||
};
|
};
|
||||||
let (tx, rx) = mpsc::channel(10);
|
let (tx, rx) = mpsc::channel(10);
|
||||||
let in_stream: DynByteStream = Box::pin(MetricsStream {
|
let in_stream: DynByteStream = Box::pin(MetricsStream {
|
||||||
inner: ReceiverStream::new(rx),
|
inner: ReceiverStream::new(rx),
|
||||||
});
|
});
|
||||||
let body = Body::from(in_stream);
|
let body = Body::from(in_stream);
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
while n > 0 {
|
while n > 0 {
|
||||||
let mut m = RealtimeMetrics::default();
|
let mut metrics = RealtimeMetrics::default();
|
||||||
let m_local = collect_local_metrics(types, &opts).await;
|
let local_metrics = collect_local_metrics(types, &opts).await;
|
||||||
m.merge(m_local);
|
metrics.merge(local_metrics);
|
||||||
|
|
||||||
if !by_host {
|
if !by_host {
|
||||||
m.by_host = HashMap::new();
|
metrics.by_host = HashMap::new();
|
||||||
}
|
}
|
||||||
if !by_disk {
|
if !by_disk {
|
||||||
m.by_disk = HashMap::new();
|
metrics.by_disk = HashMap::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
m.finally = n <= 1;
|
metrics.finally = n <= 1;
|
||||||
|
|
||||||
// todo write resp
|
match serde_json::to_vec(&metrics) {
|
||||||
match serde_json::to_vec(&m) {
|
Ok(mut encoded) => {
|
||||||
Ok(mut re) => {
|
encoded.push(b'\n');
|
||||||
// NDJSON framing allows stream clients to parse incremental records.
|
let _ = tx.send(Ok(Bytes::from(encoded))).await;
|
||||||
re.push(b'\n');
|
|
||||||
let _ = tx.send(Ok(Bytes::from(re))).await;
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(err) => {
|
||||||
error!("MetricsHandler: json encode failed, err: {:?}", e);
|
error!("MetricsHandler: json encode failed, err: {:?}", err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,13 +255,19 @@ impl Operation for MetricsHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(S3Response::new((StatusCode::OK, body)))
|
let mut header = HeaderMap::new();
|
||||||
|
header.insert(CONTENT_TYPE, HeaderValue::from_static(CONSOLE_METRICS_CONTENT_TYPE));
|
||||||
|
|
||||||
|
Ok(S3Response::with_headers((StatusCode::OK, body), header))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{DEFAULT_METRICS_SAMPLES, MAX_METRICS_SAMPLES, extract_metrics_init_params, resolve_sample_count};
|
use super::{
|
||||||
|
CONSOLE_METRICS_CONTENT_TYPE, DEFAULT_METRICS_SAMPLES, MAX_METRICS_SAMPLES, extract_metrics_init_params,
|
||||||
|
resolve_sample_count,
|
||||||
|
};
|
||||||
use http::Uri;
|
use http::Uri;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -288,4 +293,9 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(resolve_sample_count(&mp), MAX_METRICS_SAMPLES);
|
assert_eq!(resolve_sample_count(&mp), MAX_METRICS_SAMPLES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn metrics_handler_uses_ndjson_content_type() {
|
||||||
|
assert_eq!(CONSOLE_METRICS_CONTENT_TYPE, "application/x-ndjson");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
//!
|
//!
|
||||||
//! - Configurable buffer size with high/low watermarks
|
//! - Configurable buffer size with high/low watermarks
|
||||||
//! - Backpressure state monitoring and events
|
//! - Backpressure state monitoring and events
|
||||||
//! - Prometheus metrics for backpressure events
|
//! - Backpressure metrics emitted through the shared metrics pipeline
|
||||||
//! - Graceful handling of slow consumers
|
//! - Graceful handling of slow consumers
|
||||||
//!
|
//!
|
||||||
//! # Architecture
|
//! # Architecture
|
||||||
|
|||||||
@@ -1683,7 +1683,7 @@ impl<T> IoPriorityQueue<T> {
|
|||||||
|
|
||||||
/// Global metrics for I/O priority queue monitoring.
|
/// Global metrics for I/O priority queue monitoring.
|
||||||
///
|
///
|
||||||
/// These metrics are exposed for Prometheus scraping and provide
|
/// These metrics are emitted through the shared metrics pipeline and provide
|
||||||
/// visibility into the priority queue behavior.
|
/// visibility into the priority queue behavior.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct IoPriorityMetrics {
|
pub struct IoPriorityMetrics {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
//! - Request resource tracking (locks, memory, file handles)
|
//! - Request resource tracking (locks, memory, file handles)
|
||||||
//! - Lock wait graph analysis for cycle detection
|
//! - Lock wait graph analysis for cycle detection
|
||||||
//! - Configurable detection interval and hang threshold
|
//! - Configurable detection interval and hang threshold
|
||||||
//! - Prometheus metrics for deadlock events
|
//! - Deadlock metrics emitted through the shared metrics pipeline
|
||||||
//! - Detailed diagnostic logging
|
//! - Detailed diagnostic logging
|
||||||
//!
|
//!
|
||||||
//! # Usage
|
//! # Usage
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
//! - Early lock release after metadata read
|
//! - Early lock release after metadata read
|
||||||
//! - Lock hold time monitoring
|
//! - Lock hold time monitoring
|
||||||
//! - Configurable optimization (can be disabled for debugging)
|
//! - Configurable optimization (can be disabled for debugging)
|
||||||
//! - Prometheus metrics for lock contention analysis
|
//! - Lock contention metrics emitted through the shared metrics pipeline
|
||||||
//!
|
//!
|
||||||
//! # Architecture
|
//! # Architecture
|
||||||
//!
|
//!
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
//! - Configurable request-level timeout (default 30 seconds)
|
//! - Configurable request-level timeout (default 30 seconds)
|
||||||
//! - Automatic cancellation of sub-tasks on timeout
|
//! - Automatic cancellation of sub-tasks on timeout
|
||||||
//! - Resource cleanup on timeout (locks, memory, file handles)
|
//! - Resource cleanup on timeout (locks, memory, file handles)
|
||||||
//! - Prometheus metrics for timeout monitoring
|
//! - Timeout metrics emitted through `rustfs-io-metrics`
|
||||||
|
|
||||||
// Allow dead_code for public API that may be used by external modules or future features
|
// Allow dead_code for public API that may be used by external modules or future features
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
//! - Configurable request-level timeout (default 30 seconds)
|
//! - Configurable request-level timeout (default 30 seconds)
|
||||||
//! - Automatic cancellation of sub-tasks on timeout
|
//! - Automatic cancellation of sub-tasks on timeout
|
||||||
//! - Resource cleanup on timeout (locks, memory, file handles)
|
//! - Resource cleanup on timeout (locks, memory, file handles)
|
||||||
//! - Prometheus metrics for timeout monitoring
|
//! - Timeout metrics emitted through `rustfs-io-metrics`
|
||||||
//!
|
//!
|
||||||
//! # Usage
|
//! # Usage
|
||||||
//!
|
//!
|
||||||
|
|||||||
Reference in New Issue
Block a user