diff --git a/Cargo.lock b/Cargo.lock index a5b44fb8a..587211b45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8082,6 +8082,7 @@ dependencies = [ name = "rustfs-io-metrics" version = "0.0.5" dependencies = [ + "criterion", "metrics", "num_cpus", "thiserror 2.0.18", diff --git a/crates/io-metrics/Cargo.toml b/crates/io-metrics/Cargo.toml index 8173fb999..cc920732e 100644 --- a/crates/io-metrics/Cargo.toml +++ b/crates/io-metrics/Cargo.toml @@ -24,6 +24,10 @@ description = "Metrics collection and reporting for RustFS (using metrics crate keywords = ["metrics", "zero-copy", "rustfs", "otel", "performance"] categories = ["development-tools", "filesystem"] +[[bench]] +name = "metrics_pipeline" +harness = false + [dependencies] metrics = { workspace = true } num_cpus = { workspace = true } @@ -32,6 +36,7 @@ tokio = { workspace = true, features = ["sync","rt"] } tracing = { workspace = true } [dev-dependencies] +criterion = { workspace = true } tokio = { workspace = true, features = ["test-util","rt","macros"] } [lints] diff --git a/crates/io-metrics/README.md b/crates/io-metrics/README.md index a8143e23e..ddd3bdcd6 100644 --- a/crates/io-metrics/README.md +++ b/crates/io-metrics/README.md @@ -31,6 +31,7 @@ - **Bandwidth Monitoring**: Real-time bandwidth observation and analysis - **Performance Metrics**: I/O performance metrics collection - **Unified Configuration**: Centralized configuration management +- **Exporter Boundary**: Emit via `metrics`, export via `rustfs-obs`, no Prometheus HTTP endpoint ## Features @@ -199,7 +200,7 @@ cargo test --package rustfs-io-metrics cargo test --package rustfs-io-metrics --lib adaptive_ttl # Run benchmarks -cargo bench --package rustfs-io-metrics +cargo bench --package rustfs-io-metrics --bench metrics_pipeline ``` ## Documentation diff --git a/crates/io-metrics/README_zh.md b/crates/io-metrics/README_zh.md index b2baa0921..249f94fde 100644 --- a/crates/io-metrics/README_zh.md +++ b/crates/io-metrics/README_zh.md @@ -31,6 +31,7 @@ - **带宽监控**:实时带宽观测和分析 - **性能指标**: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 bench --package rustfs-io-metrics +cargo bench --package rustfs-io-metrics --bench metrics_pipeline ``` ## 📚 文档 diff --git a/crates/io-metrics/benches/metrics_pipeline.rs b/crates/io-metrics/benches/metrics_pipeline.rs new file mode 100644 index 000000000..20967f6b1 --- /dev/null +++ b/crates/io-metrics/benches/metrics_pipeline.rs @@ -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); diff --git a/crates/io-metrics/examples/metrics_example.rs b/crates/io-metrics/examples/metrics_example.rs index 249a4351b..153fa188a 100644 --- a/crates/io-metrics/examples/metrics_example.rs +++ b/crates/io-metrics/examples/metrics_example.rs @@ -143,7 +143,7 @@ fn metrics_recording_example() { println!(" Recorded 10 cache operations (hits: 7, misses: 3)"); println!(" Metrics reported via metrics crate"); - println!(" View via Prometheus/Grafana"); + println!(" Export via rustfs-obs OTEL pipeline"); println!(); } diff --git a/crates/io-metrics/src/lib.rs b/crates/io-metrics/src/lib.rs index c61fca193..4a175feea 100644 --- a/crates/io-metrics/src/lib.rs +++ b/crates/io-metrics/src/lib.rs @@ -23,6 +23,8 @@ //! - **PerformanceMetrics**: Shared atomic counter struct for advanced use cases //! - **MetricsCollector**: I/O operation tracking with percentile calculation //! - **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 //! diff --git a/rustfs/src/admin/handlers/metrics.rs b/rustfs/src/admin/handlers/metrics.rs index d1e114162..b0af4211f 100644 --- a/rustfs/src/admin/handlers/metrics.rs +++ b/rustfs/src/admin/handlers/metrics.rs @@ -12,22 +12,29 @@ // See the License for the specific language governing permissions and // 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 bytes::Bytes; use futures::{Stream, StreamExt}; -use http::Uri; +use http::{HeaderMap, HeaderValue, Uri}; use hyper::StatusCode; use matchit::Params; use rustfs_ecstore::metrics_realtime::{CollectMetricsOpts, MetricType, collect_local_metrics}; use rustfs_madmin::metrics::RealtimeMetrics; use rustfs_madmin::utils::parse_duration; +use s3s::header::CONTENT_TYPE; use s3s::stream::{ByteStream, DynByteStream}; use s3s::{Body, S3Request, S3Response, S3Result, StdError, s3_error}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::pin::Pin; use std::task::{Context, Poll}; -use std::time::Duration as std_Duration; +use std::time::Duration as StdDuration; use tokio::sync::mpsc; use tokio::time::interval; use tokio::{select, spawn}; @@ -36,6 +43,7 @@ use tracing::{debug, error, warn}; const DEFAULT_METRICS_SAMPLES: u64 = 1; const MAX_METRICS_SAMPLES: u64 = 120; +const CONSOLE_METRICS_CONTENT_TYPE: &str = "application/x-ndjson"; #[derive(Debug, Serialize, Deserialize)] struct MetricsParams { @@ -173,14 +181,15 @@ pub struct MetricsHandler {} impl Operation for MetricsHandler { async fn call(&self, req: S3Request
, params: Params<'_, '_>) -> S3Result