Skip to content

Commit

Permalink
Run sio2jail self-check with Sio2jailExecutor
Browse files Browse the repository at this point in the history
We currently don't pass enough cmdline arguments to sio2jail self-check,
which makes it fail erroneously on some system configurations.
This change prevents that from happening, by using Sio2jailExecutor to
run the self check.

Notably this change required adding the original cmdline to
ExecutionResult for logging errors later - this feels like a leaky
abstraction, but that class actually already carries the process exit
signal and stderr.

Also improved sio2jail related error messages for better user experience.
  • Loading branch information
j4b6ski committed Jan 19, 2025
1 parent 6440173 commit 5c527f6
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 44 deletions.
4 changes: 3 additions & 1 deletion src/sinol_make/executors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ def execute(self, command: List[str], time_limit, hard_time_limit, memory_limit,
"""

command = self._wrap_command(command, result_file_path, time_limit, memory_limit)
tle, mle, return_code, proc_stderr = self._execute(command, time_limit, hard_time_limit, memory_limit,
cmdline = " ".join(command)
tle, mle, return_code, proc_stderr = self._execute(cmdline, time_limit, hard_time_limit, memory_limit,
result_file_path, executable, execution_dir, stdin, stdout,
stderr, fds_to_close, *args, **kwargs)
result = self._parse_result(tle, mle, return_code, result_file_path)
result.Cmdline = cmdline
if not result.Stderr:
result.Stderr = proc_stderr
if tle:
Expand Down
4 changes: 2 additions & 2 deletions src/sinol_make/executors/detailed.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ class DetailedExecutor(BaseExecutor):
def _wrap_command(self, command: List[str], result_file_path: str, time_limit: int, memory_limit: int) -> List[str]:
return command

def _execute(self, command: List[str], time_limit: int, hard_time_limit: int, memory_limit: int,
def _execute(self, cmdline: str, time_limit: int, hard_time_limit: int, memory_limit: int,
result_file_path: str, executable: str, execution_dir: str, stdin: int, stdout: int,
stderr: Union[None, int], fds_to_close: Union[None, List[int]],
*args, **kwargs) -> Tuple[bool, bool, int, List[str]]:
timeout = False
mem_used = 0
if stderr is None:
stderr = subprocess.PIPE
process = subprocess.Popen(" ".join(command), shell=True, *args, stdin=stdin, stdout=stdout, stderr=stderr,
process = subprocess.Popen(cmdline, shell=True, *args, stdin=stdin, stdout=stdout, stderr=stderr,
preexec_fn=os.setpgrp, cwd=execution_dir, **kwargs)
if fds_to_close is not None:
for fd in fds_to_close:
Expand Down
24 changes: 15 additions & 9 deletions src/sinol_make/executors/sio2jail.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import signal
import subprocess
import sys
import traceback
from typing import List, Tuple, Union

from sinol_make import util
Expand All @@ -22,18 +23,18 @@ def _wrap_command(self, command: List[str], result_file_path: str, time_limit: i
'--rtimelimit', f'{int(16 * time_limit + 1000)}ms', '--memory-limit', f'{int(memory_limit)}K',
'--output-limit', '51200K', '--output', 'oiaug', '--stderr', '--'] + command

def _execute(self, command: List[str], time_limit: int, hard_time_limit: int, memory_limit: int,
def _execute(self, cmdline: str, time_limit: int, hard_time_limit: int, memory_limit: int,
result_file_path: str, executable: str, execution_dir: str, stdin: int, stdout: int,
stderr: Union[None, int], fds_to_close: Union[None, List[int]],
*args, **kwargs) -> Tuple[bool, bool, int, List[str]]:
env = os.environ.copy()
env['UNDER_SIO2JAIL'] = "1"
with open(result_file_path, "w") as result_file:
try:
process = subprocess.Popen(' '.join(command), *args, shell=True, stdin=stdin, stdout=stdout, env=env,
process = subprocess.Popen(cmdline, *args, shell=True, stdin=stdin, stdout=stdout, env=env,
stderr=result_file, preexec_fn=os.setpgrp, cwd=execution_dir, **kwargs)
except TypeError as e:
print(util.error("Invalid command: " + str(command)))
print(util.error(f"Invalid command: `{cmdline}`"))
raise e
if fds_to_close is not None:
for fd in fds_to_close:
Expand All @@ -55,12 +56,17 @@ def _parse_result(self, _, mle, return_code, result_file_path) -> ExecutionResul
with open(result_file_path, "r") as result_file:
lines = result_file.readlines()

result.stderr = lines[:-2]

status, code, time_ms, _, memory_kb, _ = lines[-2].strip().split()
message = lines[-1].strip()
result.Time = int(time_ms)
result.Memory = int(memory_kb)
try:
result.stderr = lines[:-2]
status, code, time_ms, _, memory_kb, _ = lines[-2].strip().split()
message = lines[-1].strip()
result.Time = int(time_ms)
result.Memory = int(memory_kb)
except:
output = "".join(lines)
util.exit_with_error("Could not parse sio2jail output:"
f"\n---\n{output}"
f"\n---\n{traceback.format_exc()}")

# ignoring `status` is weird, but sio2 does it this way
if message == 'ok':
Expand Down
4 changes: 2 additions & 2 deletions src/sinol_make/executors/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ def _wrap_command(self, command: List[str], result_file_path: str, time_limit: i

return [f'{time_name}', '-f', '"%U\\n%M\\n%x"', '-o', result_file_path] + command

def _execute(self, command: List[str], time_limit: int, hard_time_limit: int, memory_limit: int,
def _execute(self, cmdline: str, time_limit: int, hard_time_limit: int, memory_limit: int,
result_file_path: str, executable: str, execution_dir: str, stdin: int, stdout: int,
stderr: Union[None, int], fds_to_close: Union[None, List[int]],
*args, **kwargs) -> Tuple[bool, bool, int, List[str]]:
timeout = False
mem_limit_exceeded = False
if stderr is None:
stderr = subprocess.PIPE
process = subprocess.Popen(" ".join(command), shell=True, *args, stdin=stdin, stdout=stdout, stderr=stderr,
process = subprocess.Popen(cmdline, shell=True, *args, stdin=stdin, stdout=stdout, stderr=stderr,
preexec_fn=os.setpgrp, cwd=execution_dir, **kwargs)
if fds_to_close is not None:
for fd in fds_to_close:
Expand Down
88 changes: 60 additions & 28 deletions src/sinol_make/sio2jail/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import requests

from sinol_make import util

from sinol_make.executors.sio2jail import Sio2jailExecutor
from sinol_make.structs.status_structs import Status

def sio2jail_supported():
return util.is_linux()
Expand Down Expand Up @@ -82,35 +83,66 @@ def check_perf_counters_enabled():
with open('/proc/sys/kernel/perf_event_paranoid') as f:
perf_event_paranoid = int(f.read())

sio2jail = get_default_sio2jail_path()
executor = Sio2jailExecutor(get_default_sio2jail_path())
test_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'perf_test.py')
python_executable = sys.executable

# subprocess.Pipe is not used, because than the code would hang on process.communicate()
with tempfile.TemporaryFile() as tmpfile:
process = subprocess.Popen([sio2jail, '--mount-namespace', 'off', '--', python_executable, test_file],
stdout=tmpfile, stderr=subprocess.DEVNULL)
process.wait()
tmpfile.seek(0)
output = tmpfile.read().decode('utf-8')
process.terminate()

if output != "Test string\n":
command = [python_executable, test_file]
time_limit = 1000
memory_limit = 65536

with (
tempfile.NamedTemporaryFile() as sio2jail_result,
tempfile.TemporaryFile() as command_stdout,
tempfile.TemporaryFile() as command_stderr
):
result = executor.execute(
command=command,
time_limit=time_limit,
hard_time_limit=None,
memory_limit=memory_limit,
result_file_path=sio2jail_result.name,
executable=None,
execution_dir=None,
stdout=command_stdout,
stderr=command_stderr)
command_stdout.seek(0)
output_str = command_stdout.read().decode('utf-8')
command_stderr.seek(0)
error_str = command_stderr.read().decode('utf-8')
sio2jail_result.seek(0)
result_raw = sio2jail_result.read().decode('utf-8')

expected_output = "Test Successful!\n"
if result.Status != Status.OK or output_str != expected_output or error_str:
max_perf_event_paranoid = 2
if perf_event_paranoid > max_perf_event_paranoid:
hint = (f"You have `kernel.perf_event_paranoid` set to `{perf_event_paranoid}`"
", which might be preventing userspace perf counters from working.\n"
f"Try running: `sudo sysctl kernel.perf_event_paranoid={max_perf_event_paranoid}`\n"
"If that fixes the problem, you can set this permanently by adding "
f"`kernel.perf_event_paranoid={max_perf_event_paranoid}` to `/etc/sysctl.conf` and rebooting.\n")
hint = (f"You have sysctl kernel.perf_event_paranoid = {perf_event_paranoid}"
"\nThis might restrict access to instruction counting."
"\nTry relaxing this setting by running:"
f"\n\tsudo sysctl kernel.perf_event_paranoid={max_perf_event_paranoid}"
"\nIf that fixes the problem, you can set this permanently by adding:"
f"\n\tkernel.perf_event_paranoid={max_perf_event_paranoid}"
"\nto /etc/sysctl.conf and rebooting."
)
else:
hint = ("Your kernel, drivers, or hardware might be too old.\n"
"Check if the Intel PMU driver is loaded: `dmesg | grep -i 'perf'`\n"
"You can also check if the perf tool works correctly: `perf stat -e instructions:u -- sleep 0`\n"
"(if perf can't be found, it might be located in: `/usr/lib/linux-tools/*/perf`).\n")
cmdline = " ".join(process.args)
util.exit_with_error(f"Failed performance counters test: `{cmdline}`\n"
+ hint +
"Alternatively, you can run sinol-make without instruction counting"
", by adding the `--time-tool time` flag.\n"
"For more details, see https://github.com/sio2project/sio2jail#running.\n")
hint = ("Your kernel, drivers, or hardware might be unsupported."
"\nDiagnose this further by trying the following commands:"
"\n1. Check if the `perf` tool is able to read performance counters correctly:"
"\n\tperf stat -e instructions:u -- sleep 0"
"\nIf `perf` can't be found, it might be located in: /usr/lib/linux-tools/*/perf"
"\n2. Check if the Performance Monitoring Unit driver was successfully loaded:"
"\n\tdmesg | grep PMU"
)
opt_stdout_hint = f"\nCommand stdout (expected {repr(expected_output)}):\n---\n{output_str}" if output_str != expected_output else ""
opt_stderr_hint = f"\nCommand stderr (expected none):\n---\n{error_str}" if error_str else ""
opt_sio2jail_hint = f"\nsio2jail result:\n---\n{result_raw}" if result.Status != Status.OK else ""
util.exit_with_error("Failed sio2jail instruction counting test!"
f"\n\nTest command:\n---\n{result.Cmdline}\n"
f"{opt_stdout_hint}"
f"{opt_stderr_hint}"
f"{opt_sio2jail_hint}"
f"\n\n{hint}"
"\n\nYou can also disable instruction counting by adding the `--time-tool time` flag."
"\nThis will make measured solution run times significantly different from SIO2."
"\nFor more details, see https://github.com/sio2project/sio2jail#running."
)
2 changes: 1 addition & 1 deletion src/sinol_make/sio2jail/perf_test.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
print("Test string")
print("Test Successful!")
7 changes: 6 additions & 1 deletion src/sinol_make/structs/status_structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,11 @@ class ExecutionResult:
Comment: str
# Stderr of the program (used for checkers/interactors)
Stderr: List[str]
# Original command line that was run
Cmdline: str

def __init__(self, status=None, Time=None, Memory=None, Points=0, Error=None, Fail=False, ExitSignal=0, Comment="",
Stderr=None):
Stderr=None, Cmdline=None):
self.Status = status
self.Time = Time
self.Memory = Memory
Expand All @@ -109,6 +111,7 @@ def __init__(self, status=None, Time=None, Memory=None, Points=0, Error=None, Fa
self.ExitSignal = ExitSignal
self.Comment = Comment
self.Stderr = Stderr if Stderr is not None else []
self.Cmdline = Cmdline

@staticmethod
def from_dict(dict):
Expand All @@ -122,6 +125,7 @@ def from_dict(dict):
ExitSignal=dict.get("ExitSignal", 0),
Comment=dict.get("Comment", ""),
Stderr=dict.get("Stderr", []),
Cmdline=dict.get("Cmdline", ""),
)

def to_dict(self):
Expand All @@ -135,4 +139,5 @@ def to_dict(self):
"ExitSignal": self.ExitSignal,
"Comment": self.Comment,
"Stderr": self.Stderr,
"Cmdline": self.Cmdline,
}

0 comments on commit 5c527f6

Please sign in to comment.