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 pub fn with_null_kstat(ks_module: &'a str, ks_instance: i32, ks_name: &'a str) -> Self {
214 Self {
215 ks_crtime: 0,
216 ks_snaptime: 0,
217 ks_module,
218 ks_instance,
219 ks_name,
220 ks_type: Type::Named,
221 ks_class: "",
222 ks: std::ptr::null_mut(),
223 }
224 }
225
226 fn read(&mut self, ctl: *mut sys::kstat_ctl_t) -> Result<(), Error> {
227 sys::read(ctl, self.ks, std::ptr::null_mut())?;
228 self.ks_snaptime = unsafe { (*self.ks).ks_snaptime };
229 Ok(())
230 }
231
232 fn data(&self) -> Result<Data<'a>, Error> {
233 let ks = unsafe { self.ks.as_ref() }.ok_or_else(|| Error::NullData)?;
234 match self.ks_type {
235 Type::Raw => Ok(Data::Raw(sys::kstat_data_raw(ks))),
236 Type::Named => Ok(Data::Named(
237 sys::kstat_data_named(ks)
238 .iter()
239 .map(Named::try_from)
240 .collect::<Result<_, _>>()?,
241 )),
242 Type::Intr => Ok(Data::Intr(Intr::from(sys::kstat_data_intr(ks)))),
243 Type::Io => Ok(Data::Io(Io::from(sys::kstat_data_io(ks)))),
244 Type::Timer => Ok(Data::Timer(
245 sys::kstat_data_timer(ks)
246 .iter()
247 .map(Timer::try_from)
248 .collect::<Result<_, _>>()?,
249 )),
250 }
251 }
252}
253
254impl<'a> TryFrom<&'a sys::kstat_t> for Kstat<'a> {
255 type Error = Error;
256 fn try_from(k: &'a sys::kstat_t) -> Result<Self, Self::Error> {
257 Ok(Kstat {
258 ks_crtime: k.ks_crtime,
259 ks_snaptime: k.ks_snaptime,
260 ks_module: sys::array_to_cstr(&k.ks_module)?,
261 ks_instance: k.ks_instance,
262 ks_name: sys::array_to_cstr(&k.ks_name)?,
263 ks_type: Type::try_from(k.ks_type)?,
264 ks_class: sys::array_to_cstr(&k.ks_name)?,
265 ks: k as *const _ as *mut _,
266 })
267 }
268}
269
270impl<'a> TryFrom<&'a *mut sys::kstat_t> for Kstat<'a> {
271 type Error = Error;
272 fn try_from(k: &'a *mut sys::kstat_t) -> Result<Self, Self::Error> {
273 if let Some(k) = unsafe { k.as_ref() } {
274 Kstat::try_from(k)
275 } else {
276 Err(Error::NullData)
277 }
278 }
279}
280
281#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
283pub enum Type {
284 Raw,
285 Named,
286 Intr,
287 Io,
288 Timer,
289}
290
291impl TryFrom<u8> for Type {
292 type Error = Error;
293 fn try_from(t: u8) -> Result<Self, Self::Error> {
294 match t {
295 sys::KSTAT_TYPE_RAW => Ok(Type::Raw),
296 sys::KSTAT_TYPE_NAMED => Ok(Type::Named),
297 sys::KSTAT_TYPE_INTR => Ok(Type::Intr),
298 sys::KSTAT_TYPE_IO => Ok(Type::Io),
299 sys::KSTAT_TYPE_TIMER => Ok(Type::Timer),
300 other => Err(Self::Error::InvalidType(other)),
301 }
302 }
303}
304
305#[derive(Debug, Copy, Clone, PartialEq)]
307pub enum NamedType {
308 Char,
309 Int32,
310 UInt32,
311 Int64,
312 UInt64,
313 String,
314}
315
316impl TryFrom<u8> for NamedType {
317 type Error = Error;
318 fn try_from(t: u8) -> Result<Self, Self::Error> {
319 match t {
320 sys::KSTAT_DATA_CHAR => Ok(NamedType::Char),
321 sys::KSTAT_DATA_INT32 => Ok(NamedType::Int32),
322 sys::KSTAT_DATA_UINT32 => Ok(NamedType::UInt32),
323 sys::KSTAT_DATA_INT64 => Ok(NamedType::Int64),
324 sys::KSTAT_DATA_UINT64 => Ok(NamedType::UInt64),
325 sys::KSTAT_DATA_STRING => Ok(NamedType::String),
326 other => Err(Self::Error::InvalidNamedType(other)),
327 }
328 }
329}
330
331#[derive(Clone, Debug)]
333pub enum Data<'a> {
334 Raw(Vec<&'a [u8]>),
335 Named(Vec<Named<'a>>),
336 Intr(Intr),
337 Io(Io),
338 Timer(Vec<Timer<'a>>),
339 Null,
340}
341
342#[derive(Debug, Clone, Copy)]
344pub struct Io {
345 pub nread: u64,
346 pub nwritten: u64,
347 pub reads: u32,
348 pub writes: u32,
349 pub wtime: i64,
350 pub wlentime: i64,
351 pub wlastupdate: i64,
352 pub rtime: i64,
353 pub rlentime: i64,
354 pub rlastupdate: i64,
355 pub wcnt: u32,
356 pub rcnt: u32,
357}
358
359impl From<&sys::kstat_io_t> for Io {
360 fn from(k: &sys::kstat_io_t) -> Self {
361 Io {
362 nread: k.nread,
363 nwritten: k.nwritten,
364 reads: k.reads,
365 writes: k.writes,
366 wtime: k.wtime,
367 wlentime: k.wlentime,
368 wlastupdate: k.wlastupdate,
369 rtime: k.rtime,
370 rlentime: k.rlentime,
371 rlastupdate: k.rlastupdate,
372 wcnt: k.wcnt,
373 rcnt: k.rcnt,
374 }
375 }
376}
377
378impl TryFrom<&*const sys::kstat_io_t> for Io {
379 type Error = Error;
380 fn try_from(k: &*const sys::kstat_io_t) -> Result<Self, Self::Error> {
381 if let Some(k) = unsafe { k.as_ref() } {
382 Ok(Io::from(k))
383 } else {
384 Err(Error::NullData)
385 }
386 }
387}
388
389#[derive(Debug, Copy, Clone)]
391pub struct Timer<'a> {
392 pub name: &'a str,
393 pub num_events: usize,
394 pub elapsed_time: i64,
395 pub min_time: i64,
396 pub max_time: i64,
397 pub start_time: i64,
398 pub stop_time: i64,
399}
400
401impl<'a> TryFrom<&'a sys::kstat_timer_t> for Timer<'a> {
402 type Error = Error;
403 fn try_from(k: &'a sys::kstat_timer_t) -> Result<Self, Self::Error> {
404 Ok(Self {
405 name: sys::array_to_cstr(&k.name)?,
406 num_events: k.num_events as _,
407 elapsed_time: k.elapsed_time,
408 min_time: k.min_time,
409 max_time: k.max_time,
410 start_time: k.start_time,
411 stop_time: k.stop_time,
412 })
413 }
414}
415
416impl<'a> TryFrom<&'a *const sys::kstat_timer_t> for Timer<'a> {
417 type Error = Error;
418 fn try_from(k: &'a *const sys::kstat_timer_t) -> Result<Self, Self::Error> {
419 if let Some(k) = unsafe { k.as_ref() } {
420 Timer::try_from(k)
421 } else {
422 Err(Error::NullData)
423 }
424 }
425}
426
427#[derive(Debug, Copy, Clone)]
429pub struct Intr {
430 pub hard: u32,
431 pub soft: u32,
432 pub watchdog: u32,
433 pub spurious: u32,
434 pub multisvc: u32,
435}
436
437impl From<&sys::kstat_intr_t> for Intr {
438 fn from(k: &sys::kstat_intr_t) -> Self {
439 Self {
440 hard: k.intr_hard,
441 soft: k.intr_soft,
442 watchdog: k.intr_watchdog,
443 spurious: k.intr_spurious,
444 multisvc: k.intr_multisvc,
445 }
446 }
447}
448
449impl TryFrom<&*const sys::kstat_intr_t> for Intr {
450 type Error = Error;
451 fn try_from(k: &*const sys::kstat_intr_t) -> Result<Self, Self::Error> {
452 if let Some(k) = unsafe { k.as_ref() } {
453 Ok(Intr::from(k))
454 } else {
455 Err(Error::NullData)
456 }
457 }
458}
459
460#[derive(Clone, Debug)]
462pub struct Named<'a> {
463 pub name: &'a str,
464 pub value: NamedData<'a>,
465}
466
467impl<'a> Named<'a> {
468 pub fn data_type(&self) -> NamedType {
470 self.value.data_type()
471 }
472}
473
474#[derive(Clone, Debug)]
476pub enum NamedData<'a> {
477 Char(&'a [u8]),
478 Int32(i32),
479 UInt32(u32),
480 Int64(i64),
481 UInt64(u64),
482 String(&'a str),
483}
484
485impl<'a> NamedData<'a> {
486 pub fn data_type(&self) -> NamedType {
488 match self {
489 NamedData::Char(_) => NamedType::Char,
490 NamedData::Int32(_) => NamedType::Int32,
491 NamedData::UInt32(_) => NamedType::UInt32,
492 NamedData::Int64(_) => NamedType::Int64,
493 NamedData::UInt64(_) => NamedType::UInt64,
494 NamedData::String(_) => NamedType::String,
495 }
496 }
497}
498
499impl<'a> TryFrom<&'a sys::kstat_named_t> for Named<'a> {
500 type Error = Error;
501 fn try_from(k: &'a sys::kstat_named_t) -> Result<Self, Self::Error> {
502 let name = sys::array_to_cstr(&k.name)?;
503 match NamedType::try_from(k.data_type)? {
504 NamedType::Char => {
505 let slice = unsafe {
506 let p = k.value.charc.as_ptr();
507 let len = k.value.charc.len();
508 std::slice::from_raw_parts(p, len)
509 };
510 Ok(Named {
511 name,
512 value: NamedData::Char(slice),
513 })
514 }
515 NamedType::Int32 => Ok(Named {
516 name,
517 value: NamedData::Int32(unsafe { k.value.i32 }),
518 }),
519 NamedType::UInt32 => Ok(Named {
520 name,
521 value: NamedData::UInt32(unsafe { k.value.ui32 }),
522 }),
523 NamedType::Int64 => Ok(Named {
524 name,
525 value: NamedData::Int64(unsafe { k.value.i64 }),
526 }),
527
528 NamedType::UInt64 => Ok(Named {
529 name,
530 value: NamedData::UInt64(unsafe { k.value.ui64 }),
531 }),
532 NamedType::String => {
533 let s = (&unsafe { k.value.str }).try_into()?;
534 Ok(Named {
535 name,
536 value: NamedData::String(s),
537 })
538 }
539 }
540 }
541}
542
543#[cfg(all(test, target_os = "illumos"))]
544mod test {
545 use super::*;
546 use std::collections::BTreeMap;
547
548 #[test]
549 fn basic_test() {
550 let ctl = Ctl::new().expect("Failed to create kstat control");
551 for mut kstat in ctl.iter() {
552 match ctl.read(&mut kstat) {
553 Ok(_) => {}
554 Err(e) => {
555 println!("{}", e);
556 }
557 }
558 }
559 }
560
561 #[test]
562 fn compare_with_kstat_cli() {
563 let ctl = Ctl::new().expect("Failed to create kstat control");
564 let mut kstat = ctl
565 .filter(Some("cpu_info"), Some(0), Some("cpu_info0"))
566 .next()
567 .expect("Failed to find kstat cpu_info:0:cpu_info0");
568 if let Data::Named(data) = ctl.read(&mut kstat).expect("Failed to read kstat") {
569 let mut items = BTreeMap::new();
570 for item in data.iter() {
571 items.insert(item.name, item);
572 }
573 let out = subprocess::Exec::cmd("/usr/bin/kstat")
574 .arg("-p")
575 .arg("cpu_info:0:cpu_info0:")
576 .stdout(subprocess::Redirection::Pipe)
577 .capture()
578 .expect("Failed to run /usr/bin/kstat");
579 let kstat_items: BTreeMap<_, _> = String::from_utf8(out.stdout)
580 .expect("Non UTF-8 output from kstat")
581 .lines()
582 .filter_map(|line| {
583 let parts = line.trim().split('\t').collect::<Vec<_>>();
584 assert_eq!(
585 parts.len(),
586 2,
587 "Lines from kstat should be 2 tab-separated items, found {:#?}",
588 parts
589 );
590 let (id, value) = (parts[0], parts[1]);
591 if id.ends_with("crtime") {
592 let crtime: f64 = value.parse().expect("Expected a crtime in nanoseconds");
593 let crtime = (crtime * 1e9) as i64;
594 assert!(
595 (crtime - kstat.ks_crtime) < 5 || (kstat.ks_crtime - crtime) < 5,
596 "Expected nearly equal crtimes"
597 );
598 None
600 } else if id.ends_with("snaptime") {
601 let snaptime: f64 =
602 value.parse().expect("Expected a snaptime in nanoseconds");
603 let snaptime = (snaptime * 1e9) as i64;
604 assert!(
605 (snaptime - kstat.ks_snaptime) < 5
606 || (kstat.ks_snaptime - snaptime) < 5,
607 "Expected nearly equal snaptimes"
608 );
609 None
611 } else if id.ends_with("class") {
612 None
614 } else {
615 Some((id.to_string(), value.to_string()))
616 }
617 })
618 .collect();
619 assert_eq!(
620 items.len(),
621 kstat_items.len(),
622 "Expected the same number of items from /usr/bin/kstat:\n{:#?}\n{:#?}",
623 items,
624 kstat_items
625 );
626 const SKIPPED_STATS: &[&'static str] = &["current_clock_Hz", "current_cstate"];
627 for (key, value) in kstat_items.iter() {
628 let name = key.split(':').last().expect("Expected to split on ':'");
629 if SKIPPED_STATS.contains(&name) {
630 println!("Skipping stat '{}', not stable enough for testing", name);
631 continue;
632 }
633 let item = items
634 .get(name)
635 .expect(&format!("Expected a name/value pair with name '{}'", name));
636 println!("key: {:#?}\nvalue: {:#?}", key, value);
637 println!("item: {:#?}", item);
638 match item.value {
639 NamedData::Char(slice) => {
640 for (sl, by) in slice.iter().zip(value.as_bytes().iter()) {
641 if by == &0 {
642 break;
643 }
644 assert_eq!(sl, by, "Expected equal bytes, found {} and {}", sl, by);
645 }
646 }
647 NamedData::Int32(i) => assert_eq!(i, value.parse().unwrap()),
648 NamedData::UInt32(u) => assert_eq!(u, value.parse().unwrap()),
649 NamedData::Int64(i) => assert_eq!(i, value.parse().unwrap()),
650 NamedData::UInt64(u) => assert_eq!(u, value.parse().unwrap()),
651 NamedData::String(s) => assert_eq!(s, value),
652 }
653 }
654 }
655 }
656}