mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 21:11:56 +00:00
af94851172
- UnitResult.timing (not in the report): wall time of case building,
run_cpp, run_rust, compare and the slowest Rust case; main prints
`TIME <unit> ...` to stderr per unit
- DIFFTEST_RUST_DIGEST=<dir>: writes <dir>/<unit>.digest with one
`<case>\t<fnv1a64 of format!("{rust_output:?}")>` line per case, so two
builds can be proved bit-identical; off by default
- README: --green takes ~35 min on 16 threads (not ~2 min), TIME line,
digest, and scripts/perf (ptrace sampler + analyzer, no sudo needed)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9QwJFBTKLZ9FFK3nVd1X4
94 lines
3.0 KiB
Python
94 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Poor man's sampling profiler: ptrace + frame-pointer unwinding (x86_64 Linux).
|
|
|
|
No perf_event needed (perf_event_paranoid=2 blocks samply). The target is our own
|
|
child (PTRACE_TRACEME), so yama ptrace_scope=1 allows it. Build the target with
|
|
`-C force-frame-pointers=yes` and debug line tables.
|
|
|
|
usage: sampler.py OUT.json INTERVAL_MS -- cmd args...
|
|
Writes {"maps": [...], "samples": [[addr, ...], ...]} (innermost first).
|
|
"""
|
|
import ctypes
|
|
import json
|
|
import os
|
|
import signal
|
|
import struct
|
|
import sys
|
|
import time
|
|
|
|
libc = ctypes.CDLL(None, use_errno=True)
|
|
libc.ptrace.restype = ctypes.c_long
|
|
libc.ptrace.argtypes = [ctypes.c_long, ctypes.c_long, ctypes.c_void_p, ctypes.c_void_p]
|
|
PTRACE_TRACEME, PTRACE_CONT, PTRACE_GETREGS, PTRACE_SETOPTIONS = 0, 7, 12, 0x4200
|
|
PTRACE_O_EXITKILL = 0x100000
|
|
|
|
|
|
class Regs(ctypes.Structure):
|
|
_fields_ = [(n, ctypes.c_ulonglong) for n in (
|
|
"r15 r14 r13 r12 rbp rbx r11 r10 r9 r8 rax rcx rdx rsi rdi orig_rax rip cs "
|
|
"eflags rsp ss fs_base gs_base ds es fs gs").split()]
|
|
|
|
|
|
def main():
|
|
out, interval = sys.argv[1], float(sys.argv[2]) / 1000.0
|
|
cmd = sys.argv[sys.argv.index("--") + 1:]
|
|
pid = os.fork()
|
|
if pid == 0:
|
|
libc.ptrace(PTRACE_TRACEME, 0, None, None)
|
|
os.execvp(cmd[0], cmd)
|
|
_, st = os.waitpid(pid, 0) # exec SIGTRAP stop
|
|
libc.ptrace(PTRACE_SETOPTIONS, pid, None, ctypes.c_void_p(PTRACE_O_EXITKILL))
|
|
libc.ptrace(PTRACE_CONT, pid, None, None)
|
|
mem = os.open(f"/proc/{pid}/mem", os.O_RDONLY)
|
|
samples, maps = [], None
|
|
regs = Regs()
|
|
while True:
|
|
time.sleep(interval)
|
|
try:
|
|
os.kill(pid, signal.SIGSTOP)
|
|
except ProcessLookupError:
|
|
break
|
|
# Wait for our SIGSTOP; forward any other signal.
|
|
done = False
|
|
while True:
|
|
_, st = os.waitpid(pid, 0)
|
|
if os.WIFEXITED(st) or os.WIFSIGNALED(st):
|
|
done = True
|
|
break
|
|
sig = os.WSTOPSIG(st)
|
|
if sig == signal.SIGSTOP:
|
|
break
|
|
libc.ptrace(PTRACE_CONT, pid, None, ctypes.c_void_p(sig))
|
|
if done:
|
|
break
|
|
if maps is None:
|
|
with open(f"/proc/{pid}/maps") as f:
|
|
maps = f.read()
|
|
libc.ptrace(PTRACE_GETREGS, pid, None, ctypes.byref(regs))
|
|
stack = [regs.rip]
|
|
fp = regs.rbp
|
|
for _ in range(512):
|
|
if fp == 0 or fp & 7 or fp >= (1 << 47):
|
|
break
|
|
try:
|
|
b = os.pread(mem, 16, fp)
|
|
except OSError:
|
|
break
|
|
if len(b) < 16:
|
|
break
|
|
nfp, ret = struct.unpack("<QQ", b)
|
|
if ret == 0:
|
|
break
|
|
stack.append(ret)
|
|
if nfp <= fp:
|
|
break
|
|
fp = nfp
|
|
samples.append(stack)
|
|
libc.ptrace(PTRACE_CONT, pid, None, None)
|
|
with open(out, "w") as f:
|
|
json.dump({"maps": maps or "", "samples": samples, "exe": cmd[0]}, f)
|
|
print(f"sampler: {len(samples)} samples -> {out}", file=sys.stderr)
|
|
|
|
|
|
main()
|