kstat/
lib.rs

1#![deny(warnings)]
2#![deny(missing_docs)]
3
4//! # kstat
5//!
6//! A simple rust crate that allows you to read kernel statistics via the kstat framework on
7//! illumos. The `kstat` crate exposes a `KstatReader` type that tracks kstats that are of
8//! interest to the consumer, allowing them to call the `read` method on the type to read in all of
9//! the named-value pairs associated with those particular kstats. This means that the crate only
10//! allows the consumer to track/read kstats that are of type KSTAT_TYPE_NAMED or KSTAT_TYPE_IO.
11//!
12//! # Example:
13//! ```
14//! extern crate kstat;
15//!
16//! use kstat::KstatReader;
17//!
18//! fn main() {
19//!     let reader = KstatReader::new(None, None, None, Some("zone_vfs"))
20//!         .expect("failed to create kstat reader");
21//!     let stats = reader.read().expect("failed to read kstats");
22//!     println!("{:#?}", stats);
23//! }
24//! ```
25
26extern crate byteorder;
27extern crate libc;
28
29use std::borrow::Cow;
30use std::collections::HashMap;
31use std::io;
32use std::marker::PhantomData;
33
34mod ffi;
35mod kstat_ctl;
36/// The type of data found in named-value pairs of a kstat
37pub mod kstat_named;
38
39use kstat_ctl::{Kstat, KstatCtl};
40use kstat_named::KstatNamedData;
41
42/// The corresponding data read in from a kstat
43#[derive(Debug)]
44pub struct KstatData {
45    /// string denoting class of kstat
46    pub class: String,
47    /// string denoting module of kstat
48    pub module: String,
49    /// int denoting instance of kstat
50    pub instance: i32,
51    /// string denoting name of kstat
52    pub name: String,
53    /// nanoseconds since boot of this snapshot
54    pub snaptime: i64,
55    /// creation time of this kstat in nanoseconds since boot
56    pub crtime: i64,
57    /// A hashmap of the named-value pairs for the kstat
58    pub data: HashMap<String, KstatNamedData>,
59}
60
61/// `KstatReader` represents all of the kstats that matched the fields of interest when created
62/// with `KstatCtl.reader(...)`
63#[derive(Debug)]
64pub struct KstatReader<'a> {
65    module: Option<Cow<'a, str>>,
66    instance: Option<i32>,
67    name: Option<Cow<'a, str>>,
68    class: Option<Cow<'a, str>>,
69    ctl: KstatCtl,
70}
71
72impl<'a> KstatReader<'a> {
73    /// Returns a `KstatReader` that tracks the kstats of interest.
74    ///
75    /// * `module` - optional string denoting module of kstat(s) to read
76    /// * `instance` - optional int denoting instance of kstat(s) to read
77    /// * `name` - optional string denoting name of kstat(s) to read
78    /// * `class` - optional string denoting class of kstat(s) to read
79    ///
80    /// # Example
81    /// ```
82    /// let reader = kstat::KstatReader::new(None, None, None, Some("zone_vfs"))
83    /// .expect("failed to create kstat reader");
84    ///
85    /// // Currently when creating a reader with class, module, and name set to "None" you
86    /// // will need to help the generics out and clue the reader in on the "String" type.
87    /// // The API may eventually change to not require this.
88    ///
89    /// let other_reader = kstat::KstatReader::new::<String>(None, Some(-1), None, None)
90    /// .expect("failed to create kstat reader");
91    ///
92    /// ```
93    pub fn new<S>(
94        module: Option<S>,
95        instance: Option<i32>,
96        name: Option<S>,
97        class: Option<S>,
98    ) -> io::Result<Self>
99    where
100        S: Into<Cow<'a, str>>,
101    {
102        let ctl = KstatCtl::new()?;
103        let module = module.map_or(None, |m| Some(m.into()));
104        let name = name.map_or(None, |n| Some(n.into()));
105        let class = class.map_or(None, |c| Some(c.into()));
106
107        Ok(KstatReader {
108            module,
109            instance,
110            name,
111            class,
112            ctl,
113        })
114    }
115
116    /// Calling read on the Reader will update the kstat chain and proceed to walk the chain
117    /// reading the corresponding data of a kstat that matches the search criteria.
118    ///
119    /// # Example
120    /// ```
121    /// # let reader = kstat::KstatReader::new(None, None, None, Some("zone_vfs")).unwrap();
122    /// let stats = reader.read().expect("failed to read kstat(s)");
123    /// ```
124    pub fn read(&self) -> io::Result<Vec<KstatData>> {
125        // First update the chain
126        self.ctl.chain_update()?;
127
128        let mut ret = Vec::new();
129        let mut kstat_ptr = self.ctl.get_chain();
130        while !kstat_ptr.is_null() {
131            let kstat = Kstat {
132                inner: kstat_ptr,
133                _marker: PhantomData,
134            };
135
136            // Loop until we reach the end of the chain
137            kstat_ptr = unsafe { (*kstat_ptr).ks_next };
138
139            // must be NAMED or IO
140            let ks_type = kstat.get_type();
141            if ks_type != ffi::KSTAT_TYPE_NAMED && ks_type != ffi::KSTAT_TYPE_IO {
142                continue;
143            }
144
145            // Compare against module/instance/name/class
146            if self.module.is_some() && kstat.get_module() != *self.module.as_ref().unwrap() {
147                continue;
148            }
149
150            if self.instance.is_some() && kstat.get_instance() != *self.instance.as_ref().unwrap() {
151                continue;
152            }
153
154            if self.name.is_some() && kstat.get_name() != *self.name.as_ref().unwrap() {
155                continue;
156            }
157
158            if self.class.is_some() && kstat.get_class() != *self.class.as_ref().unwrap() {
159                continue;
160            }
161
162            match kstat.read(&self.ctl) {
163                Ok(k) => ret.push(k),
164                Err(e) => {
165                    match e.raw_os_error().unwrap() {
166                        // the kstat went away by the time we call read, so forget it and move on
167                        // example: a zone is no longer running
168                        libc::ENXIO => continue,
169                        // I don't know why EIO seems to be common here. The kstat cmd on illumos
170                        // seems to ignore all errors and continue while only reporting the errors
171                        // when REPORT_UNKNOWN is set
172                        libc::EIO => continue,
173                        _ => return Err(e),
174                    }
175                }
176            }
177        }
178
179        Ok(ret)
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn all_reader() {
189        let reader =
190            KstatReader::new::<String>(None, None, None, None).expect("failed to create reader");
191        let stats = reader.read().expect("failed to read kstat(s)");
192        assert!(stats.len() > 0);
193    }
194
195    #[test]
196    fn module_reader() {
197        let module = "cpu";
198        let reader =
199            KstatReader::new(Some(module), None, None, None).expect("failed to create reader");
200        let stats = reader.read().expect("failed to read kstat(s)");
201        for stat in stats {
202            assert_eq!(stat.module, module);
203        }
204    }
205
206    #[test]
207    fn instance_reader() {
208        let instance: i32 = 0;
209        let reader = KstatReader::new::<String>(None, Some(instance), None, None)
210            .expect("failed to create reader");
211        let stats = reader.read().expect("failed to read kstat(s)");
212        for stat in stats {
213            assert_eq!(stat.instance, instance);
214        }
215    }
216
217    #[test]
218    fn name_reader() {
219        let name = "vm";
220        let reader =
221            KstatReader::new(None, None, Some(name), None).expect("failed to create reader");
222        let stats = reader.read().expect("failed to read kstat(s)");
223        for stat in stats {
224            assert_eq!(stat.name, name);
225        }
226    }
227
228    #[test]
229    fn class_reader() {
230        let class = "misc";
231        let reader =
232            KstatReader::new(None, None, None, Some(class)).expect("failed to create reader");
233        let stats = reader.read().expect("failed to read kstat(s)");
234        for stat in stats {
235            assert_eq!(stat.class, class);
236        }
237    }
238
239    #[test]
240    fn module_instance_name_class_reader() {
241        let module = "unix";
242        let instance = 1;
243        let name = "kmem_alloc_16";
244        let class = "keme_cache";
245        let reader = KstatReader::new(Some(module), Some(instance), Some(name), Some(class))
246            .expect("failed to create reader");
247        let stats = reader.read().expect("failed to read kstat(s)");
248        for stat in stats {
249            assert_eq!(stat.module, module);
250            assert_eq!(stat.instance, instance);
251            assert_eq!(stat.name, name);
252            assert_eq!(stat.class, class);
253        }
254    }
255}