From 475ef899dab13ea894717f5b7dc957c421a244e5 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 20 Mar 2026 08:08:20 +0000 Subject: [PATCH 1/6] feat: add security_opt support for execution client containers Add a `security_opt` field to the scenario config that gets passed to Docker's `containers.run()`. This allows setting Docker security options like `seccomp=unconfined` which is needed for profiling tools (e.g., perf/perfcollect) to call `perf_event_open` inside containers. Example usage in scenario config: ```yaml security_opt: - seccomp=unconfined ``` --- src/expb/configs/scenarios.py | 4 ++++ src/expb/payloads/executor/executor.py | 2 ++ src/expb/payloads/executor/executor_config.py | 1 + 3 files changed, 7 insertions(+) diff --git a/src/expb/configs/scenarios.py b/src/expb/configs/scenarios.py index 759ebb2..68fe472 100644 --- a/src/expb/configs/scenarios.py +++ b/src/expb/configs/scenarios.py @@ -161,6 +161,10 @@ class Scenario(BaseModel): description="Extra commands to run in the execution client docker container during the test execution.", default=[], ) + security_opt: list[str] = Field( + description="Docker security options for the execution client container (e.g., seccomp=unconfined).", + default=[], + ) @field_validator("client", mode="before") @classmethod diff --git a/src/expb/payloads/executor/executor.py b/src/expb/payloads/executor/executor.py index 91d725d..35ffafe 100644 --- a/src/expb/payloads/executor/executor.py +++ b/src/expb/payloads/executor/executor.py @@ -230,6 +230,8 @@ def start_execution_client( run_kwargs["cpuset_cpus"] = self.config.resources.cpuset if self.config.resources and self.config.resources.mem_swappiness is not None: run_kwargs["mem_swappiness"] = self.config.resources.mem_swappiness + if self.config.security_opt: + run_kwargs["security_opt"] = self.config.security_opt container = self.config.docker_client.containers.run(**run_kwargs) return container diff --git a/src/expb/payloads/executor/executor_config.py b/src/expb/payloads/executor/executor_config.py index db03245..58b61e5 100644 --- a/src/expb/payloads/executor/executor_config.py +++ b/src/expb/payloads/executor/executor_config.py @@ -62,6 +62,7 @@ def __init__( scenario.extra_volumes ) self.execution_client_extra_commands = scenario.extra_commands + self.security_opt = scenario.security_opt # Executor Additional Tooling config ## Docker client From daa23163da666a2fe47b54a7a98d86e8e7fe4aa8 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 22 Mar 2026 22:02:11 +0000 Subject: [PATCH 2/6] feat: add 30s grace period before stopping execution client --- src/expb/payloads/executor/executor.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/expb/payloads/executor/executor.py b/src/expb/payloads/executor/executor.py index 35ffafe..978fb51 100644 --- a/src/expb/payloads/executor/executor.py +++ b/src/expb/payloads/executor/executor.py @@ -643,6 +643,11 @@ def cleanup_scenario( except docker.errors.NotFound: pass + # Allow execution client time to flush data (e.g. PGO profiles) + # before sending SIGTERM + import time + time.sleep(30) + # Clean execution client container try: execution_client_container = self.config.docker_client.containers.get( From b8af79e7ed6d669c2e4534dfea380850a9454a37 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 22 Mar 2026 22:03:21 +0000 Subject: [PATCH 3/6] fix: remove redundant inline import - time already imported at top --- src/expb/payloads/executor/executor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/expb/payloads/executor/executor.py b/src/expb/payloads/executor/executor.py index 978fb51..fa21924 100644 --- a/src/expb/payloads/executor/executor.py +++ b/src/expb/payloads/executor/executor.py @@ -645,7 +645,6 @@ def cleanup_scenario( # Allow execution client time to flush data (e.g. PGO profiles) # before sending SIGTERM - import time time.sleep(30) # Clean execution client container From 0eb18074aa05c8ce73e505575a1335d4ccaca1ff Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 22 Mar 2026 22:05:37 +0000 Subject: [PATCH 4/6] fix: increase stop timeout to 60s instead of pre-stop sleep WritePGOData only flushes on process exit (SIGTERM handler), not while idle. The 30s sleep before stop was pointless - Nethermind just sits waiting for blocks. Instead increase Docker's SIGTERM-to-SIGKILL timeout from 10s to 60s so the shutdown handler has time to flush. --- src/expb/payloads/executor/executor.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/expb/payloads/executor/executor.py b/src/expb/payloads/executor/executor.py index fa21924..7c88285 100644 --- a/src/expb/payloads/executor/executor.py +++ b/src/expb/payloads/executor/executor.py @@ -643,10 +643,6 @@ def cleanup_scenario( except docker.errors.NotFound: pass - # Allow execution client time to flush data (e.g. PGO profiles) - # before sending SIGTERM - time.sleep(30) - # Clean execution client container try: execution_client_container = self.config.docker_client.containers.get( @@ -654,7 +650,9 @@ def cleanup_scenario( ) execution_client_container.reload() execution_client_volumes = execution_client_container.attrs["Mounts"] - execution_client_container.stop() + # Give execution client 60s after SIGTERM to flush data (e.g. PGO + # profiles via WritePGOData) before Docker sends SIGKILL (default 10s) + execution_client_container.stop(timeout=60) logs_file = ( self.config.outputs_dir / f"{self.config.get_execution_client_name()}.log" From 14b206cf0c52a6f8a3d8a7d381e9e59865d63a3e Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 22 Mar 2026 22:11:11 +0000 Subject: [PATCH 5/6] fix: rename security_opt to execution_client_security_opt for consistency --- src/expb/payloads/executor/executor.py | 4 ++-- src/expb/payloads/executor/executor_config.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/expb/payloads/executor/executor.py b/src/expb/payloads/executor/executor.py index 7c88285..7f15819 100644 --- a/src/expb/payloads/executor/executor.py +++ b/src/expb/payloads/executor/executor.py @@ -230,8 +230,8 @@ def start_execution_client( run_kwargs["cpuset_cpus"] = self.config.resources.cpuset if self.config.resources and self.config.resources.mem_swappiness is not None: run_kwargs["mem_swappiness"] = self.config.resources.mem_swappiness - if self.config.security_opt: - run_kwargs["security_opt"] = self.config.security_opt + if self.config.execution_client_security_opt: + run_kwargs["security_opt"] = self.config.execution_client_security_opt container = self.config.docker_client.containers.run(**run_kwargs) return container diff --git a/src/expb/payloads/executor/executor_config.py b/src/expb/payloads/executor/executor_config.py index 58b61e5..7e7f56e 100644 --- a/src/expb/payloads/executor/executor_config.py +++ b/src/expb/payloads/executor/executor_config.py @@ -62,7 +62,7 @@ def __init__( scenario.extra_volumes ) self.execution_client_extra_commands = scenario.extra_commands - self.security_opt = scenario.security_opt + self.execution_client_security_opt = scenario.security_opt # Executor Additional Tooling config ## Docker client From 20469e557206b28efed59ecb9adca0fb95b79ca2 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 22 Mar 2026 22:13:09 +0000 Subject: [PATCH 6/6] fix: increase stop timeout to 120s for RocksDB flush + PGO data write --- src/expb/payloads/executor/executor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/expb/payloads/executor/executor.py b/src/expb/payloads/executor/executor.py index 7f15819..6bd8ab7 100644 --- a/src/expb/payloads/executor/executor.py +++ b/src/expb/payloads/executor/executor.py @@ -652,7 +652,7 @@ def cleanup_scenario( execution_client_volumes = execution_client_container.attrs["Mounts"] # Give execution client 60s after SIGTERM to flush data (e.g. PGO # profiles via WritePGOData) before Docker sends SIGKILL (default 10s) - execution_client_container.stop(timeout=60) + execution_client_container.stop(timeout=120) logs_file = ( self.config.outputs_dir / f"{self.config.get_execution_client_name()}.log"