feat: support for power profile heuristics configuration (#361)

* feat: basic support for power profile heuristics configuration

* attempt to support RDNA heuristics

* chore: finalization
This commit is contained in:
Ilya Zlobintsev 2024-08-20 22:35:16 +03:00 committed by GitHub
parent 32e25dc269
commit 44704a101e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 312 additions and 117 deletions

4
Cargo.lock generated
View File

@ -53,9 +53,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]]
name = "amdgpu-sysfs"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd30ccf0f663fbe9465c7819c0f52cfcb9bf890c7445192f23ff381fad0b3204"
checksum = "13879cbb65d9f6ebfb4e15a4096fefe442c45b4b93f7b273673d56e50aec3c5a"
dependencies = [
"enum_dispatch",
"serde",

View File

@ -10,7 +10,7 @@ members = [
]
[workspace.dependencies]
amdgpu-sysfs = { version = "0.16.0", features = ["serde"] }
amdgpu-sysfs = { version = "0.16.1", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_with = { version = "3.5.0", default-features = false, features = [
"macros",

View File

@ -164,9 +164,18 @@ impl DaemonClient {
.inner()
}
pub fn set_power_profile_mode(&self, id: &str, index: Option<u16>) -> anyhow::Result<u64> {
self.make_request(Request::SetPowerProfileMode { id, index })?
.inner()
pub fn set_power_profile_mode(
&self,
id: &str,
index: Option<u16>,
custom_heuristics: Vec<Vec<Option<i32>>>,
) -> anyhow::Result<u64> {
self.make_request(Request::SetPowerProfileMode {
id,
index,
custom_heuristics,
})?
.inner()
}
pub fn confirm_pending_config(&self, command: ConfirmCommand) -> anyhow::Result<()> {

View File

@ -67,6 +67,9 @@ pub struct Gpu {
#[serde(default, flatten)]
pub clocks_configuration: ClocksConfiguration,
pub power_profile_mode_index: Option<u16>,
/// Outer vector is for power profile components, inner vector is for the heuristics within a component
#[serde(default)]
pub custom_power_profile_mode_hueristics: Vec<Vec<Option<i32>>>,
#[serde(default)]
pub power_states: HashMap<PowerLevelKind, Vec<u8>>,
}
@ -287,6 +290,7 @@ mod tests {
performance_level: None,
clocks_configuration: ClocksConfiguration::default(),
power_profile_mode_index: None,
custom_power_profile_mode_hueristics: vec![],
power_states: HashMap::new(),
};

View File

@ -783,9 +783,17 @@ impl GpuController {
));
}
self.handle
.set_active_power_profile_mode(mode_index)
.context("Failed to set active power profile mode")?;
if config.custom_power_profile_mode_hueristics.is_empty() {
self.handle
.set_active_power_profile_mode(mode_index)
.context("Failed to set active power profile mode")?;
} else {
self.handle
.set_custom_power_profile_mode_heuristics(
&config.custom_power_profile_mode_hueristics,
)
.context("Failed to set custom power profile mode heuristics")?;
}
}
for (kind, states) in &config.power_states {

View File

@ -473,9 +473,11 @@ impl<'a> Handler {
&self,
id: &str,
index: Option<u16>,
custom_heuristics: Vec<Vec<Option<i32>>>,
) -> anyhow::Result<u64> {
self.edit_gpu_config(id.to_owned(), |gpu_config| {
gpu_config.power_profile_mode_index = index;
gpu_config.custom_power_profile_mode_hueristics = custom_heuristics;
})
.await
.context("Failed to edit GPU config and set power profile mode")

View File

@ -99,9 +99,15 @@ async fn handle_request<'a>(request: Request<'a>, handler: &'a Handler) -> anyho
Request::BatchSetClocksValue { id, commands } => {
ok_response(handler.batch_set_clocks_value(id, commands).await?)
}
Request::SetPowerProfileMode { id, index } => {
ok_response(handler.set_power_profile_mode(id, index).await?)
}
Request::SetPowerProfileMode {
id,
index,
custom_heuristics,
} => ok_response(
handler
.set_power_profile_mode(id, index, custom_heuristics)
.await?,
),
Request::GetPowerStates { id } => ok_response(handler.get_power_states(id)?),
Request::SetEnabledPowerStates { id, kind, states } => {
ok_response(handler.set_enabled_power_states(id, kind, states).await?)

View File

@ -502,7 +502,7 @@ impl App {
// Reset the power profile mode for switching to/from manual performance level
self.daemon_client
.set_power_profile_mode(&gpu_id, None)
.set_power_profile_mode(&gpu_id, None, vec![])
.context("Could not set default power profile mode")?;
self.daemon_client
.confirm_pending_config(ConfirmCommand::Confirm)
@ -521,8 +521,14 @@ impl App {
.oc_page
.performance_frame
.get_selected_power_profile_mode();
let custom_heuristics = self
.root_stack
.oc_page
.performance_frame
.get_power_profile_mode_custom_heuristics();
self.daemon_client
.set_power_profile_mode(&gpu_id, mode_index)
.set_power_profile_mode(&gpu_id, mode_index, custom_heuristics)
.context("Could not set active power profile mode")?;
self.daemon_client
.confirm_pending_config(ConfirmCommand::Confirm)

View File

@ -1,11 +1,17 @@
use crate::app::page_section::PageSection;
use amdgpu_sysfs::gpu_handle::{power_profile_mode::PowerProfileModesTable, PerformanceLevel};
use glib::clone;
use gtk::prelude::*;
use gtk::*;
use gtk::subclass::prelude::ObjectSubclassIsExt;
use gtk::{
glib, DropDown, Label, ListBox, MenuButton, Notebook, NotebookPage, Popover, SelectionMode,
StringObject,
};
use gtk::{prelude::*, Align, Orientation, StringList};
use std::{cell::RefCell, rc::Rc, str::FromStr};
use super::power_profile::power_profile_component_grid::PowerProfileComponentGrid;
use super::power_profile::power_profile_heuristics_grid::PowerProfileHeuristicsGrid;
type ValuesChangedCallback = Rc<dyn Fn()>;
#[derive(Clone)]
pub struct PerformanceFrame {
@ -15,9 +21,11 @@ pub struct PerformanceFrame {
mode_menu_button: MenuButton,
description_label: Label,
manual_info_button: MenuButton,
mode_box: Box,
mode_box: gtk::Box,
modes_table: Rc<RefCell<Option<PowerProfileModesTable>>>,
power_mode_info_notebook: Notebook,
values_changed_callback: Rc<RefCell<Option<ValuesChangedCallback>>>,
}
impl PerformanceFrame {
@ -28,7 +36,7 @@ impl PerformanceFrame {
.into_iter()
.collect();
let level_box = Box::new(Orientation::Horizontal, 10);
let level_box = gtk::Box::new(Orientation::Horizontal, 10);
let level_drop_down = DropDown::builder()
.model(&levels_model)
@ -43,7 +51,7 @@ impl PerformanceFrame {
container.append(&level_box);
let mode_box = Box::new(Orientation::Horizontal, 10);
let mode_box = gtk::Box::new(Orientation::Horizontal, 10);
let mode_menu_button = MenuButton::builder()
.sensitive(false)
@ -99,6 +107,7 @@ impl PerformanceFrame {
mode_box,
modes_table: Rc::new(RefCell::new(None)),
power_mode_info_notebook,
values_changed_callback: Rc::default(),
};
frame.level_drop_down.connect_selected_notify(clone!(
@ -173,6 +182,8 @@ impl PerformanceFrame {
self.modes_listbox.connect_row_selected(clone!(
#[strong(rename_to = modes_table)]
self.modes_table,
#[strong]
f,
move |_, row| {
let modes_table = modes_table.borrow();
@ -185,6 +196,8 @@ impl PerformanceFrame {
}
}
));
*self.values_changed_callback.borrow_mut() = Some(Rc::new(f));
}
pub fn get_selected_performance_level(&self) -> PerformanceLevel {
@ -207,6 +220,37 @@ impl PerformanceFrame {
}
}
pub fn get_power_profile_mode_custom_heuristics(&self) -> Vec<Vec<Option<i32>>> {
let modes_table = self.modes_table.borrow();
if let Some(table) = modes_table.as_ref() {
if let Some(row) = self.modes_listbox.selected_row() {
let active_index = row.index() as u16;
if let Some(active_profile) = table.modes.get(&active_index) {
if active_profile.is_custom() {
let mut components = vec![];
for page in self
.power_mode_info_notebook
.pages()
.iter::<NotebookPage>()
.flatten()
{
let values_grid = page
.child()
.downcast::<PowerProfileHeuristicsGrid>()
.unwrap();
components.push(values_grid.imp().component.borrow().values.clone());
}
return components;
}
}
}
}
vec![]
}
fn update_from_selection(&self) {
self.power_mode_info_notebook.set_visible(false);
@ -229,11 +273,14 @@ impl PerformanceFrame {
self.mode_menu_button.set_sensitive(enable_mode_control);
self.mode_menu_button.set_hexpand(enable_mode_control);
let values_changed_callback = self.values_changed_callback.borrow();
let modes_table = self.modes_table.borrow();
if let Some(table) = modes_table.as_ref() {
if let Some(row) = self.modes_listbox.selected_row() {
if let Some(profile) = table.modes.get(&(row.index() as u16)) {
self.mode_menu_button.set_label(&profile.name);
let active_index = row.index() as u16;
if let Some(active_profile) = table.modes.get(&active_index) {
self.mode_menu_button.set_label(&active_profile.name);
self.power_mode_info_notebook.set_visible(true);
@ -244,8 +291,8 @@ impl PerformanceFrame {
self.power_mode_info_notebook.remove_page(None);
}
for component in &profile.components {
let values_grid = PowerProfileComponentGrid::new();
for (i, component) in active_profile.components.iter().enumerate() {
let values_grid = PowerProfileHeuristicsGrid::new();
values_grid.set_component(component, table);
let title = component.clock_type.as_deref().unwrap_or("All");
@ -256,10 +303,45 @@ impl PerformanceFrame {
.build();
self.power_mode_info_notebook
.append_page(&values_grid, Some(&title_label));
if let Some(f) = &*values_changed_callback {
values_grid.connect_component_values_changed(clone!(
#[strong]
f,
#[strong(rename_to = modes_table)]
self.modes_table,
#[strong]
values_grid,
move || {
let mut modes_table = modes_table.borrow_mut();
if let Some(current_table) = &mut *modes_table {
let changed_component =
values_grid.imp().component.borrow().clone();
current_table
.modes
.get_mut(&active_index)
.unwrap()
.components[i] = changed_component;
}
f();
}
));
}
}
self.power_mode_info_notebook
.set_show_tabs(profile.components.len() > 1);
.set_show_tabs(active_profile.components.len() > 1);
let is_custom = active_profile.is_custom();
for page in self
.power_mode_info_notebook
.pages()
.iter::<NotebookPage>()
.flatten()
{
page.child().set_sensitive(is_custom);
}
// Restore selected page
if current_page.is_some() {

View File

@ -1 +1 @@
pub mod power_profile_component_grid;
pub mod power_profile_heuristics_grid;

View File

@ -1,90 +0,0 @@
use amdgpu_sysfs::gpu_handle::power_profile_mode::{PowerProfileComponent, PowerProfileModesTable};
use gtk::{
glib::{self, Object},
prelude::GridExt,
prelude::WidgetExt,
Label,
};
glib::wrapper! {
pub struct PowerProfileComponentGrid(ObjectSubclass<imp::PowerProfileComponentGrid>)
@extends gtk::Grid,
@implements gtk::Accessible, gtk::Buildable, gtk::Widget;
}
impl PowerProfileComponentGrid {
pub fn new() -> Self {
Object::builder()
.property("margin-start", 5)
.property("margin-end", 5)
.property("margin-top", 5)
.property("margin-bottom", 5)
.build()
}
pub fn set_component(&self, component: &PowerProfileComponent, table: &PowerProfileModesTable) {
while let Some(child) = self.first_child() {
self.remove(&child);
}
for (i, value) in component.values.iter().enumerate() {
let name = &table.value_names[i];
let name_label = Label::builder()
.label(&format!("{name}:"))
.hexpand(true)
.halign(gtk::Align::Start)
.build();
self.attach(&name_label, 0, i as i32, 1, 1);
let mut value_label_builder = Label::builder().halign(gtk::Align::End);
value_label_builder = match value {
Some(value) => value_label_builder.label(value.to_string()),
None => value_label_builder.label("Not set"),
};
self.attach(&value_label_builder.build(), 1, i as i32, 1, 1);
}
}
}
impl Default for PowerProfileComponentGrid {
fn default() -> Self {
Self::new()
}
}
mod imp {
use gtk::{
glib::{self, subclass::InitializingObject},
subclass::{
prelude::*,
widget::{CompositeTemplateClass, WidgetImpl},
},
CompositeTemplate,
};
#[derive(CompositeTemplate, Default)]
#[template(file = "ui/oc_page/power_profile/power_profile_component_grid.blp")]
pub struct PowerProfileComponentGrid {}
#[glib::object_subclass]
impl ObjectSubclass for PowerProfileComponentGrid {
const NAME: &'static str = "PowerProfileComponentGrid";
type Type = super::PowerProfileComponentGrid;
type ParentType = gtk::Grid;
fn class_init(class: &mut Self::Class) {
class.bind_template();
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for PowerProfileComponentGrid {}
impl WidgetImpl for PowerProfileComponentGrid {}
impl GridImpl for PowerProfileComponentGrid {}
}

View File

@ -0,0 +1,166 @@
use amdgpu_sysfs::gpu_handle::power_profile_mode::{PowerProfileComponent, PowerProfileModesTable};
use gtk::{
glib::{self, clone, Object},
prelude::{AdjustmentExt, BoxExt, CheckButtonExt, GridExt, WidgetExt},
subclass::prelude::ObjectSubclassIsExt,
Adjustment, CheckButton, Label, SpinButton,
};
glib::wrapper! {
pub struct PowerProfileHeuristicsGrid(ObjectSubclass<imp::PowerProfileHeuristicsGrid>)
@extends gtk::Grid,
@implements gtk::Accessible, gtk::Buildable, gtk::Widget;
}
impl PowerProfileHeuristicsGrid {
pub fn new() -> Self {
Object::builder()
.property("margin-start", 5)
.property("margin-end", 5)
.property("margin-top", 5)
.property("margin-bottom", 5)
.build()
}
pub fn connect_component_values_changed<F: Fn() + 'static + Clone>(&self, f: F) {
for (adj, toggle_button) in self.imp().adjustments.borrow().iter() {
adj.connect_value_changed(clone!(
#[strong]
f,
move |_| f()
));
toggle_button.connect_toggled(clone!(
#[strong]
f,
move |_| f()
));
}
}
pub fn set_component(&self, component: &PowerProfileComponent, table: &PowerProfileModesTable) {
self.imp().component.replace(component.clone());
while let Some(child) = self.first_child() {
self.remove(&child);
}
let mut adjustments = Vec::with_capacity(component.values.len());
for (i, value) in component.values.iter().enumerate() {
let name = &table.value_names[i];
let name_label = Label::builder()
.label(&format!("{name}:"))
.hexpand(true)
.halign(gtk::Align::Start)
.build();
self.attach(&name_label, 0, i as i32, 1, 1);
let value_box = gtk::Box::new(gtk::Orientation::Horizontal, 5);
value_box.set_hexpand(true);
let value_checkbutton = CheckButton::new();
let adj = Adjustment::new(0.0, f64::MIN, f64::MAX, 1.0, 1.0, 1.0);
let value_spinbutton = SpinButton::new(Some(&adj), 1.0, 0);
value_checkbutton.connect_toggled(clone!(
#[strong]
value_spinbutton,
#[strong(rename_to = this)]
self,
move |check| {
this.imp().update_values(&value_spinbutton, check, i);
}
));
value_spinbutton.connect_value_changed(clone!(
#[strong]
value_checkbutton,
#[strong(rename_to = this)]
self,
move |spin_button| {
this.imp().update_values(spin_button, &value_checkbutton, i);
}
));
value_box.append(&value_checkbutton);
value_box.append(&value_spinbutton);
value_checkbutton.set_active(value.is_some());
if let Some(value) = value {
adj.set_value(*value as f64);
}
self.imp()
.update_values(&value_spinbutton, &value_checkbutton, i);
self.attach(&value_box, 1, i as i32, 1, 1);
adjustments.push((adj, value_checkbutton));
}
*self.imp().adjustments.borrow_mut() = adjustments;
}
}
impl Default for PowerProfileHeuristicsGrid {
fn default() -> Self {
Self::new()
}
}
mod imp {
use amdgpu_sysfs::gpu_handle::power_profile_mode::PowerProfileComponent;
use gtk::{
glib::{self, subclass::InitializingObject},
prelude::{CheckButtonExt, WidgetExt},
subclass::{
prelude::*,
widget::{CompositeTemplateClass, WidgetImpl},
},
Adjustment, CheckButton, CompositeTemplate, SpinButton,
};
use std::{cell::RefCell, rc::Rc};
#[derive(CompositeTemplate, Default)]
#[template(file = "ui/oc_page/power_profile/power_profile_heuristics_grid.blp")]
pub struct PowerProfileHeuristicsGrid {
pub component: Rc<RefCell<PowerProfileComponent>>,
pub(super) adjustments: Rc<RefCell<Vec<(Adjustment, CheckButton)>>>,
}
#[glib::object_subclass]
impl ObjectSubclass for PowerProfileHeuristicsGrid {
const NAME: &'static str = "PowerProfileHeuristicsGrid";
type Type = super::PowerProfileHeuristicsGrid;
type ParentType = gtk::Grid;
fn class_init(class: &mut Self::Class) {
class.bind_template();
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for PowerProfileHeuristicsGrid {}
impl WidgetImpl for PowerProfileHeuristicsGrid {}
impl GridImpl for PowerProfileHeuristicsGrid {}
impl PowerProfileHeuristicsGrid {
pub fn update_values(&self, spin_button: &SpinButton, check: &CheckButton, i: usize) {
let mut component = self.component.borrow_mut();
spin_button.set_sensitive(check.is_active());
if check.is_active() {
component.values[i] = Some(spin_button.value() as i32);
} else {
component.values[i] = None;
}
}
}
}

View File

@ -1,6 +1,6 @@
using Gtk 4.0;
template $PowerProfileComponentGrid: Grid {
template $PowerProfileHeuristicsGrid: Grid {
row-spacing: 5;
column-spacing: 5;
}

View File

@ -43,6 +43,8 @@ pub enum Request<'a> {
SetPowerProfileMode {
id: &'a str,
index: Option<u16>,
#[serde(default)]
custom_heuristics: Vec<Vec<Option<i32>>>,
},
GetPowerStates {
id: &'a str,