1
Fork 0
mirror of https://github.com/RGBCube/superfreq synced 2025-07-30 02:17:44 +00:00

Compare commits

...

4 commits

Author SHA1 Message Date
a341d08c45
watt: turn into a library 2025-06-14 22:58:53 +03:00
3c82679ada
build.rs: remove 2025-06-14 22:38:50 +03:00
b4e5dd7fea
fmt: remove ads (wtf!!) 2025-06-14 22:36:26 +03:00
6ea92c1624
address review comments 2025-06-14 22:35:38 +03:00
14 changed files with 154 additions and 173 deletions

View file

@ -1,6 +1,3 @@
# Taken from https://github.com/cull-os/carcass.
# Modified to have 2 space indents and 80 line width.
# float_literal_trailing_zero = "Always" # TODO: Warning for some reason?
condense_wildcard_suffixes = true
doc_comment_code_block_width = 80

View file

@ -1,5 +1,3 @@
# Taken from https://github.com/cull-os/carcass.
[formatting]
align_entries = true
column_width = 100

View file

@ -1,12 +1,17 @@
[package]
name = "watt"
description = "Modern CPU frequency and power management utility for Linux"
version = "0.4.0"
edition = "2024"
authors = [ "NotAShelf <raf@notashelf.dev>", "RGBCube <git@rgbcu.be>" ]
rust-version = "1.85"
[workspace]
members = [ "watt" ]
resolver = "3"
[dependencies]
[workspace.package]
authors = [ "NotAShelf <raf@notashelf.dev>", "RGBCube <git@rgbcu.be>" ]
description = "Modern CPU frequency and power management utility for Linux"
edition = "2024" # Keep in sync with .rustfmt.toml.
license = "MPL-2.0"
repository = "https://github.com/notashelf/watt"
rust-version = "1.85"
version = "0.4.0"
[workspace.dependencies]
anyhow = "1.0"
clap = { version = "4.0", features = [ "derive", "env" ] }
clap-verbosity-flag = "3.0.2"

View file

@ -1,57 +0,0 @@
use std::{
env,
fs,
path::PathBuf,
};
const MULTICALL_NAMES: &[&str] = &["cpu", "power"];
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=target");
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
let target = out_dir
.parent() // target/debug/build/<pkg>-<hash>/out
.and_then(|p| p.parent()) // target/debug/build/<pkg>-<hash>
.and_then(|p| p.parent()) // target/debug/
.ok_or("failed to find target directory")?;
let main_binary_name = env::var("CARGO_PKG_NAME")?;
let main_binary_path = target.join(&main_binary_name);
let mut errored = false;
for name in MULTICALL_NAMES {
let hardlink_path = target.join(name);
if hardlink_path.exists() {
if hardlink_path.is_dir() {
fs::remove_dir_all(&hardlink_path)?;
} else {
fs::remove_file(&hardlink_path)?;
}
}
if let Err(error) = fs::hard_link(&main_binary_path, &hardlink_path) {
println!(
"cargo:warning=failed to create hard link '{path}': {error}",
path = hardlink_path.display(),
);
errored = true;
}
}
if errored {
println!(
"cargo:warning=this often happens because the target binary isn't built \
yet, try running `cargo build` again"
);
println!(
"cargo:warning=keep in mind that this is for development purposes only"
);
}
Ok(())
}

28
watt/Cargo.toml Normal file
View file

@ -0,0 +1,28 @@
[package]
name = "watt"
description.workspace = true
version.workspace = true
edition.workspace = true
authors.workspace = true
rust-version.workspace = true
[lib]
path = "lib.rs"
[[bin]]
name = "watt"
path = "main.rs"
[dependencies]
anyhow.workspace = true
clap.workspace = true
clap-verbosity-flag.workspace = true
ctrlc.workspace = true
derive_more.workspace = true
env_logger.workspace = true
log.workspace = true
num_cpus.workspace = true
serde.workspace = true
thiserror.workspace = true
toml.workspace = true
yansi.workspace = true

View file

@ -523,7 +523,7 @@ pub struct DaemonConfig {
}
impl DaemonConfig {
const DEFAULT: &str = include_str!("../config.toml");
const DEFAULT: &str = include_str!("config.toml");
pub fn load_from(path: Option<&Path>) -> anyhow::Result<Self> {
let contents = if let Some(path) = path {

View file

@ -5,28 +5,27 @@
# Emergency thermal protection (highest priority).
[[rule]]
if = { value = "$cpu-temperature", is-more-than = 85.0 }
priority = 100
cpu.energy-performance-preference = "power"
cpu.frequency-mhz-maximum = 2000
cpu.governor = "powersave"
cpu.turbo = false
if = { value = "$cpu-temperature", is-more-than = 85.0 }
priority = 100
# Critical battery preservation.
[[rule]]
if.all = [ "?discharging", { value = "%power-supply-charge", is-less-than = 0.3 } ]
priority = 90
cpu.energy-performance-preference = "power"
cpu.frequency-mhz-maximum = 800 # More aggressive below critical threshold.
cpu.frequency-mhz-maximum = 800 # More aggressive below critical threshold.
cpu.governor = "powersave"
cpu.turbo = false
if.all = [ "?discharging", { value = "%power-supply-charge", is-less-than = 0.3 } ]
power.platform-profile = "low-power"
priority = 90
# High performance mode for sustained high load.
[[rule]]
cpu.energy-performance-preference = "performance"
cpu.governor = "performance"
cpu.turbo = true
if.all = [
{ value = "%cpu-usage", is-more-than = 0.8 },
{ value = "$cpu-idle-seconds", is-less-than = 30.0 },
@ -34,12 +33,12 @@ if.all = [
]
priority = 80
cpu.energy-performance-preference = "performance"
cpu.governor = "performance"
cpu.turbo = true
# Performance mode when not discharging.
[[rule]]
cpu.energy-performance-bias = "balance_performance"
cpu.energy-performance-preference = "performance"
cpu.governor = "performance"
cpu.turbo = true
if.all = [
{ not = "?discharging" },
{ value = "%cpu-usage", is-more-than = 0.1 },
@ -47,56 +46,66 @@ if.all = [
]
priority = 70
cpu.energy-performance-bias = "balance_performance"
cpu.energy-performance-preference = "performance"
cpu.governor = "performance"
cpu.turbo = true
# Moderate performance for medium load.
[[rule]]
cpu.energy-performance-preference = "balance_performance"
cpu.governor = "schedutil"
if.all = [
{ value = "%cpu-usage", is-more-than = 0.4 },
{ value = "%cpu-usage", is-less-than = 0.8 },
]
priority = 60
cpu.energy-performance-preference = "balance_performance"
cpu.governor = "schedutil"
# Power saving during low activity.
[[rule]]
cpu.energy-performance-preference = "power"
cpu.governor = "powersave"
cpu.turbo = false
if.all = [
{ value = "%cpu-usage", is-less-than = 0.2 },
{ value = "$cpu-idle-seconds", is-more-than = 60.0 },
]
priority = 50
cpu.energy-performance-preference = "power"
cpu.governor = "powersave"
cpu.turbo = false
# Extended idle power optimization.
[[rule]]
if = { value = "$cpu-idle-seconds", is-more-than = 300.0 }
priority = 40
cpu.energy-performance-preference = "power"
cpu.frequency-mhz-maximum = 1600
cpu.governor = "powersave"
cpu.turbo = false
if = { value = "$cpu-idle-seconds", is-more-than = 300.0 }
priority = 40
# Battery conservation when discharging.
[[rule]]
if.all = [ "?discharging", { value = "%power-supply-charge", is-less-than = 0.5 } ]
priority = 30
cpu.energy-performance-preference = "power"
cpu.frequency-mhz-maximum = 2000
cpu.governor = "powersave"
cpu.turbo = false
if.all = [ "?discharging", { value = "%power-supply-charge", is-less-than = 0.5 } ]
power.platform-profile = "low-power"
priority = 30
# General battery mode.
[[rule]]
if = "?discharging"
priority = 20
cpu.energy-performance-bias = "balance_power"
cpu.energy-performance-preference = "power"
cpu.frequency-mhz-maximum = 1800
cpu.frequency-mhz-minimum = 200
cpu.governor = "powersave"
cpu.turbo = false
if = "?discharging"
priority = 20
# Balanced performance for general use. Default fallback rule.
[[rule]]

View file

@ -1,37 +1,27 @@
mod cpu;
mod power_supply;
mod system;
use std::path::PathBuf;
mod fs;
mod config;
// mod core;
mod daemon;
// mod engine;
// mod monitor;
use std::{
fmt::Write as _,
io,
io::Write as _,
path::PathBuf,
process,
};
use anyhow::Context;
use anyhow::Context as _;
use clap::Parser as _;
use yansi::Paint as _;
pub mod cpu;
pub mod power_supply;
pub mod system;
pub mod fs;
pub mod config;
pub mod daemon;
#[derive(clap::Parser, Debug)]
#[clap(author, version, about)]
struct Cli {
pub struct Cli {
#[clap(subcommand)]
command: Command,
}
#[derive(clap::Parser, Debug)]
#[clap(multicall = true)]
enum Command {
pub enum Command {
/// Watt daemon.
Watt {
#[command(flatten)]
@ -62,18 +52,18 @@ enum Command {
}
#[derive(clap::Parser, Debug)]
enum CpuCommand {
pub enum CpuCommand {
/// Modify CPU attributes.
Set(config::CpuDelta),
}
#[derive(clap::Parser, Debug)]
enum PowerCommand {
pub enum PowerCommand {
/// Modify power supply attributes.
Set(config::PowerDelta),
}
fn real_main() -> anyhow::Result<()> {
pub fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
yansi::whenever(yansi::Condition::TTY_AND_COLOR);
@ -107,52 +97,3 @@ fn real_main() -> anyhow::Result<()> {
} => delta.apply(),
}
}
fn main() {
let Err(error) = real_main() else {
return;
};
let mut err = io::stderr();
let mut message = String::new();
let mut chain = error.chain().rev().peekable();
while let Some(error) = chain.next() {
let _ = write!(
err,
"{header} ",
header = if chain.peek().is_none() {
"error:"
} else {
"cause:"
}
.red()
.bold(),
);
String::clear(&mut message);
let _ = write!(message, "{error}");
let mut chars = message.char_indices();
let _ = match (chars.next(), chars.next()) {
(Some((_, first)), Some((second_start, second)))
if second.is_lowercase() =>
{
writeln!(
err,
"{first_lowercase}{rest}",
first_lowercase = first.to_lowercase(),
rest = &message[second_start..],
)
},
_ => {
writeln!(err, "{message}")
},
};
}
process::exit(1);
}

57
watt/main.rs Normal file
View file

@ -0,0 +1,57 @@
use std::{
fmt::Write as _,
io,
io::Write as _,
process,
};
use yansi::Paint as _;
fn main() {
let Err(error) = watt::main() else {
return;
};
let mut err = io::stderr();
let mut message = String::new();
let mut chain = error.chain().rev().peekable();
while let Some(error) = chain.next() {
let _ = write!(
err,
"{header} ",
header = if chain.peek().is_none() {
"error:"
} else {
"cause:"
}
.red()
.bold(),
);
String::clear(&mut message);
let _ = write!(message, "{error}");
let mut chars = message.char_indices();
let _ = match (chars.next(), chars.next()) {
(Some((_, first)), Some((second_start, second)))
if second.is_lowercase() =>
{
writeln!(
err,
"{first_lowercase}{rest}",
first_lowercase = first.to_lowercase(),
rest = &message[second_start..],
)
},
_ => {
writeln!(err, "{message}")
},
};
}
process::exit(1);
}

View file

@ -227,10 +227,13 @@ impl PowerSupply {
}
}
// Check for model name that indicates a peripheral
if let Some(model) = fs::read(self.path.join("model_name"))
if let Some(model_name) = fs::read(self.path.join("model_name"))
.with_context(|| format!("failed to read the model name of {self}"))?
{
if model.contains("bluetooth") || model.contains("wireless") {
let model_name_lower = model_name.to_lowercase();
if model_name_lower.contains("bluetooth")
|| model_name_lower.contains("wireless")
{
break 'is_from_peripheral true;
}
}

View file

@ -295,8 +295,8 @@ impl System {
"stripped content is a valid number, taking it as the core number"
);
log::debug!(
"it is fine if this number doesn't seem accurate due to CPU binning, see a more detailed explanation at: https://rgbcu.be/blog/why-cores"
);
"it is fine if this number doesn't seem accurate due to CPU binning, see a more detailed explanation at: https://rgbcu.be/blog/why-cores"
);
let Some(temperature_mc) =
fs::read_n::<i64>(&input_path).with_context(|| {