From 3db03791e841d7720b5f4680ba3b300af20c5a1e Mon Sep 17 00:00:00 2001 From: John Kacur Date: Mon, 6 May 2024 17:15:09 -0400 Subject: [PATCH] Add timerlat as a measurement module Add timerlat as a measurement module Resolves: RHEL-28058 Signed-off-by: John Kacur --- gating.yaml | 2 +- rteval-Add-relative-cpulists-for-loads.patch | 70 ++++ ...tla-timerlat-as-a-measurement-module.patch | 189 +++++++++ ...l-Add-summary-reporting-for-timerlat.patch | 178 +++++++++ ...t.py-Make-standalone-file-work-again.patch | 88 +++++ ...raw-histogram-data-for-an-existing-t.patch | 69 ++++ ...-output-work-with-new-dmiinfo-format.patch | 60 +++ ...nstead-of-setdefault-for-calculating.patch | 45 +++ ...t.py-Fix-the-description-in-the-xml-.patch | 45 +++ ...ictest.py-Fix-the-median-calculation.patch | 60 +++ ...ctest.py-Remove-unused-method-sample.patch | 32 ++ ...t.py-reduce-Fix-exception-with-missi.patch | 35 ++ ...t.py-Add-statistics-and-generate-xml.patch | 358 ++++++++++++++++++ rteval.spec | 33 +- 14 files changed, 1260 insertions(+), 4 deletions(-) create mode 100644 rteval-Add-relative-cpulists-for-loads.patch create mode 100644 rteval-Add-rtla-timerlat-as-a-measurement-module.patch create mode 100644 rteval-Add-summary-reporting-for-timerlat.patch create mode 100644 rteval-Cyclictest.py-Make-standalone-file-work-again.patch create mode 100644 rteval-Generate-raw-histogram-data-for-an-existing-t.patch create mode 100644 rteval-Make-output-work-with-new-dmiinfo-format.patch create mode 100644 rteval-Use-get-instead-of-setdefault-for-calculating.patch create mode 100644 rteval-cyclictest.py-Fix-the-description-in-the-xml-.patch create mode 100644 rteval-cyclictest.py-Fix-the-median-calculation.patch create mode 100644 rteval-cyclictest.py-Remove-unused-method-sample.patch create mode 100644 rteval-cyclictest.py-reduce-Fix-exception-with-missi.patch create mode 100644 rteval-timerlat.py-Add-statistics-and-generate-xml.patch diff --git a/gating.yaml b/gating.yaml index 648918d..4ca9235 100644 --- a/gating.yaml +++ b/gating.yaml @@ -1,6 +1,6 @@ --- !Policy product_versions: - - rhel-9 + - rhel-10 decision_context: osci_compose_gate rules: - !PassingTestCaseRule {test_case_name: osci.brew-build.tier0.functional} diff --git a/rteval-Add-relative-cpulists-for-loads.patch b/rteval-Add-relative-cpulists-for-loads.patch new file mode 100644 index 0000000..2f45c89 --- /dev/null +++ b/rteval-Add-relative-cpulists-for-loads.patch @@ -0,0 +1,70 @@ +From 636701e66cb98b979948b7a47320809c734e2a9e Mon Sep 17 00:00:00 2001 +From: Tomas Glozar +Date: Thu, 4 Apr 2024 10:14:38 +0200 +Subject: [PATCH] rteval: Add relative cpulists for loads + +Relative cpulists were added for measurements in 64ce7848 ("rteval: Add +relative cpulists for measurements"). It was observed since that this +feature would also be useful for load cpulists, for example when the +measurements are performed externally and rteval is only used to run +loads. + +Add support for relative cpulists also for loads. This works the same +way as for measurements using parse_cpulist_from_config, only difference +is there is no --loads-run-on-isolcpus option. That is, --loads-cpulist +now also takes lists with addition (+) and removal (-) of CPUs against +the default list, e.g. +0,1,-7,8. + +Signed-off-by: Tomas Glozar +Signed-off-by: John Kacur +--- + rteval-cmd | 17 ++++++++++------- + 1 file changed, 10 insertions(+), 7 deletions(-) + +diff --git a/rteval-cmd b/rteval-cmd +index ed13af3..c72bc61 100755 +--- a/rteval-cmd ++++ b/rteval-cmd +@@ -331,29 +331,32 @@ if __name__ == '__main__': + + ldcfg = config.GetSection('loads') + msrcfg = config.GetSection('measurement') ++ # Remember if cpulists were explicitly set by the user before running ++ # parse_cpulist_from_config, which generates default value for them + msrcfg_cpulist_present = msrcfg.cpulist != "" +- # Parse measurement cpulist using parse_cpulist_from_config to account for run-on-isolcpus +- # and relative cpusets ++ ldcfg_cpulist_present = ldcfg.cpulist != "" ++ # Parse cpulists using parse_cpulist_from_config to account for ++ # run-on-isolcpus and relative cpusets + cpulist = parse_cpulist_from_config(msrcfg.cpulist, msrcfg.run_on_isolcpus) + if msrcfg_cpulist_present and not cpulist_utils.is_relative(msrcfg.cpulist) and msrcfg.run_on_isolcpus: + logger.log(Log.WARN, "ignoring --measurement-run-on-isolcpus, since cpulist is specified") + msrcfg.cpulist = collapse_cpulist(cpulist) +- if ldcfg.cpulist: +- ldcfg.cpulist = remove_offline(ldcfg.cpulist) ++ cpulist = parse_cpulist_from_config(ldcfg.cpulist) ++ ldcfg.cpulist = collapse_cpulist(cpulist) + # if we only specified one set of cpus (loads or measurement) + # default the other to the inverse of the specified list +- if not ldcfg.cpulist and msrcfg_cpulist_present: ++ if not ldcfg_cpulist_present and msrcfg_cpulist_present: + tmplist = expand_cpulist(msrcfg.cpulist) + tmplist = SysTopology().invert_cpulist(tmplist) + tmplist = cpulist_utils.online_cpulist(tmplist) + ldcfg.cpulist = collapse_cpulist(tmplist) +- if not msrcfg_cpulist_present and ldcfg.cpulist: ++ if not msrcfg_cpulist_present and ldcfg_cpulist_present: + tmplist = expand_cpulist(ldcfg.cpulist) + tmplist = SysTopology().invert_cpulist(tmplist) + tmplist = cpulist_utils.online_cpulist(tmplist) + msrcfg.cpulist = collapse_cpulist(tmplist) + +- if ldcfg.cpulist: ++ if ldcfg_cpulist_present: + logger.log(Log.DEBUG, f"loads cpulist: {ldcfg.cpulist}") + # if --onlyload is specified msrcfg.cpulist is unused + if msrcfg_cpulist_present and not rtevcfg.onlyload: +-- +2.44.0 + diff --git a/rteval-Add-rtla-timerlat-as-a-measurement-module.patch b/rteval-Add-rtla-timerlat-as-a-measurement-module.patch new file mode 100644 index 0000000..50e7ff7 --- /dev/null +++ b/rteval-Add-rtla-timerlat-as-a-measurement-module.patch @@ -0,0 +1,189 @@ +From 393c95ee69ec98816a26aa0583f6b1cac4acb5e7 Mon Sep 17 00:00:00 2001 +From: John Kacur +Date: Fri, 12 Apr 2024 14:40:37 -0400 +Subject: [PATCH 02/13] rteval: Add rtla timerlat as a measurement module + +This is the first step to adding timerlat as a measurement module +With this change you can run timerlat as a standalone rteval file like +this (as root) + +python rteval/modules/measurement/timerlat.py + +You can also modify your rteval.conf to list timerlat in the +[measurement] section, for example like this + +[measurement] +cyclictest: module +timerlat: module + +and then both measurement moduels will be run from rteval, for example + +rteval -D -d5m --measurement-cpulist=1-5 + +Will run rteval with Debug info, for 5m and cyclictest and timerlat will +run on cpus 1-5 and load modules will run on the other available cpus. + +Currently MakeReport just prints to standard out the same information +that timerlat outputs, in otherwords, there is no processing into xml +yet. Also, there is no way to invoke tracing at the time, but that will +be added soon! + +Signed-off-by: John Kacur +--- + rteval-cmd | 1 + + rteval/modules/measurement/timerlat.py | 131 +++++++++++++++++++++++++ + 2 files changed, 132 insertions(+) + create mode 100644 rteval/modules/measurement/timerlat.py + +diff --git a/rteval-cmd b/rteval-cmd +index c72bc614ad78..5cb6d7a44523 100755 +--- a/rteval-cmd ++++ b/rteval-cmd +@@ -247,6 +247,7 @@ if __name__ == '__main__': + if not config.HasSection('measurement'): + config.AppendConfig('measurement', { + 'cyclictest' : 'module', ++ 'timerlat' : 'module', + 'sysstat' : 'module'}) + + # Prepare log levels before loading modules, not to have unwanted log messages +diff --git a/rteval/modules/measurement/timerlat.py b/rteval/modules/measurement/timerlat.py +new file mode 100644 +index 000000000000..d4e78de8d2a2 +--- /dev/null ++++ b/rteval/modules/measurement/timerlat.py +@@ -0,0 +1,131 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Copyright 2024 John Kacur ++# ++""" timerlat.py - objectd to manage rtla timerlat """ ++import os ++import subprocess ++import signal ++import time ++import tempfile ++import libxml2 ++from rteval.Log import Log ++from rteval.modules import rtevalModulePrototype ++from rteval.systopology import cpuinfo, SysTopology ++from rteval.cpulist_utils import expand_cpulist, collapse_cpulist ++ ++class Timerlat(rtevalModulePrototype): ++ """ measurement modules for rteval """ ++ def __init__(self, config, logger=None): ++ rtevalModulePrototype.__init__(self, 'measurement', 'timerlat', logger) ++ ++ self.__cfg = config ++ ++ self.__numanodes = int(self.__cfg.setdefault('numanodes', 0)) ++ self.__priority = int(self.__cfg.setdefault('priority', 95)) ++ ++ self.__cpulist = self.__cfg.setdefault('cpulist', "") ++ self.__cpus = [str(c) for c in expand_cpulist(self.__cpulist)] ++ self.__numcores = len(self.__cpus) ++ ++ self.__timerlat_out = None ++ self.__timerlat_err = None ++ self.__started = False ++ self._log(Log.DEBUG, f"system using {self.__numcores} cpu cores") ++ ++ ++ def _WorkloadSetup(self): ++ self.__timerlat_process = None ++ ++ def _WorkloadBuild(self): ++ self._setReady() ++ ++ def _WorkloadPrepare(self): ++ self.__cmd = ['rtla', 'timerlat', 'hist', '-P', f'f:{int(self.__priority)}', '-u'] ++ self.__cmd.append(f'-c{self.__cpulist}') ++ self._log(Log.DEBUG, f'self.__cmd = {self.__cmd}') ++ self.__timerlat_out = tempfile.SpooledTemporaryFile(mode='w+b') ++ self.__timerlat_err = tempfile.SpooledTemporaryFile(mode='w+b') ++ ++ def _WorkloadTask(self): ++ if self.__started: ++ return ++ ++ self._log(Log.DEBUG, f'starting with cmd: {" ".join(self.__cmd)}') ++ ++ self.__timerlat_out.seek(0) ++ self.__timerlat_err.seek(0) ++ try: ++ self.__timerlat_process = subprocess.Popen(self.__cmd, ++ stdout=self.__timerlat_out, ++ stderr=self.__timerlat_err, ++ stdin=None) ++ self.__started = True ++ except OSError: ++ self.__started = False ++ ++ def WorkloadAlive(self): ++ if self.__started: ++ return self.__timerlat_process.poll() is None ++ return False ++ ++ def _WorkloadCleanup(self): ++ if not self.__started: ++ return ++ while self.__timerlat_process.poll() is None: ++ self._log(Log.DEBUG, "Sending SIGINT") ++ os.kill(self.__timerlat_process.pid, signal.SIGINT) ++ time.sleep(2) ++ ++ self._setFinished() ++ self.__started = False ++ ++ def MakeReport(self): ++ self.__timerlat_out.seek(0) ++ for line in self.__timerlat_out: ++ line = bytes.decode(line) ++ print(line) ++ self.__timerlat_out.close() ++ ++ ++def ModuleInfo(): ++ """ Required measurement module information """ ++ return {"parallel": True, ++ "loads": True} ++ ++def ModuleParameters(): ++ """ default parameters """ ++ return {"priority": {"descr": "Run rtla timerlat with this priority", ++ "default": 95, ++ "metavar": "PRIO" } ++ } ++ ++def create(params, logger): ++ """ Instantiate a Timerlat measurement module object""" ++ return Timerlat(params, logger) ++ ++if __name__ == '__main__': ++ from rteval.rtevalConfig import rtevalConfig ++ ++ l = Log() ++ l.SetLogVerbosity(Log.INFO|Log.DEBUG|Log.ERR|Log.WARN) ++ ++ cfg = rtevalConfig({}, logger=l) ++ prms = {} ++ modprms = ModuleParameters() ++ for c, p in list(modprms.items()): ++ prms[c] = p['default'] ++ cfg.AppendConfig('timerlat', prms) ++ ++ cfg_tl = cfg.GetSection('timerlat') ++ cfg_tl.cpulist = collapse_cpulist(SysTopology().online_cpus()) ++ ++ RUNTIME = 10 ++ ++ tl = Timerlat(cfg_tl, l) ++ tl._WorkloadSetup() ++ tl._WorkloadPrepare() ++ tl._WorkloadTask() ++ time.sleep(RUNTIME) ++ tl._WorkloadCleanup() ++ tl.MakeReport() +-- +2.44.0 + diff --git a/rteval-Add-summary-reporting-for-timerlat.patch b/rteval-Add-summary-reporting-for-timerlat.patch new file mode 100644 index 0000000..2a94208 --- /dev/null +++ b/rteval-Add-summary-reporting-for-timerlat.patch @@ -0,0 +1,178 @@ +From 20fbcc1547d77a77a3e333bac28b7f28632d8707 Mon Sep 17 00:00:00 2001 +From: John Kacur +Date: Fri, 26 Apr 2024 14:45:48 -0400 +Subject: [PATCH 08/13] rteval: Add summary reporting for timerlat + +This adds an rteval section to the xsl file and generates a timerlat +report at the end of a run. To use it edit your rteval.conf file +to comment out cyclictest and uncomment timerlat + +Another interesting thing you can do is uncomment both of them and +get a report from both in one run. + +You can also use this with the summary report, for example + +rteval -Z rteval-20240426-3/summary.xml + +will work with timerlat now. (or both cyclictest and timerlat at the +same time.) + +Signed-off-by: John Kacur +--- + rteval.conf | 1 + + rteval/rteval_text.xsl | 103 +++++++++++++++++++++++++++++++++++++++-- + 2 files changed, 100 insertions(+), 4 deletions(-) + +diff --git a/rteval.conf b/rteval.conf +index 4c32fcf4d842..601410b51c28 100644 +--- a/rteval.conf ++++ b/rteval.conf +@@ -7,6 +7,7 @@ report_interval: 600 + + [measurement] + cyclictest: module ++# timerlat: module + + [loads] + kcompile: module +diff --git a/rteval/rteval_text.xsl b/rteval/rteval_text.xsl +index f526526d4d49..1e9c0f0d26c5 100644 +--- a/rteval/rteval_text.xsl ++++ b/rteval/rteval_text.xsl +@@ -201,11 +201,11 @@ + + + +- ++ + + + +- ++ + + Latency test + +@@ -237,7 +237,7 @@ + + + +- ++ + + CPU core + +@@ -300,6 +300,101 @@ + + + ++ ++ ++ Latency test ++ ++ Started: ++ ++ ++ ++ Stopped: ++ ++ ++ ++ Command: ++ ++ ++ ++ ++ ++ System: ++ ++ ++ ++ Statistics: ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ CPU core ++ ++ Priority: ++ ++ ++ Statistics: ++ ++ ++ ++ ++ ++ ++ ++ Samples: ++ ++ ++ ++ ++ Mean: ++ ++ ++ ++ ++ Median: ++ ++ ++ ++ ++ Mode: ++ ++ ++ ++ ++ Range: ++ ++ ++ ++ ++ Min: ++ ++ ++ ++ ++ Max: ++ ++ ++ ++ ++ Mean Absolute Dev: ++ ++ ++ ++ ++ Std.dev: ++ ++ ++ ++ ++ ++ ++ + + + +@@ -340,7 +435,7 @@ + us + + +- ++ + + sysstat measurements + +-- +2.44.0 + diff --git a/rteval-Cyclictest.py-Make-standalone-file-work-again.patch b/rteval-Cyclictest.py-Make-standalone-file-work-again.patch new file mode 100644 index 0000000..fb6354f --- /dev/null +++ b/rteval-Cyclictest.py-Make-standalone-file-work-again.patch @@ -0,0 +1,88 @@ +From ea72c99ead85d1fcf156b9a3825a547ba7f4d0f8 Mon Sep 17 00:00:00 2001 +From: John Kacur +Date: Fri, 12 Apr 2024 14:34:11 -0400 +Subject: [PATCH 01/13] rteval: Cyclictest.py: Make standalone file work again + +Make standalone Cyclictest.py work again for testing purposes +- remove unused parse_cpulist_from_config +- Instead of "import as", use "from" and the requested functionality +- Obtain the default buckets from the ModuleParameters to use if a + number is not otherwise provided +- set the cpulist to "" if not otherwise provided +- add a few docstrings to functions +- obtain a default cpulist from online_cpus for the standalone test + +Signed-off-by: John Kacur +--- + rteval/modules/measurement/cyclictest.py | 19 ++++++++----------- + 1 file changed, 8 insertions(+), 11 deletions(-) + +diff --git a/rteval/modules/measurement/cyclictest.py b/rteval/modules/measurement/cyclictest.py +index 722422589d62..671426e13c4d 100644 +--- a/rteval/modules/measurement/cyclictest.py ++++ b/rteval/modules/measurement/cyclictest.py +@@ -16,10 +16,8 @@ import math + import libxml2 + from rteval.Log import Log + from rteval.modules import rtevalModulePrototype +-from rteval.systopology import cpuinfo, parse_cpulist_from_config +-import rteval.cpulist_utils as cpulist_utils +- +-expand_cpulist = cpulist_utils.expand_cpulist ++from rteval.systopology import cpuinfo, SysTopology ++from rteval.cpulist_utils import expand_cpulist, collapse_cpulist + + class RunData: + '''class to keep instance data from a cyclictest run''' +@@ -190,10 +188,11 @@ class Cyclictest(rtevalModulePrototype): + # Create a RunData object per CPU core + self.__numanodes = int(self.__cfg.setdefault('numanodes', 0)) + self.__priority = int(self.__cfg.setdefault('priority', 95)) +- self.__buckets = int(self.__cfg.setdefault('buckets', 2000)) ++ default_buckets = ModuleParameters()["buckets"]["default"] ++ self.__buckets = int(self.__cfg.setdefault('buckets', default_buckets)) + self.__numcores = 0 + self.__cyclicdata = {} +- self.__cpulist = self.__cfg.cpulist ++ self.__cpulist = self.__cfg.setdefault('cpulist', "") + self.__cpus = [str(c) for c in expand_cpulist(self.__cpulist)] + self.__numcores = len(self.__cpus) + +@@ -393,14 +392,13 @@ class Cyclictest(rtevalModulePrototype): + return rep_n + + +- + def ModuleInfo(): + return {"parallel": True, + "loads": True} + + +- + def ModuleParameters(): ++ """ default parameters """ + return {"interval": {"descr": "Base interval of the threads in microseconds", + "default": 100, + "metavar": "INTV_US"}, +@@ -421,6 +419,7 @@ def ModuleParameters(): + + + def create(params, logger): ++ """ Instantiate a Cyclictest measurement module object """ + return Cyclictest(params, logger) + + +@@ -438,9 +437,7 @@ if __name__ == '__main__': + cfg.AppendConfig('cyclictest', prms) + + cfg_ct = cfg.GetSection('cyclictest') +- cfg_ct.reportdir = "." +- cfg_ct.buckets = 200 +- # cfg_ct.breaktrace = 30 ++ cfg_ct.cpulist = collapse_cpulist(SysTopology().online_cpus()) + + runtime = 10 + +-- +2.44.0 + diff --git a/rteval-Generate-raw-histogram-data-for-an-existing-t.patch b/rteval-Generate-raw-histogram-data-for-an-existing-t.patch new file mode 100644 index 0000000..13d5e7a --- /dev/null +++ b/rteval-Generate-raw-histogram-data-for-an-existing-t.patch @@ -0,0 +1,69 @@ +From 843ff713340ab701aa8859ca8b3d8a776608175c Mon Sep 17 00:00:00 2001 +From: John Kacur +Date: Mon, 29 Apr 2024 10:47:41 -0400 +Subject: [PATCH 09/13] rteval: Generate raw histogram data for an existing + timerlat report + +Generate raw histogram data for an existing timerlat report. + +Signed-off-by: John Kacur +--- + rteval/rteval_histogram_raw.xsl | 29 ++++++++++++++++++++++++++++- + 1 file changed, 28 insertions(+), 1 deletion(-) + +diff --git a/rteval/rteval_histogram_raw.xsl b/rteval/rteval_histogram_raw.xsl +index 038419bf03b7..00b2be34f305 100644 +--- a/rteval/rteval_histogram_raw.xsl ++++ b/rteval/rteval_histogram_raw.xsl +@@ -12,15 +12,26 @@ + + + +- ++ + + + ++ ++ ++ ++ ++ + + + + + ++ ++ ++ ++ ++ ++ + + + +@@ -43,4 +54,20 @@ + + + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + +-- +2.44.0 + diff --git a/rteval-Make-output-work-with-new-dmiinfo-format.patch b/rteval-Make-output-work-with-new-dmiinfo-format.patch new file mode 100644 index 0000000..452011d --- /dev/null +++ b/rteval-Make-output-work-with-new-dmiinfo-format.patch @@ -0,0 +1,60 @@ +From 701ab9f5d50fb0e1308ad8ea163d49525d3d90af Mon Sep 17 00:00:00 2001 +From: Tomas Glozar +Date: Mon, 29 Apr 2024 10:47:20 +0200 +Subject: [PATCH 10/13] rteval: Make output work with new dmiinfo format + +Commit 43c45ba7 ("rteval: Implement initial dmidecode support") +re-introduced DMI information into rteval XML summary, but in a +different format, which is not correctly picked up in rteval_text.xsl +for formatting BIOS and vendor information into rteval text output. + +This lead to the information not being displayed correctly: + Model: - + BIOS version: (ver: , rev :, release date: ) + +Modify the relevant section of rteval_text.xsl to make it understand the +new format. Also, adjust the incorrect spacing around "rev". + +New output (in QEMU VM): + Model: QEMU - Standard PC (Q35 + ICH9, 2009) + BIOS version: SeaBIOS (ver: 1.16.3-1.fc39, rev: 0.0, release date: 04/01/2014) + +Signed-off-by: Tomas Glozar +Signed-off-by: John Kacur +--- + rteval/rteval_text.xsl | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +diff --git a/rteval/rteval_text.xsl b/rteval/rteval_text.xsl +index 1e9c0f0d26c5..2f03bda0bb55 100644 +--- a/rteval/rteval_text.xsl ++++ b/rteval/rteval_text.xsl +@@ -39,18 +39,18 @@ + + + Model: +- +- - ++ ++ - + + + BIOS version: +- ++ + (ver: +- +- , rev : +- ++ ++ , rev: ++ + , release date: +- ++ + ) + + +-- +2.44.0 + diff --git a/rteval-Use-get-instead-of-setdefault-for-calculating.patch b/rteval-Use-get-instead-of-setdefault-for-calculating.patch new file mode 100644 index 0000000..195f6bf --- /dev/null +++ b/rteval-Use-get-instead-of-setdefault-for-calculating.patch @@ -0,0 +1,45 @@ +From a5f3289e2ee6577cbbb78661bea58b821942ba56 Mon Sep 17 00:00:00 2001 +From: John Kacur +Date: Thu, 2 May 2024 13:09:01 -0400 +Subject: [PATCH 12/13] rteval: Use get instead of setdefault for calculating + range + +As Crystal Wood points out, there is no +need to setdefault when calculating the range, just use get +if there is no value. + +Signed-off-by: John Kacur +--- + rteval/modules/measurement/cyclictest.py | 2 +- + rteval/modules/measurement/timerlat.py | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/rteval/modules/measurement/cyclictest.py b/rteval/modules/measurement/cyclictest.py +index 925065367eaf..3301e1b45e11 100644 +--- a/rteval/modules/measurement/cyclictest.py ++++ b/rteval/modules/measurement/cyclictest.py +@@ -115,7 +115,7 @@ class RunData: + low = i + break + high = keys[-1] +- while high and self.__samples.setdefault(high, 0) == 0: ++ while high and self.__samples.get(high, 0) == 0: + high -= 1 + self.__range = high - low + +diff --git a/rteval/modules/measurement/timerlat.py b/rteval/modules/measurement/timerlat.py +index 9fa931043e40..e8345fab1ad7 100644 +--- a/rteval/modules/measurement/timerlat.py ++++ b/rteval/modules/measurement/timerlat.py +@@ -106,7 +106,7 @@ class TLRunData: + low = i + break + high = keys[-1] +- while high and self.__samples.setdefault(high, 0) == 0: ++ while high and self.__samples.get(high, 0) == 0: + high -= 1 + self.__range = high - low + +-- +2.44.0 + diff --git a/rteval-cyclictest.py-Fix-the-description-in-the-xml-.patch b/rteval-cyclictest.py-Fix-the-description-in-the-xml-.patch new file mode 100644 index 0000000..8969421 --- /dev/null +++ b/rteval-cyclictest.py-Fix-the-description-in-the-xml-.patch @@ -0,0 +1,45 @@ +From 0a431278cc681270b54efea4eebce9571c237624 Mon Sep 17 00:00:00 2001 +From: John Kacur +Date: Thu, 18 Apr 2024 12:39:33 -0400 +Subject: [PATCH 03/13] rteval: cyclictest.py: Fix the description in the xml + report + +If the __type is 'system' we should add the description to the xml +The output should be something like this + + + +Before this fix it was + + + +Signed-off-by: John Kacur +--- + rteval/modules/measurement/cyclictest.py | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/rteval/modules/measurement/cyclictest.py b/rteval/modules/measurement/cyclictest.py +index 671426e13c4d..48b86a03cbfc 100644 +--- a/rteval/modules/measurement/cyclictest.py ++++ b/rteval/modules/measurement/cyclictest.py +@@ -25,7 +25,7 @@ class RunData: + self.__id = coreid + self.__type = datatype + self.__priority = int(priority) +- self.__description = '' ++ self.description = '' + # histogram of data + self.__samples = {} + self.__numsamples = 0 +@@ -130,7 +130,7 @@ class RunData: + def MakeReport(self): + rep_n = libxml2.newNode(self.__type) + if self.__type == 'system': +- rep_n.newProp('description', self.__description) ++ rep_n.newProp('description', self.description) + else: + rep_n.newProp('id', str(self.__id)) + rep_n.newProp('priority', str(self.__priority)) +-- +2.44.0 + diff --git a/rteval-cyclictest.py-Fix-the-median-calculation.patch b/rteval-cyclictest.py-Fix-the-median-calculation.patch new file mode 100644 index 0000000..ce0c8e3 --- /dev/null +++ b/rteval-cyclictest.py-Fix-the-median-calculation.patch @@ -0,0 +1,60 @@ +From 071bd1be404f1b5c71f8b2f2d9fa1a85bd485ce9 Mon Sep 17 00:00:00 2001 +From: John Kacur +Date: Thu, 25 Apr 2024 13:48:05 -0400 +Subject: [PATCH 05/13] rteval: cyclictest.py: Fix the median calculation + +Fix the calculation of the median in rteval for cyclictest + +Signed-off-by: John Kacur +--- + rteval/modules/measurement/cyclictest.py | 20 ++++++++++++++------ + 1 file changed, 14 insertions(+), 6 deletions(-) + +diff --git a/rteval/modules/measurement/cyclictest.py b/rteval/modules/measurement/cyclictest.py +index 1ba8b8b2323c..cef73abd1b4b 100644 +--- a/rteval/modules/measurement/cyclictest.py ++++ b/rteval/modules/measurement/cyclictest.py +@@ -80,26 +80,34 @@ class RunData: + return + + self._log(Log.INFO, f"reducing {self.__id}") +- total = 0 ++ total = 0 # total number of samples ++ total_us = 0 + keys = list(self.__samples.keys()) + keys.sort() + +- mid = self.__numsamples / 2 ++ # if numsamples is odd, then + 1 gives us the actual mid ++ # if numsamples is even, we avg mid and mid + 1, so we actually ++ # want to know mid + 1 since we will combine it with mid and ++ # the lastkey if the last key is at the end of a previous bucket ++ mid = int(self.__numsamples / 2) + 1 + + # mean, mode, and median + occurances = 0 + lastkey = -1 + for i in keys: +- if mid > total and mid <= (total + self.__samples[i]): +- if self.__numsamples & 1 and mid == total+1: ++ if mid > total and mid <= total + self.__samples[i]: ++ # Test if numsamples is even and if mid+1 is the next bucket ++ if self.__numsamples & 1 != 0 and mid == total+1: + self.__median = (lastkey + i) / 2 + else: + self.__median = i +- total += (i * self.__samples[i]) ++ lastkey = i ++ total += self.__samples[i] ++ total_us += (i * self.__samples[i]) + if self.__samples[i] > occurances: + occurances = self.__samples[i] + self.__mode = i +- self.__mean = float(total) / float(self.__numsamples) ++ self.__mean = float(total_us) / float(self.__numsamples) + + # range + for i in keys: +-- +2.44.0 + diff --git a/rteval-cyclictest.py-Remove-unused-method-sample.patch b/rteval-cyclictest.py-Remove-unused-method-sample.patch new file mode 100644 index 0000000..0c77182 --- /dev/null +++ b/rteval-cyclictest.py-Remove-unused-method-sample.patch @@ -0,0 +1,32 @@ +From 6a185e50bb9b0903e034a2361456b5742e60c47b Mon Sep 17 00:00:00 2001 +From: John Kacur +Date: Fri, 19 Apr 2024 12:17:31 -0400 +Subject: [PATCH 04/13] rteval: cyclictest.py: Remove unused method sample + +Remove the unused method sample() in class RunData + +Signed-off-by: John Kacur +--- + rteval/modules/measurement/cyclictest.py | 6 ------ + 1 file changed, 6 deletions(-) + +diff --git a/rteval/modules/measurement/cyclictest.py b/rteval/modules/measurement/cyclictest.py +index 48b86a03cbfc..1ba8b8b2323c 100644 +--- a/rteval/modules/measurement/cyclictest.py ++++ b/rteval/modules/measurement/cyclictest.py +@@ -61,12 +61,6 @@ class RunData: + if value < self.__min: + self.__min = value + +- def sample(self, value): +- self.__samples[value] += self.__samples.setdefault(value, 0) + 1 +- self.update_max(value) +- self.update_min(value) +- self.__numsamples += 1 +- + def bucket(self, index, value): + self.__samples[index] = self.__samples.setdefault(index, 0) + value + if value: +-- +2.44.0 + diff --git a/rteval-cyclictest.py-reduce-Fix-exception-with-missi.patch b/rteval-cyclictest.py-reduce-Fix-exception-with-missi.patch new file mode 100644 index 0000000..d3fcd62 --- /dev/null +++ b/rteval-cyclictest.py-reduce-Fix-exception-with-missi.patch @@ -0,0 +1,35 @@ +From eb344bffd1525bbb8cd95fb4f4501e467cdc21ce Mon Sep 17 00:00:00 2001 +From: Crystal Wood +Date: Wed, 1 May 2024 15:09:22 -0500 +Subject: [PATCH 11/13] rteval: cyclictest.py: reduce: Fix exception with + missing samples + +If cyclictest omits a line of zeroes rather than printing it, +__samples[high] will not exist (rather than be zero). Handle this +in preparation for cyclictest doing this. + +Note that get() seems more suitable than setdefault(), but the latter +is used in order to match the code in timerlat.py. + +Signed-off-by: Crystal Wood +Signed-off-by: John Kacur +--- + rteval/modules/measurement/cyclictest.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/rteval/modules/measurement/cyclictest.py b/rteval/modules/measurement/cyclictest.py +index cef73abd1b4b..925065367eaf 100644 +--- a/rteval/modules/measurement/cyclictest.py ++++ b/rteval/modules/measurement/cyclictest.py +@@ -115,7 +115,7 @@ class RunData: + low = i + break + high = keys[-1] +- while high and self.__samples[high] == 0: ++ while high and self.__samples.setdefault(high, 0) == 0: + high -= 1 + self.__range = high - low + +-- +2.44.0 + diff --git a/rteval-timerlat.py-Add-statistics-and-generate-xml.patch b/rteval-timerlat.py-Add-statistics-and-generate-xml.patch new file mode 100644 index 0000000..7f1749f --- /dev/null +++ b/rteval-timerlat.py-Add-statistics-and-generate-xml.patch @@ -0,0 +1,358 @@ +From 53248cbad7b0b353e31a18b40181f38da0a721aa Mon Sep 17 00:00:00 2001 +From: John Kacur +Date: Thu, 25 Apr 2024 13:59:27 -0400 +Subject: [PATCH 06/13] rteval: timerlat.py Add statistics and generate xml + +This adds calculating statitics such as samples (count), min, max, mean, +median, mode, range, absolute and standard deviations to xml + +Currently it combines the kernel thread, IRQs and userspace threads into +one statistic like it does with cyclictest, but in the future we can +separate this out too. + +To see this functioning from git, do this, + +su -c 'python rteval/modules/measurement/timerlat.py ' + +Signed-off-by: John Kacur +--- + rteval/modules/measurement/timerlat.py | 266 ++++++++++++++++++++++++- + 1 file changed, 259 insertions(+), 7 deletions(-) + +diff --git a/rteval/modules/measurement/timerlat.py b/rteval/modules/measurement/timerlat.py +index d4e78de8d2a2..9fa931043e40 100644 +--- a/rteval/modules/measurement/timerlat.py ++++ b/rteval/modules/measurement/timerlat.py +@@ -8,12 +8,170 @@ import subprocess + import signal + import time + import tempfile ++import math ++import sys + import libxml2 + from rteval.Log import Log + from rteval.modules import rtevalModulePrototype + from rteval.systopology import cpuinfo, SysTopology + from rteval.cpulist_utils import expand_cpulist, collapse_cpulist + ++class TLRunData: ++ ''' class to store instance data from a timerlat run ''' ++ def __init__(self, coreid, datatype, priority, logfnc): ++ self.__id = coreid ++ self.__type = datatype ++ self.__priority = int(priority) ++ self.description = '' ++ self._log = logfnc ++ self.duration = '' ++ # histogram data, irqs, kernel threads and user threads per core ++ self.irqs = {} ++ self.thrs = {} ++ self.usrs = {} ++ self.__samples = {} ++ self.__numsamples = 0 ++ self.min = 100000000 ++ self.max = 0 ++ self.__stddev = 0.0 ++ self.__mean = 0.0 ++ self.__mode = 0.0 ++ self.__median = 0.0 ++ self.__range = 0.0 ++ ++ def update_max(self, value): ++ """ highest bucket with a value """ ++ if value > self.max: ++ self.max = value ++ ++ def update_min(self, value): ++ """ lowest bucket with a value """ ++ if value < self.min: ++ self.min = value ++ ++ def bucket(self, index, val1, val2, val3): ++ """ Store results index=bucket number, val1=IRQ, val2=thr, val3=usr """ ++ values = val1 + val2 + val3 ++ self.__samples[index] = self.__samples.setdefault(index, 0) + values ++ self.irqs[index] = val1 ++ self.thrs[index] = val2 ++ self.usrs[index] = val3 ++ if values: ++ self.update_max(index) ++ self.update_min(index) ++ self.__numsamples += values ++ ++ def reduce(self): ++ """ Calculate statistics """ ++ # Check to see if we have any samples. If there are 1 or 0, return ++ if self.__numsamples <= 1: ++ self._log(Log.DEBUG, f"skipping {self.__id} ({self.__numsamples} sampples)") ++ self.__mad = 0 ++ self.__stddev = 0 ++ return ++ ++ self._log(Log.INFO, f"reducing {self.__id}") ++ total = 0 # total number of samples ++ total_us = 0 ++ keys = list(self.__samples.keys()) ++ keys.sort() ++ ++ # if numsamples is odd, then + 1 gives us the actual mid ++ # if numsamples is even, we avg mid and mid + 1, so we actually ++ # want to know mid + 1 since we will combine it with mid and ++ # the lastkey if the last key is at the end of a previous bucket ++ mid = int(self.__numsamples / 2) + 1 ++ ++ # mean, mode and median ++ occurances = 0 ++ lastkey = -1 ++ for i in keys: ++ if mid > total and mid <= total + self.__samples[i]: ++ # Test if numsamples is even and if mid+1 is the next bucket ++ if self.__numsamples & 1 != 0 and mid == total+1: ++ self.__median = (lastkey + i) / 2 ++ else: ++ self.__median = i ++ lastkey = i ++ total += self.__samples[i] ++ total_us += i * self.__samples[i] ++ if self.__samples[i] > occurances: ++ occurances = self.__samples[i] ++ self.__mode = i ++ self.__mean = float(total_us) / float(self.__numsamples) ++ ++ # range ++ for i in keys: ++ if self.__samples[i]: ++ low = i ++ break ++ high = keys[-1] ++ while high and self.__samples.setdefault(high, 0) == 0: ++ high -= 1 ++ self.__range = high - low ++ ++ # Mean Absolute Deviation and Standard Deviation ++ madsum = 0 ++ varsum = 0 ++ for i in keys: ++ madsum += float(abs(float(i) - self.__mean) * self.__samples[i]) ++ varsum += float(((float(i) - self.__mean) ** 2) * self.__samples[i]) ++ self.__mad = madsum / self.__numsamples ++ self.__stddev = math.sqrt(varsum / (self.__numsamples - 1)) ++ ++ ++ def MakeReport(self): ++ rep_n = libxml2.newNode(self.__type) ++ ++ if self.__type == 'system': ++ rep_n.newProp('description', self.description) ++ else: ++ rep_n.newProp('id', str(self.__id)) ++ rep_n.newProp('priority', str(self.__priority)) ++ ++ stat_n = rep_n.newChild(None, 'statistics', None) ++ stat_n.newTextChild(None, 'samples', str(self.__numsamples)) ++ ++ if self.__numsamples > 0: ++ n = stat_n.newTextChild(None, 'minimum', str(self.min)) ++ n.newProp('unit', 'us') ++ ++ n = stat_n.newTextChild(None, 'maximum', str(self.max)) ++ n.newProp('unit', 'us') ++ ++ n = stat_n.newTextChild(None, 'median', str(self.__median)) ++ n.newProp('unit', 'us') ++ ++ n = stat_n.newTextChild(None, 'mode', str(self.__mode)) ++ n.newProp('unit', 'us') ++ ++ n = stat_n.newTextChild(None, 'range', str(self.__range)) ++ n.newProp('unit', 'us') ++ ++ n = stat_n.newTextChild(None, 'mean', str(self.__mean)) ++ n.newProp('unit', 'us') ++ ++ n = stat_n.newTextChild(None, 'mean_absolute_deviation', str(self.__mad)) ++ n.newProp('unit', 'us') ++ ++ n = stat_n.newTextChild(None, 'standard_deviation', str(self.__stddev)) ++ n.newProp('unit', 'us') ++ ++ hist_n = rep_n.newChild(None, 'histogram', None) ++ hist_n.newProp('nbuckets', str(len(self.__samples))) ++ ++ keys = list(self.__samples.keys()) ++ keys.sort() ++ for k in keys: ++ if self.__samples[k] == 0: ++ # Don't report buckets without any samples ++ continue ++ b_n = hist_n.newChild(None, 'bucket', None) ++ b_n.newProp('index', str(k)) ++ b_n.newProp('value', str(self.__samples[k])) ++ ++ return rep_n ++ + class Timerlat(rtevalModulePrototype): + """ measurement modules for rteval """ + def __init__(self, config, logger=None): +@@ -23,6 +181,8 @@ class Timerlat(rtevalModulePrototype): + + self.__numanodes = int(self.__cfg.setdefault('numanodes', 0)) + self.__priority = int(self.__cfg.setdefault('priority', 95)) ++ default_buckets = ModuleParameters()["buckets"]["default"] ++ self.__buckets = int(self.__cfg.setdefault('buckets', default_buckets)) + + self.__cpulist = self.__cfg.setdefault('cpulist', "") + self.__cpus = [str(c) for c in expand_cpulist(self.__cpulist)] +@@ -31,6 +191,20 @@ class Timerlat(rtevalModulePrototype): + self.__timerlat_out = None + self.__timerlat_err = None + self.__started = False ++ ++ # Create a TLRunData object for each core we'll measure ++ info = cpuinfo() ++ self.__timerlatdata = {} ++ for core in self.__cpus: ++ self.__timerlatdata[core] = TLRunData(core, 'core', self.__priority, ++ logfnc=self._log) ++ self.__timerlatdata[core].description = info[core]['model name'] ++ ++ # Create a TLRunData object for the overall system ++ self.__timerlatdata['system'] = TLRunData('system', 'system', ++ self.__priority, ++ logfnc=self._log) ++ self.__timerlatdata['system'].description = (f"({self.__numcores} cores) ") + info['0']['model name'] + self._log(Log.DEBUG, f"system using {self.__numcores} cpu cores") + + +@@ -43,6 +217,7 @@ class Timerlat(rtevalModulePrototype): + def _WorkloadPrepare(self): + self.__cmd = ['rtla', 'timerlat', 'hist', '-P', f'f:{int(self.__priority)}', '-u'] + self.__cmd.append(f'-c{self.__cpulist}') ++ self.__cmd.append(f'-E{self.__buckets}') + self._log(Log.DEBUG, f'self.__cmd = {self.__cmd}') + self.__timerlat_out = tempfile.SpooledTemporaryFile(mode='w+b') + self.__timerlat_err = tempfile.SpooledTemporaryFile(mode='w+b') +@@ -77,16 +252,82 @@ class Timerlat(rtevalModulePrototype): + os.kill(self.__timerlat_process.pid, signal.SIGINT) + time.sleep(2) + +- self._setFinished() +- self.__started = False +- +- def MakeReport(self): ++ # Parse histogram output + self.__timerlat_out.seek(0) + for line in self.__timerlat_out: + line = bytes.decode(line) +- print(line) ++ ++ # Skip any blank lines ++ if not line: ++ continue ++ ++ if line.startswith('#'): ++ if line.startswith('# Duration:'): ++ duration = line.split()[2] ++ duration += line.split()[3] ++ self.__timerlatdata['system'].duration = duration ++ continue ++ elif line.startswith('Index'): ++ #print(line) ++ continue ++ elif line.startswith('over:'): ++ #print(line) ++ continue ++ elif line.startswith('count:'): ++ #print(line) ++ continue ++ elif line.startswith('min:'): ++ #print(line) ++ continue ++ elif line.startswith('avg:'): ++ #print(line) ++ continue ++ elif line.startswith('max:'): ++ #print(line) ++ continue ++ else: ++ pass ++ #print(line) ++ ++ vals = line.split() ++ if not vals: ++ # If we don't have any values, don't try parsing ++ continue ++ try: ++ # The index corresponds to the bucket number ++ index = int(vals[0]) ++ except: ++ self._log(Log.DEBUG, f'timerlat: unexpected output: {line}') ++ continue ++ ++ for i, core in enumerate(self.__cpus): ++ self.__timerlatdata[core].bucket(index, int(vals[i*3+1]), ++ int(vals[i*3+2]), ++ int(vals[i*3+3])) ++ self.__timerlatdata['system'].bucket(index, int(vals[i*3+1]), ++ int(vals[i*3+2]), ++ int(vals[i*3+3])) ++ # Generate statistics for each RunData object ++ for n in list(self.__timerlatdata.keys()): ++ self.__timerlatdata[n].reduce() ++ + self.__timerlat_out.close() + ++ self._setFinished() ++ self.__started = False ++ ++ def MakeReport(self): ++ rep_n = libxml2.newNode('timerlat') ++ rep_n.newProp('command_line', ' '.join(self.__cmd)) ++ ++ rep_n.addChild(self.__timerlatdata['system'].MakeReport()) ++ for thr in self.__cpus: ++ if str(thr) not in self.__timerlatdata: ++ continue ++ rep_n.addChild(self.__timerlatdata[str(thr)].MakeReport()) ++ ++ return rep_n ++ + + def ModuleInfo(): + """ Required measurement module information """ +@@ -97,7 +338,10 @@ def ModuleParameters(): + """ default parameters """ + return {"priority": {"descr": "Run rtla timerlat with this priority", + "default": 95, +- "metavar": "PRIO" } ++ "metavar": "PRIO" }, ++ "buckets": {"descr": "Number of buckets", ++ "default": 3500, ++ "metavar": "NUM"} + } + + def create(params, logger): +@@ -107,6 +351,10 @@ def create(params, logger): + if __name__ == '__main__': + from rteval.rtevalConfig import rtevalConfig + ++ if os.getuid() != 0: ++ print("Must be root to run timerlat!") ++ sys.exit(1) ++ + l = Log() + l.SetLogVerbosity(Log.INFO|Log.DEBUG|Log.ERR|Log.WARN) + +@@ -128,4 +376,8 @@ if __name__ == '__main__': + tl._WorkloadTask() + time.sleep(RUNTIME) + tl._WorkloadCleanup() +- tl.MakeReport() ++ rep_n = tl.MakeReport() ++ ++ xml = libxml2.newDoc('1.0') ++ xml.setRootElement(rep_n) ++ xml.saveFormatFileEnc('-', 'UTF-8', 1) +-- +2.44.0 + diff --git a/rteval.spec b/rteval.spec index 11378d8..b4e0c6f 100644 --- a/rteval.spec +++ b/rteval.spec @@ -1,6 +1,6 @@ Name: rteval Version: 3.7 -Release: 5%{?dist} +Release: 6%{?dist} Summary: Utility to evaluate system suitability for RT Linux Group: Development/Tools @@ -41,7 +41,18 @@ Patch6: rteval-Minor-improvements-to-CpuList-class.patch Patch7: rteval-Convert-CpuList-class-to-a-module.patch Patch8: rteval-Add-relative-cpulists-for-measurements.patch Patch9: rteval-Implement-initial-dmidecode-support.patch - +Patch10: rteval-Add-relative-cpulists-for-loads.patch +Patch11: rteval-Cyclictest.py-Make-standalone-file-work-again.patch +Patch12: rteval-Add-rtla-timerlat-as-a-measurement-module.patch +Patch13: rteval-cyclictest.py-Fix-the-description-in-the-xml-.patch +Patch14: rteval-cyclictest.py-Remove-unused-method-sample.patch +Patch15: rteval-cyclictest.py-Fix-the-median-calculation.patch +Patch16: rteval-timerlat.py-Add-statistics-and-generate-xml.patch +Patch17: rteval-Add-summary-reporting-for-timerlat.patch +Patch18: rteval-Generate-raw-histogram-data-for-an-existing-t.patch +Patch19: rteval-Make-output-work-with-new-dmiinfo-format.patch +Patch20: rteval-cyclictest.py-reduce-Fix-exception-with-missi.patch +Patch21: rteval-Use-get-instead-of-setdefault-for-calculating.patch %description The rteval script is a utility for measuring various aspects of @@ -63,6 +74,18 @@ to the screen. %patch 7 -p1 %patch 8 -p1 %patch 9 -p1 +%patch 10 -p1 +%patch 11 -p1 +%patch 12 -p1 +%patch 13 -p1 +%patch 14 -p1 +%patch 15 -p1 +%patch 16 -p1 +%patch 17 -p1 +%patch 18 -p1 +%patch 19 -p1 +%patch 20 -p1 +%patch 21 -p1 %build %{__python3} setup.py build @@ -83,7 +106,11 @@ to the screen. %{_bindir}/rteval %changelog -* Thu Apr 05 2024 Tomas Glozar - 3.7-5 +* Mon May 06 2024 John Kacur - 3.7-6 +- Add timerlat as a measurement module +Resolves: RHEL-28058 + +* Thu Apr 04 2024 Tomas Glozar - 3.7-5 - Replace python-dmidecode with initial dmidecode output parsing Resolves: RHEL-30158