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    /// 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}