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:
Roman Makarov
2026-07-12 21:42:20 +03:00
committed by GitHub
co-authored by Ilya Zlobintsev
parent 4bf5bc6166
commit 41c4f3a999
18 changed files with 266 additions and 50 deletions
Generated
+9 -4
View File
@@ -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",
]
+1
View File
@@ -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",
+3
View File
@@ -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
+31 -15
View File
@@ -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"
}
+1 -1
View File
@@ -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"] }
+1
View File
@@ -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),
+90 -29
View File
@@ -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
+1
View File
@@ -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 }
+9
View File
@@ -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
+4
View File
@@ -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),
+10
View File
@@ -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 = &gtk::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 = &gtk::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 = &gtk::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")
}
}
}
+1
View File
@@ -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 }
+2
View File
@@ -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)]
+4
View File
@@ -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>>>,
}