Stabilization pass toward 0.12.0 - #5
Merged
Merged
Conversation
…ound it tests/test_parser_compat.py collected two tests with no assert in them: each counted failures into a local, printed them, and returned a tuple pytest discards. The PytestReturnNotNoneWarning that would have flagged that was suppressed in pyproject.toml by a comment naming a file deleted in ac4708e. Its fifteen cases are the same COMPAT_CASES test_parser.py already checks with real assertions, parametrized. test_http_api.py and test_mcp.py each carried a byte-for-byte copy of the conftest `core` fixture (and test_http_api.py of `client` too), shadowing the shared ones. The shared fixtures are what both files now use. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…what a peer can send The sixth agent unfreeze; the wire protocol is unchanged. Continuous mode now flushes at 16 MB of raw text as well as at the interval, where the 64 MB cap used to swallow every chunk of a long interval without a log line. perf stat rounds run back to back and each rides the next chunk, instead of every other interval going uncounted. The call-graph probe records the probed event and requires call-chain frames, so a PMU-less target and a perf that never prints a stack are both answered honestly. Every perf script runs at nice 5, every perf child under LC_ALL=C in its own process group, and teardown reaps with a three-second grace before SIGKILL, so a stuck perf no longer holds `stop` and its `sleep` workload dies with it. Session sockets carry TCP_USER_TIMEOUT and SO_SNDTIMEO; pipes and sockets are close-on-exec; server->agent frames are capped at 64 KB and the command queue at 64; ids are validated and escaped, `start` validates its arguments, and lookups are scoped to `args`. One writev() per frame. Found on the way: a child forked from the command thread ran the parent's SIGTERM handler before exec and shut down the shared session socket. Signals are blocked across fork() and reset in the child. The server's stat parser accepts every thousands separator perf may print and no longer raises on a malformed elapsed line. Fifteen new protocol tests drive a shim that records how perf was run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…obing, sample through rounds start and reprobe now probe on the collection thread, so ping, status and stop are answered meanwhile: status reports `probing`, stop cancels the probe and the pending command is answered `cancelled`, and a lost session ends it instead of leaving perf running against the target. The probe itself batches: one perf stat over every candidate (per-event as the fallback), one perf record over the survivors (bisected on failure), and one call-graph recording that also serves the -F check -- about seven perf runs where there were twenty-five. A start on another pid re-checks only that it can be recorded. --listen keeps polling its socket while a peer is unauthenticated, so a silent connection no longer holds the slot; the auth window is 10 s. Rounds mode forks round N+1's record before symbolizing round N, so the single-core targets that get rounds mode are no longer blind while perf script runs; a round waits when the temp filesystem is nearly full. Metrics: files opened once and re-read with pread(); cpufreq holes are null instead of ending the list; the thermal zone typed as CPU/SoC is chosen, or the hottest; pre-3.14 kernels without MemAvailable no longer count the page cache as used; no 128-core cap. The process list reads comm from the stat line and cmdline only for what it returns, with per-core CPU% like the process metrics. Sleeps wake on stop/pause/resume. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t, sanitizer CI Self-update fetched the release binary and ran it for --version before any check, so a rejected update had already executed whatever the origin served. The .sha256 sidecar is now checked with the device's sha256sum before the download is made executable; a mismatch is refused and removed, an unverifiable download over wget (BusyBox's validates no certificate) too. All five release agents are musl static builds -- the three glibc ones could not reliably resolve names and armv7 was hard-float, the SIGILL trap the big-endian pass hit -- from toolchains on the repo's `toolchains` release (x86_64-linux-musl-cross, aarch64-linux-musl-cross, arm-linux-musleabi-cross uploaded, sha256 recorded in STATUS). Hardening flags, --gc-sections, and a stripped asset with the unstripped binary kept as a CI artifact: x86_64 2.06 MB -> 0.67 MB. CI asserts static linking and the soft-float/BE8 ABI, runs the protocol tests under ASan+UBSan and TSan (which is why g_shutdown and the child-pid slots are C11 atomics now), runs the C unit tests, and shellchecks the scripts. build_package.sh names armv7 like every consumer; install-agent.sh no longer reads a missing `od` as big-endian. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…utdown Phase 4 of the stabilization pass: the server side of the socket. Security: a pre-0.10.0 agent (secret in the hello) is refused by a server with a pairing code configured, with the upgrade named — the item SECURITY.md scheduled for 0.12.0. A tokenless server still pairs one. The hello/auth frames are capped at 64 KB, a session frame at 80 MB, one frame's decompressed text at 256 MB. Fixed: one malformed frame (non-object metrics, an unreadable stat line, cpu: null in the summary) ended the session; stat-only chunks after `start` lost their counters; a device that stopped reading wedged every later command behind an untimed sendall (keepalive, TCP_USER_TIMEOUT and a 30 s send bound on both connect paths; relayed command timeouts bounded 1..600 s); one accept() error ended the listener for good; an unwritable sessions dir killed the receiver before it read a byte; a chunk that failed to spool was overwritten by the next; a server killed mid-capture left chunks with no metadata (written at session start now, refreshed per chunk, finalized by a lifespan shutdown hook; startup sweep removes empty dirs and recovers orphans); replacing an agent reset state under the old receiver mid-chunk (it is closed and joined first). Tests: tests/test_agentlink_session.py (13) drives the receive loop with a scripted agent; the legacy-token test asserts the refusal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…napshot bytes Phase 5 of the stabilization pass: everything server-side of the socket. Correctness: the source mapper is shared by the rebuild worker and every request thread with no lock, and its addr2line protocol (write N, read 2N lines) silently swapped names and lines when two threads interleaved — each pipe now locks across an exchange and the mapper around its cache-mutating phases. A hung addr2line/readelf froze the worker for good: reads time out (30 s), a dead tool is restarted and given up after three failures, and unanswered addresses are neither cached nor persisted as '??'. The replaced mapper is closed (it leaked children and a sqlite handle on every PATCH), and closed at shutdown. Performance: the aggregator no longer deep-copies every event's tree per chunk; the worker serializes a changed event once (JSON + deflate segment) and /api/snapshot splices the bytes, gzip included. Ring views (threads, window, source) are memoized per (generation, chunk_count). The parser is 2x faster (tab-led lines are frames). Session metadata is re-read only when its mtime changes. Protocol additions (all additive): generation, ring_samples and session_samples on every version stamp; the SSE burst opens with status and agent. Security: the Access-Control-Allow-Origin wildcard is gone. Also: hybrid-spelled counters derive IPC and the miss rates (server and MCP), branch-instructions counts as branches, MCP's default event falls back to cpu-clock/task-clock; corrupt metadata is a 500 envelope, a refused delete is reported, the replay cache is built once and renamed into place, uploads are written off the event loop, --import reports any failure, ?limit= is bounded, equal ports and malformed map entries are refused or reported; replay/export no longer count toward the live symbolization tally; verify_perf replaces the hello instead of mutating it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ay, wizard Phase 6 of the stabilization pass: the React UI. Fixed: an event switch during a snapshot fetch was dropped and stamps were not coalesced; a half-open connection latched the fetch guard (all requests bounded now); a hybrid CPU's 400 ambiguous_event was swallowed (the UI picks the concrete PMU event the server names); any SSE drop read as a disconnected agent, retried every 3 s forever and threw the operator out of replay (distinct reconnecting state, 3 -> 30 s backoff, replay left only by the banner's button); dev builds had no SSE (the StrictMode boot guard swallowed the cleanup); the error banner rendered inside the hidden profiling view; fourteen swallowed error sites; tables stuck on "Loading…" against a dead server; exports opening raw JSON on failure; the source view resetting and scrolling on every chunk with no fetch cancellation; live-only queries (threads, window) issued during replay; a process switch resetting the sampling settings; the wizard advancing past a rejected path, polling index status forever, sending 99999 Hz, never resetting connected, never restoring toolchain/sysroot; search boxes that could not take a space; six undefined CSS tokens and a 2.5:1 light-theme contrast. Changed: one sampling event by default (A-15), every start carries an explicit list; "Stop" stops and "Disconnect" disconnects; probing state with elapsed time in the control bar; selectors instead of whole-store subscriptions; profiling view mounted only while shown; delegated flame graph handlers; var(--x) sparklines; delete confirmation; ? button; a11y labels, focus and tab roles; live/recovered session tags. Tests: 49 vitest (was 24), 16 Playwright scenarios (was 10). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ss, fix docs drift The hardware pass on both SSH beds found a regression none of the suite could: the agent runs perf under LC_ALL=C since this release, perf then prints counters without grouping, and the grouping-tolerant stat parser matched a 12-digit plain count under neither of its branches. Every counter except page-faults vanished from the live stat bar. Every committed fixture was captured under a grouping locale and the protocol shim printed 1,234,567, so nothing showed it. Plain digits are accepted first; the shim now prints C-locale numbers and the stat coverage test asserts the parsed values. Hardware results (x86_64 hybrid container / ARM64 board): probe 10.8 s / 12.7 s, a 60.0 s stat round on every chunk after the first, chunks of 9.8 MB / 16.8 MB text sent as 0.49 MB / 0.80 MB, largest health-frame gap 2.0 s / 2.4 s (so parsing off the socket thread is not needed), self-update refuses a tampered asset, armv7 runs under a 64-bit kernel. Docs: --module-map, cpu-clock/task-clock and the one-event default, the CI description (mypy, sanitizers, shellcheck, musl toolchains), 12 agent source files, musl build snippets, live-only thread views in the in-app docs. CHANGELOG section cut for 0.12.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e screenshots showed
CI's sanitizer jobs failed at link time: `make check` reused the objects
`make SANITIZE=...` had just built and linked them without the sanitizer
runtime. Objects now record the flags they were built with and rebuild when
those change, which also stops a VERSION change leaving the old version
compiled into an agent that rebuilt nothing. CI runs the unit tests
instrumented too.
Reviewing the regenerated docs screenshots found three problems:
- counters sum over the session, and trillions ("11219.4B") and fractional
task-clock ("1003215.3") ran off the stat cards; values scale to T and
large fractions scale like integers
- process CPU is per core (100% = one busy core), but the health strip drew
it on 0-100: a five-core process read 501% in red with a clipped chart;
severity and chart scale with the core count
- one unnamed frame in 2.4 million raised the "symbolization degraded"
banner; a profile is degraded only at 1% or more unnamed
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s once The screenshots and GIF are retaken from a live capture of the release candidate (the header's Disconnect and ? buttons, the control bar's Stop, Start and Disconnect, the replay banner's exit button, the scaled stat bar and per-core process CPU) and reviewed frame by frame. That review also found the flame graph's search readout summing every matching frame, so a match nested inside another match was counted twice and the readout said "4 / 266 frames (100.2%)". Each stack now counts once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A stabilization pass over the whole project toward 0.12.0: agent, transport, server, UI, then docs, hardware and the release. No new features. The wire protocol is unchanged, so a 0.11.0 server drives this agent and a 0.12.0 server drives a 0.11.0 agent.
What changed, by phase
perf statrounds run back to back instead of covering half of each interval one chunk late.stopno longer hangs on a child that ignores SIGTERM: every perf child gets its own process group and is reaped SIGTERM then SIGKILL. Also: send timeouts, close-on-exec,LC_ALL=Cfor perf, a 64 KB pre-auth frame cap, escaped and validated ids,nice 5forperf scriptin rounds mode, and monotonic deadlines.ping,statusandstopanswer during it (statusreportsprobing). The probe is batched, about 7 perf runs where there were ~25. Switching process re-checks only the new pid. A silent peer can no longer hold--listen. Rounds overlap, and metrics read their files once andpreadthem.--updatechecks the release's.sha256sidecar before running the download. Every asset is a musl static build, and both 32-bit ARM assets are soft-float. CI now asserts static linking and the float ABI, runs the protocol tests under ASan+UBSan and TSan, and shellchecks the scripts.accept()error. A server killed mid-capture leaves a listable, replayable session, and a replacement agent waits for the old receiver to finish.addr2lineexchanges used to swap names and lines. Tool reads time out, and failures are never cached as??. Snapshot cost no longer grows with the session: no tree copy, and the worker serializes each changed event once. Ring views are memoized, the parser is 2× faster, and the CORS wildcard is gone. Version stamps carry ageneration.Verification
On hardware, an x86_64 hybrid-CPU container and an 8-core ARM64 board ran the musl agents, installed through
install-agent.shwith the checksum verified. Each run used one event at 99 Hz with a 60 s interval for five minutes.The hardware run caught a regression the suite could not. Under
LC_ALL=Cperf prints counters without grouping. The stat parser matched a 12-digit plain count under neither branch, so every counter exceptpage-faultsvanished. Every committed fixture was captured under a grouping locale, and the protocol shim printed1,234,567. The parser now accepts plain digits first, the shim prints C-locale numbers, and the stat coverage test asserts the parsed values.Self-update on the ARM board refused a tampered asset and verified a good one. The armv7 soft-float agent ran under the board's 64-bit kernel.
Not covered
readelf -honly.🤖 Generated with Claude Code