use std::collections::HashMap;
use std::fs;
use std::path::Path;
use anyhow::Result;
use serde::Deserialize;
use serde::Serialize;
use crate::SchedMode;
use crate::SupportedSched;
#[derive(Debug, PartialEq, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
pub default_sched: Option<SupportedSched>,
pub default_mode: Option<SchedMode>,
pub scheds: HashMap<String, Sched>,
}
#[derive(Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct Sched {
pub auto_mode: Option<Vec<String>>,
pub gaming_mode: Option<Vec<String>>,
pub lowlatency_mode: Option<Vec<String>>,
pub powersave_mode: Option<Vec<String>>,
pub server_mode: Option<Vec<String>>,
}
pub fn init_config() -> Result<Config> {
if let Ok(config_path) = get_config_path() {
parse_config_file(&config_path)
} else {
Ok(get_default_config())
}
}
pub fn parse_config_file(filepath: &str) -> Result<Config> {
let file_content = fs::read_to_string(filepath)?;
parse_config_content(&file_content)
}
pub fn get_config_path() -> Result<String> {
let vendordir = option_env!("VENDORDIR").unwrap_or("/usr/share");
let check_paths = [
"/etc/scx_loader/config.toml".to_owned(),
"/etc/scx_loader.toml".to_owned(),
format!("{vendordir}/scx_loader/config.toml").to_owned(),
format!("{vendordir}/scx_loader.toml").to_owned(),
];
for check_path in check_paths {
if !Path::new(&check_path).exists() {
continue;
}
return Ok(check_path);
}
anyhow::bail!("Failed to find config!");
}
fn parse_config_content(file_content: &str) -> Result<Config> {
if file_content.is_empty() {
anyhow::bail!("The config file is empty!")
}
let config: Config = toml::from_str(file_content)?;
Ok(config)
}
pub fn get_default_config() -> Config {
let supported_scheds = [
SupportedSched::Bpfland,
SupportedSched::Rusty,
SupportedSched::Lavd,
SupportedSched::Flash,
SupportedSched::P2DQ,
SupportedSched::Tickless,
SupportedSched::Rustland,
SupportedSched::Cosmos,
SupportedSched::Beerland,
];
let scheds_map = HashMap::from(supported_scheds.map(init_default_config_entry));
Config {
default_sched: None,
default_mode: Some(SchedMode::Auto),
scheds: scheds_map,
}
}
pub fn get_scx_flags_for_mode(
config: &Config,
scx_sched: &SupportedSched,
sched_mode: SchedMode,
) -> Vec<String> {
let scx_name: &str = scx_sched.clone().into();
if let Some(sched_config) = config.scheds.get(scx_name) {
let scx_flags = extract_scx_flags_from_config(sched_config, sched_mode);
scx_flags.unwrap_or({
get_default_scx_flags_for_mode(scx_sched, sched_mode)
.into_iter()
.map(String::from)
.collect()
})
} else {
get_default_scx_flags_for_mode(scx_sched, sched_mode)
.into_iter()
.map(String::from)
.collect()
}
}
fn extract_scx_flags_from_config(
sched_config: &Sched,
sched_mode: SchedMode,
) -> Option<Vec<String>> {
match &sched_mode {
SchedMode::Gaming => sched_config.gaming_mode.clone(),
SchedMode::LowLatency => sched_config.lowlatency_mode.clone(),
SchedMode::PowerSave => sched_config.powersave_mode.clone(),
SchedMode::Server => sched_config.server_mode.clone(),
SchedMode::Auto => sched_config.auto_mode.clone(),
}
}
fn get_default_sched_for_config(scx_sched: &SupportedSched) -> Sched {
Sched {
auto_mode: Some(
get_default_scx_flags_for_mode(scx_sched, SchedMode::Auto)
.into_iter()
.map(String::from)
.collect(),
),
gaming_mode: Some(
get_default_scx_flags_for_mode(scx_sched, SchedMode::Gaming)
.into_iter()
.map(String::from)
.collect(),
),
lowlatency_mode: Some(
get_default_scx_flags_for_mode(scx_sched, SchedMode::LowLatency)
.into_iter()
.map(String::from)
.collect(),
),
powersave_mode: Some(
get_default_scx_flags_for_mode(scx_sched, SchedMode::PowerSave)
.into_iter()
.map(String::from)
.collect(),
),
server_mode: Some(
get_default_scx_flags_for_mode(scx_sched, SchedMode::Server)
.into_iter()
.map(String::from)
.collect(),
),
}
}
fn get_default_scx_flags_for_mode(
scx_sched: &SupportedSched,
sched_mode: SchedMode,
) -> Vec<&'static str> {
match &scx_sched {
SupportedSched::Bpfland => match sched_mode {
SchedMode::LowLatency => {
vec!["-m", "performance", "-w"]
}
SchedMode::PowerSave => {
vec!["-s", "20000", "-m", "powersave", "-I", "100", "-t", "100"]
}
SchedMode::Server => vec!["-s", "20000", "-S"],
SchedMode::Gaming | SchedMode::Auto => vec![],
},
SupportedSched::Lavd => match sched_mode {
SchedMode::Gaming | SchedMode::LowLatency => vec!["--performance"],
SchedMode::PowerSave => vec!["--powersave"],
SchedMode::Server | SchedMode::Auto => vec![],
},
SupportedSched::Flash => match sched_mode {
SchedMode::Gaming => vec!["-m", "all"],
SchedMode::LowLatency => vec!["-m", "performance", "-w", "-C", "0"],
SchedMode::PowerSave => vec![
"-m",
"powersave",
"-I",
"10000",
"-t",
"10000",
"-s",
"10000",
"-S",
"1000",
],
SchedMode::Server => vec![
"-m", "all", "-s", "20000", "-S", "1000", "-I", "-1", "-D", "-L",
],
SchedMode::Auto => vec![],
},
SupportedSched::P2DQ => match sched_mode {
SchedMode::Gaming => vec!["--task-slice", "true", "-f", "--sched-mode", "performance"],
SchedMode::LowLatency => vec!["-y", "-f", "--task-slice", "true"],
SchedMode::PowerSave => vec!["--sched-mode", "efficiency"],
SchedMode::Server => vec!["--keep-running"],
SchedMode::Auto => vec![],
},
SupportedSched::Tickless => match sched_mode {
SchedMode::Gaming => vec!["-f", "5000", "-s", "5000"],
SchedMode::LowLatency => vec!["-f", "5000", "-s", "1000"],
SchedMode::PowerSave => vec!["-f", "50"],
SchedMode::Server => vec!["-f", "100"],
SchedMode::Auto => vec![],
},
SupportedSched::Cosmos => match sched_mode {
SchedMode::Gaming => vec!["-c", "0", "-p", "0"],
SchedMode::LowLatency => vec!["-m", "performance", "-c", "0", "-p", "0", "-w"],
SchedMode::PowerSave => vec!["-m", "powersave", "-d", "-p", "5000"],
SchedMode::Server => vec!["-s", "20000"],
SchedMode::Auto => vec!["-d"],
},
SupportedSched::Rusty | SupportedSched::Rustland | SupportedSched::Beerland => vec![],
}
}
fn init_default_config_entry(scx_sched: SupportedSched) -> (String, Sched) {
let default_modes = get_default_sched_for_config(&scx_sched);
(
<SupportedSched as Into<&str>>::into(scx_sched).to_owned(),
default_modes,
)
}
#[cfg(test)]
mod tests {
use crate::config::*;
#[test]
fn test_default_config() {
let config_str = r#"
default_mode = "Auto"
[scheds.scx_bpfland]
auto_mode = []
gaming_mode = []
lowlatency_mode = ["-m", "performance", "-w"]
powersave_mode = ["-s", "20000", "-m", "powersave", "-I", "100", "-t", "100"]
server_mode = ["-s", "20000", "-S"]
[scheds.scx_rusty]
auto_mode = []
gaming_mode = []
lowlatency_mode = []
powersave_mode = []
server_mode = []
[scheds.scx_lavd]
auto_mode = []
gaming_mode = ["--performance"]
lowlatency_mode = ["--performance"]
powersave_mode = ["--powersave"]
server_mode = []
[scheds.scx_flash]
auto_mode = []
gaming_mode = ["-m", "all"]
lowlatency_mode = ["-m", "performance", "-w", "-C", "0"]
powersave_mode = ["-m", "powersave", "-I", "10000", "-t", "10000", "-s", "10000", "-S", "1000"]
server_mode = ["-m", "all", "-s", "20000", "-S", "1000", "-I", "-1", "-D", "-L"]
[scheds.scx_p2dq]
auto_mode = []
gaming_mode = ["--task-slice", "true", "-f", "--sched-mode", "performance"]
lowlatency_mode = ["-y", "-f", "--task-slice", "true"]
powersave_mode = ["--sched-mode", "efficiency"]
server_mode = ["--keep-running"]
[scheds.scx_tickless]
auto_mode = []
gaming_mode = ["-f", "5000", "-s", "5000"]
lowlatency_mode = ["-f", "5000", "-s", "1000"]
powersave_mode = ["-f", "50"]
server_mode = ["-f", "100"]
[scheds.scx_rustland]
auto_mode = []
gaming_mode = []
lowlatency_mode = []
powersave_mode = []
server_mode = []
[scheds.scx_cosmos]
auto_mode = ["-d"]
gaming_mode = ["-c", "0", "-p", "0"]
lowlatency_mode = ["-m", "performance", "-c", "0", "-p", "0", "-w"]
powersave_mode = ["-m", "powersave", "-d", "-p", "5000"]
server_mode = ["-s", "20000"]
[scheds.scx_beerland]
auto_mode = []
gaming_mode = []
lowlatency_mode = []
powersave_mode = []
server_mode = []
"#;
let parsed_config = parse_config_content(config_str).expect("Failed to parse config");
let expected_config = get_default_config();
assert_eq!(parsed_config, expected_config);
}
#[test]
fn test_simple_fallback_config_flags() {
let config_str = r#"
default_mode = "Auto"
"#;
let parsed_config = parse_config_content(config_str).expect("Failed to parse config");
let bpfland_flags =
get_scx_flags_for_mode(&parsed_config, &SupportedSched::Bpfland, SchedMode::Gaming);
let expected_flags =
get_default_scx_flags_for_mode(&SupportedSched::Bpfland, SchedMode::Gaming);
assert_eq!(
bpfland_flags
.iter()
.map(std::string::String::as_str)
.collect::<Vec<&str>>(),
expected_flags
);
}
#[test]
fn test_sched_fallback_config_flags() {
let config_str = r#"
default_mode = "Auto"
[scheds.scx_lavd]
auto_mode = ["--help"]
"#;
let parsed_config = parse_config_content(config_str).expect("Failed to parse config");
let lavd_flags =
get_scx_flags_for_mode(&parsed_config, &SupportedSched::Lavd, SchedMode::Gaming);
let expected_flags =
get_default_scx_flags_for_mode(&SupportedSched::Lavd, SchedMode::Gaming);
assert_eq!(
lavd_flags
.iter()
.map(std::string::String::as_str)
.collect::<Vec<&str>>(),
expected_flags
);
let lavd_flags =
get_scx_flags_for_mode(&parsed_config, &SupportedSched::Lavd, SchedMode::Auto);
assert_eq!(
lavd_flags
.iter()
.map(std::string::String::as_str)
.collect::<Vec<&str>>(),
vec!["--help"]
);
}
#[test]
fn test_empty_config() {
let config_str = "";
let result = parse_config_content(config_str);
assert!(result.is_err());
}
}