Compare commits

..

No commits in common. "v1.2.1" and "master" have entirely different histories.

59 changed files with 6592 additions and 1377 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: AdnanHodzic

View File

@ -0,0 +1,37 @@
---
name: Bug report or feature request
about: Report an issue or request a feature request
title: ''
labels: ''
assignees: ''
---
### Fill out information requested in this template, without doing so issue will be ignored & closed!
#### Before submitting an issue, it is strongly recommended to use the **[auto-cpufreq-genAI-chatbot](https://foolcontrol.org/?p=4903)** to get an immediate answer to your question.
### Have you tried?
- [Searching through existing/closed issues](https://github.com/AdnanHodzic/auto-cpufreq/issues) to see if your bug has already been already submitted?
- If installation failed with [auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq/#auto-cpufreq-installer),have you tried installing auto-cpufreq using [Snap package](https://github.com/AdnanHodzic/auto-cpufreq/#snap-store)?
- Have you tried [configuring auto-cpufreq](https://github.com/AdnanHodzic/auto-cpufreq/#configuring-auto-cpufreq)?
- Have you tried suggestions in [troubleshooting section](https://github.com/AdnanHodzic/auto-cpufreq#troubleshooting)?
### Error output:
```text
Add/paste error output in case of failed installation or other failing component
```
---
### System information:
Add/paste output of:
```
auto-cpufreq --debug
```
Also please be descriptive about the issue you're reporting, i.e: what you tried & what's the expected behaviour.
---

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1 @@
blank_issues_enabled: false

View File

@ -1,11 +0,0 @@
### Error output:
```text
Paste here error output
```
---
### System information:
```text
Paste here output of the auto-cpufreq --debug
```
---

28
.github/workflows/build-linux.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Linux Build
on:
push:
paths-ignore:
- "README.md"
- ".gitignore"
- "LICENSE"
pull_request:
jobs:
build_linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1
- name: Install poetry
run: pipx install poetry
- name: "Setup Python"
uses: actions/setup-python@v5.0.0
with:
python-version: 3.12
cache: "poetry"
- name: "Install auto-cpufreq"
run: sudo ./auto-cpufreq-installer --install

29
.github/workflows/build-nix.yaml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Nix Flake
on:
push:
paths-ignore:
- "README.md"
- ".gitignore"
- "LICENSE"
pull_request:
jobs:
build-nix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: "Install Nix ❄️"
uses: nixbuild/nix-quick-install-action@v30
- name: "Nix Cache ❄️"
uses: nix-community/cache-nix-action@v6
with:
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}
- name: "Build Nix Flake ❄️"
run: nix build

13
.gitignore vendored
View File

@ -110,6 +110,7 @@ venv/
ENV/
env.bak/
venv.bak/
.direnv
# Spyder project settings
.spyderproject
@ -132,3 +133,15 @@ dmypy.json
# vim
*.swp
*.swo
# snap
*.snap
# pycharm
.idea/
# nix build
/result
# vs code
.vscode/

737
README.md
View File

@ -1,77 +1,485 @@
# auto-cpufreq
[![Linux Build](https://github.com/AdnanHodzic/auto-cpufreq/actions/workflows/build-linux.yml/badge.svg?event=push)](https://github.com/AdnanHodzic/auto-cpufreq/actions/workflows/build-linux.yml)
[![Nix Flake](https://github.com/AdnanHodzic/auto-cpufreq/actions/workflows/build-nix.yaml/badge.svg?event=push)](https://github.com/AdnanHodzic/auto-cpufreq/actions/workflows/build-nix.yaml)
Automatic CPU speed & power optimizer for Linux based on active monitoring of laptop's battery state, CPU usage and system load. Ultimately allowing you to improve battery life without making any compromises.
Automatic CPU speed & power optimizer for Linux. Actively monitors laptop battery state, CPU usage, CPU temperature, and system load, ultimately allowing you to improve battery life without making any compromises.
For tl;dr folks there's a: [Youtube: auto-cpufreq - tool demo](https://www.youtube.com/watch?v=QkYRpVEEIlg)
For tl;dr folks:
[![](http://img.youtube.com/vi/QkYRpVEEIlg/0.jpg)](http://www.youtube.com/watch?v=QkYRpVEEIlg"")
[Youtube: auto-cpufreq v2.0 release & demo of all available features and options](https://www.youtube.com/watch?v=SPGpkZ0AZVU)
[![](https://img.youtube.com/vi/SPGpkZ0AZVU/0.jpg)](https://www.youtube.com/watch?v=QkYRpVEEIlg)
[Youtube: auto-cpufreq - tool demo](https://www.youtube.com/watch?v=QkYRpVEEIlg)
[![](https://img.youtube.com/vi/QkYRpVEEIlg/0.jpg)](https://www.youtube.com/watch?v=QkYRpVEEIlg)
If you're having a problem with auto-cpufreq, before ([submitting an issue](https://github.com/AdnanHodzic/auto-cpufreq/issues)), it is strongly recommended to use the **[auto-cpufreq-genAI-chatbot](https://foolcontrol.org/?p=4903)** to get an immediate answer to your question.
[![](https://img.youtube.com/vi/a-UcwAAXOoc/0.jpg)](https://www.youtube.com/watch?v=a-UcwAAXOoc)
Example of auto-cpufreq GUI (available >= v2.0)
<img src="https://github.com/user-attachments/assets/4b83b67c-8c1e-4ef0-ad2d-ffc1b9dc16de" width="480" alt="Example of auto-cpufreq GUI (available >= v2.0)" />
Example of `auto-cpufreq --stats` CLI output
<img src="https://github.com/user-attachments/assets/9c7715c4-16b7-4a5c-86be-4c390276d9e8" width="480" alt="Example of auto-cpufreq CLI output"/>
## Looking for developers and co-maintainers
- If you would like to discuss anything regarding auto-cpufreq or its development, please join the [auto-cpufreq Discord server!](https://discord.gg/Sjauxtj6kH)
- auto-cpufreq is looking for [co-maintainers & open source developers to help shape the future of the project!](https://github.com/AdnanHodzic/auto-cpufreq/discussions/312)
## Index
- [Why do I need auto-cpufreq?](#why-do-i-need-auto-cpufreq)
- [Supported architectures and devices](#supported-architectures-and-devices)
- [Features](#features)
- [Installing auto-cpufreq](#installing-auto-cpufreq)
- [auto-cpufreq-installer](#auto-cpufreq-installer)
- [Snap Store](#snap-store)
- [AUR package (Arch based distributions)](#aur-package-arch-based-distributions)
- [NixOS](#nixos)
- [For developers](#installation-development-mode-only)
- [Post-installation](#post-installation)
- [Configuring auto-cpufreq](#configuring-auto-cpufreq)
- [1: power_helper.py script (Snap package install only)](#1-power_helperpy-script-snap-package-install-only)
- [2: `--force` governor override](#2---force-governor-override)
- [3: `--turbo` mode override](#3---turbo-mode-override)
- [4: auto-cpufreq config file](#4-auto-cpufreq-config-file)
- [Example config file contents](#example-config-file-contents)
- [How to run auto-cpufreq](#how-to-run-auto-cpufreq)
- [auto-cpufreq modes and options](#auto-cpufreq-modes-and-options)
- [monitor](#monitor)
- [live](#live)
- [overriding governor](#overriding-governor)
- [overriding turbo mode](#overriding-turbo-mode)
- [Install - auto-cpufreq daemon](#install---auto-cpufreq-daemon)
- [Update - auto-cpufreq update](#update---auto-cpufreq-update)
- [Remove - auto-cpufreq daemon](#remove---auto-cpufreq-daemon)
- [stats](#stats)
- [bluetooth_boot_off](#bluetooth_boot_off)
- [bluetooth_boot_on](#bluetooth_boot_on)
- [Battery charging thresholds](#battery-charging-thresholds)
- [Supported Devices](#supported-devices)
- [Battery config](#battery-config)
- [Ignoring power supplies](#Ignoring-power-supplies)
- [Troubleshooting](#troubleshooting)
- [AUR](#aur)
- [Discussion](#discussion)
- [Donate](#donate)
- [Financial donation](#financial-donation)
- [Paypal](#paypal)
- [BitCoin](#bitcoin)
- [Code contribution](#code-contribution)
## Why do I need auto-cpufreq?
One of the problems with Linux today on laptops is that CPU will run in unoptimized manner which will negatively reflect on battery life. For example, CPU will run using "performance" governor with turbo boost enabled regardless if it's plugged in to power or not.
One of the problems with Linux today on laptops is that the CPU will run in an unoptimized manner which will negatively impact battery life. For example, the CPU may run using the "performance" governor with turbo boost enabled regardless of whether it's plugged into a power outlet or not.
Issue can be mitigated by using tools like [indicator-cpufreq](https://itsfoss.com/cpufreq-ubuntu/) or [cpufreq](https://github.com/konkor/cpufreq), but these still require maual action from your side which can be daunting and cumbersome.
These issues can be mitigated by using tools like [indicator-cpufreq](https://itsfoss.com/cpufreq-ubuntu/) or [cpufreq](https://github.com/konkor/cpufreq), but those still require manual action from your side which can be daunting and cumbersome.
Using tools like [TLP](https://github.com/linrunner/TLP) will help in this situation with extending battery life (which is something I did for numerous years now), but it also might come with its own set of problems, like losing turbo boost.
Tools like [TLP](https://github.com/linrunner/TLP) (which I used for numerous years) can help extend battery life, but may also create their own set of problems, such as losing turbo boost.
With that said, I needed a simple tool which would automatically make "cpufreq" related changes, save battery like TLP, but let Linux kernel do most of the heavy lifting. That's how auto-cpufreq was born.
Given all of the above, I needed a simple tool that would automatically make CPU frequency-related changes and save battery life, but let the Linux kernel do most of the heavy lifting. That's how auto-cpufreq was born.
Please note: this tool doesn't conflict and [works great in tandem with TLP](https://www.reddit.com/r/linux/comments/ejxx9f/github_autocpufreq_automatic_cpu_speed_power/fd4y36k/).
Please note: auto-cpufreq aims to replace TLP in terms of functionality, so after you install auto-cpufreq _it's recommended to remove TLP_. Using both for the same functionality (i.e., to set CPU frequencies) will lead to unwanted results like overheating. Hence, only use [both tools in tandem](https://github.com/AdnanHodzic/auto-cpufreq/discussions/176) if you know what you're doing.
One tool/daemon that does not conflict with auto-cpufreq in any way, and is even recommended to have running alongside, is [thermald](https://wiki.debian.org/thermald).
#### Supported architectures and devices
Supported devices must have an Intel, AMD or ARM CPU's. This tool was developed to improve performance and battery life on laptops, but running it on desktop/servers (to lower power consumption) should also be possible.
Only devices with an Intel, AMD, or ARM CPU are supported. This tool was developed to improve performance and battery life on laptops, but running it on desktops/servers (to lower power consumption) should also be possible.
## Features
* Monitoring
* Basic system information
* CPU frequency
* CPU temperatures
* Battery state
* System load
* CPU frequency scaling, governor and [turbo boost](https://en.wikipedia.org/wiki/Intel_Turbo_Boost) management based on
* battery state
* CPU usage
* System load
* Automatic CPU & power optimization (temporary and persistent)
- Monitoring
- Basic system information
- CPU frequency (system total & per core)
- CPU usage (system total & per core)
- CPU temperature (total average & per core)
- Battery state
- System load
- CPU frequency scaling, governor, and [turbo boost](https://en.wikipedia.org/wiki/Intel_Turbo_Boost) management based on
- Battery state
- CPU usage (total & per core)
- CPU temperature in combination with CPU utilization/load (to prevent overheating)
- System load
- Automatic CPU & power optimization (temporary and persistent)
- Settings battery charging thresholds (limited support)
## Installing auto-cpufreq
### Snap store
auto-cpufreq is available on [snap store](https://snapcraft.io/auto-cpufreq), or can be installed using CLI:
```
sudo snap install auto-cpufreq
```
**Please note:**
* Make sure [snapd](https://snapcraft.io/docs/installing-snapd) is installed and `snap version` version is >= 2.44 for `auto-cpufreq` to fully work due to [recent snapd changes](https://github.com/snapcore/snapd/pull/8127).
* Fedora users will [encounter following error](https://twitter.com/killyourfm/status/1291697985236144130). Due to `cgroups v2` [being in development](https://github.com/snapcore/snapd/pull/7825). This problem can be resolved by either running `sudo snap run auto-cpufreq` after snap installation. Or using [auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq/#auto-cpufreq-installer) which doesn't have this issue.
### auto-cpufreq-installer
Get source code, run installer and follow on screen instructions:
> As auto-cpufreq relies on git based versioning, users are advised to install `auto-cpufreq` using `git clone` method only. Downloading source code as a zip/from release will emit build error like [these](https://github.com/AdnanHodzic/auto-cpufreq/issues/623).
Get source code, run installer, and follow on-screen instructions:
```
git clone https://github.com/AdnanHodzic/auto-cpufreq.git
cd auto-cpufreq && sudo ./auto-cpufreq-installer
```
In case you encounter any problems with `auto-cpufreq-installer`, please [submit a bug report](https://github.com/AdnanHodzic/auto-cpufreq/issues/new).
### Snap Store
### Arch Linux
*Please note: while all [auto-cpufreq >= v2.0 CLI functionality](https://www.youtube.com/watch?v=SPGpkZ0AZVU&t=295s) will work as intended, [the GUI won't be available on Snap package installs](http://foolcontrol.org/wp-content/uploads/2023/10/auto-cpufreq-v2-snap-deprecation-notice.png) due to [Snap package confinement limitations](https://forum.snapcraft.io/t/pkexec-not-found-python-gtk-gnome-app/36579). Hence, please consider installing auto-cpufreq using [auto-cpufreq-installer](#auto-cpufreq-installer)*.
[AUR package is available](https://aur.archlinux.org/packages/auto-cpufreq-git/) for install. After which `auto-cpufreq` will be available as a binary and you can skip to [auto-cpufreq: modes and options](https://github.com/AdnanHodzic/auto-cpufreq#auto-cpufreq-modes-and-options) for further reference.
auto-cpufreq is available on the [Snap Store](https://snapcraft.io/auto-cpufreq) or via CLI:
```
sudo snap install auto-cpufreq
```
**Please note:**
- Make sure [snapd](https://snapcraft.io/docs/installing-snapd) is installed and `snap version` is >= 2.44 for `auto-cpufreq` to fully work due to [recent snapd changes](https://github.com/snapcore/snapd/pull/8127).
- Fedora users will [encounter the following error](https://twitter.com/killyourfm/status/1291697985236144130) due to `cgroups v2` [being in development](https://github.com/snapcore/snapd/pull/7825). This problem can be resolved by either running `sudo snap run auto-cpufreq` after the snap installation or by using the [auto-cpufreq-installer](#auto-cpufreq-installer) which doesn't have this issue.
### AUR package (Arch based distributions)
[![AUR package](https://repology.org/badge/version-for-repo/aur/auto-cpufreq.svg)](https://aur.archlinux.org/packages/auto-cpufreq)
The AUR [Release Package](https://aur.archlinux.org/packages/auto-cpufreq) is currently being maintained by [MusicalArtist12](https://github.com/MusicalArtist12), [liljaylj](https://github.com/liljaylj), and [parmjotsinghrobot](https://github.com/parmjotsinghrobot).
**Notices**
- The [Git Package](https://aur.archlinux.org/packages/auto-cpufreq-git) is seperately maintained and was last updated on version 1.9.6.
- The build process links to `/usr/share/` instead of `/usr/local/share/`
- The daemon installer provided does not work, instead start the daemon with
```
# systemctl enable --now auto-cpufreq
```
- The GNOME Power Profiles daemon is [automatically disabled by auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq#1-power_helperpy-script-snap-package-install-only) due to it's conflict with auto-cpufreq.service. However, this doesn't happen with AUR installs, which can lead to problems (e.g., [#463](https://github.com/AdnanHodzic/auto-cpufreq/issues/463)) if not masked manually.
- Open a terminal and run `sudo systemctl mask power-profiles-daemon.service` (then `enable` and `start` the auto-cpufreq.service if you haven't already).
- The TuneD daemon(enabled by default with Fedora 41) is [automatically disabled by auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq#1-power_helperpy-script-snap-package-install-only) due to it's conflict with auto-cpufreq.service.
### Gentoo Linux (GURU Repository)
New versions of auto-cpufreq were recently added to GURU, Gentoo's official community-maintained ebuild repository. The [ebuild](https://github.com/gentoo-mirror/guru/tree/master/sys-power/auto-cpufreq) is maintaned by [S41G0N](https://github.com/S41G0N) and other [GURU contributors](https://bugs.gentoo.org), who can respond in case of issues.
In order to build auto-cpufreq, it is necessary to add & sync GURU repository first. Adding ~amd64 keyword is also needed to unmask the package.
```
# echo "sys-power/auto-cpufreq ~amd64" >> /etc/portage/package.accept_keywords
# eselect repository enable guru
# emaint sync -r guru
# emerge --ask auto-cpufreq
```
**Notices**
- The build process links to `/usr/share/` instead of `/usr/local/share/`
- The build works on both systemd/OpenRC systems (both systemd and OpenRC will have a service called auto-cpufreq which can be started automatically)
- The daemon installer provided does work, but it is RECOMMENDED to install the daemon with:
```
# systemctl enable --now auto-cpufreq
# rc-update add auto-cpufreq default && rc-service auto-cpufreq start
```
### NixOS
<details>
<summary>Flakes</summary>
<br>
This repo contains a flake that exposes a NixOS Module that manages and offers options for auto-cpufreq. To use it, add the flake as an input to your `flake.nix` file and enable the module:
```nix
# flake.nix
{
inputs = {
# ---Snip---
auto-cpufreq = {
url = "github:AdnanHodzic/auto-cpufreq";
inputs.nixpkgs.follows = "nixpkgs";
};
# ---Snip---
}
outputs = {nixpkgs, auto-cpufreq, ...} @ inputs: {
nixosConfigurations.HOSTNAME = nixpkgs.lib.nixosSystem {
specialArgs = { inherit inputs; };
modules = [
./configuration.nix
auto-cpufreq.nixosModules.default
];
};
}
}
```
Then you can enable the program in your `configuration.nix` file:
```nix
# configuration.nix
{inputs, pkgs, ...}: {
# ---Snip---
programs.auto-cpufreq.enable = true;
# optionally, you can configure your auto-cpufreq settings, if you have any
programs.auto-cpufreq.settings = {
charger = {
governor = "performance";
turbo = "auto";
};
battery = {
governor = "powersave";
turbo = "auto";
};
};
# ---Snip---
}
```
</details>
<details>
<summary>Nixpkgs</summary>
<br>
There is a nixpkg available, but it is more prone to being outdated, whereas the flake pulls from the latest commit. You can install it in your `configuration.nix` and enable the system service:
```nix
# configuration.nix
# ---Snip---
environment.systemPackages = with pkgs; [
auto-cpufreq
];
services.auto-cpufreq.enable = true;
# ---Snip---
```
</details>
### Installation (development mode only)
- If you have `poetry` installed:
```bash
git clone https://github.com/AdnanHodzic/auto-cpufreq.git
cd auto-cpufreq
poetry install
poetry run auto-cpufreq --help
```
- Alternatively, we can use an editable pip install for development purposes:
```bash
git clone https://github.com/AdnanHodzic/auto-cpufreq.git
cd auto-cpufreq
# set up virtual environment (details removed for brevity)
pip3 install -e .
auto-cpufreq
```
- Regularly run `poetry update` if you get any inconsistent lock file issues.
## Post-installation
After installation, `auto-cpufreq` is available as a binary. Refer to [auto-cpufreq modes and options](https://github.com/AdnanHodzic/auto-cpufreq#auto-cpufreq-modes-and-options) for detailed information on how to run and configure `auto-cpufreq`.
## Configuring auto-cpufreq
auto-cpufreq makes all decisions automatically based on various factors such as CPU usage, temperature, and system load. However, it's possible to perform additional configurations:
### 1: power_helper.py script (Snap package install **only**)
When installing auto-cpufreq via [auto-cpufreq-installer](#auto-cpufreq-installer), if it detects the [GNOME Power Profiles service](https://twitter.com/fooctrl/status/1467469508373884933) is running, it will automatically disable it. Otherwise, that daemon will cause conflicts and various other performance issues.
However, when auto-cpufreq is installed as a Snap package it's running as part of a container with limited permissions, hence it's *highly recommended* to disable the GNOME Power Profiles daemon using the `power_helper.py` script.
**Please Note:**<br>
The [`power_helper.py`](https://github.com/AdnanHodzic/auto-cpufreq/blob/master/auto_cpufreq/power_helper.py) script is located within the auto-cpufreq repo at `auto_cpufreq/power_helper.py`. In order to access it, first clone
the repository:
`git clone https://github.com/AdnanHodzic/auto-cpufreq`
Make sure to have `psutil` & `pyinotify` Python library installed before next step:
If you're using Debian based distro install them by running:
`sudo apt install python3-psutil python3-pyinotify`
or manually using pip, e.g:
`sudo pip3 install psutil pyinotify --break-system-packages`
Then disable the GNOME Power Profiles daemon:
`sudo python3 -m auto_cpufreq.power_helper --gnome_power_disable`
for full list of options run --help, e.g:
`sudo python3 -m auto_cpufreq.power_helper --help`
### 2: `--force` governor override
By default, auto-cpufreq uses `balanced` mode which works best for many systems and situations.
However, you can override this behaviour by switching to `performance` or `powersave` mode manually. The `performance` mode results in higher default frequencies, but also higher energy use (battery consumption) and should only be used if maximum performance is needed. The `powersave` mode does the opposite and extends battery life to its maximum.
See [`--force` flag](#overriding-governor) for more info.
### 3: `--turbo` mode override
By default, auto-cpufreq handles CPU turbo mode automatically, enabling it under load and disabling it otherwise to balance performance and efficiency.
However, you can override this behavior by forcing CPU turbo's mode to `always` or `never`. Setting to `always` keeps turbo mode always enabled, allowing the CPU to reach its maximum frequency at the cost of higher energy use (battery consumption). `never`, on the other hand, keeps turbo mode always disabled, limiting the CPU's maximum frequency to extend battery life.
See [`--turbo` flag](#overriding-turbo-mode) for more info.
### 4: auto-cpufreq config file
You can configure separate profiles for the battery and power supply. These profiles will let you pick which governor to use, as well as how and when turbo boost is enabled. The possible values for turbo boost behavior are `always`, `auto`, and `never`. The default behavior is `auto`, which only activates turbo during high load.
By default, auto-cpufreq does not use a config file. If you wish to configure auto-cpufreq statically, we look for a configuration file in the following order:
1. Commandline argument: `--config <FILE>` if passed as commandline argument to `auto-cpufreq`
2. User-specific configuration: `$XDG_CONFIG_HOME/auto-cpufreq/auto-cpufreq.conf`
3. System-wide configuration: `/etc/auto-cpufreq.conf`
#### Example config file contents
```python
# settings for when connected to a power source
[charger]
# see available governors by running: cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
# preferred governor
governor = performance
# EPP: see available preferences by running: cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences
energy_performance_preference = performance
# EPB (Energy Performance Bias) for the intel_pstate driver
# see conversion info: https://www.kernel.org/doc/html/latest/admin-guide/pm/intel_epb.html
# available EPB options include a numeric value between 0-15
# (where 0 = maximum performance and 15 = maximum power saving),
# or one of the following strings:
# performance (0), balance_performance (4), default (6), balance_power (8), or power (15)
# if the parameter is missing in the config and the hardware supports this setting, the default value will be used
# the default value is `balance_performance` (for charger)
# energy_perf_bias = balance_performance
# Platform Profiles
# https://www.kernel.org/doc/html/latest/userspace-api/sysfs-platform_profile.html
# See available options by running:
# cat /sys/firmware/acpi/platform_profile_choices
# platform_profile = performance
# minimum cpu frequency (in kHz)
# example: for 800 MHz = 800000 kHz --> scaling_min_freq = 800000
# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html
# to use this feature, uncomment the following line and set the value accordingly
# scaling_min_freq = 800000
# maximum cpu frequency (in kHz)
# example: for 1GHz = 1000 MHz = 1000000 kHz -> scaling_max_freq = 1000000
# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html
# to use this feature, uncomment the following line and set the value accordingly
# scaling_max_freq = 1000000
# turbo boost setting. possible values: always, auto, never
turbo = auto
# settings for when using battery power
[battery]
# see available governors by running: cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
# preferred governor
governor = powersave
# EPP: see available preferences by running: cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences
energy_performance_preference = power
# EPB (Energy Performance Bias) for the intel_pstate driver
# see conversion info: https://www.kernel.org/doc/html/latest/admin-guide/pm/intel_epb.html
# available EPB options include a numeric value between 0-15
# (where 0 = maximum performance and 15 = maximum power saving),
# or one of the following strings:
# performance (0), balance_performance (4), default (6), balance_power (8), or power (15)
# if the parameter is missing in the config and the hardware supports this setting, the default value will be used
# the default value is `balance_power` (for battery)
# energy_perf_bias = balance_power
# Platform Profiles
# https://www.kernel.org/doc/html/latest/userspace-api/sysfs-platform_profile.html
# See available options by running:
# cat /sys/firmware/acpi/platform_profile_choices
# platform_profile = low-power
# minimum cpu frequency (in kHz)
# example: for 800 MHz = 800000 kHz --> scaling_min_freq = 800000
# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html
# to use this feature, uncomment the following line and set the value accordingly
# scaling_min_freq = 800000
# maximum cpu frequency (in kHz)
# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html
# example: for 1GHz = 1000 MHz = 1000000 kHz -> scaling_max_freq = 1000000
# to use this feature, uncomment the following line and set the value accordingly
# scaling_max_freq = 1000000
# turbo boost setting (always, auto, or never)
turbo = auto
# battery charging threshold
# reference: https://github.com/AdnanHodzic/auto-cpufreq/#battery-charging-thresholds
#enable_thresholds = true
#start_threshold = 20
#stop_threshold = 80
```
## How to run auto-cpufreq
auto-cpufreq should be run with with one of the following options:
auto-cpufreq can be run by simply running the `auto-cpufreq` and following on screen instructions, i.e:
- [monitor](#monitor)
- Monitor and see suggestions for CPU optimizations
`sudo auto-cpufreq`
- [live](#live)
- Monitor and automatically make (temporary) CPU optimizations
- [install](#install---auto-cpufreq-daemon) / [remove](#remove---auto-cpufreq-daemon)
- Install/remove daemon for (permanent) automatic CPU optimizations
- [install (GUI)](#install---auto-cpufreq-daemon)
- Install daemon via GUI for (permanent) automatic CPU optimizations
- [update](#update---auto-cpufreq-update)
- Update auto-cpufreq to the latest release
- [install_performance](#1-power_helperpy-script)
- Install daemon in "performance" mode
- [stats](#stats)
- View live stats of CPU optimizations made by daemon
- [bluetooth_boot_off](#bluetooth_boot_off)
- Turn off Bluetooth on boot (only)! Can be turned on any time later on.
- [bluetooth_boot_on](#bluetooth_boot_on)
- Turn on Bluetooth on boot.
- [force=TEXT](#overriding-governor)
- Force use of either the "powersave" or "performance" governor, or set to "reset" to go back to normal mode
- [turbo=TEXT](#overriding-turbo-mode)
- Force use of CPU turbo mode, if supported, with "never" or "always", or set to "auto" to automatically handle turbo mode
- config=TEXT
- Use config file at designated path
- debug
- Show debug info (include when submitting bugs)
- version
- Show currently installed version
- [donate](#financial-donation)
- To support the project
- help
- Shows all of the above options
Running `auto-cpufreq --help` will print the same list of options as above. Read [auto-cpufreq modes and options](#auto-cpufreq-modes-and-options) for more details.
## auto-cpufreq modes and options
@ -79,45 +487,268 @@ auto-cpufreq can be run by simply running the `auto-cpufreq` and following on sc
`sudo auto-cpufreq --monitor`
No changes are made to the system, and is solely made for demonstration purposes what auto-cpufreq could do differently for your system.
No changes are made to the system. This is solely to demonstrate what auto-cpufreq could do for your system.
### Live
`sudo auto-cpufreq --live`
Necessary changes are temporarily made to the system which are lost with system reboot. This mode is made to evaluate what the system would behave with auto-cpufreq permanently running on the system.
Necessary changes are temporarily made to the system over time, but this process and its changes are lost at system reboot. This mode is provided to evaluate how the system would behave with auto-cpufreq permanently running on the system.
### Overriding governor
`sudo auto-cpufreq --force=governor`
Force use of either the "powersave" or "performance" governor, or set to "reset" to go back to normal mode.
Please note that any set override will persist even after reboot.
### Overriding Turbo mode
`sudo auto-cpufreq --turbo=mode`
Force use of CPU turbo mode, if supported, with "never" or "always", or set to "auto" to automatically handle turbo mode.
Please note that any set override will persist even after reboot.
### Install - auto-cpufreq daemon
Necessary changes are made to the system for auto-cpufreq CPU optimizaton to persist across reboots. Daemon is deployed and then started as a systemd service. Changes are made automatically and live log is made for monitoring purposes.
Necessary changes are made to the system over time and this process will continue across reboots. The daemon is deployed and started as a systemd service. Changes are made automatically and live stats are generated for monitoring purposes.
**Install the daemon using CLI ([after installing auto-cpufreq](#installing-auto-cpufreq)):**
Installing the auto-cpufreq daemon using CLI is as simple as running the following command:
`sudo auto-cpufreq --install`
After daemon is installed, `auto-cpufreq` is available as a binary and is running in the background. Its logs can be viewed by running: `auto-cpufreq --log`
After the daemon is installed, `auto-cpufreq` is available as a binary and runs in the background. Its stats can be viewed by running: `auto-cpufreq --stats`
Since daemon is running as a systemd service, its status can be seen by running:
*Please note:* if the daemon is installed within a desktop environment, then its stats and options can be accessed via CLI or GUI. See "Install the daemon using GUI" below for more details.
**Install the daemon using GUI**
Starting with >= v2.0 [after installing auto-cpufreq](#installing-auto-cpufreq), an auto-cpufreq desktop entry (icon) is available, i.e.:
<img src="https://github.com/user-attachments/assets/f426d62b-00b0-4fa5-a72e-b352016ed448" width="640" alt="Example of auto-cpufreq desktop entry (icon)"/>
After selecting it to open the GUI, the auto-cpufreq daemon can be installed by clicking the "Install" button:
<img src="https://github.com/user-attachments/assets/5af47e5e-8b9e-4ff6-9ffc-e78acb623ce4" width="480" alt="The auto-cpufreq GUI's 'Install' button"/>
After that, the full auto-cpufreq GUI is available:
<img src="https://github.com/user-attachments/assets/9c7715c4-16b7-4a5c-86be-4c390276d9e8" width="640" alt="The full auto-cpufreq GUI"/>
*Please note:* after the daemon is installed (by any method), its stats and options are accessible via both CLI and GUI.
**auto-cpufreq daemon service**
Installing the auto-cpufreq daemon also enables the associated service (equivalent to `systemctl enable auto-cpufreq`), causing it to start on boot, and immediately starts it (equivalent to `systemctl start auto-cpufreq`).
Since the daemon is running as a systemd service, its status can be seen by running:
`systemctl status auto-cpufreq`
If installed via Snap package, daemon status can be viewed as follows:
`systemctl status snap.auto-cpufreq.service.service`
### Update - auto-cpufreq update
Update functionality works by cloning the auto-cpufreq repo, installing it via [auto-cpufreq-installer](#auto-cpufreq-installer), and performing a fresh [auto-cpufreq daemon install](#install---auto-cpufreq-daemon) to provide the [latest version's](https://github.com/AdnanHodzic/auto-cpufreq/releases) changes.
Update auto-cpufreq by running: `sudo auto-cpufreq --update`. By default, the latest revision is cloned to `/opt/auto-cpufreq/source`, thus maintaining existing directory structure.
Update and clone to a custom directory by running: `sudo auto-cpufreq --update=/path/to/directory`
### Remove - auto-cpufreq daemon
auto-cpufreq daemon and its systemd service, along with all its persistent changes can be removed by running:
The auto-cpufreq daemon, its systemd service, and all its persistent changes can be removed by running:
`sudo auto-cpufreq --remove`
### Log
This does, in part, the equivalent of `systemctl stop auto-cpufreq && systemctl disable auto-cpufreq`, but the above command should be used instead of using `systemctl`.
If daemon has been instaled, live log of CPU/system load monitoring and optimizaiton can be seen by running:
*Please note:* after the daemon is removed, the auto-cpufreq GUI and desktop entry (icon) are also removed.
`auto-cpufreq --log`
### Stats
If the daemon has been installed, live stats of CPU/system load monitoring and optimization can be seen by running:
`auto-cpufreq --stats`
### bluetooth_boot_off
Turn off Bluetooth on boot (only)! Bluetooth can still be turned on manually when needed. This option is executed during the installation of the auto-cpufreq daemon, but it can also be run independently without installing the daemon.
It prevents GNOME from automatically enabling Bluetooth on every reboot or after suspend/wake up even if you manually disable it, GNOME will turn it back on unless this option is used.
### bluetooth_boot_on
Useful if you prefer Bluetooth to be enabled at boot time, especially after installing the auto-cpufreq daemon, which will disable it by default.
## Battery charging thresholds
***Please note:** [Original implementor](https://github.com/AdnanHodzic/auto-cpufreq/pull/637) is looking for user input & testing to further improve this functionality. If you would like to help in this process, please refer to [Looking for developers and co-maintainers](https://github.com/AdnanHodzic/auto-cpufreq/#looking-for-developers-and-co-maintainers)*.
As of [v2.2.0](https://github.com/AdnanHodzic/auto-cpufreq/releases/tag/v2.2.0), battery charging thresholds can be set in the config file. This enforces your battery to start and stop charging at defined values.
### Supported devices
- **Lenovo ThinkPad** (thinkpad_acpi)*
- **Lenovo IdeaPad** (ideapad_acpi)*
***Please note, your laptop must have an installed ACPI kernel driver specific to the manufacturer.** To check if you have the correct module installed and loaded run `lsmod [module]`
**To request that your device be supported, please open an [issue](https://github.com/AdnanHodzic/auto-cpufreq/issues/new). In your issue, make us aware of the driver that works with your laptop**
### Battery config
Edit the config at `/etc/auto-cpufreq.conf`
Example config for battery ([already part of example config file](https://github.com/AdnanHodzic/auto-cpufreq/#example-config-file-contents))
```
[battery]
enable_thresholds = true
start_threshold = 20
stop_threshold = 80
```
### Lenovo_laptop conservation mode
this works only with `lenovo_laptop` kernel module compatable laptops.
add `ideapad_laptop_conservation_mode = true` to your `auto-cpufreq.conf` file
### Special cases of Lenovo_ideapad (or some other models with fixed threshold)
As you may know, for some laptop models you can only decide to limit battery charging but can not set the limit value. The limit value is set by the manufacturer in the system (generally 60% and sometimes 80%). Also, you can not set the value of start charging.
This limit value is not always accessible for users to avoid changing it, but you can try looking in some of these paths :
```
cat /sys/bus/platform/drivers/ideapad_acpi/VPC2004:00/charge_control_end_threshold
cat /sys/class/power_supply/BAT0/charge_control_end_threshold
cat /sys/class/power_supply/BAT0/charge_control_start_threshold
```
This is the config to apply at /etc/auto-cpufreq.conf in order to stop battery charging at 60% or 80% depending on the value set in the system by the manufacturer.
```
[battery]
enable_thresholds = true
start_threshold = 20
stop_threshold = 1
```
start_threshold = 20 (should be present with a valid number but it's ignored)
stop_threshold = 1 (to stop charging the battery at the limit value 60% or 80%)
### Ignoring power supplies
you may have a controler or headphones and when ever they may be on battery they might cause auto-cpufreq
to limit preformence to ignore them add to you config file the name of the power supply, under `[power_supply_ignore_list]`
the name of the power supply can be found with `ls /sys/class/power_supply/`
```
[power_supply_ignore_list]
name1 = this
name2 = is
name3 = an
name4 = example
# like this
xboxctrl = {the xbox controler power supply name}
```
## Troubleshooting
**Q:** If after installing auto-cpufreq you're (still) experiencing:
- high CPU temperatures
- CPU not scaling to minimum/maximum frequencies
- suboptimal CPU performance
- turbo mode not available
**A:** If you're using the `intel_pstate/amd-pstate` CPU management driver, consider changing it to `acpi-cpufreq`.
This can be done by editing the `GRUB_CMDLINE_LINUX_DEFAULT` params in `/etc/default/grub`. For instance:
```
sudo nano /etc/default/grub
# make sure you have nano installed, or you can use your favorite text editor
```
For Intel users:
```
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash intel_pstate=disable"
```
For AMD users:
```
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash initcall_blacklist=amd_pstate_init amd_pstate.enable=0"
```
Once you have made the necessary changes to the GRUB configuration file, you can update GRUB by running `sudo update-grub` on Debian/Ubuntu, `sudo grub-mkconfig -o /boot/grub/grub.cfg` on Arch Linux, or one of the following on Fedora:
```
sudo grub2-mkconfig -o /etc/grub2.cfg
```
```
sudo grub2-mkconfig -o /etc/grub2-efi.cfg
```
```
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
# legacy boot method
```
For systemd-boot users:
```
sudo nano /etc/kernel/cmdline
# make sure you have nano installed, or you can use your favorite text editor
```
For Intel users:
```
quiet splash intel_pstate=disable
```
For AMD users:
```
quiet splash initcall_blacklist=amd_pstate_init amd_pstate.enable=0
```
Once you have made the necessary changes to the `cmdline` file, you can update it by running `sudo reinstall-kernels`.
### AUR
- If the AUR installer does not work for your system, fallback to `auto-cpufreq-installer` and open an issue.
## Discussion:
* Blogpost: [auto-cpufreq - Automatic CPU speed & power optimizer for Linux](http://foolcontrol.org/?p=3124)
- Blogpost: [auto-cpufreq - Automatic CPU speed & power optimizer for Linux](http://foolcontrol.org/?p=3124)
## Donate
Since I'm working on this project in free time, please consider supporting this project by making a donation of any amount!
Showing your support and appreciation for the auto-cpufreq project can be done in two ways:
- Financial donation
- Code contribution
### Financial donation
If auto-cpufreq helped you out and you find it useful, show your appreciation by donating (any amount) to the project!
##### Become Github Sponsor
[Become a sponsor to Adnan Hodzic on Github](https://github.com/sponsors/AdnanHodzic) to acknowledge my efforts and help project's further open source development.
##### PayPal
[![paypal](https://www.paypalobjects.com/en_US/NL/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=7AHCP5PU95S4Y&item_name=Contribution+for+work+on+auto-cpufreq&currency_code=EUR&source=url)
@ -126,3 +757,9 @@ Since I'm working on this project in free time, please consider supporting this
[bc1qlncmgdjyqy8pe4gad4k2s6xtyr8f2r3ehrnl87](bitcoin:bc1qlncmgdjyqy8pe4gad4k2s6xtyr8f2r3ehrnl87)
[![bitcoin](https://foolcontrol.org/wp-content/uploads/2019/08/btc-donate-displaylink-debian.png)](bitcoin:bc1qlncmgdjyqy8pe4gad4k2s6xtyr8f2r3ehrnl87)
### Code contribution
Other ways of supporting the project consist of making a code or documentation contribution. If you have an idea for a new feature or you want to implement some of the existing feature requests or fix some of the [bugs & issues](https://github.com/AdnanHodzic/auto-cpufreq/issues), please make your changes and submit a [pull request](https://github.com/AdnanHodzic/auto-cpufreq/pulls). I'll be glad to review it and, if your changes are accepted, you'll be credited on the [releases page](https://github.com/AdnanHodzic/auto-cpufreq/releases).
**Please note: auto-cpufreq is looking for co-maintainers & open source developers to [help shape the future of the project!](https://github.com/AdnanHodzic/auto-cpufreq/discussions/312)**

View File

@ -1,196 +1,268 @@
#!/bin/bash
#
#!/usr/bin/env bash
# auto-cpufreq-installer:
# auto-cpufreq source code based installer
SCRIPT_PATH=$(readlink -f "$0")
SCRIPT_DIR=$(dirname "${SCRIPT_PATH}")
cd "${SCRIPT_DIR}"
cd "$(dirname "$(readlink -f "$0")")" || exit 1
distro="$(lsb_release -is)"
release="$(lsb_release -rs)"
codename="$(lsb_release -cs)"
COLOUMNS="`tput cols`"
MID="$((COLOUMNS / 2))"
separator(){
sep="\n-------------------------------------------------------------------------------"
echo -e $sep
APPLICATIONS_PATH="/usr/share/applications"
VENV_PATH="/opt/auto-cpufreq"
SHARE_DIR="/usr/local/share/auto-cpufreq/"
AUTO_CPUFREQ_FILE="/usr/local/bin/auto-cpufreq"
AUTO_CPUFREQ_GTK_FILE=$AUTO_CPUFREQ_FILE-gtk
AUTO_CPUFREQ_GTK_DESKTOP_FILE="$(basename $AUTO_CPUFREQ_GTK_FILE).desktop"
IMG_FILE="/usr/share/pixmaps/auto-cpufreq.png"
ORG_FILE="/usr/share/polkit-1/actions/org.auto-cpufreq.pkexec.policy"
function header {
echo
printf "%0.s─" $(seq $((MID-(${#1}/2)-2)))
printf " $1 "
printf "%0.s─" $(seq $((MID-(${#1}/2)-2)))
echo; echo
}
function ask_operation {
header "auto-cpufreq installer"
echo "Welcome to auto-cpufreq tool installer."; echo
read -p "Select a key [I]nstall/[R]emove or press ctrl+c to quit: " answer
}
function manual_install {
if command -v lsb_release > /dev/null; then
distro="$(lsb_release -is)"
release="$(lsb_release -rs)"
codename="$(lsb_release -cs)"
fi
echo "Didn't detect Debian or RedHat or Arch based distro."; echo
echo "To complete installation, you need to:"
echo "Install: python3, pip3, python3-setuptools, gobject-introspection, cairo (or cairo-devel), gcc, and gtk3"; echo
echo "Install necessary Python packages:"
echo "pip3 install psutil click distro power requests PyGObject"
echo "Run following sequence of lines:"; echo
echo "-----"; echo
echo "pip3 install ."
echo "mkdir -p $SHARE_DIR"
echo "cp -r scripts/ $SHARE_DIR"; echo
echo "-----"; echo
echo "After which tool is installed, for full list of options run:";echo
echo "auto-cpufreq --help"
echo; printf "%0.s─" $(seq $COLOUMNS); echo
echo "Consider creating a feature request to add support for your distro:"
echo "https://github.com/AdnanHodzic/auto-cpufreq/issues/new"; echo
echo "Make sure to include following information:"; echo
echo "Distribution: $distro"
echo "Release: $release"
echo "Codename: $codename"
echo
exit 1
}
function tool_install {
echo
# First argument is the distro
function detected_distro {
header "Detected $1 distribution"
header "Setting up Python environment"
}
if [ -f /etc/debian_version ]; then
detected_distro "Debian based"
VERSION=$(cat /etc/debian_version)
# install necessary libgirepository debian package dependencies
# https://github.com/AdnanHodzic/auto-cpufreq/pull/826#issuecomment-2794549837
apt update
if apt-cache show libgirepository-2.0-dev > /dev/null 2>&1; then
LIB_GI_REPO="libgirepository-2.0-dev"
PYGOBJECT_VER="^3.50.0"
else
LIB_GI_REPO="libgirepository1.0-dev"
# pin PYGOBJECT_VER to libgirepository1.0-dev
# https://github.com/AdnanHodzic/auto-cpufreq/issues/813#issuecomment-2712543486
PYGOBJECT_VER="3.50.0"
fi
# Update PyGObject in pyproject.toml
# https://github.com/AdnanHodzic/auto-cpufreq/pull/826#issuecomment-2794549837
if [ -f ./pyproject.toml ]; then
if grep -q 'PyGObject *= *{[^}]*version *= *"' pyproject.toml; then
sed -i "s/\(PyGObject *= *{[^}]*version *= *\"\)[^\"]*\(.*\)/\1$PYGOBJECT_VER\2/" pyproject.toml
echo "PyGObject version updated to $PYGOBJECT_VER in pyproject.toml"
else
echo "Warning: Could not find PyGObject version entry in pyproject.toml!"
fi
else
echo "Error: pyproject.toml not found and PyGObject version not updated!"
fi
echo "$LIB_GI_REPO needs to be installed for version $VERSION"
echo '---- '
apt install -y python3-dev python3-pip python3-venv python3-setuptools dmidecode \
"$LIB_GI_REPO" libcairo2-dev libgtk-3-dev gcc
elif [ -f /etc/redhat-release ]; then
detected_distro "RedHat based"
if [ -f /etc/centos-release ]; then yum install platform-python-devel
else yum install python-devel
fi
yum install dmidecode gcc cairo-devel gobject-introspection-devel cairo-gobject-devel gtk3-devel
elif [ -f /etc/solus-release ]; then
detected_distro "Solus"
eopkg install pip python3 python3-devel dmidecode gobject-introspection-devel libcairo-devel gcc libgtk-3
eopkg install -c system.devel
elif [ -f /etc/arch-release ]; then
detected_distro "Arch Linux based"
pacman -S --noconfirm --needed python python-pip python-setuptools base-devel dmidecode gobject-introspection gtk3 gcc
elif [ -f /etc/os-release ];then
. /etc/os-release
case $ID in
opensuse-leap)
detected_distro "OpenSUSE"
zypper install -y python3 python3-pip python311-setuptools python3-devel gcc dmidecode gobject-introspection-devel python3-cairo-devel gtk3 gtk3-devel
;;
opensuse-tumbleweed)
detected_distro "OpenSUSE"
zypper install -y python3 python3-pip python311-setuptools python3-devel gcc dmidecode gobject-introspection-devel python3-cairo-devel gtk3 gtk3-devel
;;
void)
detected_distro "Void Linux"
xbps-install -Sy python3 python3-pip python3-devel python3-setuptools base-devel dmidecode cairo-devel gobject-introspection gcc gtk+3
;;
nixos)
echo "NixOS detected"
echo "This installer is not supported on NixOS."
echo "Please refer to the install instructions for NixOS at https://github.com/AdnanHodzic/auto-cpufreq#nixos"
exit 1
;;
*) manual_install;;
esac
else # In case /etc/os-release doesn't exist
manual_install
fi
header "Installing necessary Python packages"
venv_dir=$VENV_PATH/venv
mkdir -p "$venv_dir"
python3 -m venv "$venv_dir"
source "$venv_dir/bin/activate"
python3 -m pip install --upgrade pip wheel
# debian specific PyGObject Installation
if [ -f /etc/debian_version ]; then
VERSION=$(cat /etc/debian_version | cut -d'.' -f1)
if [[ "$VERSION" =~ ^12(\.[0-9]+)?$ ]]; then
python3 -m pip install PyGObject==3.50.0
fi
fi
python3 -m pip install PyGObject
header "Installing auto-cpufreq tool"
git config --global --add safe.directory $(pwd)
python -m pip install .
mkdir -p $SHARE_DIR
cp -r scripts/ $SHARE_DIR
cp -r images/ $SHARE_DIR
cp images/icon.png $IMG_FILE
cp scripts/$(basename $ORG_FILE) $(dirname $ORG_FILE)
# this is necessary since we need this script before we can run auto-cpufreq itself
cp scripts/auto-cpufreq-venv-wrapper $AUTO_CPUFREQ_FILE
chmod a+x $AUTO_CPUFREQ_FILE
cp scripts/start_app $AUTO_CPUFREQ_GTK_FILE
chmod a+x $AUTO_CPUFREQ_GTK_FILE
desktop-file-install --dir=$APPLICATIONS_PATH scripts/$AUTO_CPUFREQ_GTK_DESKTOP_FILE
update-desktop-database $APPLICATIONS_PATH
header "auto-cpufreq tool successfully installed"
echo "For list of options, run:"
echo "auto-cpufreq --help"; echo
}
function tool_remove {
# stop any running auto-cpufreq argument (daemon/live/monitor)
tool_arg_pids=($(pgrep -f "auto-cpufreq --"))
for pid in "${tool_arg_pids[@]}"; do [ $pid != $$ ] && kill "$pid"; done
function remove_directory {
[ -d $1 ] && rm -rf $1
}
function remove_file {
[ -f $1 ] && rm $1
}
srv_remove="$AUTO_CPUFREQ_FILE-remove"
# run uninstall in case of installed daemon
if [ -f $srv_remove -o -f $AUTO_CPUFREQ_FILE ]; then
eval "$AUTO_CPUFREQ_FILE --remove"
else
echo; echo "Couldn't remove the auto-cpufreq daemon, $srv_remove do not exist."
fi
# remove auto-cpufreq and all its supporting files
remove_directory $SHARE_DIR
remove_file "$AUTO_CPUFREQ_FILE-install"
remove_file $srv_remove
remove_file $AUTO_CPUFREQ_FILE
remove_file $AUTO_CPUFREQ_GTK_FILE
remove_file $IMG_FILE
remove_file $ORG_FILE
remove_file "/usr/local/bin/cpufreqctl.auto-cpufreq"
remove_file "/var/run/auto-cpufreq.stats"
remove_file "$APPLICATIONS_PATH/$AUTO_CPUFREQ_GTK_DESKTOP_FILE"
update-desktop-database $APPLICATIONS_PATH
# remove python virtual environment
remove_directory $venv_path
echo; echo "auto-cpufreq tool and all its supporting files successfully removed"; echo
}
# root check
root_check(){
if (( $EUID != 0 ));
then
separator
echo -e "\nMust be run as root (i.e: 'sudo $0')."
separator
exit 1
if ((EUID != 0)); then
echo; echo "Must be run as root (i.e: 'sudo $0')."; echo
exit 1
fi
}
# python packages install
pip_pkg_install(){
pip3 install psutil click distro power setuptools
}
complete_msg(){
echo -e "\nauto-cpufreq tool successfully installed.\n"
echo -e "For list of options, run:\nauto-cpufreq"
}
# tool install
install(){
python3 setup.py install --record files.txt
mkdir -p /usr/local/share/auto-cpufreq/
cp -r scripts/ /usr/local/share/auto-cpufreq/
}
tool_install(){
# Debian
if [ -f /etc/debian_version ];
then
separator
echo -e "\nDetected Debian based distribution"
separator
echo -e "\nSetting up Python environment\n"
apt install python3-dev python3-pip inxi -y
separator
echo -e "\nInstalling necessary Python packages\n"
pip_pkg_install
separator
echo -e "\ninstalling auto-cpufreq tool\n"
install
separator
complete_msg
separator
# RedHat
elif [ -f /etc/redhat-release ];
then
separator
echo -e "\nDetected RedHat based distribution\n"
echo -e "\nSetting up Python environment\n"
# CentOS exception
if [ -f /etc/centos-release ];
then
yum install platform-python-devel inxi
else
yum install python-devel inxi
fi
echo -e "\nInstalling necessary Python packages\n"
pip_pkg_install
separator
echo -e "\ninstalling auto-cpufreq tool\n"
install
separator
complete_msg
separator
# Other
if [[ -z "$1" ]]; then ask_operation
else
separator
echo -e "\nDidn't detect Debian or RedHat based distro.\n"
echo -e "To complete installation, you need to:"
echo -e "Install: python3, pip3 and inxi\n"
echo -e "Install necessary Python packages:"
echo -e "pip3 install psutil click distro power"
echo -e "\nRun following sequence of lines:"
echo -e "\n-----"
echo -e "\npython3 setup.py install --record files.txt"
echo -e "mkdir -p /usr/local/share/auto-cpufreq/"
echo -e "cp -r scripts/ /usr/local/share/auto-cpufreq/"
echo -e "\n-----"
echo -e "\nAfter which tool is installed, for full list of options run:"
echo -e "auto-cpufreq"
separator
echo -e "\nConsider creating a feature request to add support for your distro:"
echo -e "https://github.com/AdnanHodzic/auto-cpufreq/issues/new"
echo -e "\nMake sure to include following information:\n"
echo -e "Distribution: $distro"
echo -e "Release: $release"
echo -e "Codename: $codename"
separator
fi
}
tool_remove(){
files="files.txt"
share_dir="/usr/local/share/auto-cpufreq/"
srv_install="/usr/bin/auto-cpufreq-install"
srv_remove="/usr/bin/auto-cpufreq-remove"
log_file="/var/log/auto-cpufreq.log"
tool_proc_rm="auto-cpufreq --remove"
# stop any running auto-cpufreq argument (daemon/live/monitor)
tool_arg_pids=( $(pgrep -f "auto-cpufreq --") )
for pid in "${tool_arg_pids[@]}"; do
if [[ $tool_arg_pids != $$ ]]; then
kill "$tool_arg_pids"
fi
done
# run uninstall in case of installed daemon
if [ -f $srv_remove ]
then
eval $tool_proc_rm
fi
# remove auto-cpufreq and all its supporting files
[ -f $files ] && cat $files | xargs sudo rm -rf && rm -f $files
[ -f $share_dir ] && rm -rf $share_dir
# files cleanup
[ -f $srv_install ] && rm $srv_install
[ -f $srv_remove ] && rm $srv_remove
[ -f $log_file ] && rm $log_file
separator
echo -e "\nauto-cpufreq tool and all its supporting files successfully removed.\n"
}
ask_operation(){
echo -e "\n-------------------------- auto-cpufreq installer -----------------------------"
echo -e "\nWelcome to auto-cpufreq tool installer."
echo -e "\nOptions:\n"
read -p "[I]nstall
[R]emove
[Q]uit
Select a key: [i/r/q]: " answer
}
root_check
if [[ -z "${1}" ]];
then
ask_operation
else
case "${1}" in
"--install")
answer="i"
;;
"--remove")
answer="r"
;;
*)
answer="n"
;;
case "$1" in
--install) answer="i";;
--remove) answer="r";;
*) ask_operation;;
esac
fi
if [[ $answer == [Ii] ]];
then
root_check
tool_install
elif [[ $answer == [Rr] ]];
then
root_check
tool_remove
elif [[ $answer == [Qq] ]];
then
separator
echo ""
exit 0
else
separator
echo -e "\nUnknown key, aborting ...\n"
case $answer in
I|i) tool_install;;
R|r) tool_remove;;
*)
echo "Unknown key, aborting ..."; echo
exit 1
fi
;;
esac

104
auto-cpufreq.conf-example Normal file
View File

@ -0,0 +1,104 @@
# settings for when connected to a power source
[charger]
# see available governors by running: cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
# preferred governor.
governor = performance
# EPP: see available preferences by running: cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences
energy_performance_preference = performance
# EPB (Energy Performance Bias) for the intel_pstate driver
# see conversion info: https://www.kernel.org/doc/html/latest/admin-guide/pm/intel_epb.html
# available EPB options include a numeric value between 0-15
# (where 0 = maximum performance and 15 = maximum power saving),
# or one of the following strings:
# performance (0), balance_performance (4), default (6), balance_power (8), or power (15)
# if the parameter is missing in the config and the hardware supports this setting, the default value will be used
# the default value is `balance_performance` (for charger)
# energy_perf_bias = balance_performance
# Platform Profiles
# https://www.kernel.org/doc/html/latest/userspace-api/sysfs-platform_profile.html
# See available options by running:
# cat /sys/firmware/acpi/platform_profile_choices
# platform_profile = performance
# minimum cpu frequency (in kHz)
# example: for 800 MHz = 800000 kHz --> scaling_min_freq = 800000
# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html
# to use this feature, uncomment the following line and set the value accordingly
# scaling_min_freq = 800000
# maximum cpu frequency (in kHz)
# example: for 1GHz = 1000 MHz = 1000000 kHz -> scaling_max_freq = 1000000
# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html
# to use this feature, uncomment the following line and set the value accordingly
# scaling_max_freq = 1000000
# turbo boost setting. possible values: always, auto, never
turbo = auto
# this is for ignoring controllers and other connected devices battery from affecting
# laptop preformence
# [power_supply_ignore_list]
# name1 = this
# name2 = is
# name3 = an
# name4 = example
# settings for when using battery power
[battery]
# see available governors by running: cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
# preferred governor
governor = powersave
# EPP: see available preferences by running: cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences
energy_performance_preference = power
# EPB (Energy Performance Bias) for the intel_pstate driver
# see conversion info: https://www.kernel.org/doc/html/latest/admin-guide/pm/intel_epb.html
# available EPB options include a numeric value between 0-15
# (where 0 = maximum performance and 15 = maximum power saving),
# or one of the following strings:
# performance (0), balance_performance (4), default (6), balance_power (8), or power (15)
# if the parameter is missing in the config and the hardware supports this setting, the default value will be used
# the default value is `balance_power` (for battery)
# energy_perf_bias = balance_power
# Platform Profiles
# https://www.kernel.org/doc/html/latest/userspace-api/sysfs-platform_profile.html
# See available options by running:
# cat /sys/firmware/acpi/platform_profile_choices
# platform_profile = low-power
# minimum cpu frequency (in kHz)
# example: for 800 MHz = 800000 kHz --> scaling_min_freq = 800000
# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html
# to use this feature, uncomment the following line and set the value accordingly
# scaling_min_freq = 800000
# maximum cpu frequency (in kHz)
# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html
# example: for 1GHz = 1000 MHz = 1000000 kHz -> scaling_max_freq = 1000000
# to use this feature, uncomment the following line and set the value accordingly
# scaling_max_freq = 1000000
# turbo boost setting. possible values: always, auto, never
turbo = auto
# experimental
# Add battery charging threshold (currently only available to Lenovo)
# checkout README.md for more info
# enable thresholds true or false
#enable_thresholds = true
#
# start threshold (0 is off ) can be 0-99
#start_threshold = 0
#
# stop threshold (100 is off) can be 1-100
#stop_threshold = 100

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
from subprocess import PIPE, run
from auto_cpufreq.battery_scripts.ideapad_acpi import ideapad_acpi_print_thresholds, ideapad_acpi_setup
from auto_cpufreq.battery_scripts.ideapad_laptop import ideapad_laptop_print_thresholds, ideapad_laptop_setup
from auto_cpufreq.battery_scripts.thinkpad import thinkpad_print_thresholds, thinkpad_setup
def lsmod(module): return module in run(['lsmod'], stdout=PIPE, stderr=PIPE, text=True, shell=True).stdout
def battery_get_thresholds():
if lsmod("ideapad_acpi"): ideapad_acpi_print_thresholds()
elif lsmod("ideapad_laptop"): ideapad_laptop_print_thresholds()
elif lsmod("thinkpad_acpi"): thinkpad_print_thresholds()
else: return
def battery_setup():
if lsmod("ideapad_acpi"): ideapad_acpi_setup()
elif lsmod("ideapad_laptop"): ideapad_laptop_setup()
elif lsmod("thinkpad_acpi"): thinkpad_setup()
else: return

View File

@ -0,0 +1,38 @@
#!/usr/bin/env python3
import os
from subprocess import check_output
from auto_cpufreq.config.config import config
from auto_cpufreq.globals import POWER_SUPPLY_DIR
def set_battery(value, mode, bat):
path = f"{POWER_SUPPLY_DIR}{bat}/charge_{mode}_threshold"
if os.path.isfile(path): check_output(f"echo {value} | tee {path}", shell=True, text=True)
else: print(f"WARNING: {path} does NOT exist")
def get_threshold_value(mode):
conf = config.get_config()
return conf["battery"][f"{mode}_threshold"] if conf.has_option("battery", f"{mode}_threshold") else (0 if mode == "start" else 100)
def ideapad_acpi_setup():
conf = config.get_config()
if not (conf.has_option("battery", "enable_thresholds") and conf["battery"]["enable_thresholds"] == "true"): return
if os.path.exists(POWER_SUPPLY_DIR):
batteries = [name for name in os.listdir(POWER_SUPPLY_DIR) if name.startswith('BAT')]
for bat in batteries:
set_battery(get_threshold_value("start"), "start", bat)
set_battery(get_threshold_value("stop"), "stop", bat)
else: print("WARNING: could NOT access", POWER_SUPPLY_DIR)
def ideapad_acpi_print_thresholds():
batteries = [name for name in os.listdir(POWER_SUPPLY_DIR) if name.startswith('BAT')]
print("\n-------------------------------- Battery Info ---------------------------------\n")
print(f"battery count = {len(batteries)}")
for bat in batteries:
try:
print(bat, "start threshold =", check_output(["cat", POWER_SUPPLY_DIR+bat+"/charge_start_threshold"]))
print(bat, "stop threshold =", check_output(["cat", POWER_SUPPLY_DIR+bat+"/charge_stop_threshold"]))
except Exception as e: print(f"ERROR: failed to read battery {bat} thresholds:", repr(e))

View File

@ -0,0 +1,69 @@
#!/usr/bin/env python3
import os
from subprocess import check_output
from auto_cpufreq.config.config import config
from auto_cpufreq.globals import CONSERVATION_MODE_FILE, POWER_SUPPLY_DIR
def set_battery(value, mode, bat):
path = f"{POWER_SUPPLY_DIR}{bat}/charge_{mode}_threshold"
if os.path.exists(path):
check_output(f"echo {value} | tee {POWER_SUPPLY_DIR}{bat}/charge_{mode}_threshold", shell=True, text=True)
else: print(f"WARNING: {path} does NOT exist")
def get_threshold_value(mode):
conf = config.get_config()
return conf["battery"][f"{mode}_threshold"] if conf.has_option("battery", f"{mode}_threshold") else (0 if mode == "start" else 100)
def conservation_mode(value):
try:
check_output(f"echo {value} | tee {CONSERVATION_MODE_FILE}", shell=True, text=True)
print(f"conservation_mode is {value}")
except: print("unable to set conservation mode")
return
def check_conservation_mode():
try:
value = check_output(["cat", CONSERVATION_MODE_FILE], text=True).rstrip()
if value == "1": return True
elif value == "0": return False
else:
print("could not get value from conservation mode")
return None
except:
print("could not get the value from conservation mode")
return False
def ideapad_laptop_setup():
conf = config.get_config()
if not (conf.has_option("battery", "enable_thresholds") and conf["battery"]["enable_thresholds"] == "true"): return
batteries = [name for name in os.listdir(POWER_SUPPLY_DIR) if name.startswith("BAT")]
if conf.has_option("battery", "ideapad_laptop_conservation_mode"):
if conf["battery"]["ideapad_laptop_conservation_mode"] == "true":
conservation_mode(1)
return
if conf["battery"]["ideapad_laptop_conservation_mode"] == "false": conservation_mode(0)
if not check_conservation_mode():
for bat in batteries:
set_battery(get_threshold_value("start"), "start", bat)
set_battery(get_threshold_value("stop"), "stop", bat)
else: print("conservation mode is enabled unable to set thresholds")
def ideapad_laptop_print_thresholds():
if check_conservation_mode():
print("conservation mode is on")
return
batteries = [name for name in os.listdir(POWER_SUPPLY_DIR) if name.startswith("BAT")]
print("\n-------------------------------- Battery Info ---------------------------------\n")
print(f"battery count = {len(batteries)}")
for bat in batteries:
try:
print(bat, "start threshold =", check_output(["cat", POWER_SUPPLY_DIR+bat+"/charge_start_threshold"]))
print(bat, "stop threshold =", check_output(["cat", POWER_SUPPLY_DIR+bat+"/charge_stop_threshold"]))
except Exception as e: print(f"ERROR: failed to read battery {bat} thresholds:", repr(e))

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
import os
from subprocess import check_output
from auto_cpufreq.config.config import config
from auto_cpufreq.globals import POWER_SUPPLY_DIR
def set_battery(value, mode, bat):
path = f"{POWER_SUPPLY_DIR}{bat}/charge_{mode}_threshold"
if os.path.isfile(path): check_output(f"echo {value} | tee {path}", shell=True, text=True)
else: print(f"WARNING: {path} does NOT exist")
def get_threshold_value(mode):
conf = config.get_config()
return conf["battery"][f"{mode}_threshold"] if conf.has_option("battery", f"{mode}_threshold") else (0 if mode == "start" else 100)
def thinkpad_setup():
conf = config.get_config()
if not (conf.has_option("battery", "enable_thresholds") and conf["battery"]["enable_thresholds"] == "true"): return
if os.path.exists(POWER_SUPPLY_DIR):
batteries = [name for name in os.listdir(POWER_SUPPLY_DIR) if name.startswith('BAT')]
for bat in batteries:
set_battery(get_threshold_value("start"), "start", bat)
set_battery(get_threshold_value("stop"), "stop", bat)
else: print(f"WARNING {POWER_SUPPLY_DIR} does NOT esixt")
def thinkpad_print_thresholds():
batteries = [name for name in os.listdir(POWER_SUPPLY_DIR) if name.startswith('BAT')]
print("\n-------------------------------- Battery Info ---------------------------------\n")
print(f"battery count = {len(batteries)}")
for bat in batteries:
try:
print(bat, "start threshold =", check_output(["cat", POWER_SUPPLY_DIR+bat+"/charge_start_threshold"]))
print(bat, "stop threshold =", check_output(["cat", POWER_SUPPLY_DIR+bat+"/charge_stop_threshold"]))
except Exception as e: print(f"ERROR: failed to read battery {bat} thresholds:", repr(e))

301
auto_cpufreq/bin/auto_cpufreq.py Executable file
View File

@ -0,0 +1,301 @@
#!/usr/bin/env python3
#
# auto-cpufreq - Automatic CPU speed & power optimizer for Linux
#
# Blog post: https://foolcontrol.org/?p=3124
# core import
import sys, time, os
from subprocess import run
from shutil import rmtree
from auto_cpufreq.battery_scripts.battery import *
from auto_cpufreq.config.config import config as conf, find_config_file
from auto_cpufreq.core import *
from auto_cpufreq.globals import GITHUB, IS_INSTALLED_WITH_AUR, IS_INSTALLED_WITH_SNAP
from auto_cpufreq.modules.system_monitor import ViewType, SystemMonitor
# import everything from power_helper, including bluetooth_disable and bluetooth_enable
from auto_cpufreq.power_helper import *
from threading import Thread
@click.command()
@click.option("--monitor", is_flag=True, help="Monitor and see suggestions for CPU optimizations")
@click.option("--live", is_flag=True, help="Monitor and make (temp.) suggested CPU optimizations")
@click.option("--daemon", is_flag=True, hidden=True)
@click.option("--install", is_flag=True, help="Install daemon for (permanent) automatic CPU optimizations")
@click.option("--update", is_flag=False, help="Update daemon and package for (permanent) automatic CPU optimizations", flag_value="--update")
@click.option("--remove", is_flag=True, help="Remove daemon for (permanent) automatic CPU optimizations")
@click.option("--force", is_flag=False, help="Force use of either \"powersave\" or \"performance\" governors. Setting to \"reset\" will go back to normal mode")
@click.option("--turbo", is_flag=False, help="Force use of CPU turbo mode, if supported, with \"never\" or \"always\". Setting to \"auto\" automatically handles turbo mode")
@click.option("--config", is_flag=False, required=False, help="Use config file at defined path",)
@click.option("--stats", is_flag=True, help="View live stats of CPU optimizations made by daemon")
@click.option("--get-state", is_flag=True, hidden=True)
@click.option("--bluetooth_boot_off", is_flag=True, help="Turn off Bluetooth on boot")
@click.option("--bluetooth_boot_on", is_flag=True, help="Turn on Bluetooth on boot")
@click.option("--debug", is_flag=True, help="Show debug info (include when submitting bugs)")
@click.option("--version", is_flag=True, help="Show currently installed version")
@click.option("--donate", is_flag=True, help="Support the project")
def main(monitor, live, daemon, install, update, remove, force, turbo, config, stats, get_state,
bluetooth_boot_off, bluetooth_boot_on, debug, version, donate):
# display info if config file is used
config_path = find_config_file(config)
conf.set_path(config_path)
def config_info_dialog():
if conf.has_config():
print("\nUsing settings defined in " + config_path + " file")
if len(sys.argv) == 1:
print("\n" + "-" * 32 + " auto-cpufreq " + "-" * 33 + "\n")
print("Automatic CPU speed & power optimizer for Linux")
print("\nExample usage:\nauto-cpufreq --monitor")
print("\n-----\n")
run(["auto-cpufreq", "--help"])
footer()
else:
# set governor override unless None or invalid
if force is not None:
not_running_daemon_check()
root_check() # Calling root_check before set_override as it will require sudo access
set_override(force) # Calling set override, only if force has some values
if turbo is not None:
not_running_daemon_check()
root_check()
set_turbo_override(turbo)
if monitor:
root_check()
battery_setup()
conf.notifier.start()
if IS_INSTALLED_WITH_SNAP:
gnome_power_detect_snap()
tlp_service_detect_snap()
else:
gnome_power_detect()
tlp_service_detect()
if IS_INSTALLED_WITH_SNAP or tlp_stat_exists or (systemctl_exists and not bool(gnome_power_status)):
try:
input("press Enter to continue or Ctrl + c to exit...")
except KeyboardInterrupt:
conf.notifier.stop()
sys.exit(0)
monitor = SystemMonitor(suggestion=True, type=ViewType.MONITOR)
monitor.run(on_quit=conf.notifier.stop)
elif live:
root_check()
battery_setup()
conf.notifier.start()
if IS_INSTALLED_WITH_SNAP:
gnome_power_detect_snap()
tlp_service_detect_snap()
else:
gnome_power_detect_install()
gnome_power_stop_live()
tuned_stop_live()
tlp_service_detect()
if IS_INSTALLED_WITH_SNAP or tlp_stat_exists or (systemctl_exists and not bool(gnome_power_status)):
try:
input("press Enter to continue or Ctrl + c to exit...")
except KeyboardInterrupt:
conf.notifier.stop()
sys.exit(0)
cpufreqctl()
def live_daemon():
# Redirect stdout to suppress prints
class NullWriter:
def write(self, _): pass
def flush(self): pass
try:
sys.stdout = NullWriter()
while True:
time.sleep(1)
set_autofreq()
except:
pass
def live_daemon_off():
gnome_power_start_live()
tuned_start_live()
cpufreqctl_restore()
conf.notifier.stop()
thread = Thread(target=live_daemon, daemon=True)
thread.start()
monitor = SystemMonitor(type=ViewType.LIVE)
monitor.run(on_quit=live_daemon_off)
elif daemon:
config_info_dialog()
root_check()
file_stats()
if IS_INSTALLED_WITH_SNAP and SNAP_DAEMON_CHECK == "enabled":
gnome_power_detect_snap()
tlp_service_detect_snap()
elif not IS_INSTALLED_WITH_SNAP:
gnome_power_detect()
tlp_service_detect()
battery_setup()
conf.notifier.start()
while True:
try:
footer()
gov_check()
cpufreqctl()
distro_info()
sysinfo()
set_autofreq()
countdown(2)
except KeyboardInterrupt: break
conf.notifier.stop()
elif install:
root_check()
if IS_INSTALLED_WITH_SNAP:
running_daemon_check()
gnome_power_detect_snap()
tlp_service_detect_snap()
bluetooth_notif_snap()
gov_check()
run("snapctl set daemon=enabled", shell=True)
run("snapctl start --enable auto-cpufreq", shell=True)
else:
running_daemon_check()
gov_check()
deploy_daemon()
deploy_complete_msg()
elif update:
root_check()
custom_dir = "/opt/auto-cpufreq/source"
for arg in sys.argv:
if arg.startswith("--update="):
custom_dir = arg.split("=")[1]
sys.argv.remove(arg)
if "--update" in sys.argv:
update = True
sys.argv.remove("--update")
if len(sys.argv) == 2: custom_dir = sys.argv[1]
if IS_INSTALLED_WITH_SNAP:
print("Detected auto-cpufreq was installed using snap")
# refresh snap directly using this command
# path wont work in this case
print("Please update using snap package manager, i.e: `sudo snap refresh auto-cpufreq`.")
#check for AUR
elif IS_INSTALLED_WITH_AUR: print("Arch-based distribution with AUR support detected. Please refresh auto-cpufreq using your AUR helper.")
else:
is_new_update = check_for_update()
if not is_new_update: return
ans = input("Do you want to update auto-cpufreq to the latest release? [Y/n]: ").strip().lower()
if not os.path.exists(custom_dir): os.makedirs(custom_dir)
if os.path.exists(os.path.join(custom_dir, "auto-cpufreq")): rmtree(os.path.join(custom_dir, "auto-cpufreq"))
if ans in ['', 'y', 'yes']:
remove_daemon()
remove_complete_msg()
new_update(custom_dir)
print("enabling daemon")
run(["auto-cpufreq", "--install"])
print("auto-cpufreq is installed with the latest version")
run(["auto-cpufreq", "--version"])
else: print("Aborted")
elif remove:
root_check()
if IS_INSTALLED_WITH_SNAP:
run("snapctl set daemon=disabled", shell=True)
run("snapctl stop --disable auto-cpufreq", shell=True)
if auto_cpufreq_stats_path.exists():
if auto_cpufreq_stats_file is not None:
auto_cpufreq_stats_file.close()
auto_cpufreq_stats_path.unlink()
# ToDo:
# {the following snippet also used in --update, update it there too(if required)}
# * undo bluetooth boot disable
gnome_power_rm_reminder_snap()
else: remove_daemon()
remove_complete_msg()
elif stats:
not_running_daemon_check()
config_info_dialog()
if IS_INSTALLED_WITH_SNAP:
gnome_power_detect_snap()
tlp_service_detect_snap()
else:
gnome_power_detect()
tlp_service_detect()
if IS_INSTALLED_WITH_SNAP or tlp_stat_exists or (systemctl_exists and not bool(gnome_power_status)):
try:
input("press Enter to continue or Ctrl + c to exit...")
except KeyboardInterrupt:
conf.notifier.stop()
sys.exit(0)
monitor = SystemMonitor(type=ViewType.STATS)
monitor.run()
elif get_state:
not_running_daemon_check()
override = get_override()
print(override)
elif bluetooth_boot_off:
if IS_INSTALLED_WITH_SNAP:
footer()
bluetooth_notif_snap()
footer()
else:
footer()
root_check()
bluetooth_disable()
footer()
elif bluetooth_boot_on:
if IS_INSTALLED_WITH_SNAP:
footer()
bluetooth_on_notif_snap()
footer()
else:
footer()
root_check()
bluetooth_enable()
footer()
elif debug:
# ToDo: add status of GNOME Power Profile service status
config_info_dialog()
root_check()
battery_get_thresholds()
cpufreqctl()
footer()
distro_info()
sysinfo()
print()
app_version()
print()
python_info()
print()
device_info()
print(f"Battery is: {'' if charging() else 'dis'}charging")
print()
app_res_use()
get_load()
get_current_gov()
get_turbo()
footer()
elif version:
footer()
distro_info()
app_version()
footer()
elif donate:
footer()
print("If auto-cpufreq helped you out and you find it useful ...\n")
print("Show your appreciation by donating!")
print(GITHUB+"#donate")
footer()
if __name__ == "__main__": main()

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib
from auto_cpufreq.gui.app import ToolWindow
def main():
GLib.set_prgname("auto-cpufreq-gtk")
win = ToolWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
win.handle_update()
Gtk.main()
if __name__ == "__main__": main()

View File

@ -0,0 +1,68 @@
import os, pyinotify, sys
from configparser import ConfigParser, ParsingError
from subprocess import run, PIPE
from auto_cpufreq.config.config_event_handler import ConfigEventHandler
def find_config_file(args_config_file) -> str:
"""
Find the config file to use.
Look for a config file in the following priorization order:
1. Command line argument
2. User config file
3. System config file
:param args_config_file: Path to the config file provided as a command line argument
:return: The path to the config file to use
"""
# Prepare paths
# use $SUDO_USER or $USER to get home dir since sudo can't access
# user env vars
home = run(["getent passwd ${SUDO_USER:-$USER} | cut -d: -f6"],
shell=True,
stdout=PIPE,
universal_newlines=True
).stdout.rstrip()
user_config_dir = os.getenv("XDG_CONFIG_HOME", default=os.path.join(home, ".config"))
user_config_file = os.path.join(user_config_dir, "auto-cpufreq/auto-cpufreq.conf")
system_config_file = "/etc/auto-cpufreq.conf"
if args_config_file is not None: # (1) Command line argument was specified
# Check if the config file path points to a valid file
if os.path.isfile(args_config_file): return args_config_file
else:
# Not a valid file
print(f"Config file specified with '--config {args_config_file}' not found.")
sys.exit(1)
elif os.path.isfile(user_config_file): return user_config_file # (2) User config file
else: return system_config_file # (3) System config file (default if nothing else is found)
class _Config:
def __init__(self) -> None:
self.path: str = ""
self._config: ConfigParser = ConfigParser()
self.watch_manager: pyinotify.WatchManager = pyinotify.WatchManager()
self.config_handler = ConfigEventHandler(self)
# check for file changes using threading
self.notifier: pyinotify.ThreadedNotifier = pyinotify.ThreadedNotifier(self.watch_manager, self.config_handler)
def set_path(self, path: str) -> None:
self.path = path
mask = pyinotify.IN_CREATE | pyinotify.IN_DELETE | pyinotify.IN_MODIFY | pyinotify.IN_MOVED_FROM | pyinotify.IN_MOVED_TO
self.watch_manager.add_watch(os.path.dirname(path), mask=mask)
if os.path.isfile(path): self.update_config()
def has_config(self) -> bool: return os.path.isfile(self.path)
def get_config(self) -> ConfigParser: return self._config
def update_config(self) -> None:
# create new ConfigParser to prevent old data from remaining
self._config = ConfigParser()
try: self._config.read(self.path)
except ParsingError as e: print(f"The following error occured while parsing the config file: \n{repr(e)}")
config = _Config()

View File

@ -0,0 +1,23 @@
from pyinotify import Event, ProcessEvent
class ConfigEventHandler(ProcessEvent):
def __init__(self, config) -> None:
self.config = config
def _process_update(self, event: Event):
if event.pathname.rstrip("~") == self.config.path: self.config.update_config()
# activates when auto-cpufreq config file is modified
def process_IN_MODIFY(self, event: Event) -> None: self._process_update(event)
# activates when auto-cpufreq config file is deleted
def process_IN_DELETE(self, event: Event) -> None: self._process_update(event)
# activates when auto-cpufreq config file is created
def process_IN_CREATE(self, event: Event) -> None: self._process_update(event)
# activates when auto-cpufreq config file is moved from watched directory
def process_IN_MOVED_FROM(self, event: Event) -> None: self._process_update(event)
# activates when auto-cpufreq config file is moved into the watched directory
def process_IN_MOVED_TO(self, event: Event) -> None: self._process_update(event)

987
auto_cpufreq/core.py Executable file
View File

@ -0,0 +1,987 @@
#!/usr/bin/env python3
#
# auto-cpufreq - core functionality
import click, distro, os, platform, psutil, sys
from importlib.metadata import metadata, PackageNotFoundError
from math import isclose
from pathlib import Path
from pickle import dump, load
from re import search
from requests import get, exceptions
from shutil import copy
from subprocess import call, check_output, DEVNULL, getoutput, run
from time import sleep
from warnings import filterwarnings
from auto_cpufreq.config.config import config
from auto_cpufreq.globals import (
ALL_GOVERNORS, AVAILABLE_GOVERNORS, AVAILABLE_GOVERNORS_SORTED, GITHUB, IS_INSTALLED_WITH_AUR, IS_INSTALLED_WITH_SNAP, POWER_SUPPLY_DIR, SNAP_DAEMON_CHECK
)
from auto_cpufreq.power_helper import *
filterwarnings("ignore")
# add path to auto-cpufreq executables for GUI
if "PATH" in os.environ:
os.environ["PATH"] += os.pathsep + "/usr/local/bin"
else:
os.environ["PATH"] = "/usr/local/bin"
# ToDo:
# - replace get system/CPU load from: psutil.getloadavg() | available in 5.6.2)
SCRIPTS_DIR = Path("/usr/local/share/auto-cpufreq/scripts/")
CPUS = os.cpu_count()
# Note:
# "load1m" & "cpuload" can't be global vars and to in order to show correct data must be
# decraled where their execution takes place
# powersave/performance system load thresholds
performance_load_threshold = (50 * CPUS) / 100
powersave_load_threshold = (75 * CPUS) / 100
# auto-cpufreq stats file path
auto_cpufreq_stats_file = None
auto_cpufreq_stats_path = None
# track governor override and turbo boost override
if IS_INSTALLED_WITH_SNAP:
auto_cpufreq_stats_path = Path("/var/snap/auto-cpufreq/current/auto-cpufreq.stats")
governor_override_state = Path("/var/snap/auto-cpufreq/current/override.pickle")
turbo_override_state = Path("/var/snap/auto-cpufreq/current/turbo-override.pickle")
else:
auto_cpufreq_stats_path = Path("/var/run/auto-cpufreq.stats")
governor_override_state = Path("/opt/auto-cpufreq/override.pickle")
turbo_override_state = Path("/opt/auto-cpufreq/turbo-override.pickle")
def file_stats():
global auto_cpufreq_stats_file
auto_cpufreq_stats_file = open(auto_cpufreq_stats_path, "w")
sys.stdout = auto_cpufreq_stats_file
def get_override():
if os.path.isfile(governor_override_state):
with open(governor_override_state, "rb") as store: return load(store)
else: return "default"
def set_override(override):
if override in ["powersave", "performance"]:
with open(governor_override_state, "wb") as store:
dump(override, store)
print(f"Set governor override to {override}")
elif override == "reset":
if os.path.isfile(governor_override_state):
os.remove(governor_override_state)
print("Governor override removed")
elif override is not None: print("Invalid option.\nUse force=performance, force=powersave, or force=reset")
def get_turbo_override():
if os.path.isfile(turbo_override_state):
with open(turbo_override_state, "rb") as store: return load(store)
else: return "auto"
def set_turbo_override(override):
if override in ["never", "always"]:
with open(turbo_override_state, "wb") as store:
dump(override, store)
print(f"Set turbo boost override to {override}")
elif override == "auto":
if os.path.isfile(turbo_override_state):
os.remove(turbo_override_state)
print("Turbo override removed")
elif override is not None: print("Invalid option.\nUse turbo=always, turbo=never, or turbo=auto")
# get distro name
try: dist_name = distro.id()
except PermissionError:
# Current work-around for Pop!_OS where symlink causes permission issues
print("[!] Warning: Cannot get distro name")
if IS_INSTALLED_WITH_SNAP and os.path.exists("/etc/pop-os/os-release"):
print("[!] Snap install on PopOS detected, you must manually run the following"
" commands in another terminal:\n")
print("[!] Backup the /etc/os-release file:")
print("sudo mv /etc/os-release /etc/os-release-backup\n")
print("[!] Create hardlink to /etc/os-release:")
print("sudo ln /etc/pop-os/os-release /etc/os-release\n")
print("[!] Aborting. Restart auto-cpufreq when you created the hardlink")
else:
print("[!] Check /etc/os-release permissions and make sure it is not a symbolic link")
print("[!] Aborting...")
sys.exit(1)
# display running version of auto-cpufreq
def app_version():
print("auto-cpufreq version: ", end="")
if IS_INSTALLED_WITH_SNAP: print(getoutput(r"echo \(Snap\) $SNAP_VERSION"))
elif IS_INSTALLED_WITH_AUR: print(getoutput("pacman -Qi auto-cpufreq | grep Version"))
else:
try: print(get_formatted_version())
except Exception as e: print(repr(e))
def check_for_update():
# returns True if a new release is available from the GitHub repo
# Specify the repository and package name
# IT IS IMPORTANT TO THAT IF THE REPOSITORY STRUCTURE IS CHANGED, THE FOLLOWING FUNCTION NEEDS TO BE UPDATED ACCORDINGLY
# Fetch the latest release information from GitHub API
latest_release_url = GITHUB.replace("github.com", "api.github.com/repos") + "/releases/latest"
try:
response = get(latest_release_url)
if response.status_code == 200: latest_release = response.json()
else:
message = response.json().get("message")
print("Error fetching recent release!")
if message is not None and message.startswith("API rate limit exceeded"):
print("GitHub Rate limit exceeded. Please try again later within 1 hour or use different network/VPN.")
else: print("Unexpected status code:", response.status_code)
return False
except (exceptions.ConnectionError, exceptions.Timeout,
exceptions.RequestException, exceptions.HTTPError):
print("Error Connecting to server!")
return False
latest_version = latest_release.get("tag_name")
if latest_version is not None:
# Get the current version of auto-cpufreq
# Extract version number from the output string
output = check_output(['auto-cpufreq', '--version']).decode('utf-8')
try: version_line = next((search(r'\d+\.\d+\.\d+', line).group() for line in output.split('\n') if line.startswith('auto-cpufreq version')), None)
except AttributeError:
print("Error Retrieving Current Version!")
exit(1)
installed_version = "v" + version_line
#Check whether the same is installed or not
# Compare the latest version with the installed version and perform update if necessary
if latest_version == installed_version:
print("auto-cpufreq is up to date")
return False
else:
print(f"Updates are available,\nCurrent version: {installed_version}\nLatest version: {latest_version}")
print("Note that your previous custom settings might be erased with the following update")
return True
# Handle the case where "tag_name" key doesn't exist
else: print("Malformed Released data!\nReinstall manually or Open an issue on GitHub for help!")
def new_update(custom_dir):
os.chdir(custom_dir)
print(f"Cloning the latest release to {custom_dir}")
run(["git", "clone", GITHUB+".git"])
os.chdir("auto-cpufreq")
print(f"package cloned to directory {custom_dir}")
run(['./auto-cpufreq-installer'], input='i\n', encoding='utf-8')
def get_literal_version(package_name):
try:
package_metadata = metadata(package_name)
package_name = package_metadata['Name']
numbered_version, _, git_version = package_metadata['Version'].partition("+")
return f"{numbered_version}+{git_version}" # Construct the literal version string
except PackageNotFoundError: return f"Package '{package_name}' not found"
# return formatted version for a better readability
def get_formatted_version():
splitted_version = get_literal_version("auto-cpufreq").split("+")
return splitted_version[0] + ("" if len(splitted_version) > 1 else " (git: " + splitted_version[1] + ")")
def app_res_use():
p = psutil.Process()
print("auto-cpufreq system resource consumption:")
print("cpu usage:", p.cpu_percent(), "%")
print("memory use:", round(p.memory_percent(), 2), "%")
# set/change state of turbo
def turbo(value: bool = None):
"""
Get and set turbo mode
"""
p_state = Path("/sys/devices/system/cpu/intel_pstate/no_turbo")
cpufreq = Path("/sys/devices/system/cpu/cpufreq/boost")
amd_pstate = Path("/sys/devices/system/cpu/amd_pstate/status")
if p_state.exists():
inverse = True
f = p_state
elif cpufreq.exists():
f = cpufreq
inverse = False
elif amd_pstate.exists():
amd_value = amd_pstate.read_text().strip()
if amd_value == "active":
print("CPU turbo is controlled by amd-pstate-epp driver")
# Basically, no other value should exist.
return False
else:
print("Warning: CPU turbo is not available")
return False
turbo_override = get_turbo_override()
if turbo_override != "auto":
# Set the value in respect to if turbo override is enabled or not.
if turbo_override == "always":
value = True
elif turbo_override == "never":
value = False
if value is not None:
try: f.write_text(f"{int(value ^ inverse)}\n")
except PermissionError:
print("Warning: Changing CPU turbo is not supported. Skipping.")
return False
return bool(int(f.read_text().strip())) ^ inverse
def get_turbo(): print("Currently turbo boost is:", "on" if turbo() else "off")
def set_turbo(value:bool):
print("Setting turbo boost:", "on" if value else "off")
turbo(value)
# ignore these devices under /sys/class/power_supply/
def get_power_supply_ignore_list():
conf = config.get_config()
list = []
if conf.has_section("power_supply_ignore_list"):
for i in conf["power_supply_ignore_list"]:
list.append(conf["power_supply_ignore_list"][i])
# these are hard coded power supplies that will always be ignored
list.append("hidpp_battery")
return list
def charging():
"""
get charge state: is battery charging or discharging
"""
# sort it so AC is 'always' first
power_supplies = sorted(os.listdir(Path(POWER_SUPPLY_DIR)))
POWER_SUPPLY_IGNORELIST = get_power_supply_ignore_list()
# check if we found power supplies. on a desktop these are not found and we assume we are on a powercable.
if len(power_supplies) == 0: return True # nothing found, so nothing to check
# we found some power supplies, lets check their state
for supply in power_supplies:
# Check if supply is in ignore list, if found in ignore list, skip it.
if any(item in supply for item in POWER_SUPPLY_IGNORELIST): continue
power_supply_type_path = Path(POWER_SUPPLY_DIR + supply + "/type")
if not power_supply_type_path.exists(): continue
with open(power_supply_type_path) as f: supply_type = f.read()[:-1]
if supply_type == "Mains":
# we found an AC
power_supply_online_path = Path(POWER_SUPPLY_DIR + supply + "/online")
if not power_supply_online_path.exists(): continue
with open(power_supply_online_path) as f:
if int(f.read()[:-1]) == 1: return True # we are definitely charging
elif supply_type == "Battery":
# we found a battery, check if its being discharged
power_supply_status_path = Path(POWER_SUPPLY_DIR + supply + "/status")
if not power_supply_status_path.exists(): continue
with open(power_supply_status_path) as f:
# we found a discharging battery
if str(f.read()[:-1]) == "Discharging": return False
return True # we cannot determine discharging state, assume we are on powercable
def get_current_gov():
return print(
"Currently using:",
getoutput("cpufreqctl.auto-cpufreq --governor").strip().split(" ")[0],
"governor",
)
def cpufreqctl():
"""
deploy cpufreqctl.auto-cpufreq script
"""
if not (IS_INSTALLED_WITH_SNAP or os.path.isfile("/usr/local/bin/cpufreqctl.auto-cpufreq")):
copy(SCRIPTS_DIR / "cpufreqctl.sh", "/usr/local/bin/cpufreqctl.auto-cpufreq")
call(["chmod", "a+x", "/usr/local/bin/cpufreqctl.auto-cpufreq"])
def cpufreqctl_restore():
"""
remove cpufreqctl.auto-cpufreq script
"""
if not IS_INSTALLED_WITH_SNAP and os.path.isfile("/usr/local/bin/cpufreqctl.auto-cpufreq"):
os.remove("/usr/local/bin/cpufreqctl.auto-cpufreq")
def footer(l=79): print("\n" + "-" * l + "\n")
def deploy_complete_msg():
print("\n" + "-" * 17 + " auto-cpufreq daemon installed and running " + "-" * 17 + "\n")
print("To view live stats, run:\nauto-cpufreq --stats")
print("\nauto-cpufreq makes all decisions automatically, if you would like to")
print("configure certain setting to your own liking, please refer to:\nhttps://github.com/AdnanHodzic/auto-cpufreq#configuring-auto-cpufreq")
print("\nTo disable and remove auto-cpufreq daemon, run:\nsudo auto-cpufreq --remove")
footer()
def remove_complete_msg():
print("\n" + "-" * 25 + " auto-cpufreq daemon removed " + "-" * 25 + "\n")
print("auto-cpufreq successfully removed.")
footer()
def deploy_daemon():
print("\n" + "-" * 21 + " Deploying auto-cpufreq as a daemon " + "-" * 22 + "\n")
cpufreqctl() # deploy cpufreqctl script func call
bluetooth_disable() # turn off bluetooth on boot
auto_cpufreq_stats_path.touch(exist_ok=True)
print("\n* Deploy auto-cpufreq install script")
copy(SCRIPTS_DIR / "auto-cpufreq-install.sh", "/usr/local/bin/auto-cpufreq-install")
call(["chmod", "a+x", "/usr/local/bin/auto-cpufreq-install"])
print("\n* Deploy auto-cpufreq remove script")
copy(SCRIPTS_DIR / "auto-cpufreq-remove.sh", "/usr/local/bin/auto-cpufreq-remove")
call(["chmod", "a+x", "/usr/local/bin/auto-cpufreq-remove"])
# output warning if gnome power profile is running
gnome_power_detect_install()
gnome_power_svc_disable()
tuned_svc_disable()
tlp_service_detect() # output warning if TLP service is detected
call("/usr/local/bin/auto-cpufreq-install", shell=True)
def deploy_daemon_performance():
print("\n" + "-" * 21 + " Deploying auto-cpufreq as a daemon (performance) " + "-" * 22 + "\n")
# check that performance is in scaling_available_governors
if "performance" not in AVAILABLE_GOVERNORS_SORTED:
print("\"performance\" governor is unavailable on this system, run:\n"
"sudo sudo auto-cpufreq --install\n\n"
"to install auto-cpufreq using default \"balanced\" governor.\n")
cpufreqctl() # deploy cpufreqctl script func call
bluetooth_disable() # turn off bluetooth on boot
auto_cpufreq_stats_path.touch(exist_ok=True)
print("\n* Deploy auto-cpufreq install script")
copy(SCRIPTS_DIR / "auto-cpufreq-install.sh", "/usr/local/bin/auto-cpufreq-install")
print("\n* Deploy auto-cpufreq remove script")
copy(SCRIPTS_DIR / "auto-cpufreq-remove.sh", "/usr/local/bin/auto-cpufreq-remove")
# output warning if gnome power profile is running
gnome_power_detect_install()
#"gnome_power_svc_disable_performance" is not defined
#gnome_power_svc_disable_performance()
tlp_service_detect() # output warning if TLP service is detected
call("/usr/local/bin/auto-cpufreq-install", shell=True)
def remove_daemon():
# check if auto-cpufreq is installed
if not os.path.exists("/usr/local/bin/auto-cpufreq-remove"):
print("\nauto-cpufreq daemon is not installed.\n")
sys.exit(1)
print("\n" + "-" * 21 + " Removing auto-cpufreq daemon " + "-" * 22 + "\n")
bluetooth_enable() # turn on bluetooth on boot
# output warning if gnome power profile is stopped
gnome_power_rm_reminder()
gnome_power_svc_enable()
tuned_svc_enable()
# run auto-cpufreq daemon remove script
call("/usr/local/bin/auto-cpufreq-remove", shell=True)
# remove auto-cpufreq-remove
os.remove("/usr/local/bin/auto-cpufreq-remove")
# delete override pickle if it exists
if os.path.exists(governor_override_state): os.remove(governor_override_state)
# delete stats file
if auto_cpufreq_stats_path.exists():
if auto_cpufreq_stats_file is not None: auto_cpufreq_stats_file.close()
auto_cpufreq_stats_path.unlink()
cpufreqctl_restore() # restore original cpufrectl script
def gov_check():
for gov in AVAILABLE_GOVERNORS:
if gov not in ALL_GOVERNORS:
print("\n" + "-" * 18 + " Checking for necessary scaling governors " + "-" * 19 + "\n")
sys.exit("ERROR:\n\nCouldn't find any of the necessary scaling governors.\n")
def root_check():
if not os.geteuid() == 0:
print("\n" + "-" * 33 + " Root check " + "-" * 34 + "\n")
print("ERROR:\n\nMust be run root for this functionality to work, i.e: \nsudo " + app_name)
footer()
exit(1)
def countdown(s):
# Fix for wrong stats output and "TERM environment variable not set"
os.environ["TERM"] = "xterm"
print("\t\t\"auto-cpufreq\" is about to refresh ", end = "")
# empty log file if size is larger then 10mb
if auto_cpufreq_stats_file is not None:
log_size = os.path.getsize(auto_cpufreq_stats_path)
if log_size >= 1e+7:
auto_cpufreq_stats_file.seek(0)
auto_cpufreq_stats_file.truncate(0)
# auto-refresh counter
for remaining in range(s, -1, -1):
if remaining <= 3 and remaining >= 0: print(".", end="", flush=True)
sleep(s/3)
print("\n\t\tExecuted on:", getoutput('date'))
# get cpu usage + system load for (last minute)
def get_load():
cpuload = psutil.cpu_percent(interval=1) # get CPU utilization as a percentage
load1m, _, _ = os.getloadavg() # get system/CPU load
print("\nTotal CPU usage:", cpuload, "%")
print("Total system load: {:.2f}".format(load1m))
from auto_cpufreq.modules.system_info import SystemInfo
print("Average temp. of all cores: {:.2f} °C \n".format(SystemInfo.avg_temp()))
return cpuload, load1m
def display_system_load_avg(): print(" (load average: {:.2f}, {:.2f}, {:.2f})".format(*os.getloadavg()))
# set minimum and maximum CPU frequencies
def set_frequencies():
"""
Sets frequencies:
- if option is used in auto-cpufreq.conf: use configured value
- if option is disabled/no conf file used: set default frequencies
Frequency setting is performed only once on power supply change
"""
power_supply = "charger" if charging() else "battery"
# don't do anything if the power supply hasn't changed
if (
hasattr(set_frequencies, "prev_power_supply")
and power_supply == set_frequencies.prev_power_supply
): return
else: set_frequencies.prev_power_supply = power_supply
frequency = {
"scaling_max_freq": {
"cmdargs": "--frequency-max",
"minmax": "maximum",
},
"scaling_min_freq": {
"cmdargs": "--frequency-min",
"minmax": "minimum",
},
}
if not hasattr(set_frequencies, "max_limit"):
set_frequencies.max_limit = int(getoutput(f"cpufreqctl.auto-cpufreq --frequency-max-limit"))
if not hasattr(set_frequencies, "min_limit"):
set_frequencies.min_limit = int(getoutput(f"cpufreqctl.auto-cpufreq --frequency-min-limit"))
conf = config.get_config()
for freq_type in frequency.keys():
value = None
if not conf.has_option(power_supply, freq_type):
# fetch and use default frequencies
if freq_type == "scaling_max_freq":
curr_freq = int(getoutput(f"cpufreqctl.auto-cpufreq --frequency-max"))
value = set_frequencies.max_limit
else:
curr_freq = int(getoutput(f"cpufreqctl.auto-cpufreq --frequency-min"))
value = set_frequencies.min_limit
if curr_freq == value: continue
try: frequency[freq_type]["value"] = value if value else int(conf[power_supply][freq_type].strip())
except ValueError:
print(f"Invalid value for '{freq_type}': {frequency[freq_type]['value']}")
exit(1)
if not set_frequencies.min_limit <= frequency[freq_type]["value"] <= set_frequencies.max_limit:
print(
f"Given value for '{freq_type}' is not within the allowed frequencies {set_frequencies.min_limit}-{set_frequencies.max_limit} kHz"
)
exit(1)
print(f'Setting {frequency[freq_type]["minmax"]} CPU frequency to {round(frequency[freq_type]["value"]/1000)} Mhz')
# set the frequency
run(f"cpufreqctl.auto-cpufreq {frequency[freq_type]['cmdargs']} --set={frequency[freq_type]['value']}", shell=True)
def set_platform_profile(conf, profile):
if conf.has_option(profile, "platform_profile"):
if not Path("/sys/firmware/acpi/platform_profile").exists():
print('Not setting Platform Profile (not supported by system)')
else:
pp = conf[profile]["platform_profile"]
print(f'Setting to use: "{pp}" Platform Profile')
run(f"cpufreqctl.auto-cpufreq --pp --set={pp}", shell=True)
def set_energy_perf_bias(conf, profile):
if Path("/sys/devices/system/cpu/intel_pstate").exists() is False:
print('Not setting EPB (not supported by system)')
return
epb = "balance_performance" if profile == "charger" else "balance_power"
if conf.has_option(profile, "energy_perf_bias"):
epb = conf[profile]["energy_perf_bias"]
run(f"cpufreqctl.auto-cpufreq --epb --set={epb}", shell=True)
print(f'Setting to use: "{epb}" EPB')
def set_powersave():
conf = config.get_config()
gov = conf["battery"]["governor"] if conf.has_option("battery", "governor") else AVAILABLE_GOVERNORS_SORTED[-1]
print(f'Setting to use: "{gov}" governor')
if get_override() != "default": print("Warning: governor overwritten using `--force` flag.")
run(f"cpufreqctl.auto-cpufreq --governor --set={gov}", shell=True)
if Path("/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference").exists() is False:
print('Not setting EPP (not supported by system)')
else:
dynboost_enabled = Path("/sys/devices/system/cpu/intel_pstate/hwp_dynamic_boost").exists()
if dynboost_enabled:
dynboost_enabled = bool(int(
os.popen("cat /sys/devices/system/cpu/intel_pstate/hwp_dynamic_boost").read()
))
if dynboost_enabled: print('Not setting EPP (dynamic boosting is enabled)')
else:
if conf.has_option("battery", "energy_performance_preference"):
epp = conf["battery"]["energy_performance_preference"]
run(f"cpufreqctl.auto-cpufreq --epp --set={epp}", shell=True)
print(f'Setting to use: "{epp}" EPP')
else:
run("cpufreqctl.auto-cpufreq --epp --set=balance_power", shell=True)
print('Setting to use: "balance_power" EPP')
set_energy_perf_bias(conf, "battery")
set_platform_profile(conf, "battery")
set_frequencies()
cpuload, load1m= get_load()
auto = conf["battery"]["turbo"] if conf.has_option("battery", "turbo") else "auto"
auto = get_turbo_override() if (get_turbo_override() != "auto") else auto # Override turbo if override file is present, otherwise stick to config.
if auto == "always":
print("Configuration file enforces turbo boost")
set_turbo(True)
elif auto == "never":
print("Configuration file disables turbo boost")
set_turbo(False)
else:
if psutil.cpu_percent(percpu=False, interval=0.01) >= 30.0 or isclose(
max(psutil.cpu_percent(percpu=True, interval=0.01)), 100
): print("High CPU load", end="")
elif load1m > powersave_load_threshold: print("High system load", end="")
else: print("Load optimal", end="")
display_system_load_avg()
if cpuload >= 20: set_turbo(True) # high cpu usage trigger
else: # set turbo state based on average of all core temperatures
from auto_cpufreq.modules.system_info import SystemInfo
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
set_turbo(False)
footer()
def mon_powersave():
cpuload, load1m = get_load()
if psutil.cpu_percent(percpu=False, interval=0.01) >= 30.0 or isclose(
max(psutil.cpu_percent(percpu=True, interval=0.01)), 100
): print("High CPU load", end="")
elif load1m > powersave_load_threshold: print("High system load", end="")
else: print("Load optimal", end="")
display_system_load_avg()
if cpuload >= 20: print("suggesting to set turbo boost: on") # high cpu usage trigger
else: # set turbo state based on average of all core temperatures
from auto_cpufreq.modules.system_info import SystemInfo
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
print("suggesting to set turbo boost: off")
get_turbo()
footer()
def set_performance():
conf = config.get_config()
gov = conf["charger"]["governor"] if conf.has_option("charger", "governor") else AVAILABLE_GOVERNORS_SORTED[0]
print(f'Setting to use: "{gov}" governor')
if get_override() != "default": print("Warning: governor overwritten using `--force` flag.")
run("cpufreqctl.auto-cpufreq --governor --set="+gov, shell=True)
if not Path("/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference").exists():
print('Not setting EPP (not supported by system)')
else:
if Path("/sys/devices/system/cpu/intel_pstate").exists():
dynboost_enabled = Path("/sys/devices/system/cpu/intel_pstate/hwp_dynamic_boost").exists()
if dynboost_enabled:
dynboost_enabled = bool(int(
os.popen("cat /sys/devices/system/cpu/intel_pstate/hwp_dynamic_boost").read()
))
if dynboost_enabled: print('Not setting EPP (dynamic boosting is enabled)')
else:
intel_pstate_status_path = "/sys/devices/system/cpu/intel_pstate/status"
if conf.has_option("charger", "energy_performance_preference"):
epp = conf["charger"]["energy_performance_preference"]
if Path(intel_pstate_status_path).exists() and open(intel_pstate_status_path, 'r').read().strip() == "active" and epp != "performance" and gov == "performance":
print(f'Warning "{epp}" EPP cannot be used in performance governor')
print('Overriding EPP to "performance"')
epp = "performance"
run(f"cpufreqctl.auto-cpufreq --epp --set={epp}", shell=True)
print(f'Setting to use: "{epp}" EPP')
else:
if Path(intel_pstate_status_path).exists() and open(intel_pstate_status_path, 'r').read().strip() == "active":
run("cpufreqctl.auto-cpufreq --epp --set=performance", shell=True)
print('Setting to use: "performance" EPP')
else:
run("cpufreqctl.auto-cpufreq --epp --set=balance_performance", shell=True)
print('Setting to use: "balance_performance" EPP')
elif Path("/sys/devices/system/cpu/amd_pstate").exists():
amd_pstate_status_path = "/sys/devices/system/cpu/amd_pstate/status"
if conf.has_option("charger", "energy_performance_preference"):
epp = conf["charger"]["energy_performance_preference"]
if Path(amd_pstate_status_path).exists() and open(amd_pstate_status_path, 'r').read().strip() == "active" and epp != "performance" and gov == "performance":
print(f'Warning "{epp} EPP cannot be used in performance governor')
print('Overriding EPP to "performance"')
epp = "performance"
run(f"cpufreqctl.auto-cpufreq --epp --set={epp}", shell=True)
print(f'Setting to use: "{epp}" EPP')
else:
if Path(amd_pstate_status_path).exists() and open(amd_pstate_status_path, 'r').read().strip() == "active":
run("cpufreqctl.auto-cpufreq --epp --set=performance", shell=True)
print('Setting to use: "performance" EPP')
else:
run("cpufreqctl.auto-cpufreq --epp --set=balance_performance", shell=True)
print('Setting to use: "balance_performance" EPP')
set_energy_perf_bias(conf, "charger")
set_platform_profile(conf, "charger")
set_frequencies()
cpuload, load1m = get_load()
auto = conf["charger"]["turbo"] if conf.has_option("charger", "turbo") else "auto"
auto = get_turbo_override() if (get_turbo_override() != "auto") else auto # Override turbo if override file is present, otherwise stick to config.
if auto == "always":
print("Configuration file enforces turbo boost")
set_turbo(True)
elif auto == "never":
print("Configuration file disables turbo boost")
set_turbo(False)
else:
from auto_cpufreq.modules.system_info import SystemInfo
if (
psutil.cpu_percent(percpu=False, interval=0.01) >= 20.0
or max(psutil.cpu_percent(percpu=True, interval=0.01)) >= 75
):
print("High CPU load", end=""), display_system_load_avg()
if cpuload >= 20: set_turbo(True) # high cpu usage trigger
elif SystemInfo.avg_temp() >= 70: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
set_turbo(False)
else: set_turbo(True)
elif load1m >= performance_load_threshold:
print("High system load", end=""), display_system_load_avg()
if cpuload >= 20: set_turbo(True) # high cpu usage trigger
elif SystemInfo.avg_temp() >= 65: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
set_turbo(False)
else: set_turbo(True)
else:
print("Load optimal", end=""), display_system_load_avg()
if cpuload >= 20: set_turbo(True) # high cpu usage trigger
else: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
set_turbo(False)
footer()
def mon_performance():
from auto_cpufreq.modules.system_info import SystemInfo
cpuload, load1m = get_load()
if (
psutil.cpu_percent(percpu=False, interval=0.01) >= 20.0
or max(psutil.cpu_percent(percpu=True, interval=0.01)) >= 75
):
print("High CPU load", end=""), display_system_load_avg()
if cpuload >= 20: # high cpu usage trigger
print("suggesting to set turbo boost: on")
get_turbo()
# set turbo state based on average of all core temperatures
elif cpuload <= 25 and SystemInfo.avg_temp() >= 70:
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
print("suggesting to set turbo boost: off")
get_turbo()
else:
print("suggesting to set turbo boost: on")
get_turbo()
elif load1m > performance_load_threshold:
print("High system load", end=""), display_system_load_avg()
if cpuload >= 20: # high cpu usage trigger
print("suggesting to set turbo boost: on")
get_turbo()
elif cpuload <= 25 and SystemInfo.avg_temp() >= 65: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
print("suggesting to set turbo boost: off")
get_turbo()
else:
print("suggesting to set turbo boost: on")
get_turbo()
else:
print("Load optimal", end=""), display_system_load_avg()
if cpuload >= 20: # high cpu usage trigger
print("suggesting to set turbo boost: on")
get_turbo()
elif cpuload <= 25 and SystemInfo.avg_temp() >= 60: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
print("suggesting to set turbo boost: off")
get_turbo()
else:
print("suggesting to set turbo boost: on")
get_turbo()
footer()
def set_autofreq():
"""
set cpufreq governor based if device is charging
"""
print("\n" + "-" * 28 + " CPU frequency scaling " + "-" * 28 + "\n")
# determine which governor should be used
override = get_override()
if override == "powersave": set_powersave()
elif override == "performance": set_performance()
elif charging():
print("Battery is: charging\n")
set_performance()
else:
print("Battery is: discharging\n")
set_powersave()
def mon_autofreq():
"""
make cpufreq suggestions
:return:
"""
print("\n" + "-" * 28 + " CPU frequency scaling " + "-" * 28 + "\n")
# determine which governor should be used
if charging():
print("Battery is: charging\n")
get_current_gov()
print(f'Suggesting use of "{AVAILABLE_GOVERNORS_SORTED[0]}" governor')
mon_performance()
else:
print("Battery is: discharging\n")
get_current_gov()
print(f'Suggesting use of "{AVAILABLE_GOVERNORS_SORTED[-1]}" governor')
mon_powersave()
def python_info():
print("Python:", platform.python_version())
print("psutil package:", psutil.__version__)
print("platform package:", platform.__version__)
print("click package:", click.__version__)
print("distro package:", distro.__version__)
def device_info(): print("Computer type:", getoutput("dmidecode --string chassis-type"))
def distro_info():
dist = "UNKNOWN distro"
version = "UNKNOWN version"
if IS_INSTALLED_WITH_SNAP:
try:
with open("/var/lib/snapd/hostfs/etc/os-release", "r") as searchfile:
for line in searchfile:
if line.startswith("NAME="):
dist = line[5 : line.find("$")].strip('"')
continue
elif line.startswith("VERSION="):
version = line[8 : line.find("$")].strip('"')
continue
except PermissionError as e: print(repr(e))
dist = f"{dist} {version}"
else: # get distro information
fdist = distro.linux_distribution()
dist = " ".join(x for x in fdist)
print("Linux distro: " + dist)
print("Linux kernel: " + platform.release())
def sysinfo():
"""
get system information
"""
# processor_info
model_name = getoutput("grep -E 'model name' /proc/cpuinfo -m 1").split(":")[-1]
print(f"Processor:{model_name}")
# get core count
total_cpu_count = int(getoutput("nproc"))
print("Cores:", total_cpu_count)
# get architecture
cpu_arch = platform.machine()
print("Architecture:", cpu_arch)
# get driver
driver = getoutput("cpufreqctl.auto-cpufreq --driver")
print("Driver: " + driver)
# get usage and freq info of cpus
usage_per_cpu = psutil.cpu_percent(interval=1, percpu=True)
# psutil current freq not used, gives wrong values with offline cpu's
minmax_freq_per_cpu = psutil.cpu_freq(percpu=True)
# max and min freqs, psutil reports wrong max/min freqs with offline cores with percpu=False
max_freq = max([freq.max for freq in minmax_freq_per_cpu])
min_freq = min([freq.min for freq in minmax_freq_per_cpu])
print("\n" + "-" * 30 + " Current CPU stats " + "-" * 30 + "\n")
print(f"CPU max frequency: {max_freq:.0f} MHz")
print(f"CPU min frequency: {min_freq:.0f} MHz\n")
# get coreid's and frequencies of online cpus by parsing /proc/cpuinfo
coreid_info = getoutput("grep -E 'processor|cpu MHz|core id' /proc/cpuinfo").split("\n")
cpu_core = dict()
freq_per_cpu = []
for i in range(0, len(coreid_info), 3):
# ensure that indices are within the valid range, before accessing the corresponding elements
if i + 1 < len(coreid_info): freq_per_cpu.append(float(coreid_info[i + 1].split(":")[-1]))
else: continue # handle the case where the index is out of range
# ensure that indices are within the valid range, before accessing the corresponding elements
cpu = int(coreid_info[i].split(":")[-1])
if i + 2 < len(coreid_info):
core = int(coreid_info[i + 2].split(":")[-1])
cpu_core[cpu] = core
else: continue # handle the case where the index is out of range
online_cpu_count = len(cpu_core)
offline_cpus = [str(cpu) for cpu in range(total_cpu_count) if cpu not in cpu_core]
# temperatures
temp_sensors = psutil.sensors_temperatures()
temp_per_cpu = [float("nan")] * online_cpu_count
try:
# the priority for CPU temp is as follows: coretemp sensor -> sensor with CPU in the label -> acpi -> k10temp
if "coretemp" in temp_sensors:
# list labels in 'coretemp'
core_temp_labels = [temp.label for temp in temp_sensors["coretemp"]]
for i, cpu in enumerate(cpu_core):
# get correct index in temp_sensors
core = cpu_core[cpu]
cpu_temp_index = core_temp_labels.index(f"Core {core}")
temp_per_cpu[i] = temp_sensors["coretemp"][cpu_temp_index].current
else:
# iterate over all sensors
for sensor in temp_sensors:
# iterate over all temperatures in the current sensor
for temp in temp_sensors[sensor]:
if ('CPU' in temp.label or 'Tctl' in temp.label) and temp.current != 0:
temp_per_cpu = [temp.current] * online_cpu_count
break
else: continue
break
else:
for sensor in ["acpitz", "k10temp", "zenpower"]:
if sensor in temp_sensors and temp_sensors[sensor][0].current != 0:
temp_per_cpu = [temp_sensors[sensor][0].current] * online_cpu_count
break
except Exception as e: print(repr(e))
print("Core\tUsage\tTemperature\tFrequency")
for (cpu, usage, freq, temp) in zip(cpu_core, usage_per_cpu, freq_per_cpu, temp_per_cpu):
print(f"CPU{cpu} {usage:>5.1f}% {temp:>3.0f} °C {freq:>5.0f} MHz")
if offline_cpus: print(f"\nDisabled CPUs: {','.join(offline_cpus)}")
# print current fan speed
current_fans = list(psutil.sensors_fans())
for current_fan in current_fans: print("\nCPU fan speed:", psutil.sensors_fans()[current_fan][0].current, "RPM")
def read_stats():
if os.path.isfile(auto_cpufreq_stats_path): call(["tail", "-n 50", "-f", str(auto_cpufreq_stats_path)], stderr=DEVNULL)
footer()
# check if program (argument) is running
def is_running(program, argument):
# iterate over all processes found by psutil
# and find the one with name and args passed to the function
for p in psutil.process_iter():
try: cmd = p.cmdline()
except: continue
for s in filter(lambda x: program in x, cmd):
if argument in cmd: return True
def daemon_running_msg():
print("\n" + "-" * 24 + " auto-cpufreq running " + "-" * 30 + "\n")
print(
"ERROR: auto-cpufreq is running in daemon mode.\n\nMake sure to stop the daemon before running with --live or --monitor mode"
)
footer()
def daemon_not_running_msg():
print("\n" + "-" * 24 + " auto-cpufreq not running " + "-" * 30 + "\n")
print(
"ERROR: auto-cpufreq is not running in daemon mode.\n\nMake sure to run \"sudo auto-cpufreq --install\" first"
)
footer()
# check if auto-cpufreq --daemon is running
def running_daemon_check():
if is_running("auto-cpufreq", "--daemon"):
daemon_running_msg()
exit(1)
elif IS_INSTALLED_WITH_SNAP and SNAP_DAEMON_CHECK == "enabled":
daemon_running_msg()
exit(1)
# check if auto-cpufreq --daemon is not running
def not_running_daemon_check():
if not is_running("auto-cpufreq", "--daemon"):
daemon_not_running_msg()
exit(1)
elif IS_INSTALLED_WITH_SNAP and SNAP_DAEMON_CHECK == "disabled":
daemon_not_running_msg()
exit(1)

15
auto_cpufreq/globals.py Normal file
View File

@ -0,0 +1,15 @@
from os import getenv, path
from subprocess import getoutput
ALL_GOVERNORS = ('performance', 'ondemand', 'conservative', 'schedutil', 'userspace', 'powersave') # from the highest performance to the lowest
AVAILABLE_GOVERNORS = getoutput('cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors').strip().split(' ')
AVAILABLE_GOVERNORS_SORTED = tuple(filter(lambda gov: gov in AVAILABLE_GOVERNORS, ALL_GOVERNORS))
CONSERVATION_MODE_FILE = "/sys/bus/platform/drivers/ideapad_acpi/VPC2004:00/conservation_mode"
GITHUB = "https://github.com/AdnanHodzic/auto-cpufreq"
IS_INSTALLED_WITH_AUR = path.isfile("/etc/arch-release") and bool(getoutput("pacman -Qs auto-cpufreq"))
IS_INSTALLED_WITH_SNAP = getenv("PKG_MARKER") == "SNAP"
POWER_SUPPLY_DIR = "/sys/class/power_supply/"
SNAP_DAEMON_CHECK = getoutput("snapctl get daemon")
CPU_TEMP_SENSOR_PRIORITY = ("coretemp", "acpitz", "k10temp", "zenpower")

122
auto_cpufreq/gui/app.py Normal file
View File

@ -0,0 +1,122 @@
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk
from contextlib import redirect_stdout
from io import StringIO
from subprocess import PIPE, run
from threading import Thread
from auto_cpufreq.core import check_for_update, is_running
from auto_cpufreq.globals import GITHUB, IS_INSTALLED_WITH_SNAP
from auto_cpufreq.gui.objects import CPUFreqStatsLabel, CurrentGovernorBox, DaemonNotRunningView, DropDownMenu, RadioButtonView, CPUTurboOverride, SystemStatsLabel, UpdateDialog
from auto_cpufreq.gui.objects import get_stats
if IS_INSTALLED_WITH_SNAP:
CSS_FILE = "/snap/auto-cpufreq/current/style.css"
ICON_FILE = "/snap/auto-cpufreq/current/icon.png"
else:
CSS_FILE = "/usr/local/share/auto-cpufreq/scripts/style.css"
ICON_FILE = "/usr/local/share/auto-cpufreq/images/icon.png"
HBOX_PADDING = 20
PKEXEC_ERROR = "Error executing command as another user: Not authorized\n\nThis incident has been reported.\n"
class ToolWindow(Gtk.Window):
def __init__(self):
super().__init__(title="auto-cpufreq")
self.set_default_size(600, 480)
self.set_border_width(10)
self.set_resizable(False)
self.load_css()
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(filename=ICON_FILE, width=500, height=500, preserve_aspect_ratio=True)
self.set_icon(pixbuf)
self.build()
def main(self):
# Main HBOX
self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=HBOX_PADDING)
self.systemstats = SystemStatsLabel()
self.hbox.pack_start(self.systemstats, False, False, 0)
self.add(self.hbox)
self.vbox_right = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=52)
self.menu = DropDownMenu(self)
self.hbox.pack_end(self.menu, False, False, 0)
self.currentgovernor = CurrentGovernorBox()
self.vbox_right.pack_start(self.currentgovernor, False, False, 0)
self.vbox_right.pack_start(RadioButtonView(), False, False, 0)
if "Warning: CPU turbo is not available" not in get_stats():
self.vbox_right.pack_start(CPUTurboOverride(), False, False, 0)
self.cpufreqstats = CPUFreqStatsLabel()
self.vbox_right.pack_start(self.cpufreqstats, False, False, 0)
self.hbox.pack_start(self.vbox_right, False, False, 0)
GLib.timeout_add_seconds(5, self.refresh_in_thread)
def snap(self):
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER)
# reference: https://forum.snapcraft.io/t/pkexec-not-found-python-gtk-gnome-app/36579
label = Gtk.Label(label="GUI not available due to Snap package confinement limitations.\nPlease install auto-cpufreq using auto-cpufreq-installer\nVisit the GitHub repo for more info")
label.set_justify(Gtk.Justification.CENTER)
button = Gtk.LinkButton.new_with_label(
uri=GITHUB,
label="GitHub Repo"
)
box.pack_start(label, False, False, 0)
box.pack_start(button, False, False, 0)
self.add(box)
def handle_update(self):
new_stdout = StringIO()
with redirect_stdout(new_stdout):
if not check_for_update(): return
captured_output = new_stdout.getvalue().splitlines()
dialog = UpdateDialog(self, captured_output[1], captured_output[2])
response = dialog.run()
dialog.destroy()
if response != Gtk.ResponseType.YES: return
updater = run(["pkexec", "auto-cpufreq", "--update"], input="y\n", encoding="utf-8", stderr=PIPE)
if updater.stderr == PKEXEC_ERROR:
error = Gtk.MessageDialog(self, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, "Error updating")
error.format_secondary_text("Authorization Failed")
error.run()
error.destroy()
return
success = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, "Update successful")
success.format_secondary_text("The app will now close. Please reopen to apply changes")
success.run()
success.destroy()
exit(0)
def daemon_not_running(self):
self.box = DaemonNotRunningView(self)
self.add(self.box)
def build(self):
if IS_INSTALLED_WITH_SNAP: self.snap()
elif is_running("auto-cpufreq", "--daemon"): self.main()
else: self.daemon_not_running()
def load_css(self):
screen = Gdk.Screen.get_default()
self.gtk_provider = Gtk.CssProvider()
self.gtk_context = Gtk.StyleContext()
self.gtk_context.add_provider_for_screen(screen, self.gtk_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
self.gtk_provider.load_from_file(Gio.File.new_for_path(CSS_FILE))
def refresh_in_thread(self):
Thread(target=self._refresh).start()
return True
def _refresh(self):
self.systemstats.refresh()
self.currentgovernor.refresh()
self.cpufreqstats.refresh()

332
auto_cpufreq/gui/objects.py Normal file
View File

@ -0,0 +1,332 @@
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GdkPixbuf, Gtk
import sys
from concurrent.futures import ThreadPoolExecutor
from io import StringIO
from os.path import isfile
from platform import python_version
from subprocess import getoutput, PIPE, run
from auto_cpufreq.core import distro_info, get_formatted_version, get_override, get_turbo_override, sysinfo
from auto_cpufreq.globals import GITHUB, IS_INSTALLED_WITH_AUR, IS_INSTALLED_WITH_SNAP
PKEXEC_ERROR = "Error executing command as another user: Not authorized\n\nThis incident has been reported.\n"
auto_cpufreq_stats_path = ("/var/snap/auto-cpufreq/current" if IS_INSTALLED_WITH_SNAP else "/var/run") + "/auto-cpufreq.stats"
def get_stats():
if isfile(auto_cpufreq_stats_path):
with open(auto_cpufreq_stats_path, "r") as file: stats = [line for line in (file.readlines() [-50:])]
return "".join(stats)
def get_version():
# snap package
if IS_INSTALLED_WITH_SNAP: return getoutput(r"echo \(Snap\) $SNAP_VERSION")
# aur package
elif IS_INSTALLED_WITH_AUR: return getoutput("pacman -Qi auto-cpufreq | grep Version")
else:
# source code (auto-cpufreq-installer)
try: return get_formatted_version()
except Exception as e:
print(repr(e))
pass
class RadioButtonView(Gtk.Box):
def __init__(self):
super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
self.set_hexpand(True)
self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.label = Gtk.Label("Governor Override", name="bold")
self.default = Gtk.RadioButton.new_with_label_from_widget(None, "Default")
self.default.connect("toggled", self.on_button_toggled, "reset")
self.default.set_halign(Gtk.Align.END)
self.powersave = Gtk.RadioButton.new_with_label_from_widget(self.default, "Powersave")
self.powersave.connect("toggled", self.on_button_toggled, "powersave")
self.powersave.set_halign(Gtk.Align.END)
self.performance = Gtk.RadioButton.new_with_label_from_widget(self.default, "Performance")
self.performance.connect("toggled", self.on_button_toggled, "performance")
self.performance.set_halign(Gtk.Align.END)
# this keeps track of whether or not the button was toggled by the app or the user to prompt for authorization
self.set_by_app = True
self.set_selected()
self.pack_start(self.label, False, False, 0)
self.pack_start(self.default, True, True, 0)
self.pack_start(self.powersave, True, True, 0)
self.pack_start(self.performance, True, True, 0)
def on_button_toggled(self, button, override):
if button.get_active():
if not self.set_by_app:
result = run(f"pkexec auto-cpufreq --force={override}", shell=True, stdout=PIPE, stderr=PIPE)
if result.stderr.decode() == PKEXEC_ERROR: self.set_selected()
else: self.set_by_app = False
def set_selected(self):
override = get_override()
match override:
case "powersave": self.powersave.set_active(True)
case "performance": self.performance.set_active(True)
case "default":
# because this is the default button, it does not trigger the callback when set by the app
if self.set_by_app: self.set_by_app = False
self.default.set_active(True)
class CPUTurboOverride(Gtk.Box):
def __init__(self):
super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
self.set_hexpand(True)
self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.label = Gtk.Label("CPU Turbo Override", name="bold")
self.auto = Gtk.RadioButton.new_with_label_from_widget(None, "Auto")
self.auto.connect("toggled", self.on_button_toggled, "auto")
self.auto.set_halign(Gtk.Align.END)
self.never = Gtk.RadioButton.new_with_label_from_widget(self.auto, "Never")
self.never.connect("toggled", self.on_button_toggled, "never")
self.never.set_halign(Gtk.Align.END)
self.always = Gtk.RadioButton.new_with_label_from_widget(self.auto, "Always")
self.always.connect("toggled", self.on_button_toggled, "always")
self.always.set_halign(Gtk.Align.END)
self.set_by_app = True
self.set_selected()
self.pack_start(self.label, False, False, 0)
self.pack_start(self.auto, True, True, 0)
self.pack_start(self.never, True, True, 0)
self.pack_start(self.always, True, True, 0)
def on_button_toggled(self, button, override):
if button.get_active():
if not self.set_by_app:
result = run(f"pkexec auto-cpufreq --turbo={override}", shell=True, stdout=PIPE, stderr=PIPE)
if result.stderr.decode() == PKEXEC_ERROR: self.set_selected()
else: self.set_by_app = False
def set_selected(self):
override = get_turbo_override()
match override:
case "never": self.never.set_active(True)
case "always": self.always.set_active(True)
case "auto":
# because this is the default button, it does not trigger the callback when set by the app
if self.set_by_app: self.set_by_app = False
self.auto.set_active(True)
class CurrentGovernorBox(Gtk.Box):
def __init__(self):
super().__init__(spacing=25)
self.static = Gtk.Label(label="Current Governor", name="bold")
self.governor = Gtk.Label(label=getoutput("cpufreqctl.auto-cpufreq --governor").strip().split(" ")[0], halign=Gtk.Align.END)
self.pack_start(self.static, False, False, 0)
self.pack_start(self.governor, False, False, 0)
def refresh(self):
self.governor.set_label(getoutput("cpufreqctl.auto-cpufreq --governor").strip().split(" ")[0])
class SystemStatsLabel(Gtk.Label):
def __init__(self):
super().__init__()
self.refresh()
def refresh(self):
# change stdout and store label text to file-like object
old_stdout = sys.stdout
text = StringIO()
sys.stdout = text
distro_info()
sysinfo()
self.set_label(text.getvalue())
sys.stdout = old_stdout
class CPUFreqStatsLabel(Gtk.Label):
def __init__(self):
super().__init__()
self.refresh()
def refresh(self):
stats = get_stats().split("\n")
start = None
for i, line in enumerate(stats):
if line == ("-" * 28 + " CPU frequency scaling " + "-" * 28):
start = i
break
if start is not None:
del stats[:i]
del stats[-4:]
self.set_label("\n".join(stats))
class DropDownMenu(Gtk.MenuButton):
def __init__(self, parent):
super().__init__()
self.set_halign(Gtk.Align.END)
self.set_valign(Gtk.Align.START)
self.image = Gtk.Image.new_from_icon_name("open-menu-symbolic", Gtk.IconSize.LARGE_TOOLBAR)
self.add(self.image)
self.menu = self.build_menu(parent)
self.set_popup(self.menu)
def build_menu(self, parent):
menu = Gtk.Menu()
daemon = Gtk.MenuItem(label="Remove Daemon")
daemon.connect("activate", self._remove_daemon, parent)
menu.append(daemon)
about = Gtk.MenuItem(label="About")
about.connect("activate", self.about_dialog, parent)
menu.append(about)
menu.show_all()
return menu
def about_dialog(self, MenuItem, parent):
dialog = AboutDialog(parent)
response = dialog.run()
dialog.destroy()
def _remove_daemon(self, MenuItem, parent):
confirm = ConfirmDialog(parent, message="Are you sure you want to remove the daemon?")
response = confirm.run()
confirm.destroy()
if response == Gtk.ResponseType.YES:
try:
# run in thread to prevent GUI from hanging
with ThreadPoolExecutor() as executor:
kwargs = {"shell": True, "stdout": PIPE, "stderr": PIPE}
future = executor.submit(run, "pkexec auto-cpufreq --remove", **kwargs)
result = future.result()
assert result.stderr.decode() != PKEXEC_ERROR, Exception("Authorization was cancelled")
dialog = Gtk.MessageDialog(
transient_for=parent,
message_type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.OK,
text="Daemon successfully removed"
)
dialog.format_secondary_text("The app will now close. Please reopen to apply changes")
dialog.run()
dialog.destroy()
parent.destroy()
except Exception as e:
dialog = Gtk.MessageDialog(
transient_for=parent,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.OK,
text="Daemon removal failed"
)
dialog.format_secondary_text(f"The following error occured:\n{e}")
dialog.run()
dialog.destroy()
class AboutDialog(Gtk.Dialog):
def __init__(self, parent):
super().__init__(title="About", transient_for=parent)
app_version = get_version()
self.box = self.get_content_area()
self.box.set_spacing(10)
self.add_button("Close", Gtk.ResponseType.CLOSE)
self.set_default_size(400, 350)
img_buffer = GdkPixbuf.Pixbuf.new_from_file_at_scale(
filename="/usr/local/share/auto-cpufreq/images/icon.png",
width=150,
height=150,
preserve_aspect_ratio=True
)
self.image = Gtk.Image.new_from_pixbuf(img_buffer)
self.title = Gtk.Label(label="auto-cpufreq", name="bold")
self.version = Gtk.Label(label=app_version)
self.python = Gtk.Label(label=f"Python {python_version()}")
self.github = Gtk.Label(label=GITHUB)
self.license = Gtk.Label(label="Licensed under LGPL3", name="small")
self.love = Gtk.Label(label="Made with <3", name="small")
self.box.pack_start(self.image, False, False, 0)
self.box.pack_start(self.title, False, False, 0)
self.box.pack_start(self.version, False, False, 0)
self.box.pack_start(self.python, False, False, 0)
self.box.pack_start(self.github, False, False, 0)
self.box.pack_start(self.license, False, False, 0)
self.box.pack_start(self.love, False, False, 0)
self.show_all()
class UpdateDialog(Gtk.Dialog):
def __init__(self, parent, current_version: str, latest_version: str):
super().__init__(title="Update Available", transient_for=parent)
self.box = self.get_content_area()
self.set_default_size(400, 100)
self.add_buttons("Update", Gtk.ResponseType.YES, "Cancel", Gtk.ResponseType.NO)
self.label = Gtk.Label(label="An update is available\n")
self.current_version = Gtk.Label(label=current_version + "\n")
self.latest_version = Gtk.Label(label=latest_version + "\n")
self.box.pack_start(self.label, True, False, 0)
self.box.pack_start(self.current_version, True, False, 0)
self.box.pack_start(self.latest_version, True, False, 0)
self.show_all()
class ConfirmDialog(Gtk.Dialog):
def __init__(self, parent, message: str):
super().__init__(title="Confirmation", transient_for=parent)
self.box = self.get_content_area()
self.set_default_size(400, 100)
self.add_buttons("Yes", Gtk.ResponseType.YES, "No", Gtk.ResponseType.NO)
self.label = Gtk.Label(label=message)
self.box.pack_start(self.label, True, False, 0)
self.show_all()
class DaemonNotRunningView(Gtk.Box):
def __init__(self, parent):
super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=10, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER)
self.label = Gtk.Label(label="auto-cpufreq daemon is not running. Please click the install button")
self.install_button = Gtk.Button.new_with_label("Install")
self.install_button.connect("clicked", self.install_daemon, parent)
self.pack_start(self.label, False, False, 0)
self.pack_start(self.install_button, False, False, 0)
def install_daemon(self, button, parent):
try:
# run in thread to prevent GUI from hanging
with ThreadPoolExecutor() as executor:
kwargs = {"shell": True, "stdout": PIPE, "stderr": PIPE}
future = executor.submit(run, "pkexec auto-cpufreq --install", **kwargs)
result = future.result()
assert result.stderr.decode() != PKEXEC_ERROR, Exception("Authorization was cancelled")
# enable for debug. causes issues if kept
# elif result.stderr is not None:
# raise Exception(result.stderr.decode())
dialog = Gtk.MessageDialog(
transient_for=parent,
message_type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.OK,
text="Daemon successfully installed"
)
dialog.format_secondary_text("The app will now close. Please reopen to apply changes")
dialog.run()
dialog.destroy()
parent.destroy()
except Exception as e:
dialog = Gtk.MessageDialog(
transient_for=parent,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.OK,
text="Daemon install failed"
)
dialog.format_secondary_text(f"The following error occured:\n{e}")
dialog.run()
dialog.destroy()

28
auto_cpufreq/gui/tray.py Normal file
View File

@ -0,0 +1,28 @@
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, AppIndicator3 as appindicator
from subprocess import run
def main():
indicator = appindicator.Indicator.new("auto-cpufreq-tray", "network-idle-symbolic", appindicator.IndicatorCategory.APPLICATION_STATUS)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
indicator.set_menu(build_menu())
Gtk.main()
def build_menu():
menu = Gtk.Menu()
program = Gtk.MenuItem("auto-cpufreq")
program.connect("activate", open_app)
menu.append(program)
_quit = Gtk.MenuItem("Quit")
_quit.connect("activate", Gtk.main_quit)
menu.append(_quit)
menu.show_all()
return menu
def open_app(MenuItem): run("sudo -E python app.py", shell=True)
if __name__ == "__main__": main()

View File

@ -0,0 +1,345 @@
from dataclasses import dataclass
import os
from pathlib import Path
import platform
from subprocess import getoutput
from typing import Tuple, List
import psutil
import distro
from pathlib import Path
from auto_cpufreq.config.config import config
from auto_cpufreq.core import get_power_supply_ignore_list
from auto_cpufreq.globals import (
AVAILABLE_GOVERNORS_SORTED,
CPU_TEMP_SENSOR_PRIORITY,
IS_INSTALLED_WITH_SNAP,
POWER_SUPPLY_DIR,
)
from typing import Optional
@dataclass
class CoreInfo:
id: int
usage: float
temperature: float
frequency: float
@dataclass
class BatteryInfo:
is_charging: bool | None
is_ac_plugged: bool | None
charging_start_threshold: int | None
charging_stop_threshold: int | None
battery_level: int | None
power_consumption: float | None
def __repr__(self) -> str:
if self.is_charging:
return "charging"
elif not self.is_ac_plugged:
return f"discharging {('(' + '{:.2f}'.format(self.power_consumption) + ' W)') if self.power_consumption != None else ''}"
return "Not Charging"
@dataclass
class SystemReport:
distro_name: str
distro_ver: str
arch: str
processor_model: str
total_core: int | None
kernel_version: str
current_gov: str | None
current_epp: str | None
current_epb: str | None
cpu_driver: str
cpu_fan_speed: int | None
cpu_usage: float
cpu_max_freq: float | None
cpu_min_freq: float | None
load: float
avg_load: Tuple[float, float, float] | None
cores_info: list[CoreInfo]
battery_info: BatteryInfo
is_turbo_on: Tuple[bool | None, bool | None]
class SystemInfo:
"""
Provides system information related to CPU, distribution, and performance metrics.
"""
def __init__(self):
self.distro_name: str = (
distro.name(pretty=True) if not IS_INSTALLED_WITH_SNAP else "UNKNOWN"
)
self.distro_version: str = (
distro.version() if not IS_INSTALLED_WITH_SNAP else "UNKNOWN"
)
self.architecture: str = platform.machine()
self.processor_model: str = (
getoutput("grep -E 'model name' /proc/cpuinfo -m 1").split(":")[-1].strip()
)
self.total_cores: int | None = psutil.cpu_count(logical=True)
self.cpu_driver: str = getoutput(
"cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver"
).strip()
self.kernel_version: str = platform.release()
@staticmethod
def cpu_min_freq() -> float | None:
freqs = psutil.cpu_freq(percpu=True)
return min((freq.min for freq in freqs), default=None)
@staticmethod
def cpu_max_freq() -> float | None:
freqs = psutil.cpu_freq(percpu=True)
return max((freq.max for freq in freqs), default=None)
@staticmethod
def get_cpu_info() -> List[CoreInfo]:
"""Returns detailed CPU information for each core."""
cpu_usage = psutil.cpu_percent(percpu=True)
cpu_freqs = psutil.cpu_freq(percpu=True)
try:
temps = psutil.sensors_temperatures()
temp_sensor = []
for sensor in CPU_TEMP_SENSOR_PRIORITY:
temp_sensor = temps.get(sensor, [])
if temp_sensor != []:
break
core_temps = [temp.current for temp in temp_sensor]
except AttributeError:
core_temps = []
avg_temp = sum(core_temps) / len(core_temps) if core_temps else 0.0
return [
CoreInfo(
id=i,
usage=cpu_usage[i],
temperature=core_temps[i] if i < len(core_temps) else avg_temp,
frequency=cpu_freqs[i].current,
)
for i in range(len(cpu_usage))
]
@staticmethod
def cpu_fan_speed() -> int | None:
fans = psutil.sensors_fans()
return next((fan[0].current for fan in fans.values() if fan), None)
@staticmethod
def current_gov() -> str | None:
try:
with open(
"/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "r"
) as f:
return f.read().strip()
except:
return None
@staticmethod
def current_epp(is_ac_plugged: bool) -> str | None:
epp_path = "/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference"
if not Path(epp_path).exists():
return None
return config.get_config().get(
"charger" if is_ac_plugged else "battery", "energy_performance_preference", fallback="balance_power"
)
@staticmethod
def current_epb(is_ac_plugged: bool) -> str | None:
epb_path = "/sys/devices/system/cpu/intel_pstate"
if not Path(epb_path).exists():
return None
return config.get_config().get(
"charger" if is_ac_plugged else "battery", "energy_perf_bias", fallback="balance_power"
)
@staticmethod
def cpu_usage() -> float:
return psutil.cpu_percent(
interval=0.5
) # Reduced interval for better responsiveness
@staticmethod
def system_load() -> float:
return os.getloadavg()[0]
@staticmethod
def avg_load() -> Tuple[float, float, float]:
return os.getloadavg()
@staticmethod
def avg_temp() -> int:
temps: List[float] = [i.temperature for i in SystemInfo.get_cpu_info()]
return int(sum(temps) / len(temps))
@staticmethod
def turbo_on() -> Tuple[bool | None, bool | None]:
"""Get CPU turbo mode status.
Returns: Tuple[bool | None, bool | None]:
The first value indicates whether turbo mode is enabled, None if unknown
The second value indicates whether auto mode is enabled (amd_pstate only), None if unknown
"""
intel_pstate = Path("/sys/devices/system/cpu/intel_pstate/no_turbo")
cpu_freq = Path("/sys/devices/system/cpu/cpufreq/boost")
amd_pstate = Path("/sys/devices/system/cpu/amd_pstate/status")
if intel_pstate.exists():
control_file: Path = intel_pstate
inverse_logic = True
elif cpu_freq.exists():
control_file = cpu_freq
inverse_logic = False
elif amd_pstate.exists():
amd_status: str = amd_pstate.read_text().strip()
if amd_status == "active":
return None, True
return None, False
else:
return None, None
try:
current_value = int(control_file.read_text().strip())
return bool(current_value) ^ inverse_logic, False
except Exception as e:
return None, None
@staticmethod
def read_file(path: str) -> Optional[str]:
try:
with open(path, "r") as f:
return f.read().strip()
except (FileNotFoundError, OSError):
return None
@staticmethod
def get_battery_path() -> Optional[str]:
try:
for entry in os.listdir(POWER_SUPPLY_DIR):
path = os.path.join(POWER_SUPPLY_DIR, entry)
type_path = os.path.join(path, "type")
if os.path.isfile(type_path):
content = SystemInfo.read_file(type_path)
if content and content.lower() == "battery":
return path
except Exception:
return None
return None
@staticmethod
def battery_info() -> BatteryInfo:
battery_path = SystemInfo.get_battery_path()
# By default, AC is considered connected if no battery is detected
is_ac_plugged = True
is_charging = None
battery_level = None
power_consumption = None
charging_start_threshold = None
charging_stop_threshold = None
if not battery_path:
# No battery detected
return BatteryInfo(
is_charging=None,
is_ac_plugged=is_ac_plugged,
charging_start_threshold=None,
charging_stop_threshold=None,
battery_level=None,
power_consumption=None,
)
# Reading AC info (Hands)
for supply in os.listdir(POWER_SUPPLY_DIR):
supply_path = os.path.join(POWER_SUPPLY_DIR, supply)
supply_type = SystemInfo.read_file(os.path.join(supply_path, "type"))
if supply_type == "Mains":
online = SystemInfo.read_file(os.path.join(supply_path, "online"))
is_ac_plugged = online == "1"
# Reading battery information
battery_status = SystemInfo.read_file(os.path.join(battery_path, "status"))
battery_capacity = SystemInfo.read_file(os.path.join(battery_path, "capacity"))
energy_rate = (
SystemInfo.read_file(os.path.join(battery_path, "power_now"))
or SystemInfo.read_file(os.path.join(battery_path, "current_now"))
)
charge_start_threshold = SystemInfo.read_file(os.path.join(battery_path, "charge_start_threshold"))
charge_stop_threshold = SystemInfo.read_file(os.path.join(battery_path, "charge_stop_threshold"))
is_charging = battery_status.lower() == "charging" if battery_status else None
battery_level = int(battery_capacity) if battery_capacity and battery_capacity.isdigit() else None
power_consumption = float(energy_rate) / 1_000_000 if energy_rate \
and energy_rate.replace('.', '', 1).isdigit() else None
charging_start_threshold = int(charge_start_threshold) if charge_start_threshold \
and charge_start_threshold.isdigit() else None
charging_stop_threshold = int(charge_stop_threshold) if charge_stop_threshold \
and charge_stop_threshold.isdigit() else None
return BatteryInfo(
is_charging=is_charging,
is_ac_plugged=is_ac_plugged,
charging_start_threshold=charging_start_threshold,
charging_stop_threshold=charging_stop_threshold,
battery_level=battery_level,
power_consumption=power_consumption,
)
@staticmethod
def turbo_on_suggestion() -> bool:
usage = SystemInfo.cpu_usage()
if usage >= 20.0:
return True
elif usage <= 25 and SystemInfo.avg_temp() >= 70:
return False
return False
@staticmethod
def governor_suggestion() -> str:
if SystemInfo.battery_info().is_ac_plugged:
return AVAILABLE_GOVERNORS_SORTED[0]
return AVAILABLE_GOVERNORS_SORTED[-1]
def generate_system_report(self) -> SystemReport:
battery_info = self.battery_info()
return SystemReport(
distro_name=self.distro_name,
distro_ver=self.distro_version,
arch=self.architecture,
processor_model=self.processor_model,
total_core=self.total_cores,
cpu_driver=self.cpu_driver,
kernel_version=self.kernel_version,
current_gov=self.current_gov(),
current_epp=self.current_epp(battery_info.is_ac_plugged),
current_epb=self.current_epb(battery_info.is_ac_plugged),
cpu_fan_speed=self.cpu_fan_speed(),
cpu_usage=self.cpu_usage(),
cpu_max_freq=self.cpu_max_freq(),
cpu_min_freq=self.cpu_min_freq(),
load=self.system_load(),
avg_load=self.avg_load(),
cores_info=self.get_cpu_info(),
is_turbo_on=self.turbo_on(),
battery_info=battery_info,
)
system_info = SystemInfo()

View File

@ -0,0 +1,301 @@
import sys
from typing import Callable
import urwid
import time
from .system_info import SystemReport, system_info
from auto_cpufreq.config.config import config
from enum import Enum
class ViewType(str, Enum):
STATS = "Stats"
MONITOR = "Monitor"
LIVE = "Live"
def __str__(self) -> str:
return self.value
class SystemMonitor:
def __init__(self, type: ViewType, suggestion: bool = False):
self.type: ViewType = type
self.title_header = urwid.Text(f"{type} Mode", align="center")
self.header = urwid.Columns(
[
self.title_header,
]
)
# Create separate content walkers for left and right columns
self.left_content = urwid.SimpleListWalker([])
self.right_content = urwid.SimpleListWalker([])
# Create listboxes for both columns
self.left_listbox = urwid.ListBox(self.left_content)
self.right_listbox = urwid.ListBox(self.right_content)
# Create a columns widget with a vertical line (using box drawing character)
self.columns = urwid.Columns(
[
("weight", 1, self.left_listbox),
(
"fixed",
1,
urwid.AttrMap(urwid.SolidFill(""), "divider"),
), # Vertical line # type: ignore
("weight", 1, self.right_listbox),
],
dividechars=0,
)
self.footer = urwid.AttrMap(
urwid.Text(
"Press Q or Ctrl+C to quit | Use ↑↓ or PageUp/PageDown to scroll",
align="center",
),
"footer",
)
self.frame = urwid.Frame(
body=self.columns,
header=urwid.AttrMap(self.header, "header"),
footer=self.footer,
)
palette = [
("header", "white", "dark blue"),
("footer", "white", "dark green"),
("body", "white", "default"),
("divider", "light gray", "default"), # Style for the vertical line
]
if suggestion:
palette.append(("suggestion", "yellow", "default"))
self.loop = urwid.MainLoop(
self.frame, palette=palette, unhandled_input=self.handle_input
)
self.last_focus_left = 0
self.last_focus_right = 0
self.on_quit: Callable[[], None] | None = None
self.suggestion = suggestion
def update(self, loop: urwid.MainLoop, user_data: dict) -> None:
# Store current focus positions
if len(self.left_content) > 0:
_, self.last_focus_left = self.left_listbox.get_focus()
if self.last_focus_left is None:
self.last_focus_left = 0
if len(self.right_content) > 0:
_, self.last_focus_right = self.right_listbox.get_focus()
if self.last_focus_right is None:
self.last_focus_right = 0
current_time = time.strftime("%H:%M:%S")
self.title_header.set_text(f"{self.type} Mode - {current_time}")
report: SystemReport = system_info.generate_system_report()
self.format_system_info(report)
# Restore focus positions
if len(self.left_content) > 0:
self.left_listbox.set_focus(min(self.last_focus_left, len(self.left_content) - 1)) # type: ignore
if len(self.right_content) > 0:
self.right_listbox.set_focus(min(self.last_focus_right, len(self.right_content) - 1)) # type: ignore
self.loop.set_alarm_in(2, self.update) # type: ignore
def handle_input(self, key):
if key in ("q", "Q"):
if self.on_quit:
self.on_quit()
raise urwid.ExitMainLoop()
def format_system_info(self, report: SystemReport):
self.left_content.clear()
self.right_content.clear()
# Helper function to create centered text
def aligned_text(text: str) -> urwid.Text:
return urwid.Text(text, align="left")
# Left Column - System Info and CPU Stats
self.left_content.extend(
[
urwid.AttrMap(aligned_text("System Information"), "header"),
aligned_text(""),
aligned_text(f"Linux distro: {report.distro_name} {report.distro_ver}"),
aligned_text(f"Linux kernel: {report.kernel_version}"),
aligned_text(f"Processor: {report.processor_model}"),
aligned_text(f"Cores: {report.total_core}"),
aligned_text(f"Architecture: {report.arch}"),
aligned_text(f"Driver: {report.cpu_driver}"),
aligned_text(""),
]
)
if config.has_config():
self.left_content.append(
aligned_text(f"Using settings defined in {config.path} file")
)
self.left_content.append(aligned_text(""))
# CPU Stats
self.left_content.extend(
[
urwid.AttrMap(aligned_text("Current CPU Stats"), "header"),
aligned_text(""),
aligned_text(f"CPU max frequency: {report.cpu_max_freq} MHz"),
aligned_text(f"CPU min frequency: {report.cpu_min_freq} MHz"),
aligned_text(""),
aligned_text("Core Usage Temperature Frequency"),
]
)
for core in report.cores_info:
self.left_content.append(
aligned_text(
f"CPU{core.id:<2} {core.usage:>4.1f}% {core.temperature:>6.0f} °C {core.frequency:>6.0f} MHz"
)
)
if report.cpu_fan_speed:
self.left_content.append(aligned_text(""))
self.left_content.append(
aligned_text(f"CPU fan speed: {report.cpu_fan_speed} RPM")
)
# Right Column - Battery, Frequency Scaling, and System Stats
if report.battery_info != None:
self.right_content.extend(
[
urwid.AttrMap(aligned_text("Battery Stats"), "header"),
aligned_text(""),
aligned_text(f"Battery status: {str(report.battery_info)}"),
aligned_text(
f"Battery precentage: {(str(report.battery_info.battery_level) + '%') if report.battery_info.battery_level != None else 'Unknown'}"
),
aligned_text(
f'AC plugged: {("Yes" if report.battery_info.is_ac_plugged else "No") if report.battery_info.is_ac_plugged != None else "Unknown"}'
),
aligned_text(
f'Charging start threshold: {report.battery_info.charging_start_threshold if report.battery_info.is_ac_plugged != None else "Unknown"}'
),
aligned_text(
f'Charging stop threshold: {report.battery_info.charging_stop_threshold if report.battery_info.is_ac_plugged != None else "Unknown"}'
),
aligned_text(""),
]
)
# CPU Frequency Scaling
self.right_content.extend(
[
urwid.AttrMap(aligned_text("CPU Frequency Scaling"), "header"),
aligned_text(""),
aligned_text(
f'Setting to use: "{report.current_gov if report.current_gov != None else "Unknown"}" governor'
),
]
)
if (
self.suggestion
and report.current_gov != None
and system_info.governor_suggestion() != report.current_gov
):
self.right_content.append(
urwid.AttrMap(
aligned_text(
f'Suggesting use of: "{system_info.governor_suggestion()}" governor'
),
"suggestion",
)
)
if report.current_epp:
self.right_content.append(
aligned_text(f"EPP setting: {report.current_epp}")
)
else:
self.right_content.append(
aligned_text("Not setting EPP (not supported by system)")
)
if report.current_epb:
self.right_content.append(
aligned_text(f'Setting to use: "{report.current_epb}" EPB')
)
self.right_content.append(aligned_text(""))
# System Statistics
self.right_content.extend(
[
urwid.AttrMap(aligned_text("System Statistics"), "header"),
aligned_text(""),
aligned_text(f"Total CPU usage: {report.cpu_usage:.1f} %"),
aligned_text(f"Total system load: {report.load:.2f}"),
]
)
if report.cores_info:
avg_temp = sum(core.temperature for core in report.cores_info) / len(
report.cores_info
)
self.right_content.append(
aligned_text(f"Average temp. of all cores: {avg_temp:.2f} °C")
)
if report.avg_load:
load_status = "Load optimal" if report.load < 1.0 else "Load high"
self.right_content.append(
aligned_text(
f"{load_status} (load average: {report.avg_load[0]:.2f}, {report.avg_load[1]:.2f}, {report.avg_load[2]:.2f})"
)
)
if report.cores_info:
usage_status = "Optimal" if report.cpu_usage < 70 else "High"
temp_status = "high" if avg_temp > 75 else "normal" # type: ignore
self.right_content.append(
aligned_text(
f"{usage_status} total CPU usage: {report.cpu_usage:.1f}%, {temp_status} average core temp: {avg_temp:.1f}°C" # type: ignore
)
)
turbo_status: str
if report.is_turbo_on[0] != None:
turbo_status = "On" if report.is_turbo_on[0] else "Off"
elif report.is_turbo_on[1] != None:
turbo_status = (
f"Auto mode {'enabled' if report.is_turbo_on[1] else 'disabled'}"
)
else:
turbo_status = "Unknown"
self.right_content.append(aligned_text(f"Setting turbo boost: {turbo_status}"))
if (
self.suggestion
and report.is_turbo_on[0] != None
and system_info.turbo_on_suggestion() != report.is_turbo_on[0]
):
self.right_content.append(
urwid.AttrMap(
aligned_text(
f'Suggesting to set turbo boost: {"on" if system_info.turbo_on_suggestion() else "off"}'
),
"suggestion",
)
)
def run(self, on_quit: Callable[[], None] | None = None):
try:
if on_quit:
self.on_quit = on_quit
self.loop.set_alarm_in(0, self.update) # type: ignore
self.loop.run()
except KeyboardInterrupt:
if on_quit:
on_quit()
sys.exit(0)

View File

@ -0,0 +1,313 @@
# * add status as one of the available options
# * alert user on snap if detected and how to remove first time live/stats message starts
# * if daemon is disabled and auto-cpufreq is removed (snap) remind user to enable it back
import click
from shutil import which
from subprocess import call, DEVNULL, getoutput, STDOUT
from sys import argv
# ToDo: update README part how to run this script
from auto_cpufreq.core import *
from auto_cpufreq.globals import GITHUB, IS_INSTALLED_WITH_SNAP
from auto_cpufreq.tlp_stat_parser import TLPStatusParser
# app_name var
app_name = "python3 power_helper.py" if argv[0] == "power_helper.py" else "auto-cpufreq"
def header(): print("\n------------------------- auto-cpufreq: Power helper -------------------------\n")
def warning(): print("\n----------------------------------- Warning -----------------------------------\n")
def helper_opts(): print("\nFor full list of options run: python3 -m auto_cpufreq.power_helper --help")
# used to check if binary exists on the system
def does_command_exists(cmd): return which(cmd) is not None
bluetoothctl_exists = does_command_exists("bluetoothctl")
powerprofilesctl_exists = does_command_exists("powerprofilesctl")
systemctl_exists = does_command_exists("systemctl")
tlp_stat_exists = does_command_exists("tlp-stat")
tuned_stat_exists = does_command_exists("tuned")
# detect if gnome power profile service is running
if not IS_INSTALLED_WITH_SNAP:
if systemctl_exists:
try: gnome_power_status = call(["systemctl", "is-active", "--quiet", "power-profiles-daemon"])
except:
print("\nUnable to determine init system")
print("If this causes any problems, please submit an issue:")
print(GITHUB+"/issues")
# alert in case TLP service is running
def tlp_service_detect():
if tlp_stat_exists:
status_output = getoutput("tlp-stat -s")
tlp_status = TLPStatusParser(status_output)
if tlp_status.is_enabled():
warning()
print("Detected you are running a TLP service!")
print("This daemon might interfere with auto-cpufreq which can lead to unexpected results.")
print("We strongly encourage you to remove TLP unless you really know what you are doing.")
# alert about TLP when using snap
def tlp_service_detect_snap():
warning()
print("Unable to detect if you are using a TLP service!")
print("This daemon might interfere with auto-cpufreq which can lead to unexpected results.")
print("We strongly encourage you not to use TLP unless you really know what you are doing.")
# alert in case gnome power profile service is running
def gnome_power_detect():
if systemctl_exists and not bool(gnome_power_status):
warning()
print("Detected running GNOME Power Profiles daemon service!")
print("\nThis daemon might interfere with auto-cpufreq and will be automatically")
print("disabled when auto-cpufreq daemon is installed and")
print("it will be re-enabled after auto-cpufreq is removed.")
print("\nOnly necessary to be manually done on Snap package installs!")
print("Steps to perform this action using auto-cpufreq: power_helper script:")
print(f"git clone {GITHUB}.git")
print("python3 -m auto_cpufreq.power_helper --gnome_power_disable")
print(f"\nReference: {GITHUB}#configuring-auto-cpufreq")
# automatically disable gnome power profile service in case it's running during install
def gnome_power_detect_install():
if systemctl_exists and not bool(gnome_power_status):
warning()
print("Detected running GNOME Power Profiles daemon service!")
print("\nThis daemon might interfere with auto-cpufreq and has been disabled.\n")
print('This daemon is not automatically disabled in "monitor" mode and')
print("will be enabled after auto-cpufreq daemon is removed.")
# notification on snap
def gnome_power_detect_snap():
warning()
print("Due to Snap package confinement limitations please consider installing auto-cpufreq using")
print(f"auto-cpufreq-installer: {GITHUB}#auto-cpufreq-installer")
print()
print("Unable to detect state of GNOME Power Profiles daemon service!")
print("This daemon might interfere with auto-cpufreq and should be disabled!")
print("\nSteps to perform this action using auto-cpufreq: power_helper script:")
print(f"git clone {GITHUB}.git")
print("python3 -m auto_cpufreq.power_helper --gnome_power_disable")
print(f"\nReference: {GITHUB}#configuring-auto-cpufreq")
# stops gnome >= 40 power profiles (live)
def gnome_power_stop_live():
if systemctl_exists and not bool(gnome_power_status) and powerprofilesctl_exists:
call(["powerprofilesctl", "set", "balanced"])
call(["systemctl", "stop", "power-profiles-daemon"])
# stops tuned (live)
def tuned_stop_live():
if systemctl_exists and tuned_stat_exists:
call(["systemctl", "stop", "tuned"])
# starts gnome >= 40 power profiles (live)
def gnome_power_start_live():
if systemctl_exists: call(["systemctl", "start", "power-profiles-daemon"])
def tuned_start_live():
if systemctl_exists and tuned_stat_exists:
call(["systemctl", "start", "tuned"])
# enable gnome >= 40 power profiles (uninstall)
def gnome_power_svc_enable():
if systemctl_exists:
try:
print("* Enabling GNOME power profiles\n")
call(["systemctl", "unmask", "power-profiles-daemon"])
call(["systemctl", "enable", "--now", "power-profiles-daemon"])
except:
print("\nUnable to enable GNOME power profiles")
print("If this causes any problems, please submit an issue:")
print(GITHUB+"/issues")
def tuned_svc_enable():
if systemctl_exists and tuned_stat_exists:
try:
print("* Enabling TuneD\n")
call(["systemctl", "unmask", "tuned"])
call(["systemctl", "enable", "--now", "tuned"])
except:
print("\nUnable to enable TuneD daemon")
print("If this causes any problems, please submit an issue:")
print(GITHUB+"/issues")
# gnome power profiles current status
def gnome_power_svc_status():
if systemctl_exists:
try:
print("* GNOME power profiles status")
call(["systemctl", "status", "power-profiles-daemon"])
except:
print("\nUnable to see GNOME power profiles status")
print("If this causes any problems, please submit an issue:")
print(GITHUB+"/issues")
# disable bluetooth on boot
def bluetooth_disable():
if IS_INSTALLED_WITH_SNAP: bluetooth_notif_snap()
elif bluetoothctl_exists:
print("* Turn off Bluetooth on boot (only)!")
print(" If you want bluetooth enabled on boot run: auto-cpufreq --bluetooth_boot_on")
btconf = Path("/etc/bluetooth/main.conf")
try:
orig_set = "AutoEnable=true"
change_set = "AutoEnable=false"
with btconf.open(mode="r+") as f:
content = f.read()
f.seek(0)
f.truncate()
f.write(content.replace(orig_set, change_set))
except Exception as e: print(f"\nERROR:\nWas unable to turn off bluetooth on boot\n{repr(e)}")
else: print("* Turn off bluetooth on boot [skipping] (package providing bluetooth access is not present)")
# enable bluetooth on boot
def bluetooth_enable():
if IS_INSTALLED_WITH_SNAP: bluetooth_on_notif_snap()
if bluetoothctl_exists:
print("* Turn on bluetooth on boot")
btconf = "/etc/bluetooth/main.conf"
try:
orig_set = "AutoEnable=true"
change_set = "AutoEnable=false"
with open(btconf, "r+") as f:
content = f.read()
f.seek(0)
f.truncate()
f.write(content.replace(change_set, orig_set))
except Exception as e: print(f"\nERROR:\nWas unable to turn on bluetooth on boot\n{repr(e)}")
else: print("* Turn on bluetooth on boot [skipping] (package providing bluetooth access is not present)")
# turn off bluetooth on snap message
def bluetooth_notif_snap():
print("\n* Unable to turn off bluetooth on boot due to Snap package restrictions!")
print("\nSteps to perform this action using auto-cpufreq: power_helper script:")
print("python3 -m auto_cpufreq.power_helper --bluetooth_boot_off")
print("\nFor help see: https://github.com/AdnanHodzic/auto-cpufreq/#1-power_helperpy-script-snap-package-install-only")
# turn off bluetooth on snap message
def bluetooth_on_notif_snap():
print("\n* Unable to turn on bluetooth on boot due to Snap package restrictions!")
print("\nSteps to perform this action using auto-cpufreq: power_helper script:")
print("python3 -m auto_cpufreq.power_helper --bluetooth_boot_on")
print("\nFor help see: https://github.com/AdnanHodzic/auto-cpufreq/#1-power_helperpy-script-snap-package-install-only")
# gnome power removal reminder
def gnome_power_rm_reminder():
if systemctl_exists and bool(gnome_power_status):
warning()
print("Detected GNOME Power Profiles daemon service is stopped!")
print("This service will now be enabled and started again.\n")
def gnome_power_rm_reminder_snap():
warning()
print("Unable to detect state of GNOME Power Profiles daemon service!")
print("Now it's recommended to enable this service.")
print("\nSteps to perform this action using auto-cpufreq: power_helper script:")
print(f"git clone {GITHUB}.git")
print("python3 -m auto_cpufreq.power_helper --gnome_power_enable")
print(f"\nReference: {GITHUB}#configuring-auto-cpufreq")
def valid_options():
print("--gnome_power_enable\t\tEnable GNOME Power Profiles daemon")
print("--gnome_power_disable\t\tDisable GNOME Power Profiles daemon\n")
def disable_power_profiles_daemon():
# always disable power-profiles-daemon
try:
print("\n* Disabling GNOME power profiles")
call(["systemctl", "disable", "--now", "power-profiles-daemon"])
call(["systemctl", "mask", "power-profiles-daemon"])
except:
print("\nUnable to disable GNOME power profiles")
print("If this causes any problems, please submit an issue:")
print(GITHUB+"/issues")
def disable_tuned_daemon():
# always disable TuneD daemon
try:
print("\n* Disabling TuneD daemon")
call(["systemctl", "disable", "--now", "tuned"])
call(["systemctl", "mask", "tuned"])
except:
print("\nUnable to disable TuneD daemon")
print("If this causes any problems, please submit an issue:")
print(GITHUB+"/issues")
# default gnome_power_svc_disable func (balanced)
def gnome_power_svc_disable():
snap_pkg_check = 0
if systemctl_exists:
if bool(gnome_power_status):
try:
# check if snap package installed
snap_pkg_check = call(['snap', 'list', '|', 'grep', 'auto-cpufreq'],
stdout=DEVNULL,
stderr=STDOUT)
# check if snapd is present and if snap package is installed | 0 is success
if not bool(snap_pkg_check):
print("GNOME Power Profiles Daemon is already disabled, it can be re-enabled by running:\n"
"sudo python3 -m auto_cpufreq.power_helper --gnome_power_enable\n"
)
elif snap_pkg_check == 1:
print("auto-cpufreq snap package not installed\nGNOME Power Profiles Daemon should be enabled. run:\n\n"
"sudo python3 -m auto_cpufreq.power_helper --gnome_power_enable"
)
except:
# snapd not found on the system
print("There was a problem, couldn't determine GNOME Power Profiles Daemon")
snap_pkg_check = 0
if not bool(gnome_power_status) and powerprofilesctl_exists:
if snap_pkg_check == 1:
print("auto-cpufreq snap package not installed.\nGNOME Power Profiles Daemon should be enabled, run:\n\n"
"sudo python3 -m auto_cpufreq.power_helper --gnome_power_enable"
)
else:
print("auto-cpufreq snap package installed, GNOME Power Profiles Daemon should be disabled.\n")
print("Using profile: ", "balanced")
call(["powerprofilesctl", "set", "balanced"])
disable_power_profiles_daemon()
def tuned_svc_disable():
if systemctl_exists and tuned_stat_exists:
disable_tuned_daemon()
# cli
@click.command()
#@click.option("--gnome_power_disable", help="Disable GNOME Power profiles service (default: balanced), reference:\n https://bit.ly/3bjVZW1", type=click.Choice(['balanced', 'performance'], case_sensitive=False))
@click.option("--gnome_power_disable", is_flag=True, help="Disable GNOME Power profiles service")
# ToDo:
# * update readme/docs
@click.option("--gnome_power_enable", is_flag=True, help="Enable GNOME Power profiles service")
@click.option("--gnome_power_status", is_flag=True, help="Get status of GNOME Power profiles service")
@click.option("--bluetooth_boot_on", is_flag=True, help="Turn on Bluetooth on boot")
@click.option("--bluetooth_boot_off", is_flag=True, help="Turn off Bluetooth on boot")
def main(
gnome_power_enable,
gnome_power_disable,
gnome_power_status,
bluetooth_boot_off,
bluetooth_boot_on,
):
root_check()
header()
if len(argv) == 1: print('Unrecognized option!\n\nRun: "' + app_name + ' --help" for list of available options.')
else:
if gnome_power_enable: gnome_power_svc_enable()
elif gnome_power_disable: gnome_power_svc_disable()
elif gnome_power_status: gnome_power_svc_status()
elif bluetooth_boot_off: bluetooth_disable()
elif bluetooth_boot_on: bluetooth_enable()
helper_opts()
footer()
if __name__ == "__main__": main()

View File

@ -0,0 +1,13 @@
class TLPStatusParser:
def __init__(self, tlp_stat_output):
self.data = {}
self._parse(tlp_stat_output)
def _parse(self, data):
for line in data.split("\n"):
key_val = line.split("=", 1)
if len(key_val) > 1: self.data[key_val[0].strip().lower()] = key_val[1].strip()
def _get_key(self, key): return self.data[key] if key in self.data else ""
def is_enabled(self): return self._get_key("state") == "enabled"

View File

@ -1,115 +0,0 @@
#!/usr/bin/env python3
#
# auto-cpufreq - Automatic CPU speed & power optimizer for Linux
#
# Blog post: http://foolcontrol.org/?p=3124
# core import
import sys
from subprocess import call, run
sys.path.append('../')
from source.core import *
import click
# cli
@click.command()
@click.option("--monitor", is_flag=True, help="Monitor and suggest CPU optimizations")
@click.option("--live", is_flag=True, help="Monitor and make suggested CPU optimizations")
@click.option("--install/--remove", default=True, help="Install/remove daemon for automatic CPU optimizations")
@click.option("--log", is_flag=True, help="View live CPU optimization log made by daemon")
@click.option("--daemon", is_flag=True, hidden=True)
@click.option("--debug", is_flag=True, help="Show debug info (include when submitting bugs)")
def main(monitor, live, daemon, install, log, debug):
if len(sys.argv) == 1:
print("\n" + "-" * 32 + " auto-cpufreq " + "-" * 33 + "\n")
print("Automatic CPU speed & power optimizer for Linux")
print("\nExample usage:\nauto-cpufreq --monitor")
print("\n-----\n")
run(["auto-cpufreq", "--help"])
footer()
else:
# Important: order does matter
if daemon:
if os.getenv("PKG_MARKER") == "SNAP" and dcheck == "enabled":
while True:
root_check()
gov_check()
cpufreqctl()
sysinfo()
set_autofreq()
countdown(5)
run("clear")
elif os.getenv("PKG_MARKER") != "SNAP":
while True:
root_check()
gov_check()
cpufreqctl()
sysinfo()
set_autofreq()
countdown(5)
run("clear")
else:
print("\n" + "-" * 32 + " Daemon check " + "-" * 33 + "\n")
print("ERROR:\n\nDaemon not enabled, must run install first, i.e: \nsudo auto-cpufreq --install")
footer()
exit(1)
elif monitor:
while True:
root_check()
running_daemon()
gov_check()
cpufreqctl()
sysinfo()
mon_autofreq()
countdown(5)
run("clear")
elif live:
while True:
root_check()
running_daemon()
gov_check()
cpufreqctl()
sysinfo()
set_autofreq()
countdown(5)
run("clear")
elif log:
# ToDo: fail if log is missing or empty (on)
read_log()
elif debug:
footer()
print(get_sys_info())
footer()
elif install:
if os.getenv('PKG_MARKER') == "SNAP":
root_check()
running_daemon()
gov_check()
run("snapctl set daemon=enabled", shell=True)
run("snapctl start --enable auto-cpufreq", shell=True)
deploy_complete_msg()
else:
root_check()
running_daemon()
gov_check()
deploy_daemon()
deploy_complete_msg()
elif remove:
if os.getenv('PKG_MARKER') == "SNAP":
root_check()
run("snapctl set daemon=disabled", shell=True)
run("snapctl stop --disable auto-cpufreq", shell=True)
if auto_cpufreq_log_file.exists():
auto_cpufreq_log_file.unlink()
remove_complete_msg()
else:
root_check()
remove()
remove_complete_msg()
if __name__ == '__main__':
main()

27
flake.lock generated Normal file
View File

@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1750605355,
"narHash": "sha256-xT8cPLTxlktxI9vSdoBlAVK7dXgd8IK59j7ZwzkkhnI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3078b9a9e75f1790e6d6ef9955fdc6a2d1740cc6",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

24
flake.nix Normal file
View File

@ -0,0 +1,24 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
outputs = {nixpkgs, ...} @ inputs: let
forAllSystems = nixpkgs.lib.genAttrs ["x86_64-linux" "i686-linux" "aarch64-linux"];
pkgsForEach = nixpkgs.legacyPackages;
in {
packages = forAllSystems (system: {
default = pkgsForEach.${system}.callPackage ./nix/default.nix {};
});
devShells = forAllSystems (system: {
default = pkgsForEach.${system}.callPackage ./nix/shell.nix {};
});
overlays.default = final: _: {
auto-cpufreq = final.callPackage ./nix/default.nix {};
};
nixosModules.default = import ./nix/module.nix inputs;
};
}

BIN
images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

92
nix/default.nix Normal file
View File

@ -0,0 +1,92 @@
{
lib,
python3Packages,
pkgs,
fetchFromGitHub,
fetchPypi,
}:
let
pyinotify = python3Packages.pyinotify.overrideAttrs (oldAttrs: {
src = fetchFromGitHub {
owner = "shadeyg56";
repo = "pyinotify-3.12";
rev = "923cebec3a2a84c7e38c9e68171eb93f5d07ce5d";
hash = "sha256-714CximEK4YhIqDmvqJYOUGs39gvDkWGrkNrXwxT8iM=";
};
patches = [];
});
requests = python3Packages.requests.overrideAttrs (oldAttrs: rec {
version = "2.32.4";
src = fetchPypi {
pname = "requests";
inherit version;
hash = "sha256-J9AxZoLIopg00yZIIAJLYqNpQgg9Usry8UwFkTNtNCI=";
};
patches = [];
});
in
python3Packages.buildPythonPackage {
# use pyproject.toml instead of setup.py
format = "pyproject";
pname = "auto-cpufreq";
version = "2.6.0";
src = ../.;
nativeBuildInputs = with pkgs; [wrapGAppsHook gobject-introspection];
buildInputs = with pkgs; [gtk3 python3Packages.poetry-core];
propagatedBuildInputs = with python3Packages; [requests pygobject3 click distro psutil setuptools poetry-dynamic-versioning pyinotify urwid pyasyncore pkgs.getent];
doCheck = false;
pythonImportsCheck = ["auto_cpufreq"];
patches = [
# patch to prevent script copying and to disable install
./patches/prevent-install-and-copy.patch
];
postPatch = ''
substituteInPlace auto_cpufreq/core.py --replace-fail '/opt/auto-cpufreq/override.pickle' /var/run/override.pickle
substituteInPlace scripts/org.auto-cpufreq.pkexec.policy --replace-fail "/opt/auto-cpufreq/venv/bin/auto-cpufreq" $out/bin/auto-cpufreq
substituteInPlace auto_cpufreq/gui/app.py auto_cpufreq/gui/objects.py --replace-fail "/usr/local/share/auto-cpufreq/images/icon.png" $out/share/pixmaps/auto-cpufreq.png
substituteInPlace auto_cpufreq/gui/app.py --replace-fail "/usr/local/share/auto-cpufreq/scripts/style.css" $out/share/auto-cpufreq/scripts/style.css
'';
postInstall = ''
# copy script manually
cp scripts/cpufreqctl.sh $out/bin/cpufreqctl.auto-cpufreq
# move the css to the right place
mkdir -p $out/share/auto-cpufreq/scripts
cp scripts/style.css $out/share/auto-cpufreq/scripts/style.css
# systemd service
mkdir -p $out/lib/systemd/system
cp scripts/auto-cpufreq.service $out/lib/systemd/system
# desktop icon
mkdir -p $out/share/applications
mkdir $out/share/pixmaps
cp scripts/auto-cpufreq-gtk.desktop $out/share/applications
cp images/icon.png $out/share/pixmaps/auto-cpufreq.png
# polkit policy
mkdir -p $out/share/polkit-1/actions
cp scripts/org.auto-cpufreq.pkexec.policy $out/share/polkit-1/actions
'';
meta = {
homepage = "https://github.com/AdnanHodzic/auto-cpufreq";
description = "Automatic CPU speed & power optimizer for Linux";
license = lib.licenses.lgpl3Plus;
platforms = lib.platforms.linux;
maintainers = with lib.maintainers; [Technical27];
mainProgram = "auto-cpufreq";
};
}

64
nix/module.nix Normal file
View File

@ -0,0 +1,64 @@
inputs: {
config,
lib,
pkgs,
...
}:
let
cfg = config.programs.auto-cpufreq;
inherit (pkgs.stdenv.hostPlatform) system;
defaultPackage = inputs.self.packages.${system}.default;
cfgFilename = "auto-cpufreq.conf";
cfgFile = format.generate cfgFilename cfg.settings;
inherit (lib) types;
inherit (lib.modules) mkIf mkForce;
inherit (lib.options) mkOption mkEnableOption;
format = pkgs.formats.ini {};
in {
options.programs.auto-cpufreq = {
enable = mkEnableOption "Automatic CPU speed & power optimizer for Linux";
settings = mkOption {
description = ''
Configuration for `auto-cpufreq`.
See its [example configuration file] for supported settings.
[example configuration file]: https://github.com/AdnanHodzic/auto-cpufreq/blob/master/auto-cpufreq.conf-example
'';
default = {};
type = types.submodule {freeformType = format.type;};
};
};
config = mkIf cfg.enable {
environment.systemPackages = [ defaultPackage ];
systemd = {
packages = [ defaultPackage ];
services.auto-cpufreq = {
wantedBy = [ "multi-user.target" ];
path = with pkgs; [ bash coreutils ];
overrideStrategy = "asDropin";
serviceConfig.WorkingDirectory = "";
serviceConfig.ExecStart = mkForce [
""
"${defaultPackage}/bin/auto-cpufreq --daemon --config ${cfgFile}"
];
};
};
assertions = [
{
assertion = !config.services.power-profiles-daemon.enable;
message = ''
You have set services.power-profiles-daemon.enable = true;
which conflicts with auto-cpufreq
'';
}
];
};
}

View File

@ -0,0 +1,101 @@
diff --git a/auto_cpufreq/core.py b/auto_cpufreq/core.py
index f03e7de..2dff5fb 100755
--- a/auto_cpufreq/core.py
+++ b/auto_cpufreq/core.py
@@ -277,19 +277,12 @@ def get_current_gov():
)
def cpufreqctl():
- """
- deploy cpufreqctl.auto-cpufreq script
- """
- if not (IS_INSTALLED_WITH_SNAP or os.path.isfile("/usr/local/bin/cpufreqctl.auto-cpufreq")):
- copy(SCRIPTS_DIR / "cpufreqctl.sh", "/usr/local/bin/cpufreqctl.auto-cpufreq")
- call(["chmod", "a+x", "/usr/local/bin/cpufreqctl.auto-cpufreq"])
+ # scripts are already in the correct place
+ pass
def cpufreqctl_restore():
- """
- remove cpufreqctl.auto-cpufreq script
- """
- if not IS_INSTALLED_WITH_SNAP and os.path.isfile("/usr/local/bin/cpufreqctl.auto-cpufreq"):
- os.remove("/usr/local/bin/cpufreqctl.auto-cpufreq")
+ #no need to restore
+ pass
def footer(l=79): print("\n" + "-" * l + "\n")
@@ -307,31 +300,8 @@ def remove_complete_msg():
footer()
def deploy_daemon():
- print("\n" + "-" * 21 + " Deploying auto-cpufreq as a daemon " + "-" * 22 + "\n")
-
- cpufreqctl() # deploy cpufreqctl script func call
-
- bluetooth_disable() # turn off bluetooth on boot
-
- auto_cpufreq_stats_path.touch(exist_ok=True)
-
- print("\n* Deploy auto-cpufreq install script")
- copy(SCRIPTS_DIR / "auto-cpufreq-install.sh", "/usr/local/bin/auto-cpufreq-install")
- call(["chmod", "a+x", "/usr/local/bin/auto-cpufreq-install"])
-
- print("\n* Deploy auto-cpufreq remove script")
- copy(SCRIPTS_DIR / "auto-cpufreq-remove.sh", "/usr/local/bin/auto-cpufreq-remove")
- call(["chmod", "a+x", "/usr/local/bin/auto-cpufreq-remove"])
-
- # output warning if gnome power profile is running
- gnome_power_detect_install()
- gnome_power_svc_disable()
-
- tuned_svc_disable()
-
- tlp_service_detect() # output warning if TLP service is detected
-
- call("/usr/local/bin/auto-cpufreq-install", shell=True)
+ # prevent needless copying and system changes
+ pass
def deploy_daemon_performance():
print("\n" + "-" * 21 + " Deploying auto-cpufreq as a daemon (performance) " + "-" * 22 + "\n")
@@ -363,37 +333,7 @@ def deploy_daemon_performance():
call("/usr/local/bin/auto-cpufreq-install", shell=True)
-def remove_daemon():
- # check if auto-cpufreq is installed
- if not os.path.exists("/usr/local/bin/auto-cpufreq-remove"):
- print("\nauto-cpufreq daemon is not installed.\n")
- sys.exit(1)
-
- print("\n" + "-" * 21 + " Removing auto-cpufreq daemon " + "-" * 22 + "\n")
-
- bluetooth_enable() # turn on bluetooth on boot
-
- # output warning if gnome power profile is stopped
- gnome_power_rm_reminder()
- gnome_power_svc_enable()
-
- tuned_svc_enable()
-
- # run auto-cpufreq daemon remove script
- call("/usr/local/bin/auto-cpufreq-remove", shell=True)
-
- # remove auto-cpufreq-remove
- os.remove("/usr/local/bin/auto-cpufreq-remove")
-
- # delete override pickle if it exists
- if os.path.exists(governor_override_state): os.remove(governor_override_state)
-
- # delete stats file
- if auto_cpufreq_stats_path.exists():
- if auto_cpufreq_stats_file is not None: auto_cpufreq_stats_file.close()
- auto_cpufreq_stats_path.unlink()
-
- cpufreqctl_restore() # restore original cpufrectl script
+def remove_daemon(): pass
def gov_check():
for gov in AVAILABLE_GOVERNORS:

15
nix/shell.nix Normal file
View File

@ -0,0 +1,15 @@
{
python3Packages,
pkgs,
...
}: let
mainPkg = python3Packages.callPackage ./default.nix {};
in
mainPkg.overrideAttrs (oa: {
nativeBuildInputs =
[
python3Packages.pip
pkgs.poetry
]
++ (oa.nativeBuildInputs or []);
})

1397
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

54
pyproject.toml Normal file
View File

@ -0,0 +1,54 @@
[tool.poetry]
name = "auto-cpufreq"
version = "2.6.0"
description = "Automatic CPU speed & power optimizer for Linux"
authors = ["Adnan Hodzic <adnan@hodzic.org>"]
license = "GPL-3.0-or-later"
readme = "README.md"
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Operating System :: POSIX :: Linux",
"Environment :: Console",
"Natural Language :: English"
]
keywords=["linux", "cpu", "speed", "power", "frequency", "turbo", "optimzier", "auto", "cpufreq"]
repository = "https://github.com/AdnanHodzic/auto-cpufreq"
documentation = "https://github.com/AdnanHodzic/auto-cpufreq#readme"
packages = [
{ include = "./auto_cpufreq" },
{ include = "./auto_cpufreq/gui" },
]
[tool.poetry.dependencies]
python = ">=3.9, <4.0"
psutil = ">=6.0.0,<8.0.0"
click = "^8.1.0"
distro = "^1.8.0"
requests = "^2.32.4"
# PyObject version will be updated automatically for Debian based distro based on libgirepository
# apt package version: https://github.com/AdnanHodzic/auto-cpufreq/pull/826#issuecomment-2794549837
PyGObject = {version="3.50.0", optional=true}
urwid = "^2.6.16"
pyinotify = {git = "https://github.com/shadeyg56/pyinotify-3.12"}
pyasyncore = "^1.0.4"
[tool.poetry.group.dev.dependencies]
poetry = "^1.6.1"
[build-system]
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
build-backend = "poetry_dynamic_versioning.backend"
[tool.poetry.scripts]
auto-cpufreq = "auto_cpufreq.bin.auto_cpufreq:main"
auto-cpufreq-gtk = "auto_cpufreq.bin.auto_cpufreq_gtk:main"
# https://github.com/mtkennerly/poetry-dynamic-versioning
[tool.poetry-dynamic-versioning]
enable = true
vcs = "git"
format = "v{base}+{commit}"
# SideNote
# Regarding zip_safe = https://setuptools.pypa.io/en/latest/deprecated/zip_safe.html

View File

@ -1,3 +0,0 @@
psutil
click
distro

View File

@ -0,0 +1,3 @@
type = scripted
command = /usr/local/bin/auto-cpufreq --daemon
run-as = root

View File

@ -0,0 +1,8 @@
[Desktop Entry]
Name=auto-cpufreq
Exec=auto-cpufreq-gtk
Type=Application
Terminal=false
Icon=auto-cpufreq
StartupWMClass=app.py
Categories=System;

View File

@ -1,27 +1,96 @@
#!/bin/bash
#!/usr/bin/env bash
#
# auto-cpufreq daemon install script
# reference: https://github.com/AdnanHodzic/auto-cpufreq
# Thanks to https://github.com/errornonamer for openrc fix
echo -e "\n------------------ Running auto-cpufreq daemon install script ------------------"
MID="$((`tput cols` / 2))"
if [[ $EUID != 0 ]];
then
echo -e "\nERROR\nMust be run as root (i.e: 'sudo $0')\n"
exit 1
echo
printf "%0.s─" $(seq $(( (MID-(${#1}/2)-2) / 2 )))
printf " Running auto-cpufreq daemon install script "
printf "%0.s─" $(seq $(( (MID-(${#1}/2)-2) / 2 )))
echo; echo
# root check
if ((EUID != 0)); then
echo; echo "Must be run as root (i.e: 'sudo $0')."; echo
exit 1
fi
echo -e "\n* Deploy auto-cpufreq systemd unit file"
cp /usr/local/share/auto-cpufreq/scripts/auto-cpufreq.service /etc/systemd/system/auto-cpufreq.service
# First argument is the init name, second argument is the start command, third argument is the enable command
function auto_cpufreq_install {
echo -e "\n* Starting auto-cpufreq daemon ($1) service"
$2
echo -e "\n* Enabling auto-cpufreq daemon ($1) at boot"
$3
}
echo -e "\n* Reloading systemd manager configuration"
systemctl daemon-reload
case "$(ps h -o comm 1)" in
dinit)
echo -e "\n* Deploying auto-cpufreq (dinit) unit file"
cp /usr/local/share/auto-cpufreq/scripts/auto-cpufreq-dinit /etc/dinit.d/auto-cpufreq
echo -e "\n* Stopping auto-cpufreq daemon (systemd) service"
systemctl stop auto-cpufreq
auto_cpufreq_install "dinit" "dinitctl start auto-cpufreq" "dinitctl enable auto-cpufreq"
;;
init)
echo -e "\n* Deploying auto-cpufreq openrc unit file"
cp /usr/local/share/auto-cpufreq/scripts/auto-cpufreq-openrc /etc/init.d/auto-cpufreq
chmod +x /etc/init.d/auto-cpufreq
echo -e "\n* Starting auto-cpufreq daemon (systemd) service"
systemctl start auto-cpufreq
auto_cpufreq_install "openrc" "rc-service auto-cpufreq start" "rc-update add auto-cpufreq"
;;
runit)
# First argument is the "sv" path, second argument is the "service" path
runit_ln() {
echo -e "\n* Deploying auto-cpufreq (runit) unit file"
mkdir "$1"/sv/auto-cpufreq
cp /usr/local/share/auto-cpufreq/scripts/auto-cpufreq-runit "$1"/sv/auto-cpufreq/run
chmod +x "$1"/sv/auto-cpufreq/run
echo -e "\n* Enabling auto-cpufreq daemon (systemd) service at boot"
systemctl enable auto-cpufreq
echo -e "\n* Creating symbolic link ($2/service/auto-cpufreq -> $1/sv/auto-cpufreq)"
ln -s "$1"/sv/auto-cpufreq "$2"/service
auto_cpufreq_install "runit"
sv start auto-cpufreq
sv up auto-cpufreq
}
if [ -f /etc/os-release ];then
eval "$(cat /etc/os-release)"
case $ID in
void) runit_ln /etc /var;;
artix) runit_ln /etc/runit /run/runit;;
*)
echo -e "\n* Runit init detected but your distro is not supported\n"
echo -e "\n* Please open an issue on https://github.com/AdnanHodzic/auto-cpufreq\n"
esac
fi
;;
systemd)
echo -e "Deploying auto-cpufreq systemd unit file"
cp /usr/local/share/auto-cpufreq/scripts/auto-cpufreq.service /etc/systemd/system/auto-cpufreq.service
echo -e "\n* Reloading systemd manager configuration"
systemctl daemon-reload
auto_cpufreq_install "systemd" "systemctl start auto-cpufreq" "systemctl enable auto-cpufreq"
;;
s6-svscan)
echo -e "\n* Deploying auto-cpufreq (s6) unit file"
cp -r /usr/local/share/auto-cpufreq/scripts/auto-cpufreq-s6 /etc/s6/sv/auto-cpufreq
echo -e "\n* Add auto-cpufreq service (s6) to default bundle"
s6-service add default auto-cpufreq
auto_cpufreq_install "s6" "s6-rc -u change auto-cpufreq default"
echo -e "\n* Update daemon service bundle (s6)"
s6-db-reload
;;
*)
echo -e "\n* Unsupported init system detected, could not install the daemon\n"
echo -e "\n* Please open an issue on https://github.com/AdnanHodzic/auto-cpufreq\n"
;;
esac

View File

@ -0,0 +1,12 @@
#!/sbin/openrc-run
name=$RC_SVCNAME
description="auto-cpufreq - Automatic CPU speed & power optimizer for Linux"
supervisor="supervise-daemon"
command="/usr/local/bin/auto-cpufreq"
command_args="--daemon"
command_user="root"
depend() {
after net
}

View File

@ -2,26 +2,70 @@
#
# auto-cpufreq daemon removal script
# reference: https://github.com/AdnanHodzic/auto-cpufreq
# Thanks to https://github.com/errornonamer for openrc fix
echo -e "\n------------------ Running auto-cpufreq daemon removal script ------------------"
MID="$((`tput cols` / 2))"
if [[ $EUID != 0 ]];
then
echo -e "\nERROR\nMust be run as root (i.e: 'sudo $0')\n"
exit 1
echo
printf "%0.s─" $(seq $(( (MID-(${#1}/2)-2) / 2 )))
printf " Running auto-cpufreq daemon removal script "
printf "%0.s─" $(seq $(( (MID-(${#1}/2)-2) / 2 )))
echo; echo
# root check
if ((EUID != 0)); then
echo; echo "Must be run as root (i.e: 'sudo $0')."; echo
exit 1
fi
echo -e "\n* Stopping auto-cpufreq daemon (systemd) service"
systemctl stop auto-cpufreq
# First argument is the init name, second argument is the stop command, third argument is the disable command and the fourth is the "service" path
function auto_cpufreq_remove {
echo -e "\n* Stopping auto-cpufreq daemon ($1) service"
$2
echo -e "\n* Disabling auto-cpufreq daemon ($1) at boot"
$3
echo -e "\n* Removing auto-cpufreq daemon ($1) unit file"
rm $4
}
echo -e "\n* Disabling auto-cpufreq daemon (systemd) at boot"
systemctl disable auto-cpufreq
case "$(ps h -o comm 1)" in
dinit) auto_cpufreq_remove "dinit" "dinitctl stop auto-cpufreq" "dinitctl disable auto-cpufreq" "/etc/dinit.d/auto-cpufreq";;
init) auto_cpufreq_remove "openrc" "rc-service auto-cpufreq stop" "rc-update del auto-cpufreq" "/etc/init.d/auto-cpufreq";;
runit)
# First argument is the "sv" path, second argument is the "service" path
rm_sv() {
auto_cpufreq_remove "runit" "sv stop auto-cpufreq" "" "-rf $1/sv/auto-cpufreq $2/service/auto-cpufreq"
}
echo -e "\n* Removing auto-cpufreq daemon (systemd) unit file"
rm /etc/systemd/system/auto-cpufreq.service
if [ -f /etc/os-release ]; then
. /etc/os-release
case $ID in
void) rm_sv /etc /var;;
artix) rm_sv /etc/runit /run/runit;;
*)
echo -e "\n* Runit init detected but your distro is not supported\n"
echo -e "\n* Please open an issue on https://github.com/AdnanHodzic/auto-cpufreq\n"
;;
esac
fi
;;
systemd)
auto_cpufreq_remove "systemd" "systemctl stop auto-cpufreq" "systemctl disable auto-cpufreq" "/etc/systemd/system/auto-cpufreq.service"
echo -e "\n* Reloading systemd manager configuration"
systemctl daemon-reload
echo -e "\n* Reloading systemd manager configuration"
systemctl daemon-reload
echo -e "reset failed"
systemctl reset-failed
echo "reset failed"
systemctl reset-failed
;;
s6-svscan)
auto_cpufreq_remove "s6" "" "s6-service delete default auto-cpufreq" "-rf /etc/s6/sv/auto-cpufreq"
echo -e "\n* Update daemon service bundle (s6)"
s6-db-reload
;;
*)
echo -e "\n* Unsupported init system detected, could not remove the daemon"
echo -e "\n* Please open an issue on https://github.com/AdnanHodzic/auto-cpufreq\n"
;;
esac

3
scripts/auto-cpufreq-runit Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
export PATH="$PATH:/usr/local/bin"
exec /usr/local/bin/auto-cpufreq --daemon

View File

@ -0,0 +1,4 @@
#!/bin/sh
exec /usr/local/bin/auto-cpufreq --daemon

View File

@ -0,0 +1 @@
longrun

View File

@ -0,0 +1,17 @@
#!/bin/bash
# Wrapper script around auto-cpufreq using the python virtual environment
set -eu
# bailout function
err_exit() {
echo "$(basename $0): ${1:-wrong invocation. try --help for help.}" 1>&2
exit 1
}
# load python virtual environment
opt_path=/opt/auto-cpufreq
venv_bin_dir=$opt_path/venv/bin
. "$venv_bin_dir/activate"
PYTHONPATH=$opt_path $venv_bin_dir/python $venv_bin_dir/auto-cpufreq "$@"

View File

@ -1,12 +1,13 @@
[Unit]
Description=auto-cpufreq - Automatic CPU speed & power optimizer for Linux
After=network.target network-online.target
ConditionPathExists=/var/log/auto-cpufreq.log
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/auto-cpufreq --daemon
StandardOutput=append:/var/log/auto-cpufreq.log
WorkingDirectory=/opt/auto-cpufreq/venv
Environment=PYTHONPATH=/opt/auto-cpufreq
ExecStart=/opt/auto-cpufreq/venv/bin/python /opt/auto-cpufreq/venv/bin/auto-cpufreq --daemon
Restart=on-failure
[Install]
WantedBy=multi-user.target

View File

@ -1,109 +1,104 @@
#!/usr/bin/env bash
VERSION='20'
cpucount=`cat /proc/cpuinfo|grep processor|wc -l`
cpucount=`cat /proc/cpuinfo | grep processor | wc -l`
FLROOT=/sys/devices/system/cpu
FWROOT=/sys/firmware
DRIVER=auto
VERBOSE=0
## parse special options
for i in "$@"
do
case $i in
-v|--verbose)
VERBOSE=1
shift
;;
--set=*)
VALUE="${i#*=}"
shift
;;
-c=*|--core=*)
CORE="${i#*=}"
shift
;;
--available)
AVAILABLE=1
shift
;;
-*)
OPTION=$i
shift
;;
*) # unknown
;;
esac
for i in "$@"; do
case $i in
-v|--verbose)
VERBOSE=1
shift
;;
-s=*|--set=*)
VALUE="${i#*=}"
shift
;;
-c=*|--core=*)
CORE="${i#*=}"
shift
;;
-a|--available)
AVAILABLE=1
shift
;;
-*)
OPTION=$i
shift
;;
*) exit 1;;
esac
done
function help () {
echo "Package version: "$VERSION
echo "Usage:"
echo " cpufreqctl [OPTION[=VALUE]...]"
echo ""
echo " --help Show help options"
echo " --version Package version"
echo " --verbose, -v Verbose output"
echo ""
echo " --set=VALUE Set VALUE for selected option"
echo " --core=NUMBER Apply selected option just for the core NUMBER (0 ~ N - 1)"
echo " --available Get available values instand of default: current"
echo ""
echo " --driver Current processor driver"
echo " --governor Scaling governor's options"
echo " --epp Governor's energy_performance_preference options"
echo " --frequency Frequency options"
echo " --on Turn on --core=NUMBER"
echo " --off Turn off --core=NUMBER"
echo " --frequency-min Minimal frequency options"
echo " --frequency-max Maximum frequency options"
echo " --boost Current cpu boost value"
echo ""
echo "Usage: cpufreqctl [OPTION[=VALUE]...]"
echo
echo " -h, --help Show help options"
echo " --version Package version"
echo " -v, --verbose Verbose output"
echo
echo " -s, --set =VALUE Set VALUE for selected option"
echo " -c, --core =NUMBER Apply selected option just for the core NUMBER (0 ~ N - 1)"
echo " -a, --available Get available values instand of default: current"
echo
echo " -d, --driver Current processor driver"
echo " -g, --governor Scaling governor's options"
echo " -e, --epp Governor's energy_performance_preference options"
echo " -f, --frequency Frequency options"
echo " --on Turn on --core=NUMBER"
echo " --off Turn off --core=NUMBER"
echo " --frequency-min Minimal frequency options"
echo " --frequency-max Maximum frequency options"
echo " --frequency-min-limit Get minimal frequency limit"
echo " --frequency-max-limit Get maximum frequency limit"
echo " -b, --boost Current cpu boost value"
echo
echo "intel_pstate options"
echo " --no-turbo Current no_turbo value"
echo " --min-perf Current min_perf_pct options"
echo " --max-perf Current max_perf_pct options"
echo ""
echo "Package options"
echo " --install Install extra components for all users"
echo " --uninstall Uninstall extra components for all users"
echo " --update-fonts Update font cache"
echo " --reset Reset to defaults for current user"
echo ""
echo " --no-turbo Current no_turbo value"
echo " --min-perf Current min_perf_pct options"
echo " --max-perf Current max_perf_pct options"
echo
echo "Events options"
echo " --throttle Get thermal throttle counter"
echo " --throttle-event Get kernel thermal throttle events counter"
echo " --irqbalance Get irqbalance presence"
echo " --throttle Get thermal throttle counter"
echo " --throttle-event Get kernel thermal throttle events counter"
echo " --irqbalance Get irqbalance presence"
}
function info () {
echo "CPU driver: "`driver`
echo "Governors: "`cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors`
echo "Frequencies: "`cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies`
echo ""
echo "Governors: "`cat $FLROOT/cpu0/cpufreq/scaling_available_governors`
echo "Frequencies: "`cat $FLROOT/cpu0/cpufreq/scaling_available_frequencies`
echo
echo "Usage:"
echo "## list scaling governors:"
echo "cpufreqctl --governor"
echo ""
echo
echo "## Set all active cpu cores to the 'performance' scaling governor:"
echo "cpufreqctl --governor --set=performance"
echo ""
echo
echo "## Set 'performance' scaling governor for the selected core:"
echo "cpufreqctl --governor --set=performance --core=0"
echo ""
echo
echo "Use --help argument to see available options"
}
verbose () {
if [ $VERBOSE = 1 ]
then
echo $1
fi
if [ $VERBOSE = 1 ]; then echo $1; fi
}
function driver () {
cat $FLROOT/cpu0/cpufreq/scaling_driver
}
function write_value () {
if [ -w $FLNM ]; then echo $VALUE > $FLNM; fi
}
function set_driver () {
DRIVER=`driver`
case $DRIVER in
@ -117,385 +112,339 @@ function get_governor () {
then
i=0
ag=''
while [ $i -ne $cpucount ]
do
if [ $i = 0 ]
then
ag=`cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor`
else
ag=$ag' '`cat /sys/devices/system/cpu/cpu$i/cpufreq/scaling_governor`
while [ $i -ne $cpucount ]; do
if [ $i = 0 ]; then ag=`cat $FLROOT/cpu0/cpufreq/scaling_governor`
else ag=$ag' '`cat $FLROOT/cpu$i/cpufreq/scaling_governor`
fi
i=`expr $i + 1`
done
echo $ag
else
cat /sys/devices/system/cpu/cpu$CORE/cpufreq/scaling_governor
else cat $FLROOT/cpu$CORE/cpufreq/scaling_governor
fi
}
function set_governor () {
if [ -z $CORE ]
then
if [ -z $CORE ]; then
i=0
while [ $i -ne $cpucount ]
do
while [ $i -ne $cpucount ]; do
FLNM="$FLROOT/cpu"$i"/cpufreq/scaling_governor"
echo $VALUE > $FLNM
write_value
i=`expr $i + 1`
done
else
echo $VALUE > /sys/devices/system/cpu/cpu$CORE/cpufreq/scaling_governor
else echo $VALUE > $FLROOT/cpu$CORE/cpufreq/scaling_governor
fi
}
function get_frequency () {
if [ -z $CORE ]
then
if [ -z $CORE ]; then
i=0
V=0
M=$(cat "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq")
while [ $i -ne $cpucount ]
do
V=$(cat "/sys/devices/system/cpu/cpu"$i"/cpufreq/scaling_cur_freq")
if [[ $V > $M ]]
then
M=$V
fi
M=$(cat "$FLROOT/cpu0/cpufreq/scaling_cur_freq")
while [ $i -ne $cpucount ]; do
V=$(cat "$FLROOT/cpu"$i"/cpufreq/scaling_cur_freq")
if [[ $V > $M ]]; then M=$V; fi
i=`expr $i + 1`
done
echo "$M"
else
cat /sys/devices/system/cpu/cpu$CORE/cpufreq/scaling_cur_freq
else cat $FLROOT/cpu$CORE/cpufreq/scaling_cur_freq
fi
}
function set_frequency () {
set_driver
if [ $DRIVER = 'pstate']
then
echo "Unavaible function for intel_pstate"
if [ $DRIVER = 'pstate' ]; then
echo "Unavailable function for intel_pstate"
return
fi
if [ -z $CORE ]
then
if [ -z $CORE ]; then
i=0
while [ $i -ne $cpucount ]
do
while [ $i -ne $cpucount ]; do
FLNM="$FLROOT/cpu"$i"/cpufreq/scaling_setspeed"
echo $VALUE > $FLNM
write_value
i=`expr $i + 1`
done
else
echo $VALUE > /sys/devices/system/cpu/cpu$CORE/cpufreq/scaling_setspeed
else echo $VALUE > $FLROOT/cpu$CORE/cpufreq/scaling_setspeed
fi
}
function get_frequency_min () {
if [ -z $CORE ]
then
CORE=0
fi
cat /sys/devices/system/cpu/cpu$CORE/cpufreq/scaling_min_freq
if [ -z $CORE ]; then CORE=0; fi
cat $FLROOT/cpu$CORE/cpufreq/scaling_min_freq
}
function set_frequency_min () {
if [ -z $CORE ]
then
if [ -z $CORE ]; then
i=0
while [ $i -ne $cpucount ]
do
while [ $i -ne $cpucount ]; do
FLNM="$FLROOT/cpu"$i"/cpufreq/scaling_min_freq"
echo $VALUE > $FLNM
write_value
i=`expr $i + 1`
done
else
echo $VALUE > /sys/devices/system/cpu/cpu$CORE/cpufreq/scaling_min_freq
else echo $VALUE > $FLROOT/cpu$CORE/cpufreq/scaling_min_freq
fi
}
function get_frequency_max () {
if [ -z $CORE ]
then
CORE=0
fi
cat /sys/devices/system/cpu/cpu$CORE/cpufreq/scaling_max_freq
if [ -z $CORE ]; then CORE=0; fi
cat $FLROOT/cpu$CORE/cpufreq/scaling_max_freq
}
function set_frequency_max () {
if [ -z $CORE ]
then
if [ -z $CORE ]; then
i=0
while [ $i -ne $cpucount ]
do
while [ $i -ne $cpucount ]; do
FLNM="$FLROOT/cpu"$i"/cpufreq/scaling_max_freq"
echo $VALUE > $FLNM
write_value
i=`expr $i + 1`
done
else
echo $VALUE > /sys/devices/system/cpu/cpu$CORE/cpufreq/scaling_max_freq
else echo $VALUE > $FLROOT/cpu$CORE/cpufreq/scaling_max_freq
fi
}
function get_frequency_min_limit () {
if [ -z $CORE ]; then CORE=0; fi
cat $FLROOT/cpu$CORE/cpufreq/cpuinfo_min_freq
}
function get_frequency_max_limit () {
if [ -z $CORE ]; then CORE=0; fi
cat $FLROOT/cpu$CORE/cpufreq/scaling_max_freq
}
function get_energy_performance_preference () {
if [ -z $CORE ]
then
if [ -z $CORE ]; then
i=0
ag=''
while [ $i -ne $cpucount ]
do
if [ $i = 0 ]
then
ag=`cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference`
while [ $i -ne $cpucount ]; do
if [ $i = 0 ]; then
ag=`cat $FLROOT/cpu0/cpufreq/energy_performance_preference`
else
ag=$ag' '`cat /sys/devices/system/cpu/cpu$i/cpufreq/energy_performance_preference`
ag=$ag' '`cat $FLROOT/cpu$i/cpufreq/energy_performance_preference`
fi
i=`expr $i + 1`
done
echo $ag
else
cat /sys/devices/system/cpu/cpu$CORE/cpufreq/energy_performance_preference
else cat $FLROOT/cpu$CORE/cpufreq/energy_performance_preference
fi
}
function set_energy_performance_preference () {
if [ -z $CORE ]
then
if [ -z $CORE ]; then
i=0
while [ $i -ne $cpucount ]
do
while [ $i -ne $cpucount ]; do
FLNM="$FLROOT/cpu"$i"/cpufreq/energy_performance_preference"
echo $VALUE > $FLNM
write_value
i=`expr $i + 1`
done
else
echo $VALUE > /sys/devices/system/cpu/cpu$CORE/cpufreq/energy_performance_preference
else echo $VALUE > $FLROOT/cpu$CORE/cpufreq/energy_performance_preference
fi
}
if [ -z $OPTION ] # No options
then
info
exit
fi
if [ $OPTION = "--help" ]
then
help
exit
fi
if [ $OPTION = "--version" ]
then
echo $VERSION
exit
fi
if [ $OPTION = "--driver" ]
then
driver
exit
fi
if [ $OPTION = "--governor" ]
then
if [ ! -z $AVAILABLE ]
then
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
exit
fi
if [ -z $VALUE ]
then
verbose "Getting CPU"$CORE" governors"
get_governor
else
verbose "Setting CPU"$CORE" governors to "$VALUE
set_governor
fi
exit
fi
if [ $OPTION = "--epp" ]
then
if [ ! -z $AVAILABLE ]
then
cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences
exit
fi
if [ -z $VALUE ]
then
verbose "Getting CPU"$CORE" EPPs"
get_energy_performance_preference
else
verbose "Setting CPU"$CORE" EPPs to "$VALUE
set_energy_performance_preference
fi
exit
fi
if [ $OPTION = "--frequency" ]
then
if [ ! -z $AVAILABLE ]
then
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies
exit
fi
if [ -z $VALUE ]
then
verbose "Getting CPU"$CORE" frequency"
get_frequency
else
verbose "Setting CPU"$CORE" frequency to "$VALUE
set_frequency
fi
exit
fi
if [ $OPTION = "--no-turbo" ]
then
if [ -z $VALUE ]
then
verbose "Getting no_turbo value"
cat /sys/devices/system/cpu/intel_pstate/no_turbo
else
verbose "Setting no_turbo value "$VALUE
echo $VALUE > /sys/devices/system/cpu/intel_pstate/no_turbo
fi
exit
fi
if [ $OPTION = "--boost" ]
then
if [ -z $VALUE ]
then
verbose "Getting boost value"
cat /sys/devices/system/cpu/cpufreq/boost
else
verbose "Setting boost value "$VALUE
echo $VALUE > /sys/devices/system/cpu/cpufreq/boost
fi
exit
fi
if [ $OPTION = "--frequency-min" ]
then
if [ -z $VALUE ]
then
verbose "Getting CPU"$CORE" minimal frequency"
get_frequency_min
else
verbose "Setting CPU"$CORE" minimal frequency to "$VALUE
set_frequency_min
fi
exit
fi
if [ $OPTION = "--frequency-max" ]
then
if [ -z $VALUE ]
then
verbose "Getting CPU"$CORE" maximal frequency"
get_frequency_max
else
verbose "Setting CPU"$CORE" maximal frequency to "$VALUE
set_frequency_max
fi
exit
fi
if [ $OPTION = "--min-perf" ]
then
if [ -z $VALUE ]
then
verbose "Getting min_perf_pct value"
cat /sys/devices/system/cpu/intel_pstate/min_perf_pct
else
verbose "Setting min_perf_pct value "$VALUE
echo $VALUE > /sys/devices/system/cpu/intel_pstate/min_perf_pct
fi
exit
fi
if [ $OPTION = "--max-perf" ]
then
if [ -z $VALUE ]
then
verbose "Getting max_perf_pct value"
cat /sys/devices/system/cpu/intel_pstate/max_perf_pct
else
verbose "Setting max_perf_pct value "$VALUE
echo $VALUE > /sys/devices/system/cpu/intel_pstate/max_perf_pct
fi
exit
fi
if [ $OPTION = "--on" ]
then
if [ -z $CORE ]
then
verbose "Should be specify --core=NUMBER"
else
verbose "Power on CPU Core"$CORE
echo "1" > $FLROOT/cpu"$CORE"/online
fi
exit
fi
if [ $OPTION = "--off" ]
then
if [ -z $CORE ]
then
verbose "Should be specify --core=NUMBER"
else
verbose "Power off CPU Core"$CORE
echo "0" > $FLROOT/cpu"$CORE"/online
fi
exit
fi
if [ $OPTION = "--throttle" ]
then
i=1
V=0
M=$(cat "/sys/devices/system/cpu/cpu0/thermal_throttle/core_throttle_count")
while [ $i -ne $cpucount ]
do
V=$(cat "/sys/devices/system/cpu/cpu"$i"/thermal_throttle/core_throttle_count")
M=`expr $M + $V`
i=`expr $i + 1`
done
echo "$M"
exit
fi
if [ $OPTION = "--throttle-events" ]
then
M=$(journalctl --dmesg --boot --since=yesterday | grep "cpu clock throttled" | wc -l)
echo "$M"
exit
fi
if [ $OPTION = "--irqbalance" ]
then
M=$(ps -A |grep irqbalance)
echo "$M"
exit
fi
function get_energy_performance_bias () {
if [ -z $CORE ]; then
i=0
ag=''
while [ $i -ne $cpucount ]; do
if [ $i = 0 ]; then
ag=`cat $FLROOT/cpu0/power/energy_perf_bias`
else
ag=$ag' '`cat $FLROOT/cpu$i/power/energy_perf_bias`
fi
i=`expr $i + 1`
done
echo $ag
else cat $FLROOT/cpu$CORE/power/energy_perf_bias
fi
}
if [ $OPTION = "--install" ]
then
echo 'installing helpers...'
cp $0 /usr/bin/
echo 'installing policy...'
cp $(dirname "$(readlink -f "$0")")/konkor.cpufreq.policy /usr/share/polkit-1/actions/
echo 'installing fonts...'
mkdir -p /usr/share/fonts/truetype/cpufreq
cp $(dirname "$(readlink -f "$0")")/fonts/cpufreq.ttf /usr/share/fonts/truetype/cpufreq/
echo "done"
exit
fi
if [ $OPTION = "--update-fonts" ]
then
fc-cache -f
exit
fi
if [ $OPTION = "--uninstall" ]
then
echo 'uninstalling cpufreqctl helper...'
rm /usr/bin/cpufreqctl
echo 'uninstalling policy...'
rm /usr/share/polkit-1/actions/konkor.cpufreq.policy
echo 'uninstalling fonts...'
rm -rf /usr/share/fonts/truetype/cpufreq
echo "done"
exit
fi
if [ $OPTION = "--reset" ]
then
echo 'reset to default values...'
dconf reset -f "/org/gnome/shell/extensions/cpufreq/"
exit
fi
function set_energy_performance_bias () {
if [ `driver` != 'intel_pstate' ]; then
verbose "EPB is not supported by a driver other than intel_pstate"
return
fi
local EPB_VALUE=6 # default value
if [[ "$VALUE" =~ ^[0-9]+$ && $VALUE -ge 0 && $VALUE -le 15 ]]; then
EPB_VALUE=$VALUE
else
case $VALUE in
performance) EPB_VALUE=0;;
balance_performance) EPB_VALUE=4;;
default) EPB_VALUE=6;;
balance_power) EPB_VALUE=8;;
power) EPB_VALUE=15;;
*)
verbose "Invalid value provided for EPB"
verbose "Acceptable values: performance|balance-power|default|balance-power|power or a number in the range [0-15]"
return
;;
esac
fi
if [ -z $CORE ]; then
i=0
while [ $i -ne $cpucount ]; do
FLNM="$FLROOT/cpu"$i"/power/energy_perf_bias"
if [ -w $FLNM ]; then echo $EPB_VALUE > $FLNM; fi
i=`expr $i + 1`
done
else echo $EPB_VALUE > $FLROOT/cpu$CORE/power/energy_perf_bias
fi
}
case $OPTION in
-h|--help) help;;
--version) echo $VERSION;;
-d|--driver) driver;;
-g|--governor)
if [ ! -z $AVAILABLE ]; then cat $FLROOT/cpu0/cpufreq/scaling_available_governors
elif [ -z $VALUE ]; then
verbose "Getting CPU"$CORE" governors"
get_governor
else
verbose "Setting CPU"$CORE" governors to "$VALUE
set_governor
fi
;;
-e|--epp)
if [ ! -z $AVAILABLE ]; then cat $FLROOT/cpu0/cpufreq/energy_performance_available_preferences
elif [ -z $VALUE ]; then
verbose "Getting CPU"$CORE" EPPs"
get_energy_performance_preference
else
verbose "Setting CPU"$CORE" EPPs to "$VALUE
set_energy_performance_preference
fi
;;
--epb)
if [ ! -z $AVAILABLE ]; then cat $FLROOT/cpu0/power/energy_perf_bias
elif [ -z $VALUE ]; then
verbose "Getting CPU"$CORE" EPBs"
get_energy_performance_bias
else
verbose "Setting CPU"$CORE" EPBs to "$VALUE
set_energy_performance_bias
fi
;;
-p|--pp)
if [ ! -z $AVAILABLE ]; then cat $FWROOT/acpi/platform_profile_choices
elif [ -z $VALUE ]; then
verbose "Getting Platform Profile"
cat $FWROOT/acpi/platform_profile
else
verbose "Getting Platform Profile to "$VALUE
echo $VALUE > $FWROOT/acpi/platform_profile
fi
;;
-f|--frequency)
if [ ! -z $AVAILABLE ]; then cat $FLROOT/cpu0/cpufreq/scaling_available_frequencies
elif [ -z $VALUE ]; then
verbose "Getting CPU"$CORE" frequency"
get_frequency
else
verbose "Setting CPU"$CORE" frequency to "$VALUE
set_frequency
fi
;;
--no-turbo)
if [ -z $VALUE ]; then
verbose "Getting no_turbo value"
cat $FLROOT/intel_pstate/no_turbo
else
verbose "Setting no_turbo value "$VALUE
echo $VALUE > $FLROOT/intel_pstate/no_turbo
fi
;;
-b|--boost)
if [ -z $VALUE ]; then
verbose "Getting boost value"
cat $FLROOT/cpufreq/boost
else
verbose "Setting boost value "$VALUE
echo $VALUE > $FLROOT/cpufreq/boost
fi
;;
--frequency-min)
if [ -z $VALUE ]; then
verbose "Getting CPU"$CORE" minimal frequency"
get_frequency_min
else
verbose "Setting CPU"$CORE" minimal frequency to "$VALUE
set_frequency_min
fi
;;
--frequency-max)
if [ -z $VALUE ]; then
verbose "Getting CPU"$CORE" maximal frequency"
get_frequency_max
else
verbose "Setting CPU"$CORE" maximal frequency to "$VALUE
set_frequency_max
fi
;;
--frequency-min-limit)
verbose "Getting CPU"$CORE" minimal frequency limit"
get_frequency_min_limit
;;
--frequency-max-limit)
verbose "Getting CPU"$CORE" maximum frequency limit"
get_frequency_max_limit
;;
--min-perf)
if [ -z $VALUE ]; then
verbose "Getting min_perf_pct value"
cat $FLROOT/intel_pstate/min_perf_pct
else
verbose "Setting min_perf_pct value "$VALUE
echo $VALUE > $FLROOT/intel_pstate/min_perf_pct
fi
;;
--max-perf)
if [ -z $VALUE ]; then
verbose "Getting max_perf_pct value"
cat $FLROOT/intel_pstate/max_perf_pct
else
verbose "Setting max_perf_pct value "$VALUE
echo $VALUE > $FLROOT/intel_pstate/max_perf_pct
fi
;;
--on)
if [ -z $CORE ]; then verbose "Should be specify --core=NUMBER"
else
verbose "Power on CPU Core"$CORE
echo "1" > $FLROOT/cpu"$CORE"/online
fi
;;
--off)
if [ -z $CORE ]; then verbose "Should be specify --core=NUMBER"
else
verbose "Power off CPU Core$CORE"
echo "0" > $FLROOT/cpu"$CORE"/online
fi
;;
--throttle)
i=1
V=0
M=$(cat "$FLROOT/cpu0/thermal_throttle/core_throttle_count")
while [ $i -ne $cpucount ]; do
V=$(cat "$FLROOT/cpu$i/thermal_throttle/core_throttle_count")
M=`expr $M + $V`
i=`expr $i + 1`
done
echo "$M"
;;
--throttle-events)
M=$(journalctl --dmesg --boot --since=yesterday | grep "cpu clock throttled" | wc -l)
echo "$M"
;;
--irqbalance)
M=$(ps -A | grep irqbalance)
echo "$M"
;;
*)
info
exit 1
;;
esac
exit 0

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<policyconfig>
<action id="org.auto-cpufreq.pkexec">
<description>Run auto-cpufreq command</description>
<message>Authentication is required to run auto-cpufreq</message>
<icon_name>auto-cpufreq</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/opt/auto-cpufreq/venv/bin/auto-cpufreq</annotate>
<!-- <annotate key="org.freedesktop.policykit.exec.argv1">/opt/auto-cpufreq/venv/bin/auto-cpufreq</annotate> -->
<!-- <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate> -->
</action>
</policyconfig>

4
scripts/snapdaemon.sh Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
#
# workaround for running Daemon without polluting syslog (#53, #82)
$SNAP/bin/auto-cpufreq --daemon 2>&1 >> $SNAP_DATA/auto-cpufreq.stats

18
scripts/start_app Normal file
View File

@ -0,0 +1,18 @@
#!/usr/bin/sh
# load python virtual environment
venv_dir=/opt/auto-cpufreq/venv
. "$venv_dir/bin/activate"
python_command="$venv_dir/bin/auto-cpufreq-gtk"
# if [ "$XDG_SESSION_TYPE" = "wayland" ] ; then
# # necessary for running on wayland
# xhost +SI:localuser:root
# pkexec $python_command
# xhost -SI:localuser:root
# xhost
# else
# pkexec $python_command
# fi
$python_command

8
scripts/style.css Normal file
View File

@ -0,0 +1,8 @@
label {
/*font-family: Noto Sans;*/
font-size: 15px;
}
#bold { font-weight: bold; }
#small { font-size: 12px; }

View File

@ -1,35 +0,0 @@
import os
from setuptools import setup
with open('README.md') as readme_file:
readme = readme_file.read()
this = os.path.dirname(os.path.realpath(__file__))
def read(name):
with open(os.path.join(this, name)) as f:
return f.read()
setup(
name='auto-cpufreq',
version='1.0',
description='Automatic CPU speed & power optimizer for Linux',
long_description=readme,
author='Adnan Hodzic',
author_email='adnan@hodzic.org',
url='https://github.com/AdnanHodzic/auto-cpufreq',
packages=['source'],
install_requires=read('requirements.txt'),
include_package_data=True,
zip_safe=True,
license='GPLv3',
keywords='linux cpu speed power frequency turbo optimzier auto cpufreq',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Operating System :: POSIX :: Linux'
'Environment :: Console'
'Natural Language :: English'
],
scripts=['bin/auto-cpufreq']
)

BIN
snap/gui/auto-cpufreq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Encoding=UTF-8
Name=auto-cpufreq
Comment=Automatic CPU speed & power optimizer for Linux
Exec=auto-cpufreq.auto-cpufreq-gtk
StartupWMClass=app.py
Terminal=false
Icon=${SNAP}/meta/gui/auto-cpufreq.png
Categories=System;

View File

@ -1,6 +1,5 @@
name: auto-cpufreq
base: core18
version: '1.2.1'
base: core22
summary: Automatic CPU speed & power optimizer for Linux
description: |
Automatic CPU speed & power optimizer for Linux based on active
@ -11,40 +10,84 @@ description: |
license: LGPL-3.0
grade: stable
confinement: strict
adopt-info: auto-cpufreq
compression: lzo
architectures:
- build-on: [amd64]
build-for: [amd64]
- build-on: [amd64]
build-for: [arm64]
parts:
auto-cpufreq:
plugin: python
python-version: python3
python-packages:
- setuptools
- wheel
- requests
build-packages:
- gcc
- python3-dev
- python3-wheel
stage-packages:
- inxi
- lm-sensors
- coreutils
- dmidecode
- pkexec
source: .
override-pull: |
snapcraftctl pull
snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/pyproject.toml | sed 's/.*"\(.*\)"/\1/'`
deploy-cpufrectl:
deploy-scripts:
plugin: dump
source: scripts
organize:
cpufreqctl.sh: usr/bin/cpufreqctl
cpufreqctl.sh: usr/bin/cpufreqctl.auto-cpufreq
snapdaemon.sh: usr/bin/snapdaemon
copy-image:
plugin: dump
source: images
plugs:
etc-auto-cpufreq-conf:
interface: system-files
write:
- /etc/auto-cpufreq.conf
apps:
auto-cpufreq:
command: bin/auto-cpufreq
environment:
PYTHONPATH: $SNAP/usr/lib/python3/site-packages:$SNAP/usr/lib/python3/dist-packages:$PYTHONPATH
LC_ALL: C.UTF-8
LANG: C.UTF-8
PKG_MARKER: SNAP
plugs:
- cpu-control
- system-observe
- hardware-observe
- etc-auto-cpufreq-conf
auto-cpufreq-gtk:
command: bin/auto-cpufreq-gtk
extensions: [gnome]
environment:
PYTHONPATH: $SNAP/usr/lib/python3/site-packages:$SNAP/usr/lib/python3/dist-packages:$PYTHONPATH
LC_ALL: C.UTF-8
LANG: C.UTF-8
PKG_MARKER: SNAP
plugs:
- cpu-control
- system-observe
- hardware-observe
- desktop
- desktop-legacy
- wayland
- x11
service:
command: bin/auto-cpufreq --daemon 2>&1 | tee -a $SNAP_DATA/auto-cpufreq.log
command: usr/bin/snapdaemon
plugs:
- cpu-control
- system-observe

View File

View File

@ -1,562 +0,0 @@
#!/usr/bin/env python3
#
# auto-cpufreq - core functionality
import os
import platform as pl
import re
import shutil
import sys
import time
import warnings
from pathlib import Path
from pprint import pformat
from subprocess import getoutput, call, run
import psutil
warnings.filterwarnings("ignore")
# ToDo:
# - re-enable CPU fan speed display and make more generic and not only for thinkpad
# - replace get system/CPU load from: psutil.getloadavg() | available in 5.6.2)
SCRIPTS_DIR = Path("/usr/local/share/auto-cpufreq/scripts/")
# from the highest performance to the lowest
ALL_GOVERNORS = ("performance", "ondemand", "conservative", "schedutil", "userspace", "powersave")
CPUS = os.cpu_count()
def turbo(value: bool = None):
"""
Get and set turbo mode
"""
p_state = Path("/sys/devices/system/cpu/intel_pstate/no_turbo")
cpufreq = Path("/sys/devices/system/cpu/cpufreq/boost")
if p_state.exists():
inverse = True
f = p_state
elif cpufreq.exists():
f = cpufreq
inverse = False
else:
print("Warning: CPU turbo is not available")
return False
if value is not None:
if inverse:
value = not value
try:
f.write_text(str(int(value)) + "\n")
except PermissionError:
print("Warning: Changing CPU turbo is not supported. Skipping.")
return False
value = bool(int(f.read_text().strip()))
if inverse:
value = not value
return value
def get_sys_info():
"""
Return sys info of inxi command with injected governors information
"""
govs = " ".join(get_avail_gov())
sensors = {"temperatures:": psutil.sensors_temperatures(),
"battery": psutil.sensors_battery(),
"fans": psutil.sensors_fans()}
sensors = pformat(sensors)
if shutil.which("inxi") is not None:
sys_info = getoutput("inxi -Fzc0")
f = re.MULTILINE | re.DOTALL
# remove errors at the beginning that could occur in the snap container
sys_info = re.fullmatch(r"(.*)(System:.*)", sys_info, flags=f).group(2)
# insert governors after "CPU:"
p = re.compile(pattern=r"(.*)(CPU:)(\s+)(.+)", flags=f)
indent = " " * len(p.search(sys_info).group(3))
sys_info = p.sub(fr"\1\2{indent}Governors: {govs} \4", sys_info)
# insert psutil sensors after Sensors:
p = re.compile(pattern=r"(.*)(Sensors:)(\s+)(.+)", flags=f)
indent = " " * len(p.search(sys_info).group(3))
sys_info = p.sub(fr"\1\2{indent}\n{sensors} \4", sys_info)
else:
sys_info = ("Warning: inxi is not installed.\n"
f"Governors: {govs}\n"
f"Sensors: {sensors}\n")
return sys_info
def charging():
"""
get charge state: is battery charging or discharging
"""
bat_info = psutil.sensors_battery()
if bat_info is None:
state = True
else:
state = bat_info.power_plugged
return state
def get_avail_gov():
f = Path("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors")
return f.read_text().strip().split(" ")
def get_avail_powersave():
"""
Iterate over ALL_GOVERNORS in reverse order: from powersave to performance
:return:
"""
for g in ALL_GOVERNORS[::-1]:
if g in get_avail_gov():
return g
def get_avail_performance():
for g in ALL_GOVERNORS:
if g in get_avail_gov():
return g
def get_current_gov():
return getoutput("cpufreqctl --governor").strip().split(" ")[0]
# auto-cpufreq log file
auto_cpufreq_log_file = Path("/var/log/auto-cpufreq.log")
auto_cpufreq_log_file_snap = Path("/var/snap/auto-cpufreq/current/auto-cpufreq.log")
# daemon check
dcheck = getoutput("snapctl get daemon")
def cpufreqctl():
"""
deploy cpufreqctl script
"""
# detect if running on a SNAP
if os.getenv('PKG_MARKER') == "SNAP":
pass
else:
# deploy cpufreqctl script (if missing)
if os.path.isfile("/usr/bin/cpufreqctl"):
shutil.copy("/usr/bin/cpufreqctl", "/usr/bin/cpufreqctl.auto-cpufreq.bak")
shutil.copy(SCRIPTS_DIR / "cpufreqctl.sh", "/usr/bin/cpufreqctl")
else:
shutil.copy(SCRIPTS_DIR / "cpufreqctl.sh", "/usr/bin/cpufreqctl")
def cpufreqctl_restore():
"""
restore original cpufreqctl script
"""
# detect if running on a SNAP
if os.getenv('PKG_MARKER') == "SNAP":
pass
else:
# restore original cpufreqctl script
if os.path.isfile("/usr/bin/cpufreqctl.auto-cpufreq.bak"):
os.system("cp /usr/bin/cpufreqctl.auto-cpufreq.bak /usr/bin/cpufreqctl")
os.remove("/usr/bin/cpufreqctl.auto-cpufreq.bak")
# ToDo: implement mechanism to make sure cpufreqctl (auto-cpufreq) file is
# restored if overwritten by system. But during tool removal to also remove it
# in def cpufreqctl
def footer(l=79):
print("\n" + "-" * l + "\n")
def deploy_complete_msg():
print("\n" + "-" * 17 + " auto-cpufreq daemon installed and running " + "-" * 17 + "\n")
print("To view live log, run:\nauto-cpufreq --log")
print("\nTo disable and remove auto-cpufreq daemon, run:\nsudo auto-cpufreq --remove")
footer()
def remove_complete_msg():
print("\n" + "-" * 25 + " auto-cpufreq daemon removed " + "-" * 25 + "\n")
print("auto-cpufreq successfully removed.")
footer()
def deploy_daemon():
print("\n" + "-" * 21 + " Deploying auto-cpufreq as a daemon " + "-" * 22 + "\n")
# deploy cpufreqctl script func call
cpufreqctl()
print("* Turn off bluetooth on boot")
btconf = Path("/etc/bluetooth/main.conf")
try:
orig_set = "AutoEnable=true"
change_set = "AutoEnable=false"
with btconf.open(mode="r+") as f:
content = f.read()
f.seek(0)
f.truncate()
f.write(content.replace(orig_set, change_set))
except:
print("\nERROR:\nWas unable to turn off bluetooth on boot")
auto_cpufreq_log_file.touch(exist_ok=True)
print("\n* Deploy auto-cpufreq install script")
shutil.copy(SCRIPTS_DIR / "auto-cpufreq-install.sh", "/usr/bin/auto-cpufreq-install")
print("\n* Deploy auto-cpufreq remove script")
shutil.copy(SCRIPTS_DIR / "auto-cpufreq-remove.sh", "/usr/bin/auto-cpufreq-remove")
call("/usr/bin/auto-cpufreq-install", shell=True)
# remove auto-cpufreq daemon
def remove():
print("\n" + "-" * 21 + " Removing auto-cpufreq daemon " + "-" * 22 + "\n")
print("* Turn on bluetooth on boot")
btconf = "/etc/bluetooth/main.conf"
try:
orig_set = "AutoEnable=true"
change_set = "AutoEnable=false"
with open(btconf, "r+") as f:
content = f.read()
f.seek(0)
f.truncate()
f.write(content.replace(change_set, orig_set))
except:
print("\nERROR:\nWas unable to turn on bluetooth on boot")
# run auto-cpufreq daemon install script
call("/usr/bin/auto-cpufreq-remove", shell=True)
# remove auto-cpufreq-remove
os.remove("/usr/bin/auto-cpufreq-remove")
# delete log file
if auto_cpufreq_log_file.exists():
auto_cpufreq_log_file.unlink()
# restore original cpufrectl script
cpufreqctl_restore()
def gov_check():
for gov in get_avail_gov():
if gov not in ALL_GOVERNORS:
print("\n" + "-" * 18 + " Checking for necessary scaling governors " + "-" * 19 + "\n")
sys.exit("ERROR:\n\nCouldn't find any of the necessary scaling governors.\n")
# root check func
def root_check():
if not os.geteuid() == 0:
print("\n" + "-" * 33 + " Root check " + "-" * 34 + "\n")
print("ERROR:\n\nMust be run root for this functionality to work, i.e: \nsudo auto-cpufreq")
footer()
exit(1)
# refresh countdown
def countdown(s):
# Fix for wrong log output and "TERM environment variable not set"
os.environ['TERM'] = 'xterm'
for remaining in range(s, 0, -1):
sys.stdout.write("\r")
sys.stdout.write("\t\t\t\"auto-cpufreq\" refresh in:{:2d}".format(remaining))
sys.stdout.flush()
time.sleep(1)
# set powersave and enable turbo
def set_powersave():
print(f"Setting to use: \"{get_avail_powersave()}\" governor")
run(f"cpufreqctl --governor --set={get_avail_powersave()}", shell=True)
if Path("/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference").exists():
run("cpufreqctl --epp --set=balance_power", shell=True)
print("Setting to use: \"balance_power\" EPP")
# get system/CPU load
load1m, _, _ = os.getloadavg()
# get CPU utilization as a percentage
cpuload = psutil.cpu_percent(interval=1)
print("\nTotal CPU usage:", cpuload, "%")
print("Total system load:", load1m, "\n")
# conditions for setting turbo in powersave
if load1m > CPUS / 7:
print("High load, setting turbo boost: on")
turbo(True)
elif cpuload > 25:
print("High CPU load, setting turbo boost: on")
turbo(True)
else:
print("Load optimal, setting turbo boost: off")
turbo(False)
footer()
# make turbo suggestions in powersave
def mon_powersave():
# get system/CPU load
load1m, _, _ = os.getloadavg()
# get CPU utilization as a percentage
cpuload = psutil.cpu_percent(interval=1)
print("\nTotal CPU usage:", cpuload, "%")
print("Total system load:", load1m, "\n")
if load1m > CPUS / 7:
print("High load, suggesting to set turbo boost: on")
if turbo():
print("Currently turbo boost is: on")
else:
print("Currently turbo boost is: off")
footer()
elif cpuload > 25:
print("High CPU load, suggesting to set turbo boost: on")
if turbo():
print("Currently turbo boost is: on")
else:
print("Currently turbo boost is: off")
footer()
else:
print("Load optimal, suggesting to set turbo boost: off")
if turbo():
print("Currently turbo boost is: on")
else:
print("Currently turbo boost is: off")
footer()
# set performance and enable turbo
def set_performance():
print(f"Setting to use: \"{get_avail_performance()}\" governor")
run(f"cpufreqctl --governor --set={get_avail_performance()}", shell=True)
if os.path.exists("/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference"):
run("cpufreqctl --epp --set=balance_performance", shell=True)
print("Setting to use: \"balance_performance\" EPP")
load1m, _, _ = os.getloadavg()
cpuload = psutil.cpu_percent(interval=1)
print("\nTotal CPU usage:", cpuload, "%")
print("Total system load:", load1m, "\n")
if load1m >= CPUS / 5:
print("High load, setting turbo boost: on")
turbo(True)
elif cpuload > 20:
print("High CPU load, setting turbo boost: on")
turbo(True)
else:
print("Load optimal, setting turbo boost: off")
turbo(False)
footer()
# make turbo suggestions in performance
def mon_performance():
# get system/CPU load
load1m, _, _ = os.getloadavg()
# get CPU utilization as a percentage
cpuload = psutil.cpu_percent(interval=1)
print("\nTotal CPU usage:", cpuload, "%")
print("Total system load:", load1m, "\n")
if turbo():
print("Currently turbo boost is: on")
print("Suggesting to set turbo boost: on")
else:
print("Currently turbo boost is: off")
print("Suggesting to set turbo boost: on")
footer()
def set_autofreq():
"""
set cpufreq governor based if device is charging
"""
print("\n" + "-" * 28 + " CPU frequency scaling " + "-" * 28 + "\n")
# determine which governor should be used
if charging():
print("Battery is: charging")
set_performance()
else:
print("Battery is: discharging")
set_powersave()
def mon_autofreq():
"""
make cpufreq suggestions
:return:
"""
print("\n" + "-" * 28 + " CPU frequency scaling " + "-" * 28 + "\n")
# determine which governor should be used
if charging():
print("Battery is: charging")
print(f"Suggesting use of \"{get_avail_performance()}\" governor\nCurrently using:", get_current_gov())
mon_performance()
else:
print("Battery is: discharging")
print(f"Suggesting use of \"{get_avail_powersave()}\" governor\nCurrently using:", get_current_gov())
mon_powersave()
def sysinfo():
"""
get system information
"""
print("\n" + "-" * 29 + " System information " + "-" * 30 + "\n")
import distro
dist = "UNKNOWN distro"
version = "UNKNOWN version"
# get distro information in snap env.
if os.getenv("PKG_MARKER") == "SNAP":
try:
with open("/var/lib/snapd/hostfs/etc/os-release", "r") as searchfile:
for line in searchfile:
if line.startswith('NAME='):
dist = line[5:line.find('$')].strip("\"")
continue
elif line.startswith('VERSION='):
version = line[8:line.find('$')].strip("\"")
continue
except PermissionError:
pass
dist = f"{dist} {version}"
else:
# get distro information
fdist = distro.linux_distribution()
dist = " ".join(x for x in fdist)
print("Linux distro: " + dist)
print("Linux kernel: " + pl.release())
# driver check
driver = getoutput("cpufreqctl --driver")
print("Driver: " + driver)
cpu_arch = pl.machine()
cpu_count = psutil.cpu_count()
print("Architecture:", cpu_arch)
# get processor
with open("/proc/cpuinfo", "r") as f:
line = f.readline()
while line:
if "model name" in line:
print("Processor:" + line.split(':')[1].rstrip())
break
line = f.readline()
print("Cores:", cpu_count)
print("\n" + "-" * 30 + " Current CPU states " + "-" * 30 + "\n")
print(f"CPU max frequency: {psutil.cpu_freq().max:.0f}MHz")
print(f"CPU min frequency: {psutil.cpu_freq().min:.0f}MHz")
core_usage = psutil.cpu_freq(percpu=True)
print("\nCPU frequency for each core:\n")
core_num = 0
while core_num < cpu_count:
print(f"CPU{core_num}: {core_usage[core_num].current:.0f} MHz")
core_num += 1
# get number of core temp sensors
core_temp_num = psutil.cpu_count(logical=False)
# get hardware temperatures
core_temp = psutil.sensors_temperatures()
print("\nTemperature for each physical core:\n")
core_num = 0
while core_num < core_temp_num:
temp = float("nan")
try:
if "coretemp" in core_temp:
temp = core_temp['coretemp'][core_num].current
elif "k10temp" in core_temp:
# https://www.kernel.org/doc/Documentation/hwmon/k10temp
temp = core_temp['k10temp'][0].current
elif "acpitz" in core_temp:
temp = core_temp['acpitz'][0].current
except:
pass
print(f"CPU{core_num} temp: {temp:.0f}°C")
core_num += 1
# print current fan speed | temporarily commented
# current_fans = psutil.sensors_fans()['thinkpad'][0].current
# print("\nCPU fan speed:", current_fans, "RPM")
# read log func
def read_log():
if os.getenv("PKG_MARKER") == "SNAP":
call(["tail", "-n 50", "-f", str(auto_cpufreq_log_file_snap)])
elif os.path.isfile(auto_cpufreq_log_file):
call(["tail", "-n 50", "-f", str(auto_cpufreq_log_file)])
else:
print("\n" + "-" * 30 + " auto-cpufreq log " + "-" * 31 + "\n")
print("ERROR: auto-cpufreq log is missing.\n\nMake sure to run: \"auto-cpufreq --install\" first")
footer()
# check if program (argument) is running
def is_running(program, argument):
# iterate over all process id's found by psutil
for pid in psutil.pids():
try:
# requests the process information corresponding to each process id
proc = psutil.Process(pid)
# check if value of program-variable that was used to call the function
# matches the name field of the plutil.Process(pid) output
if program in proc.name():
# check output of p.name(), output name of program
# p.cmdline() - echo the exact command line via which p was called.
for arg in proc.cmdline():
if argument in str(arg):
return True
except:
continue
# check if auto-cpufreq --daemon is running
def running_daemon():
if is_running('auto-cpufreq', '--daemon'):
deploy_complete_msg()
exit(1)
elif os.getenv("PKG_MARKER") == "SNAP" and dcheck == "enabled":
deploy_complete_msg()
exit(1)