This method executes the command line and waits for the termination of it,
        handling all setup and cleanup, but does not check whether arguments are valid.

def _execute(self, args, output_filename, error_filename, stdin, write_header,
                 hardtimelimit, softtimelimit, walltimelimit, memlimit,
                 cores, memory_nodes,
                 cgroup_values,
                 environments, workingDir, max_output_size,
                 files_count_limit, files_size_limit,
                 **kwargs):
        """
        This method executes the command line and waits for the termination of it,
        handling all setup and cleanup, but does not check whether arguments are valid.
        """

        if self._energy_measurement is not None:
            # Calculate which packages we should use for energy measurements
            if cores is None:
                packages = True # We use all cores and thus all packages
            else:
                all_siblings = set(util.flatten(
                    resources.get_cores_of_same_package_as(core) for core in cores))
                if all_siblings == set(cores):
                    packages = set(resources.get_cpu_package_for_core(core) for core in cores)
                else:
                    # Disable energy measurements because we use only parts of a CPU
                    packages = None

        def preParent():
            """Setup that is executed in the parent process immediately before the actual tool is started."""
            # start measurements
            if self._energy_measurement is not None and packages:
                self._energy_measurement.start()
            walltime_before = util.read_monotonic_time()
            return walltime_before

        def postParent(preParent_result, exit_code, base_path):
            """Cleanup that is executed in the parent process immediately after the actual tool terminated."""
            # finish measurements
            walltime_before = preParent_result
            walltime = util.read_monotonic_time() - walltime_before
            energy = self._energy_measurement.stop() if self._energy_measurement else None

            if exit_code.value not in [0, 1]:
                _get_debug_output_after_crash(output_filename, base_path)

            return (walltime, energy)

        def preSubprocess():
            """Setup that is executed in the forked process before the actual tool is started."""
            os.setpgrp() # make subprocess to group-leader
            os.nice(5) # increase niceness of subprocess
            self._setup_ulimit_time_limit(hardtimelimit, cgroups)

        # preparations that are not time critical
        cgroups = self._setup_cgroups(cores, memlimit, memory_nodes, cgroup_values)
        temp_dir = self._create_temp_dir()
        run_environment = self._setup_environment(environments)
        outputFile = self._setup_output_file(output_filename, args, write_header=write_header)
        if error_filename is None:
            errorFile = outputFile
        else:
            errorFile = self._setup_output_file(error_filename, args, write_header=write_header)

        timelimitThread = None
        oomThread = None
        file_hierarchy_limit_thread = None
        pid = None
        returnvalue = 0
        ru_child = None
        self._termination_reason = None
        result = collections.OrderedDict()

        throttle_check = systeminfo.CPUThrottleCheck(cores)
        swap_check = systeminfo.SwapCheck()

        logging.debug('Starting process.')

        try:
            pid, result_fn = self._start_execution(args=args,
                stdin=stdin, stdout=outputFile, stderr=errorFile,
                env=run_environment, cwd=workingDir, temp_dir=temp_dir,
                memlimit=memlimit, memory_nodes=memory_nodes,
                cgroups=cgroups,
                parent_setup_fn=preParent, child_setup_fn=preSubprocess,
                parent_cleanup_fn=postParent,
                **kwargs)

            with self.SUB_PROCESS_PIDS_LOCK:
                self.SUB_PROCESS_PIDS.add(pid)

            timelimitThread = self._setup_cgroup_time_limit(
                hardtimelimit, softtimelimit, walltimelimit, cgroups, cores, pid)
            oomThread = self._setup_cgroup_memory_limit(memlimit, cgroups, pid)
            file_hierarchy_limit_thread = self._setup_file_hierarchy_limit(
                files_count_limit, files_size_limit, temp_dir, cgroups, pid)

            returnvalue, ru_child, (walltime, energy) = result_fn() # blocks until process has terminated
            result['walltime'] = walltime
        finally:
            # cleanup steps that need to get executed even in case of failure
            logging.debug('Process terminated, exit code %s.', returnvalue)

            with self.SUB_PROCESS_PIDS_LOCK:
                self.SUB_PROCESS_PIDS.discard(pid)

            if timelimitThread:
                timelimitThread.cancel()

            if oomThread:
                oomThread.cancel()

            if file_hierarchy_limit_thread:
                file_hierarchy_limit_thread.cancel()

            # Kill all remaining processes (needs to come early to avoid accumulating more CPU time)
            cgroups.kill_all_tasks(self._kill_process0)

            # normally subprocess closes file, we do this again after all tasks terminated
            outputFile.close()
            if errorFile is not outputFile:
                errorFile.close()

            # measurements are not relevant in case of failure, but need to come before cgroup cleanup
            self._get_cgroup_measurements(cgroups, ru_child, result)
            logging.debug("Cleaning up cgroups.")
            cgroups.remove()

            self._cleanup_temp_dir(temp_dir)

            if timelimitThread:
                _try_join_cancelled_thread(timelimitThread)
            if oomThread:
                _try_join_cancelled_thread(oomThread)
            if file_hierarchy_limit_thread:
                _try_join_cancelled_thread(file_hierarchy_limit_thread)

            if self._energy_measurement:
                self._energy_measurement.stop()

        # cleanup steps that are only relevant in case of success
        if throttle_check.has_throttled():
            logging.warning('CPU throttled itself during benchmarking due to overheating. '
                            'Benchmark results are unreliable!')
        if swap_check.has_swapped():
            logging.warning('System has swapped during benchmarking. '
                            'Benchmark results are unreliable!')

        if error_filename is not None:
            _reduce_file_size_if_necessary(error_filename, max_output_size)

        _reduce_file_size_if_necessary(output_filename, max_output_size)

        result['exitcode'] = returnvalue
        if energy:
            if packages == True:
                result['cpuenergy'] = energy
            else:
                result['cpuenergy'] = {pkg: energy[pkg] for pkg in energy if pkg in packages}
        if self._termination_reason:
            result['terminationreason'] = self._termination_reason
        elif memlimit and 'memory' in result and result['memory'] >= memlimit:
            # The kernel does not always issue OOM notifications and thus the OOMHandler
            # does not always run even in case of OOM. We detect this there and report OOM.
            result['terminationreason'] = 'memory'

        return result