@mark1russell7/prober 0.1.0 → 0.3.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.
- package/README.md +11 -1
- package/bpf/prober.bpf.c +25 -3
- package/bpf/prober.h +10 -1
- package/build/index.d.ts +22 -1
- package/build/index.js +2 -1
- package/build/index.js.map +1 -1
- package/fixtures/records.bin +0 -0
- package/native/src/record.rs +57 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,11 +17,21 @@ The kernel program records these events:
|
|
|
17
17
|
- TIME_WAIT sockets and request sockets.
|
|
18
18
|
- Each keepalive probe or zero-window probe that `tcp_write_wakeup` sends.
|
|
19
19
|
- Each segment that an established socket receives (`tcp_probe`).
|
|
20
|
+
- Each write of the owner of a socket (`tx`, at the entry of `tcp_sendmsg`), with the first sequence number of the bytes and their length. The time of a proxy is from an arrival on one of its sockets to a write on the other.
|
|
20
21
|
- The state of the retransmit timer at each retransmission.
|
|
21
22
|
- Each time that the timer wheel fires the keepalive timer or the retransmit timer of a socket (`timer`).
|
|
22
23
|
|
|
23
24
|
The state of the retransmit timer tells the tail loss probe from the retransmissions of the RTO timer. Only the RTO timer counts toward `tcp_retries2`.
|
|
24
25
|
|
|
26
|
+
Each record with TCP state (`tcp`) also has the SACK scoreboard and the reordering state of the socket, by the names of `tcp_info`:
|
|
27
|
+
|
|
28
|
+
- **`sacked`** and **`lost`** are the segments that the peer SACKed and that the kernel marked lost.
|
|
29
|
+
- **`reordering`** is the reordering degree of the kernel, in segments. It starts at `tcp_reordering`, and it increases when the kernel detects reordering. **`reord_seen`** counts those detections.
|
|
30
|
+
- **`dsack_dups`** counts the DSACK blocks that arrived: segments that the peer got two times.
|
|
31
|
+
- **`rcv_ooopack`** counts the segments that arrived out of order.
|
|
32
|
+
|
|
33
|
+
A record of version 0.1 does not have these fields, and it has no `tx` records.
|
|
34
|
+
|
|
25
35
|
A `timer` record has three more fields:
|
|
26
36
|
|
|
27
37
|
- **`behind`** is the number of jiffies between the slot of the timer on the wheel and the time that the CPU fired it. The wheel fires a slot in its tick. Thus a timer behind its slot shows a delay of its CPU, for example a vCPU that the host stopped.
|
|
@@ -64,7 +74,7 @@ The Go and C collectors are in the repository for the comparison. They are not i
|
|
|
64
74
|
|
|
65
75
|
Each collector has a `--decode FILE` mode, which reads raw records from a file and not from the kernel. This mode holds the three to one contract:
|
|
66
76
|
|
|
67
|
-
- `fixtures/records.bin` has
|
|
77
|
+
- `fixtures/records.bin` has 48 raw records, and one record that is cut short. The records have each kind, each optional field, the extremes of each integer, the escapes of JSON and each way to break UTF-8. `src/fixture.ts` makes the file, and its test keeps the committed bytes the same as the output.
|
|
68
78
|
- The Rust test compares the writer of the crate with the output of serde, record by record. It also compares the names with the lossy decoder of the standard library.
|
|
69
79
|
- The comparison image (`image/Dockerfile`) decodes the fixture with the three collectors. The build stops unless each line is the same, byte for byte.
|
|
70
80
|
|
package/bpf/prober.bpf.c
CHANGED
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Its core was built and verified first: the state changes, the owner at
|
|
7
7
|
* connect and accept, retransmissions, resets, error reports, destruction, TIME_WAIT and request
|
|
8
|
-
* socks, with the socket cookie, the namespace and the cgroup. The harness adds
|
|
8
|
+
* socks, with the socket cookie, the namespace and the cgroup. The harness adds five things:
|
|
9
9
|
* - each probe tcp_write_wakeup sends, as keepalive or zero-window;
|
|
10
10
|
* - every segment received on an established socket, so a frozen peer's ACKs show;
|
|
11
11
|
* - the connection's retransmit-timer state on each retransmission, so the tail loss probe (its
|
|
12
12
|
* pending event is the loss probe) parts from the RTO timer's retransmissions, which alone
|
|
13
|
-
* count toward tcp_retries2;
|
|
13
|
+
* count toward tcp_retries2; with it the SACK scoreboard and the reordering state;
|
|
14
14
|
* - each run of a socket's keepalive or retransmit timer, with how far behind its wheel slot it
|
|
15
|
-
* ran, so a run that a stalled vCPU delayed tells itself apart from the kernel's own timing
|
|
15
|
+
* ran, so a run that a stalled vCPU delayed tells itself apart from the kernel's own timing;
|
|
16
|
+
* - each write of the owner, so a proxy's own time shows between its two sockets. */
|
|
16
17
|
#include "vmlinux.h"
|
|
17
18
|
#include <bpf/bpf_helpers.h>
|
|
18
19
|
#include <bpf/bpf_tracing.h>
|
|
@@ -95,6 +96,12 @@ static __always_inline void fill_tcp(struct ev *e, const struct sock *sk)
|
|
|
95
96
|
e->tcp.snd_una = BPF_CORE_READ(tp, snd_una);
|
|
96
97
|
e->tcp.snd_nxt = BPF_CORE_READ(tp, snd_nxt);
|
|
97
98
|
e->tcp.rcv_nxt = BPF_CORE_READ(tp, rcv_nxt);
|
|
99
|
+
e->tcp.sacked_out = BPF_CORE_READ(tp, sacked_out);
|
|
100
|
+
e->tcp.lost_out = BPF_CORE_READ(tp, lost_out);
|
|
101
|
+
e->tcp.reordering = BPF_CORE_READ(tp, reordering);
|
|
102
|
+
e->tcp.reord_seen = BPF_CORE_READ(tp, reord_seen);
|
|
103
|
+
e->tcp.dsack_dups = BPF_CORE_READ(tp, dsack_dups);
|
|
104
|
+
e->tcp.rcv_ooopack = BPF_CORE_READ(tp, rcv_ooopack);
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
SEC("tp_btf/inet_sock_set_state")
|
|
@@ -287,6 +294,21 @@ int BPF_PROG(on_rx, struct sock *sk, struct sk_buff *skb)
|
|
|
287
294
|
return 0;
|
|
288
295
|
}
|
|
289
296
|
|
|
297
|
+
/* every write the owner makes: the bytes enter the socket here, and with TCP_NODELAY leave it in
|
|
298
|
+
* the same call. A proxy's time is from its arrival on one socket to this on the other. */
|
|
299
|
+
SEC("fentry/tcp_sendmsg")
|
|
300
|
+
int BPF_PROG(on_tx, struct sock *sk, struct msghdr *msg, size_t size)
|
|
301
|
+
{
|
|
302
|
+
struct ev *e = sk_record(sk, EV_TX, 0);
|
|
303
|
+
if (!e) return 0;
|
|
304
|
+
fill_tcp(e, sk);
|
|
305
|
+
e->has_seg = 1;
|
|
306
|
+
e->seg.seq = BPF_CORE_READ((struct tcp_sock *)sk, write_seq);
|
|
307
|
+
e->seg.len = (__u32)size;
|
|
308
|
+
bpf_ringbuf_submit(e, wake());
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
290
312
|
SEC("tp_btf/tcp_send_reset")
|
|
291
313
|
int BPF_PROG(on_rst_tx, const struct sock *sk, const struct sk_buff *skb) { return sk_event(sk, EV_RST_TX, skb == 0); }
|
|
292
314
|
|
package/bpf/prober.h
CHANGED
|
@@ -17,6 +17,7 @@ enum {
|
|
|
17
17
|
EV_PROBE, /* a probe sent by tcp_write_wakeup: keepalive or zero window */
|
|
18
18
|
EV_RX, /* a segment received on an established socket (tcp_probe) */
|
|
19
19
|
EV_TIMER, /* a socket's own timer run from the wheel: its keepalive, or its retransmit timer */
|
|
20
|
+
EV_TX, /* bytes the owner gave the socket to send (tcp_sendmsg): a proxy's departure */
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
/* what EV_PROBE's extra says the probe was */
|
|
@@ -35,9 +36,17 @@ struct tcp_now {
|
|
|
35
36
|
__u32 rto; /* icsk_rto, in jiffies */
|
|
36
37
|
__u32 srtt_us; /* the smoothed RTT, in microseconds */
|
|
37
38
|
__u32 snd_una, snd_nxt, rcv_nxt;
|
|
39
|
+
/* the scoreboard and the reordering state: what SACK, DSACK and out-of-order arrivals did */
|
|
40
|
+
__u32 sacked_out; /* the segments the peer SACKed */
|
|
41
|
+
__u32 lost_out; /* the segments marked lost */
|
|
42
|
+
__u32 reordering; /* the reordering degree the kernel holds, in segments: tcp_reordering at first */
|
|
43
|
+
__u32 reord_seen; /* the reorderings the kernel detected */
|
|
44
|
+
__u32 dsack_dups; /* the DSACK blocks received: segments the peer got twice */
|
|
45
|
+
__u32 rcv_ooopack; /* the segments that arrived out of order */
|
|
38
46
|
};
|
|
39
47
|
|
|
40
|
-
/* the segment a record is about: retransmitted, or received
|
|
48
|
+
/* the segment a record is about: retransmitted, or received; for EV_TX, the write: its first
|
|
49
|
+
* sequence number (write_seq) and its length */
|
|
41
50
|
struct seg {
|
|
42
51
|
__u32 seq, ack, len, flags;
|
|
43
52
|
};
|
package/build/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Quantity } from "@mark1russell7/quantity";
|
|
2
2
|
//#region src/index.d.ts
|
|
3
3
|
/** These are the kinds of record, in the sequence of the enum in `bpf/prober.h` and of `KINDS` in the Rust decoder. */
|
|
4
|
-
export declare const RECORD_KINDS: readonly ["state", "accept", "retrans", "rst_tx", "rst_rx", "error", "destroy", "tw_enter", "tw_kill", "req_new", "rst_v4", "probe", "rx", "timer"];
|
|
4
|
+
export declare const RECORD_KINDS: readonly ["state", "accept", "retrans", "rst_tx", "rst_rx", "error", "destroy", "tw_enter", "tw_kill", "req_new", "rst_v4", "probe", "rx", "timer", "tx"];
|
|
5
5
|
/** A record kind is one kind of record that the kernel side writes. */
|
|
6
6
|
export type RecordKind = (typeof RECORD_KINDS)[number];
|
|
7
7
|
/** These are the lines of the collector itself: its start, its clock track, its failure, and a record that it cannot read. */
|
|
@@ -40,10 +40,31 @@ export interface KernelRecord {
|
|
|
40
40
|
readonly probes_out: number;
|
|
41
41
|
readonly rto: Quantity;
|
|
42
42
|
readonly srtt: Quantity;
|
|
43
|
+
readonly snd_una?: number;
|
|
44
|
+
readonly snd_nxt?: number;
|
|
45
|
+
readonly rcv_nxt?: number;
|
|
46
|
+
/**
|
|
47
|
+
* These counts are the SACK scoreboard and the reordering state, as `tcp_info` names them. A
|
|
48
|
+
* record of version 0.1 does not have them. `sacked` and `lost` are the segments that the peer
|
|
49
|
+
* SACKed and that the kernel marked lost. `reordering` is the reordering degree of the kernel,
|
|
50
|
+
* in segments, and `reord_seen` counts the reorderings that it detected. `dsack_dups` counts
|
|
51
|
+
* the DSACK blocks that arrived, and `rcv_ooopack` the segments that arrived out of order.
|
|
52
|
+
*/
|
|
53
|
+
readonly sacked?: number;
|
|
54
|
+
readonly lost?: number;
|
|
55
|
+
readonly reordering?: number;
|
|
56
|
+
readonly reord_seen?: number;
|
|
57
|
+
readonly dsack_dups?: number;
|
|
58
|
+
readonly rcv_ooopack?: number;
|
|
43
59
|
};
|
|
60
|
+
/**
|
|
61
|
+
* This record is the segment of a record. For `rx`, it has the acknowledgment and the flags. For
|
|
62
|
+
* `tx`, it is the write: its first sequence number and its length.
|
|
63
|
+
*/
|
|
44
64
|
readonly seg?: {
|
|
45
65
|
readonly seq: number;
|
|
46
66
|
readonly ack?: number;
|
|
67
|
+
readonly flags?: number;
|
|
47
68
|
readonly len: Quantity;
|
|
48
69
|
};
|
|
49
70
|
readonly [field: string]: unknown;
|
package/build/index.js
CHANGED
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["// @mark1russell7/prober: the instrument that records what each layer did to a connection. Its\n// first source is the kernel: bpf/prober.bpf.c, a libbpf CO-RE program, and the collector in\n// native/ that prints each record as a JSON line. This module is the record contract for the\n// readers of those lines.\n\nimport type { Quantity } from \"@mark1russell7/quantity\";\n\n/** These are the kinds of record, in the sequence of the enum in `bpf/prober.h` and of `KINDS` in the Rust decoder. */\nexport const RECORD_KINDS = [\n \"state\",\n \"accept\",\n \"retrans\",\n \"rst_tx\",\n \"rst_rx\",\n \"error\",\n \"destroy\",\n \"tw_enter\",\n \"tw_kill\",\n \"req_new\",\n \"rst_v4\",\n \"probe\",\n \"rx\",\n \"timer\",\n] as const;\n\n/** A record kind is one kind of record that the kernel side writes. */\nexport type RecordKind = (typeof RECORD_KINDS)[number];\n\n/** These are the lines of the collector itself: its start, its clock track, its failure, and a record that it cannot read. */\nexport type CollectorLine = \"begin\" | \"clock\" | \"fail\" | \"undecodable\";\n\n/**\n * A kernel record is one record of the prober: what the kernel did to a socket. The eBPF side\n * stamps it on CLOCK_MONOTONIC, the clock that all containers on the VM share (`native/src/record.rs`).\n */\nexport interface KernelRecord {\n readonly ev: RecordKind | CollectorLine | string;\n readonly mono: number;\n readonly cookie?: number;\n readonly netns?: number;\n readonly src?: string;\n readonly dst?: string;\n readonly state?: number;\n /** This number is the `sk_err` of the socket at the time of the record: the errno of an error report. */\n readonly err?: number;\n /** This value is the cause of a retransmission: the tail loss probe, the RTO timer, or a fast retransmission from ACK processing. */\n readonly why?: \"tlp\" | \"rto\" | \"fast\";\n /** This value is the kind of a probe that `tcp_write_wakeup` sent, or of a socket timer that fired. */\n readonly what?: \"keepalive\" | \"window\" | \"retransmit\" | \"other\";\n /**\n * For a socket timer that fired, `behind` is the delay of the CPU after the slot of the timer\n * wheel. Only a stalled CPU causes that delay. `late` is the time after the expiry of the timer,\n * with the rounding of the wheel.\n */\n readonly behind?: Quantity;\n readonly late?: Quantity;\n /** This flag tells that a socket timer fired while the owner of the socket held it. Keepalive then tries again HZ/20 later. */\n readonly owned?: boolean;\n readonly tcp?: {\n readonly retransmits: number;\n readonly pending: number;\n readonly backoff: number;\n readonly probes_out: number;\n readonly rto: Quantity;\n readonly srtt: Quantity;\n };\n readonly seg?: {
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["// @mark1russell7/prober: the instrument that records what each layer did to a connection. Its\n// first source is the kernel: bpf/prober.bpf.c, a libbpf CO-RE program, and the collector in\n// native/ that prints each record as a JSON line. This module is the record contract for the\n// readers of those lines.\n\nimport type { Quantity } from \"@mark1russell7/quantity\";\n\n/** These are the kinds of record, in the sequence of the enum in `bpf/prober.h` and of `KINDS` in the Rust decoder. */\nexport const RECORD_KINDS = [\n \"state\",\n \"accept\",\n \"retrans\",\n \"rst_tx\",\n \"rst_rx\",\n \"error\",\n \"destroy\",\n \"tw_enter\",\n \"tw_kill\",\n \"req_new\",\n \"rst_v4\",\n \"probe\",\n \"rx\",\n \"timer\",\n \"tx\",\n] as const;\n\n/** A record kind is one kind of record that the kernel side writes. */\nexport type RecordKind = (typeof RECORD_KINDS)[number];\n\n/** These are the lines of the collector itself: its start, its clock track, its failure, and a record that it cannot read. */\nexport type CollectorLine = \"begin\" | \"clock\" | \"fail\" | \"undecodable\";\n\n/**\n * A kernel record is one record of the prober: what the kernel did to a socket. The eBPF side\n * stamps it on CLOCK_MONOTONIC, the clock that all containers on the VM share (`native/src/record.rs`).\n */\nexport interface KernelRecord {\n readonly ev: RecordKind | CollectorLine | string;\n readonly mono: number;\n readonly cookie?: number;\n readonly netns?: number;\n readonly src?: string;\n readonly dst?: string;\n readonly state?: number;\n /** This number is the `sk_err` of the socket at the time of the record: the errno of an error report. */\n readonly err?: number;\n /** This value is the cause of a retransmission: the tail loss probe, the RTO timer, or a fast retransmission from ACK processing. */\n readonly why?: \"tlp\" | \"rto\" | \"fast\";\n /** This value is the kind of a probe that `tcp_write_wakeup` sent, or of a socket timer that fired. */\n readonly what?: \"keepalive\" | \"window\" | \"retransmit\" | \"other\";\n /**\n * For a socket timer that fired, `behind` is the delay of the CPU after the slot of the timer\n * wheel. Only a stalled CPU causes that delay. `late` is the time after the expiry of the timer,\n * with the rounding of the wheel.\n */\n readonly behind?: Quantity;\n readonly late?: Quantity;\n /** This flag tells that a socket timer fired while the owner of the socket held it. Keepalive then tries again HZ/20 later. */\n readonly owned?: boolean;\n readonly tcp?: {\n readonly retransmits: number;\n readonly pending: number;\n readonly backoff: number;\n readonly probes_out: number;\n readonly rto: Quantity;\n readonly srtt: Quantity;\n readonly snd_una?: number;\n readonly snd_nxt?: number;\n readonly rcv_nxt?: number;\n /**\n * These counts are the SACK scoreboard and the reordering state, as `tcp_info` names them. A\n * record of version 0.1 does not have them. `sacked` and `lost` are the segments that the peer\n * SACKed and that the kernel marked lost. `reordering` is the reordering degree of the kernel,\n * in segments, and `reord_seen` counts the reorderings that it detected. `dsack_dups` counts\n * the DSACK blocks that arrived, and `rcv_ooopack` the segments that arrived out of order.\n */\n readonly sacked?: number;\n readonly lost?: number;\n readonly reordering?: number;\n readonly reord_seen?: number;\n readonly dsack_dups?: number;\n readonly rcv_ooopack?: number;\n };\n /**\n * This record is the segment of a record. For `rx`, it has the acknowledgment and the flags. For\n * `tx`, it is the write: its first sequence number and its length.\n */\n readonly seg?: {\n readonly seq: number;\n readonly ack?: number;\n readonly flags?: number;\n readonly len: Quantity;\n };\n readonly [field: string]: unknown;\n}\n"],"mappings":";;AAQA,MAAa,eAAe;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
|
package/fixtures/records.bin
CHANGED
|
Binary file
|
package/native/src/record.rs
CHANGED
|
@@ -11,14 +11,14 @@ use std::borrow::Cow;
|
|
|
11
11
|
use std::net::Ipv4Addr;
|
|
12
12
|
|
|
13
13
|
/// `struct ev`'s size in bytes. In order: seven u64, five u32, seven i32, two u16, two u32,
|
|
14
|
-
/// `struct tcp_now` (
|
|
15
|
-
/// Every field sits on its own alignment, so the C compiler adds no padding.
|
|
16
|
-
pub const SIZE: usize =
|
|
14
|
+
/// `struct tcp_now` (fifteen u32), `struct seg` (four u32), two u32 for a timer and a 16-byte
|
|
15
|
+
/// comm. Every field sits on its own alignment, so the C compiler adds no padding.
|
|
16
|
+
pub const SIZE: usize = 216;
|
|
17
17
|
|
|
18
18
|
/// The event kinds, in `prober.h`'s order, from 1.
|
|
19
|
-
const KINDS: [&str;
|
|
19
|
+
const KINDS: [&str; 15] = [
|
|
20
20
|
"state", "accept", "retrans", "rst_tx", "rst_rx", "error", "destroy", "tw_enter", "tw_kill",
|
|
21
|
-
"req_new", "rst_v4", "probe", "rx", "timer",
|
|
21
|
+
"req_new", "rst_v4", "probe", "rx", "timer", "tx",
|
|
22
22
|
];
|
|
23
23
|
|
|
24
24
|
/// ICSK_TIME_LOSS_PROBE in include/net/inet_connection_sock.h: the tail loss probe is armed
|
|
@@ -39,6 +39,13 @@ pub struct TcpNow {
|
|
|
39
39
|
pub snd_una: u32,
|
|
40
40
|
pub snd_nxt: u32,
|
|
41
41
|
pub rcv_nxt: u32,
|
|
42
|
+
/// the SACK scoreboard and the reordering state, as `tcp_info` names them
|
|
43
|
+
pub sacked_out: u32,
|
|
44
|
+
pub lost_out: u32,
|
|
45
|
+
pub reordering: u32,
|
|
46
|
+
pub reord_seen: u32,
|
|
47
|
+
pub dsack_dups: u32,
|
|
48
|
+
pub rcv_ooopack: u32,
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
#[derive(Debug, Clone, PartialEq)]
|
|
@@ -138,6 +145,12 @@ impl Ev {
|
|
|
138
145
|
snd_una: f.u32(),
|
|
139
146
|
snd_nxt: f.u32(),
|
|
140
147
|
rcv_nxt: f.u32(),
|
|
148
|
+
sacked_out: f.u32(),
|
|
149
|
+
lost_out: f.u32(),
|
|
150
|
+
reordering: f.u32(),
|
|
151
|
+
reord_seen: f.u32(),
|
|
152
|
+
dsack_dups: f.u32(),
|
|
153
|
+
rcv_ooopack: f.u32(),
|
|
141
154
|
};
|
|
142
155
|
let seg = Seg { seq: f.u32(), ack: f.u32(), len: f.u32(), flags: f.u32() };
|
|
143
156
|
let (behind, late) = (f.u32(), f.u32());
|
|
@@ -206,11 +219,17 @@ impl Ev {
|
|
|
206
219
|
#[derive(Serialize)]
|
|
207
220
|
pub struct TcpOut {
|
|
208
221
|
pub backoff: u32,
|
|
222
|
+
pub dsack_dups: u32,
|
|
223
|
+
pub lost: u32,
|
|
209
224
|
pub pending: u32,
|
|
210
225
|
pub probes_out: u32,
|
|
211
226
|
pub rcv_nxt: u32,
|
|
227
|
+
pub rcv_ooopack: u32,
|
|
228
|
+
pub reord_seen: u32,
|
|
229
|
+
pub reordering: u32,
|
|
212
230
|
pub retransmits: u32,
|
|
213
231
|
pub rto: Quantity,
|
|
232
|
+
pub sacked: u32,
|
|
214
233
|
pub snd_nxt: u32,
|
|
215
234
|
pub snd_una: u32,
|
|
216
235
|
pub srtt: Quantity,
|
|
@@ -334,6 +353,12 @@ impl<'a> From<&'a Ev> for Record<'a> {
|
|
|
334
353
|
snd_una: t.snd_una,
|
|
335
354
|
snd_nxt: t.snd_nxt,
|
|
336
355
|
rcv_nxt: t.rcv_nxt,
|
|
356
|
+
sacked: t.sacked_out,
|
|
357
|
+
lost: t.lost_out,
|
|
358
|
+
reordering: t.reordering,
|
|
359
|
+
reord_seen: t.reord_seen,
|
|
360
|
+
dsack_dups: t.dsack_dups,
|
|
361
|
+
rcv_ooopack: t.rcv_ooopack,
|
|
337
362
|
}),
|
|
338
363
|
seg: e.seg.as_ref().map(|s| SegOut {
|
|
339
364
|
seq: s.seq,
|
|
@@ -546,16 +571,28 @@ impl Ev {
|
|
|
546
571
|
if let Some(t) = &self.tcp {
|
|
547
572
|
out.extend_from_slice(b",\"tcp\":{\"backoff\":");
|
|
548
573
|
push_u64(out, t.backoff.into());
|
|
574
|
+
out.extend_from_slice(b",\"dsack_dups\":");
|
|
575
|
+
push_u64(out, t.dsack_dups.into());
|
|
576
|
+
out.extend_from_slice(b",\"lost\":");
|
|
577
|
+
push_u64(out, t.lost_out.into());
|
|
549
578
|
out.extend_from_slice(b",\"pending\":");
|
|
550
579
|
push_u64(out, t.pending.into());
|
|
551
580
|
out.extend_from_slice(b",\"probes_out\":");
|
|
552
581
|
push_u64(out, t.probes_out.into());
|
|
553
582
|
out.extend_from_slice(b",\"rcv_nxt\":");
|
|
554
583
|
push_u64(out, t.rcv_nxt.into());
|
|
584
|
+
out.extend_from_slice(b",\"rcv_ooopack\":");
|
|
585
|
+
push_u64(out, t.rcv_ooopack.into());
|
|
586
|
+
out.extend_from_slice(b",\"reord_seen\":");
|
|
587
|
+
push_u64(out, t.reord_seen.into());
|
|
588
|
+
out.extend_from_slice(b",\"reordering\":");
|
|
589
|
+
push_u64(out, t.reordering.into());
|
|
555
590
|
out.extend_from_slice(b",\"retransmits\":");
|
|
556
591
|
push_u64(out, t.retransmits.into());
|
|
557
592
|
out.extend_from_slice(b",\"rto\":");
|
|
558
593
|
push_quantity(out, t.rto, "jiffy");
|
|
594
|
+
out.extend_from_slice(b",\"sacked\":");
|
|
595
|
+
push_u64(out, t.sacked_out.into());
|
|
559
596
|
out.extend_from_slice(b",\"snd_nxt\":");
|
|
560
597
|
push_u64(out, t.snd_nxt.into());
|
|
561
598
|
out.extend_from_slice(b",\"snd_una\":");
|
|
@@ -610,7 +647,7 @@ mod tests {
|
|
|
610
647
|
b.extend_from_slice(&6379_u16.to_ne_bytes());
|
|
611
648
|
b.extend_from_slice(&1_u32.to_ne_bytes());
|
|
612
649
|
b.extend_from_slice(&1_u32.to_ne_bytes());
|
|
613
|
-
for v in [retransmits, pending, 1, 0, 102, 80, 10, 16, 20] {
|
|
650
|
+
for v in [retransmits, pending, 1, 0, 102, 80, 10, 16, 20, 2, 1, 3, 0, 1, 4] {
|
|
614
651
|
b.extend_from_slice(&v.to_ne_bytes());
|
|
615
652
|
}
|
|
616
653
|
for v in [10_u32, 0, 6, 0] {
|
|
@@ -637,6 +674,18 @@ mod tests {
|
|
|
637
674
|
assert_eq!(json["why"], "rto");
|
|
638
675
|
assert_eq!(json["src"], "172.21.0.3:41978");
|
|
639
676
|
assert_eq!(json["tcp"]["rto"], serde_json::json!({ "value": 102.0, "unit": "jiffy" }));
|
|
677
|
+
// the SACK scoreboard and the reordering state, by the names of tcp_info
|
|
678
|
+
assert_eq!((&json["tcp"]["sacked"], &json["tcp"]["lost"]), (&serde_json::json!(2), &serde_json::json!(1)));
|
|
679
|
+
assert_eq!((&json["tcp"]["reordering"], &json["tcp"]["rcv_ooopack"]), (&serde_json::json!(3), &serde_json::json!(4)));
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
#[test]
|
|
683
|
+
fn says_what_a_write_gave_the_socket() {
|
|
684
|
+
let ev = Ev::decode(&raw(15, 0, 0)).unwrap();
|
|
685
|
+
assert_eq!(ev.kind_name(), "tx");
|
|
686
|
+
let json = serde_json::to_value(Record::from(&ev)).unwrap();
|
|
687
|
+
// a write has its first sequence number and its length, but no ack and no flags
|
|
688
|
+
assert_eq!(json["seg"], serde_json::json!({ "len": { "value": 6.0, "unit": "B" }, "seq": 10 }));
|
|
640
689
|
}
|
|
641
690
|
|
|
642
691
|
#[test]
|
|
@@ -670,7 +719,7 @@ mod tests {
|
|
|
670
719
|
|
|
671
720
|
#[test]
|
|
672
721
|
fn refuses_a_record_of_the_wrong_size() {
|
|
673
|
-
assert!(Ev::decode(&[0u8; 12]).unwrap_err().contains("
|
|
722
|
+
assert!(Ev::decode(&[0u8; 12]).unwrap_err().contains("216 bytes"));
|
|
674
723
|
}
|
|
675
724
|
|
|
676
725
|
/// every whole record of the identity fixture, which the Go and C collectors are held to
|
|
@@ -691,6 +740,6 @@ mod tests {
|
|
|
691
740
|
);
|
|
692
741
|
checked += 1;
|
|
693
742
|
}
|
|
694
|
-
assert_eq!(checked,
|
|
743
|
+
assert_eq!(checked, 48);
|
|
695
744
|
}
|
|
696
745
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/prober",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "An eBPF prober for TCP: a CO-RE kernel program and its Rust collector, which record what the kernel did to each socket, with the record contract in TypeScript.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"ebpf",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"node": ">=22.19"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@mark1russell7/quantity": "^0.
|
|
54
|
+
"@mark1russell7/quantity": "^0.2.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"zod": "^4.1.0"
|