1use linsight_core::{Category, Cell, Reading, SensorId, SensorKind, TableRow, Unit};
43use stabby::option::Option as SOption;
44use stabby::string::String as SString;
45use stabby::vec::Vec as SVec;
46
47#[stabby::stabby]
61#[derive(Clone, Debug)]
62pub struct RSensorId {
63 pub value: SString,
64}
65
66impl From<SensorId> for RSensorId {
67 fn from(id: SensorId) -> Self {
68 Self { value: id.as_str().into() }
69 }
70}
71
72impl From<&SensorId> for RSensorId {
73 fn from(id: &SensorId) -> Self {
74 Self { value: id.as_str().into() }
75 }
76}
77
78impl From<RSensorId> for SensorId {
79 fn from(r: RSensorId) -> Self {
80 SensorId::new(r.value.as_str())
81 }
82}
83
84#[stabby::stabby]
92#[repr(u8)]
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94pub enum RUnitKind {
95 Percent,
96 Celsius,
97 Bytes,
98 BytesPerSec,
99 Hertz,
100 Watts,
101 Volts,
102 Rpm,
103 Count,
104 Custom,
105}
106
107#[stabby::stabby]
111#[derive(Clone, Debug)]
112pub struct RUnit {
113 pub kind: RUnitKind,
114 pub custom: SOption<SString>,
115}
116
117impl From<Unit> for RUnit {
118 fn from(u: Unit) -> Self {
119 match u {
120 Unit::Percent => Self { kind: RUnitKind::Percent, custom: SOption::None() },
121 Unit::Celsius => Self { kind: RUnitKind::Celsius, custom: SOption::None() },
122 Unit::Bytes => Self { kind: RUnitKind::Bytes, custom: SOption::None() },
123 Unit::BytesPerSec => Self { kind: RUnitKind::BytesPerSec, custom: SOption::None() },
124 Unit::Hertz => Self { kind: RUnitKind::Hertz, custom: SOption::None() },
125 Unit::Watts => Self { kind: RUnitKind::Watts, custom: SOption::None() },
126 Unit::Volts => Self { kind: RUnitKind::Volts, custom: SOption::None() },
127 Unit::Rpm => Self { kind: RUnitKind::Rpm, custom: SOption::None() },
128 Unit::Count => Self { kind: RUnitKind::Count, custom: SOption::None() },
129 Unit::Custom(s) => {
130 Self { kind: RUnitKind::Custom, custom: SOption::Some(s.as_str().into()) }
131 }
132 }
133 }
134}
135
136impl From<RUnit> for Unit {
137 fn from(r: RUnit) -> Self {
138 match r.kind {
139 RUnitKind::Percent => Unit::Percent,
140 RUnitKind::Celsius => Unit::Celsius,
141 RUnitKind::Bytes => Unit::Bytes,
142 RUnitKind::BytesPerSec => Unit::BytesPerSec,
143 RUnitKind::Hertz => Unit::Hertz,
144 RUnitKind::Watts => Unit::Watts,
145 RUnitKind::Volts => Unit::Volts,
146 RUnitKind::Rpm => Unit::Rpm,
147 RUnitKind::Count => Unit::Count,
148 RUnitKind::Custom => {
149 let opt: Option<&SString> = r.custom.as_ref();
154 let label = opt.map(|s| s.as_str().to_owned()).unwrap_or_default();
155 Unit::Custom(label)
156 }
157 }
158 }
159}
160
161#[stabby::stabby]
168#[repr(u8)]
169#[derive(Clone, Copy, Debug)]
170pub enum RSensorKind {
171 Scalar,
172 Counter,
173 Table,
174 State,
175}
176
177impl From<SensorKind> for RSensorKind {
178 fn from(k: SensorKind) -> Self {
179 match k {
180 SensorKind::Scalar => RSensorKind::Scalar,
181 SensorKind::Counter => RSensorKind::Counter,
182 SensorKind::Table => RSensorKind::Table,
183 SensorKind::State => RSensorKind::State,
184 }
185 }
186}
187
188impl From<RSensorKind> for SensorKind {
189 fn from(r: RSensorKind) -> Self {
190 match r {
191 RSensorKind::Scalar => SensorKind::Scalar,
192 RSensorKind::Counter => SensorKind::Counter,
193 RSensorKind::Table => SensorKind::Table,
194 RSensorKind::State => SensorKind::State,
195 }
196 }
197}
198
199#[stabby::stabby]
206#[repr(u8)]
207#[derive(Clone, Copy, Debug)]
208pub enum RCategory {
209 Cpu,
210 Gpu,
211 Memory,
212 Storage,
213 Network,
214 Custom,
215}
216
217impl From<Category> for RCategory {
218 fn from(c: Category) -> Self {
219 match c {
220 Category::Cpu => RCategory::Cpu,
221 Category::Gpu => RCategory::Gpu,
222 Category::Memory => RCategory::Memory,
223 Category::Storage => RCategory::Storage,
224 Category::Network => RCategory::Network,
225 Category::Custom => RCategory::Custom,
226 }
227 }
228}
229
230impl From<RCategory> for Category {
231 fn from(r: RCategory) -> Self {
232 match r {
233 RCategory::Cpu => Category::Cpu,
234 RCategory::Gpu => Category::Gpu,
235 RCategory::Memory => Category::Memory,
236 RCategory::Storage => Category::Storage,
237 RCategory::Network => Category::Network,
238 RCategory::Custom => Category::Custom,
239 }
240 }
241}
242
243#[stabby::stabby]
253#[repr(u8)]
254#[derive(Clone, Copy, Debug, PartialEq)]
255pub enum RHardwareCategoryKind {
256 Gpu,
257 Storage,
258 Network,
259 Cpu,
260 Other,
261}
262
263impl From<linsight_core::HardwareCategory> for RHardwareCategoryKind {
264 fn from(c: linsight_core::HardwareCategory) -> Self {
265 match c {
266 linsight_core::HardwareCategory::Gpu => Self::Gpu,
267 linsight_core::HardwareCategory::Storage => Self::Storage,
268 linsight_core::HardwareCategory::Network => Self::Network,
269 linsight_core::HardwareCategory::Cpu => Self::Cpu,
270 linsight_core::HardwareCategory::Other => Self::Other,
271 }
272 }
273}
274
275impl From<RHardwareCategoryKind> for linsight_core::HardwareCategory {
276 fn from(r: RHardwareCategoryKind) -> Self {
277 match r {
278 RHardwareCategoryKind::Gpu => Self::Gpu,
279 RHardwareCategoryKind::Storage => Self::Storage,
280 RHardwareCategoryKind::Network => Self::Network,
281 RHardwareCategoryKind::Cpu => Self::Cpu,
282 RHardwareCategoryKind::Other => Self::Other,
283 }
284 }
285}
286
287#[stabby::stabby]
300#[repr(C)]
301#[derive(Clone, Debug)]
302pub struct RHardwareDevice {
303 pub key: SString,
304 pub category_kind: RHardwareCategoryKind,
305 pub model: SString,
306 pub vendor: SOption<SString>,
307 pub location: SOption<SString>,
308 pub plugin_device_id: SString,
309}
310
311impl From<linsight_core::HardwareDevice> for RHardwareDevice {
312 fn from(d: linsight_core::HardwareDevice) -> Self {
313 Self {
314 key: SString::from(d.key.as_str()),
315 category_kind: d.category.into(),
316 model: SString::from(d.model.as_str()),
317 vendor: d.vendor.map(|s| SString::from(s.as_str())).into(),
318 location: d.location.map(|s| SString::from(s.as_str())).into(),
319 plugin_device_id: SString::from(d.plugin_device_id.as_str()),
320 }
321 }
322}
323
324impl From<RHardwareDevice> for linsight_core::HardwareDevice {
325 fn from(r: RHardwareDevice) -> Self {
326 Self {
327 key: linsight_core::HardwareDeviceKey::try_new(r.key.as_str().to_owned())
328 .expect("RHardwareDevice key was validated by host_init"),
329 category: r.category_kind.into(),
330 model: r.model.as_str().to_owned(),
331 vendor: Option::from(r.vendor).map(|s: SString| s.as_str().to_owned()),
332 location: Option::from(r.location).map(|s: SString| s.as_str().to_owned()),
333 plugin_id: String::new(),
334 plugin_device_id: r.plugin_device_id.as_str().to_owned(),
335 sensor_ids: vec![],
336 }
337 }
338}
339
340#[stabby::stabby]
346#[repr(u8)]
347#[derive(Clone, Copy, Debug, PartialEq, Eq)]
348pub enum RCellKind {
349 Text,
350 Number,
351 Bytes,
352}
353
354#[stabby::stabby]
358#[derive(Clone, Debug)]
359pub struct RCell {
360 pub kind: RCellKind,
361 pub text: SOption<SString>,
362 pub number: f64,
363 pub bytes: u64,
364}
365
366impl From<Cell> for RCell {
367 fn from(c: Cell) -> Self {
368 match c {
369 Cell::Text(s) => Self {
370 kind: RCellKind::Text,
371 text: SOption::Some(s.as_str().into()),
372 number: 0.0,
373 bytes: 0,
374 },
375 Cell::Number(n) => {
376 Self { kind: RCellKind::Number, text: SOption::None(), number: n, bytes: 0 }
377 }
378 Cell::Bytes(b) => {
379 Self { kind: RCellKind::Bytes, text: SOption::None(), number: 0.0, bytes: b }
380 }
381 }
382 }
383}
384
385impl From<RCell> for Cell {
386 fn from(r: RCell) -> Self {
387 match r.kind {
388 RCellKind::Text => {
389 let opt: Option<&SString> = r.text.as_ref();
390 Cell::Text(opt.map(|s| s.as_str().to_owned()).unwrap_or_default())
391 }
392 RCellKind::Number => Cell::Number(r.number),
393 RCellKind::Bytes => Cell::Bytes(r.bytes),
394 }
395 }
396}
397
398#[stabby::stabby]
401#[derive(Clone, Debug)]
402pub struct RTableRow {
403 pub cells: SVec<RCell>,
404}
405
406impl From<TableRow> for RTableRow {
407 fn from(row: TableRow) -> Self {
408 let mut cells = SVec::with_capacity(row.cells.len());
409 for c in row.cells {
410 cells.push(c.into());
411 }
412 Self { cells }
413 }
414}
415
416impl From<RTableRow> for TableRow {
417 fn from(r: RTableRow) -> Self {
418 TableRow { cells: svec_into_std(r.cells) }
419 }
420}
421
422#[stabby::stabby]
428#[repr(u8)]
429#[derive(Clone, Copy, Debug, PartialEq, Eq)]
430pub enum RReadingKind {
431 Scalar,
432 Counter,
433 Table,
434 State,
435}
436
437#[stabby::stabby]
443#[derive(Clone, Debug)]
444pub struct RReading {
445 pub kind: RReadingKind,
446 pub scalar: f64,
447 pub counter: u64,
448 pub state: SOption<SString>,
449 pub table: SVec<RTableRow>,
450}
451
452impl From<Reading> for RReading {
453 fn from(r: Reading) -> Self {
454 match r {
455 Reading::Scalar(v) => Self {
456 kind: RReadingKind::Scalar,
457 scalar: v,
458 counter: 0,
459 state: SOption::None(),
460 table: SVec::new(),
461 },
462 Reading::Counter(v) => Self {
463 kind: RReadingKind::Counter,
464 scalar: 0.0,
465 counter: v,
466 state: SOption::None(),
467 table: SVec::new(),
468 },
469 Reading::State(s) => Self {
470 kind: RReadingKind::State,
471 scalar: 0.0,
472 counter: 0,
473 state: SOption::Some(s.as_str().into()),
474 table: SVec::new(),
475 },
476 Reading::Table(rows) => {
477 let mut out = SVec::with_capacity(rows.len());
478 for row in rows {
479 out.push(row.into());
480 }
481 Self {
482 kind: RReadingKind::Table,
483 scalar: 0.0,
484 counter: 0,
485 state: SOption::None(),
486 table: out,
487 }
488 }
489 }
490 }
491}
492
493impl From<RReading> for Reading {
494 fn from(r: RReading) -> Self {
495 match r.kind {
496 RReadingKind::Scalar => Reading::Scalar(r.scalar),
497 RReadingKind::Counter => Reading::Counter(r.counter),
498 RReadingKind::State => {
499 let opt: Option<&SString> = r.state.as_ref();
500 Reading::State(opt.map(|s| s.as_str().to_owned()).unwrap_or_default())
501 }
502 RReadingKind::Table => Reading::Table(svec_into_std(r.table)),
503 }
504 }
505}
506
507pub(crate) fn svec_into_std<R, T>(mut v: SVec<R>) -> Vec<T>
514where
515 T: From<R>,
516 R: stabby::IStable + Clone,
517{
518 let len = v.len();
519 let mut out: Vec<T> = Vec::with_capacity(len);
520 while let Some(item) = v.pop() {
521 out.push(T::from(item));
522 }
523 out.reverse();
524 out
525}
526
527#[cfg(test)]
532mod tests {
533 use super::*;
534
535 #[test]
536 fn unit_round_trips() {
537 for u in [
538 Unit::Percent,
539 Unit::Celsius,
540 Unit::Bytes,
541 Unit::BytesPerSec,
542 Unit::Hertz,
543 Unit::Watts,
544 Unit::Volts,
545 Unit::Rpm,
546 Unit::Count,
547 Unit::Custom("Mb/s".into()),
548 ] {
549 let r: RUnit = u.clone().into();
550 let back: Unit = r.into();
551 assert_eq!(u, back, "round trip failed for variant {u:?}");
552 }
553 }
554
555 #[test]
556 fn sensor_kind_round_trips() {
557 for k in [SensorKind::Scalar, SensorKind::Counter, SensorKind::Table, SensorKind::State] {
558 let r: RSensorKind = k.into();
559 assert_eq!(SensorKind::from(r), k);
560 }
561 }
562
563 #[test]
564 fn category_round_trips() {
565 for c in [
566 Category::Cpu,
567 Category::Gpu,
568 Category::Memory,
569 Category::Storage,
570 Category::Network,
571 Category::Custom,
572 ] {
573 let r: RCategory = c.into();
574 assert_eq!(Category::from(r), c);
575 }
576 }
577
578 #[test]
579 fn reading_table_round_trips() {
580 let r = Reading::Table(vec![TableRow {
581 cells: vec![Cell::Text("p".into()), Cell::Number(1.0), Cell::Bytes(42)],
582 }]);
583 let rr: RReading = r.clone().into();
584 let back: Reading = rr.into();
585 assert_eq!(back, r);
586 }
587
588 #[test]
589 fn reading_scalar_round_trips() {
590 let r = Reading::Scalar(42.5);
591 let rr: RReading = r.clone().into();
592 assert_eq!(Reading::from(rr), r);
593 }
594
595 #[test]
596 fn reading_counter_round_trips() {
597 let r = Reading::Counter(123_456_789);
598 let rr: RReading = r.clone().into();
599 assert_eq!(Reading::from(rr), r);
600 }
601
602 #[test]
603 fn reading_state_round_trips() {
604 let r = Reading::State("up".into());
605 let rr: RReading = r.clone().into();
606 assert_eq!(Reading::from(rr), r);
607 }
608
609 #[test]
610 fn sensor_id_round_trips() {
611 let id = SensorId::new("cpu.util");
612 let r: RSensorId = id.clone().into();
613 assert_eq!(SensorId::from(r), id);
614 }
615
616 #[test]
617 fn hardware_category_kind_round_trips() {
618 use linsight_core::HardwareCategory;
619 for c in [
620 HardwareCategory::Gpu,
621 HardwareCategory::Storage,
622 HardwareCategory::Network,
623 HardwareCategory::Cpu,
624 HardwareCategory::Other,
625 ] {
626 let r: RHardwareCategoryKind = c.into();
627 let back: HardwareCategory = r.into();
628 assert_eq!(back, c);
629 }
630 }
631
632 #[test]
633 fn hardware_device_round_trips_minimal() {
634 use linsight_core::{HardwareCategory, HardwareDevice, HardwareDeviceKey};
635 let dev = HardwareDevice {
636 key: HardwareDeviceKey::try_new("pci:0000:06:00.0").unwrap(),
637 category: HardwareCategory::Gpu,
638 model: "Intel Arc B-series".into(),
639 vendor: None,
640 location: None,
641 plugin_id: String::new(),
642 plugin_device_id: "gpu0".into(),
643 sensor_ids: vec![],
644 };
645 let r: RHardwareDevice = dev.clone().into();
646 let back: HardwareDevice = r.into();
647 assert_eq!(back, dev);
648 }
649
650 #[test]
651 fn hardware_device_round_trips_with_options() {
652 use linsight_core::{HardwareCategory, HardwareDevice, HardwareDeviceKey};
653 let dev = HardwareDevice {
654 key: HardwareDeviceKey::try_new("nvml:uuid:gpu-abc").unwrap(),
655 category: HardwareCategory::Gpu,
656 model: "NVIDIA RTX 5080 Mobile".into(),
657 vendor: Some("NVIDIA".into()),
658 location: Some("PCI 0000:01:00.0".into()),
659 plugin_id: String::new(),
660 plugin_device_id: "gpu0".into(),
661 sensor_ids: vec![],
662 };
663 let r: RHardwareDevice = dev.clone().into();
664 let back: HardwareDevice = r.into();
665 assert_eq!(back, dev);
666 }
667}