fix: avoid freezing the UI when enabling/disabling overdrive (#300)

This commit is contained in:
Ilya Zlobintsev 2024-04-06 12:12:57 +03:00 committed by GitHub
parent d9553a91a1
commit 76e5c2bcc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 130 additions and 64 deletions

View File

@ -463,7 +463,7 @@ impl<'a> Handler {
.context("Failed to edit GPU config and set enabled power states") .context("Failed to edit GPU config and set enabled power states")
} }
pub fn generate_snapshot(&self) -> anyhow::Result<String> { pub async fn generate_snapshot(&self) -> anyhow::Result<String> {
let datetime = chrono::Local::now().format("%Y%m%d-%H%M%S"); let datetime = chrono::Local::now().format("%Y%m%d-%H%M%S");
let out_path = format!("/tmp/LACT-sysfs-snapshot-{datetime}.tar.gz"); let out_path = format!("/tmp/LACT-sysfs-snapshot-{datetime}.tar.gz");
@ -516,10 +516,12 @@ impl<'a> Handler {
} }
let system_info = system::info() let system_info = system::info()
.await
.ok() .ok()
.map(|info| serde_json::to_value(info).unwrap()); .map(|info| serde_json::to_value(info).unwrap());
let initramfs_type = match OS_RELEASE.as_ref() { let initramfs_type = match OS_RELEASE.as_ref() {
Ok(os_release) => detect_initramfs_type(os_release) Ok(os_release) => detect_initramfs_type(os_release)
.await
.map(|initramfs_type| serde_json::to_value(initramfs_type).unwrap()), .map(|initramfs_type| serde_json::to_value(initramfs_type).unwrap()),
Err(err) => Some(err.to_string().into()), Err(err) => Some(err.to_string().into()),
}; };

View File

@ -78,7 +78,7 @@ pub async fn handle_stream(stream: UnixStream, handler: Handler) -> anyhow::Resu
async fn handle_request<'a>(request: Request<'a>, handler: &'a Handler) -> anyhow::Result<Vec<u8>> { async fn handle_request<'a>(request: Request<'a>, handler: &'a Handler) -> anyhow::Result<Vec<u8>> {
match request { match request {
Request::Ping => ok_response(ping()), Request::Ping => ok_response(ping()),
Request::SystemInfo => ok_response(system::info()?), Request::SystemInfo => ok_response(system::info().await?),
Request::ListDevices => ok_response(handler.list_devices()), Request::ListDevices => ok_response(handler.list_devices()),
Request::DeviceInfo { id } => ok_response(handler.get_device_info(id)?), Request::DeviceInfo { id } => ok_response(handler.get_device_info(id)?),
Request::DeviceStats { id } => ok_response(handler.get_gpu_stats(id)?), Request::DeviceStats { id } => ok_response(handler.get_gpu_stats(id)?),
@ -106,9 +106,9 @@ async fn handle_request<'a>(request: Request<'a>, handler: &'a Handler) -> anyho
Request::SetEnabledPowerStates { id, kind, states } => { Request::SetEnabledPowerStates { id, kind, states } => {
ok_response(handler.set_enabled_power_states(id, kind, states).await?) ok_response(handler.set_enabled_power_states(id, kind, states).await?)
} }
Request::EnableOverdrive => ok_response(system::enable_overdrive()?), Request::EnableOverdrive => ok_response(system::enable_overdrive().await?),
Request::DisableOverdrive => ok_response(system::disable_overdrive()?), Request::DisableOverdrive => ok_response(system::disable_overdrive().await?),
Request::GenerateSnapshot => ok_response(handler.generate_snapshot()?), Request::GenerateSnapshot => ok_response(handler.generate_snapshot().await?),
Request::ConfirmPendingConfig(command) => { Request::ConfirmPendingConfig(command) => {
ok_response(handler.confirm_pending_config(command)?) ok_response(handler.confirm_pending_config(command)?)
} }

View File

@ -6,15 +6,15 @@ use std::{
io::Write, io::Write,
os::unix::prelude::PermissionsExt, os::unix::prelude::PermissionsExt,
path::Path, path::Path,
process::Command,
}; };
use tokio::process::Command;
use tracing::{info, warn}; use tracing::{info, warn};
const PP_OVERDRIVE_MASK: u64 = 0x4000; const PP_OVERDRIVE_MASK: u64 = 0x4000;
pub const PP_FEATURE_MASK_PATH: &str = "/sys/module/amdgpu/parameters/ppfeaturemask"; pub const PP_FEATURE_MASK_PATH: &str = "/sys/module/amdgpu/parameters/ppfeaturemask";
pub const MODULE_CONF_PATH: &str = "/etc/modprobe.d/99-amdgpu-overdrive.conf"; pub const MODULE_CONF_PATH: &str = "/etc/modprobe.d/99-amdgpu-overdrive.conf";
pub fn info() -> anyhow::Result<SystemInfo<'static>> { pub async fn info() -> anyhow::Result<SystemInfo<'static>> {
let version = env!("CARGO_PKG_VERSION"); let version = env!("CARGO_PKG_VERSION");
let profile = if cfg!(debug_assertions) { let profile = if cfg!(debug_assertions) {
"debug" "debug"
@ -24,6 +24,7 @@ pub fn info() -> anyhow::Result<SystemInfo<'static>> {
let kernel_output = Command::new("uname") let kernel_output = Command::new("uname")
.arg("-r") .arg("-r")
.output() .output()
.await
.context("Could not read kernel version")?; .context("Could not read kernel version")?;
let kernel_version = String::from_utf8(kernel_output.stdout) let kernel_version = String::from_utf8(kernel_output.stdout)
.context("Invalid kernel version output")? .context("Invalid kernel version output")?
@ -45,7 +46,7 @@ pub fn info() -> anyhow::Result<SystemInfo<'static>> {
}) })
} }
pub fn enable_overdrive() -> anyhow::Result<String> { pub async fn enable_overdrive() -> anyhow::Result<String> {
let current_mask = read_current_mask()?; let current_mask = read_current_mask()?;
let new_mask = current_mask | PP_OVERDRIVE_MASK; let new_mask = current_mask | PP_OVERDRIVE_MASK;
@ -62,7 +63,7 @@ pub fn enable_overdrive() -> anyhow::Result<String> {
file.write_all(conf.as_bytes()) file.write_all(conf.as_bytes())
.context("Could not write config")?; .context("Could not write config")?;
let message = match regenerate_initramfs() { let message = match regenerate_initramfs().await {
Ok(initramfs_type) => { Ok(initramfs_type) => {
format!("Initramfs was successfully regenerated (detected type {initramfs_type:?})") format!("Initramfs was successfully regenerated (detected type {initramfs_type:?})")
} }
@ -72,10 +73,10 @@ pub fn enable_overdrive() -> anyhow::Result<String> {
Ok(message) Ok(message)
} }
pub fn disable_overdrive() -> anyhow::Result<String> { pub async fn disable_overdrive() -> anyhow::Result<String> {
if Path::new(MODULE_CONF_PATH).exists() { if Path::new(MODULE_CONF_PATH).exists() {
fs::remove_file(MODULE_CONF_PATH).context("Could not remove module config file")?; fs::remove_file(MODULE_CONF_PATH).context("Could not remove module config file")?;
match regenerate_initramfs() { match regenerate_initramfs().await {
Ok(initramfs_type) => Ok(format!( Ok(initramfs_type) => Ok(format!(
"Initramfs was successfully regenerated (detected type {initramfs_type:?})" "Initramfs was successfully regenerated (detected type {initramfs_type:?})"
)), )),
@ -98,15 +99,17 @@ fn read_current_mask() -> anyhow::Result<u64> {
u64::from_str_radix(ppfeaturemask, 16).context("Invalid ppfeaturemask") u64::from_str_radix(ppfeaturemask, 16).context("Invalid ppfeaturemask")
} }
fn regenerate_initramfs() -> anyhow::Result<InitramfsType> { async fn regenerate_initramfs() -> anyhow::Result<InitramfsType> {
let os_release = OS_RELEASE.as_ref().context("Could not detect distro")?; let os_release = OS_RELEASE.as_ref().context("Could not detect distro")?;
match detect_initramfs_type(os_release) { match detect_initramfs_type(os_release).await {
Some(initramfs_type) => { Some(initramfs_type) => {
info!("Detected initramfs type {initramfs_type:?}, regenerating"); info!("Detected initramfs type {initramfs_type:?}, regenerating");
let result = match initramfs_type { let result = match initramfs_type {
InitramfsType::Debian => run_command("update-initramfs", &["-u"]), InitramfsType::Debian => run_command("update-initramfs", &["-u"]).await,
InitramfsType::Mkinitcpio => run_command("mkinitcpio", &["-P"]), InitramfsType::Mkinitcpio => run_command("mkinitcpio", &["-P"]).await,
InitramfsType::Dracut => run_command("dracut", &["--regenerate-all", "--force"]), InitramfsType::Dracut => {
run_command("dracut", &["--regenerate-all", "--force"]).await
}
}; };
result.map(|()| initramfs_type) result.map(|()| initramfs_type)
} }
@ -116,13 +119,18 @@ fn regenerate_initramfs() -> anyhow::Result<InitramfsType> {
} }
} }
pub(crate) fn detect_initramfs_type(os_release: &OsRelease) -> Option<InitramfsType> { pub(crate) async fn detect_initramfs_type(os_release: &OsRelease) -> Option<InitramfsType> {
let id_like: Vec<_> = os_release.id_like.split_whitespace().collect(); let id_like: Vec<_> = os_release.id_like.split_whitespace().collect();
if os_release.id == "debian" || id_like.contains(&"debian") { if os_release.id == "debian" || id_like.contains(&"debian") {
Some(InitramfsType::Debian) Some(InitramfsType::Debian)
} else if os_release.id == "arch" || id_like.contains(&"arch") { } else if os_release.id == "arch" || id_like.contains(&"arch") {
if Command::new("mkinitcpio").arg("--version").output().is_ok() { if Command::new("mkinitcpio")
.arg("--version")
.output()
.await
.is_ok()
{
Some(InitramfsType::Mkinitcpio) Some(InitramfsType::Mkinitcpio)
} else { } else {
warn!( warn!(
@ -131,7 +139,12 @@ pub(crate) fn detect_initramfs_type(os_release: &OsRelease) -> Option<InitramfsT
None None
} }
} else if os_release.id == "fedora" { } else if os_release.id == "fedora" {
if Command::new("dracut").arg("--version").output().is_ok() { if Command::new("dracut")
.arg("--version")
.output()
.await
.is_ok()
{
Some(InitramfsType::Dracut) Some(InitramfsType::Dracut)
} else { } else {
warn!("Fedora without dracut detected, refusing to regenerate initramfs"); warn!("Fedora without dracut detected, refusing to regenerate initramfs");
@ -142,11 +155,12 @@ pub(crate) fn detect_initramfs_type(os_release: &OsRelease) -> Option<InitramfsT
} }
} }
fn run_command(exec: &str, args: &[&str]) -> anyhow::Result<()> { async fn run_command(exec: &str, args: &[&str]) -> anyhow::Result<()> {
info!("Running {exec} with args {args:?}"); info!("Running {exec} with args {args:?}");
let output = Command::new(exec) let output = Command::new(exec)
.args(args) .args(args)
.output() .output()
.await
.context("Could not run command")?; .context("Could not run command")?;
if output.status.success() { if output.status.success() {
Ok(()) Ok(())
@ -163,8 +177,8 @@ mod tests {
use lact_schema::InitramfsType; use lact_schema::InitramfsType;
use os_release::OsRelease; use os_release::OsRelease;
#[test] #[tokio::test]
fn detect_initramfs_debian() { async fn detect_initramfs_debian() {
let data = r#" let data = r#"
PRETTY_NAME="Debian GNU/Linux trixie/sid" PRETTY_NAME="Debian GNU/Linux trixie/sid"
NAME="Debian GNU/Linux" NAME="Debian GNU/Linux"
@ -177,12 +191,12 @@ BUG_REPORT_URL="https://bugs.debian.org/"
let os_release: OsRelease = data.lines().map(str::to_owned).collect(); let os_release: OsRelease = data.lines().map(str::to_owned).collect();
assert_eq!( assert_eq!(
Some(InitramfsType::Debian), Some(InitramfsType::Debian),
detect_initramfs_type(&os_release) detect_initramfs_type(&os_release).await
); );
} }
#[test] #[tokio::test]
fn detect_initramfs_mint() { async fn detect_initramfs_mint() {
let data = r#" let data = r#"
NAME="Linux Mint" NAME="Linux Mint"
VERSION="21.2 (Victoria)" VERSION="21.2 (Victoria)"
@ -200,7 +214,7 @@ UBUNTU_CODENAME=jammy
let os_release: OsRelease = data.lines().map(str::to_owned).collect(); let os_release: OsRelease = data.lines().map(str::to_owned).collect();
assert_eq!( assert_eq!(
Some(InitramfsType::Debian), Some(InitramfsType::Debian),
detect_initramfs_type(&os_release) detect_initramfs_type(&os_release).await
); );
} }
} }

View File

@ -4,7 +4,7 @@ mod info_row;
mod page_section; mod page_section;
mod root_stack; mod root_stack;
use crate::{APP_ID, GUI_VERSION}; use crate::{create_connection, APP_ID, GUI_VERSION};
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use apply_revealer::ApplyRevealer; use apply_revealer::ApplyRevealer;
use glib::clone; use glib::clone;
@ -536,7 +536,7 @@ impl App {
} }
fn enable_overclocking(&self) { fn enable_overclocking(&self) {
let text = format!("This will enable the overdrive feature of the amdgpu driver by creating a file at <b>{MODULE_CONF_PATH}</b> and updating the initramfs. Are you sure you want to do this? (Note: the GUI may freeze for a bit)"); let text = format!("This will enable the overdrive feature of the amdgpu driver by creating a file at <b>{MODULE_CONF_PATH}</b> and updating the initramfs. Are you sure you want to do this?");
let dialog = MessageDialog::builder() let dialog = MessageDialog::builder()
.title("Enable Overclocking") .title("Enable Overclocking")
.use_markup(true) .use_markup(true)
@ -548,7 +548,19 @@ impl App {
dialog.run_async(clone!(@strong self as app => move |diag, response| { dialog.run_async(clone!(@strong self as app => move |diag, response| {
if response == ResponseType::Ok { if response == ResponseType::Ok {
match app.daemon_client.enable_overdrive().and_then(|buffer| buffer.inner()) { let handle = gio::spawn_blocking(|| {
let (daemon_client, _) = create_connection().expect("Could not create new daemon connection");
daemon_client.enable_overdrive().and_then(|buffer| buffer.inner())
});
let dialog = app.spinner_dialog("Turning overclocking on (this may take a while)");
dialog.show();
glib::spawn_future_local(async move {
let result = handle.await.unwrap();
dialog.hide();
match result {
Ok(msg) => { Ok(msg) => {
let success_dialog = MessageDialog::builder() let success_dialog = MessageDialog::builder()
.title("Success") .title("Success")
@ -564,6 +576,7 @@ impl App {
show_error(&app.window, err); show_error(&app.window, err);
} }
} }
});
} }
diag.hide(); diag.hide();
})); }));
@ -573,7 +586,7 @@ impl App {
let dialog = MessageDialog::builder() let dialog = MessageDialog::builder()
.title("Disable Overclocking") .title("Disable Overclocking")
.use_markup(true) .use_markup(true)
.text("The overclocking functionality in the driver will now be turned off. (Note: the LACT window might hang)") .text("The overclocking functionality in the driver will now be turned off.")
.message_type(MessageType::Info) .message_type(MessageType::Info)
.buttons(ButtonsType::Ok) .buttons(ButtonsType::Ok)
.transient_for(&self.window) .transient_for(&self.window)
@ -581,7 +594,20 @@ impl App {
dialog.run_async(clone!(@strong self as app => move |diag, _| { dialog.run_async(clone!(@strong self as app => move |diag, _| {
diag.hide(); diag.hide();
match app.daemon_client.disable_overdrive().and_then(|buffer| buffer.inner()) {
let handle = gio::spawn_blocking(|| {
let (daemon_client, _) = create_connection().expect("Could not create new daemon connection");
daemon_client.disable_overdrive().and_then(|buffer| buffer.inner())
});
let dialog = app.spinner_dialog("Turning overclocking off (this may take a while)");
dialog.show();
glib::spawn_future_local(async move {
let result = handle.await.unwrap();
dialog.hide();
match result {
Ok(msg) => { Ok(msg) => {
let success_dialog = MessageDialog::builder() let success_dialog = MessageDialog::builder()
.title("Success") .title("Success")
@ -597,6 +623,8 @@ impl App {
show_error(&app.window, err); show_error(&app.window, err);
} }
} }
});
})); }));
} }
@ -650,6 +678,25 @@ impl App {
app.set_initial(&gpu_id); app.set_initial(&gpu_id);
})); }));
} }
fn spinner_dialog(&self, title: &str) -> MessageDialog {
let spinner = gtk::Spinner::new();
spinner.start();
spinner.set_margin_top(10);
spinner.set_margin_bottom(10);
let dialog = MessageDialog::builder()
.title(title)
.child(&spinner)
.message_type(MessageType::Info)
.transient_for(&self.window)
.build();
dialog.titlebar().unwrap().set_margin_start(15);
dialog.titlebar().unwrap().set_margin_end(15);
dialog
}
} }
fn show_error(parent: &ApplicationWindow, err: anyhow::Error) { fn show_error(parent: &ApplicationWindow, err: anyhow::Error) {

View File

@ -4,7 +4,7 @@ use anyhow::{anyhow, Context};
use app::App; use app::App;
use lact_client::{schema::args::GuiArgs, DaemonClient}; use lact_client::{schema::args::GuiArgs, DaemonClient};
use std::os::unix::net::UnixStream; use std::os::unix::net::UnixStream;
use tracing::{error, info, metadata::LevelFilter}; use tracing::{debug, error, info, metadata::LevelFilter};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
const GUI_VERSION: &str = env!("CARGO_PKG_VERSION"); const GUI_VERSION: &str = env!("CARGO_PKG_VERSION");
@ -29,7 +29,10 @@ pub fn run(args: GuiArgs) -> anyhow::Result<()> {
fn create_connection() -> anyhow::Result<(DaemonClient, Option<anyhow::Error>)> { fn create_connection() -> anyhow::Result<(DaemonClient, Option<anyhow::Error>)> {
match DaemonClient::connect() { match DaemonClient::connect() {
Ok(connection) => Ok((connection, None)), Ok(connection) => {
debug!("Established daemon connection");
Ok((connection, None))
}
Err(err) => { Err(err) => {
info!("could not connect to socket: {err:#}"); info!("could not connect to socket: {err:#}");
info!("using a local daemon"); info!("using a local daemon");