def main(argv=None):
    """
    A simple command-line interface for the runexecutor module of BenchExec.
    """
    if argv is None:
        argv = sys.argv

    # parse options
    parser = argparse.ArgumentParser(
        fromfile_prefix_chars='@',
        description=
        """Execute a command with resource limits and measurements.
           Command-line parameters can additionally be read from a file if file name prefixed with '@' is given as argument.
           Part of BenchExec: https://github.com/sosy-lab/benchexec/""")

    resource_args = parser.add_argument_group("optional arguments for resource limits")
    resource_args.add_argument("--memlimit", type=util.parse_memory_value, metavar="BYTES",
        help="memory limit in bytes")
    resource_args.add_argument("--timelimit", type=util.parse_timespan_value, metavar="SECONDS",
        help="CPU time limit in seconds")
    resource_args.add_argument("--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)')
    resource_args.add_argument("--walltimelimit", type=util.parse_timespan_value, metavar="SECONDS",
        help='wall time limit in seconds (default is CPU time limit plus a few seconds)')
    resource_args.add_argument("--cores", type=util.parse_int_list, metavar="N,M-K",
        help="list of CPU cores to use")
    resource_args.add_argument("--memoryNodes", type=util.parse_int_list, metavar="N,M-K",
        help="list of memory nodes to use")

    io_args = parser.add_argument_group("optional arguments for run I/O")
    io_args.add_argument("--input", metavar="FILE",
        help="name of file used as stdin for command "
            "(default: /dev/null; use - for stdin passthrough)")
    io_args.add_argument("--output", default="output.log", metavar="FILE",
        help="name of file where command output (stdout and stderr) is written")
    io_args.add_argument("--maxOutputSize", type=util.parse_memory_value, metavar="BYTES",
        help="shrink output file to approximately this size if necessary "
            "(by removing lines from the middle of the output)")
    io_args.add_argument("--filesCountLimit", type=int, metavar="COUNT",
        help="maximum number of files the tool may write to (checked periodically, counts only files written in container mode or to temporary directories, only supported with --no-tmpfs)")
    io_args.add_argument("--filesSizeLimit", type=util.parse_memory_value, metavar="BYTES",
        help="maximum size of files the tool may write (checked periodically, counts only files written in container mode or to temporary directories, only supported with --no-tmpfs)")
    io_args.add_argument("--skip-cleanup", action="store_false", dest="cleanup",
        help="do not delete files created by the tool in temp directory")

    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("--container", action='store_true',
        help="force isolation of run in container (future default starting with BenchExec 2.0)")
    container_on_args.add_argument("--no-container", action='store_true',
        help="disable use of containers for isolation of runs (current default)")
    containerexecutor.add_basic_container_args(container_args)
    containerexecutor.add_container_output_args(container_args)

    environment_args = parser.add_argument_group("optional arguments for run environment")
    environment_args.add_argument("--require-cgroup-subsystem", action="append", default=[], metavar="SUBSYSTEM",
        help="additional cgroup system that should be enabled for runs "
            "(may be specified multiple times)")
    environment_args.add_argument("--set-cgroup-value", action="append", dest="cgroup_values", default=[],
        metavar="SUBSYSTEM.OPTION=VALUE",
        help="additional cgroup values that should be set for runs (e.g., 'cpu.shares=1000')")
    environment_args.add_argument("--dir", metavar="DIR",
        help="working directory for executing the command (default is current directory)")
    environment_args.add_argument("--user", metavar="USER",
        help="execute tool under given user account (needs password-less sudo setup, "
            "not supported in combination with --container)")

    baseexecutor.add_basic_executor_options(parser)

    options = parser.parse_args(argv[1:])
    baseexecutor.handle_basic_executor_options(options, parser)

    if options.container:
        if options.user is not None:
            sys.exit("Cannot use --user in combination with --container.")
        container_options = containerexecutor.handle_basic_container_args(options, parser)
        container_output_options = containerexecutor.handle_container_output_args(options, parser)
        if container_options['container_tmpfs'] and (options.filesCountLimit or options.filesSizeLimit):
            parser.error("Files-count limit and files-size limit are not supported if tmpfs is used in container. Use --no-tmpfs to make these limits work or disable them (typically they are unnecessary if a tmpfs is used).")
    else:
        container_options = {}
        container_output_options = {}
        if options.user is not None:
            logging.warning(
                "Executing benchmarks at another user with --user is deprecated and may be removed in the future. "
                "Consider using the container mode instead for isolating runs "
                "(cf. https://github.com/sosy-lab/benchexec/issues/215).")
        elif not options.no_container:
            logging.warning(
                "Neither --container or --no-container was specified, "
                "not using containers for isolation of runs. "
                "Either specify --no-container to silence this warning, "
                "or specify --container to use containers for better isolation of runs "
                "(this will be the default starting with BenchExec 2.0). "
                "Please read https://github.com/sosy-lab/benchexec/blob/master/doc/container.md "
                "for more information.")

    # For integrating into some benchmarking frameworks,
    # there is a DEPRECATED special mode
    # where the first and only command-line argument is a serialized dict
    # with additional options
    env = {}
    if len(options.args) == 1 and options.args[0].startswith("{"):
        data = eval(options.args[0])
        options.args = data["args"]
        env = data.get("env", {})
        options.debug = data.get("debug", options.debug)
        if "maxLogfileSize" in data:
            try:
                options.maxOutputSize = int(data["maxLogfileSize"]) * _BYTE_FACTOR * _BYTE_FACTOR # MB to bytes
            except ValueError:
                options.maxOutputSize = util.parse_memory_value(data["maxLogfileSize"])

    if options.input == '-':
        stdin = sys.stdin
    elif options.input is not None:
        if options.input == options.output:
            parser.error("Input and output files cannot be the same.")
        try:
            stdin = open(options.input, 'rt')
        except IOError as e:
            parser.error(e)
    else:
        stdin = None

    cgroup_subsystems = set(options.require_cgroup_subsystem)
    cgroup_values = {}
    for arg in options.cgroup_values:
        try:
            key, value = arg.split("=", 1)
            subsystem, option = key.split(".", 1)
            if not subsystem or not option:
                raise ValueError()
        except ValueError:
            parser.error(
                'Cgroup value "{}" has invalid format, needs to be "subsystem.option=value".'
                    .format(arg))
        cgroup_values[(subsystem, option)] = value
        cgroup_subsystems.add(subsystem)

    executor = RunExecutor(user=options.user, cleanup_temp_dir=options.cleanup,
                           additional_cgroup_subsystems=list(cgroup_subsystems),
                           use_namespaces=options.container, **container_options)

    # ensure that process gets killed on interrupt/kill signal
    def signal_handler_kill(signum, frame):
        executor.stop()
    signal.signal(signal.SIGTERM, signal_handler_kill)
    signal.signal(signal.SIGINT,  signal_handler_kill)

    formatted_args = " ".join(map(util.escape_string_shell, options.args))
    logging.info('Starting command %s', formatted_args)
    if options.container and options.output_directory and options.result_files:
        logging.info('Writing output to %s and result files to %s',
                     util.escape_string_shell(options.output),
                     util.escape_string_shell(options.output_directory))
    else:
        logging.info('Writing output to %s', util.escape_string_shell(options.output))

    # actual run execution
    try:
        result = executor.execute_run(
                            args=options.args,
                            output_filename=options.output,
                            stdin=stdin,
                            hardtimelimit=options.timelimit,
                            softtimelimit=options.softtimelimit,
                            walltimelimit=options.walltimelimit,
                            cores=options.cores,
                            memlimit=options.memlimit,
                            memory_nodes=options.memoryNodes,
                            cgroupValues=cgroup_values,
                            environments=env,
                            workingDir=options.dir,
                            maxLogfileSize=options.maxOutputSize,
                            files_count_limit=options.filesCountLimit,
                            files_size_limit=options.filesSizeLimit,
                            **container_output_options)
    finally:
        if stdin:
            stdin.close()

    executor.check_for_new_files_in_home()

    # exit_code is a special number:
    exit_code = util.ProcessExitCode.from_raw(result['exitcode'])

    def print_optional_result(key, unit=''):
        if key in result:
            # avoid unicode literals such that the string can be parsed by Python 3.2
            print(key + "=" + str(result[key]).replace("'u", '') + unit)

    # output results
    print_optional_result('terminationreason')
    print("exitcode=" + str(exit_code.raw))
    if exit_code.value is not None:
        print("returnvalue=" + str(exit_code.value))
    if exit_code.signal is not None:
        print("exitsignal=" + str(exit_code.signal))
    print("walltime=" + str(result['walltime']) + "s")
    print("cputime=" + str(result['cputime']) + "s")
    for key in sorted(result.keys()):
        if key.startswith('cputime-'):
            print("{}={:.9f}s".format(key, result[key]))
    print_optional_result('memory')
    print_optional_result('blkio-read', 'B')
    print_optional_result('blkio-write', 'B')
    energy = intel_cpu_energy.format_energy_results(result.get('cpuenergy'))
    for energy_key, energy_value in energy.items():
        print('{}={}J'.format(energy_key, energy_value))
def main ( argv = None ) : if argv is None : argv = sys . argv parser = argparse . ArgumentParser ( fromfile_prefix_chars = '@' , description = """Execute a command with resource limits and measurements.
           Command-line parameters can additionally be read from a file if file name prefixed with '@' is given as argument.
           Part of BenchExec: https://github.com/sosy-lab/benchexec/""" ) resource_args = parser . add_argument_group ( "optional arguments for resource limits" ) resource_args . add_argument ( "--memlimit" , type = util . parse_memory_value , metavar = "BYTES" , help = "memory limit in bytes" ) resource_args . add_argument ( "--timelimit" , type = util . parse_timespan_value , metavar = "SECONDS" , help = "CPU time limit in seconds" ) resource_args . add_argument ( "--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)' ) resource_args . add_argument ( "--walltimelimit" , type = util . parse_timespan_value , metavar = "SECONDS" , help = 'wall time limit in seconds (default is CPU time limit plus a few seconds)' ) resource_args . add_argument ( "--cores" , type = util . parse_int_list , metavar = "N,M-K" , help = "list of CPU cores to use" ) resource_args . add_argument ( "--memoryNodes" , type = util . parse_int_list , metavar = "N,M-K" , help = "list of memory nodes to use" ) io_args = parser . add_argument_group ( "optional arguments for run I/O" ) io_args . add_argument ( "--input" , metavar = "FILE" , help = "name of file used as stdin for command " "(default: /dev/null; use - for stdin passthrough)" ) io_args . add_argument ( "--output" , default = "output.log" , metavar = "FILE" , help = "name of file where command output (stdout and stderr) is written" ) io_args . add_argument ( "--maxOutputSize" , type = util . parse_memory_value , metavar = "BYTES" , help = "shrink output file to approximately this size if necessary " "(by removing lines from the middle of the output)" ) io_args . add_argument ( "--filesCountLimit" , type = int , metavar = "COUNT" , help = "maximum number of files the tool may write to (checked periodically, counts only files written in container mode or to temporary directories, only supported with --no-tmpfs)" ) io_args . add_argument ( "--filesSizeLimit" , type = util . parse_memory_value , metavar = "BYTES" , help = "maximum size of files the tool may write (checked periodically, counts only files written in container mode or to temporary directories, only supported with --no-tmpfs)" ) io_args . add_argument ( "--skip-cleanup" , action = "store_false" , dest = "cleanup" , help = "do not delete files created by the tool in temp directory" ) 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 ( "--container" , action = 'store_true' , help = "force isolation of run in container (future default starting with BenchExec 2.0)" ) container_on_args . add_argument ( "--no-container" , action = 'store_true' , help = "disable use of containers for isolation of runs (current default)" ) containerexecutor . add_basic_container_args ( container_args ) containerexecutor . add_container_output_args ( container_args ) environment_args = parser . add_argument_group ( "optional arguments for run environment" ) environment_args . add_argument ( "--require-cgroup-subsystem" , action = "append" , default = [ ] , metavar = "SUBSYSTEM" , help = "additional cgroup system that should be enabled for runs " "(may be specified multiple times)" ) environment_args . add_argument ( "--set-cgroup-value" , action = "append" , dest = "cgroup_values" , default = [ ] , metavar = "SUBSYSTEM.OPTION=VALUE" , help = "additional cgroup values that should be set for runs (e.g., 'cpu.shares=1000')" ) environment_args . add_argument ( "--dir" , metavar = "DIR" , help = "working directory for executing the command (default is current directory)" ) environment_args . add_argument ( "--user" , metavar = "USER" , help = "execute tool under given user account (needs password-less sudo setup, " "not supported in combination with --container)" ) baseexecutor . add_basic_executor_options ( parser ) options = parser . parse_args ( argv [ 1 : ] ) baseexecutor . handle_basic_executor_options ( options , parser ) if options . container : if options . user is not None : sys . exit ( "Cannot use --user in combination with --container." ) container_options = containerexecutor . handle_basic_container_args ( options , parser ) container_output_options = containerexecutor . handle_container_output_args ( options , parser ) if container_options [ 'container_tmpfs' ] and ( options . filesCountLimit or options . filesSizeLimit ) : parser . error ( "Files-count limit and files-size limit are not supported if tmpfs is used in container. Use --no-tmpfs to make these limits work or disable them (typically they are unnecessary if a tmpfs is used)." ) else : container_options = { } container_output_options = { } if options . user is not None : logging . warning ( "Executing benchmarks at another user with --user is deprecated and may be removed in the future. " "Consider using the container mode instead for isolating runs " "(cf. https://github.com/sosy-lab/benchexec/issues/215)." ) elif not options . no_container : logging . warning ( "Neither --container or --no-container was specified, " "not using containers for isolation of runs. " "Either specify --no-container to silence this warning, " "or specify --container to use containers for better isolation of runs " "(this will be the default starting with BenchExec 2.0). " "Please read https://github.com/sosy-lab/benchexec/blob/master/doc/container.md " "for more information." ) env = { } if len ( options . args ) == 1 and options . args [ 0 ] . startswith ( "{" ) : data = eval ( options . args [ 0 ] ) options . args = data [ "args" ] env = data . get ( "env" , { } ) options . debug = data . get ( "debug" , options . debug ) if "maxLogfileSize" in data : try : options . maxOutputSize = int ( data [ "maxLogfileSize" ] ) * _BYTE_FACTOR * _BYTE_FACTOR except ValueError : options . maxOutputSize = util . parse_memory_value ( data [ "maxLogfileSize" ] ) if options . input == '-' : stdin = sys . stdin elif options . input is not None : if options . input == options . output : parser . error ( "Input and output files cannot be the same." ) try : stdin = open ( options . input , 'rt' ) except IOError as e : parser . error ( e ) else : stdin = None cgroup_subsystems = set ( options . require_cgroup_subsystem ) cgroup_values = { } for arg in options . cgroup_values : try : key , value = arg . split ( "=" , 1 ) subsystem , option = key . split ( "." , 1 ) if not subsystem or not option : raise ValueError ( ) except ValueError : parser . error ( 'Cgroup value "{}" has invalid format, needs to be "subsystem.option=value".' . format ( arg ) ) cgroup_values [ ( subsystem , option ) ] = value cgroup_subsystems . add ( subsystem ) executor = RunExecutor ( user = options . user , cleanup_temp_dir = options . cleanup , additional_cgroup_subsystems = list ( cgroup_subsystems ) , use_namespaces = options . container , ** container_options ) def signal_handler_kill ( signum , frame ) : executor . stop ( ) signal . signal ( signal . SIGTERM , signal_handler_kill ) signal . signal ( signal . SIGINT , signal_handler_kill ) formatted_args = " " . join ( map ( util . escape_string_shell , options . args ) ) logging . info ( 'Starting command %s' , formatted_args ) if options . container and options . output_directory and options . result_files : logging . info ( 'Writing output to %s and result files to %s' , util . escape_string_shell ( options . output ) , util . escape_string_shell ( options . output_directory ) ) else : logging . info ( 'Writing output to %s' , util . escape_string_shell ( options . output ) ) try : result = executor . execute_run ( args = options . args , output_filename = options . output , stdin = stdin , hardtimelimit = options . timelimit , softtimelimit = options . softtimelimit , walltimelimit = options . walltimelimit , cores = options . cores , memlimit = options . memlimit , memory_nodes = options . memoryNodes , cgroupValues = cgroup_values , environments = env , workingDir = options . dir , maxLogfileSize = options . maxOutputSize , files_count_limit = options . filesCountLimit , files_size_limit = options . filesSizeLimit , ** container_output_options ) finally : if stdin : stdin . close ( ) executor . check_for_new_files_in_home ( ) exit_code = util . ProcessExitCode . from_raw ( result [ 'exitcode' ] ) def print_optional_result ( key , unit = '' ) : if key in result : print ( key + "=" + str ( result [ key ] ) . replace ( "'u" , '' ) + unit ) print_optional_result ( 'terminationreason' ) print ( "exitcode=" + str ( exit_code . raw ) ) if exit_code . value is not None : print ( "returnvalue=" + str ( exit_code . value ) ) if exit_code . signal is not None : print ( "exitsignal=" + str ( exit_code . signal ) ) print ( "walltime=" + str ( result [ 'walltime' ] ) + "s" ) print ( "cputime=" + str ( result [ 'cputime' ] ) + "s" ) for key in sorted ( result . keys ( ) ) : if key . startswith ( 'cputime-' ) : print ( "{}={:.9f}s" . format ( key , result [ key ] ) ) print_optional_result ( 'memory' ) print_optional_result ( 'blkio-read' , 'B' ) print_optional_result ( 'blkio-write' , 'B' ) energy = intel_cpu_energy . format_energy_results ( result . get ( 'cpuenergy' ) ) for energy_key , energy_value in energy . items ( ) : print ( '{}={}J' . format ( energy_key , energy_value ) )
A simple command-line interface for the runexecutor module of BenchExec.