[PATCH 1/2] rteval: Add -L/--logging support to timerlat measurement module

John Kacur <[email protected]> Tue, 2 Jun 2026 16:47:01 -0400
Newsgroups org.kernel.vger.linux-rt-users
Message-ID <[email protected]>
This implements file logging for the timerlat measurement module, matching
the functionality available in load modules like hackbench and kcompile.

When rteval is run with the -L/--logging flag, timerlat will now save
rtla timerlat's stdout and stderr to files in workdir/logs/:
- timerlat.stdout - raw output from rtla timerlat hist
- timerlat.stderr - any error messages from rtla

This is useful for:
- Debugging new rtla versions with different output formats
- Investigating parsing issues
- Understanding rtla behavior when things go wrong

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/timerlat.py | 54 +++++++++++++++++++++-----
 1 file changed, 45 insertions(+), 9 deletions(-)

diff --git a/rteval/modules/measurement/timerlat.py b/rteval/modules/measurement/timerlat.py
index 2172f6ece520..03faa08639d5 100644
--- a/rteval/modules/measurement/timerlat.py
+++ b/rteval/modules/measurement/timerlat.py
@@ -189,6 +189,10 @@ class Timerlat(rtevalModulePrototype):
         self.__cpus = [str(c) for c in expand_cpulist(self.__cpulist)]
         self.__numcores = len(self.__cpus)
 
+        # Logging configuration
+        self._logging = self.__cfg.setdefault('logging', False)
+        self.__reportdir = self.__cfg.setdefault('reportdir', os.getcwd())
+
         # Has tracing been triggered
         self.__stoptrace = False
         # This stores the output from rtla
@@ -218,6 +222,12 @@ class Timerlat(rtevalModulePrototype):
         self._log(Log.DEBUG, f"system using {self.__numcores} cpu cores")
         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)
 
     def _WorkloadSetup(self):
         self.__timerlat_process = None
@@ -250,8 +260,14 @@ class Timerlat(rtevalModulePrototype):
                 self.__cmd.append(f'-t={self.__cfg.trace}')
 
         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')
+
+        # Setup output files - use actual log files if logging enabled, otherwise temp files
+        if self._logging:
+            self.__timerlat_out = self.__open_logfile("timerlat.stdout")
+            self.__timerlat_err = self.__open_logfile("timerlat.stderr")
+        else:
+            self.__timerlat_out = tempfile.SpooledTemporaryFile(mode='w+b')
+            self.__timerlat_err = tempfile.SpooledTemporaryFile(mode='w+b')
 
 
     def _WorkloadTask(self):
@@ -260,8 +276,11 @@ class Timerlat(rtevalModulePrototype):
 
         self._log(Log.DEBUG, f'starting with cmd: {" ".join(self.__cmd)}')
 
-        self.__timerlat_out.seek(0)
-        self.__timerlat_err.seek(0)
+        # Seek to beginning - only needed for temp files, file descriptors start at position 0
+        if not self._logging:
+            self.__timerlat_out.seek(0)
+            self.__timerlat_err.seek(0)
+
         self.__timerlat_process = subprocess.Popen(self.__cmd,
                                                    stdout=self.__timerlat_out,
                                                    stderr=self.__timerlat_err,
@@ -308,14 +327,26 @@ class Timerlat(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.__timerlat_out.seek(0)
+            # Read output - handle both file descriptors and temp files
+            if self._logging:
+                # For file descriptors, read the entire file content
+                os.lseek(self.__timerlat_out, 0, os.SEEK_SET)
+                output_data = os.read(self.__timerlat_out, 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.__timerlat_out.seek(0)
+                output_lines = self.__timerlat_out
 
             blocking_thread_detected = False
             softirq_interference_detected = False
             irq_interference_detected = False
 
-            for line in self.__timerlat_out:
-                line = bytes.decode(line)
+            for line in output_lines:
+                if isinstance(line, bytes):
+                    line = bytes.decode(line)
+                elif not isinstance(line, str):
+                    line = str(line)
 
                 # Skip any blank lines
                 if not line:
@@ -474,8 +505,13 @@ class Timerlat(rtevalModulePrototype):
             self._setFinished()
             self.__started = False
 
-            self.__timerlat_err.close()
-            self.__timerlat_out.close()
+            # Close output files - method differs for file descriptors vs temp files
+            if self._logging:
+                os.close(self.__timerlat_out)
+                os.close(self.__timerlat_err)
+            else:
+                self.__timerlat_out.close()
+                self.__timerlat_err.close()
 
     def MakeReport(self):
         rep_n = libxml2.newNode('timerlat')
-- 
2.54.0