Skip to main content

cubecl_monitoring/
utilization.rs

1use alloc::string::String;
2use core::fmt;
3
4use cubecl_ir::PciVendor;
5
6/// How busy a device is, as its counter read it.
7#[derive(Clone, Copy, Debug, PartialEq)]
8#[non_exhaustive]
9pub struct DeviceUtilization {
10    /// The percent of the counter's last period the device spent running work, from 0 to 100.
11    pub busy_percent: f32,
12}
13
14impl DeviceUtilization {
15    /// Holds a reading outside 0 to 100 to the nearer bound.
16    pub fn new(busy_percent: f32) -> Self {
17        Self {
18            busy_percent: busy_percent.clamp(0.0, 100.0),
19        }
20    }
21}
22
23/// Why a device's utilization could not be read.
24#[derive(Clone, Debug, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum UtilizationUnavailable {
27    /// This build does not link the device's runtime, which is what says which card the device
28    /// is.
29    RuntimeNotLinked,
30    /// The device's runtime reads no counter for its devices.
31    RuntimeReadsNoCounter,
32    /// The runtime reports no card behind the device: a software adapter.
33    NoCard,
34    /// This platform keeps no counter read here for a card of this vendor, or of no vendor the
35    /// runtime named.
36    NoCounterForCard(Option<PciVendor>),
37    /// The card's counter is found by an address the runtime did not report: the PCI address on
38    /// Linux, the LUID on Windows, and on macOS the registry entry id, where the machine has more
39    /// than one GPU.
40    CardAddressNotReported,
41    /// The driver's library would not load: no driver installed, or none on the loader's path.
42    /// Holds the loader's message.
43    DriverLibraryNotFound(String),
44    /// The counter measures the time between two readings, and this was the device's first, or
45    /// the first since the counter restarted.
46    NoPreviousReading,
47    /// The counter was reached and gave no reading. Holds the driver's, the platform's or the
48    /// filesystem's message.
49    QueryFailed(String),
50}
51
52impl fmt::Display for UtilizationUnavailable {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            Self::RuntimeNotLinked => f.write_str("this build does not link the device's runtime"),
56            Self::RuntimeReadsNoCounter => {
57                f.write_str("the device's runtime reads no utilization counter")
58            }
59            Self::NoCard => f.write_str("the device is no card: a software adapter"),
60            Self::NoCounterForCard(Some(vendor)) => {
61                write!(f, "no counter on this platform for a {vendor} card")
62            }
63            Self::NoCounterForCard(None) => {
64                f.write_str("no counter on this platform for a card of no known vendor")
65            }
66            Self::CardAddressNotReported => {
67                f.write_str("the runtime did not report the address the card's counter is found by")
68            }
69            Self::DriverLibraryNotFound(message) => {
70                write!(f, "the driver's library would not load: {message}")
71            }
72            Self::NoPreviousReading => {
73                f.write_str("the counter measures between two readings, and this was the first")
74            }
75            Self::QueryFailed(message) => write!(f, "the counter gave no reading: {message}"),
76        }
77    }
78}
79
80impl core::error::Error for UtilizationUnavailable {}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn a_reading_from_0_to_100_is_kept_as_it_is() {
88        for busy_percent in [0.0, 37.5, 100.0] {
89            assert_eq!(
90                DeviceUtilization::new(busy_percent).busy_percent,
91                busy_percent
92            );
93        }
94    }
95
96    #[test]
97    fn a_reading_above_100_is_held_to_100() {
98        for busy_percent in [100.5, f32::MAX] {
99            assert_eq!(DeviceUtilization::new(busy_percent).busy_percent, 100.0);
100        }
101    }
102}