@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.
- package/LICENSE +21 -0
- package/README.md +111 -2
- package/bpf/prober.bpf.c +372 -0
- package/bpf/prober.h +59 -0
- package/build/index.d.ts +52 -0
- package/build/index.js +22 -0
- package/build/index.js.map +1 -0
- package/fixtures/records.bin +0 -0
- package/native/Cargo.lock +206 -0
- package/native/Cargo.toml +30 -0
- package/native/src/bin/prober.rs +161 -0
- package/native/src/lib.rs +75 -0
- package/native/src/record.rs +696 -0
- package/package.json +74 -3
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "bitflags"
|
|
7
|
+
version = "2.13.2"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "cc"
|
|
13
|
+
version = "1.6.0"
|
|
14
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
15
|
+
checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630"
|
|
16
|
+
dependencies = [
|
|
17
|
+
"find-msvc-tools",
|
|
18
|
+
"shlex",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[[package]]
|
|
22
|
+
name = "cfg-if"
|
|
23
|
+
version = "1.0.5"
|
|
24
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
25
|
+
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
|
26
|
+
|
|
27
|
+
[[package]]
|
|
28
|
+
name = "cfg_aliases"
|
|
29
|
+
version = "0.2.2"
|
|
30
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
31
|
+
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
|
32
|
+
|
|
33
|
+
[[package]]
|
|
34
|
+
name = "find-msvc-tools"
|
|
35
|
+
version = "0.1.14"
|
|
36
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
37
|
+
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
|
|
38
|
+
|
|
39
|
+
[[package]]
|
|
40
|
+
name = "itoa"
|
|
41
|
+
version = "1.0.18"
|
|
42
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
43
|
+
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
44
|
+
|
|
45
|
+
[[package]]
|
|
46
|
+
name = "libbpf-rs"
|
|
47
|
+
version = "0.27.2"
|
|
48
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
49
|
+
checksum = "ffef943d6690772a293abe80412ca438c227867cac93448af2518166df1b1a7b"
|
|
50
|
+
dependencies = [
|
|
51
|
+
"bitflags",
|
|
52
|
+
"libbpf-sys",
|
|
53
|
+
"libc",
|
|
54
|
+
"vsprintf",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[[package]]
|
|
58
|
+
name = "libbpf-sys"
|
|
59
|
+
version = "1.7.0+v1.7.0"
|
|
60
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
61
|
+
checksum = "a109478760b2900aa2a6f2087e9d0de1d9c535b1758602af2845d5d2ccfaed7c"
|
|
62
|
+
dependencies = [
|
|
63
|
+
"cc",
|
|
64
|
+
"nix",
|
|
65
|
+
"pkg-config",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[[package]]
|
|
69
|
+
name = "libc"
|
|
70
|
+
version = "0.2.190"
|
|
71
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
72
|
+
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
|
73
|
+
|
|
74
|
+
[[package]]
|
|
75
|
+
name = "memchr"
|
|
76
|
+
version = "2.8.3"
|
|
77
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
78
|
+
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
|
79
|
+
|
|
80
|
+
[[package]]
|
|
81
|
+
name = "nix"
|
|
82
|
+
version = "0.31.3"
|
|
83
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
84
|
+
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
|
|
85
|
+
dependencies = [
|
|
86
|
+
"bitflags",
|
|
87
|
+
"cfg-if",
|
|
88
|
+
"cfg_aliases",
|
|
89
|
+
"libc",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
[[package]]
|
|
93
|
+
name = "pkg-config"
|
|
94
|
+
version = "0.3.34"
|
|
95
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
96
|
+
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
|
97
|
+
|
|
98
|
+
[[package]]
|
|
99
|
+
name = "prober"
|
|
100
|
+
version = "0.1.0"
|
|
101
|
+
dependencies = [
|
|
102
|
+
"libbpf-rs",
|
|
103
|
+
"libc",
|
|
104
|
+
"serde",
|
|
105
|
+
"serde_json",
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
[[package]]
|
|
109
|
+
name = "proc-macro2"
|
|
110
|
+
version = "1.0.107"
|
|
111
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
112
|
+
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
|
113
|
+
dependencies = [
|
|
114
|
+
"unicode-ident",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
[[package]]
|
|
118
|
+
name = "quote"
|
|
119
|
+
version = "1.0.47"
|
|
120
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
121
|
+
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
|
122
|
+
dependencies = [
|
|
123
|
+
"proc-macro2",
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
[[package]]
|
|
127
|
+
name = "serde"
|
|
128
|
+
version = "1.0.229"
|
|
129
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
130
|
+
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
|
131
|
+
dependencies = [
|
|
132
|
+
"serde_core",
|
|
133
|
+
"serde_derive",
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
[[package]]
|
|
137
|
+
name = "serde_core"
|
|
138
|
+
version = "1.0.229"
|
|
139
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
140
|
+
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
|
141
|
+
dependencies = [
|
|
142
|
+
"serde_derive",
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
[[package]]
|
|
146
|
+
name = "serde_derive"
|
|
147
|
+
version = "1.0.229"
|
|
148
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
149
|
+
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|
150
|
+
dependencies = [
|
|
151
|
+
"proc-macro2",
|
|
152
|
+
"quote",
|
|
153
|
+
"syn",
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
[[package]]
|
|
157
|
+
name = "serde_json"
|
|
158
|
+
version = "1.0.151"
|
|
159
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
160
|
+
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
|
161
|
+
dependencies = [
|
|
162
|
+
"itoa",
|
|
163
|
+
"memchr",
|
|
164
|
+
"serde",
|
|
165
|
+
"serde_core",
|
|
166
|
+
"zmij",
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
[[package]]
|
|
170
|
+
name = "shlex"
|
|
171
|
+
version = "2.0.1"
|
|
172
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
173
|
+
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
|
174
|
+
|
|
175
|
+
[[package]]
|
|
176
|
+
name = "syn"
|
|
177
|
+
version = "3.0.6"
|
|
178
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
179
|
+
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
|
180
|
+
dependencies = [
|
|
181
|
+
"proc-macro2",
|
|
182
|
+
"quote",
|
|
183
|
+
"unicode-ident",
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
[[package]]
|
|
187
|
+
name = "unicode-ident"
|
|
188
|
+
version = "1.0.26"
|
|
189
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
190
|
+
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
|
191
|
+
|
|
192
|
+
[[package]]
|
|
193
|
+
name = "vsprintf"
|
|
194
|
+
version = "2.0.0"
|
|
195
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
196
|
+
checksum = "aec2f81b75ca063294776b4f7e8da71d1d5ae81c2b1b149c8d89969230265d63"
|
|
197
|
+
dependencies = [
|
|
198
|
+
"cc",
|
|
199
|
+
"libc",
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
[[package]]
|
|
203
|
+
name = "zmij"
|
|
204
|
+
version = "1.0.23"
|
|
205
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
206
|
+
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "prober"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
publish = false
|
|
6
|
+
description = "The prober's native side: the record contract, the clock, and the collector for the kernel source"
|
|
7
|
+
|
|
8
|
+
[lib]
|
|
9
|
+
name = "prober"
|
|
10
|
+
path = "src/lib.rs"
|
|
11
|
+
|
|
12
|
+
# the collector loads the kernel source, so it alone needs libbpf: the harness's tools use the
|
|
13
|
+
# library without it
|
|
14
|
+
[[bin]]
|
|
15
|
+
name = "prober"
|
|
16
|
+
path = "src/bin/prober.rs"
|
|
17
|
+
required-features = ["collector"]
|
|
18
|
+
|
|
19
|
+
[features]
|
|
20
|
+
collector = ["dep:libbpf-rs"]
|
|
21
|
+
|
|
22
|
+
[dependencies]
|
|
23
|
+
libbpf-rs = { version = "=0.27.2", optional = true }
|
|
24
|
+
libc = "0.2"
|
|
25
|
+
serde = { version = "1", features = ["derive"] }
|
|
26
|
+
serde_json = "1"
|
|
27
|
+
|
|
28
|
+
[profile.release]
|
|
29
|
+
lto = true
|
|
30
|
+
codegen-units = 1
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
//! prober [--object PATH] NETNS... | prober --decode FILE
|
|
2
|
+
//!
|
|
3
|
+
//! The collector for the prober's kernel source. It loads `prober.bpf.o`, watches
|
|
4
|
+
//! the network namespaces given by their inode numbers, and prints every record the kernel writes
|
|
5
|
+
//! as one JSON line. Every second it also prints the clock track (CLOCK_TAI beside CLOCK_MONOTONIC)
|
|
6
|
+
//! and how many records the ring buffer had to drop, which must stay at zero.
|
|
7
|
+
|
|
8
|
+
use prober::record::{self, Ev};
|
|
9
|
+
use prober::{monotonic_ns, tai_ns};
|
|
10
|
+
use libbpf_rs::{MapCore, MapFlags, ObjectBuilder, RingBufferBuilder};
|
|
11
|
+
use serde_json::json;
|
|
12
|
+
use std::cell::RefCell;
|
|
13
|
+
use std::error::Error;
|
|
14
|
+
use std::ffi::CString;
|
|
15
|
+
use std::io::{BufWriter, Stdout, Write};
|
|
16
|
+
use std::path::Path;
|
|
17
|
+
use std::rc::Rc;
|
|
18
|
+
use std::time::Duration;
|
|
19
|
+
|
|
20
|
+
const OBJECT: &str = "/opt/harness/prober.bpf.o";
|
|
21
|
+
const TRACEFS: &str = "/sys/kernel/tracing";
|
|
22
|
+
|
|
23
|
+
/// Classic tracepoints (the connect and accept syscalls) attach through tracefs, which a
|
|
24
|
+
/// container does not mount. A privileged one may mount it for itself.
|
|
25
|
+
fn mount_tracefs() -> Result<(), Box<dyn Error>> {
|
|
26
|
+
if Path::new(TRACEFS).join("events").exists() {
|
|
27
|
+
return Ok(());
|
|
28
|
+
}
|
|
29
|
+
let (source, target) = (CString::new("tracefs")?, CString::new(TRACEFS)?);
|
|
30
|
+
// SAFETY: every pointer is a NUL-terminated string that outlives the call; data may be null
|
|
31
|
+
let status = unsafe {
|
|
32
|
+
libc::mount(source.as_ptr(), target.as_ptr(), source.as_ptr(), 0, std::ptr::null())
|
|
33
|
+
};
|
|
34
|
+
if status != 0 {
|
|
35
|
+
return Err(format!("mount tracefs: {}", std::io::Error::last_os_error()).into());
|
|
36
|
+
}
|
|
37
|
+
Ok(())
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn line(out: &Rc<RefCell<BufWriter<Stdout>>>, value: &serde_json::Value) {
|
|
41
|
+
let mut out = out.borrow_mut();
|
|
42
|
+
let _ = serde_json::to_writer(&mut *out, value);
|
|
43
|
+
let _ = out.write_all(b"\n");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// A record's line, written by hand into a buffer the caller reuses, then out in one write.
|
|
47
|
+
fn record(out: &Rc<RefCell<BufWriter<Stdout>>>, ev: &Ev, buffer: &mut Vec<u8>) {
|
|
48
|
+
buffer.clear();
|
|
49
|
+
ev.append_line(buffer);
|
|
50
|
+
buffer.push(b'\n');
|
|
51
|
+
let _ = out.borrow_mut().write_all(buffer);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Prints the line for every record in a file of raw records, as the ring buffer would hand them
|
|
55
|
+
/// over: how the three collectors are held to one contract byte for byte.
|
|
56
|
+
fn decode_file(path: &str) -> Result<(), Box<dyn Error>> {
|
|
57
|
+
let data = std::fs::read(path).map_err(|error| format!("{path}: {error}"))?;
|
|
58
|
+
let out = Rc::new(RefCell::new(BufWriter::with_capacity(1 << 16, std::io::stdout())));
|
|
59
|
+
let mut buffer = Vec::with_capacity(1024);
|
|
60
|
+
for chunk in data.chunks(record::SIZE) {
|
|
61
|
+
match Ev::decode(chunk) {
|
|
62
|
+
Ok(ev) => record(&out, &ev, &mut buffer),
|
|
63
|
+
Err(why) => line(&out, &json!({ "ev": "undecodable", "why": why })),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
out.borrow_mut().flush()?;
|
|
67
|
+
Ok(())
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fn run() -> Result<(), Box<dyn Error>> {
|
|
71
|
+
let mut args: Vec<String> = std::env::args().skip(1).collect();
|
|
72
|
+
if let Some(at) = args.iter().position(|arg| arg == "--decode") {
|
|
73
|
+
return decode_file(args.get(at + 1).ok_or("--decode needs a file")?);
|
|
74
|
+
}
|
|
75
|
+
let mut object = OBJECT.to_string();
|
|
76
|
+
if let Some(at) = args.iter().position(|arg| arg == "--object") {
|
|
77
|
+
object = args.get(at + 1).ok_or("--object needs a path")?.clone();
|
|
78
|
+
args.drain(at..=at + 1);
|
|
79
|
+
}
|
|
80
|
+
let watched: Vec<u32> = args
|
|
81
|
+
.iter()
|
|
82
|
+
.map(|arg| arg.parse::<u32>().map_err(|_| format!("{arg} is not a netns inode")))
|
|
83
|
+
.collect::<Result<_, _>>()?;
|
|
84
|
+
if watched.is_empty() {
|
|
85
|
+
return Err("name at least one network namespace to watch, by its inode".into());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
mount_tracefs()?;
|
|
89
|
+
let obj = ObjectBuilder::default().open_file(&object)?.load()?;
|
|
90
|
+
let want = obj.maps().find(|map| map.name() == "want").ok_or("the object has no map want")?;
|
|
91
|
+
for netns in &watched {
|
|
92
|
+
want.update(&netns.to_ne_bytes(), &[1u8], MapFlags::ANY)?;
|
|
93
|
+
}
|
|
94
|
+
let mut links = Vec::new();
|
|
95
|
+
for prog in obj.progs_mut() {
|
|
96
|
+
let name = prog.name().to_string_lossy().into_owned();
|
|
97
|
+
links.push(prog.attach().map_err(|error| format!("attach {name}: {error}"))?);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let out = Rc::new(RefCell::new(BufWriter::with_capacity(1 << 16, std::io::stdout())));
|
|
101
|
+
let records = Rc::new(RefCell::new(0u64));
|
|
102
|
+
let ring = obj.maps().find(|map| map.name() == "rb").ok_or("the object has no map rb")?;
|
|
103
|
+
let mut builder = RingBufferBuilder::new();
|
|
104
|
+
{
|
|
105
|
+
let (out, records) = (Rc::clone(&out), Rc::clone(&records));
|
|
106
|
+
let mut buffer = Vec::with_capacity(1024);
|
|
107
|
+
builder.add(&ring, move |data: &[u8]| {
|
|
108
|
+
match Ev::decode(data) {
|
|
109
|
+
Ok(ev) => {
|
|
110
|
+
*records.borrow_mut() += 1;
|
|
111
|
+
record(&out, &ev, &mut buffer);
|
|
112
|
+
}
|
|
113
|
+
Err(why) => line(&out, &json!({ "ev": "undecodable", "why": why })),
|
|
114
|
+
}
|
|
115
|
+
0
|
|
116
|
+
})?;
|
|
117
|
+
}
|
|
118
|
+
let ring = builder.build()?;
|
|
119
|
+
let lost = obj.maps().find(|map| map.name() == "lost").ok_or("the object has no map lost")?;
|
|
120
|
+
let dropped = || -> u64 {
|
|
121
|
+
lost.lookup_percpu(&0u32.to_ne_bytes(), MapFlags::ANY)
|
|
122
|
+
.ok()
|
|
123
|
+
.flatten()
|
|
124
|
+
.map(|cpus| {
|
|
125
|
+
cpus.iter()
|
|
126
|
+
.filter_map(|bytes| bytes.get(..8))
|
|
127
|
+
.map(|bytes| u64::from_ne_bytes(bytes.try_into().unwrap_or([0; 8])))
|
|
128
|
+
.sum()
|
|
129
|
+
})
|
|
130
|
+
.unwrap_or(0)
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
line(
|
|
134
|
+
&out,
|
|
135
|
+
&json!({ "ev": "begin", "mono": monotonic_ns(), "tai": tai_ns(), "netns": watched, "programs": links.len() }),
|
|
136
|
+
);
|
|
137
|
+
out.borrow_mut().flush()?;
|
|
138
|
+
let mut next = monotonic_ns() + 1_000_000_000;
|
|
139
|
+
loop {
|
|
140
|
+
// woken only when 256 KiB are waiting, so drain on every 20 ms tick as well
|
|
141
|
+
ring.poll(Duration::from_millis(20))?;
|
|
142
|
+
ring.consume()?;
|
|
143
|
+
let now = monotonic_ns();
|
|
144
|
+
if now >= next {
|
|
145
|
+
let count = *records.borrow();
|
|
146
|
+
line(
|
|
147
|
+
&out,
|
|
148
|
+
&json!({ "ev": "clock", "mono": now, "tai": tai_ns(), "records": count, "lost": dropped() }),
|
|
149
|
+
);
|
|
150
|
+
next += 1_000_000_000;
|
|
151
|
+
}
|
|
152
|
+
out.borrow_mut().flush()?;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn main() {
|
|
157
|
+
if let Err(error) = run() {
|
|
158
|
+
println!("{}", json!({ "ev": "fail", "mono": monotonic_ns(), "why": error.to_string() }));
|
|
159
|
+
std::process::exit(1);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//! The prober's native side:
|
|
2
|
+
//! - the record contract: the C struct the kernel source writes (`record`), and the JSON line it
|
|
3
|
+
//! becomes;
|
|
4
|
+
//! - the clock, the quantities and the errno names, which the harness's tools share;
|
|
5
|
+
//! - with the `collector` feature, the `prober` binary, which loads `bpf/prober.bpf.c` and prints
|
|
6
|
+
//! every record the kernel writes as one JSON line.
|
|
7
|
+
//!
|
|
8
|
+
//! Every instant printed is CLOCK_MONOTONIC in nanoseconds: eBPF stamps with that clock, and every
|
|
9
|
+
//! container on the VM shares it. Every amount is a quantity,
|
|
10
|
+
//! `{ "value": …, "unit": … }`, in @mark1russell7/quantity's canonical units.
|
|
11
|
+
|
|
12
|
+
pub mod record;
|
|
13
|
+
|
|
14
|
+
use serde::Serialize;
|
|
15
|
+
|
|
16
|
+
fn clock_ns(clock: libc::clockid_t) -> u64 {
|
|
17
|
+
let mut now = libc::timespec { tv_sec: 0, tv_nsec: 0 };
|
|
18
|
+
// SAFETY: clock_gettime only writes the timespec it is handed, which lives on this stack
|
|
19
|
+
let status = unsafe { libc::clock_gettime(clock, &mut now) };
|
|
20
|
+
assert_eq!(status, 0, "clock_gettime({clock}) failed");
|
|
21
|
+
now.tv_sec as u64 * 1_000_000_000 + now.tv_nsec as u64
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// CLOCK_MONOTONIC, the clock the kernel source stamps with.
|
|
25
|
+
pub fn monotonic_ns() -> u64 {
|
|
26
|
+
clock_ns(libc::CLOCK_MONOTONIC)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// CLOCK_TAI, for the clock track: REALTIME steps on this VM, TAI does not jump relative to it.
|
|
30
|
+
pub fn tai_ns() -> u64 {
|
|
31
|
+
clock_ns(libc::CLOCK_TAI)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// An amount and its canonical unit, as @mark1russell7/quantity writes one; its keys in byte order, as
|
|
35
|
+
/// serde_json writes any object it holds as a map.
|
|
36
|
+
#[derive(Serialize, Clone, Copy, Debug, PartialEq)]
|
|
37
|
+
pub struct Quantity {
|
|
38
|
+
pub unit: &'static str,
|
|
39
|
+
pub value: f64,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub fn quantity(value: impl Into<f64>, unit: &'static str) -> Quantity {
|
|
43
|
+
Quantity { value: value.into(), unit }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// The name of an errno, as the harness's predictions name the error a detector raises.
|
|
47
|
+
pub fn errno_name(code: i32) -> String {
|
|
48
|
+
match code {
|
|
49
|
+
libc::ETIMEDOUT => "ETIMEDOUT".into(),
|
|
50
|
+
libc::ECONNRESET => "ECONNRESET".into(),
|
|
51
|
+
libc::ECONNREFUSED => "ECONNREFUSED".into(),
|
|
52
|
+
libc::EHOSTUNREACH => "EHOSTUNREACH".into(),
|
|
53
|
+
libc::ENETUNREACH => "ENETUNREACH".into(),
|
|
54
|
+
libc::EPIPE => "EPIPE".into(),
|
|
55
|
+
libc::ECONNABORTED => "ECONNABORTED".into(),
|
|
56
|
+
other => format!("E{other}"),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[cfg(test)]
|
|
61
|
+
mod tests {
|
|
62
|
+
use super::*;
|
|
63
|
+
|
|
64
|
+
#[test]
|
|
65
|
+
fn names_the_errors_a_detector_raises() {
|
|
66
|
+
assert_eq!(errno_name(libc::ETIMEDOUT), "ETIMEDOUT");
|
|
67
|
+
assert_eq!(errno_name(9999), "E9999");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[test]
|
|
71
|
+
fn reads_a_monotonic_clock_that_never_steps_back() {
|
|
72
|
+
let first = monotonic_ns();
|
|
73
|
+
assert!(monotonic_ns() >= first);
|
|
74
|
+
}
|
|
75
|
+
}
|