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")
}
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 out_path = format!("/tmp/LACT-sysfs-snapshot-{datetime}.tar.gz");
@ -516,10 +516,12 @@ impl<'a> Handler {
}
let system_info = system::info()
.await
.ok()
.map(|info| serde_json::to_value(info).unwrap());
let initramfs_type = match OS_RELEASE.as_ref() {
Ok(os_release) => detect_initramfs_type(os_release)
.await
.map(|initramfs_type| serde_json::to_value(initramfs_type).unwrap()),
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>> {
match request {
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::DeviceInfo { id } => ok_response(handler.get_device_info(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 } => {
ok_response(handler.set_enabled_power_states(id, kind, states).await?)
}
Request::EnableOverdrive => ok_response(system::enable_overdrive()?),
Request::DisableOverdrive => ok_response(system::disable_overdrive()?),
Request::GenerateSnapshot => ok_response(handler.generate_snapshot()?),
Request::EnableOverdrive => ok_response(system::enable_overdrive().await?),
Request::DisableOverdrive => ok_response(system::disable_overdrive().await?),
Request::GenerateSnapshot => ok_response(handler.generate_snapshot().await?),
Request::ConfirmPendingConfig(command) => {
ok_response(handler.confirm_pending_config(command)?)
}

View File

@ -6,15 +6,15 @@ use std::{
io::Write,
os::unix::prelude::PermissionsExt,
path::Path,
process::Command,
};
use tokio::process::Command;
use tracing::{info, warn};
const PP_OVERDRIVE_MASK: u64 = 0x4000;
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 fn info() -> anyhow::Result<SystemInfo<'static>> {
pub async fn info() -> anyhow::Result<SystemInfo<'static>> {
let version = env!("CARGO_PKG_VERSION");
let profile = if cfg!(debug_assertions) {
"debug"
@ -24,6 +24,7 @@ pub fn info() -> anyhow::Result<SystemInfo<'static>> {
let kernel_output = Command::new("uname")
.arg("-r")
.output()
.await
.context("Could not read kernel version")?;
let kernel_version = String::from_utf8(kernel_output.stdout)
.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 new_mask = current_mask | PP_OVERDRIVE_MASK;
@ -62,7 +63,7 @@ pub fn enable_overdrive() -> anyhow::Result<String> {
file.write_all(conf.as_bytes())
.context("Could not write config")?;
let message = match regenerate_initramfs() {
let message = match regenerate_initramfs().await {
Ok(initramfs_type) => {
format!("Initramfs was successfully regenerated (detected type {initramfs_type:?})")
}
@ -72,10 +73,10 @@ pub fn enable_overdrive() -> anyhow::Result<String> {
Ok(message)
}
pub fn disable_overdrive() -> anyhow::Result<String> {
pub async fn disable_overdrive() -> anyhow::Result<String> {
if Path::new(MODULE_CONF_PATH).exists() {
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!(
"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")
}
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")?;
match detect_initramfs_type(os_release) {
match detect_initramfs_type(os_release).await {
Some(initramfs_type) => {
info!("Detected initramfs type {initramfs_type:?}, regenerating");
let result = match initramfs_type {
InitramfsType::Debian => run_command("update-initramfs", &["-u"]),
InitramfsType::Mkinitcpio => run_command("mkinitcpio", &["-P"]),
InitramfsType::Dracut => run_command("dracut", &["--regenerate-all", "--force"]),
InitramfsType::Debian => run_command("update-initramfs", &["-u"]).await,
InitramfsType::Mkinitcpio => run_command("mkinitcpio", &["-P"]).await,
InitramfsType::Dracut => {
run_command("dracut", &["--regenerate-all", "--force"]).await
}
};
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();
if os_release.id == "debian" || id_like.contains(&"debian") {
Some(InitramfsType::Debian)
} 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)
} else {
warn!(
@ -131,7 +139,12 @@ pub(crate) fn detect_initramfs_type(os_release: &OsRelease) -> Option<InitramfsT
None
}
} 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)
} else {
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:?}");
let output = Command::new(exec)
.args(args)
.output()
.await
.context("Could not run command")?;
if output.status.success() {
Ok(())
@ -163,8 +177,8 @@ mod tests {
use lact_schema::InitramfsType;
use os_release::OsRelease;
#[test]
fn detect_initramfs_debian() {
#[tokio::test]
async fn detect_initramfs_debian() {
let data = r#"
PRETTY_NAME="Debian GNU/Linux trixie/sid"
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();
assert_eq!(
Some(InitramfsType::Debian),
detect_initramfs_type(&os_release)
detect_initramfs_type(&os_release).await
);
}
#[test]
fn detect_initramfs_mint() {
#[tokio::test]
async fn detect_initramfs_mint() {
let data = r#"
NAME="Linux Mint"
VERSION="21.2 (Victoria)"
@ -200,7 +214,7 @@ UBUNTU_CODENAME=jammy
let os_release: OsRelease = data.lines().map(str::to_owned).collect();
assert_eq!(
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 root_stack;
use crate::{APP_ID, GUI_VERSION};
use crate::{create_connection, APP_ID, GUI_VERSION};
use anyhow::{anyhow, Context};
use apply_revealer::ApplyRevealer;
use glib::clone;
@ -536,7 +536,7 @@ impl App {
}
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()
.title("Enable Overclocking")
.use_markup(true)
@ -548,11 +548,70 @@ impl App {
dialog.run_async(clone!(@strong self as app => move |diag, response| {
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) => {
let success_dialog = MessageDialog::builder()
.title("Success")
.text(format!("Overclocking successfully enabled. A system reboot is required to apply the changes.\nSystem message: {msg}"))
.message_type(MessageType::Info)
.buttons(ButtonsType::Ok)
.build();
success_dialog.run_async(move |diag, _| {
diag.hide();
});
}
Err(err) => {
show_error(&app.window, err);
}
}
});
}
diag.hide();
}));
}
fn disable_overclocking(&self) {
let dialog = MessageDialog::builder()
.title("Disable Overclocking")
.use_markup(true)
.text("The overclocking functionality in the driver will now be turned off.")
.message_type(MessageType::Info)
.buttons(ButtonsType::Ok)
.transient_for(&self.window)
.build();
dialog.run_async(clone!(@strong self as app => move |diag, _| {
diag.hide();
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) => {
let success_dialog = MessageDialog::builder()
.title("Success")
.text(format!("Overclocking successfully enabled. A system reboot is required to apply the changes.\nSystem message: {msg}"))
.text(format!("Overclocking successfully disabled. A system reboot is required to apply the changes.\nSystem message: {msg}"))
.message_type(MessageType::Info)
.buttons(ButtonsType::Ok)
.build();
@ -564,39 +623,8 @@ impl App {
show_error(&app.window, err);
}
}
}
diag.hide();
}));
}
});
fn disable_overclocking(&self) {
let dialog = MessageDialog::builder()
.title("Disable Overclocking")
.use_markup(true)
.text("The overclocking functionality in the driver will now be turned off. (Note: the LACT window might hang)")
.message_type(MessageType::Info)
.buttons(ButtonsType::Ok)
.transient_for(&self.window)
.build();
dialog.run_async(clone!(@strong self as app => move |diag, _| {
diag.hide();
match app.daemon_client.disable_overdrive().and_then(|buffer| buffer.inner()) {
Ok(msg) => {
let success_dialog = MessageDialog::builder()
.title("Success")
.text(format!("Overclocking successfully disabled. A system reboot is required to apply the changes.\nSystem message: {msg}"))
.message_type(MessageType::Info)
.buttons(ButtonsType::Ok)
.build();
success_dialog.run_async(move |diag, _| {
diag.hide();
});
}
Err(err) => {
show_error(&app.window, err);
}
}
}));
}
@ -650,6 +678,25 @@ impl App {
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) {

View File

@ -4,7 +4,7 @@ use anyhow::{anyhow, Context};
use app::App;
use lact_client::{schema::args::GuiArgs, DaemonClient};
use std::os::unix::net::UnixStream;
use tracing::{error, info, metadata::LevelFilter};
use tracing::{debug, error, info, metadata::LevelFilter};
use tracing_subscriber::EnvFilter;
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>)> {
match DaemonClient::connect() {
Ok(connection) => Ok((connection, None)),
Ok(connection) => {
debug!("Established daemon connection");
Ok((connection, None))
}
Err(err) => {
info!("could not connect to socket: {err:#}");
info!("using a local daemon");