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}