From 55cca8299a1e1de8b26cae8c0c87455054bed052 Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Tue, 28 Oct 2025 10:45:05 +0000 Subject: [PATCH 01/21] added features for runsolver/starexec compatibility --- benchexec/baseexecutor.py | 26 +++++++++++++++++++++++--- benchexec/runexecutor.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/benchexec/baseexecutor.py b/benchexec/baseexecutor.py index 4c0a4a8ca..4bdb877a6 100644 --- a/benchexec/baseexecutor.py +++ b/benchexec/baseexecutor.py @@ -8,6 +8,7 @@ import errno import logging import os +import time import subprocess import sys import threading @@ -66,6 +67,8 @@ def _start_execution( stdin, stdout, stderr, + timestamp, + addeof, env, cwd, temp_dir, @@ -75,6 +78,7 @@ def _start_execution( parent_cleanup_fn, ): """Actually start the tool and the measurements. + @param args: the command line to run @param parent_setup_fn a function without parameters that is called in the parent process immediately before the tool is started @param child_setup_fn a function without parameters that is called in the child process @@ -110,18 +114,34 @@ def pre_subprocess(): logging.debug("Executing run with $HOME and $TMPDIR below %s.", temp_dir) parent_setup = parent_setup_fn() - + p = subprocess.Popen( args, stdin=stdin, - stdout=stdout, - stderr=stderr, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, +# stdout=stdout, +# stderr=stderr, env=env, cwd=cwd, close_fds=True, preexec_fn=pre_subprocess, + text=True, ) + for line in p.stdout: + if timestamp: + CPU = cgroups.read_cputime() + WC = time.monotonic()-parent_setup[1] + print(f"{CPU:.4f}/{WC:.4f}\t",end='',file=stdout) + print(f"{line.strip()}",file=stdout) + if addeof: + if timestamp: + CPU = cgroups.read_cputime() + WC = time.monotonic()-parent_setup[1] + print(f"{CPU:.4f}/{WC:.4f}\t",end='',file=stdout) + print(f"EOF",file=stdout) + def wait_and_get_result(): exitcode, ru_child = self._wait_for_process(p.pid, args[0]) p.poll() diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index 0e63cb1a4..24cb56583 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -133,6 +133,27 @@ def main(argv=None): help="do not delete files created by the tool in temp directory", ) + io_args.add_argument( + "--no-output-header", + action="store_true", + dest="nowriteheader", + help="suppress header in tool output log", + ) + + io_args.add_argument( + "--timestamp", + action="store_true", + dest="timestamp", + help="timestamp each line of stdout/stderr from the tool", + ) + + io_args.add_argument( + "--add-eof", + action="store_true", + dest="addeof", + help="add an \"EOF\" line after the end of the stdout/stderr from the tool", + ) + container_args = parser.add_argument_group("optional arguments for run container") container_on_args = container_args.add_mutually_exclusive_group() container_on_args.add_argument( @@ -197,6 +218,8 @@ def main(argv=None): else: container_options = {} container_output_options = {} + if options.nowriteheader: + container_output_options["write_header"] = False if options.input == "-": stdin = sys.stdin @@ -257,6 +280,8 @@ def signal_handler_kill(signum, frame): result = executor.execute_run( args=options.args, output_filename=options.output, + timestamp=options.timestamp, + addeof=options.addeof, stdin=stdin, hardtimelimit=options.timelimit, softtimelimit=options.softtimelimit, @@ -600,6 +625,8 @@ def execute_run( self, args, output_filename, + timestamp, + addeof, stdin=None, hardtimelimit=None, softtimelimit=None, @@ -745,6 +772,8 @@ def execute_run( args, output_filename, error_filename, + timestamp, + addeof, stdin, write_header, hardtimelimit, @@ -780,6 +809,8 @@ def _execute( args, output_filename, error_filename, + timestamp, + addeof, stdin, write_header, hardtimelimit, @@ -900,6 +931,8 @@ def preSubprocess(): stdin=stdin, stdout=outputFile, stderr=errorFile, + timestamp=timestamp, + addeof=addeof, env=run_environment, cwd=workingDir, temp_dir=temp_dir, From 0638db1c3b88cd377399d4204e9ef465a496d1c3 Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Tue, 28 Oct 2025 10:52:37 +0000 Subject: [PATCH 02/21] syntax fix for Ruff --- benchexec/baseexecutor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchexec/baseexecutor.py b/benchexec/baseexecutor.py index 4bdb877a6..b6122a2d8 100644 --- a/benchexec/baseexecutor.py +++ b/benchexec/baseexecutor.py @@ -140,7 +140,7 @@ def pre_subprocess(): CPU = cgroups.read_cputime() WC = time.monotonic()-parent_setup[1] print(f"{CPU:.4f}/{WC:.4f}\t",end='',file=stdout) - print(f"EOF",file=stdout) + print("EOF",file=stdout) def wait_and_get_result(): exitcode, ru_child = self._wait_for_process(p.pid, args[0]) From b5d3563121a8ed378b9847747a27e45a6ee00568 Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Tue, 28 Oct 2025 10:59:51 +0000 Subject: [PATCH 03/21] syntax fix for Ruff --- benchexec/baseexecutor.py | 14 ++++++-------- benchexec/runexecutor.py | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/benchexec/baseexecutor.py b/benchexec/baseexecutor.py index b6122a2d8..3a098b8f0 100644 --- a/benchexec/baseexecutor.py +++ b/benchexec/baseexecutor.py @@ -120,8 +120,6 @@ def pre_subprocess(): stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, -# stdout=stdout, -# stderr=stderr, env=env, cwd=cwd, close_fds=True, @@ -132,15 +130,15 @@ def pre_subprocess(): for line in p.stdout: if timestamp: CPU = cgroups.read_cputime() - WC = time.monotonic()-parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\t",end='',file=stdout) - print(f"{line.strip()}",file=stdout) + WC = time.monotonic() - parent_setup[1] + print(f"{CPU:.4f}/{WC:.4f}\t",end="", file=stdout) + print(f"{line.strip()}", file=stdout) if addeof: if timestamp: CPU = cgroups.read_cputime() - WC = time.monotonic()-parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\t",end='',file=stdout) - print("EOF",file=stdout) + WC = time.monotonic() - parent_setup[1] + print(f"{CPU:.4f}/{WC:.4f}\t",end="", file=stdout) + print("EOF", file=stdout) def wait_and_get_result(): exitcode, ru_child = self._wait_for_process(p.pid, args[0]) diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index 24cb56583..548ae836f 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -151,7 +151,7 @@ def main(argv=None): "--add-eof", action="store_true", dest="addeof", - help="add an \"EOF\" line after the end of the stdout/stderr from the tool", + help='add an "EOF" line after the end of the stdout/stderr from the tool', ) container_args = parser.add_argument_group("optional arguments for run container") From 62d38ef9a073ff0388f9c5f698a6f314d2c2b8cc Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Tue, 28 Oct 2025 11:03:08 +0000 Subject: [PATCH 04/21] syntax fix for Ruff --- benchexec/baseexecutor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benchexec/baseexecutor.py b/benchexec/baseexecutor.py index 3a098b8f0..902c67c53 100644 --- a/benchexec/baseexecutor.py +++ b/benchexec/baseexecutor.py @@ -114,7 +114,7 @@ def pre_subprocess(): logging.debug("Executing run with $HOME and $TMPDIR below %s.", temp_dir) parent_setup = parent_setup_fn() - + p = subprocess.Popen( args, stdin=stdin, @@ -131,13 +131,13 @@ def pre_subprocess(): if timestamp: CPU = cgroups.read_cputime() WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\t",end="", file=stdout) + print(f"{CPU:.4f}/{WC:.4f}\t", end="", file=stdout) print(f"{line.strip()}", file=stdout) if addeof: if timestamp: CPU = cgroups.read_cputime() WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\t",end="", file=stdout) + print(f"{CPU:.4f}/{WC:.4f}\t", end="", file=stdout) print("EOF", file=stdout) def wait_and_get_result(): From fa4eb4a69af2964bbb8ef65ac21300cabdb6861a Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Tue, 28 Oct 2025 14:01:25 +0000 Subject: [PATCH 05/21] fixed ^C, added option for output to stdout --- benchexec/baseexecutor.py | 28 +++++++++++++++------------- benchexec/runexecutor.py | 30 +++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/benchexec/baseexecutor.py b/benchexec/baseexecutor.py index 902c67c53..4dabd6a05 100644 --- a/benchexec/baseexecutor.py +++ b/benchexec/baseexecutor.py @@ -119,7 +119,7 @@ def pre_subprocess(): args, stdin=stdin, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + stderr=subprocess.STDOUT, env=env, cwd=cwd, close_fds=True, @@ -127,20 +127,22 @@ def pre_subprocess(): text=True, ) - for line in p.stdout: - if timestamp: - CPU = cgroups.read_cputime() - WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\t", end="", file=stdout) - print(f"{line.strip()}", file=stdout) - if addeof: - if timestamp: - CPU = cgroups.read_cputime() - WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\t", end="", file=stdout) - print("EOF", file=stdout) + def add_timestamps_and_EOF(): + for line in p.stdout: + if timestamp: + CPU = cgroups.read_cputime() + WC = time.monotonic() - parent_setup[1] + print(f"{CPU:.4f}/{WC:.4f}\t", end="", file=stdout) + print(f"{line.strip()}", file=stdout) + if addeof: + if timestamp: + CPU = cgroups.read_cputime() + WC = time.monotonic() - parent_setup[1] + print(f"{CPU:.4f}/{WC:.4f}\t", end="", file=stdout) + print("EOF", file=stdout) def wait_and_get_result(): + add_timestamps_and_EOF() exitcode, ru_child = self._wait_for_process(p.pid, args[0]) p.poll() diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index 548ae836f..800f9d41f 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -105,7 +105,7 @@ def main(argv=None): "--output", default="output.log", metavar="FILE", - help="name of file where command output (stdout and stderr) is written", + help="name of file where command output (stdout and stderr) is written; use - for stdout passthrough", ) io_args.add_argument( "--maxOutputSize", @@ -545,13 +545,16 @@ def _setup_output_file(self, output_filename, args, write_header=True): """Open and prepare output file.""" # write command line into outputFile # (without environment variables, they are documented by benchexec) - try: - parent_dir = os.path.dirname(output_filename) - if parent_dir: - os.makedirs(parent_dir, exist_ok=True) - output_file = open(output_filename, "w") # override existing file - except OSError as e: - sys.exit("Could not write to output file: " + str(e)) + if output_filename == "-": + output_file = sys.stdout + else: + try: + parent_dir = os.path.dirname(output_filename) + if parent_dir: + os.makedirs(parent_dir, exist_ok=True) + output_file = open(output_filename, "w") # override existing file + except OSError as e: + sys.exit("Could not write to output file: " + str(e)) if write_header: output_file.write(shlex.join(args) + "\n\n\n" + "-" * 80 + "\n\n\n") @@ -990,7 +993,8 @@ def preSubprocess(): tool_cgroups.kill_all_tasks() # normally subprocess closes file, we do this again after all tasks terminated - outputFile.close() + if outputFile is not sys.stdout: + outputFile.close() if errorFile is not outputFile: errorFile.close() @@ -1143,6 +1147,10 @@ def _reduce_file_size_if_necessary(fileName, maxSize): We remove only the middle part of a file, the file-start and the file-end remain unchanged. """ +#----hyphen is stdout + if fileName == "-": + return + fileSize = os.path.getsize(fileName) if maxSize is None: @@ -1173,6 +1181,10 @@ def _get_debug_output_after_crash(output_filename, base_path): @param output_filename name of log file with tool output @param base_path string that needs to be preprended to paths for lookup of files """ +#----hyphen is stdout + if output_filename == "-": + return + logging.debug("Analysing output for crash info.") foundDumpFile = False try: From 2577b8bb223fac152dac105e66d1c43f36459872 Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Tue, 28 Oct 2025 14:09:12 +0000 Subject: [PATCH 06/21] fix for Ruff --- benchexec/runexecutor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index 800f9d41f..03df8c1b8 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -1147,7 +1147,7 @@ def _reduce_file_size_if_necessary(fileName, maxSize): We remove only the middle part of a file, the file-start and the file-end remain unchanged. """ -#----hyphen is stdout +# ----hyphen is stdout if fileName == "-": return @@ -1181,7 +1181,7 @@ def _get_debug_output_after_crash(output_filename, base_path): @param output_filename name of log file with tool output @param base_path string that needs to be preprended to paths for lookup of files """ -#----hyphen is stdout +# ----hyphen is stdout if output_filename == "-": return From 3ec4025fb25ff6437708711d2875549a1c99d210 Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Tue, 28 Oct 2025 14:27:21 +0000 Subject: [PATCH 07/21] fix for Ruff --- benchexec/runexecutor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index 03df8c1b8..27de87751 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -1147,7 +1147,7 @@ def _reduce_file_size_if_necessary(fileName, maxSize): We remove only the middle part of a file, the file-start and the file-end remain unchanged. """ -# ----hyphen is stdout + # ----hyphen is stdout if fileName == "-": return @@ -1181,7 +1181,7 @@ def _get_debug_output_after_crash(output_filename, base_path): @param output_filename name of log file with tool output @param base_path string that needs to be preprended to paths for lookup of files """ -# ----hyphen is stdout + # ----hyphen is stdout if output_filename == "-": return From 8771788c5a0d3a42a7d3a738697e6424f6d5b729 Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Tue, 28 Oct 2025 15:29:33 +0000 Subject: [PATCH 08/21] added flag to send statistics to a file instead of stdout --- benchexec/runexecutor.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index 27de87751..fde32c512 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -101,6 +101,12 @@ def main(argv=None): help="name of file used as stdin for command " "(default: /dev/null; use - for stdin passthrough)", ) + io_args.add_argument( + "--statistics-output-file", + default="-", + metavar="FILE", + help="name of file where run statistics are written; use - for stdout passthrough", + ) io_args.add_argument( "--output", default="output.log", @@ -303,6 +309,17 @@ def signal_handler_kill(signum, frame): # exit_code is a util.ProcessExitCode instance exit_code = cast(Optional[util.ProcessExitCode], result.pop("exitcode", None)) + if options.statistics_output_file != "-": + try: + parent_dir = os.path.dirname(options.statistics_output_file) + if parent_dir: + os.makedirs(parent_dir, exist_ok=True) + statsFile = open(options.statistics_output_file, "w") # override existing file + except OSError as e: + sys.exit("Could not write to statistics file: " + str(e)) + else: + statsFile = sys.stdout + def print_optional_result(key, unit=""): if key in result: value = result[key] @@ -312,20 +329,20 @@ def print_optional_result(key, unit=""): format_fn = datetime.datetime.isoformat else: format_fn = str - print(f"{key}={format_fn(value)}{unit}") + print(f"{key}={format_fn(value)}{unit}", file=statsFile) # output results print_optional_result("starttime", unit="") print_optional_result("terminationreason") if exit_code is not None and exit_code.value is not None: - print(f"returnvalue={exit_code.value}") + print(f"returnvalue={exit_code.value}", file=statsFile) if exit_code is not None and exit_code.signal is not None: - print(f"exitsignal={exit_code.signal}") + print(f"exitsignal={exit_code.signal}", file=statsFile) print_optional_result("walltime", "s") print_optional_result("cputime", "s") for key in sorted(result.keys()): if key.startswith("cputime-"): - print(f"{key}={result[key]:.9f}s") + print(f"{key}={result[key]:.9f}s", file=statsFile) print_optional_result("memory", "B") print_optional_result("blkio-read", "B") print_optional_result("blkio-write", "B") @@ -334,8 +351,10 @@ def print_optional_result(key, unit=""): print_optional_result("pressure-memory-some", "s") energy = intel_cpu_energy.format_energy_results(result.get("cpuenergy")) for energy_key, energy_value in energy.items(): - print(f"{energy_key}={energy_value}J") + print(f"{energy_key}={energy_value}J", file=statsFile) + if statsFile is not sys.stdout: + statsFile.close() class RunExecutor(containerexecutor.ContainerExecutor): # --- object initialization --- From c0bb2d6c0e8216ac00d730c547e1a931a43ba42a Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Wed, 29 Oct 2025 07:48:40 +0000 Subject: [PATCH 09/21] fix for Ruff --- benchexec/runexecutor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index fde32c512..b10a93eee 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -314,7 +314,9 @@ def signal_handler_kill(signum, frame): parent_dir = os.path.dirname(options.statistics_output_file) if parent_dir: os.makedirs(parent_dir, exist_ok=True) - statsFile = open(options.statistics_output_file, "w") # override existing file + statsFile = open( + options.statistics_output_file, "w" + ) # override existing file except OSError as e: sys.exit("Could not write to statistics file: " + str(e)) else: @@ -356,6 +358,7 @@ def print_optional_result(key, unit=""): if statsFile is not sys.stdout: statsFile.close() + class RunExecutor(containerexecutor.ContainerExecutor): # --- object initialization --- From fee787b80281e544c67f4ece769fc46209132d2e Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Wed, 29 Oct 2025 12:19:39 +0000 Subject: [PATCH 10/21] fixed so no piping if no timestamps. working on container version but stuck --- benchexec/baseexecutor.py | 17 +++++++---------- benchexec/containerexecutor.py | 20 ++++++++++++++++++-- benchexec/runexecutor.py | 24 ++++++++++++------------ 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/benchexec/baseexecutor.py b/benchexec/baseexecutor.py index 4dabd6a05..7150d64e2 100644 --- a/benchexec/baseexecutor.py +++ b/benchexec/baseexecutor.py @@ -118,8 +118,8 @@ def pre_subprocess(): p = subprocess.Popen( args, stdin=stdin, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, + stdout=(subprocess.PIPE if timestamp else stdout), + stderr=(subprocess.STDOUT if timestamp else stderr), env=env, cwd=cwd, close_fds=True, @@ -128,18 +128,15 @@ def pre_subprocess(): ) def add_timestamps_and_EOF(): - for line in p.stdout: - if timestamp: + if timestamp: + for line in p.stdout: CPU = cgroups.read_cputime() WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\t", end="", file=stdout) - print(f"{line.strip()}", file=stdout) - if addeof: - if timestamp: + print(f"{CPU:.4f}/{WC:.4f}\t{line.strip()}", file=stdout) + if addeof: CPU = cgroups.read_cputime() WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\t", end="", file=stdout) - print("EOF", file=stdout) + print(f"{CPU:.4f}/{WC:.4f}\tEOF", file=stdout) def wait_and_get_result(): add_timestamps_and_EOF() diff --git a/benchexec/containerexecutor.py b/benchexec/containerexecutor.py index 93dbecc3e..27d5ddbd0 100644 --- a/benchexec/containerexecutor.py +++ b/benchexec/containerexecutor.py @@ -558,6 +558,8 @@ def _start_execution_in_container( stdin, stdout, stderr, + timestamp, + addeof, env, root_dir, cwd, @@ -777,8 +779,9 @@ def child(): grandchild_proc = subprocess.Popen( args, stdin=stdin, - stdout=stdout, - stderr=stderr, + stdout=(subprocess.PIPE if timestamp else stdout), + stderr=(subprocess.STDOUT if timestamp else stderr), +# ZZZZZZZ env=env, close_fds=False, preexec_fn=grandchild, @@ -1009,7 +1012,20 @@ def check_child_exit_code(): os.close(from_grandchild) os.close(to_grandchild) + def add_timestamps_and_EOF(): + if timestamp: +# ZZZZZZZ Here is where I need grandchild_proc, but it lost in the layers. + for line in grandchild_proc.stdout: + CPU = cgroups.read_cputime() + WC = time.monotonic() - parent_setup[1] + print(f"{CPU:.4f}/{WC:.4f}\t{line.strip()}", file=stdout) + if addeof: + CPU = cgroups.read_cputime() + WC = time.monotonic() - parent_setup[1] + print(f"{CPU:.4f}/{WC:.4f}\tEOF", file=stdout) + def wait_for_grandchild(): + add_timestamps_and_EOF() # 1024 bytes ought to be enough for everyone^Wour pickled result try: received = os.read(from_grandchild_copy, 1024) diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index b10a93eee..22cec62cc 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -101,18 +101,18 @@ def main(argv=None): help="name of file used as stdin for command " "(default: /dev/null; use - for stdin passthrough)", ) - io_args.add_argument( - "--statistics-output-file", - default="-", - metavar="FILE", - help="name of file where run statistics are written; use - for stdout passthrough", - ) io_args.add_argument( "--output", default="output.log", metavar="FILE", help="name of file where command output (stdout and stderr) is written; use - for stdout passthrough", ) + io_args.add_argument( + "--statistics-file", + default="-", + metavar="FILE", + help="name of file where run statistics are written; use - for stdout passthrough", + ) io_args.add_argument( "--maxOutputSize", type=util.parse_memory_value, @@ -157,7 +157,7 @@ def main(argv=None): "--add-eof", action="store_true", dest="addeof", - help='add an "EOF" line after the end of the stdout/stderr from the tool', + help='add an "EOF" line after the stdout/stderr from the tool; only if timestamp is set', ) container_args = parser.add_argument_group("optional arguments for run container") @@ -224,8 +224,8 @@ def main(argv=None): else: container_options = {} container_output_options = {} - if options.nowriteheader: - container_output_options["write_header"] = False + if options.nowriteheader: + container_output_options["write_header"] = False if options.input == "-": stdin = sys.stdin @@ -309,13 +309,13 @@ def signal_handler_kill(signum, frame): # exit_code is a util.ProcessExitCode instance exit_code = cast(Optional[util.ProcessExitCode], result.pop("exitcode", None)) - if options.statistics_output_file != "-": + if options.statistics_file != "-": try: - parent_dir = os.path.dirname(options.statistics_output_file) + parent_dir = os.path.dirname(options.statistics_file) if parent_dir: os.makedirs(parent_dir, exist_ok=True) statsFile = open( - options.statistics_output_file, "w" + options.statistics_file, "w" ) # override existing file except OSError as e: sys.exit("Could not write to statistics file: " + str(e)) From 12fdc16bb82acad34ec4905fb618040a31d6eda4 Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Thu, 30 Oct 2025 11:58:18 +0000 Subject: [PATCH 11/21] soft wall clock limit implemented --- benchexec/runexecutor.py | 67 +++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index 22cec62cc..d3c8c07ad 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -73,7 +73,7 @@ def main(argv=None): "--softtimelimit", type=util.parse_timespan_value, metavar="SECONDS", - help='"soft" CPU time limit in seconds (command will be send the TERM signal at this time)', + help="soft CPU time limit in seconds (program will be sent the TERM signal at this time)", ) resource_args.add_argument( "--walltimelimit", @@ -81,6 +81,12 @@ def main(argv=None): metavar="SECONDS", help="wall time limit in seconds (default is CPU time limit plus a few seconds)", ) + resource_args.add_argument( + "--softwalltimelimit", + type=util.parse_timespan_value, + metavar="SECONDS", + help="soft wall time limit in seconds (program will be sent the TERM signal at this time)", + ) resource_args.add_argument( "--cores", type=util.parse_int_list, @@ -292,6 +298,7 @@ def signal_handler_kill(signum, frame): hardtimelimit=options.timelimit, softtimelimit=options.softtimelimit, walltimelimit=options.walltimelimit, + softwalltimelimit=options.softwalltimelimit, cores=options.cores, memlimit=options.memlimit, memory_nodes=options.memoryNodes, @@ -585,18 +592,19 @@ def _setup_output_file(self, output_filename, args, write_header=True): return output_file def _setup_cgroup_time_limit( - self, hardtimelimit, softtimelimit, walltimelimit, cgroups, cores, pid_to_kill + self, hardtimelimit, softtimelimit, walltimelimit, softwalltimelimit, cgroups, cores, pid_to_kill ): """Start time-limit handler. @return None or the time-limit handler for calling cancel() """ - if any([hardtimelimit, softtimelimit, walltimelimit]): + if any([hardtimelimit, softtimelimit, walltimelimit, softwalltimelimit]): # Start a timer to periodically check timelimit timelimitThread = _TimelimitThread( cgroups=cgroups, hardtimelimit=hardtimelimit, softtimelimit=softtimelimit, walltimelimit=walltimelimit, + softwalltimelimit=softwalltimelimit, pid_to_kill=pid_to_kill, cores=cores, callbackFn=self._set_termination_reason, @@ -656,6 +664,7 @@ def execute_run( hardtimelimit=None, softtimelimit=None, walltimelimit=None, + softwalltimelimit=None, cores=None, memlimit=None, memory_nodes=None, @@ -684,6 +693,7 @@ def execute_run( @param hardtimelimit: None or the CPU time in seconds after which the tool is forcefully killed. @param softtimelimit: None or the CPU time in seconds after which the tool is sent a kill signal. @param walltimelimit: None or the wall time in seconds after which the tool is forcefully killed (default: hardtimelimit + a few seconds) + @param softwalltimelimit: None or the CPU time in seconds after which the tool is sent a kill signal. @param cores: None or a list of the CPU cores to use @param memlimit: None or memory limit in bytes @param memory_nodes: None or a list of memory nodes in a NUMA system to use @@ -713,6 +723,7 @@ def execute_run( if self.cgroups.CPU not in self.cgroups: logging.error("Time limit cannot be specified without cpuacct cgroup.") critical_cgroups.add(self.cgroups.CPU) + if softtimelimit is not None: if softtimelimit <= 0: sys.exit(f"Invalid soft time limit {softtimelimit}.") @@ -724,14 +735,22 @@ def execute_run( ) critical_cgroups.add(self.cgroups.CPU) - if walltimelimit is None: + if walltimelimit is not None: + if walltimelimit <= 0: + sys.exit(f"Invalid wall time limit {walltimelimit}.") + else: if hardtimelimit is not None: walltimelimit = hardtimelimit + _WALLTIME_LIMIT_DEFAULT_OVERHEAD elif softtimelimit is not None: walltimelimit = softtimelimit + _WALLTIME_LIMIT_DEFAULT_OVERHEAD - else: - if walltimelimit <= 0: - sys.exit(f"Invalid wall time limit {walltimelimit}.") + elif softwalltimelimit is not None: + walltimelimit = softwalltimelimit + _WALLTIME_LIMIT_DEFAULT_OVERHEAD + + if softwalltimelimit is not None: + if softwalltimelimit <= 0: + sys.exit(f"Invalid soft wall time limit {wallsofttimelimit}.") + if walltimelimit and (softwalltimelimit > walltimelimit): + sys.exit("Soft wall time limit cannot be larger than the wall time limit.") if cores is not None: if self.cpus is None: @@ -804,6 +823,7 @@ def execute_run( hardtimelimit, softtimelimit, walltimelimit, + softwalltimelimit, memlimit, cores, memory_nodes, @@ -841,6 +861,7 @@ def _execute( hardtimelimit, softtimelimit, walltimelimit, + softwalltimelimit, memlimit, cores, memory_nodes, @@ -977,6 +998,7 @@ def preSubprocess(): hardtimelimit, softtimelimit, walltimelimit, + softwalltimelimit, tool_cgroups, cores, tool_pid, @@ -1265,6 +1287,7 @@ def __init__( hardtimelimit, softtimelimit, walltimelimit, + softwalltimelimit, pid_to_kill, cores, callbackFn=lambda reason: None, @@ -1288,8 +1311,20 @@ def __init__( self.cgroups = cgroups # set timelimits to large dummy value if no limit is given self.timelimit = hardtimelimit or (60 * 60 * 24 * 365 * 100) - self.softtimelimit = softtimelimit or (60 * 60 * 24 * 365 * 100) + self.softtimelimit = softtimelimit or self.timelimit + self.walltimelimit = walltimelimit or self.timelimit + self.softwalltimelimit = softwalltimelimit or self.walltimelimit self.latestKillTime = time.monotonic() + walltimelimit + logging.debug( + "TimelimitThread timelimit: %s, softtimelimit: %s, " + "walltimelimit: %s, softwalltimelimit: %s, latestKillTime %s.", + self.timelimit, + self.softtimelimit, + self.walltimelimit, + self.softwalltimelimit, + self.latestKillTime, + ) + self.pid_to_kill = pid_to_kill self.callback = callbackFn @@ -1307,14 +1342,18 @@ def run(self): remainingCpuTime = self.timelimit - usedCpuTime remainingSoftCpuTime = self.softtimelimit - usedCpuTime remainingWallTime = self.latestKillTime - time.monotonic() + remainingSoftWallTime = remainingWallTime - (self.walltimelimit - self.softwalltimelimit) logging.debug( - "TimelimitThread for process %s: used CPU time: %s, remaining CPU time: %s, " - "remaining soft CPU time: %s, remaining wall time: %s.", + "TimelimitThread for process %s: latest kill time: %s, used CPU time: %s, " + "remaining CPU time: %s, remaining soft CPU time: %s, remaining wall time: %s, " + "remaining soft wall time %s.", self.pid_to_kill, + self.latestKillTime, usedCpuTime, remainingCpuTime, remainingSoftCpuTime, remainingWallTime, + remainingSoftWallTime, ) if remainingCpuTime <= 0: self.callback("cputime") @@ -1324,6 +1363,7 @@ def run(self): util.kill_process(self.pid_to_kill) self.finished.set() return + if remainingWallTime <= 0: self.callback("walltime") logging.warning( @@ -1337,12 +1377,17 @@ def run(self): self.callback("cputime-soft") # soft time limit violated, ask process to terminate util.kill_process(self.pid_to_kill, signal.SIGTERM) - self.softtimelimit = self.timelimit + + if remainingSoftWallTime <= 0: + self.callback("walltime-soft") + # soft time limit violated, ask process to terminate + util.kill_process(self.pid_to_kill, signal.SIGTERM) remainingTime = min( remainingCpuTime / self.cpuCount, remainingSoftCpuTime / self.cpuCount, remainingWallTime, + remainingSoftWallTime, ) self.finished.wait(remainingTime + 1) From e8e0dd132afd7558becd5d93df9e2db161c5290f Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Thu, 30 Oct 2025 12:06:13 +0000 Subject: [PATCH 12/21] fixes for Ruff --- benchexec/runexecutor.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index d3c8c07ad..bf387e943 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -321,9 +321,7 @@ def signal_handler_kill(signum, frame): parent_dir = os.path.dirname(options.statistics_file) if parent_dir: os.makedirs(parent_dir, exist_ok=True) - statsFile = open( - options.statistics_file, "w" - ) # override existing file + statsFile = open(options.statistics_file, "w") # override existing file except OSError as e: sys.exit("Could not write to statistics file: " + str(e)) else: @@ -748,7 +746,7 @@ def execute_run( if softwalltimelimit is not None: if softwalltimelimit <= 0: - sys.exit(f"Invalid soft wall time limit {wallsofttimelimit}.") + sys.exit(f"Invalid soft wall time limit {softtwallimelimit}.") if walltimelimit and (softwalltimelimit > walltimelimit): sys.exit("Soft wall time limit cannot be larger than the wall time limit.") @@ -1324,7 +1322,7 @@ def __init__( self.softwalltimelimit, self.latestKillTime, ) - + self.pid_to_kill = pid_to_kill self.callback = callbackFn From b0b44916752ae773a9b93170f7abe12758f46275 Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Thu, 30 Oct 2025 12:51:57 +0000 Subject: [PATCH 13/21] Final version (I hope) with original containerexecutor.py --- benchexec/containerexecutor.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/benchexec/containerexecutor.py b/benchexec/containerexecutor.py index 27d5ddbd0..93dbecc3e 100644 --- a/benchexec/containerexecutor.py +++ b/benchexec/containerexecutor.py @@ -558,8 +558,6 @@ def _start_execution_in_container( stdin, stdout, stderr, - timestamp, - addeof, env, root_dir, cwd, @@ -779,9 +777,8 @@ def child(): grandchild_proc = subprocess.Popen( args, stdin=stdin, - stdout=(subprocess.PIPE if timestamp else stdout), - stderr=(subprocess.STDOUT if timestamp else stderr), -# ZZZZZZZ + stdout=stdout, + stderr=stderr, env=env, close_fds=False, preexec_fn=grandchild, @@ -1012,20 +1009,7 @@ def check_child_exit_code(): os.close(from_grandchild) os.close(to_grandchild) - def add_timestamps_and_EOF(): - if timestamp: -# ZZZZZZZ Here is where I need grandchild_proc, but it lost in the layers. - for line in grandchild_proc.stdout: - CPU = cgroups.read_cputime() - WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\t{line.strip()}", file=stdout) - if addeof: - CPU = cgroups.read_cputime() - WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\tEOF", file=stdout) - def wait_for_grandchild(): - add_timestamps_and_EOF() # 1024 bytes ought to be enough for everyone^Wour pickled result try: received = os.read(from_grandchild_copy, 1024) From 27d29eab1846b8ac2e211c1fc8a3664cf227102a Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Thu, 30 Oct 2025 12:56:29 +0000 Subject: [PATCH 14/21] Final version fix for Ruff --- benchexec/runexecutor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index bf387e943..c65b779fb 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -746,7 +746,7 @@ def execute_run( if softwalltimelimit is not None: if softwalltimelimit <= 0: - sys.exit(f"Invalid soft wall time limit {softtwallimelimit}.") + sys.exit(f"Invalid soft wall time limit {softtwalltimelimit}.") if walltimelimit and (softwalltimelimit > walltimelimit): sys.exit("Soft wall time limit cannot be larger than the wall time limit.") From c4dfa831e000b9680b1b201d713d9bc24df0a878 Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Thu, 30 Oct 2025 13:01:09 +0000 Subject: [PATCH 15/21] Final version fix for Ruff --- benchexec/containerexecutor.py | 20 ++++++++++++++++++-- benchexec/runexecutor.py | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/benchexec/containerexecutor.py b/benchexec/containerexecutor.py index 93dbecc3e..27d5ddbd0 100644 --- a/benchexec/containerexecutor.py +++ b/benchexec/containerexecutor.py @@ -558,6 +558,8 @@ def _start_execution_in_container( stdin, stdout, stderr, + timestamp, + addeof, env, root_dir, cwd, @@ -777,8 +779,9 @@ def child(): grandchild_proc = subprocess.Popen( args, stdin=stdin, - stdout=stdout, - stderr=stderr, + stdout=(subprocess.PIPE if timestamp else stdout), + stderr=(subprocess.STDOUT if timestamp else stderr), +# ZZZZZZZ env=env, close_fds=False, preexec_fn=grandchild, @@ -1009,7 +1012,20 @@ def check_child_exit_code(): os.close(from_grandchild) os.close(to_grandchild) + def add_timestamps_and_EOF(): + if timestamp: +# ZZZZZZZ Here is where I need grandchild_proc, but it lost in the layers. + for line in grandchild_proc.stdout: + CPU = cgroups.read_cputime() + WC = time.monotonic() - parent_setup[1] + print(f"{CPU:.4f}/{WC:.4f}\t{line.strip()}", file=stdout) + if addeof: + CPU = cgroups.read_cputime() + WC = time.monotonic() - parent_setup[1] + print(f"{CPU:.4f}/{WC:.4f}\tEOF", file=stdout) + def wait_for_grandchild(): + add_timestamps_and_EOF() # 1024 bytes ought to be enough for everyone^Wour pickled result try: received = os.read(from_grandchild_copy, 1024) diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index c65b779fb..85b74902f 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -746,7 +746,7 @@ def execute_run( if softwalltimelimit is not None: if softwalltimelimit <= 0: - sys.exit(f"Invalid soft wall time limit {softtwalltimelimit}.") + sys.exit(f"Invalid soft wall time limit {softwalltimelimit}.") if walltimelimit and (softwalltimelimit > walltimelimit): sys.exit("Soft wall time limit cannot be larger than the wall time limit.") From 365bff7a05b2b21e6f4d47724822a2fa3d205590 Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Thu, 30 Oct 2025 13:06:57 +0000 Subject: [PATCH 16/21] Put back original containerexecutor.py --- benchexec/containerexecutor.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/benchexec/containerexecutor.py b/benchexec/containerexecutor.py index 27d5ddbd0..93dbecc3e 100644 --- a/benchexec/containerexecutor.py +++ b/benchexec/containerexecutor.py @@ -558,8 +558,6 @@ def _start_execution_in_container( stdin, stdout, stderr, - timestamp, - addeof, env, root_dir, cwd, @@ -779,9 +777,8 @@ def child(): grandchild_proc = subprocess.Popen( args, stdin=stdin, - stdout=(subprocess.PIPE if timestamp else stdout), - stderr=(subprocess.STDOUT if timestamp else stderr), -# ZZZZZZZ + stdout=stdout, + stderr=stderr, env=env, close_fds=False, preexec_fn=grandchild, @@ -1012,20 +1009,7 @@ def check_child_exit_code(): os.close(from_grandchild) os.close(to_grandchild) - def add_timestamps_and_EOF(): - if timestamp: -# ZZZZZZZ Here is where I need grandchild_proc, but it lost in the layers. - for line in grandchild_proc.stdout: - CPU = cgroups.read_cputime() - WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\t{line.strip()}", file=stdout) - if addeof: - CPU = cgroups.read_cputime() - WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\tEOF", file=stdout) - def wait_for_grandchild(): - add_timestamps_and_EOF() # 1024 bytes ought to be enough for everyone^Wour pickled result try: received = os.read(from_grandchild_copy, 1024) From 7ad4a348b6f617735ec56e35349a11c84ca2de6a Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Thu, 30 Oct 2025 13:08:13 +0000 Subject: [PATCH 17/21] branch for working on container timestamps --- benchexec/containerexecutor.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/benchexec/containerexecutor.py b/benchexec/containerexecutor.py index 93dbecc3e..27d5ddbd0 100644 --- a/benchexec/containerexecutor.py +++ b/benchexec/containerexecutor.py @@ -558,6 +558,8 @@ def _start_execution_in_container( stdin, stdout, stderr, + timestamp, + addeof, env, root_dir, cwd, @@ -777,8 +779,9 @@ def child(): grandchild_proc = subprocess.Popen( args, stdin=stdin, - stdout=stdout, - stderr=stderr, + stdout=(subprocess.PIPE if timestamp else stdout), + stderr=(subprocess.STDOUT if timestamp else stderr), +# ZZZZZZZ env=env, close_fds=False, preexec_fn=grandchild, @@ -1009,7 +1012,20 @@ def check_child_exit_code(): os.close(from_grandchild) os.close(to_grandchild) + def add_timestamps_and_EOF(): + if timestamp: +# ZZZZZZZ Here is where I need grandchild_proc, but it lost in the layers. + for line in grandchild_proc.stdout: + CPU = cgroups.read_cputime() + WC = time.monotonic() - parent_setup[1] + print(f"{CPU:.4f}/{WC:.4f}\t{line.strip()}", file=stdout) + if addeof: + CPU = cgroups.read_cputime() + WC = time.monotonic() - parent_setup[1] + print(f"{CPU:.4f}/{WC:.4f}\tEOF", file=stdout) + def wait_for_grandchild(): + add_timestamps_and_EOF() # 1024 bytes ought to be enough for everyone^Wour pickled result try: received = os.read(from_grandchild_copy, 1024) From 84afb6a56abb63100ea34e55fa9f8bba603ebecb Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Thu, 30 Oct 2025 16:34:08 +0000 Subject: [PATCH 18/21] container timestamps --- benchexec/containerexecutor.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/benchexec/containerexecutor.py b/benchexec/containerexecutor.py index 27d5ddbd0..54af7110b 100644 --- a/benchexec/containerexecutor.py +++ b/benchexec/containerexecutor.py @@ -21,6 +21,7 @@ import sys import tempfile import traceback +import time from benchexec import __version__ from benchexec import baseexecutor @@ -612,6 +613,8 @@ def _start_execution_in_container( from_parent, to_grandchild = os.pipe() # "upstream" pipe grandchild/child->parent from_grandchild, to_parent = os.pipe() + # "upstream" stdout/stderr pipe grandchild/child->parent + stdout_from_grandchild, stdout_to_grandparent = os.pipe() # The protocol for these pipes is that first the parent sends the marker for # user mappings, then the grand child sends its outer PID back, @@ -646,7 +649,6 @@ def grandchild(): # and reading /proc/self in the outer procfs instance # (that's what we do). my_outer_pid = container.get_my_pid_from_procfs() - container.mount_proc(self._container_system_config) container.reset_signal_handling() child_setup_fn() # Do some other setup the caller wants. @@ -678,6 +680,8 @@ def grandchild(): raise finally: # close remaining ends of pipe +# ZZZZZ + os.close(stdout_from_grandchild) os.close(from_parent) os.close(to_parent) # here Python will exec() the tool for us @@ -711,6 +715,8 @@ def child(): stdin, stdout, stderr, +# ZZZZ + stdout_to_grandparent, } - {None} container.close_open_fds(keep_files=necessary_fds) @@ -776,12 +782,11 @@ def child(): container.setup_seccomp_filter() try: - grandchild_proc = subprocess.Popen( + grandchild_proc = subprocess.Popen( # This is in the child args, stdin=stdin, - stdout=(subprocess.PIPE if timestamp else stdout), + stdout=(stdout_to_grandparent if timestamp else stdout), stderr=(subprocess.STDOUT if timestamp else stderr), -# ZZZZZZZ env=env, close_fds=False, preexec_fn=grandchild, @@ -789,6 +794,9 @@ def child(): except (OSError, RuntimeError) as e: logging.critical("Cannot start process: %s", e) return CHILD_OSERROR +# ZZZZZ + # stdout_from_grandchild was closed earlier + os.close(stdout_to_grandparent) # keep capability for unmount if necessary later necessary_capabilities = ( @@ -933,6 +941,7 @@ def check_child_exit_code(): # if all other processes have terminated. os.close(from_parent) os.close(to_parent) + os.close(stdout_to_grandparent) container.setup_user_mapping(child_pid, uid=self._uid, gid=self._gid) # signal child to continue @@ -1014,8 +1023,8 @@ def check_child_exit_code(): def add_timestamps_and_EOF(): if timestamp: -# ZZZZZZZ Here is where I need grandchild_proc, but it lost in the layers. - for line in grandchild_proc.stdout: + read_from_grandchild = os.fdopen(stdout_from_grandchild, 'r', encoding='utf-8') + for line in read_from_grandchild: CPU = cgroups.read_cputime() WC = time.monotonic() - parent_setup[1] print(f"{CPU:.4f}/{WC:.4f}\t{line.strip()}", file=stdout) @@ -1023,8 +1032,9 @@ def add_timestamps_and_EOF(): CPU = cgroups.read_cputime() WC = time.monotonic() - parent_setup[1] print(f"{CPU:.4f}/{WC:.4f}\tEOF", file=stdout) + os.close(stdout_from_grandchild) - def wait_for_grandchild(): + def wait_for_grandchild(): # This is in the parent add_timestamps_and_EOF() # 1024 bytes ought to be enough for everyone^Wour pickled result try: From 8da5e013d1c1c151df630d16cd341c959b49b4d5 Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Thu, 30 Oct 2025 16:42:28 +0000 Subject: [PATCH 19/21] checked timestamp if add-eof --- benchexec/runexecutor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index 85b74902f..84f30cda3 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -144,21 +144,18 @@ def main(argv=None): dest="cleanup", help="do not delete files created by the tool in temp directory", ) - io_args.add_argument( "--no-output-header", action="store_true", dest="nowriteheader", help="suppress header in tool output log", ) - io_args.add_argument( "--timestamp", action="store_true", dest="timestamp", help="timestamp each line of stdout/stderr from the tool", ) - io_args.add_argument( "--add-eof", action="store_true", @@ -245,6 +242,9 @@ def main(argv=None): else: stdin = None + if options.addeof and not options.timestamp: + parser.error("Cannot add EOF without timestamps.") + cgroup_subsystems = set(options.require_cgroup_subsystem) cgroup_values = {} for arg in options.cgroup_values: From 72e17bb5046be3bd5111a602144dac89cc113126 Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Thu, 30 Oct 2025 18:03:50 +0000 Subject: [PATCH 20/21] tidy timestamps --- benchexec/baseexecutor.py | 24 ++++++++++++------------ benchexec/containerexecutor.py | 18 +++--------------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/benchexec/baseexecutor.py b/benchexec/baseexecutor.py index 7150d64e2..d1fda657f 100644 --- a/benchexec/baseexecutor.py +++ b/benchexec/baseexecutor.py @@ -127,19 +127,9 @@ def pre_subprocess(): text=True, ) - def add_timestamps_and_EOF(): - if timestamp: - for line in p.stdout: - CPU = cgroups.read_cputime() - WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\t{line.strip()}", file=stdout) - if addeof: - CPU = cgroups.read_cputime() - WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\tEOF", file=stdout) - def wait_and_get_result(): - add_timestamps_and_EOF() + if timestamp: + self._add_timestamps_and_EOF(cgroups,p.stdout,parent_setup[1],addeof) exitcode, ru_child = self._wait_for_process(p.pid, args[0]) p.poll() @@ -150,6 +140,16 @@ def wait_and_get_result(): return p.pid, cgroups, wait_and_get_result + def _add_timestamps_and_EOF(self,cgroups,tool_stdout,time_started,addeof): + for line in tool_stdout: + CPU = cgroups.read_cputime() + WC = time.monotonic() - time_started + print(f"{CPU:.4f}/{WC:.4f}\t{line.strip()}") + if addeof: + CPU = cgroups.read_cputime() + WC = time.monotonic() - time_started + print(f"{CPU:.4f}/{WC:.4f}\tEOF") + def _wait_for_process(self, pid, name): """Wait for the given process to terminate. @return tuple of exit code and resource usage diff --git a/benchexec/containerexecutor.py b/benchexec/containerexecutor.py index 54af7110b..5f4864d16 100644 --- a/benchexec/containerexecutor.py +++ b/benchexec/containerexecutor.py @@ -680,7 +680,6 @@ def grandchild(): raise finally: # close remaining ends of pipe -# ZZZZZ os.close(stdout_from_grandchild) os.close(from_parent) os.close(to_parent) @@ -715,7 +714,6 @@ def child(): stdin, stdout, stderr, -# ZZZZ stdout_to_grandparent, } - {None} container.close_open_fds(keep_files=necessary_fds) @@ -794,7 +792,7 @@ def child(): except (OSError, RuntimeError) as e: logging.critical("Cannot start process: %s", e) return CHILD_OSERROR -# ZZZZZ + # stdout_from_grandchild was closed earlier os.close(stdout_to_grandparent) @@ -1021,21 +1019,11 @@ def check_child_exit_code(): os.close(from_grandchild) os.close(to_grandchild) - def add_timestamps_and_EOF(): + def wait_for_grandchild(): # This is in the parent if timestamp: read_from_grandchild = os.fdopen(stdout_from_grandchild, 'r', encoding='utf-8') - for line in read_from_grandchild: - CPU = cgroups.read_cputime() - WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\t{line.strip()}", file=stdout) - if addeof: - CPU = cgroups.read_cputime() - WC = time.monotonic() - parent_setup[1] - print(f"{CPU:.4f}/{WC:.4f}\tEOF", file=stdout) + self._add_timestamps_and_EOF(cgroups,read_from_grandchild,parent_setup[1],addeof) os.close(stdout_from_grandchild) - - def wait_for_grandchild(): # This is in the parent - add_timestamps_and_EOF() # 1024 bytes ought to be enough for everyone^Wour pickled result try: received = os.read(from_grandchild_copy, 1024) From c5ab2d614b2e0e5f37df169ed819318698d2e7bc Mon Sep 17 00:00:00 2001 From: Geoff Sutcliffe <2geoff@pobox.com> Date: Thu, 30 Oct 2025 18:05:49 +0000 Subject: [PATCH 21/21] Ruff-ians --- benchexec/containerexecutor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/benchexec/containerexecutor.py b/benchexec/containerexecutor.py index 5f4864d16..1e450b453 100644 --- a/benchexec/containerexecutor.py +++ b/benchexec/containerexecutor.py @@ -21,7 +21,6 @@ import sys import tempfile import traceback -import time from benchexec import __version__ from benchexec import baseexecutor