freebsd_libgeom/
lib.rs

1//! Safe bindings to FreeBSD's libgeom
2//!
3//! The primary purpose of this crate is to support the
4//! [`gstat`](https://crates.io/crates/gstat) crate, so some bindings may be
5//! missing.  Open a Github issue if you have a good use for them.
6//! <https://www.freebsd.org/cgi/man.cgi?query=libgeom>
7
8// https://github.com/rust-lang/rust-clippy/issues/1553
9#![allow(clippy::redundant_closure_call)]
10
11use freebsd_libgeom_sys::*;
12use lazy_static::lazy_static;
13use std::{
14    ffi::CStr,
15    fmt,
16    io::{self, Error},
17    marker::PhantomData,
18    mem::{self, MaybeUninit},
19    ops::Sub,
20    os::raw::c_void,
21    pin::Pin,
22    ptr::NonNull
23};
24
25// BINTIME_SCALE is 1 / 2**64
26const BINTIME_SCALE: f64 = 5.421010862427522e-20;
27
28/// Used by [`Statistics::compute`]
29macro_rules! delta {
30    ($current: ident, $previous: ident, $field:ident, $index:expr) => {
31        {
32            let idx = $index as usize;
33            let old = if let Some(prev) = $previous {
34                unsafe {prev.devstat.as_ref() }.$field[idx]
35            } else {
36                0
37            };
38            let new = unsafe {$current.devstat.as_ref() }.$field[idx];
39            new - old
40        }
41    }
42}
43
44macro_rules! delta_t {
45    ($cur: expr, $prev: expr, $bintime:expr) => {
46        {
47            let old: bintime = if let Some(prev) = $prev {
48                $bintime(unsafe {prev.devstat.as_ref() })
49            } else {
50                bintime{sec: 0, frac: 0}
51            };
52            let new: bintime = $bintime(unsafe {$cur.devstat.as_ref() });
53            let mut dsec = new.sec - old.sec;
54            let (dfrac, overflow) = new.frac.overflowing_sub(old.frac);
55            if overflow {
56                dsec -= 1;
57            }
58            dsec as f64 + dfrac as f64 * BINTIME_SCALE
59        }
60    }
61}
62
63macro_rules! fields {
64    ($self: ident, $meth: ident, $field: ident) => {
65        pub fn $meth(&$self) -> u64 {
66            $self.$field
67        }
68    }
69}
70
71macro_rules! fields_per_sec {
72    ($self: ident, $meth: ident, $field: ident) => {
73        pub fn $meth(&$self) -> f64 {
74            if $self.etime > 0.0 {
75                $self.$field as f64 / $self.etime
76            } else {
77                0.0
78            }
79        }
80    }
81}
82
83macro_rules! kb_per_xfer {
84    ($self: ident, $meth: ident, $xfers: ident, $bytes: ident) => {
85        pub fn $meth(&$self) -> f64 {
86            if $self.$xfers > 0 {
87                $self.$bytes as f64 / (1<<10) as f64 / $self.$xfers as f64
88            } else {
89                0.0
90            }
91        }
92    }
93}
94
95macro_rules! mb_per_sec {
96    ($self: ident, $meth: ident, $field: ident) => {
97        pub fn $meth(&$self) -> f64 {
98            if $self.etime > 0.0 {
99                $self.$field as f64 / (1<<20) as f64 / $self.etime
100            } else {
101                0.0
102            }
103        }
104    }
105}
106
107macro_rules! ms_per_xfer {
108    ($self: ident, $meth: ident, $xfers: ident, $duration: ident) => {
109        pub fn $duration(&$self) -> f64 {
110            $self.$duration
111        }
112        pub fn $meth(&$self) -> f64 {
113            if $self.$xfers > 0 {
114                $self.$duration * 1000.0 / $self.$xfers as f64
115            } else {
116                0.0
117            }
118        }
119    }
120}
121
122lazy_static! {
123    static ref GEOM_STATS: io::Result<()> = {
124        let r = unsafe { geom_stats_open() };
125        if r != 0 {
126            Err(Error::last_os_error())
127        } else {
128            Ok(())
129        }
130    };
131}
132
133/// Describes the stats of a single geom element as part of a [`Snapshot`].
134#[derive(Debug, Copy, Clone)]
135#[repr(transparent)]
136pub struct Devstat<'a>{
137    devstat: NonNull<devstat>,
138    phantom: PhantomData<&'a devstat>
139}
140
141impl<'a> Devstat<'a> {
142    pub fn id(&'a self) -> Id<'a> {
143        Id {
144            id: unsafe { self.devstat.as_ref() }.id,
145            phantom: PhantomData
146        }
147    }
148}
149
150#[derive(Clone, Copy, Debug)]
151#[non_exhaustive]
152pub enum GidentError {
153    NotAProvider
154}
155
156impl fmt::Display for GidentError {
157    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158        match self {
159            GidentError::NotAProvider => {write!(f, "Not a GEOM provider")}
160        }
161    }
162}
163
164/// Identifies an element in the Geom [`Tree`]
165#[derive(Debug, Copy, Clone)]
166pub struct Gident<'a>{
167    ident: NonNull<gident>,
168    phantom: PhantomData<&'a Tree>
169}
170
171impl<'a> Gident<'a> {
172    pub fn is_consumer(&self) -> bool {
173        unsafe{self.ident.as_ref()}.lg_what == gident_ISCONSUMER
174    }
175
176    pub fn is_provider(&self) -> bool {
177        unsafe{self.ident.as_ref()}.lg_what == gident_ISPROVIDER
178    }
179
180    pub fn name(&self) -> Result<&'a CStr, GidentError> {
181        if !self.is_provider() {
182            Err(GidentError::NotAProvider)
183        } else {
184            unsafe{
185                let gprovider = self.ident.as_ref().lg_ptr as *const gprovider;
186                assert!(!gprovider.is_null());
187                Ok(CStr::from_ptr((*gprovider).lg_name))
188            }
189        }
190    }
191
192    /// Return the GEOM provider rank of this device, if it is a provider.
193    pub fn rank(&self) -> Option<u32> {
194        if !self.is_provider() {
195            None
196        } else {
197            unsafe{
198                let gprovider = self.ident.as_ref().lg_ptr as *const gprovider;
199                assert!(!gprovider.is_null());
200                let geom = (*gprovider).lg_geom;
201                if geom.is_null() {
202                    None
203                } else {
204                    Some((*geom).lg_rank)
205                }
206            }
207        }
208    }
209}
210
211/// A device identifier as contained in `struct devstat`.
212///
213/// It's an opaque structure, useful only with [`Tree::lookup`].
214#[derive(Debug, Copy, Clone)]
215pub struct Id<'a> {
216    id: *const c_void,
217    phantom: PhantomData<&'a Devstat<'a>>
218}
219
220/// Iterates through a pair of [`Snapshot`]s in lockstep, where one snapshot is
221/// optional.
222pub struct SnapshotPairIter<'a> {
223    cur: &'a mut Snapshot,
224    prev: Option<&'a mut Snapshot>
225}
226
227impl<'a> SnapshotPairIter<'a> {
228    fn new(cur: &'a mut Snapshot, prev: Option<&'a mut Snapshot>) -> Self {
229        SnapshotPairIter{cur, prev}
230    }
231}
232
233impl<'a> Iterator for SnapshotPairIter<'a> {
234    type Item = (Devstat<'a>, Option<Devstat<'a>>);
235
236    fn next(&mut self) -> Option<Self::Item> {
237        let ps = if let Some(prev) = self.prev.as_mut() {
238            let praw = unsafe {geom_stats_snapshot_next(prev.0.as_mut()) };
239            NonNull::new(praw)
240                .map(|devstat| Devstat{devstat, phantom: PhantomData})
241        } else {
242            None
243        };
244        let craw = unsafe {geom_stats_snapshot_next(self.cur.0.as_mut()) };
245        NonNull::new(craw)
246            .map(|devstat| (Devstat{devstat, phantom: PhantomData}, ps))
247    }
248}
249
250impl<'a> Drop for SnapshotPairIter<'a> {
251    fn drop(&mut self) {
252        self.cur.reset();
253        if let Some(prev) = self.prev.as_mut() {
254            prev.reset()
255        }
256    }
257}
258
259/// A geom statistics snapshot.
260///
261// FreeBSD BUG: geom_stats_snapshot_get should return an opaque pointer instead
262// of a void*, for better type safety.
263pub struct Snapshot(NonNull<c_void>);
264
265impl Snapshot {
266    /// Iterate through all devices described by the snapshot
267    pub fn iter(&mut self) -> SnapshotIter {
268        SnapshotIter(self)
269    }
270
271    /// Iterates through a pair of [`Snapshot`]s in lockstep, where one snapshot
272    /// is optional.
273    pub fn iter_pair<'a>(&'a mut self, prev: Option<&'a mut Snapshot>)
274        -> SnapshotPairIter<'a>
275    {
276        SnapshotPairIter::new(self, prev)
277    }
278
279    /// Acquires a new snapshot of the raw data from the kernel.
280    ///
281    /// Is not guaranteed to be completely atomic and consistent.
282    pub fn new() -> io::Result<Self> {
283        GEOM_STATS.as_ref().unwrap();
284        let raw = unsafe { geom_stats_snapshot_get() };
285        NonNull::new(raw)
286            .map(Snapshot)
287            .ok_or_else(Error::last_os_error)
288    }
289
290    /// Reset the state of the internal iterator back to the beginning
291    fn reset(&mut self) {
292        unsafe {geom_stats_snapshot_reset(self.0.as_mut())}
293    }
294
295    /// Accessor for the embedded timestamp generated by [`Snapshot::new`].
296    // FreeBSD BUG: geom_stats_snapshot_timestamp should take a const pointer,
297    // not a mut one.
298    pub fn timestamp(&mut self) -> Timespec {
299        let inner = unsafe {
300            let mut ts = MaybeUninit::uninit();
301            geom_stats_snapshot_timestamp(self.0.as_mut(), ts.as_mut_ptr());
302            ts.assume_init()
303        };
304        Timespec(inner)
305    }
306}
307
308impl Drop for Snapshot {
309    fn drop(&mut self) {
310        unsafe { geom_stats_snapshot_free(self.0.as_mut()) };
311    }
312}
313
314/// Return type of [`Snapshot::iter`].
315pub struct SnapshotIter<'a>(&'a mut Snapshot);
316
317impl<'a> Iterator for SnapshotIter<'a> {
318    type Item = Devstat<'a>;
319
320    fn next(&mut self) -> Option<Self::Item> {
321        let raw = unsafe {geom_stats_snapshot_next(self.0.0.as_mut()) };
322        NonNull::new(raw)
323            .map(|devstat| Devstat{devstat, phantom: PhantomData})
324    }
325}
326
327impl<'a> Drop for SnapshotIter<'a> {
328    fn drop(&mut self) {
329        self.0.reset();
330    }
331}
332
333/// Computes statistics between two [`Snapshot`]s for the same device.
334///
335/// This is equivalent to libgeom's
336/// [`devstat_compute_statistics`](https://www.freebsd.org/cgi/man.cgi?query=devstat&sektion=3)
337/// function.
338// Note that Rust cannot bind to devstat_compute_statistics because its API
339// includes "long double", which has no Rust equivalent.  So we reimplement the
340// logic here.
341pub struct Statistics<'a>{
342    current: Devstat<'a>,
343    previous: Option<Devstat<'a>>,
344    etime: f64,
345    total_bytes: u64,
346    total_bytes_free: u64,
347    total_bytes_read: u64,
348    total_bytes_write: u64,
349    total_blocks: u64,
350    total_blocks_free: u64,
351    total_blocks_read: u64,
352    total_blocks_write: u64,
353    total_duration: f64,
354    total_duration_free: f64,
355    total_duration_other: f64,
356    total_duration_read: f64,
357    total_duration_write: f64,
358    total_transfers: u64,
359    total_transfers_free: u64,
360    total_transfers_other: u64,
361    total_transfers_read: u64,
362    total_transfers_write: u64,
363}
364
365impl<'a> Statistics<'a> {
366    /// Compute statistics between two [`Devstat`] objects, which must
367    /// correspond to the same device, and should come from two separate
368    /// snapshots
369    ///
370    /// If `prev` is `None`, then statistics since boot will be returned.
371    /// `etime` should be the elapsed time in seconds between the two snapshots.
372    pub fn compute(
373        current: Devstat<'a>,
374        previous: Option<Devstat<'a>>,
375        etime: f64) -> Self
376    {
377        let cur = unsafe { current.devstat.as_ref() };
378
379        let total_transfers_read = delta!(current, previous, operations,
380                                          devstat_trans_flags_DEVSTAT_READ);
381        let total_transfers_write = delta!(current, previous, operations,
382                                           devstat_trans_flags_DEVSTAT_WRITE);
383        let total_transfers_other = delta!(current, previous, operations,
384                                           devstat_trans_flags_DEVSTAT_NO_DATA);
385        let total_transfers_free = delta!(current, previous, operations,
386                                          devstat_trans_flags_DEVSTAT_FREE);
387        let total_transfers = total_transfers_read + total_transfers_write +
388            total_transfers_other + total_transfers_free;
389
390        let total_bytes_free = delta!(current, previous, bytes,
391                                          devstat_trans_flags_DEVSTAT_FREE);
392        let total_bytes_read = delta!(current, previous, bytes,
393                                          devstat_trans_flags_DEVSTAT_READ);
394        let total_bytes_write = delta!(current, previous, bytes,
395                                          devstat_trans_flags_DEVSTAT_WRITE);
396        let total_bytes = total_bytes_read + total_bytes_write +
397            total_bytes_free;
398
399        let block_denominator = if cur.block_size > 0 {
400            cur.block_size as u64
401        } else {
402            512u64
403        };
404        let total_blocks = total_bytes / block_denominator;
405        let total_blocks_free = total_bytes_free / block_denominator;
406        let total_blocks_read = total_bytes_read / block_denominator;
407        let total_blocks_write = total_bytes_write / block_denominator;
408
409        let total_duration_free = delta_t!(current, previous,
410            |ds: &devstat|
411                ds.duration[devstat_trans_flags_DEVSTAT_FREE as usize]
412        );
413        let total_duration_read = delta_t!(current, previous,
414            |ds: &devstat|
415                ds.duration[devstat_trans_flags_DEVSTAT_READ as usize]
416        );
417        let total_duration_write = delta_t!(current, previous,
418            |ds: &devstat|
419                ds.duration[devstat_trans_flags_DEVSTAT_WRITE as usize]
420        );
421        let total_duration_other = delta_t!(current, previous,
422            |ds: &devstat|
423                ds.duration[devstat_trans_flags_DEVSTAT_NO_DATA as usize]
424        );
425        let total_duration = total_duration_read + total_duration_write +
426            total_duration_other + total_duration_free;
427
428        Self{
429            current,
430            previous,
431            etime,
432            total_bytes,
433            total_bytes_free,
434            total_bytes_read,
435            total_bytes_write,
436            total_blocks,
437            total_blocks_free,
438            total_blocks_read,
439            total_blocks_write,
440            total_duration,
441            total_duration_free,
442            total_duration_other,
443            total_duration_read,
444            total_duration_write,
445            total_transfers,
446            total_transfers_free,
447            total_transfers_other,
448            total_transfers_read,
449            total_transfers_write,
450        }
451    }
452
453    pub fn busy_time(&self) -> f64 {
454        let bt = unsafe{ self.current.devstat.as_ref() };
455        bt.busy_time.sec as f64 + bt.busy_time.frac as f64 * BINTIME_SCALE
456    }
457
458    /// The percentage of time the device had one or more transactions
459    /// outstanding between the acquisition of the two snapshots.
460    pub fn busy_pct(&self) -> f64 {
461        let delta = delta_t!(self.current, &self.previous,
462            |ds: &devstat| ds.busy_time);
463        (delta / self.etime * 100.0).max(0.0)
464    }
465
466    /// Returns the number of incomplete transactions at the time `cur` was
467    /// acquired.
468    pub fn queue_length(&self) -> u32 {
469        let cur = unsafe {self.current.devstat.as_ref() };
470        cur.start_count - cur.end_count
471    }
472
473    fields!{self, total_bytes, total_bytes}
474    fields!{self, total_bytes_free, total_bytes_free}
475    fields!{self, total_bytes_read, total_bytes_read}
476    fields!{self, total_bytes_write, total_bytes_write}
477    fields!{self, total_blocks, total_blocks}
478    fields!{self, total_blocks_free, total_blocks_free}
479    fields!{self, total_blocks_read, total_blocks_read}
480    fields!{self, total_blocks_write, total_blocks_write}
481    fields!{self, total_transfers, total_transfers}
482    fields!{self, total_transfers_free, total_transfers_free}
483    fields!{self, total_transfers_read, total_transfers_read}
484    fields!{self, total_transfers_other, total_transfers_other}
485    fields!{self, total_transfers_write, total_transfers_write}
486    fields_per_sec!{self, blocks_per_second, total_blocks}
487    fields_per_sec!{self, blocks_per_second_free, total_blocks_free}
488    fields_per_sec!{self, blocks_per_second_read, total_blocks_read}
489    fields_per_sec!{self, blocks_per_second_write, total_blocks_write}
490    kb_per_xfer!{self, kb_per_transfer, total_transfers, total_bytes}
491    kb_per_xfer!{self, kb_per_transfer_free, total_transfers_free, total_bytes}
492    kb_per_xfer!{self, kb_per_transfer_read, total_transfers_read, total_bytes}
493    kb_per_xfer!{self, kb_per_transfer_write, total_transfers_write,
494        total_bytes}
495    ms_per_xfer!{self, ms_per_transaction, total_transfers, total_duration}
496    ms_per_xfer!{self, ms_per_transaction_free, total_transfers_free,
497                total_duration_free}
498    ms_per_xfer!{self, ms_per_transaction_read, total_transfers_read,
499                total_duration_read}
500    ms_per_xfer!{self, ms_per_transaction_other, total_transfers_other,
501                total_duration_other}
502    ms_per_xfer!{self, ms_per_transaction_write, total_transfers_write,
503                total_duration_write}
504    mb_per_sec!{self, mb_per_second, total_bytes}
505    mb_per_sec!{self, mb_per_second_free, total_bytes_free}
506    mb_per_sec!{self, mb_per_second_read, total_bytes_read}
507    mb_per_sec!{self, mb_per_second_write, total_bytes_write}
508    fields_per_sec!{self, transfers_per_second, total_transfers}
509    fields_per_sec!{self, transfers_per_second_free, total_transfers_free}
510    fields_per_sec!{self, transfers_per_second_other, total_transfers_other}
511    fields_per_sec!{self, transfers_per_second_read, total_transfers_read}
512    fields_per_sec!{self, transfers_per_second_write, total_transfers_write}
513}
514
515/// Return type of [`Snapshot::timestamp`].  It's the familiar C `timespec`.
516#[repr(transparent)]
517#[derive(Debug, Copy, Clone)]
518// The wrapper is necessary just to be proper CamelCase
519pub struct Timespec(freebsd_libgeom_sys::timespec);
520
521impl From<Timespec> for f64 {
522    fn from(ts: Timespec) -> f64 {
523        ts.0.tv_sec as f64 + ts.0.tv_nsec as f64 * 1e-9
524    }
525}
526
527impl Sub for Timespec {
528    type Output = Self;
529
530    fn sub(self, rhs: Timespec) -> Self::Output {
531        let mut tv_sec = self.0.tv_sec - rhs.0.tv_sec;
532        let mut tv_nsec = self.0.tv_nsec - rhs.0.tv_nsec;
533        if tv_nsec < 0 {
534            tv_sec -= 1;
535            tv_nsec += 1_000_000_000;
536        }
537        Self(freebsd_libgeom_sys::timespec {tv_sec, tv_nsec})
538    }
539}
540
541/// Describes the entire Geom heirarchy.
542#[derive(Debug)]
543#[repr(transparent)]
544pub struct Tree(Pin<Box<gmesh>>);
545
546impl Tree {
547    // FreeBSD BUG: geom_lookupid takes a mutable pointer when it could be const
548    pub fn lookup<'a>(&'a mut self, id: Id) -> Option<Gident<'a>> {
549        let raw = unsafe {
550            geom_lookupid(&mut *self.0, id.id)
551        };
552        NonNull::new(raw)
553            .map(|ident| Gident{ident, phantom: PhantomData})
554    }
555
556    /// Construct a new `Tree` representing all available geom providers
557    pub fn new() -> io::Result<Self> {
558        let (inner, r) = unsafe {
559            let mut inner = Box::pin(mem::zeroed());
560            let r = geom_gettree(&mut *inner);
561            (inner, r)
562        };
563        if r != 0 {
564            Err(Error::last_os_error())
565        } else {
566            Ok(Tree(inner))
567        }
568    }
569}
570
571impl Drop for Tree {
572    fn drop(&mut self) {
573        unsafe { geom_deletetree(&mut *self.0) };
574    }
575}
576
577#[cfg(test)]
578mod t {
579    use super::*;
580    use approx::*;
581
582    mod delta_t {
583        use super::*;
584
585        macro_rules! devstat {
586            ($bintime: expr) => {{
587                let inner = unsafe{ devstat {
588                    busy_time: $bintime,
589                    .. mem::zeroed()
590                }};
591                let outer = Devstat {
592                    devstat: NonNull::from(&inner),
593                    phantom: PhantomData
594                };
595                (outer, inner)
596            }}
597        }
598
599        #[test]
600        fn zero() {
601            let (prev, _prev) = devstat!(bintime{sec: 0, frac: 0});
602            let (cur, _cur) = devstat!(bintime{sec: 0, frac: 0});
603            let r = delta_t!(cur, Some(prev), |ds: &devstat| ds.busy_time);
604            assert_relative_eq!(r, 0.0);
605        }
606
607        #[test]
608        fn half() {
609            let (prev, _prev) = devstat!(bintime{sec: 0, frac: 0});
610            let (cur, _cur) = devstat!(bintime{sec: 0, frac: 1<<63});
611            let r = delta_t!(cur, Some(prev), |ds: &devstat| ds.busy_time);
612            assert_relative_eq!(r, 0.5);
613        }
614
615        #[test]
616        fn half2() {
617            let (prev, _prev) = devstat!(bintime{sec: 0, frac: 1<<63});
618            let (cur, _cur) = devstat!(bintime{sec: 1, frac: 0});
619            let r = delta_t!(cur, Some(prev), |ds: &devstat| ds.busy_time);
620            assert_relative_eq!(r, 0.5);
621        }
622
623        #[test]
624        fn one() {
625            let (prev, _prev) = devstat!(bintime{sec: 0, frac: 0});
626            let (cur, _cur) = devstat!(bintime{sec: 1, frac: 0});
627            let r = delta_t!(cur, Some(prev), |ds: &devstat| ds.busy_time);
628            assert_relative_eq!(r, 1.0);
629        }
630
631        #[test]
632        fn neg() {
633            let (prev, _prev) = devstat!(bintime{sec: 1, frac: 1<<62});
634            let (cur, _cur) = devstat!(bintime{sec: 0, frac: 0});
635            let r = delta_t!(cur, Some(prev), |ds: &devstat| ds.busy_time);
636            assert_relative_eq!(r, -1.25);
637        }
638    }
639}