mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-22 04:16:38 +00:00
7db3882777
backlog#1823 step 10, batch 2. Eighteen of the nineteen suppress nothing and are deleted; one was real and keeps an allow that now says why. Rotation::Never is constructed only by the rolling-appender tests at rolling.rs:456, 477 and 498, so the lib target reports it as never constructed. Its allow is restored with that reason. Finding it corrected the method used for batch 1. Removing all nineteen and running cargo check -p rustfs-obs --tests reported zero warnings even after touching every source file, while clippy --lib --tests -D warnings caught Rotation::Never. cargo's warning output is not a reliable completeness check — it does not re-emit for cached compilations, and touching the sources did not cover the lib target here. Later batches should treat clippy -D warnings as the gate; batch 1's six crates were re-checked under clippy and are clean. Taken with #6086, which cleared this crate's 44 module-level blankets and left six real items, obs has now had 63 dead-code suppressions examined, of which seven were suppressing anything at all. The rest sat on items that are publicly reachable, where dead_code never applied — the same shape as the swift module and kms's dek.rs. Verification: clippy --lib --tests -D warnings clean in the default, gpu and pyroscope lanes; cargo nextest run -p rustfs-obs 324 passed; make pre-commit exit 0. Ref rustfs/backlog#1823 (step 10).
214 lines
7.8 KiB
Rust
214 lines
7.8 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.
|
|
|
|
use crate::metrics::schema::{MetricDescriptor, MetricType};
|
|
use metrics::{counter, describe_counter, describe_gauge, describe_histogram, gauge};
|
|
use std::borrow::Cow;
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::sync::{Mutex, OnceLock};
|
|
|
|
static NAME_CACHE: OnceLock<Mutex<HashMap<String, &'static str>>> = OnceLock::new();
|
|
static HELP_CACHE: OnceLock<Mutex<HashMap<String, &'static str>>> = OnceLock::new();
|
|
/// Metric names already handed to `describe_*!`. The metadata never changes, so
|
|
/// each name is described once instead of on every collection cycle.
|
|
static DESCRIBED: OnceLock<Mutex<HashSet<&'static str>>> = OnceLock::new();
|
|
|
|
fn intern_string(cache: &OnceLock<Mutex<HashMap<String, &'static str>>>, value: &str) -> &'static str {
|
|
let cache = cache.get_or_init(Default::default);
|
|
let mut cache = cache.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
|
|
if let Some(existing) = cache.get(value) {
|
|
existing
|
|
} else {
|
|
let value = Box::leak(value.to_string().into_boxed_str());
|
|
cache.insert(value.to_string(), value);
|
|
value
|
|
}
|
|
}
|
|
|
|
/// Resolve a metric name/help `Cow` to a `&'static str`, skipping the intern
|
|
/// lock+leak when it is already borrowed (the common case for statically-named
|
|
/// metrics); only owned strings pay the intern cost.
|
|
// The `&Cow` is intentional: we must inspect the borrowed-vs-owned variant to
|
|
// return the existing `&'static str` without interning, so `&str` will not do.
|
|
#[allow(clippy::ptr_arg)]
|
|
fn static_str(cache: &OnceLock<Mutex<HashMap<String, &'static str>>>, value: &Cow<'static, str>) -> &'static str {
|
|
match value {
|
|
Cow::Borrowed(s) => s,
|
|
Cow::Owned(s) => intern_string(cache, s),
|
|
}
|
|
}
|
|
|
|
/// Hand a metric name to `describe_*!` only the first time it is seen. The
|
|
/// metadata (help text) never changes, so describing on every collection cycle
|
|
/// only re-locks the recorder's metadata map. The help is resolved lazily, so an
|
|
/// already-described metric costs one `HashSet` probe and nothing else.
|
|
// `&Cow` mirrors `static_str`, which needs the borrowed-vs-owned variant.
|
|
#[allow(clippy::ptr_arg)]
|
|
fn describe_metric_once(name: &'static str, metric_type: MetricType, help: &Cow<'static, str>) {
|
|
let described = DESCRIBED.get_or_init(Default::default);
|
|
let mut described = described.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
if described.insert(name) {
|
|
let help = static_str(&HELP_CACHE, help);
|
|
match metric_type {
|
|
MetricType::Counter => describe_counter!(name, help),
|
|
MetricType::Gauge => describe_gauge!(name, help),
|
|
MetricType::Histogram => describe_histogram!(name, help),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn counter_value_from_f64(value: f64) -> Option<u64> {
|
|
if !value.is_finite() || value < 0.0 {
|
|
return None;
|
|
}
|
|
|
|
if value >= u64::MAX as f64 {
|
|
Some(u64::MAX)
|
|
} else {
|
|
Some(value as u64)
|
|
}
|
|
}
|
|
|
|
pub fn report_metrics(metrics: &[PrometheusMetric]) {
|
|
for metric in metrics {
|
|
let name = static_str(&NAME_CACHE, &metric.name);
|
|
describe_metric_once(name, metric.metric_type, &metric.help);
|
|
|
|
// `metric.labels` is already `[(&'static str, Cow<'static, str>)]`, so emit
|
|
// straight from it — no per-cycle `Vec<(String, String)>` clone.
|
|
match metric.metric_type {
|
|
MetricType::Counter => {
|
|
if let Some(value) = counter_value_from_f64(metric.value) {
|
|
counter!(name, &metric.labels).absolute(value);
|
|
}
|
|
}
|
|
MetricType::Gauge => gauge!(name, &metric.labels).set(metric.value),
|
|
MetricType::Histogram => metrics::histogram!(name, &metric.labels).record(metric.value),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct PrometheusMetric {
|
|
pub name: Cow<'static, str>,
|
|
pub metric_type: MetricType,
|
|
pub help: Cow<'static, str>,
|
|
pub labels: Vec<(&'static str, Cow<'static, str>)>,
|
|
pub value: f64,
|
|
}
|
|
|
|
impl PrometheusMetric {
|
|
#[inline]
|
|
pub const fn new(name: &'static str, metric_type: MetricType, help: &'static str, value: f64) -> Self {
|
|
Self {
|
|
name: Cow::Borrowed(name),
|
|
metric_type,
|
|
help: Cow::Borrowed(help),
|
|
labels: Vec::new(),
|
|
value,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn new_owned(name: String, metric_type: MetricType, help: String, value: f64) -> Self {
|
|
Self {
|
|
name: Cow::Owned(name),
|
|
metric_type,
|
|
help: Cow::Owned(help),
|
|
labels: Vec::new(),
|
|
value,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn from_descriptor(descriptor: &MetricDescriptor, value: f64) -> Self {
|
|
let help = intern_string(&HELP_CACHE, &descriptor.help);
|
|
Self {
|
|
name: Cow::Owned(descriptor.get_full_metric_name()),
|
|
metric_type: descriptor.metric_type,
|
|
help: Cow::Borrowed(help),
|
|
labels: Vec::new(),
|
|
value,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn with_label(mut self, key: &'static str, value: impl Into<Cow<'static, str>>) -> Self {
|
|
self.labels.push((key, value.into()));
|
|
self
|
|
}
|
|
|
|
#[inline]
|
|
pub fn with_label_owned(mut self, key: &'static str, value: String) -> Self {
|
|
self.labels.push((key, Cow::Owned(value)));
|
|
self
|
|
}
|
|
|
|
#[inline]
|
|
pub fn with_labels(mut self, labels: Vec<(&'static str, Cow<'static, str>)>) -> Self {
|
|
self.labels = labels;
|
|
self
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::metrics::schema::{MetricName, MetricNamespace, MetricSubsystem};
|
|
|
|
#[test]
|
|
fn from_descriptor_uses_prometheus_metric_names_for_all_types() {
|
|
let cases = [
|
|
(MetricType::Counter, "rustfs_api_requests_total"),
|
|
(MetricType::Gauge, "rustfs_system_memory_used_bytes"),
|
|
(MetricType::Histogram, "rustfs_custom_path_latency_seconds"),
|
|
];
|
|
|
|
for (metric_type, expected_name) in cases {
|
|
let subsystem = match metric_type {
|
|
MetricType::Counter => MetricSubsystem::ApiRequests,
|
|
MetricType::Gauge => MetricSubsystem::SystemMemory,
|
|
MetricType::Histogram => MetricSubsystem::new("/custom/path"),
|
|
};
|
|
let name = match metric_type {
|
|
MetricType::Counter => MetricName::ApiRequestsTotal,
|
|
MetricType::Gauge => MetricName::Custom("used_bytes".to_string()),
|
|
MetricType::Histogram => MetricName::Custom("latency_seconds".to_string()),
|
|
};
|
|
|
|
let metric = PrometheusMetric::from_descriptor(
|
|
&MetricDescriptor::new(name, metric_type, "test help".to_string(), vec![], MetricNamespace::RustFS, subsystem),
|
|
1.0,
|
|
);
|
|
|
|
assert_eq!(metric.name, expected_name);
|
|
assert_eq!(metric.metric_type, metric_type);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn counter_value_from_f64_rejects_negative_and_nan_inputs() {
|
|
assert_eq!(counter_value_from_f64(-1.0), None);
|
|
assert_eq!(counter_value_from_f64(f64::NAN), None);
|
|
assert_eq!(counter_value_from_f64(f64::NEG_INFINITY), None);
|
|
}
|
|
|
|
#[test]
|
|
fn counter_value_from_f64_clamps_large_values() {
|
|
assert_eq!(counter_value_from_f64(42.0), Some(42));
|
|
assert_eq!(counter_value_from_f64(u64::MAX as f64 * 2.0), Some(u64::MAX));
|
|
}
|
|
}
|