net_io/events.rs
1//! Typed events for logs, metrics, and detection.
2
3use std::net::SocketAddr;
4use std::time::{Duration, Instant};
5
6use crate::control::ControlState;
7use crate::driver::DriverKind;
8use crate::error::{CloseReason, KillReason, LimitKind, OsErrorClass, RefusalCause};
9use crate::id::{ConnectionId, ListenerId, WorkerId};
10use crate::info::Lane;
11
12/// Receives events. Runs on worker threads: copy what you need and return.
13///
14/// # Examples
15///
16/// ```
17/// use net_io::{CoalescedKind, Event, EventSink};
18/// use std::sync::atomic::{AtomicU64, Ordering};
19///
20/// #[derive(Default)]
21/// struct Counts { closed: AtomicU64, refused: AtomicU64, limits: AtomicU64 }
22/// impl EventSink for Counts {
23/// fn event(&self, event: &Event) {
24/// match event {
25/// Event::Closed { .. } => { self.closed.fetch_add(1, Ordering::Relaxed); }
26/// Event::Refused { .. } => { self.refused.fetch_add(1, Ordering::Relaxed); }
27/// Event::LimitHit { .. } => { self.limits.fetch_add(1, Ordering::Relaxed); }
28/// Event::Coalesced { kind: CoalescedKind::Refused(_), count } => { self.refused.fetch_add(u64::from(*count), Ordering::Relaxed); }
29/// Event::Coalesced { kind: CoalescedKind::LimitHit(_), count } => { self.limits.fetch_add(u64::from(*count), Ordering::Relaxed); }
30/// _ => {}
31/// }
32/// }
33/// }
34/// ```
35pub trait EventSink: Send + Sync + 'static {
36 /// One event.
37 fn event(&self, event: &Event);
38}
39
40/// Discards every event; the builder's default. Compiles to nothing.
41///
42/// ```
43/// use net_io::{Event, EventSink, NoopSink};
44/// NoopSink.event(&Event::DoorOpened);
45/// ```
46#[derive(Clone, Copy, Debug, Default)]
47pub struct NoopSink;
48
49impl EventSink for NoopSink {
50 #[inline(always)]
51 fn event(&self, _: &Event) {}
52}
53
54/// What was folded into a [`Event::Coalesced`].
55#[non_exhaustive]
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum CoalescedKind {
58 /// Refusals with this cause.
59 Refused(RefusalCause),
60 /// Limit hits of this kind.
61 LimitHit(LimitKind),
62}
63
64/// One event: fixed size, `Copy`, borrowing nothing.
65#[non_exhaustive]
66#[derive(Clone, Copy, Debug)]
67pub enum Event {
68 /// A listener was bound.
69 ListenerOpened {
70 /// The listener.
71 listener: ListenerId,
72 /// Its address.
73 local: Option<SocketAddr>,
74 /// The effective backlog, when known.
75 backlog: Option<u32>,
76 },
77 /// A listener was removed (`Server::remove_listener`); its socket is closed.
78 ListenerClosed {
79 /// The listener.
80 listener: ListenerId,
81 },
82 /// Accepting started.
83 DoorOpened,
84 /// Accepting stopped.
85 DoorClosed,
86 /// A connection was accepted.
87 Accepted {
88 /// The connection.
89 id: ConnectionId,
90 /// Its peer.
91 peer: Option<SocketAddr>,
92 /// The listener.
93 listener: ListenerId,
94 },
95 /// A connection was refused.
96 Refused {
97 /// Its peer.
98 peer: Option<SocketAddr>,
99 /// Why.
100 cause: RefusalCause,
101 },
102 /// The gate admitted a connection.
103 Admitted {
104 /// The connection.
105 id: ConnectionId,
106 /// Its lane.
107 lane: Lane,
108 },
109 /// A trusted PROXY header named the real client.
110 ProxyResolved {
111 /// The connection.
112 id: ConnectionId,
113 /// The real client.
114 real_peer: SocketAddr,
115 },
116 /// The handshake finished.
117 HandshakeDone {
118 /// The connection.
119 id: ConnectionId,
120 /// How long it took.
121 took: Duration,
122 },
123 /// A configured bound was hit.
124 LimitHit {
125 /// The connection, if one.
126 id: Option<ConnectionId>,
127 /// Which bound.
128 which: LimitKind,
129 },
130 /// A connection was killed.
131 Killed {
132 /// The connection.
133 id: ConnectionId,
134 /// The caller's code.
135 reason: KillReason,
136 },
137 /// The gate denied a write.
138 EgressDenied {
139 /// The connection.
140 id: ConnectionId,
141 /// The gate's code.
142 code: u16,
143 },
144 /// A connection closed: exactly once per connection.
145 Closed {
146 /// The connection.
147 id: ConnectionId,
148 /// Why.
149 reason: CloseReason,
150 /// Bytes received.
151 bytes_in: u64,
152 /// Bytes sent.
153 bytes_out: u64,
154 /// How long it was open.
155 lifetime: Duration,
156 },
157 /// An accept failed for a reason that affects only one connection.
158 AcceptError {
159 /// The listener.
160 listener: ListenerId,
161 /// The OS error class.
162 class: OsErrorClass,
163 },
164 /// The process ran out of descriptors; accepting pauses.
165 DescriptorPressure {
166 /// The listener.
167 listener: ListenerId,
168 /// How long accepting pauses.
169 backoff: Duration,
170 },
171 /// A listener failed and was closed.
172 ListenerFailed {
173 /// The listener.
174 listener: ListenerId,
175 /// The OS error class.
176 class: OsErrorClass,
177 },
178 /// A drain started.
179 DrainStarted {
180 /// Connections open when it started.
181 open: u32,
182 },
183 /// A drain finished.
184 DrainFinished {
185 /// Connections that finished in time.
186 graceful: u32,
187 /// Connections killed at the deadline.
188 killed: u32,
189 },
190 /// Workers reacted to a control-line change.
191 ControlReacted {
192 /// The new state.
193 state: ControlState,
194 },
195 /// A completion arrived for a connection that had closed.
196 StaleCompletion {
197 /// The worker.
198 worker: WorkerId,
199 },
200 /// A session panicked; its connection was closed.
201 SessionPanicked {
202 /// The connection.
203 id: ConnectionId,
204 },
205 /// The requested driver could not run; another was used.
206 DriverFallback {
207 /// The requested driver.
208 wanted: DriverKind,
209 /// The driver in use.
210 using: DriverKind,
211 },
212 /// Refusals or limit hits above 1,000 per second per worker, folded into one event.
213 Coalesced {
214 /// What was folded.
215 kind: CoalescedKind,
216 /// How many.
217 count: u32,
218 },
219}
220
221/// Folds floods of refusal and limit events: above 1,000 per second (per
222/// accepting thread or worker) they become one `Coalesced` event per second.
223#[derive(Debug)]
224pub(crate) struct Coalescer {
225 window_start: Option<Instant>,
226 in_window: u32,
227 folded: [(CoalescedKind, u32); 8],
228 folded_len: usize,
229}
230
231const COALESCE_ABOVE: u32 = 1000;
232
233impl Coalescer {
234 pub(crate) fn new() -> Coalescer {
235 Coalescer {
236 window_start: None,
237 in_window: 0,
238 folded: [(CoalescedKind::LimitHit(LimitKind::Connections), 0); 8],
239 folded_len: 0,
240 }
241 }
242
243 /// Emits `event` (a `Refused` or `LimitHit`) or folds it.
244 pub(crate) fn emit(
245 &mut self,
246 sink: &dyn EventSink,
247 now: Instant,
248 kind: CoalescedKind,
249 event: Event,
250 ) {
251 let start = *self.window_start.get_or_insert(now);
252 if now.duration_since(start) >= Duration::from_secs(1) {
253 self.flush(sink);
254 self.window_start = Some(now);
255 self.in_window = 0;
256 }
257 self.in_window = self.in_window.saturating_add(1);
258 if self.in_window <= COALESCE_ABOVE {
259 sink.event(&event);
260 return;
261 }
262 if let Some(entry) = self.folded[..self.folded_len]
263 .iter_mut()
264 .find(|(k, _)| *k == kind)
265 {
266 entry.1 = entry.1.saturating_add(1);
267 } else if self.folded_len < self.folded.len() {
268 self.folded[self.folded_len] = (kind, 1);
269 self.folded_len += 1;
270 } else {
271 // More distinct kinds than slots in one second: count them under the last slot.
272 self.folded[self.folded.len() - 1].1 =
273 self.folded[self.folded.len() - 1].1.saturating_add(1);
274 }
275 }
276
277 pub(crate) fn flush(&mut self, sink: &dyn EventSink) {
278 for (kind, count) in &self.folded[..self.folded_len] {
279 sink.event(&Event::Coalesced {
280 kind: *kind,
281 count: *count,
282 });
283 }
284 self.folded_len = 0;
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use std::sync::atomic::{AtomicU64, Ordering};
292
293 #[derive(Default)]
294 struct Count {
295 plain: AtomicU64,
296 folded: AtomicU64,
297 }
298 impl EventSink for Count {
299 fn event(&self, e: &Event) {
300 match e {
301 Event::Coalesced { count, .. } => {
302 self.folded.fetch_add(u64::from(*count), Ordering::Relaxed);
303 }
304 _ => {
305 self.plain.fetch_add(1, Ordering::Relaxed);
306 }
307 }
308 }
309 }
310
311 #[test]
312 fn test_event_size_at_most_64_bytes() {
313 assert!(
314 std::mem::size_of::<Event>() <= 64,
315 "{}",
316 std::mem::size_of::<Event>()
317 );
318 }
319
320 #[test]
321 fn test_coalescer_folds_above_threshold() {
322 let sink = Count::default();
323 let mut c = Coalescer::new();
324 let t = Instant::now();
325 let kind = CoalescedKind::Refused(RefusalCause::GlobalCap);
326 let ev = Event::Refused {
327 peer: None,
328 cause: RefusalCause::GlobalCap,
329 };
330 for _ in 0..5000 {
331 c.emit(&sink, t, kind, ev);
332 }
333 c.emit(&sink, t + Duration::from_secs(1), kind, ev);
334 assert_eq!(sink.plain.load(Ordering::Relaxed), 1001);
335 assert_eq!(sink.folded.load(Ordering::Relaxed), 4000);
336 }
337}