memonitor/
lib.rs

1//! **Memonitor** is a lightweight library that allows querying information from various CPU and GPU devices.
2//! The main purpose is the ability to query memory related information, like how much local memory a device has and
3//! how much is currently available to be allocated.
4//!
5//! This is achieved by dynamically loading, if present, various device APIs found in the system, and querying them
6//! directly.
7//!
8//! # Example
9//!
10//! ```
11//! use memonitor::{list_all_devices, list_backends};
12//!
13//! // Print every backend that has been found
14//! for backend in list_backends().iter() {
15//!     println!("Backend found: {}", backend.name());
16//! }
17//!
18//! // Print every device found from every backend, as well as current memory statistics
19//! for device in list_all_devices().iter() {
20//!     let stats = device.current_memory_stats();
21//!     println!(
22//!         "Device found: {} ({}) - Memory stats: {} bytes used out of {}, {} are free",
23//!         device.name(),
24//!         device.kind(),
25//!         stats.used,
26//!         stats.total,
27//!         stats.available
28//!     );
29//! }
30//! ```
31//!
32//! # Features
33//!
34//! * `vulkan` - enables the Vulkan backend, enabled by default.
35
36#![warn(missing_docs)]
37
38use std::fmt::{Display, Formatter};
39use std::ops::{Deref, RangeBounds, RangeFull};
40use std::sync::{RwLock, RwLockReadGuard};
41
42use once_cell::sync::Lazy;
43
44mod cpu;
45
46#[cfg(feature = "vulkan")]
47mod vulkan;
48
49#[cfg(all(feature = "cuda", not(apple)))]
50mod cuda;
51mod log;
52
53/// A backend identifier.
54#[non_exhaustive]
55#[derive(Debug, Eq, PartialEq, Copy, Clone)]
56pub enum BackendId {
57    /// The CPU or host backend identifier.
58    CPU,
59
60    /// The Vulkan backend identifier.
61    #[cfg(feature = "vulkan")]
62    Vulkan,
63
64    /// The CUDA backend identifier.
65    #[cfg(all(feature = "cuda", not(apple)))]
66    CUDA,
67}
68
69/// The name of the always present CPU backend.
70pub const CPU_NAME: &str = "Host";
71
72/// The name of the Vulkan backend.
73#[cfg(feature = "vulkan")]
74pub const VULKAN_NAME: &str = "Vulkan";
75
76/// The name of the Cuda backend.
77#[cfg(all(feature = "cuda", not(apple)))]
78pub const CUDA_NAME: &str = "CUDA";
79
80static CONTEXT: Lazy<RwLock<Context>> = Lazy::new(|| {
81    let ctx = RwLock::new(Context::default());
82    ctx.write().unwrap().init();
83    ctx
84});
85
86struct Context {
87    backends: Vec<Backend>,
88    devices: Vec<Device>,
89}
90
91impl Context {
92    const fn default() -> Self {
93        Context {
94            backends: vec![],
95            devices: vec![],
96        }
97    }
98
99    fn init(&mut self) {
100        if !self.backends.is_empty() {
101            return;
102        }
103
104        unsafe {
105            memonitor_sys::log::set(Some(log::log));
106        }
107
108        let (system, cpus) = cpu::Host::init();
109        self.register_backend(system, cpus);
110
111        #[cfg(feature = "vulkan")]
112        if let Some((backend, devices)) = vulkan::Vulkan::init() {
113            self.register_backend(backend, devices)
114        }
115
116        #[cfg(all(feature = "cuda", not(apple)))]
117        if let Some((backend, devices)) = cuda::Cuda::init() {
118            self.register_backend(backend, devices)
119        }
120    }
121
122    fn register_backend<B, D>(&mut self, backend: B, mut devices: Vec<D>)
123    where
124        B: BackendHandle + 'static,
125        D: DeviceHandle + 'static,
126    {
127        let old_device_count = self.devices.len();
128        let backend_id = self.backends.len();
129        let mut new_device_ids = Vec::with_capacity(devices.len());
130
131        for (idx, device) in devices.drain(..).enumerate() {
132            let global_id = idx + old_device_count;
133            let device = Device {
134                inner: Box::new(device),
135                global_id,
136                local_id: idx,
137                backend_id,
138            };
139            self.devices.push(device);
140            new_device_ids.push(global_id);
141        }
142
143        let backend = Backend {
144            inner: Box::new(backend),
145            id: backend_id,
146            device_ids: new_device_ids,
147        };
148        self.backends.push(backend);
149    }
150}
151
152/// Returns a slice containing every [`Backend`] found.
153///
154/// The contents should always be identical.
155///
156/// If any backend is not listed, it is either because its respective feature was not enabled, or
157/// because its respective library/framework was not found in the system.
158pub fn list_backends() -> SliceGuard<'static, Backend, RangeFull> {
159    let guard = CONTEXT.read().unwrap();
160    SliceGuard {
161        guard,
162        range: ..,
163        _phantom: Default::default(),
164    }
165}
166
167/// Returns a slice containing *every* [`Device`] found.
168/// Depending on the [`Backend`]s present, there may be several representations for the same hardware device.
169///
170/// The contents should always be identical after [`init`] has been called.
171pub fn list_all_devices() -> SliceGuard<'static, Device, RangeFull> {
172    let guard = CONTEXT.read().unwrap();
173    SliceGuard {
174        guard,
175        range: ..,
176        _phantom: Default::default(),
177    }
178}
179
180/// A type emulating a slice that holds a [`RwLockReadGuard`] of the inner context.
181pub struct SliceGuard<'s, T, R>
182where
183    R: RangeBounds<usize>,
184{
185    guard: RwLockReadGuard<'s, Context>,
186    range: R,
187    _phantom: std::marker::PhantomData<T>,
188}
189
190impl<'s, R> Deref for SliceGuard<'s, Backend, R>
191where
192    R: RangeBounds<usize>,
193{
194    type Target = [Backend];
195
196    fn deref(&self) -> &Self::Target {
197        &self.guard.backends[(
198            self.range.start_bound().cloned(),
199            self.range.end_bound().cloned(),
200        )]
201    }
202}
203
204impl<'s, R> Deref for SliceGuard<'s, Device, R>
205where
206    R: RangeBounds<usize>,
207{
208    type Target = [Device];
209
210    fn deref(&self) -> &Self::Target {
211        &self.guard.devices[(
212            self.range.start_bound().cloned(),
213            self.range.end_bound().cloned(),
214        )]
215    }
216}
217
218/// Trait for internal backend handles.
219pub trait BackendHandle: Send + Sync {
220    /// Returns the name of this backend.
221    fn name(&self) -> &str;
222
223    /// Returns the identifier of this backend.
224    fn id(&self) -> BackendId;
225}
226
227/// High-level abstraction over a backend.
228pub struct Backend {
229    inner: Box<dyn BackendHandle>,
230    id: usize,
231    device_ids: Vec<usize>,
232}
233
234impl PartialEq for Backend {
235    fn eq(&self, other: &Self) -> bool {
236        self.id == other.id
237    }
238}
239
240impl Eq for Backend {}
241
242impl Backend {
243    /// Return the index of this backend within the context.
244    ///
245    /// Used for indexing into slices returned by [`list_backends`].
246    pub fn index(&self) -> usize {
247        self.id
248    }
249
250    /// Return the indexes of [Device]s owned by this backend.
251    ///
252    /// Used for indexing into slices returned by [`list_all_devices`].
253    pub fn device_indexes(&self) -> &[usize] {
254        &self.device_ids
255    }
256}
257
258impl Deref for Backend {
259    type Target = Box<dyn BackendHandle>;
260
261    fn deref(&self) -> &Self::Target {
262        &self.inner
263    }
264}
265
266/// The hardware type of a [`Device`].
267#[derive(Copy, Clone)]
268pub enum DeviceKind {
269    /// A Graphics Processing Unit a.k.a. a Graphics Card.
270    GPU(GPUKind),
271    /// A Central Processing Unit.
272    CPU,
273    /// Some other, unknown type.
274    Other,
275}
276
277impl Display for DeviceKind {
278    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
279        match self {
280            DeviceKind::GPU(GPUKind::Integrated) => write!(f, "Integrated Graphics Card"),
281            DeviceKind::GPU(GPUKind::Discrete) => write!(f, "Discrete Graphics Card"),
282            DeviceKind::GPU(GPUKind::Virtual) => write!(f, "Virtual Graphics Card"),
283            DeviceKind::CPU => write!(f, "CPU"),
284            DeviceKind::Other => write!(f, "Other"),
285        }
286    }
287}
288
289/// The type of a Graphics Card.
290#[derive(Copy, Clone)]
291pub enum GPUKind {
292    /// A Graphics Card physically integrated into the CPU (probably sharing the same memory).
293    Integrated,
294    /// A discrete Graphics Card, probably connected through PCIE.
295    Discrete,
296    /// A virtual Graphics Card.
297    Virtual,
298}
299
300/// Memory information of a device at some point in time.
301pub struct MemoryStats {
302    /// The total local memory, in bytes. This value should always be the same for the same [`Device`].
303    pub total: usize,
304    /// The current amount of local memory that is available for new allocations, in bytes.
305    pub available: usize,
306    /// The current amount of local memory in use by the system, in bytes.
307    pub used: usize,
308}
309
310/// Trait for internal device handles.
311pub trait DeviceHandle: Send + Sync {
312    /// Return the name of this device.
313    fn name(&self) -> &str;
314
315    /// Return the hardware type of this device.
316    fn kind(&self) -> DeviceKind;
317
318    /// Return the identifier of the [`Backend`] that owns this device handle.
319    fn backend(&self) -> BackendId;
320
321    /// Return the memory statistics of this device at this point in time.
322    fn current_memory_stats(&self) -> MemoryStats;
323}
324
325/// High-level abstraction over a hardware device.
326pub struct Device {
327    inner: Box<dyn DeviceHandle>,
328    global_id: usize,
329    local_id: usize,
330    backend_id: usize,
331}
332
333impl Device {
334    /// Return the index of this device within the global context.
335    ///
336    /// Used for indexing into slices returned by [`list_all_devices`].
337    pub fn global_index(&self) -> usize {
338        self.global_id
339    }
340
341    /// Return the *id*/index of this device within its owning [`Backend`].
342    ///
343    /// This could be used, for example: to select this device in a CUDA context.
344    pub fn local_id(&self) -> usize {
345        self.local_id
346    }
347
348    /// Return the index of this device's owning [`Backend`].
349    ///
350    /// Used for indexing into slices returned by [`list_backends`].
351    pub fn backend_index(&self) -> usize {
352        self.backend_id
353    }
354}
355
356impl Deref for Device {
357    type Target = Box<dyn DeviceHandle>;
358
359    fn deref(&self) -> &Self::Target {
360        &self.inner
361    }
362}