feat(metrics): add per-IP and HTTP-request metrics, propagate source IP through proxy paths, and expose new metrics to the TS adapter
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
||||
|
||||
/// A single throughput sample.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ThroughputSample {
|
||||
pub timestamp_ms: u64,
|
||||
pub bytes_in: u64,
|
||||
@@ -106,6 +108,27 @@ impl ThroughputTracker {
|
||||
self.throughput(10)
|
||||
}
|
||||
|
||||
/// Return the last N samples in chronological order (oldest first).
|
||||
pub fn history(&self, window_seconds: usize) -> Vec<ThroughputSample> {
|
||||
let window = window_seconds.min(self.count);
|
||||
if window == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut result = Vec::with_capacity(window);
|
||||
for i in 0..window {
|
||||
let idx = if self.write_index >= i + 1 {
|
||||
self.write_index - i - 1
|
||||
} else {
|
||||
self.capacity - (i + 1 - self.write_index)
|
||||
};
|
||||
if idx < self.samples.len() {
|
||||
result.push(self.samples[idx]);
|
||||
}
|
||||
}
|
||||
result.reverse(); // Return oldest-first (chronological)
|
||||
result
|
||||
}
|
||||
|
||||
/// How long this tracker has been alive.
|
||||
pub fn uptime(&self) -> std::time::Duration {
|
||||
self.created_at.elapsed()
|
||||
@@ -170,4 +193,40 @@ mod tests {
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
assert!(tracker.uptime().as_millis() >= 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_returns_chronological() {
|
||||
let mut tracker = ThroughputTracker::new(60);
|
||||
for i in 1..=5 {
|
||||
tracker.record_bytes(i * 100, i * 200);
|
||||
tracker.sample();
|
||||
}
|
||||
let history = tracker.history(5);
|
||||
assert_eq!(history.len(), 5);
|
||||
// First sample should have 100 bytes_in, last should have 500
|
||||
assert_eq!(history[0].bytes_in, 100);
|
||||
assert_eq!(history[4].bytes_in, 500);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_wraps_around() {
|
||||
let mut tracker = ThroughputTracker::new(3); // Small capacity
|
||||
for i in 1..=5 {
|
||||
tracker.record_bytes(i * 100, i * 200);
|
||||
tracker.sample();
|
||||
}
|
||||
// Only last 3 should be retained
|
||||
let history = tracker.history(10); // Ask for more than available
|
||||
assert_eq!(history.len(), 3);
|
||||
assert_eq!(history[0].bytes_in, 300);
|
||||
assert_eq!(history[1].bytes_in, 400);
|
||||
assert_eq!(history[2].bytes_in, 500);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_empty() {
|
||||
let tracker = ThroughputTracker::new(60);
|
||||
let history = tracker.history(10);
|
||||
assert!(history.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user