#[cfg(feature = "args")] pub mod args; pub mod config; pub mod i18n; mod profiles; pub mod request; mod response; #[cfg(test)] mod tests; use i18n_embed_fl::fl; pub use request::Request; pub use response::Response; use amdgpu_sysfs::{ gpu_handle::{ PerformanceLevel, PowerLevelId, fan_control::FanInfo, overdrive::{ClocksTable as _, ClocksTableGen as AmdClocksTableGen}, }, hw_mon::Temperature, }; use indexmap::{IndexMap, IndexSet}; use nvml_wrapper::enums::device::PowerMizerMode; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use std::{ collections::{BTreeMap, HashMap, HashSet}, fmt::{self, Debug, Display, Write}, str::FromStr, sync::Arc, }; use crate::{config::ProfileHooks, i18n::LANGUAGE_LOADER}; pub const GIT_COMMIT: &str = env!("VERGEN_GIT_SHA"); #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "snake_case")] pub enum FanControlMode { Static, #[default] Curve, } impl FromStr for FanControlMode { type Err = String; fn from_str(s: &str) -> Result { match s { "curve" => Ok(Self::Curve), "static" => Ok(Self::Static), _ => Err("unknown fan control mode".to_string()), } } } pub type FanCurveMap = BTreeMap; pub fn default_fan_curve() -> FanCurveMap { [(40, 0.3), (50, 0.35), (60, 0.5), (70, 0.75), (80, 1.0)].into() } pub fn bytes_to_mib(bytes: u64) -> f64 { bytes as f64 / 1024.0 / 1024.0 } #[derive(Serialize, Deserialize, Debug)] pub struct Pong; #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct SystemInfo { pub version: String, pub commit: Option, pub profile: String, pub distro: Option, pub kernel_version: String, pub amdgpu_overdrive_enabled: Option, pub amdgpu_params_configurator: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct DeviceListEntry { pub id: String, pub name: Option, #[serde(default)] pub device_type: DeviceType, } impl Display for DeviceListEntry { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.name { Some(name) => Display::fmt(name, f), None => Display::fmt(&self.id, f), } } } #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq)] pub enum DeviceType { #[default] Dedicated, Integrated, } impl Display for DeviceType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { DeviceType::Dedicated => "Dedicated", DeviceType::Integrated => "Integrated", }; Display::fmt(s, f) } } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GpuPciInfo { pub device_pci_info: PciInfo, pub subsystem_pci_info: PciInfo, } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum DeviceFlag { ConfigurableFanControl, DumpableVBios, HasPmfw, AutoFanThreshold, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct DeviceInfo { pub pci_info: Option, #[serde(flatten)] pub api_info: DeviceApiInfo, pub driver: String, pub vbios_version: Option, pub link_info: LinkInfo, pub drm_info: Option, #[serde(default)] pub flags: Vec, } impl DeviceInfo { pub fn vram_clock_ratio(&self) -> f64 { self.drm_info .as_ref() .map(|info| info.vram_clock_ratio) .unwrap_or(1.0) } pub fn info_elements(&self, stats: Option<&DeviceStats>) -> Vec<(String, Option)> { let pci_info = self.pci_info.as_ref(); let mut gpu_model = self .drm_info .as_ref() .and_then(|drm| drm.device_name.as_deref()) .or_else(|| pci_info.and_then(|pci_info| pci_info.device_pci_info.model.as_deref())) .unwrap_or("Unknown") .to_owned(); let mut card_manufacturer = pci_info .and_then(|info| info.subsystem_pci_info.vendor.as_deref()) .unwrap_or("Unknown") .to_owned(); let mut card_model = pci_info .and_then(|info| info.subsystem_pci_info.model.as_deref()) .unwrap_or("Unknown") .to_owned(); if let Some(pci_info) = &self.pci_info { match self.drm_info { Some(DrmInfo { pci_revision_id: Some(pci_rev), .. }) => { let _ = write!( gpu_model, " (0x{}:0x{}:0x{pci_rev:X})", pci_info.device_pci_info.vendor_id, pci_info.device_pci_info.model_id, ); } _ => { let _ = write!( gpu_model, " (0x{}:0x{})", pci_info.device_pci_info.vendor_id, pci_info.device_pci_info.model_id ); } } let _ = write!( card_manufacturer, " (0x{})", pci_info.subsystem_pci_info.vendor_id ); let _ = write!(card_model, " (0x{})", pci_info.subsystem_pci_info.model_id); }; let mut elements = vec![ (fl!(LANGUAGE_LOADER, "gpu-model"), Some(gpu_model)), (fl!(LANGUAGE_LOADER, "subvendor"), Some(card_manufacturer)), (fl!(LANGUAGE_LOADER, "subdevice"), Some(card_model)), ( fl!(LANGUAGE_LOADER, "driver-used"), Some(self.driver.clone()), ), ( fl!(LANGUAGE_LOADER, "vbios-version"), self.vbios_version.clone(), ), ]; if let Some(stats) = stats { elements.push(( fl!(LANGUAGE_LOADER, "vram-size"), stats .vram .total .map(|size| format!("{} Mb", bytes_to_mib(size))), )); } let mut elements = elements .into_iter() .map(|(key, value)| (key.to_owned(), value)) .collect::>(); if let Some(drm_info) = &self.drm_info { let mut vram_type = drm_info.vram_type.clone(); if let Some(vram_type) = &mut vram_type { if let Some(width) = drm_info.vram_bit_width { write!(vram_type, " {width}-bit").unwrap(); } if let Some(vram_vendor) = &drm_info.vram_vendor { write!(vram_type, " ({vram_vendor})").unwrap(); } if let Some(bw) = &drm_info.vram_max_bw && bw != "0" { write!(vram_type, " {bw} GiB/s").unwrap(); } } elements.extend([ ( fl!(LANGUAGE_LOADER, "gpu-family"), drm_info.family_name.clone(), ), ( fl!(LANGUAGE_LOADER, "asic-name"), drm_info.asic_name.clone(), ), ( fl!(LANGUAGE_LOADER, "compute-units"), drm_info.compute_units.map(|count| count.to_string()), ), ( fl!(LANGUAGE_LOADER, "execution-units"), drm_info .intel .execution_units .map(|count| count.to_string()), ), ( fl!(LANGUAGE_LOADER, "subslices"), drm_info.intel.subslices.map(|count| count.to_string()), ), ( fl!(LANGUAGE_LOADER, "cuda-cores"), drm_info.cuda_cores.map(|count| count.to_string()), ), ( fl!(LANGUAGE_LOADER, "hardware-count", name = "SM"), drm_info .streaming_multiprocessors .map(|count| count.to_string()), ), ( fl!(LANGUAGE_LOADER, "hardware-count", name = "ROP"), drm_info.rop_info.as_ref().map(|rop| { format!( "{} ({} * {})", rop.operations_count, rop.unit_count, rop.operations_factor ) }), ), (fl!(LANGUAGE_LOADER, "isa"), drm_info.isa.clone()), (fl!(LANGUAGE_LOADER, "vram-type"), vram_type), ]); if let Some(memory_info) = &drm_info.memory_info { if let Some(rebar) = memory_info.resizeable_bar { let rebar = if rebar { fl!(LANGUAGE_LOADER, "enabled") } else { fl!(LANGUAGE_LOADER, "disabled") }; elements.push((fl!(LANGUAGE_LOADER, "rebar"), Some(rebar.to_owned()))); } elements.push(( fl!(LANGUAGE_LOADER, "cpu-vram"), Some(format!( "{} MiB", memory_info.cpu_accessible_total / 1024 / 1024 )), )); } } if let (Some(max_link_speed), Some(max_link_width)) = (&self.link_info.max_speed, &self.link_info.max_width) && let (Some(current_link_speed), Some(current_link_width)) = (&self.link_info.current_speed, &self.link_info.current_width) { elements.push((fl!(LANGUAGE_LOADER, "pcie-speed"), Some(format!("{current_link_speed} x{current_link_width} (Max: {max_link_speed} x{max_link_width})")))); } elements } } #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct DeviceApiInfo { #[serde(default)] pub vulkan_instances: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub opencl_instances: Vec, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct DrmInfo { pub device_name: Option, pub pci_revision_id: Option, pub family_name: Option, pub family_id: Option, pub asic_name: Option, pub chip_class: Option, pub compute_units: Option, pub isa: Option, pub streaming_multiprocessors: Option, pub cuda_cores: Option, pub vram_type: Option, pub vram_vendor: Option, pub vram_clock_ratio: f64, pub vram_bit_width: Option, pub vram_max_bw: Option, pub cache_info: Option, pub rop_info: Option, pub memory_info: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub amd_ip_info: Vec, #[serde(flatten)] pub intel: IntelDrmInfo, } #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct AmdIpInfo { pub ip_type: String, pub version_major: u32, pub version_minor: u32, pub queues: u32, pub count: u32, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum CacheInfo { Amd(Vec<(AmdCacheInstance, u16)>), Nvidia { l2: u32 }, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct AmdCacheInstance { pub types: Vec, pub level: u8, pub size: u32, pub cu_count: u16, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum CacheType { Data, Instruction, Cpu, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct RopInfo { pub unit_count: u32, pub operations_factor: u32, pub operations_count: u32, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct IntelDrmInfo { pub execution_units: Option, pub subslices: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct DrmMemoryInfo { pub cpu_accessible_used: u64, pub cpu_accessible_total: u64, pub resizeable_bar: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct ClocksInfo { pub max_sclk: Option, pub max_mclk: Option, pub max_voltage: Option, pub table: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "type", content = "value", rename_all = "snake_case")] pub enum ClocksTable { Amd(AmdClocksTableGen), Nvidia(NvidiaClocksTable), Intel(IntelClocksTable), } #[skip_serializing_none] #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct NvidiaClocksTable { #[serde(default, skip_serializing_if = "IndexMap::is_empty")] pub gpu_offsets: IndexMap, #[serde(default, skip_serializing_if = "IndexMap::is_empty")] pub mem_offsets: IndexMap, #[serde(default)] pub gpu_locked_clocks: Option<(u32, u32)>, #[serde(default)] pub vram_locked_clocks: Option<(u32, u32)>, #[serde(default)] pub gpu_clock_range: Option<(u32, u32)>, #[serde(default)] pub vram_clock_range: Option<(u32, u32)>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub gpu_vf_curve: Vec, } #[derive(Serialize, Deserialize, Default, Debug, Clone, Copy)] pub struct NvidiaVfPoint { pub index: u8, pub freq: u32, pub voltage: u32, pub base_freq: u32, pub base_voltage: u32, } /// Doc from `xe_gt_freq.c` #[skip_serializing_none] #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] pub struct IntelClocksTable { pub gt_freq: Option<(u64, u64)>, /// - rpn_freq: The Render Performance (RP) N level, which is the minimal one. pub rpn_freq: Option, /// - rpe_freq: The Render Performance (RP) E level, which is the efficient one. pub rpe_freq: Option, /// - rp0_freq: The Render Performance (RP) 0 level, which is the maximum one. pub rp0_freq: Option, } #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct NvidiaClockOffset { pub current: i32, pub min: i32, pub max: i32, } impl From for ClocksInfo { fn from(table: AmdClocksTableGen) -> Self { let max_sclk = table.get_max_sclk(); let max_mclk = table.get_max_mclk(); let max_voltage = table.get_max_sclk_voltage(); Self { max_sclk, max_mclk, max_voltage, table: Some(ClocksTable::Amd(table)), } } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct LinkInfo { pub current_width: Option, pub current_speed: Option, pub max_width: Option, pub max_speed: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct VulkanInfo { pub device_name: String, pub api_version: String, pub driver: VulkanDriverInfo, pub enabled_layers: Vec, pub features: IndexMap, pub extensions: IndexMap, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct VulkanDriverInfo { pub version: u32, pub name: Option, pub info: Option, pub driver_version: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct OpenCLInfo { pub platform_name: String, pub device_name: String, pub version: String, pub driver_version: String, pub c_version: String, pub compute_units: u32, pub workgroup_size: usize, pub global_memory: u64, pub local_memory: u64, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct PciInfo { pub vendor_id: String, pub vendor: Option, pub model_id: String, pub model: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct DeviceStats { pub fan: FanStats, #[serde(default, skip_serializing_if = "NvidiaThermalInfo::is_empty")] pub nvidia_thermal_info: NvidiaThermalInfo, pub clockspeed: ClockspeedStats, pub voltage: VoltageStats, pub vram: VramStats, pub power: PowerStats, pub temps: HashMap, pub busy_percent: Option, pub performance_level: Option, pub active_power_mizer_mode: Option, pub supported_power_mizer_modes: Option>, pub active_power_states: Option, pub throttle_info: Option>>, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)] pub struct ActivePowerStates { pub core: Option, pub memory: Option, pub pcie: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct TemperatureEntry { #[serde(flatten)] pub value: Temperature, /// Classify "important" temperature values (for display purposes) #[serde(default = "default_temp_primary")] pub primary: bool, /// If the temperature can be used for fan control #[serde(default)] pub display_only: bool, } fn default_temp_primary() -> bool { true } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct FanStats { pub control_enabled: bool, pub control_mode: Option, pub static_speed: Option, pub curve: Option, pub pwm_current: Option, pub speed_current: Option, pub speed_max: Option, pub speed_min: Option, pub pwm_max: Option, pub pwm_min: Option, pub temperature_range: Option<(i32, i32)>, pub temperature_key: Option, pub spindown_delay_ms: Option, pub change_threshold: Option, /// Nvidia-only pub auto_threshold: Option, // RDNA3+ params #[serde(default)] pub pmfw_info: PmfwInfo, } impl FanStats { pub fn percent(&self) -> Option { self.pwm_current .map(|pwm| ((pwm as f64 / u8::MAX as f64) * 100.0).round() as u64) } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct PmfwInfo { pub acoustic_limit: Option, pub acoustic_target: Option, pub target_temp: Option, pub minimum_pwm: Option, pub zero_rpm_enable: Option, pub zero_rpm_temperature: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct NvidiaThermalInfo { pub target_temp: Option, pub target_temp_default: Option, } impl NvidiaThermalInfo { pub fn is_empty(&self) -> bool { *self == Self::default() } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct ClockspeedStats { pub gpu_clockspeed: Option, /// Target clock #[serde(alias = "current_gfxclk")] pub target_gpu_clockspeed: Option, pub vram_clockspeed: Option, #[serde(default)] pub sensors: HashMap, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct VoltageStats { pub gpu: Option, #[serde(default)] pub sensors: HashMap, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)] pub struct VramStats { pub total: Option, pub used: Option, pub gtt_total_usable: Option, pub gtt_used: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct PowerStats { pub average: Option, pub current: Option, pub cap_current: Option, pub cap_max: Option, pub cap_min: Option, pub cap_default: Option, #[serde(default, skip_serializing_if = "HashMap::is_empty")] pub sensors: HashMap, } #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct PowerStates { pub core: Vec, pub vram: Vec, } impl PowerStates { pub fn is_empty(&self) -> bool { self.core.is_empty() && self.vram.is_empty() } pub fn max_gpu_clock(&self) -> Option { self.core.iter().map(|s| s.value).max() } pub fn min_gpu_clock(&self) -> Option { self.core .iter() .filter_map(|s| s.min_value.or(Some(s.value))) .min() } pub fn max_vram_clock(&self) -> Option { self.vram.iter().map(|s| s.value).max() } pub fn min_vram_clock(&self) -> Option { self.vram .iter() .filter_map(|s| s.min_value.or(Some(s.value))) .min() } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub struct PowerState { pub enabled: bool, pub min_value: Option, pub value: u64, pub id: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub enum AmdgpuParamsConfigurator { /// Enables overdrive by creating a modprobe.d file and regenerating the initramfs Modprobe(Option), BootArg(BootArgConfigurator), } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub enum InitramfsType { Debian, Mkinitcpio, Dracut, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub enum BootArgConfigurator { RpmOstree, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct PmfwOptions { pub acoustic_limit: Option, pub acoustic_target: Option, pub minimum_pwm: Option, pub target_temperature: Option, pub zero_rpm: Option, pub zero_rpm_threshold: Option, } impl PmfwOptions { pub fn is_empty(&self) -> bool { *self == Self::default() } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct NvidiaThermalOptions { pub target_temperature: Option, } impl NvidiaThermalOptions { pub fn is_empty(&self) -> bool { *self == Self::default() } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] pub struct FanOptions<'a> { pub id: &'a str, pub enabled: bool, pub mode: Option, pub static_speed: Option, pub curve: Option, #[serde(default)] pub pmfw: PmfwOptions, pub spindown_delay_ms: Option, pub change_threshold: Option, } #[derive(Serialize, Deserialize, Debug, Default)] pub struct ProfilesInfo { pub profiles: IndexMap>, #[serde(default)] pub profile_hooks: IndexMap, pub current_profile: Option, pub auto_switch: bool, pub watcher_state: Option, } impl PartialEq for ProfilesInfo { fn eq(&self, other: &Self) -> bool { self.profiles.as_slice() == other.profiles.as_slice() && self.profile_hooks.as_slice() == other.profile_hooks.as_slice() && self.current_profile == other.current_profile && self.auto_switch == other.auto_switch } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(tag = "type", content = "filter", rename_all = "lowercase")] pub enum ProfileRule { Process(ProcessProfileRule), Gamemode(Option), And(Vec), Or(Vec), } impl Default for ProfileRule { fn default() -> Self { Self::Process(ProcessProfileRule::default()) } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct ProcessProfileRule { pub name: Arc, pub args: Option, } impl Default for ProcessProfileRule { fn default() -> Self { Self { name: String::new().into(), args: None, } } } pub type ProfileProcessMap = IndexMap; #[derive(Serialize, Deserialize, Clone, Default)] pub struct ProfileWatcherState { pub process_list: ProfileProcessMap, pub gamemode_games: IndexSet, pub process_names_map: HashMap, HashSet>, } #[allow(clippy::module_name_repetitions)] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ProfileProcessInfo { pub name: Arc, pub cmdline: Box, } #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct ProcessList { pub processes: BTreeMap, pub supported_util_types: HashSet, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ProcessInfo { pub name: String, pub args: String, pub memory_used: u64, pub types: Vec, pub util: HashMap, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ProcessUtilizationType { Graphics, Compute, Memory, Encode, Decode, } impl ProcessUtilizationType { pub const ALL: &[ProcessUtilizationType] = &[ ProcessUtilizationType::Graphics, ProcessUtilizationType::Compute, ProcessUtilizationType::Memory, ProcessUtilizationType::Encode, ProcessUtilizationType::Decode, ]; } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] pub enum ProcessType { Graphics, Compute, } #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct DisplaysInfo { pub displays: BTreeMap, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct DisplayInfo { pub model: Option, pub manufacturer: Option, pub product_code: u16, pub manufacture_date: Option, pub size: Option<(u32, u32)>, pub connector_type: DisplayConnector, pub connector_id: u32, } #[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub struct DisplayManufactureDate { pub year: u16, pub week: u8, } #[derive(Serialize, Deserialize, Debug, Clone)] pub enum DisplayConnector { DisplayPort { lanes: Option, /// Per-lane bandwidth in Mbps bandwidth: Option, embedded: bool, }, Hdmi, Dvi, Vga, Other, }