@@ -25,6 +25,7 @@ from tuna import tuna, sysfs, utils
import logging
import time
import shutil
+import tuna.cpupower as cpw
def get_loglevel(level):
if level.isdigit() and int(level) in range(0,5):
@@ -115,8 +116,12 @@ def gen_parser():
"disable_perf": dict(action='store_true', help="Explicitly disable usage of perf in GUI for process view"),
"refresh": dict(default=2500, metavar='MSEC', type=int, help="Refresh the GUI every MSEC milliseconds"),
"priority": dict(default=(None, None), metavar="POLICY:RTPRIO", type=tuna.get_policy_and_rtprio, help="Set thread scheduler tunables: POLICY and RTPRIO"),
- "background": dict(action='store_true', help="Run command as background task")
- }
+ "background": dict(action='store_true', help="Run command as background task"),
+ "idle_state_disabled_status": dict(dest='idle_state_disabled_status', metavar='IDLESTATEDISABLEDSTATUS', type=int, help='Print if cpu idle state of the cpus in CPU-LIST is enabled or disabled. If CPU-LIST is not specified, default to all cpus.'),
+ "idle_info": dict(dest='idle_info', action='store_const', const=True, help='Print general idle information on cpus in CPU-LIST. If CPU-LIST is not specified, default to all cpus.'),
+ "disable_idle_state": dict(dest='disable_idle_state', metavar='IDLESTATEINDEX', type=int, help='Disable cpus in CPU-LIST\'s cpu idle (cpu sleep state). If CPU-LIST is not specified, default to all cpus.'),
+ "enable_idle_state": dict(dest='enable_idle_state', metavar='IDLESTATEINDEX', type=int, help='Enable cpus in CPU-LIST\'s cpu idle (cpu sleep state). If CPU-LIST is not specified, default to all cpus.')
+ }
parser = HelpMessageParser(description="tuna - Application Tuning Program")
@@ -147,6 +152,10 @@ def gen_parser():
show_irqs = subparser.add_parser('show_irqs', description='Show IRQ list', help='Show IRQ list')
show_configs = subparser.add_parser('show_configs', description='List preloaded profiles', help='List preloaded profiles')
+ idle_set = subparser.add_parser('idle-set',
+ description='Query and set all idle states on a given CPU list. Requires libcpupower to be installed',
+ help='Set all idle states on a given CPU-LIST.')
+
what_is = subparser.add_parser('what_is', description='Provides help about selected entities', help='Provides help about selected entities')
gui = subparser.add_parser('gui', description="Start the GUI", help="Start the GUI")
@@ -218,6 +227,13 @@ def gen_parser():
show_irqs_group.add_argument('-S', '--sockets', **MODS['sockets'])
show_irqs.add_argument('-q', '--irqs', **MODS['irqs'])
+ idle_set_group = idle_set.add_mutually_exclusive_group(required=True)
+ idle_set.add_argument('-c', '--cpus', **MODS['cpus'])
+ idle_set_group.add_argument('-s', '--status', **MODS['idle_state_disabled_status'])
+ idle_set_group.add_argument('-i', '--idle-info', **MODS['idle_info'])
+ idle_set_group.add_argument('-d', '--disable', **MODS['disable_idle_state'])
+ idle_set_group.add_argument('-e', '--enable', **MODS['enable_idle_state'])
+
what_is.add_argument('thread_list', **POS['thread_list'])
gui.add_argument('-d', '--disable_perf', **MODS['disable_perf'])
@@ -635,6 +651,19 @@ def main():
my_logger.addHandler(add_handler("DEBUG", tofile=False))
my_logger.info("Debug option set")
+ if args.command == 'idle-set':
+ if not cpw.have_cpupower:
+ print(f"Error: libcpupower bindings are not detected; need {cpw.cpupower_required_kernel} at a minimum.")
+ sys.exit(1)
+
+ if not args.cpu_list or args.cpu_list == []:
+ args.cpu_list = cpw.Cpupower().get_all_cpu_list()
+
+ my_cpupower = cpw.Cpupower(args.cpu_list)
+ ret = my_cpupower.idle_state_handler(args)
+ if ret > 0:
+ sys.exit(ret)
+
if args.loglevel:
if not args.debug:
my_logger = setup_logging("my_logger")
new file mode 100755
@@ -0,0 +1,202 @@
+# Copyright (C) 2024 John B. Wyatt IV
+# SPDX-License-Identifier: GPL-2.0-only
+
+from typing import List
+import tuna.utils as utils
+
+cpupower_required_kernel = "6.12"
+have_cpupower = None
+
+
+import raw_pylibcpupower as cpw
+try:
+ # Noticed that SWIG generated bindings will simply complain a
+ # function does not exist instead of triggering an exception.
+ # Call some query function to trigger an AttributeError for
+ # when the bindings are missing.
+ cpw.cpufreq_get_available_frequencies(0)
+ have_cpupower = True
+except:
+ have_cpupower = False
+
+
+if have_cpupower:
+ class Cpupower:
+ """The Cpupower class allows you to query and change the power states of the
+ cpu.
+
+ You may query or change the cpus all at once or a list of the cpus provided to the constructor's cpulist argument.
+
+ The bindings must be detected on the $PYTHONPATH variable.
+
+ You must use have_cpupower variable to determine if the bindings were
+ detected in your code."""
+ def __init__(self, cpulist=None):
+ if cpulist == None:
+ self.__cpulist = self.get_all_cpu_list()
+ else:
+ self.__cpulist = cpulist
+
+ @classmethod
+ def get_all_cpu_list(cls):
+ return list(range(cls.get_idle_info()["all_cpus"]))
+
+ @classmethod
+ def get_idle_states(cls, cpu):
+ """
+ Get the c-states of a cpu.
+
+ You can capture the return values with:
+ states_list, states_amt = get_idle_states()
+
+ Returns
+ List[String]: list of cstates
+ Int: amt of cstates
+ """
+ ret = []
+ for cstate in range(cpw.cpuidle_state_count(cpu)):
+ ret.append(cpw.cpuidle_state_name(cpu,cstate))
+ return ret, cpw.cpuidle_state_count(cpu)
+
+ @classmethod
+ def get_idle_info(cls, cpu=0):
+ idle_states, idle_states_amt = cls.get_idle_states(cpu)
+ idle_states_list = []
+ for idle_state in range(0, len(idle_states)):
+ idle_states_list.append(
+ {
+ "CPU ID": cpu,
+ "Idle State Name": idle_states[idle_state],
+ "Flags/Description": cpw.cpuidle_state_desc(cpu, idle_state),
+ "Latency": cpw.cpuidle_state_latency(cpu, idle_state),
+ "Usage": cpw.cpuidle_state_usage(cpu, idle_state),
+ "Duration": cpw.cpuidle_state_time(cpu, idle_state)
+ }
+ )
+ idle_info = {
+ "all_cpus": utils.get_nr_cpus(),
+ "CPUidle-driver": cpw.cpuidle_get_driver(),
+ "CPUidle-governor": cpw.cpuidle_get_governor(),
+ "idle-states-count": idle_states_amt,
+ "available-idle-states": idle_states,
+ "cpu-states": idle_states_list
+ }
+ return idle_info
+
+ @classmethod
+ def print_idle_info(cls, cpu_list=[0]):
+ cpu_count = utils.get_nr_cpus()
+ for cpu in cpu_list:
+ idle_info = cls.get_idle_info(cpu)
+ print_str = f"""
+CPUidle driver: {idle_info["CPUidle-driver"]}
+CPUidle governor: {idle_info["CPUidle-governor"]}
+analyzing CPU {cpu}
+
+Number of idle states: {idle_info["idle-states-count"]}
+Available idle states: {idle_info["available-idle-states"]}
+"""
+ for state in idle_info["cpu-states"]:
+ print_str += f"""{state["Idle State Name"]}
+Flags/Description: {state["Flags/Description"]}
+Latency: {state["Latency"]}
+Usage: {state["Usage"]}
+Duration: {state["Duration"]}
+"""
+ print(
+ print_str
+ )
+
+ def idle_state_handler(self, args) -> int:
+ if args.idle_state_disabled_status != None:
+ cstate_index = args.idle_state_disabled_status
+ cstate_list, cstate_amt = self.get_idle_states(args.cpu_list[0]) # Assumption, that all cpus have the same idle state
+ if cstate_index < 0 or cstate_index >= cstate_amt:
+ print(f"Invalid idle state range. Total for this cpu is {cstate_amt}")
+ return 1
+ cstate_name = cstate_list[cstate_index]
+ ret = self.is_disabled_idle_state(cstate_index)
+ for i, e in enumerate(ret):
+ match e:
+ case 1:
+ print(f"CPU: {args.cpu_list[i]} Idle state \"{cstate_name}\" is disabled.")
+ case 0:
+ print(f"CPU: {args.cpu_list[i]} Idle state \"{cstate_name}\" is enabled.")
+ case -1:
+ print(f"Idlestate not available")
+ case -2:
+ print(f"Disabling is not supported by the kernel")
+ case _:
+ print(f"Not documented: {e}")
+ elif args.idle_info != None:
+ self.print_idle_info(args.cpu_list)
+ return 0
+ elif args.disable_idle_state != None:
+ cstate_index = args.disable_idle_state
+ cstate_list, cstate_amt = self.get_idle_states(args.cpu_list[0]) # Assumption, that all cpus have the same idle state
+ if cstate_index < 0 or cstate_index >= cstate_amt:
+ print(f"Invalid idle state range. Total for this cpu is {cstate_amt}")
+ return 1
+ cstate_name = cstate_list[cstate_index]
+ ret = self.disable_idle_state(cstate_index, 1)
+ for i, e in enumerate(ret):
+ match e:
+ case 0:
+ print(f"CPU: {args.cpu_list[i]} Idle state \"{cstate_name}\" is disabled.")
+ case -1:
+ print(f"Idlestate not available")
+ case -2:
+ print(f"Disabling is not supported by the kernel")
+ case -3:
+ print(f"No write access to disable/enable C-states: try using sudo")
+ case _:
+ print(f"Not documented: {e}")
+ elif args.enable_idle_state != None:
+ cstate_index = args.enable_idle_state
+ cstate_list, cstate_amt = self.get_idle_states(args.cpu_list[0]) # Assumption, that all cpus have the same idle state
+ if cstate_index < 0 or cstate_index >= cstate_amt:
+ print(f"Invalid idle state range. Total for this cpu is {cstate_amt}")
+ return 1
+ cstate_name = cstate_list[cstate_index]
+ ret = self.disable_idle_state(cstate_index, 0)
+ for i, e in enumerate(ret):
+ match e:
+ case 0:
+ print(f"CPU: {args.cpu_list[i]} Idle state \"{cstate_name}\" is enabled.")
+ case -1:
+ print(f"Idlestate not available")
+ case -2:
+ print(f"Disabling is not supported by the kernel")
+ case -3:
+ print(f"No write access to disable/enable C-states: try using sudo")
+ case _:
+ print(f"Not documented: {e}")
+ else:
+ print(args)
+ print("idle-set error: you should not get here!")
+ return 0
+
+ """
+ Disable or enable an idle state using the object's stored list of cpus.
+
+ Args:
+ state (int): The cpu idle state index to disable or enable as an int starting from 0.
+ disabled (int): set to 1 to disable or 0 to enable. Less than 0 is an error.
+ """
+ def disable_idle_state(self, state, disabled) -> List[int]:
+ ret = []
+ for cpu in self.__cpulist:
+ ret.append(cpw.cpuidle_state_disable(cpu, state, disabled))
+ return ret
+
+ """
+ Query the idle state.
+
+ Args:
+ state: The cpu idle state. 1 is disabled, 0 is enabled. Less than 0 is an error.
+ """
+ def is_disabled_idle_state(self, state) -> List[int]:
+ ret = []
+ for cpu in self.__cpulist:
+ ret.append(cpw.cpuidle_is_state_disabled(cpu, state))
+ return ret