Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raspberry Pi GPU support #427

Merged
merged 6 commits into from
Dec 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading