Skip to content

Commit 4868bc2

Browse files
authored
Merge pull request #59 from AMDmi3/openbsd
Add OpenBSD support
2 parents f199808 + da32542 commit 4868bc2

File tree

5 files changed

+162
-21
lines changed

5 files changed

+162
-21
lines changed

.github/workflows/test.yml

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,17 @@ jobs:
104104
files: lcov.info,lcov.use-gauge-on-cpu-seconds-total.info
105105
env_vars: RUNNER
106106

107-
test-cross-platform-freebsd:
107+
test-cross-platform:
108+
strategy:
109+
matrix:
110+
# don't forget to update current os versions, see https://github.com/cross-platform-actions/action?tab=readme-ov-file#supported-platforms
111+
include:
112+
- { os: freebsd, version: "14.1", prepare_cmd: "sudo pkg install -y rust cmake rust-bindgen-cli" }
113+
# 7.6 is not supported by [email protected] yet
114+
- { os: openbsd, version: "7.5", prepare_cmd: "sudo pkg_add rust cmake llvm%17; export LIBCLANG_PATH=/usr/local/llvm17/lib" }
115+
fail-fast: false
108116
runs-on: ubuntu-latest
117+
name: "test (${{matrix.os}}-${{matrix.version}})"
109118
steps:
110119
- uses: actions/checkout@v4
111120
- uses: actions/cache@v4
@@ -116,12 +125,12 @@ jobs:
116125
~/.cargo/registry/cache/
117126
~/.cargo/git/db/
118127
target/
119-
key: freebsd-14.1-cargo-${{ hashFiles('**/Cargo.lock') }}
128+
key: ${{matrix.os}}-${{matrix.version}}-cargo-${{ hashFiles('**/Cargo.lock') }}
120129
- name: Cross-platform test
121130
uses: cross-platform-actions/[email protected]
122131
with:
123-
operating_system: freebsd
124-
version: '14.1'
132+
operating_system: ${{matrix.os}}
133+
version: "${{matrix.version}}"
125134
run: |
126-
sudo pkg update && sudo pkg install -y rust cmake rust-bindgen-cli
127-
cargo test
135+
${{matrix.prepare_cmd}}
136+
cargo test -- --nocapture

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ procfs = { version = "0.17.0", default-features = false }
4545
[target.'cfg(target_os = "freebsd")'.dependencies]
4646
libc = "0.2.159"
4747

48+
[target.'cfg(target_os = "openbsd")'.dependencies]
49+
libc = "0.2.159"
50+
4851
[target.'cfg(target_os = "windows")'.dependencies.windows]
4952
version = "0.58.0"
5053
features = [

README.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
# ⏱ metrics-process
99

1010
This crate provides a [Prometheus]-style [process metrics] collector for the
11-
[metrics] crate, supporting Linux, macOS, Windows, and FreeBSD. The original
12-
collector code was manually rewritten in Rust from the official Prometheus
13-
client for Go ([client_golang]), FreeBSD support was written from scratch.
11+
[metrics] crate, supporting Linux, macOS, Windows, FreeBSD, and OpenBSD. The
12+
original collector code was manually rewritten in Rust from the official
13+
Prometheus client for Go ([client_golang]), \*BSD support code was written
14+
from scratch.
1415

1516
[Prometheus]: https://prometheus.io/
1617
[process metrics]: https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics
@@ -41,17 +42,17 @@ Go ([client_golang]) provides.
4142
> Prior to version 2.0.0, the `process_cpu_seconds_total` metric was Gauge instead of Counter.
4243
> Enable `use-gauge-on-cpu-seconds-total` feature to use the previous behavior.
4344
44-
| Metric name | Linux | macOS | Windows | FreeBSD |
45-
| ---------------------------------- | ----- | ----- | ------- | ------- |
46-
| `process_cpu_seconds_total` | x | x | x | x |
47-
| `process_open_fds` | x | x | x | x |
48-
| `process_max_fds` | x | x | x | x |
49-
| `process_virtual_memory_bytes` | x | x | x | x |
50-
| `process_virtual_memory_max_bytes` | x | x | | x |
51-
| `process_resident_memory_bytes` | x | x | x | x |
52-
| `process_heap_bytes` | | | | |
53-
| `process_start_time_seconds` | x | x | x | x |
54-
| `process_threads` | x | x | | x |
45+
| Metric name | Linux | macOS | Windows | FreeBSD | OpenBSD |
46+
| ---------------------------------- | ----- | ----- | ------- | ------- | ------- |
47+
| `process_cpu_seconds_total` | x | x | x | x | x |
48+
| `process_open_fds` | x | x | x | x | |
49+
| `process_max_fds` | x | x | x | x | x |
50+
| `process_virtual_memory_bytes` | x | x | x | x | |
51+
| `process_virtual_memory_max_bytes` | x | x | | x | |
52+
| `process_resident_memory_bytes` | x | x | x | x | x |
53+
| `process_heap_bytes` | | | | | |
54+
| `process_start_time_seconds` | x | x | x | x | x |
55+
| `process_threads` | x | x | | x | |
5556

5657
> [!NOTE]
5758
>

src/collector.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#[cfg_attr(target_os = "linux", path = "collector/linux.rs")]
33
#[cfg_attr(target_os = "windows", path = "collector/windows.rs")]
44
#[cfg_attr(target_os = "freebsd", path = "collector/freebsd.rs")]
5+
#[cfg_attr(target_os = "openbsd", path = "collector/openbsd.rs")]
56
#[allow(unused_attributes)]
67
#[cfg_attr(feature = "dummy", path = "collector/dummy.rs")]
78
mod collector;
@@ -12,7 +13,8 @@ mod collector;
1213
target_os = "macos",
1314
target_os = "linux",
1415
target_os = "windows",
15-
target_os = "freebsd"
16+
target_os = "freebsd",
17+
target_os = "openbsd"
1618
))
1719
))]
1820
compile_error!(
@@ -81,10 +83,29 @@ mod tests {
8183
assert_matches!(m.threads, Some(_));
8284
}
8385

86+
#[cfg(target_os = "openbsd")]
87+
#[test]
88+
fn test_collect_internal_ok_openbsd() {
89+
// TODO: if more metrics is implemented for OpenBSD, merge this test into
90+
// test_collect_internal_ok
91+
fibonacci(40);
92+
let m = collect();
93+
dbg!(&m);
94+
assert_matches!(m.cpu_seconds_total, Some(_));
95+
assert_matches!(m.open_fds, None);
96+
assert_matches!(m.max_fds, Some(_));
97+
assert_matches!(m.virtual_memory_bytes, None);
98+
assert_matches!(m.virtual_memory_max_bytes, None);
99+
assert_matches!(m.resident_memory_bytes, Some(_));
100+
assert_matches!(m.start_time_seconds, Some(_));
101+
assert_matches!(m.threads, None);
102+
}
103+
84104
#[cfg(not(target_os = "macos"))]
85105
#[cfg(not(target_os = "linux"))]
86106
#[cfg(not(target_os = "windows"))]
87107
#[cfg(not(target_os = "freebsd"))]
108+
#[cfg(not(target_os = "openbsd"))]
88109
#[cfg(feature = "dummy")]
89110
#[test]
90111
fn test_collect_internal_ok_dummy() {

src/collector/openbsd.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use std::convert::TryInto as _;
2+
3+
use super::Metrics;
4+
5+
fn getrusage(who: libc::c_int) -> Option<libc::rusage> {
6+
let mut usage = std::mem::MaybeUninit::zeroed();
7+
// SAFETY: libc call; usage is valid pointer to rusage struct
8+
if unsafe { libc::getrusage(who, usage.as_mut_ptr()) } == 0 {
9+
// SAFETY: libc call was success, struct must be initialized
10+
Some(unsafe { usage.assume_init() })
11+
} else {
12+
None
13+
}
14+
}
15+
16+
fn getrlimit(resource: libc::c_int) -> Option<libc::rlimit> {
17+
let mut limit = std::mem::MaybeUninit::zeroed();
18+
// SAFETY: libc call; limit is valid pointer to rlimit struct
19+
if unsafe { libc::getrlimit(resource, limit.as_mut_ptr()) } == 0 {
20+
// SAFETY: libc call was success, struct must be initialized
21+
Some(unsafe { limit.assume_init() })
22+
} else {
23+
None
24+
}
25+
}
26+
27+
fn translate_rlim(rlim: libc::rlim_t) -> u64 {
28+
if rlim == libc::RLIM_INFINITY {
29+
0
30+
} else {
31+
rlim as u64
32+
}
33+
}
34+
35+
fn kinfo_getproc(pid: libc::pid_t) -> Option<libc::kinfo_proc> {
36+
let mut kinfo_proc = std::mem::MaybeUninit::zeroed();
37+
let kinfo_proc_size = std::mem::size_of_val(&kinfo_proc) as libc::size_t;
38+
let mut data_size = kinfo_proc_size;
39+
40+
// code from deno doing similar stuff: https://github.com/denoland/deno/blob/20ae8db50d7d48ad020b83ebe78dc0e9e9eab3b2/runtime/ops/os/mod.rs#L415
41+
let mib = [
42+
libc::CTL_KERN,
43+
libc::KERN_PROC,
44+
libc::KERN_PROC_PID,
45+
pid,
46+
// this is required because MIB is array of ints, and is safe
47+
// as long size of kinfo_proc structure doesn't exceed 2GB
48+
kinfo_proc_size.try_into().unwrap(),
49+
1,
50+
];
51+
52+
// SAFETY: libc call; mib is statically initialized, kinfo_proc is valid pointer
53+
// to kinfo_proc and data_size holds its size
54+
if unsafe {
55+
libc::sysctl(
56+
mib.as_ptr(),
57+
mib.len() as _,
58+
kinfo_proc.as_mut_ptr() as *mut libc::c_void,
59+
&mut data_size,
60+
std::ptr::null_mut(),
61+
0,
62+
)
63+
} == 0
64+
&& data_size == kinfo_proc_size
65+
{
66+
// SAFETY: libc call was success and check for struct size passed, struct must be initialized
67+
Some(unsafe { kinfo_proc.assume_init() })
68+
} else {
69+
None
70+
}
71+
}
72+
73+
pub fn collect() -> Metrics {
74+
let mut metrics = Metrics::default();
75+
76+
// TODO: this is based on freebsd.rs, but lacks
77+
// - virtual_memory_bytes (kinfo_proc::p_vm_map_size contains zero)
78+
// - virtual_memory_max_bytes (openbsd lacks RLIMIT_AS)
79+
// - threads (no corresponding field in kinfo_proc(
80+
// - open_fds (no idea where to get it from)
81+
82+
if let Some(usage) = getrusage(libc::RUSAGE_SELF) {
83+
metrics.cpu_seconds_total = Some(
84+
(usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) as f64
85+
+ (usage.ru_utime.tv_usec + usage.ru_stime.tv_usec) as f64 / 1000000.0,
86+
);
87+
}
88+
89+
if let Some(limit_as) = getrlimit(libc::RLIMIT_NOFILE) {
90+
metrics.max_fds = Some(translate_rlim(limit_as.rlim_cur));
91+
}
92+
93+
// SAFETY: libc call
94+
let pid = unsafe { libc::getpid() };
95+
96+
if let Some(kinfo_proc) = kinfo_getproc(pid) {
97+
// reference:
98+
// https://github.com/openbsd/src/blob/782feb691bc15d1abd5f5c66fe3c0d336903a461/sys/sys/sysctl.h#L370
99+
100+
// SAFETY: libc call
101+
let pagesize = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as u64;
102+
metrics.resident_memory_bytes = Some(kinfo_proc.p_vm_rssize as u64 * pagesize);
103+
metrics.start_time_seconds = Some(kinfo_proc.p_ustart_sec);
104+
}
105+
106+
metrics
107+
}

0 commit comments

Comments
 (0)