rteval/rteval-Added-functionality-to-allow-user-to-set-the-.patch
Anubhav Shelat 372da58721 Add functionality to allow user to execute cpupower tool to
set idle state depth  while running rteval.
Disable latency trick by using --default-system option with
cyclictest when setting idle state depth.
Resolves: RHEL-37646

Signed-off-by: Anubhav Shelat <ashelat@redhat.com>
2024-08-06 15:23:51 -04:00

222 lines
8.4 KiB
Diff

From b3c96935c82b7410422ca2973398ac9d0272c844 Mon Sep 17 00:00:00 2001
From: Anubhav Shelat <ashelat@redhat.com>
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 <ashelat@redhat.com>
Signed-off-by: John Kacur <jkacur@redhat.com>
---
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 <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):
--
2.45.2