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}