Skip to main content

sysinfo/common/
gpu.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use crate::{GpuInner, GpusInner};
4
5use std::cmp::Ordering;
6use std::fmt;
7
8/// Type containing GPU information.
9///
10/// It is returned by [`Gpus`][crate::Gpus].
11///
12/// It is currently supported on Linux, Windows and macOS.
13///
14/// ```no_run
15/// use sysinfo::Gpus;
16///
17/// if let Ok(gpus) = Gpus::new_with_refreshed_list() {
18///     for gpu in &gpus {
19///         println!("{gpu:?}");
20///     }
21/// }
22/// ```
23pub struct Gpus {
24    pub(crate) inner: GpusInner,
25}
26
27impl Gpus {
28    /// Creates a new empty [`Gpus`][crate::Gpus] type.
29    ///
30    /// If you want it to be filled directly, take a look at [`Gpus::new_with_refreshed_list`].
31    ///
32    /// ```no_run
33    /// use sysinfo::Gpus;
34    ///
35    /// if let Ok(mut gpus) = Gpus::new() {
36    ///     gpus.refresh(true);
37    ///     for gpu in &gpus {
38    ///         println!("{gpu:?}");
39    ///     }
40    /// }
41    /// ```
42    pub fn new() -> Result<Self, crate::Error> {
43        Ok(Self {
44            inner: GpusInner::new()?,
45        })
46    }
47
48    /// Creates a new [`Gpus`][crate::Gpus] type with the GPU list loaded.
49    ///
50    /// ```no_run
51    /// use sysinfo::Gpus;
52    ///
53    /// if let Ok(gpus) = Gpus::new_with_refreshed_list() {
54    ///     for gpu in &gpus {
55    ///         println!("{gpu:?}");
56    ///     }
57    /// }
58    /// ```
59    pub fn new_with_refreshed_list() -> Result<Self, crate::Error> {
60        let mut gpus = Self::new()?;
61        gpus.refresh(false);
62        Ok(gpus)
63    }
64
65    /// Refreshes the list of GPUs.
66    ///
67    /// ```no_run
68    /// use sysinfo::Gpus;
69    ///
70    /// if let Ok(mut gpus) = Gpus::new_with_refreshed_list() {
71    ///     // Wait some time...? Then refresh the data of each GPU.
72    ///     gpus.refresh(true);
73    /// }
74    /// ```
75    pub fn refresh(&mut self, remove_not_listed_gpus: bool) {
76        self.inner.refresh();
77        if remove_not_listed_gpus {
78            self.inner.gpus.retain_mut(|c| {
79                if !c.inner.updated {
80                    return false;
81                }
82                c.inner.updated = false;
83                true
84            });
85        } else {
86            for gpu in &mut self.inner.gpus {
87                gpu.inner.updated = false;
88            }
89        }
90    }
91}
92
93impl std::ops::Deref for Gpus {
94    type Target = [Gpu];
95
96    fn deref(&self) -> &Self::Target {
97        &self.inner.gpus
98    }
99}
100
101impl<'a> IntoIterator for &'a Gpus {
102    type Item = &'a Gpu;
103    type IntoIter = std::slice::Iter<'a, Gpu>;
104
105    fn into_iter(self) -> Self::IntoIter {
106        self.inner.gpus.iter()
107    }
108}
109
110impl From<Gpus> for Vec<Gpu> {
111    fn from(mut gpus: Gpus) -> Self {
112        // Needed because on Windows, `Gpus` implements the `Drop` trait.
113        std::mem::take(&mut gpus.inner.gpus)
114    }
115}
116
117/// A PCI (Peripheral Component Interconnect) is an architecture used to identify and manage
118/// hardware devices.
119///
120/// It is returned by [`Gpu::pci`][crate::Gpu::pci].
121///
122/// If you want to understand in details what a PCI is, I recommend:
123/// <https://en.wikipedia.org/wiki/Peripheral_Component_Interconnect>.
124#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
125#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
126pub struct PCI {
127    /// A PCI domain, also called "segment".
128    pub domain: u32,
129    /// A PCI bus.
130    pub bus: u32,
131    /// A PCI device.
132    pub device: u32,
133    /// A PCI function.
134    pub function: u32,
135}
136
137impl fmt::Display for PCI {
138    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139        let Self {
140            domain,
141            bus,
142            device,
143            function,
144        } = self;
145        write!(f, "{domain:04x}:{bus:02x}:{device:02x}.{function}")
146    }
147}
148
149impl core::str::FromStr for PCI {
150    type Err = &'static str;
151
152    fn from_str(s: &str) -> Result<Self, Self::Err> {
153        fn get_next_u32<'a>(
154            iter: &mut impl Iterator<Item = &'a str>,
155            missing_msg: &'static str,
156            invalid_msg: &'static str,
157        ) -> Result<u32, &'static str> {
158            let Some(value) = iter.next() else {
159                return Err(missing_msg);
160            };
161            u32::from_str_radix(value, 16).map_err(|_| invalid_msg)
162        }
163
164        let mut iter = s.split(':');
165        let domain = get_next_u32(&mut iter, "missing domain", "invalid domain")?;
166        let bus = get_next_u32(&mut iter, "missing bus", "invalid bus")?;
167        let Some(last) = iter.next() else {
168            return Err("missing device");
169        };
170        if iter.next().is_some() {
171            return Err("unexpected `:` after bus");
172        };
173        let mut iter = last.split('.');
174        let device = get_next_u32(&mut iter, "missing device", "invalid device")?;
175        let function = get_next_u32(&mut iter, "missing function", "invalid function")?;
176        if iter.next().is_some() {
177            return Err("unexpected `:` after function");
178        };
179
180        //  if not the fake address used for M series GPUs
181        if s != "ffffffff:ff:ff.255" {
182            if bus > 0xff {
183                return Err("invalid bus");
184            }
185            if device > 0x1f {
186                return Err("invalid device");
187            }
188            if function > 0x07 {
189                return Err("invalid function");
190            }
191        }
192
193        Ok(Self {
194            domain,
195            bus,
196            device,
197            function,
198        })
199    }
200}
201
202/// Type containing GPU information.
203///
204/// It is returned by [`Gpus`][crate::Gpus].
205///
206/// ```no_run
207/// use sysinfo::Gpus;
208///
209/// if let Ok(gpus) = Gpus::new_with_refreshed_list() {
210///     for gpu in &gpus {
211///         println!("{gpu:?}");
212///     }
213/// }
214/// ```
215pub struct Gpu {
216    pub(crate) inner: GpuInner,
217}
218
219impl PartialEq for Gpu {
220    fn eq(&self, other: &Self) -> bool {
221        self.pci() == other.pci()
222    }
223}
224
225impl Eq for Gpu {}
226
227impl PartialOrd for Gpu {
228    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
229        Some(self.cmp(other))
230    }
231}
232
233impl Ord for Gpu {
234    fn cmp(&self, other: &Self) -> Ordering {
235        self.pci().cmp(other.pci())
236    }
237}
238
239impl Gpu {
240    /// Returns the PCI of this GPU. Can be used as ID as it's unique to this GPU.
241    ///
242    /// Returns an invalid PCI Address, `ffffffff:ff:ff.255`, for Apple M Series GPUs.
243    pub fn pci(&self) -> &PCI {
244        self.inner.pci()
245    }
246
247    /// Returns the name of the vendor of this GPU. Returns `None` if the information cannot be
248    /// retrieved.
249    pub fn vendor(&self) -> Option<&str> {
250        self.inner.vendor()
251    }
252
253    /// Returns the model of this GPU. Returns `None` if the information cannot be retrieved.
254    pub fn model(&self) -> Option<&str> {
255        self.inner.model()
256    }
257
258    /// Returns the % usage of this GPU.
259    ///
260    /// On Linux, it's generally very tricky to get this information, as such, only some GPUs
261    /// like NVIDIA's or AMD's provide this information.
262    ///
263    /// On macOS, the information should always be available.
264    ///
265    /// On Windows, the first time you query this information, it'll return `None` as it is
266    /// time-diff based.
267    pub fn usage(&self) -> Option<f32> {
268        self.inner.usage()
269    }
270
271    /// Returns the total VRAM of this GPU in bytes.
272    ///
273    /// ⚠️ It does not take into account the GPU memory that is shared with the system's RAM.
274    ///
275    /// On macOS, GPUs share memory with the system, it always returns `None`.
276    ///
277    /// Returns `None` if the information cannot be retrieved.
278    pub fn total_memory(&self) -> Option<u64> {
279        self.inner.total_memory()
280    }
281
282    /// Returns the used VRAM of this GPU in bytes.
283    ///
284    /// ⚠️ It does not take into account the GPU memory that is shared with the system's RAM.
285    ///
286    /// On macOS, GPUs share memory with the system, it always returns `None`.
287    ///
288    /// Returns `None` if the information cannot be retrieved.
289    pub fn used_memory(&self) -> Option<u64> {
290        self.inner.used_memory()
291    }
292}