diff mbox series

[v6,1/2] rteval: Added functionality to allow user to set the cstate of specified cpus when running rteval

Message ID 20240802154636.1240064-1-ashelat@redhat.com
State New
Headers show
Series [v6,1/2] rteval: Added functionality to allow user to set the cstate of specified cpus when running rteval | expand

Commit Message

Anubhav Shelat Aug. 2, 2024, 3:46 p.m. UTC
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 <ashelat@redhat.com>
---
 rteval-cmd                             |  11 ++-
 rteval/cpupower.py                     | 125 +++++++++++++++++++++++++
 rteval/modules/__init__.py             |   2 +-
 rteval/modules/measurement/__init__.py |   2 +
 4 files changed, 138 insertions(+), 2 deletions(-)
 create mode 100644 rteval/cpupower.py

Comments

John Kacur Aug. 2, 2024, 6:01 p.m. UTC | #1
On Fri, 2 Aug 2024, Anubhav Shelat wrote:

> When running cyclictest in rteval, cyclictest automatically disables
> idle states. This means whenever the user sets the idle state of a cpu
> list using '--idle-set' it is overridden by cyclictest.
> 
> To fix this, the variable 'usingCpupower' is appended to the parameter
> dictionary that's passed to the Cyclictest measurement object which executes
> cyclictest in rteval.
> 
> If '--idle-set' is specified when running rteval,
> 'usingCpupower' is set to true and the '--default-system' option is
> appended to the cyclictest command, which will prevent cyclictest from
> disabling cstates.
> 
> Signed-off-by: Anubhav Shelat <ashelat@redhat.com>
> ---
>  rteval-cmd                               | 4 ++++
>  rteval/__init__.py                       | 1 +
>  rteval/modules/measurement/cyclictest.py | 3 +++
>  3 files changed, 8 insertions(+)
> 
> diff --git a/rteval-cmd b/rteval-cmd
> index 6e7d8f5d99cb..542d08fc67f0 100755
> --- a/rteval-cmd
> +++ b/rteval-cmd
> @@ -266,6 +266,10 @@ if __name__ == '__main__':
>                  | (rtevcfg.debugging and Log.DEBUG)
>          logger.SetLogVerbosity(loglev)
>  
> +        # check if cpupower is being used
> +        if sys.argv.count('--idle-set') > 0:
> +            rtevcfg.update({'usingCpupower': True})
> +
>          # Load modules
>          loadmods = LoadModules(config, logger=logger)
>          measuremods = MeasurementModules(config, logger=logger)
> diff --git a/rteval/__init__.py b/rteval/__init__.py
> index 4d3e0c23e5ab..8ded374d287e 100644
> --- a/rteval/__init__.py
> +++ b/rteval/__init__.py
> @@ -119,6 +119,7 @@ class RtEval(rtevalReport):
>                    'memsize':self._sysinfo.mem_get_size(),
>                    'numanodes':self._sysinfo.mem_get_numa_nodes(),
>                    'duration': float(self.__rtevcfg.duration),
> +                  'usingCpupower': self.__rtevcfg.usingCpupower
>                    }
>  
>          if self._loadmods:
> diff --git a/rteval/modules/measurement/cyclictest.py b/rteval/modules/measurement/cyclictest.py
> index d919058e927f..2e8f6f1870ed 100644
> --- a/rteval/modules/measurement/cyclictest.py
> +++ b/rteval/modules/measurement/cyclictest.py
> @@ -251,6 +251,9 @@ class Cyclictest(rtevalModulePrototype):
>          self.__cmd.append(f'-t{self.__numcores}')
>          self.__cmd.append(f'-a{self.__cpulist}')
>  
> +        if (self.__cfg.usingCpupower):
> +            self.__cmd.append('--default-system')
> +
>          if 'threads' in self.__cfg and self.__cfg.threads:
>              self.__cmd.append(f"-t{int(self.__cfg.threads)}")
>  
> -- 
> 2.45.2
> 
> 
> 
Signed-off-by: John Kacur <jkacur@redhat.com>
diff mbox series

Patch

diff --git a/rteval-cmd b/rteval-cmd
index 19c82a0b64b3..6e7d8f5d99cb 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
@@ -149,7 +150,7 @@  def parse_options(cfg, parser, cmdargs):
     parser.add_argument("--noload", dest="rteval___noload",
                         action="store_true", default=False,
                         help="only run the measurements (don't run loads)")
-
+
 
     if not cmdargs:
         cmdargs = ["--help"]
@@ -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 <ashelat@redhat.com>
+""" 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):