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 /// Accepting started.
78 DoorOpened,
79 /// Accepting stopped.
80 DoorClosed,
81 /// A connection was accepted.
82 Accepted {
83 /// The connection.
84 id: ConnectionId,
85 /// Its peer.
86 peer: Option<SocketAddr>,
87 /// The listener.
88 listener: ListenerId,
89 },
90 /// A connection was refused.
91 Refused {
92 /// Its peer.
93 peer: Option<SocketAddr>,
94 /// Why.
95 cause: RefusalCause,
96 },
97 /// The gate admitted a connection.
98 Admitted {
99 /// The connection.
100 id: ConnectionId,
101 /// Its lane.
102 lane: Lane,
103 },
104 /// A trusted PROXY header named the real client.
105 ProxyResolved {
106 /// The connection.
107 id: ConnectionId,
108 /// The real client.
109 real_peer: SocketAddr,
110 },
111 /// The handshake finished.
112 HandshakeDone {
113 /// The connection.
114 id: ConnectionId,
115 /// How long it took.
116 took: Duration,
117 },
118 /// A configured bound was hit.
119 LimitHit {
120 /// The connection, if one.
121 id: Option<ConnectionId>,
122 /// Which bound.
123 which: LimitKind,
124 },
125 /// A connection was killed.
126 Killed {
127 /// The connection.
128 id: ConnectionId,
129 /// The caller's code.
130 reason: KillReason,
131 },
132 /// The gate denied a write.
133 EgressDenied {
134 /// The connection.
135 id: ConnectionId,
136 /// The gate's code.
137 code: u16,
138 },
139 /// A connection closed: exactly once per connection.
140 Closed {
141 /// The connection.
142 id: ConnectionId,
143 /// Why.
144 reason: CloseReason,
145 /// Bytes received.
146 bytes_in: u64,
147 /// Bytes sent.
148 bytes_out: u64,
149 /// How long it was open.
150 lifetime: Duration,
151 },
152 /// An accept failed for a reason that affects only one connection.
153 AcceptError {
154 /// The listener.
155 listener: ListenerId,
156 /// The OS error class.
157 class: OsErrorClass,
158 },
159 /// The process ran out of descriptors; accepting pauses.
160 DescriptorPressure {
161 /// The listener.
162 listener: ListenerId,
163 /// How long accepting pauses.
164 backoff: Duration,
165 },
166 /// A listener failed and was closed.
167 ListenerFailed {
168 /// The listener.
169 listener: ListenerId,
170 /// The OS error class.
171 class: OsErrorClass,
172 },
173 /// A drain started.
174 DrainStarted {
175 /// Connections open when it started.
176 open: u32,
177 },
178 /// A drain finished.
179 DrainFinished {
180 /// Connections that finished in time.
181 graceful: u32,
182 /// Connections killed at the deadline.
183 killed: u32,
184 },
185 /// Workers reacted to a control-line change.
186 ControlReacted {
187 /// The new state.
188 state: ControlState,
189 },
190 /// A completion arrived for a connection that had closed.
191 StaleCompletion {
192 /// The worker.
193 worker: WorkerId,
194 },
195 /// A session panicked; its connection was closed.
196 SessionPanicked {
197 /// The connection.
198 id: ConnectionId,
199 },
200 /// The requested driver could not run; another was used.
201 DriverFallback {
202 /// The requested driver.
203 wanted: DriverKind,
204 /// The driver in use.
205 using: DriverKind,
206 },
207 /// Refusals or limit hits above 1,000 per second per worker, folded into one event.
208 Coalesced {
209 /// What was folded.
210 kind: CoalescedKind,
211 /// How many.
212 count: u32,
213 },
214}
215
216/// Folds floods of refusal and limit events: above 1,000 per second (per
217/// accepting thread or worker) they become one `Coalesced` event per second.
218#[derive(Debug)]
219pub(crate) struct Coalescer {
220 window_start: Option<Instant>,
221 in_window: u32,
222 folded: [(CoalescedKind, u32); 8],
223 folded_len: usize,
224}
225
226const COALESCE_ABOVE: u32 = 1000;
227
228impl Coalescer {
229 pub(crate) fn new() -> Coalescer {
230 Coalescer {
231 window_start: None,
232 in_window: 0,
233 folded: [(CoalescedKind::LimitHit(LimitKind::Connections), 0); 8],
234 folded_len: 0,
235 }
236 }
237
238 /// Emits `event` (a `Refused` or `LimitHit`) or folds it.
239 pub(crate) fn emit(
240 &mut self,
241 sink: &dyn EventSink,
242 now: Instant,
243 kind: CoalescedKind,
244 event: Event,
245 ) {
246 let start = *self.window_start.get_or_insert(now);
247 if now.duration_since(start) >= Duration::from_secs(1) {
248 self.flush(sink);
249 self.window_start = Some(now);
250 self.in_window = 0;
251 }
252 self.in_window = self.in_window.saturating_add(1);
253 if self.in_window <= COALESCE_ABOVE {
254 sink.event(&event);
255 return;
256 }
257 if let Some(entry) = self.folded[..self.folded_len]
258 .iter_mut()
259 .find(|(k, _)| *k == kind)
260 {
261 entry.1 = entry.1.saturating_add(1);
262 } else if self.folded_len < self.folded.len() {
263 self.folded[self.folded_len] = (kind, 1);
264 self.folded_len += 1;
265 } else {
266 // More distinct kinds than slots in one second: count them under the last slot.
267 self.folded[self.folded.len() - 1].1 =
268 self.folded[self.folded.len() - 1].1.saturating_add(1);
269 }
270 }
271
272 pub(crate) fn flush(&mut self, sink: &dyn EventSink) {
273 for (kind, count) in &self.folded[..self.folded_len] {
274 sink.event(&Event::Coalesced {
275 kind: *kind,
276 count: *count,
277 });
278 }
279 self.folded_len = 0;
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use std::sync::atomic::{AtomicU64, Ordering};
287
288 #[derive(Default)]
289 struct Count {
290 plain: AtomicU64,
291 folded: AtomicU64,
292 }
293 impl EventSink for Count {
294 fn event(&self, e: &Event) {
295 match e {
296 Event::Coalesced { count, .. } => {
297 self.folded.fetch_add(u64::from(*count), Ordering::Relaxed);
298 }
299 _ => {
300 self.plain.fetch_add(1, Ordering::Relaxed);
301 }
302 }
303 }
304 }
305
306 #[test]
307 fn test_event_size_at_most_64_bytes() {
308 assert!(
309 std::mem::size_of::<Event>() <= 64,
310 "{}",
311 std::mem::size_of::<Event>()
312 );
313 }
314
315 #[test]
316 fn test_coalescer_folds_above_threshold() {
317 let sink = Count::default();
318 let mut c = Coalescer::new();
319 let t = Instant::now();
320 let kind = CoalescedKind::Refused(RefusalCause::GlobalCap);
321 let ev = Event::Refused {
322 peer: None,
323 cause: RefusalCause::GlobalCap,
324 };
325 for _ in 0..5000 {
326 c.emit(&sink, t, kind, ev);
327 }
328 c.emit(&sink, t + Duration::from_secs(1), kind, ev);
329 assert_eq!(sink.plain.load(Ordering::Relaxed), 1001);
330 assert_eq!(sink.folded.load(Ordering::Relaxed), 4000);
331 }
332}