Merge branch 'master' into feature/intel

This commit is contained in:
Ilya Zlobintsev 2025-01-01 09:23:28 +02:00
commit 0f42435a0a
12 changed files with 386 additions and 70 deletions

49
API.md
View File

@ -1,49 +0,0 @@
# Description
The LACT Daemon exposes a JSON API over a unix socket or TCP, available on `/var/run/lactd.sock` or an arbitrary TCP port. You can configure who has access to the unix socket in `/etc/lact/config.yaml` in the `daemon.admin_groups` field. The TCP listener is disabled by default for security reasons, see [this README section](./README.md#remote-management) for how to enable it.
The API expects newline-separated JSON objects, and returns a JSON object for every request.
The general format of requests looks like:
```
{"command": "command_name", "args": {}}
```
Note that the type of `args` depends on the specific request, and may be ommited in some cases.
The response looks like this:
```
{"status": "ok|error", "data": {}}
```
Same as `args` in requests, `data` can be of a different type and may not be present depending on the specific request.
You can try sending commands to socket interactively with `ncat`:
```
echo '{"command": "list_devices"}' | ncat -U /run/lactd.sock
```
Example response:
```
{"status":"ok","data":[{"id":"1002:687F-1043:0555-0000:0b:00.0","name":"Vega 10 XL/XT [Radeon RX Vega 56/64]"}]}
```
Here's an example of calling the API with arguments to change a profile:
```
echo '{"command": "set_profile", "args": {"name":"name-of-the-profile"}}' | ncat -U /run/lactd.sock
```
In this code, `name-of-the-profile` should be replaced with the name of a profile that you've already created in LACT.
# Commands
For the full list of available commands and responses, you can look at the source code of the schema: [requests](lact-schema/src/request.rs), [the basic response structure](lact-schema/src/response.rs) and [all possible types](lact-schema/src/lib.rs).
It should also be fairly easy to figure out the API by trial and error, as the error message are quite verbose:
```
echo '{"command": "test"}' | ncat -U /run/lactd.sock
{"status":"error","data":"Failed to deserialize request: unknown variant `test`, expected one of `ping`, `list_devices`, `system_info`, `device_info`, `device_stats`, `device_clocks_info`, `set_fan_control`, `set_power_cap`, `set_performance_level`, `set_clocks_value` at line 1 column 18"}
```
# Rust
If you want to connect to the socket from a Rust program, you can simply import either the `lact-client` or `lact-schema` (if you want to write a custom client) crates from this repository.

1
API.md Symbolic link
View File

@ -0,0 +1 @@
docs/API.md

View File

@ -15,7 +15,7 @@ Current features:
- Viewing information about the GPU
- Power and thermals monitoring, power limit configuration
- Fan curve control
- Overclocking (GPU/VRAM clockspeed and voltage, currently AMD only)
- Overclocking (GPU/VRAM clockspeed and voltage)
- Power states configuration (AMD only)
Both AMD and Nvidia functionality works on X11, Wayland or even headless sessions.
@ -75,7 +75,7 @@ However the following table shows what functionality can be expected for a given
| Vega | Supported | Supported | Supported | Supported | |
| RDNA1 (RX 5000) | Supported | Supported | Supported | Supported | |
| RDNA2 (RX 6000) | Supported | Supported | Supported | Supported | |
| RDNA3 (RX 7000) | Supported | Limited | Supported | Limited | Fan zero RPM mode is enabled by default even with a custom fan curve, and requires kernel 6.13 (`linux-next` when writing this) to be disabled. The power cap is sometimes reported lower than it should be. See [#255](https://github.com/ilya-zlobintsev/LACT/issues/255) for more info. |
| RDNA3 (RX 7000) | Supported | Limited | Supported | Limited | Fan zero RPM mode is enabled by default even with a custom fan curve, and requires kernel 6.13 to be disabled. The power cap is sometimes reported lower than it should be. See [#255](https://github.com/ilya-zlobintsev/LACT/issues/255) for more info. |
GPUs not listed here will still work, but might not have full functionality available.
Monitoring/system info will be available everywhere. Integrated GPUs might also only have basic configuration available.
@ -88,6 +88,8 @@ Anything Maxwell or newer should work, but generation support has not yet been t
There is a configuration file available in `/etc/lact/config.yaml`. Most of the settings are accessible through the GUI, but some of them may be useful to be edited manually (like `admin_groups` to specify who has access to the daemon)
See [CONFIG.md](./docs/CONFIG.md) for more information.
**Socket permissions setup:**
By default, LACT uses either ether the `wheel` or `sudo` group (whichever is available) for the ownership of the unix socket that the GUI needs to connect to.

49
docs/API.md Normal file
View File

@ -0,0 +1,49 @@
# Description
The LACT Daemon exposes a JSON API over a unix socket or TCP, available on `/var/run/lactd.sock` or an arbitrary TCP port. You can configure who has access to the unix socket in `/etc/lact/config.yaml` in the `daemon.admin_groups` field. The TCP listener is disabled by default for security reasons, see [this README section](./README.md#remote-management) for how to enable it.
The API expects newline-separated JSON objects, and returns a JSON object for every request.
The general format of requests looks like:
```
{"command": "command_name", "args": {}}
```
Note that the type of `args` depends on the specific request, and may be ommited in some cases.
The response looks like this:
```
{"status": "ok|error", "data": {}}
```
Same as `args` in requests, `data` can be of a different type and may not be present depending on the specific request.
You can try sending commands to socket interactively with `ncat`:
```
echo '{"command": "list_devices"}' | ncat -U /run/lactd.sock
```
Example response:
```
{"status":"ok","data":[{"id":"1002:687F-1043:0555-0000:0b:00.0","name":"Vega 10 XL/XT [Radeon RX Vega 56/64]"}]}
```
Here's an example of calling the API with arguments to change a profile:
```
echo '{"command": "set_profile", "args": {"name":"name-of-the-profile"}}' | ncat -U /run/lactd.sock
```
In this code, `name-of-the-profile` should be replaced with the name of a profile that you've already created in LACT.
# Commands
For the full list of available commands and responses, you can look at the source code of the schema: [requests](lact-schema/src/request.rs), [the basic response structure](lact-schema/src/response.rs) and [all possible types](lact-schema/src/lib.rs).
It should also be fairly easy to figure out the API by trial and error, as the error message are quite verbose:
```
echo '{"command": "test"}' | ncat -U /run/lactd.sock
{"status":"error","data":"Failed to deserialize request: unknown variant `test`, expected one of `ping`, `list_devices`, `system_info`, `device_info`, `device_stats`, `device_clocks_info`, `set_fan_control`, `set_power_cap`, `set_performance_level`, `set_clocks_value` at line 1 column 18"}
```
# Rust
If you want to connect to the socket from a Rust program, you can simply import either the `lact-client` or `lact-schema` (if you want to write a custom client) crates from this repository.

192
docs/CONFIG.md Normal file
View File

@ -0,0 +1,192 @@
# Configuration
The LACT config file is located in `/etc/lact/config.yaml`, and contains all of the GPU settings that are typically edited in the GUI, as well as a few settings specifying the behaviour of the daemon.
LACT listens for config file changes and reloads all GPU settings automatically, but daemon-related settings such as the logging level or permissions require a service restart (`systemctl restart lactd`).
Full config file with all possible options:
```yaml
# WARNING: this is only an example of each possible setting. DO NOT COPY THIS CONFIG AS IS.
# Many options don't make sense to be used together, and depend on your hardware.
daemon:
# The logging level of the daemon.
# Possible values: `error`, `warn`, `info` (default), `debug`, `trace`
log_level: info
# User groups who should have access to the daemon.
# WARNING: only the first group from this list that is found on the system is used!
# This is made a list and not a single value to allow this config to work across
# different distros, which might have different groups for an "admin" user.
admin_groups:
- wheel
- sudo
# If set to `true`, this setting makes the LACT daemon not reset
# GPU clocks when changing other settings or when turning off the daemon.
# Can be used to work around a few very specific issues with
# some settings not applying on AMD GPUs.
disable_clocks_cleanup: false
# Daemon's TCP listening address. Not specified by default.
# By default TCP access is disabled, and only a unix socket is present.
# Specifying this option enables the TCP listener.
tcp_listen_address: 127.0.0.1:12853
# Period in seconds for how long settings should wait to be confirmed.
# Most GPU setting change commands require a confirmation command to be used
# in order to save these settings to the config.
# If a confirm command is not issued within the configured period (default: 5 seconds)
# the setting will be reverted.
apply_settings_timer: 5
# The main GPU configuration map, containing the list of GPUs and their settings.
gpus:
# A GPU config entry. This is the ID of the GPU.
# The ID is formed with a combination of a PCI device id,
# PCI subsystem id and PCI slot name to uniquely identify
# each GPU in the system, even if there are multiple of the same model.
# You can discover the id of your GPU by either:
# - Changing a setting in the UI, so it's written to the config
# - Using `lact cli list-gpus`
1002:687F-1043:0555-0000:0b:00.0:
# Whether the daemon should touch fan control settings at all.
# Setting this to `true` requires the `fan_control_settings` field to be present as well.
fan_control_enabled: true
fan_control_settings:
# Fan control mode. Can be either `curve` or `static`
mode: curve
# Static fan speed from 0 to 1. Used when `mode` is `static`
static_speed: 1.0
# The temperature sensor name to be used with a custom fan curve.
# This can be used to base the fan curve off the`junction` (hotspot)
# temperature instead of the default overall ("edge") tempreature.
# Applicable on most Vega and newer AMD GPUs.
temperature_key: edge
# Interval in milliseconds for how often the GPU temperature should be checked
# when adjusting the fan curve.
interval_ms: 500
# Custom fan curve used with `mode` set to `curve`.
# The format of the map is temperature to fan speed from 0 to 1.
# Note: on RDNA3+ AMD GPUs this must have 5 entries.
curve:
40: 0.2
50: 0.35
60: 0.5
70: 0.75
80: 1.0
# Hysteresis setting: when spinning down fans after a temperature drop,
# the target speed needs to be lower for at least this many milliseconds
# for the fan to actually slow down.
# This lets you avoid fan speed jumping around during short drops of load
# (e.g. loading screen in a game).
spindown_delay_ms: 0
# Hysteresis setting: the minimum temperature change in degrees
# to affect the fan speed. Also used to avoid rapid fan speed changes
# when the temperature only changes e.g. 1 degree.
change_threshold: 0
# Power management firmware options. Specific to RDNA3+ AMD GPUs.
# Most of these settings are only applied when not using a custom fan curve.
pmfw_options:
# This setting adjusts the PMFWs behavior about the maximum speed in RPM the fan can spin.
acoustic_limit: 3200
# This setting adjusts the PMFWs behavior about the maximum speed in RPM the fan can spin
# when the temperature is not greater than target temperature.
acoustic_target: 1450
# The minimum speed in RPM that the fan can spin at.
minimum_pwm: 15
# Target temperature for the GPU in degrees.
# Paring with the acoustic_target setting, they define the maximum speed in RPM
# the fan can spin when the temperature is not greater than target temperature.
target_temperature: 83
# When set to `true`, allows the fan to be turned turned off when below the
# `zero_rpm_threshold` temperature value.
zero_rpm: true
# Temperature in degrees below which the fan should be turned off when `zero_rpm` is set to true.
zero_rpm_threshold: 50
# Power limit in watts.
power_cap: 320.0
# Performance level option for AMD GPUs.
# Can be `auto`, `low`, `high` or `manual`.
performance_level: auto
# Index of an AMD power profile mode.
# Setting this requires `performance_level` to be set to `manual`.
power_profile_mode_index: 0
# Custom heuristic values when using the custom AMD power profile mode.
# The meaning of these values, their format and count depend on the specific GPU model.
# Check the names of these values in the UI.
custom_power_profile_mode_hueristics:
- - 0
- 5
- 1
- 0
- 4
- 800
- 4587520
- -65536
- 0
- - 0
- 5
- 1
- 0
- 1
- 0
- 3276800
- -65536
- -6553
- - 0
- 5
- 1
- 0
- 4
- 800
- 327680
- -65536
- 0
# List of AMD power states which should be enabled
power_states:
# GPU power states
core_clock:
- 0
- 2
- 3
# VRAM power states
memory_clock:
- 0
- 1
# Minimum GPU clockspeed in MHz.
min_core_clock: 300
# Minimum VRAM clockspeed in MHz.
min_memory_clock: 500
# Minimum GPU voltage in mV.
min_voltage: 900
# Maximum GPU clockspeed in MHz.
max_core_clock: 1630
# Maximum VRAM clockspeed in MHz.
max_memory_clock: 800
# Maximum GPU voltage in mV.
max_voltage: 1200
# Voltage offset value in mV for RDNA and newer AMD GPUs.
voltage_offset: 0
# Settings profiles
profiles:
# Name of the profile
vkcube:
# GPU settings in this profile.
# It is the same config format that is used for the top-level `gpus` option.
gpus: {}
# Profile activation rule for when this profile shoule be activated
# when using automatic profile switching.
rule:
# Type of the rule. Can be either `process or `gamemode`.
type: process
# Process filter. This is not required when using the gamemode rule type.
filter:
# Name of the process.
name: vkcube
# Process arguments. Not required.
args: --my-arg
# Current profile to be used. Does not have effect when `auto_switch_profiles` is used.
# Omit this option or set to `null` to use the default profile (settings in the top-level `gpus` entry).
current_profile: vkcube
# If profiles should be switched between automatically based on their configured rules.
auto_switch_profiles: true
```

View File

@ -51,7 +51,7 @@ copes = { git = "https://gitlab.com/corectrl/copes" }
divan = { workspace = true }
pretty_assertions = { workspace = true }
lact-daemon = { path = ".", features = ["bench"] }
insta = { version = "1.41.1", features = ["json"] }
insta = { version = "1.41.1", features = ["json", "yaml"] }
[build-dependencies]
bindgen = "0.68"

View File

@ -364,6 +364,7 @@ fn default_apply_settings_timer() -> u64 {
mod tests {
use super::{ClocksConfiguration, Config, Daemon, FanControlSettings, Gpu};
use crate::server::gpu_controller::fan_control::FanCurve;
use insta::assert_yaml_snapshot;
use lact_schema::{FanControlMode, PmfwOptions};
use std::collections::HashMap;
@ -395,6 +396,23 @@ mod tests {
assert_eq!(config, deserialized_config);
}
#[test]
fn parse_doc() {
let doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../docs/CONFIG.md"));
let example_config_start = doc
.find("```yaml")
.expect("Could not find example config start")
+ 7;
let example_config_end = doc[example_config_start..]
.find("```")
.expect("Could not find example config end")
+ example_config_start;
let example_config = &doc[example_config_start..example_config_end];
let deserialized_config: Config = serde_yaml::from_str(example_config).unwrap();
assert_yaml_snapshot!(deserialized_config);
}
#[test]
fn clocks_configuration_applied() {
let mut gpu = Gpu {

View File

@ -879,18 +879,6 @@ impl GpuController for AmdGpuController {
}
}
for (kind, states) in &config.power_states {
if config.performance_level != Some(PerformanceLevel::Manual) {
return Err(anyhow!(
"Performance level has to be set to `manual` to configure power states"
));
}
self.handle
.set_enabled_power_levels(*kind, states)
.with_context(|| format!("Could not set {kind:?} power states"))?;
}
if config.fan_control_enabled {
if let Some(ref settings) = config.fan_control_settings {
match settings.mode {
@ -1023,6 +1011,18 @@ impl GpuController for AmdGpuController {
handle.commit()?;
}
for (kind, states) in &config.power_states {
if config.performance_level != Some(PerformanceLevel::Manual) {
return Err(anyhow!(
"Performance level has to be set to `manual` to configure power states"
));
}
self.handle
.set_enabled_power_levels(*kind, states)
.with_context(|| format!("Could not set {kind:?} power states"))?;
}
Ok(())
})
}

View File

@ -0,0 +1,91 @@
---
source: lact-daemon/src/config.rs
expression: deserialized_config
snapshot_kind: text
---
daemon:
log_level: info
admin_groups:
- wheel
- sudo
disable_clocks_cleanup: false
tcp_listen_address: "127.0.0.1:12853"
apply_settings_timer: 5
gpus:
"1002:687F-1043:0555-0000:0b:00.0":
fan_control_enabled: true
fan_control_settings:
mode: curve
static_speed: 1
temperature_key: edge
interval_ms: 500
curve:
40: 0.2
50: 0.35
60: 0.5
70: 0.75
80: 1
spindown_delay_ms: 0
change_threshold: 0
pmfw_options:
acoustic_limit: 3200
acoustic_target: 1450
minimum_pwm: 15
target_temperature: 83
zero_rpm: true
zero_rpm_threshold: 50
power_cap: 320
performance_level: auto
min_core_clock: 300
min_memory_clock: 500
min_voltage: 900
max_core_clock: 1630
max_memory_clock: 800
max_voltage: 1200
voltage_offset: 0
power_profile_mode_index: 0
custom_power_profile_mode_hueristics:
- - 0
- 5
- 1
- 0
- 4
- 800
- 4587520
- -65536
- 0
- - 0
- 5
- 1
- 0
- 1
- 0
- 3276800
- -65536
- -6553
- - 0
- 5
- 1
- 0
- 4
- 800
- 327680
- -65536
- 0
power_states:
memory_clock:
- 0
- 1
core_clock:
- 0
- 2
- 3
profiles:
vkcube:
rule:
type: process
filter:
name: vkcube
args: "--my-arg"
current_profile: vkcube
auto_switch_profiles: true

View File

@ -121,6 +121,8 @@ impl OcPage {
.map(|info| info.vram_clock_ratio)
.unwrap_or(1.0);
self.power_states_frame
.set_vram_clock_ratio(vram_clock_ratio);
self.stats_section.set_vram_clock_ratio(vram_clock_ratio);
self.clocks_frame.set_vram_clock_ratio(vram_clock_ratio);
}

View File

@ -26,8 +26,10 @@ impl PowerStatesFrame {
imp.expander.set_expanded(false);
}
imp.core_states_list.set_power_states(states.core, "MHz");
imp.vram_states_list.set_power_states(states.vram, "MHz");
imp.core_states_list
.set_power_states(states.core, "MHz", 1.0);
imp.vram_states_list
.set_power_states(states.vram, "MHz", self.vram_clock_ratio());
}
pub fn connect_values_changed<F: Fn() + 'static + Clone>(&self, f: F) {
@ -76,7 +78,7 @@ mod imp {
},
CompositeTemplate, Expander,
};
use std::sync::atomic::AtomicBool;
use std::{cell::Cell, sync::atomic::AtomicBool};
#[derive(CompositeTemplate, Default, Properties)]
#[properties(wrapper_type = super::PowerStatesFrame)]
@ -91,6 +93,8 @@ mod imp {
#[property(get, set)]
configurable: AtomicBool,
#[property(get, set)]
vram_clock_ratio: Cell<f64>,
}
#[glib::object_subclass]

View File

@ -31,9 +31,15 @@ impl PowerStatesList {
.collect()
}
pub fn set_power_states(&self, power_states: Vec<PowerState>, value_suffix: &str) {
pub fn set_power_states(
&self,
power_states: Vec<PowerState>,
value_suffix: &str,
value_ratio: f64,
) {
let store = gio::ListStore::new::<PowerStateRow>();
for (i, state) in power_states.into_iter().enumerate() {
for (i, mut state) in power_states.into_iter().enumerate() {
state.value = (state.value as f64 * value_ratio) as u64;
let index = u8::try_from(i).expect("Power state index doesn't fit in u8?");
let row = PowerStateRow::new(state, index, value_suffix);
store.append(&row);

View File

@ -8,7 +8,7 @@ default = ["lact-gui"]
adw = ["lact-gui/adw"]
[dependencies]
lact-daemon = { path = "../lact-daemon", default-features = false }
lact-daemon = { path = "../lact-daemon" }
lact-schema = { path = "../lact-schema", features = ["args"] }
lact-cli = { path = "../lact-cli" }
lact-gui = { path = "../lact-gui", optional = true }