Skip to main content

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}