Skip to content

tuned-ppd: Add powerprofilescts#5

Open
ptr1337 wants to merge 3 commits intocachyosfrom
powerprofilesctl
Open

tuned-ppd: Add powerprofilescts#5
ptr1337 wants to merge 3 commits intocachyosfrom
powerprofilesctl

Conversation

@ptr1337
Copy link
Member

@ptr1337 ptr1337 commented Mar 20, 2026

Compat layer taken from power-profiles-daemon.

Vladdy wants to use it instead of tuned-adm for game-perf script

ptr1337 added 2 commits March 20, 2026 17:29
… twice

Signed-off-by: Peter Jung <admin@ptr1337.dev>
Signed-off-by: Peter Jung <admin@ptr1337.dev>
@ptr1337 ptr1337 requested review from 1Naim, Copilot and ventureoo March 20, 2026 17:06
@ptr1337 ptr1337 self-assigned this Mar 20, 2026
@ptr1337 ptr1337 added the enhancement New feature or request label Mar 20, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a powerprofilesctl compatibility CLI (ported from power-profiles-daemon) and wires it into the TuneD PPD compatibility layer, including a new battery-oriented balanced profile mapping.

Changes:

  • Add tuned/ppd/powerprofilesctl CLI for interacting with the PPD D-Bus API.
  • Introduce cachyos-balanced-battery TuneD profile and map PPD “balanced” on battery to it.
  • Install powerprofilesctl via the install-ppd Makefile target.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
tuned/ppd/ppd.conf Updates battery “balanced” mapping to the new cachyos-balanced-battery profile.
tuned/ppd/powerprofilesctl New CLI implementing list/get/set/launch/... commands against the PPD D-Bus interface.
profiles/cachyos-balanced-battery/tuned.conf New TuneD profile tuned for balanced behavior on battery.
Makefile Installs powerprofilesctl into $(BINDIR) as part of install-ppd.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +201 to +207
raise argparse.ArgumentError(
argument="action", message="enable or disable is required"
)
if enable is True and disable is False:
raise argparse.ArgumentError(
argument="action", message="can't set both enable and disable"
)
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_configure_action raises argparse.ArgumentError with argument="action" (a string). ArgumentError expects an argparse Action object, so this will raise a TypeError and bypass the @command error handling (resulting in a traceback). Prefer ValueError (caught by the decorator) or call parser.error(...)/use a mutually-exclusive group with required=True so argparse handles validation.

Suggested change
raise argparse.ArgumentError(
argument="action", message="enable or disable is required"
)
if enable is True and disable is False:
raise argparse.ArgumentError(
argument="action", message="can't set both enable and disable"
)
raise ValueError("enable or disable is required")
if enable is True and disable is False:
raise ValueError("can't set both enable and disable")

Copilot uses AI. Check for mistakes.
raise argparse.ArgumentError(
argument="action", message="can't set both enable and disable"
)
print(f"action: {action}, enable: {enable}")
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This print(...) in configure-action looks like leftover debug output and will add unexpected stdout on success (unlike the other subcommands). Consider removing it or only emitting output under an explicit verbosity/debug flag.

Suggested change
print(f"action: {action}, enable: {enable}")

Copilot uses AI. Check for mistakes.
if not profile:
profile = "performance"
if not reason:
reason = f"Running {args.appid}"
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When defaulting reason, this uses args.appid, but appid may have just been inferred from args.arguments[0]. This can produce Running None even though appid was computed. Use the resolved appid variable when building the default reason.

Suggested change
reason = f"Running {args.appid}"
reason = f"Running {appid}"

Copilot uses AI. Check for mistakes.
Comment on lines +148 to +171
with subprocess.Popen(args.arguments) as launched_app:
def receive_signal(signum, _stack):
launched_app.send_signal(signum)

redirected_signals = [
signal.SIGTERM,
signal.SIGINT,
signal.SIGABRT,
]

for sig in redirected_signals:
signal.signal(sig, receive_signal)

try:
launched_app.wait()
ret = launched_app.returncode
except KeyboardInterrupt:
ret = launched_app.returncode

for sig in redirected_signals:
signal.signal(sig, signal.SIG_DFL)

proxy.ReleaseProfile("(u)", cookie)

Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HoldProfile(...) is acquired before subprocess.Popen(...). If Popen raises (e.g., command not found) or another exception occurs before ReleaseProfile, the profile hold will leak. Wrap the launched process section in a try/finally that always calls ReleaseProfile (and consider handling OSError/FileNotFoundError to return a clean exit code).

Suggested change
with subprocess.Popen(args.arguments) as launched_app:
def receive_signal(signum, _stack):
launched_app.send_signal(signum)
redirected_signals = [
signal.SIGTERM,
signal.SIGINT,
signal.SIGABRT,
]
for sig in redirected_signals:
signal.signal(sig, receive_signal)
try:
launched_app.wait()
ret = launched_app.returncode
except KeyboardInterrupt:
ret = launched_app.returncode
for sig in redirected_signals:
signal.signal(sig, signal.SIG_DFL)
proxy.ReleaseProfile("(u)", cookie)
try:
with subprocess.Popen(args.arguments) as launched_app:
def receive_signal(signum, _stack):
launched_app.send_signal(signum)
redirected_signals = [
signal.SIGTERM,
signal.SIGINT,
signal.SIGABRT,
]
for sig in redirected_signals:
signal.signal(sig, receive_signal)
try:
launched_app.wait()
ret = launched_app.returncode
except KeyboardInterrupt:
ret = launched_app.returncode
finally:
for sig in redirected_signals:
signal.signal(sig, signal.SIG_DFL)
except (OSError, FileNotFoundError):
# Command could not be executed (e.g. not found); use a conventional
# "command not found" exit code instead of leaking the profile hold.
ret = 127
finally:
proxy.ReleaseProfile("(u)", cookie)

Copilot uses AI. Check for mistakes.
Comment on lines +238 to +243
parser_set.add_argument(
"profile",
nargs=1,
help="Profile to use for set command",
choices=get_profile_choices(),
)
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using choices=get_profile_choices() triggers a D-Bus call while building the parser. If the service isn't reachable at startup, choices becomes empty and powerprofilesctl set … fails with an argparse “invalid choice” error instead of the intended D-Bus communication error. Consider removing choices= and validating the profile inside _set_profile (or lazily resolving choices only for shell completion generation).

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +73
if enable is False and disable is True:
raise ValueError("enable or disable is required")
if enable is True and disable is False:
raise ValueError("can't set both enable and disable")
enable = enable if enable is not None else not disable
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enable is always a boolean here because the argparse flag uses action="store_true" (default False), so enable is not None is always true and the else not disable branch is dead code. If the intent is to require exactly one of --enable/--disable, consider using add_mutually_exclusive_group(required=True) with a single destination (e.g. dest="enable" with store_true/store_false) so the logic is simpler and validation happens in argparse.

Suggested change
if enable is False and disable is True:
raise ValueError("enable or disable is required")
if enable is True and disable is False:
raise ValueError("can't set both enable and disable")
enable = enable if enable is not None else not disable
# Require exactly one of --enable or --disable
if enable == disable:
raise ValueError("exactly one of --enable or --disable is required")

Copilot uses AI. Check for mistakes.
Signed-off-by: Peter Jung <admin@ptr1337.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants