Feature/power profile modes (#112)

* feat: daemon implementation of power profile modes

* feat: updated performance frame with manual mode support

* feat: WIP UI for power modes

* feat: setting power profile mode in UI

* feat: show power profile modes info

* fix: hide power profile modes when not available

* feat: basic power profile modes table support

* chore: use git amdgpu-sysfs source

* fix: basic modes table gui offset

* feat: use basic power profiles instead

* fix: invalid lowest clocks performance level name

* chore: remove git source
This commit is contained in:
Ilya Zlobintsev
2023-03-02 21:55:17 +02:00
committed by GitHub
parent bf61001136
commit 98cb7ace24
15 changed files with 289 additions and 108 deletions

4
Cargo.lock generated
View File

@@ -25,9 +25,9 @@ dependencies = [
[[package]]
name = "amdgpu-sysfs"
version = "0.9.7"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6667682d11c8face0277986d0cfc0862399febf666e0d9a9d8b12fa0362373ba"
checksum = "47443bb84437d283c988cc988e28bac8f841530a855d7069cf272a691af61db0"
dependencies = [
"enum_dispatch",
"serde",

View File

@@ -6,8 +6,8 @@ pub use lact_schema as schema;
use anyhow::{anyhow, Context};
use nix::unistd::getuid;
use schema::{
request::SetClocksCommand, ClocksInfo, DeviceInfo, DeviceListEntry, DeviceStats, FanCurveMap,
PerformanceLevel, Request, Response, SystemInfo,
power_profile_mode::PowerProfileModesTable, request::SetClocksCommand, ClocksInfo, DeviceInfo,
DeviceListEntry, DeviceStats, FanCurveMap, PerformanceLevel, Request, Response, SystemInfo,
};
use serde::Deserialize;
use std::{
@@ -90,6 +90,11 @@ impl DaemonClient {
request_with_id!(get_device_info, DeviceInfo, DeviceInfo);
request_with_id!(get_device_stats, DeviceStats, DeviceStats);
request_with_id!(get_device_clocks_info, DeviceClocksInfo, ClocksInfo);
request_with_id!(
get_device_power_profile_modes,
DevicePowerProfileModes,
PowerProfileModesTable
);
pub fn set_performance_level(
&self,
@@ -107,6 +112,11 @@ impl DaemonClient {
self.make_request(Request::SetClocksValue { id, command })?
.inner()
}
pub fn set_power_profile_mode(&self, id: &str, index: Option<u16>) -> anyhow::Result<()> {
self.make_request(Request::SetPowerProfileMode { id, index })?
.inner()
}
}
fn get_socket_path() -> Option<PathBuf> {

View File

@@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
amdgpu-sysfs = { version = "0.9.7", features = ["serde"] }
amdgpu-sysfs = { version = "0.10.0", features = ["serde"] }
anyhow = "1.0"
bincode = "1.3"
nix = "0.26"

View File

@@ -42,6 +42,7 @@ pub struct Gpu {
pub max_memory_clock: Option<u32>,
pub max_voltage: Option<u32>,
pub voltage_offset: Option<i32>,
pub power_profile_mode_index: Option<u16>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]

View File

@@ -341,6 +341,16 @@ impl GpuController {
.set_power_force_performance_level(PerformanceLevel::Auto)?;
}
if let Some(mode_index) = config.power_profile_mode_index {
if config.performance_level != Some(PerformanceLevel::Manual) {
return Err(anyhow!(
"Performance level has to be set to `manual` to use power profile modes"
));
}
self.handle.set_active_power_profile_mode(mode_index)?;
}
if config.max_core_clock.is_some()
|| config.max_memory_clock.is_some()
|| config.max_voltage.is_some()

View File

@@ -1,5 +1,6 @@
use super::gpu_controller::{fan_control::FanCurve, GpuController};
use crate::config::{self, Config, FanControlSettings};
use amdgpu_sysfs::gpu_handle::power_profile_mode::PowerProfileModesTable;
use anyhow::{anyhow, Context};
use lact_schema::{
request::SetClocksCommand, ClocksInfo, DeviceInfo, DeviceListEntry, DeviceStats, FanCurveMap,
@@ -226,6 +227,21 @@ impl<'a> Handler {
.await
}
pub fn get_power_profile_modes(&self, id: &str) -> anyhow::Result<PowerProfileModesTable> {
let modes_table = self
.controller_by_id(id)?
.handle
.get_power_profile_modes()?;
Ok(modes_table)
}
pub async fn set_power_profile_mode(&self, id: &str, index: Option<u16>) -> anyhow::Result<()> {
self.edit_gpu_config(id.to_owned(), |gpu_config| {
gpu_config.power_profile_mode_index = index;
})
.await
}
pub async fn cleanup(self) {
for (id, controller) in self.gpu_controllers.iter() {
if controller.handle.get_clocks_table().is_ok() {

View File

@@ -84,6 +84,9 @@ async fn handle_request<'a>(request: Request<'a>, handler: &'a Handler) -> anyho
Request::DeviceInfo { id } => ok_response(handler.get_device_info(id)?),
Request::DeviceStats { id } => ok_response(handler.get_gpu_stats(id)?),
Request::DeviceClocksInfo { id } => ok_response(handler.get_clocks_info(id)?),
Request::DevicePowerProfileModes { id } => {
ok_response(handler.get_power_profile_modes(id)?)
}
Request::SetFanControl { id, enabled, curve } => {
ok_response(handler.set_fan_control(id, enabled, curve).await?)
}
@@ -95,6 +98,9 @@ async fn handle_request<'a>(request: Request<'a>, handler: &'a Handler) -> anyho
Request::SetClocksValue { id, command } => {
ok_response(handler.set_clocks_value(id, command).await?)
}
Request::SetPowerProfileMode { id, index } => {
ok_response(handler.set_power_profile_mode(id, index).await?)
}
}
}

View File

@@ -184,6 +184,24 @@ impl App {
};
self.root_stack.oc_page.set_clocks_table(maybe_clocks_table);
let maybe_modes_table = match self.daemon_client.get_device_power_profile_modes(gpu_id) {
Ok(buf) => match buf.inner() {
Ok(table) => Some(table),
Err(err) => {
debug!("Could not extract profile modes table: {err:?}");
None
}
},
Err(err) => {
debug!("Could not get profile modes table: {err:?}");
None
}
};
self.root_stack
.oc_page
.performance_frame
.set_power_profile_modes(maybe_modes_table);
// Show apply button on setting changes
// This is done here because new widgets may appear after applying settings (like fan curve points) which should be connected
let show_revealer = clone!(@strong self.apply_revealer as apply_revealer => move || {
@@ -259,10 +277,24 @@ impl App {
.context("Failed to set power cap")?;
}
// Reset the power profile mode for switching to/from manual performance level
self.daemon_client
.set_power_profile_mode(&gpu_id, None)
.context("Could not set default power profile mode")?;
if let Some(level) = self.root_stack.oc_page.get_performance_level() {
self.daemon_client
.set_performance_level(&gpu_id, level)
.context("Failed to set power profile")?;
let mode_index = self
.root_stack
.oc_page
.performance_frame
.get_selected_power_profile_mode();
self.daemon_client
.set_power_profile_mode(&gpu_id, mode_index)
.context("Could not set active power profile mode")?;
}
if let Some(thermals_settings) = self.root_stack.thermals_page.get_thermals_settings() {

View File

@@ -110,7 +110,6 @@ impl ClocksFrame {
if let ClocksTableGen::Vega20(table) = table {
if let Some(offset) = table.voltage_offset {
// TODO: check this
self.voltage_offset_adjustment
.set_lower(VOLTAGE_OFFSET_RANGE * -1.0);
self.voltage_offset_adjustment

View File

@@ -1,5 +1,5 @@
mod clocks_frame;
mod performance_level_frame;
mod performance_frame;
mod power_cap_frame;
mod stats_frame;
@@ -7,7 +7,7 @@ use clocks_frame::ClocksFrame;
use gtk::prelude::*;
use gtk::*;
use lact_client::schema::{ClocksTableGen, DeviceStats, PerformanceLevel, SystemInfo};
use performance_level_frame::PerformanceLevelFrame;
use performance_frame::PerformanceFrame;
use power_cap_frame::PowerCapFrame;
use stats_frame::StatsFrame;
use tracing::warn;
@@ -16,7 +16,7 @@ use tracing::warn;
pub struct OcPage {
pub container: Box,
stats_frame: StatsFrame,
performance_level_frame: PerformanceLevelFrame,
pub performance_frame: PerformanceFrame,
power_cap_frame: PowerCapFrame,
pub clocks_frame: ClocksFrame,
}
@@ -37,7 +37,7 @@ impl OcPage {
container.append(&stats_frame.container);
let power_cap_frame = PowerCapFrame::new();
let performance_level_frame = PerformanceLevelFrame::new();
let performance_level_frame = PerformanceFrame::new();
let clocks_frame = ClocksFrame::new();
container.append(&power_cap_frame.container);
@@ -47,7 +47,7 @@ impl OcPage {
Self {
container,
stats_frame,
performance_level_frame,
performance_frame: performance_level_frame,
clocks_frame,
power_cap_frame,
}
@@ -83,8 +83,7 @@ impl OcPage {
}
pub fn connect_settings_changed<F: Fn() + 'static + Clone>(&self, f: F) {
self.performance_level_frame
.connect_power_profile_changed(f.clone());
self.performance_frame.connect_settings_changed(f.clone());
self.power_cap_frame.connect_cap_changed(f.clone());
self.clocks_frame.connect_clocks_changed(f);
}
@@ -92,18 +91,16 @@ impl OcPage {
pub fn set_performance_level(&self, profile: Option<PerformanceLevel>) {
match profile {
Some(profile) => {
self.performance_level_frame.show();
self.performance_level_frame.set_active_profile(profile);
self.performance_frame.show();
self.performance_frame.set_active_level(profile);
}
None => self.performance_level_frame.hide(),
None => self.performance_frame.hide(),
}
}
pub fn get_performance_level(&self) -> Option<PerformanceLevel> {
if self.performance_level_frame.get_visibility() {
let level = self
.performance_level_frame
.get_selected_performance_level();
if self.performance_frame.get_visibility() {
let level = self.performance_frame.get_selected_performance_level();
Some(level)
} else {
None

View File

@@ -0,0 +1,189 @@
use crate::app::root_stack::section_box;
use glib::clone;
use gtk::prelude::*;
use gtk::*;
use lact_client::schema::{power_profile_mode::PowerProfileModesTable, PerformanceLevel};
use std::{cell::RefCell, rc::Rc, str::FromStr};
#[derive(Clone)]
pub struct PerformanceFrame {
pub container: Box,
level_drop_down: DropDown,
mode_drop_down: DropDown,
description_label: Label,
manual_info_button: MenuButton,
mode_box: Box,
modes_table: Rc<RefCell<Option<PowerProfileModesTable>>>,
}
impl PerformanceFrame {
pub fn new() -> Self {
let container = section_box("Performance");
let levels_model: StringList = ["Automatic", "Highest Clocks", "Lowest Clocks", "Manual"]
.into_iter()
.collect();
let level_box = Box::new(Orientation::Horizontal, 10);
let level_drop_down = DropDown::builder()
.model(&levels_model)
.sensitive(false)
.build();
let description_label = Label::builder().halign(Align::End).hexpand(true).build();
let perfromance_title_label = Label::builder().label("Performance level:").build();
level_box.append(&perfromance_title_label);
level_box.append(&description_label);
level_box.append(&level_drop_down);
container.append(&level_box);
let mode_box = Box::new(Orientation::Horizontal, 10);
let mode_drop_down = DropDown::builder()
.sensitive(false)
.halign(Align::End)
.build();
let unavailable_label = Label::new(Some(
"Performance level has to be set to \"manual\" to use power profile modes",
));
let mode_info_popover = Popover::builder().child(&unavailable_label).build();
let manual_info_button = MenuButton::builder()
.icon_name("dialog-information-symbolic")
.hexpand(true)
.halign(Align::End)
.popover(&mode_info_popover)
.build();
let mode_title_label = Label::new(Some("Power level mode:"));
mode_box.append(&mode_title_label);
mode_box.append(&manual_info_button);
mode_box.append(&mode_drop_down);
container.append(&mode_box);
let frame = Self {
container,
level_drop_down,
mode_drop_down,
description_label,
manual_info_button,
mode_box,
modes_table: Rc::new(RefCell::new(None)),
};
frame
.level_drop_down
.connect_selected_notify(clone!(@strong frame => move |_| {
frame.update_from_selection();
}));
frame
.mode_drop_down
.connect_selected_notify(clone!(@strong frame => move |_| {
frame.update_from_selection();
}));
frame
}
pub fn set_active_level(&self, level: PerformanceLevel) {
self.level_drop_down.set_sensitive(true);
match level {
PerformanceLevel::Auto => self.level_drop_down.set_selected(0),
PerformanceLevel::High => self.level_drop_down.set_selected(1),
PerformanceLevel::Low => self.level_drop_down.set_selected(2),
PerformanceLevel::Manual => self.level_drop_down.set_selected(3),
};
self.update_from_selection();
}
pub fn set_power_profile_modes(&self, table: Option<PowerProfileModesTable>) {
self.mode_box.set_visible(table.is_some());
match &table {
Some(table) => {
let model: StringList = table.modes.values().cloned().collect();
let active_pos = table
.modes
.keys()
.position(|key| *key == table.active)
.expect("No active mode") as u32;
self.mode_drop_down.set_model(Some(&model));
self.mode_drop_down.set_selected(active_pos);
self.mode_drop_down.show();
}
None => {
self.mode_drop_down.hide();
}
}
self.modes_table.replace(table);
}
pub fn connect_settings_changed<F: Fn() + 'static + Clone>(&self, f: F) {
self.level_drop_down
.connect_selected_notify(clone!(@strong f => move |_| f()));
self.mode_drop_down.connect_selected_notify(move |_| f());
}
pub fn get_selected_performance_level(&self) -> PerformanceLevel {
let selected_item = self
.level_drop_down
.selected_item()
.expect("No selected item");
let string_object = selected_item.downcast_ref::<StringObject>().unwrap();
PerformanceLevel::from_str(string_object.string().as_str())
.expect("Unrecognized selected performance level")
}
pub fn get_selected_power_profile_mode(&self) -> Option<u16> {
if self.mode_drop_down.is_sensitive() {
self.modes_table.borrow().as_ref().map(|table| {
let selected_index = table
.modes
.keys()
.nth(self.mode_drop_down.selected() as usize)
.expect("Selected mode out of range");
*selected_index
})
} else {
None
}
}
fn update_from_selection(&self) {
let mut enable_mode_control = false;
let text = match self.level_drop_down.selected() {
0 => "Automatically adjust GPU and VRAM clocks. (Default)",
1 => "Always use the highest clockspeeds for GPU and VRAM.",
2 => "Always use the lowest clockspeeds for GPU and VRAM.",
3 => {
enable_mode_control = true;
"Manual performance control."
}
_ => unreachable!(),
};
self.description_label.set_text(text);
self.mode_drop_down.set_sensitive(enable_mode_control);
self.manual_info_button.set_visible(!enable_mode_control);
self.mode_drop_down.set_hexpand(enable_mode_control);
}
pub fn show(&self) {
self.container.set_visible(true);
}
pub fn hide(&self) {
self.container.set_visible(false);
}
pub fn get_visibility(&self) -> bool {
self.container.get_visible()
}
}

View File

@@ -1,86 +0,0 @@
use crate::app::root_stack::section_box;
use gtk::prelude::*;
use gtk::*;
use lact_client::schema::PerformanceLevel;
#[derive(Clone)]
pub struct PerformanceLevelFrame {
pub container: Box,
combo_box: ComboBoxText,
}
impl PerformanceLevelFrame {
pub fn new() -> Self {
let container = section_box("Performance level");
let root_box = Box::new(Orientation::Horizontal, 5);
let combo_box = ComboBoxText::new();
combo_box.set_sensitive(false);
combo_box.append(None, "Automatic");
combo_box.append(None, "Highest clocks");
combo_box.append(None, "Lowest clocks");
root_box.append(&combo_box);
let description_label = Label::new(None);
root_box.append(&description_label);
{
combo_box.connect_changed(move |combobox| match combobox.active().unwrap() {
0 => description_label
.set_text("Automatically adjust GPU and VRAM clocks. (Default)"),
1 => description_label
.set_text("Always use the highest clockspeeds for GPU and VRAM."),
2 => description_label
.set_text("Always use the lowest clockspeeds for GPU and VRAM."),
_ => unreachable!(),
});
}
container.append(&root_box);
Self {
container,
combo_box,
}
}
pub fn set_active_profile(&self, level: PerformanceLevel) {
self.combo_box.set_sensitive(true);
match level {
PerformanceLevel::Auto => self.combo_box.set_active(Some(0)),
PerformanceLevel::High => self.combo_box.set_active(Some(1)),
PerformanceLevel::Low => self.combo_box.set_active(Some(2)),
PerformanceLevel::Manual => todo!(),
};
}
pub fn connect_power_profile_changed<F: Fn() + 'static>(&self, f: F) {
self.combo_box.connect_changed(move |_| {
f();
});
}
pub fn get_selected_performance_level(&self) -> PerformanceLevel {
match self.combo_box.active().unwrap() {
0 => PerformanceLevel::Auto,
1 => PerformanceLevel::High,
2 => PerformanceLevel::Low,
_ => unreachable!(),
}
}
pub fn show(&self) {
self.container.set_visible(true);
}
pub fn hide(&self) {
self.container.set_visible(false);
}
pub fn get_visibility(&self) -> bool {
self.container.get_visible()
}
}

View File

@@ -4,7 +4,7 @@ version = "0.3.0"
edition = "2021"
[dependencies]
amdgpu-sysfs = { version = "0.9.7", features = ["serde"] }
amdgpu-sysfs = { version = "0.10.0", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
indexmap = { version = "*", features = ["serde"] }

View File

@@ -7,7 +7,7 @@ mod tests;
pub use amdgpu_sysfs::{
gpu_handle::{
overdrive::{ClocksTable, ClocksTableGen, Range},
PerformanceLevel, PowerLevels,
power_profile_mode, PerformanceLevel, PowerLevels,
},
hw_mon::Temperature,
};

View File

@@ -17,6 +17,9 @@ pub enum Request<'a> {
DeviceClocksInfo {
id: &'a str,
},
DevicePowerProfileModes {
id: &'a str,
},
SetFanControl {
id: &'a str,
enabled: bool,
@@ -34,6 +37,10 @@ pub enum Request<'a> {
id: &'a str,
command: SetClocksCommand,
},
SetPowerProfileMode {
id: &'a str,
index: Option<u16>,
},
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]