mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-31 09:18:28 +00:00
a4411a24d6
Co-authored-by: overtrue <anzhengchao@gmail.com> Co-authored-by: heihutu <heihutu@gmail.com>
177 lines
6.0 KiB
Rust
177 lines
6.0 KiB
Rust
// 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.
|
|
|
|
//! Per-bucket metrics collector.
|
|
//!
|
|
//! Collects usage metrics for each bucket in the cluster, including
|
|
//! size, object counts, and quota information.
|
|
//!
|
|
//! This collector reuses the metric descriptors defined in `metrics_type::node_bucket`
|
|
//! to avoid duplication of metric names, types, and help text.
|
|
|
|
use crate::format::PrometheusMetric;
|
|
use crate::metrics_type::node_bucket::*;
|
|
use std::borrow::Cow;
|
|
|
|
/// Bucket statistics for metrics collection.
|
|
///
|
|
/// This struct provides a decoupled interface for collecting bucket metrics
|
|
/// without depending on specific internal types. HTTP handlers should populate
|
|
/// this struct from their available data sources.
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct BucketStats {
|
|
/// Name of the bucket
|
|
pub name: String,
|
|
/// Total size of all objects in the bucket (bytes)
|
|
pub size_bytes: u64,
|
|
/// Number of objects in the bucket
|
|
pub objects_count: u64,
|
|
/// Quota limit for the bucket (bytes), 0 if no quota
|
|
pub quota_bytes: u64,
|
|
}
|
|
|
|
/// Collects per-bucket metrics from the provided bucket statistics.
|
|
///
|
|
/// Uses the metric descriptors from `metrics_type::node_bucket` module.
|
|
/// Returns a vector of Prometheus metrics for all buckets.
|
|
pub fn collect_bucket_metrics(buckets: &[BucketStats]) -> Vec<PrometheusMetric> {
|
|
if buckets.is_empty() {
|
|
return Vec::new();
|
|
}
|
|
|
|
let mut metrics = Vec::with_capacity(buckets.len() * 3);
|
|
for bucket in buckets {
|
|
let bucket_label: Cow<'static, str> = Cow::Owned(bucket.name.clone());
|
|
|
|
metrics.push(
|
|
PrometheusMetric::from_descriptor(&BUCKET_USAGE_BYTES_MD, bucket.size_bytes as f64)
|
|
.with_label("bucket", bucket_label.clone()),
|
|
);
|
|
|
|
metrics.push(
|
|
PrometheusMetric::from_descriptor(&BUCKET_OBJECTS_TOTAL_MD, bucket.objects_count as f64)
|
|
.with_label("bucket", bucket_label.clone()),
|
|
);
|
|
|
|
metrics.push(
|
|
PrometheusMetric::from_descriptor(&BUCKET_QUOTA_BYTES_MD, bucket.quota_bytes as f64)
|
|
.with_label("bucket", bucket_label),
|
|
);
|
|
}
|
|
|
|
metrics
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::format::report_metrics;
|
|
|
|
#[test]
|
|
fn test_collect_bucket_metrics() {
|
|
let buckets = vec![
|
|
BucketStats {
|
|
name: "test-bucket".to_string(),
|
|
size_bytes: 1000,
|
|
objects_count: 100,
|
|
quota_bytes: 0,
|
|
},
|
|
BucketStats {
|
|
name: "another-bucket".to_string(),
|
|
size_bytes: 2000,
|
|
objects_count: 200,
|
|
quota_bytes: 0,
|
|
},
|
|
];
|
|
|
|
let metrics = collect_bucket_metrics(&buckets);
|
|
report_metrics(&metrics);
|
|
|
|
// 2 buckets * 3 metrics each (size, objects, quota) = 6 metrics
|
|
assert_eq!(metrics.len(), 6);
|
|
|
|
// Verify test-bucket metrics have correct labels
|
|
let test_bucket_size_name = BUCKET_USAGE_BYTES_MD.get_full_metric_name();
|
|
let test_bucket_size = metrics
|
|
.iter()
|
|
.find(|m| m.name == test_bucket_size_name && m.labels.iter().any(|(k, v)| *k == "bucket" && v == "test-bucket"));
|
|
assert!(test_bucket_size.is_some());
|
|
assert_eq!(test_bucket_size.map(|m| m.value), Some(1000.0));
|
|
}
|
|
|
|
#[test]
|
|
fn test_collect_bucket_metrics_with_quotas() {
|
|
let buckets = vec![BucketStats {
|
|
name: "quota-bucket".to_string(),
|
|
size_bytes: 500,
|
|
objects_count: 10,
|
|
quota_bytes: 10000,
|
|
}];
|
|
|
|
let metrics = collect_bucket_metrics(&buckets);
|
|
report_metrics(&metrics);
|
|
|
|
// 1 bucket * 3 metrics (size, objects, quota) = 3 metrics
|
|
assert_eq!(metrics.len(), 3);
|
|
|
|
// Verify quota metric exists
|
|
let quota_metric_name = BUCKET_QUOTA_BYTES_MD.get_full_metric_name();
|
|
let quota_metric = metrics.iter().find(|m| {
|
|
m.name == quota_metric_name
|
|
&& m.value == 10000.0
|
|
&& m.labels.iter().any(|(k, v)| *k == "bucket" && v == "quota-bucket")
|
|
});
|
|
assert!(quota_metric.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_collect_bucket_metrics_empty() {
|
|
let buckets: Vec<BucketStats> = vec![];
|
|
let metrics = collect_bucket_metrics(&buckets);
|
|
assert!(metrics.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_collect_bucket_metrics_zero_quota_always_reported() {
|
|
let buckets = vec![BucketStats {
|
|
name: "no-quota-bucket".to_string(),
|
|
size_bytes: 100,
|
|
objects_count: 5,
|
|
quota_bytes: 0,
|
|
}];
|
|
|
|
let metrics = collect_bucket_metrics(&buckets);
|
|
report_metrics(&metrics);
|
|
|
|
// Zero quota should still produce a quota metric with value 0 for consistent PromQL queries
|
|
assert_eq!(metrics.len(), 3);
|
|
let quota_metric_name = BUCKET_QUOTA_BYTES_MD.get_full_metric_name();
|
|
let quota_metric = metrics.iter().find(|m| {
|
|
m.name == quota_metric_name
|
|
&& m.value == 0.0
|
|
&& m.labels.iter().any(|(k, v)| *k == "bucket" && v == "no-quota-bucket")
|
|
});
|
|
assert!(quota_metric.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_bucket_stats_default() {
|
|
let stats = BucketStats::default();
|
|
assert!(stats.name.is_empty());
|
|
assert_eq!(stats.size_bytes, 0);
|
|
assert_eq!(stats.objects_count, 0);
|
|
assert_eq!(stats.quota_bytes, 0);
|
|
}
|
|
}
|