From b3c96935c82b7410422ca2973398ac9d0272c844 Mon Sep 17 00:00:00 2001 From: Anubhav Shelat Date: Fri, 2 Aug 2024 11:46:35 -0400 Subject: [PATCH 1/2] rteval: Added functionality to allow user to set the cstate of specified cpus when running rteval We would like to be able to set the idle states of CPUs while running rteval. This patch adds the file cpupower.py and option '--idle-set' within rteval to use cpupower. The set idle state is applied to the cpulist given by '--measurement-cpulist'. cpupower.py provides the infrastructure to interface and execute the cpupower command, and the options in rteval-cmd let the user specify the idle state to be set and the CPUs to set it on. Signed-off-by: Anubhav Shelat Signed-off-by: John Kacur --- rteval-cmd | 9 ++ rteval/cpupower.py | 125 +++++++++++++++++++++++++ rteval/modules/__init__.py | 2 +- rteval/modules/measurement/__init__.py | 2 + 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 rteval/cpupower.py diff --git a/rteval-cmd b/rteval-cmd index 19c82a0b64b3..f440a8a22622 100755 --- a/rteval-cmd +++ b/rteval-cmd @@ -29,6 +29,7 @@ from rteval.Log import Log from rteval import RtEval, rtevalConfig from rteval.modules.loads import LoadModules from rteval.modules.measurement import MeasurementModules +from rteval import cpupower from rteval.version import RTEVAL_VERSION from rteval.systopology import SysTopology, parse_cpulist_from_config from rteval.modules.loads.kcompile import ModuleParameters @@ -402,6 +403,10 @@ if __name__ == '__main__': if not os.path.isdir(rtevcfg.workdir): raise RuntimeError(f"work directory {rtevcfg.workdir} does not exist") + # if idle-set has been specified, enable the idle state via cpupower + if msrcfg.idlestate: + cpupower_controller = cpupower.Cpupower(msrcfg.cpulist, msrcfg.idlestate, logger=logger) + cpupower_controller.enable_idle_state() rteval = RtEval(config, loadmods, measuremods, logger) rteval.Prepare(rtevcfg.onlyload) @@ -421,6 +426,10 @@ if __name__ == '__main__': ec = rteval.Measure() logger.log(Log.DEBUG, f"exiting with exit code: {ec}") + # restore previous idle state settings + if msrcfg.idlestate: + cpupower_controller.restore_idle_states() + sys.exit(ec) except KeyboardInterrupt: sys.exit(0) diff --git a/rteval/cpupower.py b/rteval/cpupower.py new file mode 100644 index 000000000000..37c4d33f1df4 --- /dev/null +++ b/rteval/cpupower.py @@ -0,0 +1,125 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +# Copyright 2024 Anubhav Shelat +""" Object to execute cpupower tool """ + +import subprocess +import os +import shutil +import sys +from rteval.Log import Log +from rteval.systopology import SysTopology as SysTop +from rteval import cpulist_utils + +PATH = '/sys/devices/system/cpu/' + +class Cpupower: + """ class to store data for executing cpupower and restoring idle state configuration """ + def __init__(self, cpulist, idlestate, logger=None): + if not self.cpupower_present(): + print('cpupower not found') + sys.exit(1) + + self.__idle_state = int(idlestate) + self.__states = os.listdir(PATH + 'cpu0/cpuidle/') + # self.__idle_states is a dict with cpus as keys, + # and another dict as the value. The value dict + # has idle states as keys and a boolean as the + # value indicating if the state is disabled. + self.__idle_states = {} + self.__name = "cpupower" + self.__online_cpus = SysTop().online_cpus() + self.__cpulist = cpulist + self.__logger = logger + + + def _log(self, logtype, msg): + """ Common log function for rteval modules """ + if self.__logger: + self.__logger.log(logtype, f"[{self.__name}] {msg}") + + + def enable_idle_state(self): + """ Use cpupower to set the idle state """ + self.get_idle_states() + + # ensure that idle state is in range of available idle states + if self.__idle_state > len(self.__states) - 1 or self.__idle_state < 0: + print(f'Idle state {self.__idle_state} is out of range') + sys.exit(1) + + # enable all idle states to a certain depth, and disable any deeper idle states + with open(os.devnull, 'wb') as buffer: + for state in self.__states: + s = state.strip("state") + if int(s) > self.__idle_state: + self.run_cpupower(['cpupower', '-c', self.__cpulist,'idle-set', '-d', s], buffer) + else: + self.run_cpupower(['cpupower', '-c', self.__cpulist,'idle-set', '-e', s], buffer) + + self._log(Log.DEBUG, f'Idle state depth {self.__idle_state} enabled on CPUs {self.__cpulist}') + + + def run_cpupower(self, args, output_buffer=None): + """ execute cpupower """ + try: + subprocess.run(args, check=True, stdout=output_buffer) + except subprocess.CalledProcessError: + print('cpupower failed') + sys.exit(1) + + + def get_idle_states(self): + """ Store the current idle state setting """ + for cpu in self.__online_cpus: + self.__idle_states[cpu] = {} + for state in self.__states: + fp = os.path.join(PATH, 'cpu' + str(cpu) + '/cpuidle/' + state + '/disable') + self.__idle_states[cpu][state] = self.read_idle_state(fp) + + + def restore_idle_states(self): + """ restore the idle state setting """ + for cpu, states in self.__idle_states.items(): + for state, disabled in states.items(): + fp = os.path.join(PATH, 'cpu' + str(cpu) + '/cpuidle/' + state + '/disable') + self.write_idle_state(fp, disabled) + self._log(Log.DEBUG, 'Idle state settings restored') + + + def read_idle_state(self, file): + """ read the disable value for an idle state """ + with open(file, 'r', encoding='utf-8') as f: + return f.read(1) + + + def write_idle_state(self, file, state): + """ write the disable value for and idle state """ + with open(file, 'w', encoding='utf-8') as f: + f.write(state) + + + def get_idle_info(self): + """ execute cpupower idle-info """ + self.run_cpupower(['cpupower', 'idle-info']) + + + def cpupower_present(self): + """ check if cpupower is downloaded """ + return shutil.which("cpupower") is not None + + +if __name__ == '__main__': + l = Log() + l.SetLogVerbosity(Log.DEBUG) + + online_cpus = cpulist_utils.collapse_cpulist(SysTop().online_cpus()) + idlestate = '1' + info = True + + cpupower = Cpupower(online_cpus, idlestate, logger=l) + if idlestate: + cpupower.enable_idle_state() + cpupower.restore_idle_states() + print() + cpupower.get_idle_info() diff --git a/rteval/modules/__init__.py b/rteval/modules/__init__.py index d7792108d5b8..acd6330788e2 100644 --- a/rteval/modules/__init__.py +++ b/rteval/modules/__init__.py @@ -282,7 +282,7 @@ reference from the first import""" grparser.add_argument(f'--{self.__modtype}-cpulist', dest=f'{self.__modtype}___cpulist', action='store', default="", help=f'CPU list where {self.__modtype} modules will run', - metavar='LIST') + metavar='CPULIST') for (modname, mod) in list(self.__modsloaded.items()): opts = mod.ModuleParameters() diff --git a/rteval/modules/measurement/__init__.py b/rteval/modules/measurement/__init__.py index ecadd0885991..9314d1cb6bbc 100644 --- a/rteval/modules/measurement/__init__.py +++ b/rteval/modules/measurement/__init__.py @@ -38,6 +38,8 @@ class MeasurementModules(RtEvalModules): default=self._cfg.GetSection("measurement").setdefault("run-on-isolcpus", "false").lower() == "true", help="Include isolated CPUs in default cpulist") + grparser.add_argument('--idle-set', dest='measurement___idlestate', metavar='IDLESTATE', + default=None, help='Idle state depth to set on cpus running measurement modules') def Setup(self, modparams): -- 2.45.2