mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2026-08-17 16:34:54 -05:00
feat: support nvidia powermizer (#1092)
* update nvml bindings * add basic power-mizer get/set * upd generated-sources * simplify schema * finish rename * gui: drop nvidia_power_mizer_modes * controller: remove get_nvidia_power_mizer_info * minor clean-ups * hacky fix of tests * use correct repo url for nvml-wrapper * controller: rollback to Auto when no value is supplied * gui: handle set_model reset on each tick * chore: extremely minor cleanup --------- Co-authored-by: Ilya Zlobintsev <ilya.zl@protonmail.com>
This commit is contained in:
co-authored by
Ilya Zlobintsev
parent
4bf5bc6166
commit
41c4f3a999
Generated
+9
-4
@@ -216,6 +216,9 @@ name = "bitflags"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
@@ -1754,6 +1757,7 @@ dependencies = [
|
||||
"lact-daemon",
|
||||
"lact-schema",
|
||||
"libadwaita",
|
||||
"nvml-wrapper",
|
||||
"plotters",
|
||||
"plotters-cairo",
|
||||
"pretty_assertions",
|
||||
@@ -1779,6 +1783,7 @@ dependencies = [
|
||||
"i18n-embed",
|
||||
"i18n-embed-fl",
|
||||
"indexmap",
|
||||
"nvml-wrapper",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde-error",
|
||||
@@ -2139,12 +2144,13 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "nvml-wrapper"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f049ae562349fefb8e837eb15443da1e7c6dcbd8a11f52a228f92220c2e5c85e"
|
||||
source = "git+https://github.com/rust-nvml/nvml-wrapper.git?rev=eb47417eede43443f139053479618e96ad32893d#eb47417eede43443f139053479618e96ad32893d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"libloading",
|
||||
"nvml-wrapper-sys",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"static_assertions",
|
||||
"thiserror 1.0.69",
|
||||
"wrapcenum-derive",
|
||||
@@ -2153,8 +2159,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "nvml-wrapper-sys"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b4d594420fcda43b1c2c4bd44d48974aa3c7a9ab2cbf10dc18e35265767bf0b"
|
||||
source = "git+https://github.com/rust-nvml/nvml-wrapper.git?rev=eb47417eede43443f139053479618e96ad32893d#eb47417eede43443f139053479618e96ad32893d"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
@@ -14,6 +14,7 @@ edition = "2024"
|
||||
|
||||
[workspace.dependencies]
|
||||
amdgpu-sysfs = { version = "0.21.0", features = ["serde"] }
|
||||
nvml-wrapper = { git = "https://github.com/rust-nvml/nvml-wrapper.git", rev = "eb47417eede43443f139053479618e96ad32893d" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_with = { version = "3.18.0", default-features = false, features = [
|
||||
"macros",
|
||||
|
||||
@@ -124,6 +124,9 @@ gpus:
|
||||
# Performance level option for AMD GPUs.
|
||||
# Can be `auto`, `low`, `high` or `manual`.
|
||||
performance_level: auto
|
||||
# PowerMizer mode for Nvidia GPUs.
|
||||
# Can be `Auto`, `Adaptive`, `PreferMaximumPerformance` or `PreferConsistentPerformance`.
|
||||
power_mizer_mode: Auto
|
||||
# Index of an AMD power profile mode.
|
||||
# Setting this requires `performance_level` to be set to `manual`.
|
||||
power_profile_mode_index: 0
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
[
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/rust-nvml/nvml-wrapper",
|
||||
"commit": "eb47417eede43443f139053479618e96ad32893d",
|
||||
"dest": "flatpak-cargo/git/nvml-wrapper-eb47417"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
@@ -2561,29 +2567,39 @@
|
||||
"dest-filename": ".cargo-checksum.json"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/nvml-wrapper/nvml-wrapper-0.12.1.crate",
|
||||
"sha256": "f049ae562349fefb8e837eb15443da1e7c6dcbd8a11f52a228f92220c2e5c85e",
|
||||
"dest": "cargo/vendor/nvml-wrapper-0.12.1"
|
||||
"type": "shell",
|
||||
"commands": [
|
||||
"cp -r --reflink=auto \"flatpak-cargo/git/nvml-wrapper-eb47417/nvml-wrapper\" \"cargo/vendor/nvml-wrapper\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "inline",
|
||||
"contents": "{\"package\": \"f049ae562349fefb8e837eb15443da1e7c6dcbd8a11f52a228f92220c2e5c85e\", \"files\": {}}",
|
||||
"dest": "cargo/vendor/nvml-wrapper-0.12.1",
|
||||
"contents": "[package]\nname = \"nvml-wrapper\"\nversion = \"0.12.1\"\nauthors = [\"Cldfire\"]\ndescription = \"A safe and ergonomic Rust wrapper for the NVIDIA Management Library\"\nreadme = \"../README.md\"\ndocumentation = \"https://docs.rs/nvml-wrapper\"\nrepository = \"https://github.com/Cldfire/nvml-wrapper\"\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2021\"\nrust-version = \"1.60.0\"\nkeywords = [\"nvidia\", \"gpu\", \"managment\", \"monitoring\", \"hardware\"]\ncategories = [\"api-bindings\", \"hardware-support\"]\n\n[features]\ndefault = []\nlegacy-functions = [\"nvml-wrapper-sys/legacy-functions\"]\nserde = [\"dep:serde\", \"dep:serde_derive\", \"bitflags/serde\"]\n\n[dependencies]\nthiserror = \"1.0\"\nbitflags = \"2.4.0\"\nwrapcenum-derive = \"0.4.1\"\nlibloading = \"0.8.1\"\nstatic_assertions = \"1.1\"\n\n[dependencies.serde]\nversion = \"1.0\"\noptional = true\n\n[dependencies.serde_derive]\nversion = \"1.0\"\noptional = true\n\n[dependencies.nvml-wrapper-sys]\nversion = \"0.9.1\"\npath = \"../nvml-wrapper-sys\"\n\n[dev-dependencies]\npretty-bytes = \"0.2\"\n",
|
||||
"dest": "cargo/vendor/nvml-wrapper",
|
||||
"dest-filename": "Cargo.toml"
|
||||
},
|
||||
{
|
||||
"type": "inline",
|
||||
"contents": "{\"package\": null, \"files\": {}}",
|
||||
"dest": "cargo/vendor/nvml-wrapper",
|
||||
"dest-filename": ".cargo-checksum.json"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/nvml-wrapper-sys/nvml-wrapper-sys-0.9.1.crate",
|
||||
"sha256": "6b4d594420fcda43b1c2c4bd44d48974aa3c7a9ab2cbf10dc18e35265767bf0b",
|
||||
"dest": "cargo/vendor/nvml-wrapper-sys-0.9.1"
|
||||
"type": "shell",
|
||||
"commands": [
|
||||
"cp -r --reflink=auto \"flatpak-cargo/git/nvml-wrapper-eb47417/nvml-wrapper-sys\" \"cargo/vendor/nvml-wrapper-sys\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "inline",
|
||||
"contents": "{\"package\": \"6b4d594420fcda43b1c2c4bd44d48974aa3c7a9ab2cbf10dc18e35265767bf0b\", \"files\": {}}",
|
||||
"dest": "cargo/vendor/nvml-wrapper-sys-0.9.1",
|
||||
"contents": "[package]\nname = \"nvml-wrapper-sys\"\nversion = \"0.9.1\"\nauthors = [\"Cldfire\"]\ndescription = \"Generated bindings to the NVIDIA Management Library.\"\nreadme = \"README.md\"\ndocumentation = \"https://docs.rs/nvml-wrapper-sys\"\nrepository = \"https://github.com/Cldfire/nvml-wrapper\"\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2021\"\nrust-version = \"1.60.0\"\nkeywords = [\"nvidia\", \"gpu\", \"managment\", \"monitoring\", \"hardware\"]\ncategories = [\"external-ffi-bindings\", \"hardware-support\"]\nexclude = [\"nvml.h\"]\n\n[dependencies]\nlibloading = \"0.8.1\"\n\n[features]\ndefault = []\nlegacy-functions = []\n",
|
||||
"dest": "cargo/vendor/nvml-wrapper-sys",
|
||||
"dest-filename": "Cargo.toml"
|
||||
},
|
||||
{
|
||||
"type": "inline",
|
||||
"contents": "{\"package\": null, \"files\": {}}",
|
||||
"dest": "cargo/vendor/nvml-wrapper-sys",
|
||||
"dest-filename": ".cargo-checksum.json"
|
||||
},
|
||||
{
|
||||
@@ -5409,7 +5425,7 @@
|
||||
},
|
||||
{
|
||||
"type": "inline",
|
||||
"contents": "[source.vendored-sources]\ndirectory = \"cargo/vendor\"\n\n[source.crates-io]\nreplace-with = \"vendored-sources\"\n",
|
||||
"contents": "[source.vendored-sources]\ndirectory = \"cargo/vendor\"\n\n[source.crates-io]\nreplace-with = \"vendored-sources\"\n\n[source.\"https://github.com/rust-nvml/nvml-wrapper\"]\ngit = \"https://github.com/rust-nvml/nvml-wrapper\"\nreplace-with = \"vendored-sources\"\nrev = \"eb47417eede43443f139053479618e96ad32893d\"\n",
|
||||
"dest": "cargo",
|
||||
"dest-filename": "config"
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ indexmap = { workspace = true }
|
||||
divan = { workspace = true, optional = true }
|
||||
serde_norway = { workspace = true }
|
||||
|
||||
nvml-wrapper = "0.12.1"
|
||||
nvml-wrapper = { workspace = true }
|
||||
bitflags = "2.11.1"
|
||||
pciid-parser = { version = "0.8", features = ["serde"] }
|
||||
zbus = { version = "5.14.0", default-features = false, features = ["tokio"] }
|
||||
|
||||
@@ -487,6 +487,7 @@ mod tests {
|
||||
fan_control_settings: None,
|
||||
pmfw_options: PmfwOptions::default(),
|
||||
nvidia_thermal_options: NvidiaThermalOptions::default(),
|
||||
power_mizer_mode: None,
|
||||
power_cap: None,
|
||||
performance_level: None,
|
||||
clocks_configuration: ClocksConfiguration::default(),
|
||||
|
||||
@@ -1123,6 +1123,8 @@ impl GpuController for AmdGpuController {
|
||||
},
|
||||
},
|
||||
nvidia_thermal_info: NvidiaThermalInfo::default(),
|
||||
active_power_mizer_mode: None,
|
||||
supported_power_mizer_modes: None,
|
||||
clockspeed: self.get_clockspeed(metrics),
|
||||
voltage: VoltageStats {
|
||||
gpu: self.hw_mon_and_then(HwMon::get_gpu_voltage),
|
||||
|
||||
@@ -28,9 +28,9 @@ use lact_schema::{
|
||||
};
|
||||
use nvml_wrapper::{
|
||||
Device, Nvml,
|
||||
bitmasks::device::ThrottleReasons,
|
||||
bitmasks::device::{PowerMizerModes, ThrottleReasons},
|
||||
enum_wrappers::device::{Clock, PerformanceState, TemperatureSensor, TemperatureThreshold},
|
||||
enums::device::{GpuLockedClocksSetting, UsedGpuMemory},
|
||||
enums::device::{GpuLockedClocksSetting, PowerMizerMode, UsedGpuMemory},
|
||||
error::NvmlError,
|
||||
};
|
||||
use std::{
|
||||
@@ -580,6 +580,88 @@ fn point_count_from_mask(mask: [u32; 8]) -> usize {
|
||||
count
|
||||
}
|
||||
|
||||
fn apply_power_mizer_mode(
|
||||
device: &mut Device<'_>,
|
||||
configured_mode: Option<PowerMizerMode>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mode_info = match device.power_mizer_mode() {
|
||||
Ok(info) => info,
|
||||
Err(
|
||||
NvmlError::NotSupported
|
||||
| NvmlError::FunctionNotFound
|
||||
| NvmlError::FailedToLoadSymbol(_),
|
||||
) if configured_mode.is_none() => return Ok(()),
|
||||
Err(err) => return Err(err).context("Could not get PowerMizer mode"),
|
||||
};
|
||||
let mode = configured_mode.unwrap_or(PowerMizerMode::Auto);
|
||||
let supported = supported_power_mizer_modes(mode_info.supported);
|
||||
|
||||
if !supported.contains(&mode) {
|
||||
bail!("PowerMizer mode {mode:?} is not supported by this GPU");
|
||||
}
|
||||
|
||||
if mode_info.current == mode {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
debug!("setting PowerMizer mode to {mode:?}");
|
||||
device
|
||||
.set_power_mizer_mode(mode)
|
||||
.context("Could not set PowerMizer mode")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn supported_power_mizer_modes(modes: PowerMizerModes) -> Vec<PowerMizerMode> {
|
||||
[
|
||||
(PowerMizerMode::Auto, PowerMizerModes::AUTO),
|
||||
(PowerMizerMode::Adaptive, PowerMizerModes::ADAPTIVE),
|
||||
(
|
||||
PowerMizerMode::PreferMaximumPerformance,
|
||||
PowerMizerModes::PREFER_MAXIMUM_PERFORMANCE,
|
||||
),
|
||||
(
|
||||
PowerMizerMode::PreferConsistentPerformance,
|
||||
PowerMizerModes::PREFER_CONSISTENT_PERFORMANCE,
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.filter_map(|(mode, flag)| modes.contains(flag).then_some(mode))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
fn apply_power_cap(device: &mut Device<'_>, power_cap: Option<f64>) -> anyhow::Result<()> {
|
||||
if let Some(cap) = power_cap {
|
||||
let cap = (cap * 1000.0) as u32;
|
||||
|
||||
let current_cap = device
|
||||
.power_management_limit()
|
||||
.context("Could not get current cap")?;
|
||||
|
||||
if current_cap != cap {
|
||||
debug!("setting power cap to {cap}");
|
||||
device
|
||||
.set_power_management_limit(cap)
|
||||
.context("Could not set power cap")?;
|
||||
}
|
||||
} else {
|
||||
let current_cap = device.power_management_limit();
|
||||
let default_cap = device.power_management_limit_default();
|
||||
|
||||
if let (Ok(current_cap), Ok(default_cap)) = (current_cap, default_cap)
|
||||
&& current_cap != default_cap
|
||||
{
|
||||
debug!("resetting power cap to {default_cap}");
|
||||
device
|
||||
.set_power_management_limit(default_cap)
|
||||
.context("Could not reset power cap")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl GpuController for NvidiaGpuController {
|
||||
fn controller_info(&self) -> &CommonControllerInfo {
|
||||
&self.common
|
||||
@@ -835,6 +917,7 @@ impl GpuController for NvidiaGpuController {
|
||||
.ok();
|
||||
|
||||
let fan_range = device.min_max_fan_speed().ok();
|
||||
let power_mizer_info = device.power_mizer_mode().ok();
|
||||
|
||||
DeviceStats {
|
||||
temps,
|
||||
@@ -857,6 +940,9 @@ impl GpuController for NvidiaGpuController {
|
||||
pmfw_info: PmfwInfo::default(),
|
||||
},
|
||||
nvidia_thermal_info: self.get_nvidia_thermal_info(),
|
||||
active_power_mizer_mode: power_mizer_info.as_ref().map(|info| info.current),
|
||||
supported_power_mizer_modes: power_mizer_info
|
||||
.map(|info| supported_power_mizer_modes(info.supported)),
|
||||
power: PowerStats {
|
||||
average: None,
|
||||
current: device.power_usage().map(|mw| f64::from(mw) / 1000.0).ok(),
|
||||
@@ -1015,33 +1101,8 @@ impl GpuController for NvidiaGpuController {
|
||||
Box::pin(async {
|
||||
let mut device = self.device();
|
||||
|
||||
if let Some(cap) = config.power_cap {
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
let cap = (cap * 1000.0) as u32;
|
||||
|
||||
let current_cap = device
|
||||
.power_management_limit()
|
||||
.context("Could not get current cap")?;
|
||||
|
||||
if current_cap != cap {
|
||||
debug!("setting power cap to {cap}");
|
||||
device
|
||||
.set_power_management_limit(cap)
|
||||
.context("Could not set power cap")?;
|
||||
}
|
||||
} else {
|
||||
let current_cap = device.power_management_limit();
|
||||
let default_cap = device.power_management_limit_default();
|
||||
|
||||
if let (Ok(current_cap), Ok(default_cap)) = (current_cap, default_cap)
|
||||
&& current_cap != default_cap
|
||||
{
|
||||
debug!("resetting power cap to {default_cap}");
|
||||
device
|
||||
.set_power_management_limit(default_cap)
|
||||
.context("Could not reset power cap")?;
|
||||
}
|
||||
}
|
||||
apply_power_cap(&mut device, config.power_cap)?;
|
||||
apply_power_mizer_mode(&mut device, config.power_mizer_mode)?;
|
||||
|
||||
self.reset_clocks()?;
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ gpus:
|
||||
zero_rpm_threshold: 50
|
||||
nvidia_thermal_options:
|
||||
target_temperature: 83
|
||||
power_mizer_mode: Auto
|
||||
power_cap: 320
|
||||
performance_level: auto
|
||||
min_core_clock: 300
|
||||
|
||||
@@ -15,6 +15,7 @@ lact-daemon = { path = "../lact-daemon", default-features = false }
|
||||
lact-schema = { path = "../lact-schema", features = ["args"] }
|
||||
|
||||
amdgpu-sysfs = { workspace = true }
|
||||
nvml-wrapper = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
|
||||
@@ -145,6 +145,15 @@ performance-level-manual-description = Manual performance control.
|
||||
performance-level = Performance Level
|
||||
power-profile-mode = Power Profile Mode:
|
||||
manual-level-needed = Performance level has to be set to "manual" to use power states and modes
|
||||
power-mizer-mode = PowerMizer Mode
|
||||
power-mizer-mode-auto = Auto
|
||||
power-mizer-mode-adaptive = Adaptive
|
||||
power-mizer-mode-prefer-maximum-performance = Prefer Maximum Performance
|
||||
power-mizer-mode-prefer-consistent-performance = Prefer Consistent Performance
|
||||
power-mizer-mode-auto-description = Let the driver choose the performance policy.
|
||||
power-mizer-mode-adaptive-description = Adjust GPU clocks based on GPU usage.
|
||||
power-mizer-mode-prefer-maximum-performance-description = Favor maximum performance within driver limits.
|
||||
power-mizer-mode-prefer-consistent-performance-description = Lock to GPU base clocks.
|
||||
|
||||
overclock-section = Clockspeed and Voltage
|
||||
nvidia-oc-info = Overclocking Information
|
||||
|
||||
@@ -1218,6 +1218,10 @@ impl AppModel {
|
||||
.get_power_profile_mode_custom_heuristics();
|
||||
}
|
||||
|
||||
if let Some(mode) = self.oc_page.model().get_active_power_mizer_mode() {
|
||||
gpu_config.power_mizer_mode = Some(mode);
|
||||
}
|
||||
|
||||
self.thermals_page.model().apply_config(&mut gpu_config);
|
||||
|
||||
self.oc_page
|
||||
|
||||
@@ -178,6 +178,8 @@ mod benches {
|
||||
..Default::default()
|
||||
},
|
||||
nvidia_thermal_info: Default::default(),
|
||||
active_power_mizer_mode: None,
|
||||
supported_power_mizer_modes: None,
|
||||
performance_level: Some(PerformanceLevel::Auto),
|
||||
power: PowerStats {
|
||||
average: Some(36.0),
|
||||
|
||||
@@ -17,6 +17,7 @@ use gtk::prelude::{BoxExt, OrientableExt, WidgetExt};
|
||||
use indexmap::IndexMap;
|
||||
use lact_schema::config;
|
||||
use lact_schema::{ClocksTable, DeviceInfo, PowerStates};
|
||||
use nvml_wrapper::enums::device::PowerMizerMode;
|
||||
use performance_frame::{PerformanceFrame, PerformanceFrameMsg};
|
||||
use power_cap_section::{PowerCapMsg, PowerCapSection};
|
||||
use power_states::power_states_frame::{PowerStatesFrame, PowerStatesFrameMsg};
|
||||
@@ -138,6 +139,11 @@ impl relm4::Component for OcPage {
|
||||
.emit(PerformanceFrameMsg::PerformanceLevel(
|
||||
stats.performance_level,
|
||||
));
|
||||
self.performance_frame
|
||||
.emit(PerformanceFrameMsg::PowerMizerInfo {
|
||||
active: stats.active_power_mizer_mode,
|
||||
supported: stats.supported_power_mizer_modes.clone(),
|
||||
});
|
||||
sender.input(OcPageMsg::PerformanceLevelChanged);
|
||||
}
|
||||
}
|
||||
@@ -205,6 +211,10 @@ impl OcPage {
|
||||
self.performance_frame.model().performance_level()
|
||||
}
|
||||
|
||||
pub fn get_active_power_mizer_mode(&self) -> Option<PowerMizerMode> {
|
||||
self.performance_frame.model().active_power_mizer_mode()
|
||||
}
|
||||
|
||||
pub fn get_power_profile_mode(&self) -> Option<u16> {
|
||||
self.performance_frame.model().power_profile_mode()
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use gtk::{
|
||||
};
|
||||
use heuristics_list::PowerProfileHeuristicsList;
|
||||
use i18n_embed_fl::fl;
|
||||
use nvml_wrapper::enums::device::PowerMizerMode;
|
||||
use relm4::{Component, ComponentController, ComponentParts, ComponentSender, RelmWidgetExt};
|
||||
|
||||
const PERFORMANCE_LEVELS: [PerformanceLevel; 4] = [
|
||||
@@ -26,6 +27,9 @@ pub struct PerformanceFrame {
|
||||
performance_level: Option<PerformanceLevel>,
|
||||
power_profile_modes_table: Option<PowerProfileModesTable>,
|
||||
power_profile_modes: gtk::StringList,
|
||||
active_power_mizer_mode: Option<PowerMizerMode>,
|
||||
supported_power_mizer_modes: Vec<PowerMizerMode>,
|
||||
power_mizer_modes: gtk::StringList,
|
||||
heuristics_components: Vec<relm4::Controller<PowerProfileHeuristicsList>>,
|
||||
}
|
||||
|
||||
@@ -34,6 +38,11 @@ pub enum PerformanceFrameMsg {
|
||||
PerformanceLevel(Option<PerformanceLevel>),
|
||||
PowerProfileModes(Option<PowerProfileModesTable>),
|
||||
PowerProfileSelected(u16),
|
||||
PowerMizerInfo {
|
||||
active: Option<PowerMizerMode>,
|
||||
supported: Option<Vec<PowerMizerMode>>,
|
||||
},
|
||||
PowerMizerSelected(u32),
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
@@ -46,11 +55,13 @@ impl relm4::Component for PerformanceFrame {
|
||||
view! {
|
||||
PageSection::new("Performance") {
|
||||
#[watch]
|
||||
set_visible: model.performance_level.is_some(),
|
||||
set_visible: model.performance_level.is_some() || model.active_power_mizer_mode.is_some(),
|
||||
|
||||
append_child = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
#[watch]
|
||||
set_visible: model.performance_level.is_some(),
|
||||
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "performance-level"),
|
||||
@@ -88,6 +99,8 @@ impl relm4::Component for PerformanceFrame {
|
||||
append_child = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
#[watch]
|
||||
set_visible: model.power_profile_modes_table.is_some(),
|
||||
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "power-profile-mode"),
|
||||
@@ -151,6 +164,46 @@ impl relm4::Component for PerformanceFrame {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
append_child = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
#[watch]
|
||||
set_visible: model.active_power_mizer_mode.is_some(),
|
||||
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "power-mizer-mode"),
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_label: &match model.active_power_mizer_mode {
|
||||
Some(PowerMizerMode::Auto) => fl!(I18N, "power-mizer-mode-auto-description"),
|
||||
Some(PowerMizerMode::Adaptive) => fl!(I18N, "power-mizer-mode-adaptive-description"),
|
||||
Some(PowerMizerMode::PreferMaximumPerformance) => fl!(I18N, "power-mizer-mode-prefer-maximum-performance-description"),
|
||||
Some(PowerMizerMode::PreferConsistentPerformance) => fl!(I18N, "power-mizer-mode-prefer-consistent-performance-description"),
|
||||
None => String::new(),
|
||||
},
|
||||
set_hexpand: true,
|
||||
set_halign: gtk::Align::End,
|
||||
},
|
||||
|
||||
#[name = "power_mizer_dropdown"]
|
||||
gtk::DropDown {
|
||||
#[watch]
|
||||
#[block_signal(power_mizer_select_handler)]
|
||||
set_model: Some(&model.power_mizer_modes),
|
||||
#[watch]
|
||||
#[block_signal(power_mizer_select_handler)]
|
||||
set_selected: model.active_power_mizer_mode
|
||||
.and_then(|active| model.supported_power_mizer_modes.iter().position(|mode| *mode == active))
|
||||
.unwrap_or(0) as u32,
|
||||
|
||||
connect_selected_notify[sender] => move |dropdown| {
|
||||
sender.input(PerformanceFrameMsg::PowerMizerSelected(dropdown.selected()));
|
||||
} @ power_mizer_select_handler,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +222,9 @@ impl relm4::Component for PerformanceFrame {
|
||||
performance_level: None,
|
||||
power_profile_modes_table: None,
|
||||
power_profile_modes: gtk::StringList::new(&[]),
|
||||
active_power_mizer_mode: None,
|
||||
supported_power_mizer_modes: vec![],
|
||||
power_mizer_modes: gtk::StringList::new(&[]),
|
||||
heuristics_components: vec![],
|
||||
};
|
||||
|
||||
@@ -211,6 +267,26 @@ impl relm4::Component for PerformanceFrame {
|
||||
self.update_heuristic_components(widgets);
|
||||
}
|
||||
}
|
||||
PerformanceFrameMsg::PowerMizerInfo { active, supported } => {
|
||||
let modes = gtk::StringList::new(&[]);
|
||||
let supported = supported.unwrap_or_default();
|
||||
|
||||
for mode in &supported {
|
||||
modes.append(&power_mizer_mode_friendly_name(*mode));
|
||||
}
|
||||
|
||||
self.power_mizer_modes = modes;
|
||||
self.active_power_mizer_mode = active;
|
||||
self.supported_power_mizer_modes = supported;
|
||||
}
|
||||
PerformanceFrameMsg::PowerMizerSelected(idx) => {
|
||||
if let Some(mode) = self.supported_power_mizer_modes.get(idx as usize)
|
||||
&& self.active_power_mizer_mode != Some(*mode)
|
||||
{
|
||||
self.active_power_mizer_mode = Some(*mode);
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(table) = &self.power_profile_modes_table
|
||||
@@ -260,6 +336,10 @@ impl PerformanceFrame {
|
||||
self.performance_level
|
||||
}
|
||||
|
||||
pub fn active_power_mizer_mode(&self) -> Option<PowerMizerMode> {
|
||||
self.active_power_mizer_mode
|
||||
}
|
||||
|
||||
pub fn power_profile_mode(&self) -> Option<u16> {
|
||||
if self.performance_level == Some(PerformanceLevel::Manual) {
|
||||
self.power_profile_modes_table
|
||||
@@ -302,3 +382,16 @@ fn level_friendly_name(level: PerformanceLevel) -> String {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn power_mizer_mode_friendly_name(mode: PowerMizerMode) -> String {
|
||||
match mode {
|
||||
PowerMizerMode::Auto => fl!(I18N, "power-mizer-mode-auto"),
|
||||
PowerMizerMode::Adaptive => fl!(I18N, "power-mizer-mode-adaptive"),
|
||||
PowerMizerMode::PreferMaximumPerformance => {
|
||||
fl!(I18N, "power-mizer-mode-prefer-maximum-performance")
|
||||
}
|
||||
PowerMizerMode::PreferConsistentPerformance => {
|
||||
fl!(I18N, "power-mizer-mode-prefer-consistent-performance")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ args = ["clap"]
|
||||
|
||||
[dependencies]
|
||||
amdgpu-sysfs = { workspace = true }
|
||||
nvml-wrapper = { workspace = true, features = ["serde"] }
|
||||
serde = { workspace = true, features = ["derive", "rc"] }
|
||||
serde_with = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use amdgpu_sysfs::gpu_handle::{PerformanceLevel, PowerLevelKind};
|
||||
use indexmap::IndexMap;
|
||||
use nvml_wrapper::enums::device::PowerMizerMode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
|
||||
@@ -41,6 +42,7 @@ pub struct GpuConfig {
|
||||
pub pmfw_options: PmfwOptions,
|
||||
#[serde(default, skip_serializing_if = "NvidiaThermalOptions::is_empty")]
|
||||
pub nvidia_thermal_options: NvidiaThermalOptions,
|
||||
pub power_mizer_mode: Option<PowerMizerMode>,
|
||||
pub power_cap: Option<f64>,
|
||||
pub performance_level: Option<PerformanceLevel>,
|
||||
#[serde(default, flatten)]
|
||||
|
||||
@@ -22,6 +22,8 @@ use amdgpu_sysfs::{
|
||||
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::{
|
||||
@@ -557,6 +559,8 @@ pub struct DeviceStats {
|
||||
pub temps: HashMap<String, TemperatureEntry>,
|
||||
pub busy_percent: Option<u8>,
|
||||
pub performance_level: Option<PerformanceLevel>,
|
||||
pub active_power_mizer_mode: Option<PowerMizerMode>,
|
||||
pub supported_power_mizer_modes: Option<Vec<PowerMizerMode>>,
|
||||
pub active_power_states: Option<ActivePowerStates>,
|
||||
pub throttle_info: Option<BTreeMap<String, Vec<String>>>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user