[PATCH 2/2] rteval: Add -L/--logging support to cyclictest measurement module
John Kacur <[email protected]> Tue, 2 Jun 2026 16:47:02 -0400
| Newsgroups | org.kernel.vger.linux-rt-users |
|---|---|
| Message-ID | <[email protected]> |
This implements file logging for the cyclictest measurement module, matching the functionality available in timerlat and load modules. When rteval is run with the -L/--logging flag, cyclictest will now save cyclictest's stdout to a file in workdir/logs/: - cyclictest.stdout - raw histogram output from cyclictest This is useful for: - Debugging cyclictest output parsing issues - Investigating unexpected behavior - Analyzing historical test data Changes: - Add _logging config flag (default: False) - Add __reportdir config to locate logs directory - Add __open_logfile() helper method (uses O_RDWR to support read+write) - Conditionally use real log files vs temp files in _WorkloadPrepare() - Handle reading from both file descriptors and temp files in _WorkloadCleanup() - Close files appropriately based on type (os.close vs .close()) - Conditional seek only for temp files (file descriptors start at position 0) Assisted-by: Claude:claude-sonnet-4-5 Signed-off-by: John Kacur <[email protected]> --- rteval/modules/measurement/cyclictest.py | 49 +++++++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/rteval/modules/measurement/cyclictest.py b/rteval/modules/measurement/cyclictest.py index 8a5123e8af33..a120a552da11 100644 --- a/rteval/modules/measurement/cyclictest.py +++ b/rteval/modules/measurement/cyclictest.py @@ -212,6 +212,10 @@ class Cyclictest(rtevalModulePrototype): logfnc=self._log) self.__cyclicdata['system'].description = (f"({self.__numcores} cores) ") + info['0']['model name'] + # Logging configuration + self._logging = self.__cfg.setdefault('logging', False) + self.__reportdir = self.__cfg.setdefault('reportdir', os.getcwd()) + self._log(Log.DEBUG, f"system using {self.__numcores} cpu cores") self.__started = False self.__cyclicoutput = None @@ -219,6 +223,13 @@ class Cyclictest(rtevalModulePrototype): self.set_latency_test() + def __open_logfile(self, name): + """Open a log file in the reportdir/logs directory""" + logdir = os.path.join(self.__reportdir, "logs") + if not os.path.exists(logdir): + os.makedirs(logdir) + return os.open(os.path.join(logdir, name), os.O_CREAT|os.O_RDWR|os.O_TRUNC) + @staticmethod def __get_debugfs_mount(): ret = None @@ -264,8 +275,11 @@ class Cyclictest(rtevalModulePrototype): elif self.__cfg.threshold: self.__cmd.append(f"-b{int(self.__cfg.threshold)}") - # Buffer for cyclictest data written to stdout - self.__cyclicoutput = tempfile.SpooledTemporaryFile(mode='w+b') + # Setup output file - use actual log file if logging enabled, otherwise temp file + if self._logging: + self.__cyclicoutput = self.__open_logfile("cyclictest.stdout") + else: + self.__cyclicoutput = tempfile.SpooledTemporaryFile(mode='w+b') def _WorkloadTask(self): @@ -284,7 +298,10 @@ class Cyclictest(rtevalModulePrototype): fp.write("0") fp.flush() - self.__cyclicoutput.seek(0) + # Seek to beginning - only needed for temp files, file descriptors start at position 0 + if not self._logging: + self.__cyclicoutput.seek(0) + self.__cyclicprocess = subprocess.Popen(self.__cmd, stdout=self.__cyclicoutput, stderr=self.__nullfp, @@ -345,9 +362,22 @@ class Cyclictest(rtevalModulePrototype): # Parse histogram output - use try/finally to ensure _setFinished() is always called # This prevents hangs if parsing fails due to partial output (RHEL-140898) try: - self.__cyclicoutput.seek(0) - for line in self.__cyclicoutput: - line = bytes.decode(line) + # Read output - handle both file descriptors and temp files + if self._logging: + # For file descriptors, read the entire file content + os.lseek(self.__cyclicoutput, 0, os.SEEK_SET) + output_data = os.read(self.__cyclicoutput, 10*1024*1024) # Read up to 10MB + output_lines = output_data.decode('utf-8', errors='replace').splitlines(keepends=True) + else: + # For temp files, use seek and iterate + self.__cyclicoutput.seek(0) + output_lines = self.__cyclicoutput + + for line in output_lines: + if isinstance(line, bytes): + line = bytes.decode(line) + elif not isinstance(line, str): + line = str(line) if line.startswith('#'): # Catch if cyclictest stopped due to a breaktrace if line.startswith('# Break value: '): @@ -395,6 +425,13 @@ class Cyclictest(rtevalModulePrototype): # Always signal completion to avoid hangs self._setFinished() self.__started = False + + # Close output file - use os.close for file descriptors, .close() for temp files + if self._logging: + os.close(self.__cyclicoutput) + else: + self.__cyclicoutput.close() + os.close(self.__nullfp) del self.__nullfp -- 2.54.0