Skip to content

Commit

Permalink
Merge pull request #427 from nokyan/rpi-gpu-support
Browse files Browse the repository at this point in the history
Raspberry Pi GPU support
  • Loading branch information
nokyan authored Dec 28, 2024
2 parents ea3501c + 5cfc73d commit 47926c3
Show file tree
Hide file tree
Showing 12 changed files with 325 additions and 131 deletions.
104 changes: 68 additions & 36 deletions lib/process_data/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use nvml_wrapper::{Device, Nvml};
use pci_slot::PciSlot;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt::Display;
use std::fs::File;
use std::io::{Read, Write};
use std::os::linux::fs::MetadataExt;
Expand Down Expand Up @@ -46,11 +47,11 @@ static RE_IO_READ: Lazy<Regex> = lazy_regex!(r"read_bytes:\s*(\d+)");

static RE_IO_WRITE: Lazy<Regex> = lazy_regex!(r"write_bytes:\s*(\d+)");

static RE_DRM_DRIVER: Lazy<Regex> = lazy_regex!(r"drm-driver:\s*(.+)");

static RE_DRM_PDEV: Lazy<Regex> =
lazy_regex!(r"drm-pdev:\s*([0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}\.[0-9A-Fa-f])");

static RE_DRM_CLIENT_ID: Lazy<Regex> = lazy_regex!(r"drm-client-id:\s*(\d+)");

// AMD only
static RE_DRM_ENGINE_GFX: Lazy<Regex> = lazy_regex!(r"drm-engine-gfx:\s*(\d+)\s*ns");

Expand All @@ -69,12 +70,15 @@ static RE_DRM_MEMORY_VRAM: Lazy<Regex> = lazy_regex!(r"drm-memory-vram:\s*(\d+)\
// AMD only
static RE_DRM_MEMORY_GTT: Lazy<Regex> = lazy_regex!(r"drm-memory-gtt:\s*(\d+)\s*KiB");

// Intel only
// Intel and v3d only
static RE_DRM_ENGINE_RENDER: Lazy<Regex> = lazy_regex!(r"drm-engine-render:\s*(\d+)\s*ns");

// Intel only
static RE_DRM_ENGINE_VIDEO: Lazy<Regex> = lazy_regex!(r"drm-engine-video:\s*(\d+)\s*ns");

// v3d only
static RE_DRM_TOTAL_MEMORY: Lazy<Regex> = lazy_regex!(r"drm-total-memory:\s*(\d+)\s*KiB");

static NVML: Lazy<Result<Nvml, NvmlError>> = Lazy::new(Nvml::init);

static NVML_DEVICES: Lazy<Vec<(PciSlot, Device)>> = Lazy::new(|| {
Expand Down Expand Up @@ -117,6 +121,27 @@ pub enum Containerization {
Snap,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord)]
pub enum GpuIdentifier {
PciSlot(PciSlot),
Enumerator(usize),
}

impl Default for GpuIdentifier {
fn default() -> Self {
GpuIdentifier::Enumerator(0)
}
}

impl Display for GpuIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GpuIdentifier::PciSlot(pci_slot) => write!(f, "{}", pci_slot),
GpuIdentifier::Enumerator(e) => write!(f, "{}", e),
}
}
}

/// Represents GPU usage statistics per-process. Depending on the GPU manufacturer (which should be determined in
/// Resources itself), these numbers need to interpreted differently
///
Expand Down Expand Up @@ -158,7 +183,7 @@ pub struct ProcessData {
pub write_bytes: Option<u64>,
pub timestamp: u64,
/// Key: PCI Slot ID of the GPU
pub gpu_usage_stats: BTreeMap<PciSlot, GpuUsageStats>,
pub gpu_usage_stats: BTreeMap<GpuIdentifier, GpuUsageStats>,
}

impl ProcessData {
Expand Down Expand Up @@ -389,7 +414,7 @@ impl ProcessData {
})
}

fn gpu_usage_stats(proc_path: &Path, pid: i32) -> BTreeMap<PciSlot, GpuUsageStats> {
fn gpu_usage_stats(proc_path: &Path, pid: i32) -> BTreeMap<GpuIdentifier, GpuUsageStats> {
let nvidia_stats = Self::nvidia_gpu_stats_all(pid);
let mut other_stats = Self::other_gpu_usage_stats(proc_path, pid).unwrap_or_default();
other_stats.extend(nvidia_stats);
Expand All @@ -399,7 +424,7 @@ impl ProcessData {
fn other_gpu_usage_stats(
proc_path: &Path,
pid: i32,
) -> Result<BTreeMap<PciSlot, GpuUsageStats>> {
) -> Result<BTreeMap<GpuIdentifier, GpuUsageStats>> {
let fdinfo_dir = proc_path.join("fdinfo");

let mut seen_fds = HashSet::new();
Expand Down Expand Up @@ -489,33 +514,34 @@ impl ProcessData {
fn read_fdinfo(
fdinfo_file: &mut File,
file_size: usize,
) -> Result<(PciSlot, GpuUsageStats, i64)> {
) -> Result<(GpuIdentifier, GpuUsageStats)> {
let mut content = String::with_capacity(file_size);
fdinfo_file.read_to_string(&mut content)?;
fdinfo_file.flush()?;

let pci_slot = RE_DRM_PDEV
let driver = RE_DRM_DRIVER
.captures(&content)
.and_then(|captures| captures.get(1))
.and_then(|capture| PciSlot::from_str(capture.as_str()).ok());
.map(|capture| capture.as_str());

let client_id = RE_DRM_CLIENT_ID
.captures(&content)
.and_then(|captures| captures.get(1))
.and_then(|capture| capture.as_str().parse::<i64>().ok());
if driver.is_some() {
let gpu_identifier = RE_DRM_PDEV
.captures(&content)
.and_then(|captures| captures.get(1))
.and_then(|capture| PciSlot::from_str(capture.as_str()).ok())
.map(|pci_slot| GpuIdentifier::PciSlot(pci_slot))
.unwrap_or_default();

if let (Some(pci_slot), Some(client_id)) = (pci_slot, client_id) {
let gfx = RE_DRM_ENGINE_GFX // amd
let gfx = RE_DRM_ENGINE_GFX
.captures(&content)
.and_then(|captures| captures.get(1))
.and_then(|capture| capture.as_str().parse::<u64>().ok())
.unwrap_or_default();

let render = RE_DRM_ENGINE_RENDER
.captures(&content)
.and_then(|captures| captures.get(1))
.and_then(|capture| capture.as_str().parse::<u64>().ok())
.or_else(|| {
// intel
RE_DRM_ENGINE_RENDER
.captures(&content)
.and_then(|captures| captures.get(1))
.and_then(|capture| capture.as_str().parse::<u64>().ok())
})
.unwrap_or_default();

let compute = RE_DRM_ENGINE_COMPUTE
Expand All @@ -524,17 +550,16 @@ impl ProcessData {
.and_then(|capture| capture.as_str().parse::<u64>().ok())
.unwrap_or_default();

let enc = RE_DRM_ENGINE_ENC // amd
let enc = RE_DRM_ENGINE_ENC
.captures(&content)
.and_then(|captures| captures.get(1))
.and_then(|capture| capture.as_str().parse::<u64>().ok())
.unwrap_or_default();

let video = RE_DRM_ENGINE_VIDEO
.captures(&content)
.and_then(|captures| captures.get(1))
.and_then(|capture| capture.as_str().parse::<u64>().ok())
.or_else(|| {
// intel
RE_DRM_ENGINE_VIDEO
.captures(&content)
.and_then(|captures| captures.get(1))
.and_then(|capture| capture.as_str().parse::<u64>().ok())
})
.unwrap_or_default();

let dec = RE_DRM_ENGINE_DEC
Expand All @@ -557,26 +582,33 @@ impl ProcessData {
.unwrap_or_default()
.saturating_mul(1024);

let total_memory = RE_DRM_TOTAL_MEMORY
.captures(&content)
.and_then(|captures| captures.get(1))
.and_then(|capture| capture.as_str().parse::<u64>().ok())
.unwrap_or_default()
.saturating_mul(1024);

let stats = GpuUsageStats {
gfx: gfx.saturating_add(compute),
mem: vram.saturating_add(gtt),
enc,
gfx: gfx.saturating_add(render).saturating_add(compute),
mem: vram.saturating_add(gtt).saturating_add(total_memory),
enc: enc.saturating_add(video),
dec,
nvidia: false,
};

return Ok((pci_slot, stats, client_id));
return Ok((gpu_identifier, stats));
}

bail!("unable to find gpu information in this fdinfo");
}

fn nvidia_gpu_stats_all(pid: i32) -> BTreeMap<PciSlot, GpuUsageStats> {
fn nvidia_gpu_stats_all(pid: i32) -> BTreeMap<GpuIdentifier, GpuUsageStats> {
let mut return_map = BTreeMap::new();

for (pci_slot, _) in NVML_DEVICES.iter() {
if let Ok(stats) = Self::nvidia_gpu_stats(pid, *pci_slot) {
return_map.insert(pci_slot.to_owned(), stats);
return_map.insert(GpuIdentifier::PciSlot(pci_slot.to_owned()), stats);
}
}

Expand Down
15 changes: 10 additions & 5 deletions src/ui/pages/gpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,11 @@ impl ResGPU {
}

pub fn setup_widgets(&self, gpu: &Gpu) {
trace!("Setting up ResGPU ({}) widgets…", gpu.pci_slot());
trace!("Setting up ResGPU ({}) widgets…", gpu.gpu_identifier());

let imp = self.imp();

let tab_id = format!("{}-{}", TAB_ID_PREFIX, &gpu.pci_slot().to_string());
let tab_id = format!("{}-{}", TAB_ID_PREFIX, &gpu.gpu_identifier());
imp.set_tab_id(&tab_id);

imp.gpu_usage.set_title_label(&i18n("Total Usage"));
Expand Down Expand Up @@ -236,7 +236,12 @@ impl ResGPU {
.map_or_else(|_| i18n("N/A"), |vendor| vendor.name().to_string()),
);

imp.pci_slot.set_subtitle(&gpu.pci_slot().to_string());
match gpu.gpu_identifier() {
process_data::GpuIdentifier::PciSlot(pci_slot) => {
imp.pci_slot.set_subtitle(&pci_slot.to_string())
}
process_data::GpuIdentifier::Enumerator(_) => imp.pci_slot.set_subtitle(&i18n("N/A")),
}

imp.driver_used.set_subtitle(&gpu.driver());

Expand All @@ -254,12 +259,12 @@ impl ResGPU {
}

pub fn refresh_page(&self, gpu_data: &GpuData) {
trace!("Refreshing ResGPU ({})…", gpu_data.pci_slot);
trace!("Refreshing ResGPU ({})…", gpu_data.gpu_identifier);

let imp = self.imp();

let GpuData {
pci_slot: _,
gpu_identifier: _,
usage_fraction,
encode_fraction,
decode_fraction,
Expand Down
18 changes: 10 additions & 8 deletions src/ui/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ mod imp {
use async_channel::{unbounded, Receiver, Sender};
use gtk::CompositeTemplate;
use log::debug;
use process_data::pci_slot::PciSlot;
use process_data::{pci_slot::PciSlot, GpuIdentifier};

#[derive(Debug, CompositeTemplate)]
#[template(resource = "/net/nokyan/Resources/ui/window.ui")]
Expand Down Expand Up @@ -96,7 +96,7 @@ mod imp {

pub battery_pages: RefCell<HashMap<PathBuf, adw::ToolbarView>>,

pub gpu_pages: RefCell<HashMap<PciSlot, (Gpu, adw::ToolbarView)>>,
pub gpu_pages: RefCell<HashMap<GpuIdentifier, (Gpu, adw::ToolbarView)>>,

pub npu_pages: RefCell<HashMap<PciSlot, (Npu, adw::ToolbarView)>>,

Expand Down Expand Up @@ -330,7 +330,7 @@ impl MainWindow {

imp.gpu_pages
.borrow_mut()
.insert(gpu.pci_slot(), (gpu.clone(), added_page));
.insert(gpu.gpu_identifier(), (gpu.clone(), added_page));
}
}

Expand Down Expand Up @@ -397,7 +397,7 @@ impl MainWindow {
*imp.apps_context.borrow_mut() = AppsContext::new(
gpus.iter()
.filter(|gpu| gpu.combined_media_engine().unwrap_or_default())
.map(Gpu::pci_slot)
.map(Gpu::gpu_identifier)
.collect(),
);
imp.applications.init(imp.sender.clone());
Expand Down Expand Up @@ -505,7 +505,7 @@ impl MainWindow {
Process::all_data()
.inspect_err(|e| {
warn!(
"Unable to update process and app data!\n{e}\n{}",
"Unable to update process and app data! Is resources-processes running?\n{e}\n{}",
e.backtrace()
);
})
Expand Down Expand Up @@ -574,19 +574,21 @@ impl MainWindow {
// average usage during now and the last refresh, while gpu_busy_percent is a snapshot of the current
// usage, which might not be what we want

let processes_gpu_fraction = apps_context.gpu_fraction(gpu_data.pci_slot);
let processes_gpu_fraction = apps_context.gpu_fraction(gpu_data.gpu_identifier);
gpu_data.usage_fraction = Some(f64::max(
gpu_data.usage_fraction.unwrap_or(0.0),
processes_gpu_fraction.into(),
));

let processes_encode_fraction = apps_context.encoder_fraction(gpu_data.pci_slot);
let processes_encode_fraction =
apps_context.encoder_fraction(gpu_data.gpu_identifier);
gpu_data.encode_fraction = Some(f64::max(
gpu_data.encode_fraction.unwrap_or(0.0),
processes_encode_fraction.into(),
));

let processes_decode_fraction = apps_context.decoder_fraction(gpu_data.pci_slot);
let processes_decode_fraction =
apps_context.decoder_fraction(gpu_data.gpu_identifier);
gpu_data.decode_fraction = Some(f64::max(
gpu_data.decode_fraction.unwrap_or(0.0),
processes_decode_fraction.into(),
Expand Down
Loading

0 comments on commit 47926c3

Please sign in to comment.