use std::net::SocketAddr;
use std::time::{Duration, Instant};
use crate::control::ControlState;
use crate::driver::DriverKind;
use crate::error::{CloseReason, KillReason, LimitKind, OsErrorClass, RefusalCause};
use crate::id::{ConnectionId, ListenerId, WorkerId};
use crate::info::Lane;
pub trait EventSink: Send + Sync + 'static {
fn event(&self, event: &Event);
}
#[derive(Clone, Copy, Debug, Default)]
pub struct NoopSink;
impl EventSink for NoopSink {
#[inline(always)]
fn event(&self, _: &Event) {}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CoalescedKind {
Refused(RefusalCause),
LimitHit(LimitKind),
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug)]
pub enum Event {
ListenerOpened {
listener: ListenerId,
local: Option<SocketAddr>,
backlog: Option<u32>,
},
ListenerClosed {
listener: ListenerId,
},
DoorOpened,
DoorClosed,
Accepted {
id: ConnectionId,
peer: Option<SocketAddr>,
listener: ListenerId,
},
Refused {
peer: Option<SocketAddr>,
cause: RefusalCause,
},
Admitted {
id: ConnectionId,
lane: Lane,
},
ProxyResolved {
id: ConnectionId,
real_peer: SocketAddr,
},
HandshakeDone {
id: ConnectionId,
took: Duration,
},
LimitHit {
id: Option<ConnectionId>,
which: LimitKind,
},
Killed {
id: ConnectionId,
reason: KillReason,
},
EgressDenied {
id: ConnectionId,
code: u16,
},
Closed {
id: ConnectionId,
reason: CloseReason,
bytes_in: u64,
bytes_out: u64,
lifetime: Duration,
},
AcceptError {
listener: ListenerId,
class: OsErrorClass,
},
DescriptorPressure {
listener: ListenerId,
backoff: Duration,
},
ListenerFailed {
listener: ListenerId,
class: OsErrorClass,
},
DrainStarted {
open: u32,
},
DrainFinished {
graceful: u32,
killed: u32,
},
ControlReacted {
state: ControlState,
},
StaleCompletion {
worker: WorkerId,
},
SessionPanicked {
id: ConnectionId,
},
DriverFallback {
wanted: DriverKind,
using: DriverKind,
},
Coalesced {
kind: CoalescedKind,
count: u32,
},
}
#[derive(Debug)]
pub(crate) struct Coalescer {
window_start: Option<Instant>,
in_window: u32,
folded: [(CoalescedKind, u32); 8],
folded_len: usize,
}
const COALESCE_ABOVE: u32 = 1000;
impl Coalescer {
pub(crate) fn new() -> Coalescer {
Coalescer {
window_start: None,
in_window: 0,
folded: [(CoalescedKind::LimitHit(LimitKind::Connections), 0); 8],
folded_len: 0,
}
}
pub(crate) fn emit(
&mut self,
sink: &dyn EventSink,
now: Instant,
kind: CoalescedKind,
event: Event,
) {
let start = *self.window_start.get_or_insert(now);
if now.duration_since(start) >= Duration::from_secs(1) {
self.flush(sink);
self.window_start = Some(now);
self.in_window = 0;
}
self.in_window = self.in_window.saturating_add(1);
if self.in_window <= COALESCE_ABOVE {
sink.event(&event);
return;
}
if let Some(entry) = self.folded[..self.folded_len]
.iter_mut()
.find(|(k, _)| *k == kind)
{
entry.1 = entry.1.saturating_add(1);
} else if self.folded_len < self.folded.len() {
self.folded[self.folded_len] = (kind, 1);
self.folded_len += 1;
} else {
self.folded[self.folded.len() - 1].1 =
self.folded[self.folded.len() - 1].1.saturating_add(1);
}
}
pub(crate) fn flush(&mut self, sink: &dyn EventSink) {
for (kind, count) in &self.folded[..self.folded_len] {
sink.event(&Event::Coalesced {
kind: *kind,
count: *count,
});
}
self.folded_len = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Default)]
struct Count {
plain: AtomicU64,
folded: AtomicU64,
}
impl EventSink for Count {
fn event(&self, e: &Event) {
match e {
Event::Coalesced { count, .. } => {
self.folded.fetch_add(u64::from(*count), Ordering::Relaxed);
}
_ => {
self.plain.fetch_add(1, Ordering::Relaxed);
}
}
}
}
#[test]
fn test_event_size_at_most_64_bytes() {
assert!(
std::mem::size_of::<Event>() <= 64,
"{}",
std::mem::size_of::<Event>()
);
}
#[test]
fn test_coalescer_folds_above_threshold() {
let sink = Count::default();
let mut c = Coalescer::new();
let t = Instant::now();
let kind = CoalescedKind::Refused(RefusalCause::GlobalCap);
let ev = Event::Refused {
peer: None,
cause: RefusalCause::GlobalCap,
};
for _ in 0..5000 {
c.emit(&sink, t, kind, ev);
}
c.emit(&sink, t + Duration::from_secs(1), kind, ev);
assert_eq!(sink.plain.load(Ordering::Relaxed), 1001);
assert_eq!(sink.folded.load(Ordering::Relaxed), 4000);
}
}