Skip to main content

watch/
watch.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Development-only viewer: prints everything [`gpu_probe`] reports, with a
3//! used/free bar per GPU. Built for `watch`, so it prints once and exits.
4//!
5//! ```text
6//! cargo build --example watch
7//! watch -n1 target/debug/examples/watch
8//! ```
9//!
10//! All arithmetic here is integer-only — a dev tool has no business pulling in
11//! float formatting lints, and tenths of a GiB are plenty of precision.
12
13/// Width of the memory bar, in characters.
14const BAR_WIDTH: usize = 40;
15
16const GIB: u64 = 1024 * 1024 * 1024;
17
18/// Format a byte count as `14.5 GiB`, rounding down to tenths.
19fn gib(bytes: u64) -> String {
20    let tenths = bytes / (GIB / 10);
21    format!("{}.{} GiB", tenths / 10, tenths % 10)
22}
23
24/// Render `used` of `total` as `[████░░░░] 8.1%`, or an empty bar when the
25/// split is unknown (integrated GPUs that report no used/free).
26fn bar(used: Option<u64>, total: u64) -> String {
27    let Some(used) = used.filter(|_| total > 0) else {
28        return format!("[{}] usage unknown", "·".repeat(BAR_WIDTH));
29    };
30    let used = used.min(total);
31    // u128 so the scaling multiply can't overflow on absurd inputs.
32    let scaled = u128::from(used) * BAR_WIDTH as u128 / u128::from(total);
33    let filled = usize::try_from(scaled).unwrap_or(BAR_WIDTH).min(BAR_WIDTH);
34    let tenths = u128::from(used) * 1000 / u128::from(total);
35    format!(
36        "[{}{}] {}.{}%",
37        "█".repeat(filled),
38        "░".repeat(BAR_WIDTH - filled),
39        tenths / 10,
40        tenths % 10,
41    )
42}
43
44fn main() {
45    let gpus = gpu_probe::detect();
46
47    println!("gpu-probe · {} GPU(s)", gpus.len());
48    println!();
49
50    if gpus.is_empty() {
51        println!("  no GPUs detected");
52    }
53
54    for (index, gpu) in gpus.iter().enumerate() {
55        println!("[{index}] {} · {}", gpu.name, gpu.vendor);
56        // The artifact-selection target, whichever form this vendor reports:
57        // `gfx1013` for ROCm/HIP, `sm_89` for CUDA.
58        match gpu.arch_target {
59            Some(arch) => println!("     arch   {:>10}", arch.to_string()),
60            None => println!("     arch      unavailable"),
61        }
62        println!("     total  {:>10}", gib(gpu.total_bytes));
63        match gpu.used_bytes {
64            Some(used) => println!("     used   {:>10}", gib(used)),
65            None => println!("     used      unknown"),
66        }
67        match gpu.free_bytes {
68            Some(free) => println!("     free   {:>10}", gib(free)),
69            None => println!("     free      unknown"),
70        }
71        println!("     {}", bar(gpu.used_bytes, gpu.total_bytes));
72        println!();
73    }
74
75    // Host-wide toolchains, unlike the per-GPU fields above. Every row always
76    // prints, including when absent: "we looked and found nothing" is the point
77    // of a probe tool, and a lone row for one vendor reads like it describes
78    // the GPU above it rather than the host.
79    //
80    // They are not the same measurement. `cuda` is the driver version from
81    // NVML; `rocm` and `oneapi` are userspace installs, because neither AMD nor
82    // Intel exposes a driver version anywhere. The architecture each build
83    // targets is the per-GPU `arch` row, which needs none of them installed.
84    println!("host");
85    match gpu_probe::oneapi_host() {
86        Some(oneapi) => println!("     oneapi {:>10}", oneapi.version.to_string()),
87        None => println!("     oneapi    unavailable"),
88    }
89    match gpu_probe::rocm_host() {
90        Some(rocm) => println!("     rocm   {:>10}", rocm.version.to_string()),
91        None => println!("     rocm      unavailable"),
92    }
93    match gpu_probe::cuda_host() {
94        Some(cuda) => println!("     cuda   {:>10}", cuda.driver_version.to_string()),
95        None => println!("     cuda      unavailable"),
96    }
97    match gpu_probe::vulkan_host() {
98        Some(vulkan) => println!("     vulkan {:>10}", vulkan.api_version.to_string()),
99        None => println!("     vulkan    unavailable"),
100    }
101}