mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-07 22:03:14 +00:00
7172e151de
Co-authored-by: heihutu <heihutu@gmail.com> Co-authored-by: houseme <housemecn@gmail.com>
312 lines
9.5 KiB
Rust
312 lines
9.5 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.
|
|
|
|
//! Performance metrics structure with atomic counters.
|
|
//!
|
|
//! Provides a shared metrics instance that can be used across all RustFS
|
|
//! components for consistent performance monitoring.
|
|
//!
|
|
//! # Example
|
|
//!
|
|
//! ```rust
|
|
//! use rustfs_io_metrics::PerformanceMetrics;
|
|
//!
|
|
//! let metrics = PerformanceMetrics::new();
|
|
//! metrics.record_cache_hit();
|
|
//! metrics.record_bytes_read(1024);
|
|
//! ```
|
|
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
|
|
/// Performance metrics with atomic counters.
|
|
///
|
|
/// Thread-safe metrics structure that can be shared across threads.
|
|
/// All fields use atomic operations for lock-free access.
|
|
#[derive(Debug)]
|
|
pub struct PerformanceMetrics {
|
|
// ===== Cache Metrics =====
|
|
/// Total cache hits (all levels)
|
|
pub cache_hits: AtomicU64,
|
|
/// Total cache misses
|
|
pub cache_misses: AtomicU64,
|
|
/// L1 cache hits (hot objects < 1MB)
|
|
pub l1_cache_hits: AtomicU64,
|
|
/// L2 cache hits (standard objects < 10MB)
|
|
pub l2_cache_hits: AtomicU64,
|
|
|
|
// ===== I/O Metrics =====
|
|
/// Total bytes read from disk
|
|
pub total_bytes_read: AtomicU64,
|
|
/// Total bytes written to disk
|
|
pub total_bytes_written: AtomicU64,
|
|
/// Disk read operation count
|
|
pub disk_read_count: AtomicU64,
|
|
/// Disk write operation count
|
|
pub disk_write_count: AtomicU64,
|
|
/// Average I/O latency in microseconds
|
|
pub avg_io_latency_us: AtomicU64,
|
|
/// P95 I/O latency in microseconds
|
|
pub p95_io_latency_us: AtomicU64,
|
|
/// P99 I/O latency in microseconds
|
|
pub p99_io_latency_us: AtomicU64,
|
|
|
|
// ===== Concurrency Metrics =====
|
|
/// Current concurrent requests
|
|
pub current_concurrent_requests: AtomicU64,
|
|
/// Peak concurrent requests
|
|
pub peak_concurrent_requests: AtomicU64,
|
|
|
|
// ===== Error Metrics =====
|
|
/// Total errors
|
|
pub total_errors: AtomicU64,
|
|
/// Timeout errors
|
|
pub timeout_errors: AtomicU64,
|
|
/// Disk errors
|
|
pub disk_errors: AtomicU64,
|
|
}
|
|
|
|
impl PerformanceMetrics {
|
|
/// Create a new PerformanceMetrics instance with all values initialized to zero.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
cache_hits: AtomicU64::new(0),
|
|
cache_misses: AtomicU64::new(0),
|
|
l1_cache_hits: AtomicU64::new(0),
|
|
l2_cache_hits: AtomicU64::new(0),
|
|
total_bytes_read: AtomicU64::new(0),
|
|
total_bytes_written: AtomicU64::new(0),
|
|
disk_read_count: AtomicU64::new(0),
|
|
disk_write_count: AtomicU64::new(0),
|
|
avg_io_latency_us: AtomicU64::new(0),
|
|
p95_io_latency_us: AtomicU64::new(0),
|
|
p99_io_latency_us: AtomicU64::new(0),
|
|
current_concurrent_requests: AtomicU64::new(0),
|
|
peak_concurrent_requests: AtomicU64::new(0),
|
|
total_errors: AtomicU64::new(0),
|
|
timeout_errors: AtomicU64::new(0),
|
|
disk_errors: AtomicU64::new(0),
|
|
}
|
|
}
|
|
|
|
/// Calculate the cache hit rate (0.0 to 1.0).
|
|
///
|
|
/// Returns 0.0 if there have been no cache accesses.
|
|
pub fn cache_hit_rate(&self) -> f64 {
|
|
let hits = self.cache_hits.load(Ordering::Relaxed);
|
|
let misses = self.cache_misses.load(Ordering::Relaxed);
|
|
let total = hits + misses;
|
|
|
|
if total == 0 { 0.0 } else { hits as f64 / total as f64 }
|
|
}
|
|
|
|
/// Get the L1 cache hit rate (0.0 to 1.0).
|
|
///
|
|
/// Returns the ratio of L1 hits to total cache hits.
|
|
pub fn l1_hit_rate(&self) -> f64 {
|
|
let l1_hits = self.l1_cache_hits.load(Ordering::Relaxed);
|
|
let total_hits = self.cache_hits.load(Ordering::Relaxed);
|
|
|
|
if total_hits == 0 {
|
|
0.0
|
|
} else {
|
|
l1_hits as f64 / total_hits as f64
|
|
}
|
|
}
|
|
|
|
// ===== Cache Recording Methods =====
|
|
|
|
/// Record a cache hit.
|
|
#[inline]
|
|
pub fn record_cache_hit(&self) {
|
|
self.cache_hits.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Record a cache miss.
|
|
#[inline]
|
|
pub fn record_cache_miss(&self) {
|
|
self.cache_misses.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Record an L1 cache hit (includes recording total cache hit).
|
|
#[inline]
|
|
pub fn record_l1_hit(&self) {
|
|
self.l1_cache_hits.fetch_add(1, Ordering::Relaxed);
|
|
self.record_cache_hit();
|
|
}
|
|
|
|
/// Record an L2 cache hit (includes recording total cache hit).
|
|
#[inline]
|
|
pub fn record_l2_hit(&self) {
|
|
self.l2_cache_hits.fetch_add(1, Ordering::Relaxed);
|
|
self.record_cache_hit();
|
|
}
|
|
|
|
// ===== I/O Recording Methods =====
|
|
|
|
/// Record bytes read.
|
|
#[inline]
|
|
pub fn record_bytes_read(&self, bytes: u64) {
|
|
self.total_bytes_read.fetch_add(bytes, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Record bytes written.
|
|
#[inline]
|
|
pub fn record_bytes_written(&self, bytes: u64) {
|
|
self.total_bytes_written.fetch_add(bytes, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Record a disk read operation.
|
|
#[inline]
|
|
pub fn record_disk_read(&self) {
|
|
self.disk_read_count.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Record a disk write operation.
|
|
#[inline]
|
|
pub fn record_disk_write(&self) {
|
|
self.disk_write_count.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
// ===== Concurrency Recording Methods =====
|
|
|
|
/// Update concurrent request count and track peak.
|
|
#[inline]
|
|
pub fn update_concurrent_requests(&self, count: u64) {
|
|
self.current_concurrent_requests.store(count, Ordering::Relaxed);
|
|
|
|
// Update peak using lock-free CAS loop
|
|
let mut peak = self.peak_concurrent_requests.load(Ordering::Relaxed);
|
|
loop {
|
|
if count <= peak {
|
|
break;
|
|
}
|
|
match self
|
|
.peak_concurrent_requests
|
|
.compare_exchange_weak(peak, count, Ordering::Relaxed, Ordering::Relaxed)
|
|
{
|
|
Ok(_) => break,
|
|
Err(new_peak) => peak = new_peak,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== Error Recording Methods =====
|
|
|
|
/// Record a generic error.
|
|
#[inline]
|
|
pub fn record_error(&self) {
|
|
self.total_errors.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Record a timeout error (includes recording total error).
|
|
#[inline]
|
|
pub fn record_timeout(&self) {
|
|
self.timeout_errors.fetch_add(1, Ordering::Relaxed);
|
|
self.record_error();
|
|
}
|
|
|
|
/// Record a disk error (includes recording total error).
|
|
#[inline]
|
|
pub fn record_disk_error(&self) {
|
|
self.disk_errors.fetch_add(1, Ordering::Relaxed);
|
|
self.record_error();
|
|
}
|
|
}
|
|
|
|
impl Default for PerformanceMetrics {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_metrics_creation() {
|
|
let metrics = PerformanceMetrics::new();
|
|
assert_eq!(metrics.cache_hits.load(Ordering::Relaxed), 0);
|
|
assert_eq!(metrics.cache_misses.load(Ordering::Relaxed), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_cache_hit_rate() {
|
|
let metrics = PerformanceMetrics::new();
|
|
|
|
// No accesses yet
|
|
assert_eq!(metrics.cache_hit_rate(), 0.0);
|
|
|
|
// Record some hits and misses
|
|
for _ in 0..5 {
|
|
metrics.record_cache_hit();
|
|
}
|
|
for _ in 0..3 {
|
|
metrics.record_cache_miss();
|
|
}
|
|
|
|
assert_eq!(metrics.cache_hit_rate(), 5.0 / 8.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_l1_hit_rate() {
|
|
let metrics = PerformanceMetrics::new();
|
|
|
|
metrics.record_l1_hit(); // Records both L1 and total
|
|
metrics.record_l2_hit(); // Records L2 and total
|
|
metrics.record_cache_hit(); // Direct total hit
|
|
|
|
assert_eq!(metrics.cache_hits.load(Ordering::Relaxed), 3);
|
|
assert_eq!(metrics.l1_cache_hits.load(Ordering::Relaxed), 1);
|
|
assert_eq!(metrics.l1_hit_rate(), 1.0 / 3.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_io_recording() {
|
|
let metrics = PerformanceMetrics::new();
|
|
|
|
metrics.record_bytes_read(1024 * 1024); // 1MB
|
|
metrics.record_disk_read();
|
|
|
|
assert_eq!(metrics.total_bytes_read.load(Ordering::Relaxed), 1024 * 1024);
|
|
assert_eq!(metrics.disk_read_count.load(Ordering::Relaxed), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_concurrent_tracking() {
|
|
let metrics = PerformanceMetrics::new();
|
|
|
|
metrics.update_concurrent_requests(5);
|
|
assert_eq!(metrics.current_concurrent_requests.load(Ordering::Relaxed), 5);
|
|
assert_eq!(metrics.peak_concurrent_requests.load(Ordering::Relaxed), 5);
|
|
|
|
metrics.update_concurrent_requests(3);
|
|
assert_eq!(metrics.current_concurrent_requests.load(Ordering::Relaxed), 3);
|
|
assert_eq!(metrics.peak_concurrent_requests.load(Ordering::Relaxed), 5); // Peak stays at 5
|
|
}
|
|
|
|
#[test]
|
|
fn test_error_recording() {
|
|
let metrics = PerformanceMetrics::new();
|
|
|
|
metrics.record_timeout();
|
|
assert_eq!(metrics.total_errors.load(Ordering::Relaxed), 1);
|
|
assert_eq!(metrics.timeout_errors.load(Ordering::Relaxed), 1);
|
|
|
|
metrics.record_disk_error();
|
|
assert_eq!(metrics.total_errors.load(Ordering::Relaxed), 2);
|
|
assert_eq!(metrics.disk_errors.load(Ordering::Relaxed), 1);
|
|
}
|
|
}
|