@mark1russell7/prober 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,696 @@
1
+ //! The record the kernel side writes (`bpf/prober.h`'s `struct ev`), decoded field by field
2
+ //! without `unsafe`, and the JSON line the harness reads.
3
+ //!
4
+ //! A line's keys are in byte order, as serde_json writes any object it holds as a map. The
5
+ //! structs below declare their fields in that order and serialize straight to the writer: no map
6
+ //! is built per record, and the Go and C collectors write the same bytes (the identity fixture).
7
+
8
+ use crate::{quantity, Quantity};
9
+ use serde::{Serialize, Serializer};
10
+ use std::borrow::Cow;
11
+ use std::net::Ipv4Addr;
12
+
13
+ /// `struct ev`'s size in bytes. In order: seven u64, five u32, seven i32, two u16, two u32,
14
+ /// `struct tcp_now` (nine u32), `struct seg` (four u32), two u32 for a timer and a 16-byte comm.
15
+ /// Every field sits on its own alignment, so the C compiler adds no padding.
16
+ pub const SIZE: usize = 192;
17
+
18
+ /// The event kinds, in `prober.h`'s order, from 1.
19
+ const KINDS: [&str; 14] = [
20
+ "state", "accept", "retrans", "rst_tx", "rst_rx", "error", "destroy", "tw_enter", "tw_kill",
21
+ "req_new", "rst_v4", "probe", "rx", "timer",
22
+ ];
23
+
24
+ /// ICSK_TIME_LOSS_PROBE in include/net/inet_connection_sock.h: the tail loss probe is armed
25
+ const ICSK_TIME_LOSS_PROBE: u32 = 5;
26
+
27
+ /// `prober.h`'s TIMER_OWNED: a timer record's flag that the socket's owner held the socket
28
+ const TIMER_OWNED: u32 = 0x100;
29
+
30
+ #[derive(Debug, Clone, PartialEq)]
31
+ pub struct TcpNow {
32
+ pub retransmits: u32,
33
+ pub pending: u32,
34
+ pub backoff: u32,
35
+ pub probes_out: u32,
36
+ /// icsk_rto, in jiffies
37
+ pub rto: u32,
38
+ pub srtt_us: u32,
39
+ pub snd_una: u32,
40
+ pub snd_nxt: u32,
41
+ pub rcv_nxt: u32,
42
+ }
43
+
44
+ #[derive(Debug, Clone, PartialEq)]
45
+ pub struct Seg {
46
+ pub seq: u32,
47
+ pub ack: u32,
48
+ pub len: u32,
49
+ pub flags: u32,
50
+ }
51
+
52
+ #[derive(Debug, Clone, PartialEq)]
53
+ pub struct Ev {
54
+ pub mono: u64,
55
+ pub tai: u64,
56
+ pub cookie: u64,
57
+ pub nscookie: u64,
58
+ pub cgid: u64,
59
+ pub skaddr: u64,
60
+ pub ino: u64,
61
+ pub kind: u32,
62
+ pub netns: u32,
63
+ pub saddr: Ipv4Addr,
64
+ pub daddr: Ipv4Addr,
65
+ pub extra: u32,
66
+ pub pid: i32,
67
+ pub fd: i32,
68
+ pub oldst: i32,
69
+ pub newst: i32,
70
+ pub state: i32,
71
+ pub err: i32,
72
+ pub owner: i32,
73
+ pub sport: u16,
74
+ pub dport: u16,
75
+ pub tcp: Option<TcpNow>,
76
+ pub seg: Option<Seg>,
77
+ /// a timer: how many jiffies behind its wheel slot the CPU ran it
78
+ pub behind: u32,
79
+ /// and how many after the expiry its owner asked for
80
+ pub late: u32,
81
+ /// the task's name as the kernel holds it: up to 16 bytes, not always UTF-8
82
+ pub comm_raw: [u8; 16],
83
+ pub comm_len: usize,
84
+ }
85
+
86
+ /// Reads native-endian fields in order from a record's bytes.
87
+ struct Fields<'a> {
88
+ bytes: &'a [u8],
89
+ at: usize,
90
+ }
91
+
92
+ impl<'a> Fields<'a> {
93
+ fn take<const N: usize>(&mut self) -> [u8; N] {
94
+ let mut out = [0u8; N];
95
+ out.copy_from_slice(&self.bytes[self.at..self.at + N]);
96
+ self.at += N;
97
+ out
98
+ }
99
+ fn u64(&mut self) -> u64 {
100
+ u64::from_ne_bytes(self.take())
101
+ }
102
+ fn u32(&mut self) -> u32 {
103
+ u32::from_ne_bytes(self.take())
104
+ }
105
+ fn i32(&mut self) -> i32 {
106
+ i32::from_ne_bytes(self.take())
107
+ }
108
+ fn u16(&mut self) -> u16 {
109
+ u16::from_ne_bytes(self.take())
110
+ }
111
+ /// an IPv4 address the kernel holds in network order
112
+ fn addr(&mut self) -> Ipv4Addr {
113
+ Ipv4Addr::from(self.take::<4>())
114
+ }
115
+ }
116
+
117
+ impl Ev {
118
+ /// Decodes one ring-buffer record, or says why it cannot.
119
+ pub fn decode(bytes: &[u8]) -> Result<Ev, String> {
120
+ if bytes.len() != SIZE {
121
+ return Err(format!("a record is {SIZE} bytes, not {}", bytes.len()));
122
+ }
123
+ let mut f = Fields { bytes, at: 0 };
124
+ let (mono, tai, cookie, nscookie, cgid, skaddr, ino) =
125
+ (f.u64(), f.u64(), f.u64(), f.u64(), f.u64(), f.u64(), f.u64());
126
+ let (kind, netns, saddr, daddr, extra) = (f.u32(), f.u32(), f.addr(), f.addr(), f.u32());
127
+ let (pid, fd, oldst, newst, state, err, owner) =
128
+ (f.i32(), f.i32(), f.i32(), f.i32(), f.i32(), f.i32(), f.i32());
129
+ let (sport, dport) = (f.u16(), f.u16());
130
+ let (has_tcp, has_seg) = (f.u32(), f.u32());
131
+ let tcp = TcpNow {
132
+ retransmits: f.u32(),
133
+ pending: f.u32(),
134
+ backoff: f.u32(),
135
+ probes_out: f.u32(),
136
+ rto: f.u32(),
137
+ srtt_us: f.u32(),
138
+ snd_una: f.u32(),
139
+ snd_nxt: f.u32(),
140
+ rcv_nxt: f.u32(),
141
+ };
142
+ let seg = Seg { seq: f.u32(), ack: f.u32(), len: f.u32(), flags: f.u32() };
143
+ let (behind, late) = (f.u32(), f.u32());
144
+ let comm_raw = f.take::<16>();
145
+ let comm_len = comm_raw.iter().position(|&b| b == 0).unwrap_or(comm_raw.len());
146
+ Ok(Ev {
147
+ mono,
148
+ tai,
149
+ cookie,
150
+ nscookie,
151
+ cgid,
152
+ skaddr,
153
+ ino,
154
+ kind,
155
+ netns,
156
+ saddr,
157
+ daddr,
158
+ extra,
159
+ pid,
160
+ fd,
161
+ oldst,
162
+ newst,
163
+ state,
164
+ err,
165
+ owner,
166
+ sport,
167
+ dport,
168
+ tcp: (has_tcp != 0).then_some(tcp),
169
+ seg: (has_seg != 0).then_some(seg),
170
+ behind,
171
+ late,
172
+ comm_raw,
173
+ comm_len,
174
+ })
175
+ }
176
+
177
+ /// The task's name, its broken UTF-8 replaced as `String::from_utf8_lossy` replaces it.
178
+ pub fn comm(&self) -> Cow<'_, str> {
179
+ String::from_utf8_lossy(&self.comm_raw[..self.comm_len])
180
+ }
181
+
182
+ pub fn kind_name(&self) -> &'static str {
183
+ (self.kind as usize)
184
+ .checked_sub(1)
185
+ .and_then(|at| KINDS.get(at))
186
+ .copied()
187
+ .unwrap_or("unknown")
188
+ }
189
+
190
+ /// What a retransmission was: the tail loss probe, the RTO timer's, or one from ACK
191
+ /// processing. Only the RTO timer's count toward tcp_retries2; the first of them starts the
192
+ /// give-up's budget.
193
+ pub fn retransmission(&self) -> Option<&'static str> {
194
+ let tcp = self.tcp.as_ref()?;
195
+ Some(if tcp.pending == ICSK_TIME_LOSS_PROBE {
196
+ "tlp"
197
+ } else if tcp.pending == 0 && tcp.retransmits > 0 {
198
+ "rto"
199
+ } else {
200
+ "fast"
201
+ })
202
+ }
203
+ }
204
+
205
+ /// The record's TCP state, its keys in byte order.
206
+ #[derive(Serialize)]
207
+ pub struct TcpOut {
208
+ pub backoff: u32,
209
+ pub pending: u32,
210
+ pub probes_out: u32,
211
+ pub rcv_nxt: u32,
212
+ pub retransmits: u32,
213
+ pub rto: Quantity,
214
+ pub snd_nxt: u32,
215
+ pub snd_una: u32,
216
+ pub srtt: Quantity,
217
+ }
218
+
219
+ /// The record's segment, its keys in byte order.
220
+ #[derive(Serialize)]
221
+ pub struct SegOut {
222
+ #[serde(skip_serializing_if = "Option::is_none")]
223
+ pub ack: Option<u32>,
224
+ #[serde(skip_serializing_if = "Option::is_none")]
225
+ pub flags: Option<u32>,
226
+ pub len: Quantity,
227
+ pub seq: u32,
228
+ }
229
+
230
+ /// An address and port, written as `a.b.c.d:port` without building a string first.
231
+ pub struct Endpoint(pub Ipv4Addr, pub u16);
232
+
233
+ impl Serialize for Endpoint {
234
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
235
+ serializer.collect_str(&format_args!("{}:{}", self.0, self.1))
236
+ }
237
+ }
238
+
239
+ /// One JSON line, its keys in byte order.
240
+ #[derive(Serialize)]
241
+ pub struct Record<'a> {
242
+ /// a reset sent with no segment to answer: an active reset
243
+ #[serde(skip_serializing_if = "Option::is_none")]
244
+ pub active: Option<bool>,
245
+ /// a timer: how many jiffies behind its wheel slot it ran
246
+ #[serde(skip_serializing_if = "Option::is_none")]
247
+ pub behind: Option<Quantity>,
248
+ pub cg: u64,
249
+ pub comm: Cow<'a, str>,
250
+ pub cookie: u64,
251
+ pub dst: Endpoint,
252
+ pub err: i32,
253
+ pub ev: &'static str,
254
+ #[serde(skip_serializing_if = "Option::is_none")]
255
+ pub fd: Option<i32>,
256
+ /// a timer: how many jiffies after its expiry it ran
257
+ #[serde(skip_serializing_if = "Option::is_none")]
258
+ pub late: Option<Quantity>,
259
+ pub mono: u64,
260
+ pub netns: u32,
261
+ #[serde(skip_serializing_if = "Option::is_none")]
262
+ pub new: Option<i32>,
263
+ #[serde(skip_serializing_if = "Option::is_none")]
264
+ pub old: Option<i32>,
265
+ /// a timer: whether the socket's owner held the socket as it ran
266
+ #[serde(skip_serializing_if = "Option::is_none")]
267
+ pub owned: Option<bool>,
268
+ pub pid: i32,
269
+ #[serde(skip_serializing_if = "Option::is_none")]
270
+ pub seg: Option<SegOut>,
271
+ pub src: Endpoint,
272
+ pub state: i32,
273
+ #[serde(skip_serializing_if = "Option::is_none")]
274
+ pub tai: Option<u64>,
275
+ #[serde(skip_serializing_if = "Option::is_none")]
276
+ pub tcp: Option<TcpOut>,
277
+ /// a probe: keepalive, window or other; a timer: keepalive, retransmit or other
278
+ #[serde(skip_serializing_if = "Option::is_none")]
279
+ pub what: Option<&'static str>,
280
+ /// a retransmission: tlp, rto or fast
281
+ #[serde(skip_serializing_if = "Option::is_none")]
282
+ pub why: Option<&'static str>,
283
+ #[serde(skip_serializing_if = "Option::is_none")]
284
+ pub x: Option<u32>,
285
+ }
286
+
287
+ impl<'a> From<&'a Ev> for Record<'a> {
288
+ fn from(e: &'a Ev) -> Record<'a> {
289
+ let name = e.kind_name();
290
+ // a connect or an accept names the task that owns the socket, and its fd
291
+ let has_owner = e.owner != 0;
292
+ let state_change = name == "state";
293
+ let timer = name == "timer";
294
+ Record {
295
+ ev: name,
296
+ mono: e.mono,
297
+ tai: (e.tai != 0).then_some(e.tai),
298
+ cookie: e.cookie,
299
+ netns: e.netns,
300
+ cg: e.cgid,
301
+ pid: e.pid,
302
+ comm: e.comm(),
303
+ src: Endpoint(e.saddr, e.sport),
304
+ dst: Endpoint(e.daddr, e.dport),
305
+ state: e.state,
306
+ err: e.err,
307
+ old: state_change.then_some(e.oldst),
308
+ new: state_change.then_some(e.newst),
309
+ fd: has_owner.then_some(e.fd),
310
+ behind: timer.then(|| quantity(e.behind, "jiffy")),
311
+ late: timer.then(|| quantity(e.late, "jiffy")),
312
+ owned: timer.then_some(e.extra & TIMER_OWNED != 0),
313
+ why: if name == "retrans" { e.retransmission() } else { None },
314
+ // a timer's kind is its extra's low byte, beside the owned flag
315
+ what: match (name, if timer { e.extra & 0xff } else { e.extra }) {
316
+ ("probe" | "timer", 1) => Some("keepalive"),
317
+ ("probe", 2) => Some("window"),
318
+ ("timer", 2) => Some("retransmit"),
319
+ ("probe" | "timer", _) => Some("other"),
320
+ _ => None,
321
+ },
322
+ active: (name == "rst_tx").then_some(e.extra == 1),
323
+ x: match name {
324
+ "error" | "tw_enter" | "tw_kill" | "req_new" | "rst_v4" => Some(e.extra),
325
+ _ => None,
326
+ },
327
+ tcp: e.tcp.as_ref().map(|t| TcpOut {
328
+ retransmits: t.retransmits,
329
+ pending: t.pending,
330
+ backoff: t.backoff,
331
+ probes_out: t.probes_out,
332
+ rto: quantity(t.rto, "jiffy"),
333
+ srtt: quantity(t.srtt_us, "us"),
334
+ snd_una: t.snd_una,
335
+ snd_nxt: t.snd_nxt,
336
+ rcv_nxt: t.rcv_nxt,
337
+ }),
338
+ seg: e.seg.as_ref().map(|s| SegOut {
339
+ seq: s.seq,
340
+ ack: (name == "rx").then_some(s.ack),
341
+ len: quantity(s.len, "B"),
342
+ flags: (name == "rx").then_some(s.flags),
343
+ }),
344
+ }
345
+ }
346
+ }
347
+
348
+ // ---------------------------------------------------------------------------------------------
349
+ // the line by hand: what `Record` serializes, written straight into a buffer
350
+ // ---------------------------------------------------------------------------------------------
351
+
352
+ fn push_u64(out: &mut Vec<u8>, mut v: u64) {
353
+ let mut digits = [0u8; 20];
354
+ let mut at = digits.len();
355
+ loop {
356
+ at -= 1;
357
+ digits[at] = b'0' + (v % 10) as u8;
358
+ v /= 10;
359
+ if v == 0 {
360
+ break;
361
+ }
362
+ }
363
+ out.extend_from_slice(&digits[at..]);
364
+ }
365
+
366
+ fn push_i64(out: &mut Vec<u8>, v: i64) {
367
+ if v < 0 {
368
+ out.push(b'-');
369
+ }
370
+ push_u64(out, v.unsigned_abs());
371
+ }
372
+
373
+ /// One byte of a JSON string as serde_json writes it: \" \\ \b \f \n \r \t, other control
374
+ /// characters as \u00xx in lowercase hex, everything else as it is.
375
+ fn push_escaped(out: &mut Vec<u8>, c: u8) {
376
+ const HEX: &[u8; 16] = b"0123456789abcdef";
377
+ match c {
378
+ b'"' => out.extend_from_slice(b"\\\""),
379
+ b'\\' => out.extend_from_slice(b"\\\\"),
380
+ 0x08 => out.extend_from_slice(b"\\b"),
381
+ 0x0c => out.extend_from_slice(b"\\f"),
382
+ b'\n' => out.extend_from_slice(b"\\n"),
383
+ b'\r' => out.extend_from_slice(b"\\r"),
384
+ b'\t' => out.extend_from_slice(b"\\t"),
385
+ c if c < 0x20 => {
386
+ out.extend_from_slice(b"\\u00");
387
+ out.push(HEX[(c >> 4) as usize]);
388
+ out.push(HEX[(c & 0xf) as usize]);
389
+ }
390
+ c => out.push(c),
391
+ }
392
+ }
393
+
394
+ /// Raw bytes as a JSON string, decoded as `String::from_utf8_lossy` decodes them: each maximal
395
+ /// prefix of a would-be UTF-8 sequence that cannot complete becomes one U+FFFD, and decoding
396
+ /// resumes at the byte that broke it. The Go and C collectors run this same loop.
397
+ fn push_lossy_quoted(out: &mut Vec<u8>, b: &[u8]) {
398
+ const REPLACEMENT: &[u8] = "\u{FFFD}".as_bytes();
399
+ out.push(b'"');
400
+ let mut i = 0;
401
+ while i < b.len() {
402
+ let c = b[i];
403
+ if c < 0x80 {
404
+ push_escaped(out, c);
405
+ i += 1;
406
+ continue;
407
+ }
408
+ let (need, lo, hi) = match c {
409
+ 0xc2..=0xdf => (1, 0x80, 0xbf),
410
+ 0xe0 => (2, 0xa0, 0xbf),
411
+ 0xed => (2, 0x80, 0x9f),
412
+ 0xe1..=0xef => (2, 0x80, 0xbf),
413
+ 0xf0 => (3, 0x90, 0xbf),
414
+ 0xf4 => (3, 0x80, 0x8f),
415
+ 0xf1..=0xf3 => (3, 0x80, 0xbf),
416
+ _ => {
417
+ out.extend_from_slice(REPLACEMENT);
418
+ i += 1;
419
+ continue;
420
+ }
421
+ };
422
+ let mut j = i + 1;
423
+ let mut whole = true;
424
+ for k in 0..need {
425
+ let (first, last) = if k == 0 { (lo, hi) } else { (0x80, 0xbf) };
426
+ match b.get(j) {
427
+ Some(&next) if (first..=last).contains(&next) => j += 1,
428
+ _ => {
429
+ whole = false;
430
+ break;
431
+ }
432
+ }
433
+ }
434
+ if whole {
435
+ out.extend_from_slice(&b[i..j]);
436
+ } else {
437
+ out.extend_from_slice(REPLACEMENT);
438
+ }
439
+ i = j;
440
+ }
441
+ out.push(b'"');
442
+ }
443
+
444
+ /// `{"unit":"…","value":N.0}`: every amount a record carries is a whole u32, which serde writes
445
+ /// as a float
446
+ fn push_quantity(out: &mut Vec<u8>, value: u32, unit: &str) {
447
+ out.extend_from_slice(b"{\"unit\":\"");
448
+ out.extend_from_slice(unit.as_bytes());
449
+ out.extend_from_slice(b"\",\"value\":");
450
+ push_u64(out, value.into());
451
+ out.extend_from_slice(b".0}");
452
+ }
453
+
454
+ /// `"a.b.c.d:port"`
455
+ fn push_endpoint(out: &mut Vec<u8>, addr: Ipv4Addr, port: u16) {
456
+ out.push(b'"');
457
+ for (k, octet) in addr.octets().into_iter().enumerate() {
458
+ if k > 0 {
459
+ out.push(b'.');
460
+ }
461
+ push_u64(out, octet.into());
462
+ }
463
+ out.push(b':');
464
+ push_u64(out, port.into());
465
+ out.push(b'"');
466
+ }
467
+
468
+ impl Ev {
469
+ /// Appends the record's JSON line, the bytes `Record` serializes to, written by hand: its keys
470
+ /// in byte order, straight into the buffer, building nothing per record. The Go and C
471
+ /// collectors write it the same way, and the tests hold it to `Record`'s output.
472
+ pub fn append_line(&self, out: &mut Vec<u8>) {
473
+ let name = self.kind_name();
474
+ out.push(b'{');
475
+ if name == "rst_tx" {
476
+ out.extend_from_slice(if self.extra == 1 { b"\"active\":true," } else { b"\"active\":false," });
477
+ }
478
+ if name == "timer" {
479
+ out.extend_from_slice(b"\"behind\":");
480
+ push_quantity(out, self.behind, "jiffy");
481
+ out.push(b',');
482
+ }
483
+ out.extend_from_slice(b"\"cg\":");
484
+ push_u64(out, self.cgid);
485
+ out.extend_from_slice(b",\"comm\":");
486
+ push_lossy_quoted(out, &self.comm_raw[..self.comm_len]);
487
+ out.extend_from_slice(b",\"cookie\":");
488
+ push_u64(out, self.cookie);
489
+ out.extend_from_slice(b",\"dst\":");
490
+ push_endpoint(out, self.daddr, self.dport);
491
+ out.extend_from_slice(b",\"err\":");
492
+ push_i64(out, self.err.into());
493
+ out.extend_from_slice(b",\"ev\":\"");
494
+ out.extend_from_slice(name.as_bytes());
495
+ out.push(b'"');
496
+ if self.owner != 0 {
497
+ out.extend_from_slice(b",\"fd\":");
498
+ push_i64(out, self.fd.into());
499
+ }
500
+ if name == "timer" {
501
+ out.extend_from_slice(b",\"late\":");
502
+ push_quantity(out, self.late, "jiffy");
503
+ }
504
+ out.extend_from_slice(b",\"mono\":");
505
+ push_u64(out, self.mono);
506
+ out.extend_from_slice(b",\"netns\":");
507
+ push_u64(out, self.netns.into());
508
+ if name == "state" {
509
+ out.extend_from_slice(b",\"new\":");
510
+ push_i64(out, self.newst.into());
511
+ out.extend_from_slice(b",\"old\":");
512
+ push_i64(out, self.oldst.into());
513
+ }
514
+ if name == "timer" {
515
+ out.extend_from_slice(if self.extra & TIMER_OWNED != 0 {
516
+ b",\"owned\":true".as_slice()
517
+ } else {
518
+ b",\"owned\":false"
519
+ });
520
+ }
521
+ out.extend_from_slice(b",\"pid\":");
522
+ push_i64(out, self.pid.into());
523
+ if let Some(s) = &self.seg {
524
+ out.extend_from_slice(b",\"seg\":{");
525
+ if name == "rx" {
526
+ out.extend_from_slice(b"\"ack\":");
527
+ push_u64(out, s.ack.into());
528
+ out.extend_from_slice(b",\"flags\":");
529
+ push_u64(out, s.flags.into());
530
+ out.push(b',');
531
+ }
532
+ out.extend_from_slice(b"\"len\":");
533
+ push_quantity(out, s.len, "B");
534
+ out.extend_from_slice(b",\"seq\":");
535
+ push_u64(out, s.seq.into());
536
+ out.push(b'}');
537
+ }
538
+ out.extend_from_slice(b",\"src\":");
539
+ push_endpoint(out, self.saddr, self.sport);
540
+ out.extend_from_slice(b",\"state\":");
541
+ push_i64(out, self.state.into());
542
+ if self.tai != 0 {
543
+ out.extend_from_slice(b",\"tai\":");
544
+ push_u64(out, self.tai);
545
+ }
546
+ if let Some(t) = &self.tcp {
547
+ out.extend_from_slice(b",\"tcp\":{\"backoff\":");
548
+ push_u64(out, t.backoff.into());
549
+ out.extend_from_slice(b",\"pending\":");
550
+ push_u64(out, t.pending.into());
551
+ out.extend_from_slice(b",\"probes_out\":");
552
+ push_u64(out, t.probes_out.into());
553
+ out.extend_from_slice(b",\"rcv_nxt\":");
554
+ push_u64(out, t.rcv_nxt.into());
555
+ out.extend_from_slice(b",\"retransmits\":");
556
+ push_u64(out, t.retransmits.into());
557
+ out.extend_from_slice(b",\"rto\":");
558
+ push_quantity(out, t.rto, "jiffy");
559
+ out.extend_from_slice(b",\"snd_nxt\":");
560
+ push_u64(out, t.snd_nxt.into());
561
+ out.extend_from_slice(b",\"snd_una\":");
562
+ push_u64(out, t.snd_una.into());
563
+ out.extend_from_slice(b",\"srtt\":");
564
+ push_quantity(out, t.srtt_us, "us");
565
+ out.push(b'}');
566
+ }
567
+ if name == "probe" || name == "timer" {
568
+ let kind = if name == "timer" { self.extra & 0xff } else { self.extra };
569
+ out.extend_from_slice(match (name, kind) {
570
+ (_, 1) => b",\"what\":\"keepalive\"".as_slice(),
571
+ ("probe", 2) => b",\"what\":\"window\"",
572
+ (_, 2) => b",\"what\":\"retransmit\"",
573
+ _ => b",\"what\":\"other\"",
574
+ });
575
+ }
576
+ if name == "retrans" {
577
+ if let Some(why) = self.retransmission() {
578
+ out.extend_from_slice(b",\"why\":\"");
579
+ out.extend_from_slice(why.as_bytes());
580
+ out.push(b'"');
581
+ }
582
+ }
583
+ if matches!(name, "error" | "tw_enter" | "tw_kill" | "req_new" | "rst_v4") {
584
+ out.extend_from_slice(b",\"x\":");
585
+ push_u64(out, self.extra.into());
586
+ }
587
+ out.push(b'}');
588
+ }
589
+ }
590
+
591
+ #[cfg(test)]
592
+ mod tests {
593
+ use super::*;
594
+
595
+ /// a record as the kernel side lays it out, built field by field in `struct ev`'s order
596
+ fn raw(kind: u32, retransmits: u32, pending: u32) -> Vec<u8> {
597
+ let mut b = Vec::with_capacity(SIZE);
598
+ for v in [1_000_u64, 0, 42, 7, 9, 0xffff, 0] {
599
+ b.extend_from_slice(&v.to_ne_bytes());
600
+ }
601
+ b.extend_from_slice(&kind.to_ne_bytes());
602
+ b.extend_from_slice(&4026532000_u32.to_ne_bytes());
603
+ b.extend_from_slice(&[172, 21, 0, 3]);
604
+ b.extend_from_slice(&[172, 21, 0, 2]);
605
+ b.extend_from_slice(&0_u32.to_ne_bytes());
606
+ for v in [100_i32, -1, 0, 0, 1, 0, 0] {
607
+ b.extend_from_slice(&v.to_ne_bytes());
608
+ }
609
+ b.extend_from_slice(&41978_u16.to_ne_bytes());
610
+ b.extend_from_slice(&6379_u16.to_ne_bytes());
611
+ b.extend_from_slice(&1_u32.to_ne_bytes());
612
+ b.extend_from_slice(&1_u32.to_ne_bytes());
613
+ for v in [retransmits, pending, 1, 0, 102, 80, 10, 16, 20] {
614
+ b.extend_from_slice(&v.to_ne_bytes());
615
+ }
616
+ for v in [10_u32, 0, 6, 0] {
617
+ b.extend_from_slice(&v.to_ne_bytes());
618
+ }
619
+ for v in [2_u32, 10] {
620
+ b.extend_from_slice(&v.to_ne_bytes());
621
+ }
622
+ let mut comm = [0u8; 16];
623
+ comm[..6].copy_from_slice(b"client");
624
+ b.extend_from_slice(&comm);
625
+ b
626
+ }
627
+
628
+ #[test]
629
+ fn decodes_a_record_and_names_the_rto_timers_retransmission() {
630
+ let ev = Ev::decode(&raw(3, 1, 0)).unwrap();
631
+ assert_eq!(ev.kind_name(), "retrans");
632
+ assert_eq!(ev.saddr, Ipv4Addr::new(172, 21, 0, 3));
633
+ assert_eq!((ev.sport, ev.dport), (41978, 6379));
634
+ assert_eq!(ev.comm(), "client");
635
+ assert_eq!(ev.retransmission(), Some("rto"));
636
+ let json = serde_json::to_value(Record::from(&ev)).unwrap();
637
+ assert_eq!(json["why"], "rto");
638
+ assert_eq!(json["src"], "172.21.0.3:41978");
639
+ assert_eq!(json["tcp"]["rto"], serde_json::json!({ "value": 102.0, "unit": "jiffy" }));
640
+ }
641
+
642
+ #[test]
643
+ fn tells_the_tail_loss_probe_from_the_rto_timer() {
644
+ // the loss probe is armed (pending 5) and no RTO retransmission has happened
645
+ assert_eq!(Ev::decode(&raw(3, 0, ICSK_TIME_LOSS_PROBE)).unwrap().retransmission(), Some("tlp"));
646
+ // a retransmission in ACK processing, with the RTO timer armed (pending 1)
647
+ assert_eq!(Ev::decode(&raw(3, 0, 1)).unwrap().retransmission(), Some("fast"));
648
+ }
649
+
650
+ #[test]
651
+ fn says_how_far_behind_its_wheel_slot_a_sockets_timer_ran() {
652
+ // extra, after seven u64, the kind, the namespace and two addresses: the retransmit timer
653
+ let mut bytes = raw(14, 0, 0);
654
+ bytes[72..76].copy_from_slice(&2_u32.to_ne_bytes());
655
+ let ev = Ev::decode(&bytes).unwrap();
656
+ assert_eq!(ev.kind_name(), "timer");
657
+ let json = serde_json::to_value(Record::from(&ev)).unwrap();
658
+ assert_eq!(json["what"], "retransmit");
659
+ assert_eq!(json["behind"], serde_json::json!({ "value": 2.0, "unit": "jiffy" }));
660
+ assert_eq!(json["late"], serde_json::json!({ "value": 10.0, "unit": "jiffy" }));
661
+ assert_eq!(json["owned"], false);
662
+ // the same timer, finding its socket in its owner's hands
663
+ bytes[72..76].copy_from_slice(&(1_u32 | TIMER_OWNED).to_ne_bytes());
664
+ let held = serde_json::to_value(Record::from(&Ev::decode(&bytes).unwrap())).unwrap();
665
+ assert_eq!((&held["what"], &held["owned"]), (&serde_json::json!("keepalive"), &serde_json::json!(true)));
666
+ // no other kind says either
667
+ let other = serde_json::to_value(Record::from(&Ev::decode(&raw(3, 1, 0)).unwrap())).unwrap();
668
+ assert!(other.get("behind").is_none() && other.get("late").is_none() && other.get("owned").is_none());
669
+ }
670
+
671
+ #[test]
672
+ fn refuses_a_record_of_the_wrong_size() {
673
+ assert!(Ev::decode(&[0u8; 12]).unwrap_err().contains("192 bytes"));
674
+ }
675
+
676
+ /// every whole record of the identity fixture, which the Go and C collectors are held to
677
+ const FIXTURE: &[u8] = include_bytes!("../../fixtures/records.bin");
678
+
679
+ #[test]
680
+ fn writes_by_hand_exactly_what_serde_and_from_utf8_lossy_write() {
681
+ let mut checked = 0;
682
+ for chunk in FIXTURE.chunks_exact(SIZE) {
683
+ let ev = Ev::decode(chunk).unwrap();
684
+ let mut by_hand = Vec::new();
685
+ ev.append_line(&mut by_hand);
686
+ let by_serde = serde_json::to_vec(&Record::from(&ev)).unwrap();
687
+ assert_eq!(
688
+ String::from_utf8_lossy(&by_hand),
689
+ String::from_utf8_lossy(&by_serde),
690
+ "record {checked}"
691
+ );
692
+ checked += 1;
693
+ }
694
+ assert_eq!(checked, 46);
695
+ }
696
+ }