Skip to content

Commit 52e66e6

Browse files
author
hack-cli-tests
committed
test: add bounded native compiler cost sampler
1 parent 570189c commit 52e66e6

3 files changed

Lines changed: 386 additions & 13 deletions

File tree

‎docs/reference/native-compose-benchmark.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,34 @@ fingerprints are checked before commands and after measured action blocks; no mi
167167
binary comparison qualifies. A host/kernel syscall stuck in an uninterruptible
168168
state is outside hard real-time guarantees.
169169

170+
## Separate pure compiler observations
171+
172+
The same module exports `compiler_sample` for one direct `--protocol`, `compile`
173+
or metadata-only `plan` observation. Its caller supplies the frozen absolute owned
174+
executable and SHA256, immutable request bytes and their SHA256 (no input for
175+
protocol), and the SHA256 of canonical JSON from independent untimed correctness
176+
qualification. It does not select a compiler from PATH, use a shell, read managed
177+
values or call Docker. Request bytes use an anonymous regular stdin file, avoiding
178+
pipe backpressure; input/output are bounded to one MiB and each child to five seconds.
179+
180+
It uses one-ms wait polling rather than the lifecycle collector's ten-ms polling.
181+
Each row reports launch-to-reap wall, reaped child CPU and the observation interval.
182+
Observer hashing, input setup and JSON validation are outside that boundary. A
183+
version/output mismatch, executable change, nonzero exit, output overflow or timeout
184+
returns a failed row with real metrics and a fixed reason, never a successful zero
185+
sample. No raw input, stdout or stderr is returned. Raised caller interruption,
186+
including Python's normal SIGINT, reaps only the owned group. Interrupt delivery is fenced across wait4/returncode publication,
187+
so a received interruption cannot signal an already-reaped former group.
188+
A successful exit observed after the deadline also fails with its real metrics;
189+
that post-reap refusal sends no group signal.
190+
191+
The caller owns the balanced warmup/trial schedule, host-noise admission and private
192+
report; importing or adding the helper collects no samples. Use the same two warmups
193+
and eight forward/reverse rounds declared before execution, preserving failures.
194+
Standalone protocol/compile/plan costs are not additive estimates of integrated CLI
195+
commands. They do not measure secret delivery, rendering, app/engine/VM work or
196+
demonstrate a speedup. This collector requires POSIX wait4 and pthread signal masks.
197+
170198
## Cleanup and remaining qualification
171199

172200
Before later warm up, restart, down and cleanup effects, the harness verifies the

‎scripts/benchmark-native-compose.py‎

Lines changed: 119 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
Docker/VM or application CPU.
99
"""
1010
import argparse
11+
from contextlib import ExitStack
1112
import hashlib
1213
import http.client
1314
import json
@@ -38,6 +39,8 @@
3839
PRIVATE_TMPFS = "rw,noexec,nosuid,nodev,mode=700"
3940
BUILD = re.compile(r"^(cargo|rustc|clang|clang\+\+|ld|ld64|zig|swiftc|swift-frontend|xcodebuild)$")
4041
OUTPUT_LIMIT = 2 * 1024 * 1024
42+
COMPILER_INPUT_LIMIT = 1024 * 1024
43+
COMPILER_POLL_SECONDS = 0.001
4144
ACTIONS = ("up", "ps", "exec", "restart", "down")
4245
GATES = {
4346
"rounds": 8,
@@ -107,23 +110,43 @@ def private_json(path, with_anchor=False):
107110
os.close(descriptor)
108111

109112

110-
def command(argv, *, cwd, env, timeout, capture=None, output_limit=OUTPUT_LIMIT):
113+
def command(argv, *, cwd, env, timeout, capture=None, output_limit=OUTPUT_LIMIT,
114+
stdin_bytes=None, poll_seconds=0.01):
111115
"""wait4 isolates each terminated CLI and the children it actually reaped.
112116
113117
Keep the session leader unreaped until interruption decisions are finished.
114118
Never signal a group after successful wait4: its numeric ID may be reused.
115119
Detached engine processes are deliberately outside this accounting boundary.
116120
"""
117-
with tempfile.TemporaryFile() as out, tempfile.TemporaryFile() as err:
121+
if not (isinstance(poll_seconds, (int, float)) and not isinstance(poll_seconds, bool) and
122+
math.isfinite(poll_seconds) and 0 < poll_seconds <= 0.01):
123+
raise Failure("Invalid process observation interval")
124+
if stdin_bytes is not None and (not isinstance(stdin_bytes, bytes) or len(stdin_bytes) > COMPILER_INPUT_LIMIT):
125+
raise Failure("Compiler input exceeds its byte budget or has invalid shape")
126+
with ExitStack() as files:
127+
out = files.enter_context(tempfile.TemporaryFile())
128+
err = files.enter_context(tempfile.TemporaryFile())
129+
incoming = subprocess.DEVNULL
130+
if stdin_bytes is not None:
131+
incoming = files.enter_context(tempfile.TemporaryFile())
132+
incoming.write(stdin_bytes)
133+
incoming.seek(0)
118134
started = time.monotonic()
119-
child = subprocess.Popen(argv, cwd=cwd, env=env, stdin=subprocess.DEVNULL,
135+
child = subprocess.Popen(argv, cwd=cwd, env=env, stdin=incoming,
120136
stdout=out, stderr=err, start_new_session=True)
121137
interrupted = None
122138
try:
123139
while True:
124-
pid, status, usage = os.wait4(child.pid, os.WNOHANG)
140+
# Deliver asynchronous interruption only after reaping is reflected
141+
# in the owned child object; an already-reaped PID no longer pins a group.
142+
previous_mask = signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGINT, signal.SIGTERM})
143+
try:
144+
pid, status, usage = os.wait4(child.pid, os.WNOHANG)
145+
if pid:
146+
child.returncode = os.waitstatus_to_exitcode(status)
147+
finally:
148+
signal.pthread_sigmask(signal.SIG_SETMASK, previous_mask)
125149
if pid:
126-
child.returncode = os.waitstatus_to_exitcode(status)
127150
break
128151
if interrupted is None:
129152
if time.monotonic() - started >= timeout:
@@ -136,15 +159,19 @@ def command(argv, *, cwd, env, timeout, capture=None, output_limit=OUTPUT_LIMIT)
136159
os.killpg(child.pid, signal.SIGKILL)
137160
except ProcessLookupError:
138161
pass
139-
time.sleep(0.01)
162+
time.sleep(poll_seconds)
140163
except BaseException:
141-
if child.returncode is None:
142-
try:
143-
os.killpg(child.pid, signal.SIGKILL)
144-
except ProcessLookupError:
145-
pass
146-
_, status, _ = os.wait4(child.pid, 0)
147-
child.returncode = os.waitstatus_to_exitcode(status)
164+
previous_mask = signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGINT, signal.SIGTERM})
165+
try:
166+
if child.returncode is None:
167+
try:
168+
os.killpg(child.pid, signal.SIGKILL)
169+
except ProcessLookupError:
170+
pass
171+
_, status, _ = os.wait4(child.pid, 0)
172+
child.returncode = os.waitstatus_to_exitcode(status)
173+
finally:
174+
signal.pthread_sigmask(signal.SIG_SETMASK, previous_mask)
148175
raise
149176
wall = time.monotonic() - started
150177
out.seek(0)
@@ -165,6 +192,85 @@ def command(argv, *, cwd, env, timeout, capture=None, output_limit=OUTPUT_LIMIT)
165192
"interrupted": interrupted}
166193

167194

195+
def compiler_fingerprint(path, expected):
196+
"""Fence this pure measurement input; do not search PATH or follow a replacement link."""
197+
if not path.is_absolute():
198+
raise Failure("Compiler measurement requires an absolute executable")
199+
try:
200+
before = path.lstat()
201+
if not (stat.S_ISREG(before.st_mode) and before.st_uid == os.getuid() and before.st_nlink == 1 and
202+
before.st_mode & 0o022 == 0 and before.st_mode & 0o111):
203+
raise Failure("Compiler measurement executable has unsafe ownership or shape")
204+
digest = fingerprint(path)
205+
after = path.lstat()
206+
except (OSError, ValueError):
207+
raise Failure("Compiler measurement executable is unavailable") from None
208+
anchor = lambda value: (value.st_dev, value.st_ino, value.st_size, value.st_mtime_ns, value.st_ctime_ns, value.st_mode)
209+
if digest != expected or anchor(before) != anchor(after):
210+
raise Failure("Compiler measurement executable changed")
211+
return anchor(after)
212+
213+
214+
def compiler_sample(*, compiler, compiler_sha256, action, input_bytes=None, input_sha256=None,
215+
expected_output_sha256, cwd, timeout=5):
216+
"""One direct pure-compiler observation; callers own the fixed trial schedule and private report.
217+
218+
Use untimed, independently checked JSON to pin the expected canonical output.
219+
Anonymous regular stdin avoids pipe backpressure and shell wrappers. Input setup,
220+
hashing/JSON checks and this observer's CPU are outside launch-to-reap accounting.
221+
Failure rows retain real metrics; they are never successful zero samples.
222+
"""
223+
if not (action in ("--protocol", "compile", "plan") and isinstance(compiler_sha256, str) and
224+
OBJECT_ID.fullmatch(compiler_sha256) and isinstance(expected_output_sha256, str) and
225+
OBJECT_ID.fullmatch(expected_output_sha256) and isinstance(timeout, (int, float)) and
226+
not isinstance(timeout, bool) and math.isfinite(timeout) and 0 < timeout <= 5):
227+
raise Failure("Compiler measurement requires exact bounded qualification")
228+
if action == "--protocol":
229+
if input_bytes is not None or input_sha256 is not None:
230+
raise Failure("Protocol measurement accepts no authored input")
231+
elif not (isinstance(input_bytes, bytes) and 0 < len(input_bytes) <= COMPILER_INPUT_LIMIT and
232+
isinstance(input_sha256, str) and OBJECT_ID.fullmatch(input_sha256) and
233+
hashlib.sha256(input_bytes).hexdigest() == input_sha256):
234+
raise Failure("Compiler measurement input does not match its frozen byte budget")
235+
if not isinstance(compiler, (str, Path)):
236+
raise Failure("Compiler measurement requires an absolute executable")
237+
compiler = Path(compiler)
238+
anchor = compiler_fingerprint(compiler, compiler_sha256)
239+
observed = command([str(compiler), action], cwd=cwd,
240+
env={"PATH": os.environ.get("PATH", ""), "LANG": "C", "LC_ALL": "C"},
241+
timeout=timeout, output_limit=COMPILER_INPUT_LIMIT, stdin_bytes=input_bytes,
242+
poll_seconds=COMPILER_POLL_SECONDS)
243+
result = {"action": action, "ok": False, "exit": observed["exit"], "interrupted": observed["interrupted"],
244+
"wall_s": observed["wall_s"], "compiler_cpu_s": observed["cli_cpu_s"],
245+
"poll_seconds": COMPILER_POLL_SECONDS, "input_sha256": input_sha256}
246+
try:
247+
if compiler_fingerprint(compiler, compiler_sha256) != anchor:
248+
raise Failure("Compiler measurement executable changed")
249+
except (Failure, OSError):
250+
return {**result, "reason": "compiler-changed"}
251+
if observed["interrupted"]:
252+
return {**result, "reason": "interrupted"}
253+
if observed["wall_s"] >= timeout:
254+
# Reaping can win the polling race after the budget has elapsed. Keep
255+
# that observation failed without signaling an already-reaped group.
256+
return {**result, "interrupted": "timeout", "reason": "interrupted"}
257+
if observed["exit"] != 0:
258+
return {**result, "reason": "nonzero-exit"}
259+
try:
260+
payload = json.loads(observed["stdout"])
261+
versions = ("transport_version", "authored_version", "plan_version") if action == "--protocol" else ("transport_version",)
262+
if not (isinstance(payload, dict) and all(type(payload.get(key)) is int and payload[key] == 1 for key in versions) and
263+
(action == "--protocol" or payload.get("ok") is True)):
264+
raise ValueError("invalid envelope")
265+
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"), allow_nan=False).encode()
266+
except (ValueError, TypeError):
267+
return {**result, "reason": "invalid-output"}
268+
digest = hashlib.sha256(canonical).hexdigest()
269+
if digest != expected_output_sha256:
270+
return {**result, "reason": "output-mismatch", "output_sha256": digest}
271+
return {**result, "ok": True, "output_sha256": digest}
272+
273+
168274
def fixture_documents(name, image, hostname, token):
169275
"""One workload definition feeds both authored formats; live inspect checks it again."""
170276
common = {"image": image, "pull_policy": "never", "init": True,

0 commit comments

Comments
 (0)