Introducing tlp-psd: Automatic Power Profiles Switching based on Workload

Hello Community,

I want to point out a common pain point for laptop users: the latency and rigidity of standard power profile daemons. While tools like power-profiles-daemon or TLP are great, they are usually either entirely reactive or purely bound to the power source (AC/Battery). As a result, users often have to manually switch profiles to either save power or gain enough performance when needed.

On my laptop I am using TLP and was pleased when TLP 1.9.0 added support for the power-profiles-daemon API. However, after a while, manually switching profiles between power-saving, balanced and performance became a bit annoying.

So I asked myself: why can’t the device decide for itself which profile is right for the current workload, while keeping a general focus on power savings?

After some research, I couldn’t find an existing solution to bridge this gap, so I decided to create my own. I forked TLP and created the tlp-psd daemon — a background daemon designed to bring proactive, pattern-based workload automation to power management without requiring any user interaction.

The daemon is written entirely in pure POSIX shell script to ensure maximum compatibility — meaning no Bash-specific features and no heavy runtime dependencies. This design choice makes the daemon lightweight, fast, and easily portable. It integrates seamlessly with TLP’s existing shell-based infrastructure, ensuring consistency and significantly reducing the attack surface.

How it works

The daemon continuously monitors system metrics — CPU load, I/O utilization, process queue length, and latency requirements — combining them into a dynamic “lag score”.

With sufficient historical data, the daemon identifies recurring workload sequences. If a known pattern reoccurs, it proactively switches to the appropriate performance level before the load actually rises and causes UI lag.

For unknown or new workloads, the daemon relies on adaptive thresholds calculated from moving averages, using dedicated hysteresis zones to prevent rapid oscillation between profiles.

Implementation Details:

  • Persistent Memory: All recognized workload patterns are stored persistently to survive reboots.
  • Continuous Optimization: The daemon improves predictive capabilities over time by evaluating past predictions against actual performance outcomes.
  • Dynamic Learning: The learning cycle adapts dynamically, automatically slowing down as system stability increases.
  • Resource Management: CPU core scaling is managed via cgroup controllers for precise allocation.
  • Next-Gen Scheduling: On compatible kernels (6.11+), it integrates with the scx_p2dq scheduler (sched_ext) to provide energy-aware task scheduling that complements the profile decisions.

For more details, see the detailed daemon documentation:

:backhand_index_pointing_right: easyTLP/README-POWER-SAVER-DAEMON.md at power-saver-daemon · Mylinde/easyTLP · GitHub

Status of a running daemon

The tlp-psd status output from a 10-day daemon uptime cycle on my laptop:

=== TLP Power Saver Daemon Status ===

Daemon Runtime:
  10 days 1 hour 34 minutes 55 seconds

Estimated fullcharge Battery Life:
  12 hours 25 minutes 12 seconds

Intelligence:
  Confidence Level: 70/100 (11 min learning cycle)

Predictions:
  Accuracy: 682/733 (93%)

Learned Patterns:
  Total: 37 (SAV: 11, BAL: 23, PRF: 3)

Proactive Switches:
  Executed: 422

Profile Distribution:
  SAV: 95% | BAL: 4% | PRF: 1%

Energy Consumption:
  SAV: 655.93 Wh | BAL: 19.12 Wh | PRF: 1.49 Wh | Total: 676.53 Wh | Average: 2.80 Wh/h

Current Profile:
  SAV

Key Takeaways

  • Prediction Accuracy (93%): Out of 733 predicted workload changes, 682 were accurate.
  • 422 Proactive Switches: The system seamlessly ramped up the profile before lagging or stuttering could happen.
  • Efficiency (95% in SAV): The laptop safely spends 95% of its time in the lowest power-saving mode (SAV), maximizing battery life without sacrificing responsiveness when hitting a heavy workload.

Check it out

The project can be found here:

:backhand_index_pointing_right: GitHub - Mylinde/easyTLP: TLP - Optimize Linux Laptop Battery Life · GitHub

Please note: easyTLP will conflict with upstream TLP, power-profiles-daemon, and scx-scheds.

I would love to get your thoughts on this approach.

1 Like

This needs to come with a major caveat: All TLP variants conflict with power-settings-daemon in KDE Plasma, so Kubuntu and Ubuntu Studio machines cannot use this.

Furthermore, it is known to cause problems in GNOME as GNOME now has the CPU power settings built-in.

2 Likes

The intention behind the daemon is to relieve the user of the “mundane” decision regarding which performance profile to select and leaving that task to the system instead. This entails replacing the currently existing solutions; at my budgie desktop it replaces the power-profiles-daemon, there is no more need to switch any profile manually.

Right, but in KDE Plasma plasma-profiles-daemon is required by the DE itself, so it’s a non-negotiable.

1 Like

Could you please explain in more detail what you mean? The behavior observed in Plasma 6.6.6 contradicts your statement.

kubuntu@kubuntu:~$ inxi -S
System:
  Host: kubuntu Kernel: 7.0.0-30-generic arch: x86_64 bits: 64
  Desktop: KDE Plasma v: 6.6.6 Distro: Kubuntu 26.04.1 LTS (Resolute
    Raccoon)
kubuntu@kubuntu:~$ apt why plasma-profiles-daemon
Error: Unable to locate package plasma-profiles-daemon
kubuntu@kubuntu:~$ apt why power-profiles-daemon
power-profiles-daemon:amd64 is selected for install because:
1. kubuntu-desktop:amd64 is selected for install
2. kubuntu-desktop:amd64 Depends powerdevil
3. powerdevil:amd64 Recommends power-profiles-daemon
kubuntu@kubuntu:~$ sudo apt remove power-profiles-daemon
REMOVING:
  power-profiles-daemon

Summary:
  Upgrading: 0, Installing: 0, Removing: 1, Not Upgrading: 0
  Freed space: 190 kB

Continue? [Y/n] y
(Reading database ... 221490 files and directories currently installed.)
Removing power-profiles-daemon (0.30-2) ...
Processing triggers for dbus (1.16.2-2ubuntu4) ...
Processing triggers for man-db (2.13.1-1build1) ...
kubuntu@kubuntu:~$ sudo tlp-stat
--- TLP 1.10.5 --------------------------------------------

+++ Configured Settings:
defaults.conf L0006: TLP_ENABLE="1"
defaults.conf L0007: TLP_WARN_LEVEL="3"
defaults.conf L0008: TLP_MSG_COLORS="91 93 1 92"
defaults.conf L0009: TLP_AUTO_SWITCH="1"
defaults.conf L0010: TLP_PROFILE_AC="PRF"
defaults.conf L0011: TLP_PROFILE_BAT="SAV"
defaults.conf L0012: DISK_IDLE_SECS_ON_AC="0"
defaults.conf L0013: DISK_IDLE_SECS_ON_BAT="2"
defaults.conf L0014: MAX_LOST_WORK_SECS_ON_AC="15"
defaults.conf L0015: MAX_LOST_WORK_SECS_ON_BAT="60"
defaults.conf L0016: CPU_ENERGY_PERF_POLICY_ON_AC="balance_performance"
defaults.conf L0017: CPU_ENERGY_PERF_POLICY_ON_BAT="balance_power"
defaults.conf L0018: CPU_ENERGY_PERF_POLICY_ON_SAV="power"
defaults.conf L0019: PLATFORM_PROFILE_ON_AC="performance"
defaults.conf L0020: PLATFORM_PROFILE_ON_BAT="balanced"
defaults.conf L0021: PLATFORM_PROFILE_ON_SAV="low-power cool quiet"
defaults.conf L0022: NMI_WATCHDOG="0"
defaults.conf L0023: DISK_DEVICES="nvme0n1 sda"
defaults.conf L0024: DISK_APM_LEVEL_ON_AC="254 254"
defaults.conf L0025: DISK_APM_LEVEL_ON_BAT="128 128"
defaults.conf L0026: DISK_APM_CLASS_DENYLIST="usb ieee1394"
defaults.conf L0027: DISK_IOSCHED="keep keep"
defaults.conf L0028: SATA_LINKPWR_ON_AC="med_power_with_dipm"
defaults.conf L0029: SATA_LINKPWR_ON_BAT="med_power_with_dipm"
defaults.conf L0030: AHCI_RUNTIME_PM_ON_AC="on"
defaults.conf L0031: AHCI_RUNTIME_PM_ON_BAT="auto"
defaults.conf L0032: AHCI_RUNTIME_PM_TIMEOUT="15"
defaults.conf L0033: PCIE_ASPM_ON_AC="default"
defaults.conf L0034: PCIE_ASPM_ON_BAT="default"
defaults.conf L0035: PCIE_ASPM_ON_SAV="default"
defaults.conf L0036: INTEL_GPU_POWER_PROFILE_ON_AC="base"
defaults.conf L0037: INTEL_GPU_POWER_PROFILE_ON_BAT="power_saving"
defaults.conf L0038: INTEL_GPU_POWER_PROFILE_ON_SAV="power_saving"
defaults.conf L0039: RADEON_DPM_PERF_LEVEL_ON_AC="auto"
defaults.conf L0040: RADEON_DPM_PERF_LEVEL_ON_BAT="auto"
defaults.conf L0041: RADEON_DPM_PERF_LEVEL_ON_SAV="low"
defaults.conf L0042: AMDGPU_ABM_LEVEL_ON_AC="0"
defaults.conf L0043: AMDGPU_ABM_LEVEL_ON_BAT="1"
defaults.conf L0044: AMDGPU_ABM_LEVEL_ON_SAV="3"
defaults.conf L0045: WIFI_PWR_ON_AC="off"
defaults.conf L0046: WIFI_PWR_ON_BAT="on"
defaults.conf L0047: WOL_DISABLE="Y"
defaults.conf L0048: POWER_SAVER_ENABLE="1"
defaults.conf L0049: SAV_DYNAMIC_INTERVAL="2"
defaults.conf L0050: SAV_DYNAMIC_SAMPLES="4"
defaults.conf L0051: SAV_DYNAMIC_HYSTERESIS="5"
defaults.conf L0052: SAV_DYNAMIC_MIN_DELAY="3"
defaults.conf L0053: SOUND_POWER_SAVE_ON_AC="1"
defaults.conf L0054: SOUND_POWER_SAVE_ON_BAT="1"
defaults.conf L0055: SOUND_POWER_SAVE_CONTROLLER="Y"
defaults.conf L0056: BAY_POWEROFF_ON_AC="0"
defaults.conf L0057: BAY_POWEROFF_ON_BAT="0"
defaults.conf L0058: BAY_DEVICE="sr0"
defaults.conf L0059: RUNTIME_PM_ON_AC="on"
defaults.conf L0060: RUNTIME_PM_ON_BAT="auto"
defaults.conf L0061: RUNTIME_PM_DRIVER_DENYLIST="amdgpu mei_me nouveau nvidia xhci_hcd"
defaults.conf L0062: USB_AUTOSUSPEND="1"
defaults.conf L0063: USB_EXCLUDE_AUDIO="1"
defaults.conf L0064: USB_EXCLUDE_BTUSB="0"
defaults.conf L0065: USB_EXCLUDE_PHONE="0"
defaults.conf L0066: USB_EXCLUDE_PRINTER="1"
defaults.conf L0067: USB_EXCLUDE_WWAN="0"
defaults.conf L0068: RESTORE_THRESHOLDS_ON_BAT="1"
defaults.conf L0069: NATACPI_ENABLE="1"
defaults.conf L0070: TPSMAPI_ENABLE="1"

+++ System Info
System         = ASUSTeK COMPUTER INC. 1.0 Vivobook Go E1504FA_E1504FA
BIOS           = American Megatrends International, LLC. E1504FA.320
OS release     = Ubuntu 26.04.1 LTS
Kernel         = 7.0.0-30-generic #30-Ubuntu SMP PREEMPT_DYNAMIC Fri Jul 31 18:22:54 UTC 2026 x86_64
/proc/cmdline  = BOOT_IMAGE=/casper/vmlinuz --- quiet splash
Init system    = systemd 259
Boot mode      = UEFI
Suspend mode   = [s2idle]

+++ TLP Status
tlp            = enabled, last run: 14:05:40, 57 sec(s) ago
tlp-rdw        = enabled
TLP profile    = power-saver/SAV
Power source   = battery

+++ Processor
CPU model = AMD Ryzen 3 7320U with Radeon Graphics

/sys/devices/system/cpu/cpu0/cpufreq/scaling_driver    = amd-pstate-epp
/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor  = powersave
/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors = performance powersave
/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq  =  1100459 [kHz]
/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq  =  4151730 [kHz]
/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq  =   425178 [kHz]
/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq  =  4151730 [kHz]
/sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_lowest_nonlinear_freq  =  1100459 [kHz]
/sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_max_freq               =  4151730 [kHz]
/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference = power [EPP]
/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences = default performance balance_performance balance_power power

/sys/devices/system/cpu/cpu1..cpu7: omitted for clarity, use -v to show all

/sys/devices/system/cpu/amd_pstate/status              = active
/sys/devices/system/cpu/cpufreq/boost                  = 1
/sys/module/workqueue/parameters/power_efficient       = Y
/proc/sys/kernel/nmi_watchdog                          = 0

+++ Platform
Driver = asus-nb-wmi
/sys/firmware/acpi/platform_profile                    = quiet
/sys/firmware/acpi/platform_profile_choices            = quiet balanced performance

+++ Temperatures
CPU temp (acpitz)      =   38 [°C]
Fan speed              = (not available)

+++ File System
/proc/sys/vm/laptop_mode               =     2
/proc/sys/vm/dirty_writeback_centisecs =  6000
/proc/sys/vm/dirty_expire_centisecs    =  6000
/proc/sys/vm/dirty_ratio               =    20
/proc/sys/vm/dirty_background_ratio    =    10

+++ Disks
Devices = nvme0n1 sda

/dev/nvme0n1:
  Type       = NVMe
  Disk ID    = nvme-SAMSUNG_MZVL4512HBLU-00BTW_**************
  Model      = SAMSUNG MZVL4512HBLU-00BTW
  Firmware   = HXC70W1Q
  Temp       = 28 °C
  Scheduler  = [none] mq-deadline (multi queue)

  Runtime PM:
    /sys/block/nvme0n1/device/power/control = auto, autosuspend_delay_ms = (not available)

  SMART info:
    Critical Warning:                   0x00
    Temperature:                        28 Celsius
    Available Spare:                    100%
    Available Spare Threshold:          10%
    Percentage Used:                    2%
    Data Units Written:                 11.918.988 [6,10 TB]
    Power Cycles:                       2.872
    Power On Hours:                     2.789
    Unsafe Shutdowns:                   80
    Media and Data Integrity Errors:    0

/dev/sda:
  Type       = USB
  Disk ID    = usb-Kingston_DataTraveler_3.0_****************************
  Model      = Kingston DataTraveler 3.0
  Firmware   = unknown
  APM Level  = none/disabled
  Status     = standby
  Scheduler  = none [mq-deadline] (multi queue)

  Runtime PM:
    /sys/block/sda/device/power/control = auto, autosuspend_delay_ms = 15000

+++ AMD Radeon Graphics
/sys/class/drm/card1/device/driver                                = amdgpu
/sys/class/drm/card1/device/power_state                           = D0
/sys/class/drm/card1/device/power_dpm_force_performance_level     = low
/sys/class/drm/card1/card1-eDP-1/amdgpu/panel_power_savings       = 3

+++ Wireless
bluetooth = on
nfc       = none (no device)
wifi      = on
wwan      = none (no device)

hci0(btusb)                   : bluetooth, not connected
wlp2s0(rtw88_8821ce)          : wifi, connected, power management = off

+++ Audio
/sys/module/snd_hda_intel/parameters/power_save            = 1
/sys/module/snd_hda_intel/parameters/power_save_controller = Y

+++ PCIe Active State Power Management
/sys/module/pcie_aspm/parameters/policy = [default] performance powersave powersupersave

+++ PCIe Runtime Power Management
Autosuspend       = enabled
Enable devices    = (disabled)
Disable devices   = (disabled)
Device denylist   = (disabled)
Driver denylist   = amdgpu mei_me nouveau nvidia xhci_hcd

/sys/bus/pci/devices/0000:00:00.0/power/control = auto (0x060000, Host bridge, no driver)
/sys/bus/pci/devices/0000:00:00.2/power/control = auto (0x080600, IOMMU, no driver)
/sys/bus/pci/devices/0000:00:01.0/power/control = auto (0x060000, Host bridge, no driver)
/sys/bus/pci/devices/0000:00:02.0/power/control = auto (0x060000, Host bridge, no driver)
/sys/bus/pci/devices/0000:00:02.2/power/control = auto (0x060400, PCI bridge, pcieport)
/sys/bus/pci/devices/0000:00:02.3/power/control = auto (0x060400, PCI bridge, pcieport)
/sys/bus/pci/devices/0000:00:08.0/power/control = auto (0x060000, Host bridge, no driver)
/sys/bus/pci/devices/0000:00:08.1/power/control = auto (0x060400, PCI bridge, pcieport)
/sys/bus/pci/devices/0000:00:08.3/power/control = auto (0x060400, PCI bridge, pcieport)
/sys/bus/pci/devices/0000:00:14.0/power/control = auto (0x0c0500, SMBus, piix4_smbus)
/sys/bus/pci/devices/0000:00:14.3/power/control = auto (0x060100, ISA bridge, no driver)
/sys/bus/pci/devices/0000:00:18.0/power/control = auto (0x060000, Host bridge, no driver)
/sys/bus/pci/devices/0000:00:18.1/power/control = auto (0x060000, Host bridge, no driver)
/sys/bus/pci/devices/0000:00:18.2/power/control = auto (0x060000, Host bridge, no driver)
/sys/bus/pci/devices/0000:00:18.3/power/control = auto (0x060000, Host bridge, k10temp)
/sys/bus/pci/devices/0000:00:18.4/power/control = auto (0x060000, Host bridge, no driver)
/sys/bus/pci/devices/0000:00:18.5/power/control = auto (0x060000, Host bridge, no driver)
/sys/bus/pci/devices/0000:00:18.6/power/control = auto (0x060000, Host bridge, no driver)
/sys/bus/pci/devices/0000:00:18.7/power/control = auto (0x060000, Host bridge, no driver)
/sys/bus/pci/devices/0000:01:00.0/power/control = auto (0x010802, Non-Volatile memory controller, nvme)
/sys/bus/pci/devices/0000:02:00.0/power/control = auto (0x028000, Network controller, rtw88_8821ce)
/sys/bus/pci/devices/0000:03:00.0/power/control = on   (0x030000, VGA compatible controller, amdgpu)
/sys/bus/pci/devices/0000:03:00.1/power/control = auto (0x040300, Audio device, snd_hda_intel)
/sys/bus/pci/devices/0000:03:00.2/power/control = auto (0x108000, Encryption controller, ccp)
/sys/bus/pci/devices/0000:03:00.3/power/control = auto (0x0c0330, USB controller, xhci_hcd)
/sys/bus/pci/devices/0000:03:00.4/power/control = auto (0x0c0330, USB controller, xhci_hcd)
/sys/bus/pci/devices/0000:03:00.5/power/control = auto (0x048000, Multimedia controller, snd_pci_acp6x)
/sys/bus/pci/devices/0000:03:00.6/power/control = auto (0x040300, Audio device, snd_hda_intel)
/sys/bus/pci/devices/0000:04:00.0/power/control = auto (0x0c0330, USB controller, xhci_hcd)

+++ USB
Autosuspend       = enabled
Device allowlist  = (not configured)
Device denylist   = (not configured)
Exclude audio     = enabled
Exclude bluetooth = disabled
Exclude phones    = disabled
Exclude printers  = enabled
Exclude WWAN      = disabled

Bus 001 Device 001 ID 1d6b:0002 control = auto, autosuspend_delay_ms =    0 -- Linux Foundation 2.0 root hub (hub)
Bus 001 Device 002 ID 05e3:0610 control = auto, autosuspend_delay_ms =    0 -- Genesys Logic, Inc. Hub (hub)
Bus 001 Device 003 ID 13d3:3556 control = auto, autosuspend_delay_ms = 2000 -- IMC Networks Bluetooth Radio (btusb)
Bus 002 Device 001 ID 1d6b:0003 control = auto, autosuspend_delay_ms =    0 -- Linux Foundation 3.0 root hub (hub)
Bus 003 Device 001 ID 1d6b:0002 control = auto, autosuspend_delay_ms =    0 -- Linux Foundation 2.0 root hub (hub)
Bus 003 Device 002 ID 0951:1666 control = on,   autosuspend_delay_ms = 2000 -- Kingston Technology DataTraveler 100 G3/G4/SE9 G2/50 Kyson (usb-storage)
Bus 004 Device 001 ID 1d6b:0003 control = auto, autosuspend_delay_ms =    0 -- Linux Foundation 3.0 root hub (hub)
Bus 005 Device 001 ID 1d6b:0002 control = auto, autosuspend_delay_ms =    0 -- Linux Foundation 2.0 root hub (hub)
Bus 005 Device 002 ID 13d3:54a1 control = auto, autosuspend_delay_ms = 2000 -- IMC Networks USB2.0 HD UVC WebCam (uvcvideo)

+++ Battery Care
Plugin: asus
Supported features: charge threshold
Driver usage:
* natacpi (asus_wmi) = active (charge threshold)
Parameter value range:
* STOP_CHARGE_THRESH_BAT0/1: 1..100(default)

+++ Battery Status: BAT0
/sys/class/power_supply/BAT0/manufacturer                   = OEM
/sys/class/power_supply/BAT0/model_name                     = X160535
/sys/class/power_supply/BAT0/cycle_count                    =      0 (or not supported)
/sys/class/power_supply/BAT0/charge_full_design             =   3550 [mAh]
/sys/class/power_supply/BAT0/charge_full                    =   3174 [mAh]
/sys/class/power_supply/BAT0/charge_now                     =   1534 [mAh]
/sys/class/power_supply/BAT0/current_now                    =    440 [mA]
/sys/class/power_supply/BAT0/status                         = Discharging

/sys/class/power_supply/BAT0/charge_control_end_threshold   =    100 [%]

Charge                                                      =   48.3 [%]
Capacity                                                    =   89.4 [%]

kubuntu@kubuntu:~$ tlp-psd status

=== TLP Power Saver Daemon Status ===

Daemon Runtime:
  1 minute 2 seconds

Estimated fullcharge Battery Life:
  8 hours 1 minute 48 seconds

Intelligence:
  Confidence Level: 15/100 (1 min learning cycle)

Predictions:
  Accuracy: 1/1 (100%)

Learned Patterns:
  Total: 0 (SAV: 0, BAL: 0, PRF: 0)

Proactive Switches:
  Executed: 0

Profile Distribution:
  SAV: 100% | BAL: 0% | PRF: 0%

Energy Consumption:
  SAV: 0.08 Wh | BAL: 0.00 Wh | PRF: 0.00 Wh | Total: 0.08 Wh | Average: 4.44 Wh/h

Current Profile:
  SAV

kubuntu@kubuntu:~$

$ sudo apt install tlp
[sudo: authenticate] Password:         
Installing:                     
  tlp

Installing dependencies:
  tlp-rdw

Suggested packages:
  tp-smapi-dkms

REMOVING:
  power-profiles-daemon  ubuntustudio-desktop  ubuntustudio-desktop-core

Summary:
  Upgrading: 0, Installing: 2, Removing: 3, Not Upgrading: 4
  Download size: 101 kB
  Space needed: 674 kB / 850 GB available

Continue? [Y/n] n
Abort.

Additionally, from https://kfocus.org/wf/battery-reset.html:

Q: Can I use TLP? It seems like a great tool to increase battery life.

A: PLEASE DO NOT! TLP can be incompatible with other power saving systems already in place on all Ubuntu-based OSes. This can cause instability. Instead, we recommend you consider using powertop to check your power use. We don’t recommend changing the parameters though, since they often cause more trouble than they are worth. For example, powering down your Wi-Fi more aggressively will save battery, but then it can cause connection problems.

In summary, I refuse, with my Ubuntu Studio leader and Edubuntu Technical Lead hats on, to support TLP or anything related.