feat(compression): show cluster-level compression stat in grafana (#4112)

This commit is contained in:
wood
2026-06-30 19:05:05 +08:00
committed by GitHub
parent cfb24b31ea
commit 7484a61fa3
21 changed files with 1173 additions and 29 deletions
@@ -0,0 +1,110 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::metrics::report::PrometheusMetric;
use crate::metrics::schema::compression::*;
/// Cluster-level compression statistics.
#[derive(Debug, Clone, Default)]
pub struct CompressionClusterStats {
pub original_bytes_total: u64,
pub compressed_bytes_total: u64,
pub bytes_saved_total: u64,
pub compression_ratio: f64,
pub compression_operations_total: u64,
}
/// Collect cluster-level compression metrics from cluster stats.
pub fn collect_compression_cluster_metrics(stats: &CompressionClusterStats) -> Vec<PrometheusMetric> {
vec![
PrometheusMetric::from_descriptor(&COMPRESSION_OPERATIONS_TOTAL, stats.compression_operations_total as f64),
PrometheusMetric::from_descriptor(&COMPRESSION_BYTES_ORIGINAL_TOTAL, stats.original_bytes_total as f64),
PrometheusMetric::from_descriptor(&COMPRESSION_BYTES_COMPRESSED_TOTAL, stats.compressed_bytes_total as f64),
PrometheusMetric::from_descriptor(&COMPRESSION_BYTES_SAVED_TOTAL, stats.bytes_saved_total as f64),
PrometheusMetric::from_descriptor(&COMPRESSION_RATIO, stats.compression_ratio),
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metrics::report::report_metrics;
#[test]
fn test_collect_compression_cluster_metrics() {
let stats = CompressionClusterStats {
original_bytes_total: 1000,
compressed_bytes_total: 700,
bytes_saved_total: 300,
compression_ratio: 0.7,
compression_operations_total: 10,
};
let metrics = collect_compression_cluster_metrics(&stats);
report_metrics(&metrics);
assert_eq!(metrics.len(), 5);
// Verify original bytes
let name = COMPRESSION_BYTES_ORIGINAL_TOTAL.get_full_metric_name();
let m = metrics.iter().find(|m| m.name == name && m.value == 1000.0);
assert!(m.is_some(), "missing or wrong value for original_bytes_total");
// Verify compressed bytes
let name = COMPRESSION_BYTES_COMPRESSED_TOTAL.get_full_metric_name();
let m = metrics.iter().find(|m| m.name == name && m.value == 700.0);
assert!(m.is_some(), "missing or wrong value for compressed_bytes_total");
// Verify saved bytes
let name = COMPRESSION_BYTES_SAVED_TOTAL.get_full_metric_name();
let m = metrics.iter().find(|m| m.name == name && m.value == 300.0);
assert!(m.is_some(), "missing or wrong value for bytes_saved_total");
// Verify compression ratio
let name = COMPRESSION_RATIO.get_full_metric_name();
let m = metrics.iter().find(|m| m.name == name && m.value == 0.7);
assert!(m.is_some(), "missing or wrong value for compression_ratio");
// Verify operations total
let name = COMPRESSION_OPERATIONS_TOTAL.get_full_metric_name();
let m = metrics.iter().find(|m| m.name == name && m.value == 10.0);
assert!(m.is_some(), "missing or wrong value for compression_operations_total");
}
#[test]
fn test_collect_compression_cluster_metrics_empty() {
let stats = CompressionClusterStats::default();
let metrics = collect_compression_cluster_metrics(&stats);
report_metrics(&metrics);
assert_eq!(metrics.len(), 5);
// All values should be zero
for metric in &metrics {
assert_eq!(metric.value, 0.0, "expected zero value for {}", metric.name);
assert!(metric.labels.is_empty());
}
}
#[test]
fn test_compression_cluster_stats_default() {
let stats = CompressionClusterStats::default();
assert_eq!(stats.original_bytes_total, 0);
assert_eq!(stats.compressed_bytes_total, 0);
assert_eq!(stats.bytes_saved_total, 0);
assert_eq!(stats.compression_ratio, 0.0);
assert_eq!(stats.compression_operations_total, 0);
}
}
+2
View File
@@ -21,6 +21,7 @@ pub mod cluster_erasure_set;
pub mod cluster_health;
pub mod cluster_iam;
pub mod cluster_usage;
pub mod compression;
pub mod dial9;
pub mod ilm;
pub mod node;
@@ -51,6 +52,7 @@ pub use cluster_erasure_set::{ErasureSetStats, collect_erasure_set_metrics};
pub use cluster_health::{ClusterHealthStats, collect_cluster_health_metrics};
pub use cluster_iam::{IamStats, collect_iam_metrics};
pub use cluster_usage::{BucketUsageStats, ClusterUsageStats, collect_bucket_usage_metrics, collect_cluster_usage_metrics};
pub use compression::{CompressionClusterStats, collect_compression_cluster_metrics};
pub use dial9::{Dial9Stats, collect_dial9_metrics, is_dial9_enabled};
pub use ilm::{IlmStats, collect_ilm_metrics};
pub use node::{DiskStats, collect_node_metrics};
+3 -2
View File
@@ -33,6 +33,7 @@ pub use scheduler::{
pub(crate) use storage_api::metrics::{
BucketOperations, BucketOptions, ObsBucketBandwidthMonitor, ObsEcstoreResult, ObsReplicationStats, ObsStore, StorageAdminApi,
obs_expiry_state_handle, obs_get_global_bucket_monitor, obs_get_global_replication_stats, obs_get_quota_config,
obs_get_total_usable_capacity, obs_get_total_usable_capacity_free, obs_load_data_usage_from_backend,
obs_resolve_object_store_handle, obs_transition_state_handle,
obs_get_total_usable_capacity, obs_get_total_usable_capacity_free, obs_is_disk_compression_enabled,
obs_load_compression_total_from_memory, obs_load_data_usage_from_backend, obs_resolve_object_store_handle,
obs_transition_state_handle,
};
+30 -3
View File
@@ -42,6 +42,7 @@ use crate::metrics::collectors::{
collect_cluster_health_metrics,
collect_cluster_metrics,
collect_cluster_usage_metrics,
collect_compression_cluster_metrics,
collect_cpu_metrics,
collect_drive_count_metrics,
collect_drive_detailed_metrics,
@@ -70,6 +71,7 @@ use crate::metrics::config::{
ENV_BUCKET_REPLICATION_BANDWIDTH_METRICS_INTERVAL, ENV_CLUSTER_METRICS_INTERVAL, ENV_DEFAULT_METRICS_INTERVAL,
ENV_NODE_METRICS_INTERVAL, ENV_NOTIFICATION_METRICS_INTERVAL, ENV_RESOURCE_METRICS_INTERVAL,
};
use crate::metrics::obs_is_disk_compression_enabled;
use crate::metrics::report::{PrometheusMetric, report_metrics};
use crate::metrics::runtime_sources::bucket_monitor_available;
use crate::metrics::schema::bucket_replication::{
@@ -78,9 +80,10 @@ use crate::metrics::schema::bucket_replication::{
use crate::metrics::stats_collector::{
ProcessMetricBundle, collect_bucket_replication_bandwidth_stats, collect_bucket_replication_detail_stats,
collect_bucket_stats, collect_cluster_and_health_stats, collect_cluster_config_stats, collect_cluster_usage_metric_stats,
collect_disk_and_system_drive_stats, collect_erasure_set_stats, collect_host_network_stats, collect_iam_stats,
collect_ilm_metric_stats, collect_internode_network_stats, collect_process_metric_bundle, collect_replication_stats,
collect_scanner_metric_stats, collect_system_cpu_and_memory_stats_with,
collect_compression_cluster_stats, collect_disk_and_system_drive_stats, collect_erasure_set_stats,
collect_host_network_stats, collect_iam_stats, collect_ilm_metric_stats, collect_internode_network_stats,
collect_process_metric_bundle, collect_replication_stats, collect_scanner_metric_stats,
collect_system_cpu_and_memory_stats_with,
};
use rustfs_audit::audit_target_metrics;
use rustfs_notify::{notification_metrics_snapshot, notification_target_metrics};
@@ -801,6 +804,30 @@ pub fn init_metrics_runtime(token: CancellationToken) {
}
});
// Spawn task for compression metrics.
if obs_is_disk_compression_enabled() {
let token_clone = token.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(cluster_interval);
loop {
tokio::select! {
_ = interval.tick() => {
if let Some(stats) = collect_compression_cluster_stats().await {
let metrics = collect_compression_cluster_metrics(&stats);
if !metrics.is_empty() {
report_metrics(&metrics);
}
}
}
_ = token_clone.cancelled() => {
warn!(event = EVENT_METRICS_RUNTIME_STATE, component = LOG_COMPONENT_OBS, subsystem = LOG_SUBSYSTEM_METRICS_RUNTIME, collector = "compression_cluster_stats", state = "cancelled", "metrics runtime state changed");
return;
}
}
}
});
}
// Spawn task for internode/system network metrics.
let token_clone = token;
tokio::spawn(async move {
@@ -0,0 +1,61 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{MetricDescriptor, MetricName, new_counter_md, new_gauge_md, subsystems};
use std::sync::LazyLock;
pub static COMPRESSION_BYTES_ORIGINAL_TOTAL: LazyLock<MetricDescriptor> = LazyLock::new(|| {
new_counter_md(
MetricName::OriginedBytesTotal,
"Total bytes before compression (original)",
&["compression"],
subsystems::COMPRESSION,
)
});
pub static COMPRESSION_BYTES_COMPRESSED_TOTAL: LazyLock<MetricDescriptor> = LazyLock::new(|| {
new_counter_md(
MetricName::CompressedBytesTotal,
"Total bytes after compression",
&["compression"],
subsystems::COMPRESSION,
)
});
pub static COMPRESSION_BYTES_SAVED_TOTAL: LazyLock<MetricDescriptor> = LazyLock::new(|| {
new_counter_md(
MetricName::SavedBytesTotal,
"Total bytes saved by compression",
&["compression"],
subsystems::COMPRESSION,
)
});
pub static COMPRESSION_RATIO: LazyLock<MetricDescriptor> = LazyLock::new(|| {
new_gauge_md(
MetricName::CompressionRatio,
"Compression ratio (0.0 - 1.0)",
&["compression"],
subsystems::COMPRESSION,
)
});
pub static COMPRESSION_OPERATIONS_TOTAL: LazyLock<MetricDescriptor> = LazyLock::new(|| {
new_counter_md(
MetricName::CompressionOperationsTotal,
"Total number of compression operations performed",
&["compression"],
subsystems::COMPRESSION,
)
});
@@ -338,6 +338,13 @@ pub enum MetricName {
ScannerPartialCycles,
ScannerPartialCyclesByReason,
// Compression-related metrics
CompressedBytesTotal,
OriginedBytesTotal,
SavedBytesTotal,
CompressionRatio,
CompressionOperationsTotal,
// CPU system-related metrics
SysCPUAvgIdle,
SysCPUAvgIOWait,
@@ -737,6 +744,13 @@ impl MetricName {
Self::ScannerPartialCycles => "partial_cycles".to_string(),
Self::ScannerPartialCyclesByReason => "partial_cycles_by_reason".to_string(),
// Compression-related metrics
Self::CompressedBytesTotal => "compressed_bytes".to_string(),
Self::OriginedBytesTotal => "original_bytes".to_string(),
Self::SavedBytesTotal => "saved_bytes".to_string(),
Self::CompressionRatio => "compression_ratio".to_string(),
Self::CompressionOperationsTotal => "compression_operation_total".to_string(),
// CPU system-related metrics
Self::SysCPUAvgIdle => "avg_idle".to_string(),
Self::SysCPUAvgIOWait => "avg_iowait".to_string(),
@@ -51,6 +51,7 @@ pub enum MetricSubsystem {
Replication,
Notification,
Scanner,
Compression,
// Custom paths
Custom(String),
@@ -93,6 +94,7 @@ impl MetricSubsystem {
Self::Replication => "/replication",
Self::Notification => "/notification",
Self::Scanner => "/scanner",
Self::Compression => "/compression",
// Custom paths
Self::Custom(path) => path,
@@ -141,6 +143,7 @@ impl MetricSubsystem {
"/replication" => Self::Replication,
"/notification" => Self::Notification,
"/scanner" => Self::Scanner,
"/compression" => Self::Compression,
// Treat other paths as custom subsystems
_ => Self::Custom(path.to_string()),
@@ -203,6 +206,7 @@ pub mod subsystems {
pub const REPLICATION: MetricSubsystem = MetricSubsystem::Replication;
pub const NOTIFICATION: MetricSubsystem = MetricSubsystem::Notification;
pub const SCANNER: MetricSubsystem = MetricSubsystem::Scanner;
pub const COMPRESSION: MetricSubsystem = MetricSubsystem::Compression;
}
#[cfg(test)]
+1
View File
@@ -22,6 +22,7 @@ pub mod cluster_health;
pub mod cluster_iam;
pub mod cluster_notification;
pub mod cluster_usage;
pub mod compression;
pub mod entry;
pub mod ilm;
pub mod node_bucket;
+27 -5
View File
@@ -22,17 +22,17 @@
use crate::metrics::collectors::{
BucketReplicationBandwidthStats, BucketReplicationStats, BucketReplicationTargetStats, BucketStats, BucketUsageStats,
ClusterConfigStats, ClusterHealthStats, ClusterStats, ClusterUsageStats, CpuStats, DiskStats, DriveCountStats,
DriveDetailedStats, ErasureSetStats, HostNetworkStats, IamStats, IlmStats, MemoryStats, NetworkStats, ProcessStats,
ProcessStatusType, ReplicationStats, ResourceStats, ScannerStats,
ClusterConfigStats, ClusterHealthStats, ClusterStats, ClusterUsageStats, CompressionClusterStats, CpuStats, DiskStats,
DriveCountStats, DriveDetailedStats, ErasureSetStats, HostNetworkStats, IamStats, IlmStats, MemoryStats, NetworkStats,
ProcessStats, ProcessStatusType, ReplicationStats, ResourceStats, ScannerStats,
};
use crate::metrics::runtime_sources::{
ObsIlmRuntimeSnapshot, bucket_monitor_handle, iam_metrics_snapshot, ilm_runtime_snapshot, replication_stats_handle,
};
use crate::metrics::{
BucketOperations, BucketOptions, ObsEcstoreResult, ObsStore, StorageAdminApi, obs_get_quota_config,
obs_get_total_usable_capacity, obs_get_total_usable_capacity_free, obs_load_data_usage_from_backend,
obs_resolve_object_store_handle,
obs_get_total_usable_capacity, obs_get_total_usable_capacity_free, obs_load_compression_total_from_memory,
obs_load_data_usage_from_backend, obs_resolve_object_store_handle,
};
use chrono::Utc;
use rustfs_common::heal_channel::HealScanMode;
@@ -1100,6 +1100,28 @@ pub async fn collect_scanner_metric_stats() -> Option<ScannerStats> {
})
}
/// Collect cluster-level compression statistics.
pub async fn collect_compression_cluster_stats() -> Option<CompressionClusterStats> {
let compression_data_usage = obs_load_compression_total_from_memory().await?;
let original_bytes_total = compression_data_usage.original_bytes_total;
let compressed_bytes_total = compression_data_usage.compressed_bytes_total;
let bytes_saved_total = original_bytes_total.saturating_sub(compressed_bytes_total);
let compression_ratio = if original_bytes_total > 0 {
compressed_bytes_total as f64 / original_bytes_total as f64
} else {
0.0
};
let compression_operations_total = compression_data_usage.compression_operations_total;
Some(CompressionClusterStats {
original_bytes_total,
compressed_bytes_total,
bytes_saved_total,
compression_ratio,
compression_operations_total,
})
}
#[cfg(test)]
mod tests {
use super::*;
+4 -2
View File
@@ -21,6 +21,8 @@ pub(crate) use rustfs_ecstore::api::capacity::{
get_total_usable_capacity as obs_get_total_usable_capacity,
get_total_usable_capacity_free as obs_get_total_usable_capacity_free,
};
pub(crate) use rustfs_ecstore::api::compression::is_disk_compression_enabled as obs_is_disk_compression_enabled;
pub(crate) use rustfs_ecstore::api::data_usage::load_compression_total_from_memory as obs_load_compression_total_from_memory;
pub(crate) use rustfs_ecstore::api::data_usage::load_data_usage_from_backend as obs_load_data_usage_from_backend;
pub(crate) use rustfs_ecstore::api::error::Result as ObsEcstoreResult;
pub(crate) use rustfs_ecstore::api::runtime::{
@@ -36,7 +38,7 @@ pub(crate) mod metrics {
pub(crate) use super::{
ObsBucketBandwidthMonitor, ObsEcstoreResult, ObsReplicationStats, ObsStore, obs_expiry_state_handle,
obs_get_global_bucket_monitor, obs_get_global_replication_stats, obs_get_quota_config, obs_get_total_usable_capacity,
obs_get_total_usable_capacity_free, obs_load_data_usage_from_backend, obs_resolve_object_store_handle,
obs_transition_state_handle,
obs_get_total_usable_capacity_free, obs_is_disk_compression_enabled, obs_load_compression_total_from_memory,
obs_load_data_usage_from_backend, obs_resolve_object_store_handle, obs_transition_state_handle,
};
}