diff --git a/.github/workflows/architecture-migration-rules.yml b/.github/workflows/architecture-migration-rules.yml index 365168dc7..c9eabcea7 100644 --- a/.github/workflows/architecture-migration-rules.yml +++ b/.github/workflows/architecture-migration-rules.yml @@ -53,9 +53,9 @@ jobs: persist-credentials: false - name: Install ripgrep - run: | - sudo apt-get update - sudo apt-get install -y ripgrep + uses: taiki-e/install-action@bffeee26d4db9be238a4ea78d8826604ebcb594d # v2 + with: + tool: ripgrep@15.2.0 - name: Check architecture migration rules run: ./scripts/check_architecture_migration_rules.sh diff --git a/.github/workflows/ci-docs-only.yml b/.github/workflows/ci-docs-only.yml index 5cd8ab733..89ae4d18f 100644 --- a/.github/workflows/ci-docs-only.yml +++ b/.github/workflows/ci-docs-only.yml @@ -83,7 +83,9 @@ jobs: persist-credentials: false - name: Install ripgrep - run: sudo apt-get update && sudo apt-get install -y ripgrep + uses: taiki-e/install-action@bffeee26d4db9be238a4ea78d8826604ebcb594d # v2 + with: + tool: ripgrep@15.2.0 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23a37d82b..4812b118f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -118,7 +118,9 @@ jobs: persist-credentials: false - name: Install ripgrep - run: sudo apt-get update && sudo apt-get install -y ripgrep + uses: taiki-e/install-action@bffeee26d4db9be238a4ea78d8826604ebcb594d # v2 + with: + tool: ripgrep@15.2.0 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable diff --git a/Cargo.lock b/Cargo.lock index c73ac8d64..e42b1732b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9933,6 +9933,7 @@ dependencies = [ "libc", "log", "metrics", + "metrics-util", "num_cpus", "nvml-wrapper", "opentelemetry", diff --git a/Cargo.toml b/Cargo.toml index b6a37b028..438472db2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -264,6 +264,7 @@ lazy_static = "1.5.0" libc = "0.2.189" libsystemd = "0.7.2" local-ip-address = "0.6.13" +log = "0.4" memmap2 = "0.9.11" lz4 = "1.28.1" matchit = "0.9.2" @@ -326,6 +327,7 @@ zstd = "0.13.3" # Observability and Metrics metrics = "0.24.6" +metrics-util = "0.20" dial9-tokio-telemetry = "0.3" opentelemetry = { version = "0.32.0" } opentelemetry-appender-tracing = { version = "0.32.0" } diff --git a/crates/common/src/metrics.rs b/crates/common/src/metrics.rs index 4c409279f..00521b55b 100644 --- a/crates/common/src/metrics.rs +++ b/crates/common/src/metrics.rs @@ -1401,25 +1401,11 @@ impl CurrentCycle { } /// OTEL metric name constants for scanner metrics -const OTEL_SCANNER_OBJECTS_SCANNED: &str = "rustfs_scanner_objects_scanned_total"; -const OTEL_SCANNER_DIRECTORIES_SCANNED: &str = "rustfs_scanner_directories_scanned_total"; const OTEL_SCANNER_BUCKETS_SCANNED: &str = "rustfs_scanner_buckets_scanned_total"; const OTEL_SCANNER_CYCLES: &str = "rustfs_scanner_cycles_total"; const OTEL_SCANNER_CYCLE_DURATION_SECONDS: &str = "rustfs_scanner_cycle_duration_seconds"; const OTEL_SCANNER_BUCKET_DRIVE_DURATION_SECONDS: &str = "rustfs_scanner_bucket_drive_duration_seconds"; -fn emit_otel_counter(metric: usize, count: u64) { - match Metric::from_index(metric) { - Some(Metric::ScanObject) => { - metrics::counter!(OTEL_SCANNER_OBJECTS_SCANNED).increment(count); - } - Some(Metric::ScanFolder) => { - metrics::counter!(OTEL_SCANNER_DIRECTORIES_SCANNED).increment(count); - } - _ => {} - } -} - fn scan_cycle_result_label(result: u8) -> &'static str { match result { SCAN_CYCLE_RESULT_SUCCESS => SCAN_CYCLE_RESULT_SUCCESS_LABEL, @@ -1960,7 +1946,6 @@ impl Metrics { let duration = SystemTime::now().duration_since(start).unwrap_or_default(); global_metrics().operations[metric_idx].fetch_add(1, Ordering::Relaxed); global_metrics().record_source_work_for_metric(metric, 1); - emit_otel_counter(metric_idx, 1); if metric_idx < Metric::LastRealtime as usize { global_metrics().latency[metric_idx].add(duration); } @@ -1976,7 +1961,6 @@ impl Metrics { let duration = SystemTime::now().duration_since(start).unwrap_or_default(); global_metrics().operations[metric_idx].fetch_add(1, Ordering::Relaxed); global_metrics().record_source_work_for_metric(metric, 1); - emit_otel_counter(metric_idx, 1); if metric_idx < Metric::LastRealtime as usize { global_metrics().latency[metric_idx].add_size(duration, size); } @@ -1992,7 +1976,6 @@ impl Metrics { let duration = SystemTime::now().duration_since(start).unwrap_or_default(); global_metrics().operations[metric_idx].fetch_add(1, Ordering::Relaxed); global_metrics().record_source_work_for_metric(metric, 1); - emit_otel_counter(metric_idx, 1); if metric_idx < Metric::LastRealtime as usize { global_metrics().latency[metric_idx].add(duration); } @@ -2010,7 +1993,6 @@ impl Metrics { let count = usize_to_u64_saturated(count); global_metrics().operations[metric_idx].fetch_add(count, Ordering::Relaxed); global_metrics().record_source_work_for_metric(metric, count); - emit_otel_counter(metric_idx, count); if metric_idx < Metric::LastRealtime as usize { global_metrics().latency[metric_idx].add(duration); } @@ -2031,7 +2013,6 @@ impl Metrics { let duration = SystemTime::now().duration_since(start).unwrap_or_default(); let metric_idx = Metric::Ilm as usize; global_metrics().operations[metric_idx].fetch_add(versions, Ordering::Relaxed); - emit_otel_counter(metric_idx, versions); global_metrics().actions[a_idx].fetch_add(versions, Ordering::Relaxed); global_metrics().actions_latency[a_idx].add(duration); }) @@ -2044,7 +2025,6 @@ impl Metrics { let metric_idx = metric as usize; global_metrics().operations[metric_idx].fetch_add(1, Ordering::Relaxed); global_metrics().record_source_work_for_metric(metric, 1); - emit_otel_counter(metric_idx, 1); if metric_idx < Metric::LastRealtime as usize { global_metrics().latency[metric_idx].add(duration); } diff --git a/crates/io-metrics/Cargo.toml b/crates/io-metrics/Cargo.toml index 6124de8ca..0591aa4e7 100644 --- a/crates/io-metrics/Cargo.toml +++ b/crates/io-metrics/Cargo.toml @@ -58,7 +58,7 @@ sysinfo = { workspace = true } [dev-dependencies] criterion = { workspace = true, features = ["html_reports"] } -metrics-util = { version = "0.20", features = ["debugging"] } +metrics-util = { workspace = true, features = ["debugging"] } tokio = { workspace = true, features = ["test-util", "macros", "fs", "rt-multi-thread"] } [lints] diff --git a/crates/kms/Cargo.toml b/crates/kms/Cargo.toml index 2883667c1..f1a41c888 100644 --- a/crates/kms/Cargo.toml +++ b/crates/kms/Cargo.toml @@ -94,7 +94,7 @@ aws-smithy-types = { workspace = true } [dev-dependencies] anyhow = { workspace = true } # Debugging recorder for asserting emitted metrics in tests. -metrics-util = { version = "0.20", features = ["debugging"] } +metrics-util = { workspace = true, features = ["debugging"] } insta = { workspace = true, features = ["yaml", "json"] } tempfile = { workspace = true } temp-env = { workspace = true } diff --git a/crates/lifecycle/Cargo.toml b/crates/lifecycle/Cargo.toml index 8f5f63556..ae8232f20 100644 --- a/crates/lifecycle/Cargo.toml +++ b/crates/lifecycle/Cargo.toml @@ -67,7 +67,7 @@ url.workspace = true uuid = { workspace = true, features = ["v4", "serde", "fast-rng", "macro-diagnostics"] } [dev-dependencies] -metrics-util = { version = "0.20", features = ["debugging"] } +metrics-util = { workspace = true, features = ["debugging"] } proptest = "1" serial_test.workspace = true temp-env.workspace = true diff --git a/crates/object-data-cache/Cargo.toml b/crates/object-data-cache/Cargo.toml index 704f3df6f..152b73992 100644 --- a/crates/object-data-cache/Cargo.toml +++ b/crates/object-data-cache/Cargo.toml @@ -58,7 +58,7 @@ tracing = { workspace = true, optional = true } [dev-dependencies] criterion = { workspace = true, features = ["html_reports"] } -metrics-util = { version = "0.20", features = ["debugging"] } +metrics-util = { workspace = true, features = ["debugging"] } # `rt-multi-thread` lets the concurrency stress tests run tasks on real worker # threads, so they exercise true parallelism on the shared singleflight/index # state rather than only cooperative interleaving. diff --git a/crates/obs/Cargo.toml b/crates/obs/Cargo.toml index eeec4ee02..17ba113ed 100644 --- a/crates/obs/Cargo.toml +++ b/crates/obs/Cargo.toml @@ -163,4 +163,5 @@ libc = { workspace = true } [dev-dependencies] tempfile = { workspace = true } temp-env = { workspace = true } -log = "0.4" +log.workspace = true +metrics-util = { workspace = true, features = ["debugging"] } diff --git a/crates/obs/src/metrics/collectors/scanner.rs b/crates/obs/src/metrics/collectors/scanner.rs index db220ca96..56f1db556 100644 --- a/crates/obs/src/metrics/collectors/scanner.rs +++ b/crates/obs/src/metrics/collectors/scanner.rs @@ -465,6 +465,57 @@ fn bool_metric_value(enabled: bool) -> f64 { mod tests { use super::*; use crate::metrics::report::report_metrics; + use metrics_util::debugging::DebuggingRecorder; + use rustfs_common::metrics::{Metric, Metrics}; + + fn prometheus_counter_name(name: &str) -> String { + if name.ends_with("_total") { + name.to_string() + } else { + format!("{name}_total") + } + } + + #[test] + fn scanner_lifetime_counters_have_one_prometheus_producer() { + let recorder = DebuggingRecorder::new(); + let snapshotter = recorder.snapshotter(); + let scanner_metrics = collect_scanner_metrics(&ScannerStats { + directories_scanned: 3, + objects_scanned: 7, + ..Default::default() + }); + + metrics::with_local_recorder(&recorder, || { + Metrics::time(Metric::ScanObject)(); + Metrics::time(Metric::ScanFolder)(); + report_metrics(&scanner_metrics); + }); + + let normalized_counter_names: Vec<_> = snapshotter + .snapshot() + .into_vec() + .into_iter() + .filter_map(|(composite, _, _, value)| { + matches!(value, metrics_util::debugging::DebugValue::Counter(_)) + .then(|| prometheus_counter_name(composite.key().name())) + }) + .collect(); + + for name in [ + "rustfs_scanner_objects_scanned_total", + "rustfs_scanner_directories_scanned_total", + ] { + assert_eq!( + normalized_counter_names + .iter() + .filter(|candidate| candidate.as_str() == name) + .count(), + 1, + "scanner lifetime counter must have exactly one producer after Prometheus name normalization" + ); + } + } #[test] fn test_collect_scanner_metrics() { diff --git a/crates/protos/src/lib.rs b/crates/protos/src/lib.rs index d23224b93..a9e59b8a0 100644 --- a/crates/protos/src/lib.rs +++ b/crates/protos/src/lib.rs @@ -2433,7 +2433,7 @@ mod tests { json_field: "opts", bin_field: "opts_bin", }, - json_encoder: "let opts_str = compat_json(opts)?;", + json_encoder: "let encoded_opts = compat_json(opts).and_then(|opts_str| encode_msgpack(opts).map(|opts_bin| (opts_str, opts_bin)));", policy: RequestJsonPolicy::MsgpackOnlyEligible, }, RequestCompatSendSite { diff --git a/rustfs/Cargo.toml b/rustfs/Cargo.toml index 69916207e..2e1952c34 100644 --- a/rustfs/Cargo.toml +++ b/rustfs/Cargo.toml @@ -350,7 +350,7 @@ proptest = "1" tokio = { workspace = true, features = ["test-util", "fs", "rt-multi-thread"] } temp-env = { workspace = true, features = ["async_closure"] } tracing-subscriber = { workspace = true, features = ["env-filter", "time"] } -metrics-util = { version = "0.20", features = ["debugging"] } +metrics-util = { workspace = true, features = ["debugging"] } opentelemetry_sdk = { workspace = true, features = ["rt-tokio"] } rsa = { workspace = true } rcgen = { workspace = true }