1use std::cmp::Ord;
32use std::cmp::Ordering;
33use std::cmp::PartialOrd;
34use std::convert::TryFrom;
35use std::marker::PhantomData;
36use thiserror::Error;
37
38mod sys;
39
40#[derive(Debug, Error)]
42pub enum Error {
43 #[error("The byte-string is not a valid Rust string")]
45 InvalidString,
46
47 #[error("Kstat type {0} is invalid")]
49 InvalidType(u8),
50
51 #[error("The named kstat data type {0} is invalid")]
53 InvalidNamedType(u8),
54
55 #[error("A null pointer or empty kstat was encountered")]
57 NullData,
58
59 #[error(transparent)]
61 Io(#[from] std::io::Error),
62}
63
64#[derive(Debug)]
69pub struct Ctl {
70 ctl: *mut sys::kstat_ctl_t,
71}
72
73unsafe impl Send for Ctl {}
77
78impl Ctl {
79 pub fn new() -> Result<Self, Error> {
81 sys::open().map(|ctl| Ctl { ctl })
82 }
83
84 pub fn update(self) -> Result<Self, Error> {
89 sys::update(self.ctl).map(|_| self)
90 }
91
92 pub fn iter(&self) -> Iter<'_> {
97 Iter {
98 kstat: unsafe { (*self.ctl).kc_chain },
99 _d: PhantomData,
100 }
101 }
102
103 pub fn read<'a>(&self, kstat: &mut Kstat<'a>) -> Result<Data<'a>, Error> {
105 kstat.read(self.ctl)?;
106 kstat.data()
107 }
108
109 pub fn filter<'a>(
113 &'a self,
114 module: Option<&'a str>,
115 instance: Option<i32>,
116 name: Option<&'a str>,
117 ) -> impl Iterator<Item = Kstat<'a>> {
118 self.iter().filter(move |kstat| {
119 fn should_include<T>(inner: &T, cmp: &Option<T>) -> bool
120 where
121 T: PartialEq,
122 {
123 if let Some(cmp) = cmp {
124 inner == cmp
125 } else {
126 true }
128 }
129 should_include(&kstat.ks_module, &module)
130 && should_include(&kstat.ks_instance, &instance)
131 && should_include(&kstat.ks_name, &name)
132 })
133 }
134}
135
136impl Drop for Ctl {
137 fn drop(&mut self) {
138 let _ = sys::close(self.ctl);
139 }
140}
141
142#[derive(Debug)]
143pub struct Iter<'a> {
144 kstat: *mut sys::kstat_t,
145 _d: PhantomData<&'a ()>,
146}
147
148impl<'a> Iterator for Iter<'a> {
149 type Item = Kstat<'a>;
150
151 fn next(&mut self) -> Option<Self::Item> {
152 loop {
153 if let Some(ks) = unsafe { self.kstat.as_ref() } {
154 self.kstat = unsafe { *self.kstat }.ks_next;
155 if let Ok(ks) = Kstat::try_from(ks) {
156 break Some(ks);
157 }
158 } else {
160 break None;
161 }
162 }
163 }
164}
165
166unsafe impl<'a> Send for Iter<'a> {}
167
168#[derive(Clone, Copy, Debug, Eq, PartialEq)]
170pub struct Kstat<'a> {
171 pub ks_crtime: i64,
173 pub ks_snaptime: i64,
175 pub ks_module: &'a str,
177 pub ks_instance: i32,
179 pub ks_name: &'a str,
181 pub ks_type: Type,
183 pub ks_class: &'a str,
185 ks: *mut sys::kstat_t,
186}
187
188#[allow(clippy::non_canonical_partial_ord_impl)]
189impl<'a> PartialOrd for Kstat<'a> {
190 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
191 Some(
192 self.ks_class
193 .cmp(other.ks_class)
194 .then_with(|| self.ks_module.cmp(other.ks_module))
195 .then_with(|| self.ks_instance.cmp(&other.ks_instance))
196 .then_with(|| self.ks_name.cmp(other.ks_name))
197 .then_with(|| self.ks_class.cmp(other.ks_name)),
198 )
199 }
200}
201
202impl<'a> Ord for Kstat<'a> {
203 fn cmp(&self, other: &Self) -> Ordering {
204 self.partial_cmp(other).unwrap()
205 }
206}
207
208unsafe impl<'a> Send for Kstat<'a> {}
209
210impl<'a> Kstat<'a> {
211 fn read(&mut self, ctl: *mut sys::kstat_ctl_t) -> Result<(), Error> {
212 sys::read(ctl, self.ks, std::ptr::null_mut())?;
213 self.ks_snaptime = unsafe { (*self.ks).ks_snaptime };
214 Ok(())
215 }
216
217 fn data(&self) -> Result<Data<'a>, Error> {
218 let ks = unsafe { self.ks.as_ref() }.ok_or_else(|| Error::NullData)?;
219 match self.ks_type {
220 Type::Raw => Ok(Data::Raw(sys::kstat_data_raw(ks))),
221 Type::Named => Ok(Data::Named(
222 sys::kstat_data_named(ks)
223 .iter()
224 .map(Named::try_from)
225 .collect::<Result<_, _>>()?,
226 )),
227 Type::Intr => Ok(Data::Intr(Intr::from(sys::kstat_data_intr(ks)))),
228 Type::Io => Ok(Data::Io(Io::from(sys::kstat_data_io(ks)))),
229 Type::Timer => Ok(Data::Timer(
230 sys::kstat_data_timer(ks)
231 .iter()
232 .map(Timer::try_from)
233 .collect::<Result<_, _>>()?,
234 )),
235 }
236 }
237}
238
239impl<'a> TryFrom<&'a sys::kstat_t> for Kstat<'a> {
240 type Error = Error;
241 fn try_from(k: &'a sys::kstat_t) -> Result<Self, Self::Error> {
242 Ok(Kstat {
243 ks_crtime: k.ks_crtime,
244 ks_snaptime: k.ks_snaptime,
245 ks_module: sys::array_to_cstr(&k.ks_module)?,
246 ks_instance: k.ks_instance,
247 ks_name: sys::array_to_cstr(&k.ks_name)?,
248 ks_type: Type::try_from(k.ks_type)?,
249 ks_class: sys::array_to_cstr(&k.ks_name)?,
250 ks: k as *const _ as *mut _,
251 })
252 }
253}
254
255impl<'a> TryFrom<&'a *mut sys::kstat_t> for Kstat<'a> {
256 type Error = Error;
257 fn try_from(k: &'a *mut sys::kstat_t) -> Result<Self, Self::Error> {
258 if let Some(k) = unsafe { k.as_ref() } {
259 Kstat::try_from(k)
260 } else {
261 Err(Error::NullData)
262 }
263 }
264}
265
266#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
268pub enum Type {
269 Raw,
270 Named,
271 Intr,
272 Io,
273 Timer,
274}
275
276impl TryFrom<u8> for Type {
277 type Error = Error;
278 fn try_from(t: u8) -> Result<Self, Self::Error> {
279 match t {
280 sys::KSTAT_TYPE_RAW => Ok(Type::Raw),
281 sys::KSTAT_TYPE_NAMED => Ok(Type::Named),
282 sys::KSTAT_TYPE_INTR => Ok(Type::Intr),
283 sys::KSTAT_TYPE_IO => Ok(Type::Io),
284 sys::KSTAT_TYPE_TIMER => Ok(Type::Timer),
285 other => Err(Self::Error::InvalidType(other)),
286 }
287 }
288}
289
290#[derive(Debug, Copy, Clone, PartialEq)]
292pub enum NamedType {
293 Char,
294 Int32,
295 UInt32,
296 Int64,
297 UInt64,
298 String,
299}
300
301impl TryFrom<u8> for NamedType {
302 type Error = Error;
303 fn try_from(t: u8) -> Result<Self, Self::Error> {
304 match t {
305 sys::KSTAT_DATA_CHAR => Ok(NamedType::Char),
306 sys::KSTAT_DATA_INT32 => Ok(NamedType::Int32),
307 sys::KSTAT_DATA_UINT32 => Ok(NamedType::UInt32),
308 sys::KSTAT_DATA_INT64 => Ok(NamedType::Int64),
309 sys::KSTAT_DATA_UINT64 => Ok(NamedType::UInt64),
310 sys::KSTAT_DATA_STRING => Ok(NamedType::String),
311 other => Err(Self::Error::InvalidNamedType(other)),
312 }
313 }
314}
315
316#[derive(Clone, Debug)]
318pub enum Data<'a> {
319 Raw(Vec<&'a [u8]>),
320 Named(Vec<Named<'a>>),
321 Intr(Intr),
322 Io(Io),
323 Timer(Vec<Timer<'a>>),
324 Null,
325}
326
327#[derive(Debug, Clone, Copy)]
329pub struct Io {
330 pub nread: u64,
331 pub nwritten: u64,
332 pub reads: u32,
333 pub writes: u32,
334 pub wtime: i64,
335 pub wlentime: i64,
336 pub wlastupdate: i64,
337 pub rtime: i64,
338 pub rlentime: i64,
339 pub rlastupdate: i64,
340 pub wcnt: u32,
341 pub rcnt: u32,
342}
343
344impl From<&sys::kstat_io_t> for Io {
345 fn from(k: &sys::kstat_io_t) -> Self {
346 Io {
347 nread: k.nread,
348 nwritten: k.nwritten,
349 reads: k.reads,
350 writes: k.writes,
351 wtime: k.wtime,
352 wlentime: k.wlentime,
353 wlastupdate: k.wlastupdate,
354 rtime: k.rtime,
355 rlentime: k.rlentime,
356 rlastupdate: k.rlastupdate,
357 wcnt: k.wcnt,
358 rcnt: k.rcnt,
359 }
360 }
361}
362
363impl TryFrom<&*const sys::kstat_io_t> for Io {
364 type Error = Error;
365 fn try_from(k: &*const sys::kstat_io_t) -> Result<Self, Self::Error> {
366 if let Some(k) = unsafe { k.as_ref() } {
367 Ok(Io::from(k))
368 } else {
369 Err(Error::NullData)
370 }
371 }
372}
373
374#[derive(Debug, Copy, Clone)]
376pub struct Timer<'a> {
377 pub name: &'a str,
378 pub num_events: usize,
379 pub elapsed_time: i64,
380 pub min_time: i64,
381 pub max_time: i64,
382 pub start_time: i64,
383 pub stop_time: i64,
384}
385
386impl<'a> TryFrom<&'a sys::kstat_timer_t> for Timer<'a> {
387 type Error = Error;
388 fn try_from(k: &'a sys::kstat_timer_t) -> Result<Self, Self::Error> {
389 Ok(Self {
390 name: sys::array_to_cstr(&k.name)?,
391 num_events: k.num_events as _,
392 elapsed_time: k.elapsed_time,
393 min_time: k.min_time,
394 max_time: k.max_time,
395 start_time: k.start_time,
396 stop_time: k.stop_time,
397 })
398 }
399}
400
401impl<'a> TryFrom<&'a *const sys::kstat_timer_t> for Timer<'a> {
402 type Error = Error;
403 fn try_from(k: &'a *const sys::kstat_timer_t) -> Result<Self, Self::Error> {
404 if let Some(k) = unsafe { k.as_ref() } {
405 Timer::try_from(k)
406 } else {
407 Err(Error::NullData)
408 }
409 }
410}
411
412#[derive(Debug, Copy, Clone)]
414pub struct Intr {
415 pub hard: u32,
416 pub soft: u32,
417 pub watchdog: u32,
418 pub spurious: u32,
419 pub multisvc: u32,
420}
421
422impl From<&sys::kstat_intr_t> for Intr {
423 fn from(k: &sys::kstat_intr_t) -> Self {
424 Self {
425 hard: k.intr_hard,
426 soft: k.intr_soft,
427 watchdog: k.intr_watchdog,
428 spurious: k.intr_spurious,
429 multisvc: k.intr_multisvc,
430 }
431 }
432}
433
434impl TryFrom<&*const sys::kstat_intr_t> for Intr {
435 type Error = Error;
436 fn try_from(k: &*const sys::kstat_intr_t) -> Result<Self, Self::Error> {
437 if let Some(k) = unsafe { k.as_ref() } {
438 Ok(Intr::from(k))
439 } else {
440 Err(Error::NullData)
441 }
442 }
443}
444
445#[derive(Clone, Debug)]
447pub struct Named<'a> {
448 pub name: &'a str,
449 pub value: NamedData<'a>,
450}
451
452impl<'a> Named<'a> {
453 pub fn data_type(&self) -> NamedType {
455 self.value.data_type()
456 }
457}
458
459#[derive(Clone, Debug)]
461pub enum NamedData<'a> {
462 Char(&'a [u8]),
463 Int32(i32),
464 UInt32(u32),
465 Int64(i64),
466 UInt64(u64),
467 String(&'a str),
468}
469
470impl<'a> NamedData<'a> {
471 pub fn data_type(&self) -> NamedType {
473 match self {
474 NamedData::Char(_) => NamedType::Char,
475 NamedData::Int32(_) => NamedType::Int32,
476 NamedData::UInt32(_) => NamedType::UInt32,
477 NamedData::Int64(_) => NamedType::Int64,
478 NamedData::UInt64(_) => NamedType::UInt64,
479 NamedData::String(_) => NamedType::String,
480 }
481 }
482}
483
484impl<'a> TryFrom<&'a sys::kstat_named_t> for Named<'a> {
485 type Error = Error;
486 fn try_from(k: &'a sys::kstat_named_t) -> Result<Self, Self::Error> {
487 let name = sys::array_to_cstr(&k.name)?;
488 match NamedType::try_from(k.data_type)? {
489 NamedType::Char => {
490 let slice = unsafe {
491 let p = k.value.charc.as_ptr();
492 let len = k.value.charc.len();
493 std::slice::from_raw_parts(p, len)
494 };
495 Ok(Named {
496 name,
497 value: NamedData::Char(slice),
498 })
499 }
500 NamedType::Int32 => Ok(Named {
501 name,
502 value: NamedData::Int32(unsafe { k.value.i32 }),
503 }),
504 NamedType::UInt32 => Ok(Named {
505 name,
506 value: NamedData::UInt32(unsafe { k.value.ui32 }),
507 }),
508 NamedType::Int64 => Ok(Named {
509 name,
510 value: NamedData::Int64(unsafe { k.value.i64 }),
511 }),
512
513 NamedType::UInt64 => Ok(Named {
514 name,
515 value: NamedData::UInt64(unsafe { k.value.ui64 }),
516 }),
517 NamedType::String => {
518 let s = (&unsafe { k.value.str }).try_into()?;
519 Ok(Named {
520 name,
521 value: NamedData::String(s),
522 })
523 }
524 }
525 }
526}
527
528#[cfg(all(test, target_os = "illumos"))]
529mod test {
530 use super::*;
531 use std::collections::BTreeMap;
532
533 #[test]
534 fn basic_test() {
535 let ctl = Ctl::new().expect("Failed to create kstat control");
536 for mut kstat in ctl.iter() {
537 match ctl.read(&mut kstat) {
538 Ok(_) => {}
539 Err(e) => {
540 println!("{}", e);
541 }
542 }
543 }
544 }
545
546 #[test]
547 fn compare_with_kstat_cli() {
548 let ctl = Ctl::new().expect("Failed to create kstat control");
549 let mut kstat = ctl
550 .filter(Some("cpu_info"), Some(0), Some("cpu_info0"))
551 .next()
552 .expect("Failed to find kstat cpu_info:0:cpu_info0");
553 if let Data::Named(data) = ctl.read(&mut kstat).expect("Failed to read kstat") {
554 let mut items = BTreeMap::new();
555 for item in data.iter() {
556 items.insert(item.name, item);
557 }
558 let out = subprocess::Exec::cmd("/usr/bin/kstat")
559 .arg("-p")
560 .arg("cpu_info:0:cpu_info0:")
561 .stdout(subprocess::Redirection::Pipe)
562 .capture()
563 .expect("Failed to run /usr/bin/kstat");
564 let kstat_items: BTreeMap<_, _> = String::from_utf8(out.stdout)
565 .expect("Non UTF-8 output from kstat")
566 .lines()
567 .filter_map(|line| {
568 let parts = line.trim().split('\t').collect::<Vec<_>>();
569 assert_eq!(
570 parts.len(),
571 2,
572 "Lines from kstat should be 2 tab-separated items, found {:#?}",
573 parts
574 );
575 let (id, value) = (parts[0], parts[1]);
576 if id.ends_with("crtime") {
577 let crtime: f64 = value.parse().expect("Expected a crtime in nanoseconds");
578 let crtime = (crtime * 1e9) as i64;
579 assert!(
580 (crtime - kstat.ks_crtime) < 5 || (kstat.ks_crtime - crtime) < 5,
581 "Expected nearly equal crtimes"
582 );
583 None
585 } else if id.ends_with("snaptime") {
586 let snaptime: f64 =
587 value.parse().expect("Expected a snaptime in nanoseconds");
588 let snaptime = (snaptime * 1e9) as i64;
589 assert!(
590 (snaptime - kstat.ks_snaptime) < 5
591 || (kstat.ks_snaptime - snaptime) < 5,
592 "Expected nearly equal snaptimes"
593 );
594 None
596 } else if id.ends_with("class") {
597 None
599 } else {
600 Some((id.to_string(), value.to_string()))
601 }
602 })
603 .collect();
604 assert_eq!(
605 items.len(),
606 kstat_items.len(),
607 "Expected the same number of items from /usr/bin/kstat:\n{:#?}\n{:#?}",
608 items,
609 kstat_items
610 );
611 const SKIPPED_STATS: &[&'static str] = &["current_clock_Hz", "current_cstate"];
612 for (key, value) in kstat_items.iter() {
613 let name = key.split(':').last().expect("Expected to split on ':'");
614 if SKIPPED_STATS.contains(&name) {
615 println!("Skipping stat '{}', not stable enough for testing", name);
616 continue;
617 }
618 let item = items
619 .get(name)
620 .expect(&format!("Expected a name/value pair with name '{}'", name));
621 println!("key: {:#?}\nvalue: {:#?}", key, value);
622 println!("item: {:#?}", item);
623 match item.value {
624 NamedData::Char(slice) => {
625 for (sl, by) in slice.iter().zip(value.as_bytes().iter()) {
626 if by == &0 {
627 break;
628 }
629 assert_eq!(sl, by, "Expected equal bytes, found {} and {}", sl, by);
630 }
631 }
632 NamedData::Int32(i) => assert_eq!(i, value.parse().unwrap()),
633 NamedData::UInt32(u) => assert_eq!(u, value.parse().unwrap()),
634 NamedData::Int64(i) => assert_eq!(i, value.parse().unwrap()),
635 NamedData::UInt64(u) => assert_eq!(u, value.parse().unwrap()),
636 NamedData::String(s) => assert_eq!(s, value),
637 }
638 }
639 }
640 }
641}