use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
use xds_core::NodeHash;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StreamId(u64);
impl StreamId {
pub fn new() -> Self {
static COUNTER: AtomicU64 = AtomicU64::new(1);
Self(COUNTER.fetch_add(1, Ordering::Relaxed))
}
#[inline]
pub fn as_u64(&self) -> u64 {
self.0
}
}
impl Default for StreamId {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for StreamId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "stream-{}", self.0)
}
}
#[derive(Debug)]
pub struct StreamContext {
id: StreamId,
node_hash: Option<NodeHash>,
node_id: Option<String>,
created_at: Instant,
requests: AtomicU64,
responses: AtomicU64,
last_request: std::sync::Mutex<Instant>,
}
impl StreamContext {
pub fn new() -> Self {
let now = Instant::now();
Self {
id: StreamId::new(),
node_hash: None,
node_id: None,
created_at: now,
requests: AtomicU64::new(0),
responses: AtomicU64::new(0),
last_request: std::sync::Mutex::new(now),
}
}
#[inline]
pub fn id(&self) -> StreamId {
self.id
}
#[inline]
pub fn node_hash(&self) -> Option<NodeHash> {
self.node_hash
}
#[inline]
pub fn node_id(&self) -> Option<&str> {
self.node_id.as_deref()
}
pub fn set_node(&mut self, node_id: String, node_hash: NodeHash) {
self.node_id = Some(node_id);
self.node_hash = Some(node_hash);
}
#[inline]
pub fn created_at(&self) -> Instant {
self.created_at
}
#[inline]
pub fn duration(&self) -> std::time::Duration {
self.created_at.elapsed()
}
pub fn record_request(&self) {
self.requests.fetch_add(1, Ordering::Relaxed);
if let Ok(mut last) = self.last_request.lock() {
*last = Instant::now();
}
}
pub fn record_response(&self) {
self.responses.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn request_count(&self) -> u64 {
self.requests.load(Ordering::Relaxed)
}
#[inline]
pub fn response_count(&self) -> u64 {
self.responses.load(Ordering::Relaxed)
}
pub fn idle_time(&self) -> std::time::Duration {
self.last_request
.lock()
.map(|t| t.elapsed())
.unwrap_or_default()
}
}
impl Default for StreamContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stream_id_unique() {
let id1 = StreamId::new();
let id2 = StreamId::new();
assert_ne!(id1, id2);
}
#[test]
fn stream_context_basic() {
let ctx = StreamContext::new();
assert_eq!(ctx.request_count(), 0);
assert_eq!(ctx.response_count(), 0);
assert!(ctx.node_hash().is_none());
}
#[test]
fn stream_context_counting() {
let ctx = StreamContext::new();
ctx.record_request();
ctx.record_request();
ctx.record_response();
assert_eq!(ctx.request_count(), 2);
assert_eq!(ctx.response_count(), 1);
}
#[test]
fn stream_context_node() {
let mut ctx = StreamContext::new();
let hash = NodeHash::from_id("test-node");
ctx.set_node("test-node".to_string(), hash);
assert_eq!(ctx.node_id(), Some("test-node"));
assert_eq!(ctx.node_hash(), Some(hash));
}
}