feat: fmt gpu name in gpu-picker (#1157)

* feat: fmt gpu name in gpu-picker

* move implementation to daemon

* move to schema

* add Radeon/Geforce prefix back

* remove from info_elements
This commit is contained in:
Roman Makarov
2026-08-17 10:49:12 +03:00
committed by GitHub
parent 979f087096
commit bd687203e2
7 changed files with 113 additions and 22 deletions
+2 -2
View File
@@ -265,7 +265,7 @@ There is also a cli available.
Example output:
```
10DE:2704-1462:5110-0000:09:00.0 (AD103 [GeForce RTX 4080])
0: 10DE:2704-1462:5110-0000:09:00.0 (GeForce RTX 4080) [Dedicated]
```
- Getting GPU information:
@@ -277,7 +277,7 @@ There is also a cli available.
$ lact cli info
GPU 10DE:2704-1462:5110-0000:09:00.0:
=====================================
GPU Model: NVIDIA GeForce RTX 4080 (0x10DE:0x2704)
GPU Model: GeForce RTX 4080 (0x10DE:0x2704)
Card Manufacturer: Micro-Star International Co., Ltd. [MSI] (0x1462)
Card Model: Unknown (0x5110)
Driver Used: nvidia 570.124.04
+8 -2
View File
@@ -8,6 +8,7 @@ use anyhow::{Context, Result, bail};
use lact_client::DaemonClient;
use lact_schema::{
args::cli::{CliArgs, CliCommand, ProfileAutoSwitchCommand, ProfileCommand},
clean_gpu_name,
config::GpuConfig,
request::ConfirmCommand,
};
@@ -100,7 +101,11 @@ impl CliContext<'_> {
if entries.len() > 1 {
eprintln!(
"GPU id not specified, selecting {}",
first_entry.name.as_deref().unwrap_or("<Unknown>")
first_entry
.name
.as_deref()
.map(clean_gpu_name)
.unwrap_or("<Unknown>")
);
}
Ok(first_entry.id.clone())
@@ -141,6 +146,7 @@ impl CliContext<'_> {
.await?
.into_iter()
.find(|entry| entry.id == gpu_id)
.and_then(|entry| entry.name))
.and_then(|entry| entry.name)
.map(|name| clean_gpu_name(&name).to_owned()))
}
}
+3 -6
View File
@@ -11,13 +11,10 @@ const PROFILE_DEFAULT: &str = "Default";
pub async fn list_gpus(ctx: CliContext<'_>) -> Result<()> {
let entries = ctx.client.list_devices().await?;
for (i, entry) in entries.into_iter().enumerate() {
let id = entry.id;
let device_type = entry.device_type;
if let Some(name) = entry.name {
println!("{i}: {id} ({name}) [{device_type}]");
if entry.name.is_some() {
println!("{i}: {} ({entry}) [{}]", entry.id, entry.device_type);
} else {
println!("{i}: {id} [{device_type}]");
println!("{i}: {} [{}]", entry.id, entry.device_type);
}
}
Ok(())
+10 -2
View File
@@ -6,7 +6,7 @@ use crate::{
};
use indexmap::IndexMap;
use jiff::Zoned;
use lact_schema::DeviceStats;
use lact_schema::{DeviceStats, clean_gpu_name};
use schema::{
Attribute, Gauge, GaugeDataPoint, Metric, MetricsPayload, Resource, ResourceMetric, Scope,
ScopeMetric, Value,
@@ -335,7 +335,15 @@ async fn get_stats(handler: &Handler) -> anyhow::Result<IndexMap<String, (String
for device in device_list {
let stats = handler.get_gpu_stats(&device.id).await?;
devices.insert(device.id, (device.name.unwrap_or_default(), stats));
devices.insert(
device.id,
(
device
.name
.map_or_else(String::new, |name| clean_gpu_name(&name).to_owned()),
stats,
),
);
}
Ok(devices)
@@ -13,7 +13,7 @@ use crate::app::{
use gtk::pango::AttrList;
use gtk::prelude::{BoxExt, OrientableExt, PopoverExt as _, WidgetExt};
use i18n_embed_fl::fl;
use lact_schema::{DeviceInfo, DeviceStats, PowerStates, PowerStats};
use lact_schema::{DeviceInfo, DeviceStats, PowerStates, PowerStats, clean_gpu_name};
use relm4::{ComponentParts, ComponentSender, RelmWidgetExt as _};
use std::collections::HashSet;
use std::str::FromStr as _;
@@ -360,13 +360,14 @@ impl relm4::SimpleComponent for GpuStatsSection {
GpuStatsSectionMsg::Info(info) => {
self.vram_clock_ratio = info.vram_clock_ratio();
if let Some(pci_info) = &info.pci_info {
self.gpu_model = info
.drm_info
.as_ref()
.and_then(|drm| drm.device_name.as_deref())
.or(pci_info.device_pci_info.model.as_deref())
.unwrap_or("Unknown")
.to_owned();
self.gpu_model = clean_gpu_name(
info.drm_info
.as_ref()
.and_then(|drm| drm.device_name.as_deref())
.or(pci_info.device_pci_info.model.as_deref())
.unwrap_or("Unknown"),
)
.to_owned();
}
}
GpuStatsSectionMsg::Stats(stats) => {
+28 -1
View File
@@ -67,6 +67,33 @@ pub fn bytes_to_mib(bytes: u64) -> f64 {
bytes as f64 / 1024.0 / 1024.0
}
const GPU_VENDOR_PREFIXES: &[&str] = &["AMD ", "NVIDIA ", "Intel "];
pub fn clean_gpu_name(name: &str) -> &str {
let mut short = name.trim();
if let Some(marketing_name) = GPU_VENDOR_PREFIXES
.iter()
.find_map(|&prefix| short.strip_prefix(prefix))
{
short = marketing_name
.split_once('[')
.map_or(marketing_name, |(model, _)| model.trim_end());
} else if let Some((_, bracketed)) = short.split_once('[')
&& let Some((model, _)) = bracketed.split_once(']')
{
// PCI device names carry the marketing name in brackets, e.g. "DG2 [Arc A770]"
short = model.trim();
}
short = GPU_VENDOR_PREFIXES
.iter()
.find_map(|&prefix| short.strip_prefix(prefix))
.unwrap_or(short);
short
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Pong;
@@ -121,7 +148,7 @@ pub struct DeviceListEntry {
impl Display for DeviceListEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.name {
Some(name) => Display::fmt(name, f),
Some(name) => Display::fmt(clean_gpu_name(name), f),
None => Display::fmt(&self.id, f),
}
}
+53 -1
View File
@@ -1,4 +1,4 @@
use crate::{FanControlMode, FanOptions, PmfwOptions, Pong, Request, Response};
use crate::{FanControlMode, FanOptions, PmfwOptions, Pong, Request, Response, clean_gpu_name};
use anyhow::anyhow;
use serde_json::json;
use std::collections::BTreeMap;
@@ -86,3 +86,55 @@ fn set_fan_clocks() {
});
assert_eq!(expected_request, request);
}
#[test]
fn clean_gpu_name_removes_vendor_prefixes() {
assert_eq!(clean_gpu_name("AMD Radeon RX 9070 XT"), "Radeon RX 9070 XT");
assert_eq!(
clean_gpu_name("NVIDIA GeForce RTX 5090"),
"GeForce RTX 5090"
);
assert_eq!(clean_gpu_name("NVIDIA GeForce MX450"), "GeForce MX450");
assert_eq!(
clean_gpu_name("NVIDIA GeForce RTX 5090 [Founders Edition]"),
"GeForce RTX 5090"
);
assert_eq!(
clean_gpu_name("NVIDIA GeForce RTX 4070 Super"),
"GeForce RTX 4070 Super"
);
assert_eq!(clean_gpu_name("Intel Arc A380"), "Arc A380");
}
#[test]
fn clean_gpu_name_unwraps_pci_names() {
assert_eq!(
clean_gpu_name("Pitcairn XT [Radeon HD 7870 GHz Edition]"),
"Radeon HD 7870 GHz Edition"
);
assert_eq!(clean_gpu_name("DG2 [Arc A380]"), "Arc A380");
assert_eq!(clean_gpu_name("GK107M [GeForce 710A]"), "GeForce 710A");
assert_eq!(clean_gpu_name("GK107M [GeForce 820M]"), "GeForce 820M");
assert_eq!(clean_gpu_name("TU117M [GeForce MX450]"), "GeForce MX450");
assert_eq!(
clean_gpu_name("TigerLake-LP GT2 [Iris Xe Graphics]"),
"Iris Xe Graphics"
);
}
#[test]
fn clean_gpu_name_keeps_consumer_brands_and_unrecognized_names() {
assert_eq!(
clean_gpu_name("AMD Radeon 780M Graphics"),
"Radeon 780M Graphics"
);
assert_eq!(clean_gpu_name("AMD Radeon VII"), "Radeon VII");
assert_eq!(clean_gpu_name("Phoenix1"), "Phoenix1");
}
#[test]
fn clean_gpu_name_keeps_professional_product_brands() {
assert_eq!(clean_gpu_name("GK107GL [Quadro K600]"), "Quadro K600");
assert_eq!(clean_gpu_name("GK110GL [Tesla K20]"), "Tesla K20");
assert_eq!(clean_gpu_name("NVIDIA Quadro RTX 6000"), "Quadro RTX 6000");
}