From e5a7d2e063b77ea43eb2d33f1b9c6474e8ffdef8 Mon Sep 17 00:00:00 2001 From: harshithsunku Date: Mon, 14 Sep 2026 16:06:06 +0000 Subject: [PATCH] docs: describe 0.12.0 as it is The descriptions trailed the code: - agent size said ~2 MB; the musl release agents are about 0.6 MB - architecture badges listed two or three targets; agents ship for five, big-endian ARM included - the README, landing page and architecture page described collection as 8-second rounds; that is the fallback, and continuous pipe mode with 16 MB size flushing and back-to-back perf stat is the default - the probed event lists missed cpu-clock and task-clock - compression was "20-40x"; device captures measure about 20x - --duration was "the length of each round"; in continuous mode it is the chunk interval - probe time said 6-12 s or 10-20 s; measured 10.8 s and 12.7 s, with the agent answering status and stop meanwhile - the README highlights had no line for the MCP server Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 4 +-- README.md | 21 +++++++------- docs/architecture.html | 38 ++++++++++++++++++-------- docs/index.html | 10 +++---- docs/reference.html | 4 +-- docs/wire-protocol.svg | 2 +- frontend/src/components/docsContent.ts | 8 +++--- 7 files changed, 52 insertions(+), 35 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e9d726a..9059161 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,7 +57,7 @@ resolution. See [STATUS.md](STATUS.md) for what is open. `### PERF_STAT ###` section. - Agent compresses with in-process zstd (vendored). Server decompresses in-process via the `zstandard` package (external `zstd` binary as - fallback). Typical ratio 20–40×. + fallback). About 20× on device captures. ### Key design decisions - The agent is a zero-dependency static C binary. The server is a normal @@ -123,7 +123,7 @@ resolution. See [STATUS.md](STATUS.md) for what is open. runs at `nice 5` so the profiler yields to the workload, every perf child runs with `LC_ALL=C` in its own process group, and `perf stat` rounds run back to back so every interval is counted. -- Single agent implementation: a static musl C binary (~0.7 MB, vendored +- Single agent implementation: a static musl C binary (~0.6 MB, vendored zstd, zero deps) that cross-compiles for five architectures from the toolchains on the repo's `toolchains` GitHub release, installs with one curl command (install-agent.sh), and self-updates with --update after diff --git a/README.md b/README.md index 8cc3431..1525fc9 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ quick start license agent - arch + arch wire install

@@ -23,20 +23,20 @@

Live demo: function table updating in real time as perf samples stream in, then flame graph, then source view -
Sample counts climb live as perf record rounds stream in. Flip to flame graph, click a function, drop into source with line-level heat. Zero polling — Server-Sent Events. +
Sample counts climb live as chunks stream in from a continuous perf record pipeline. Flip to flame graph, click a function, drop into source with line-level heat. Zero polling — Server-Sent Events.

# PerfLens **PerfLens** is a remote Linux performance profiler with a real-time web UI. Drop the agent on any Linux device (ARM or x86), point it at a PID, and watch flame graphs, function tables, `perf stat` metrics, and line-level annotated source update live in your browser. -No Docker, no sudo. A modern React + TypeScript UI shipped **prebuilt** inside the Python wheel (end users never need Node), and a single static C agent binary (~2 MB) with zero runtime dependencies — it runs on anything from bare-metal embedded boards to servers, installs with one curl command, and updates itself with `--update`. +No Docker, no sudo. A modern React + TypeScript UI shipped **prebuilt** inside the Python wheel (end users never need Node), and a single static C agent binary (about 0.6 MB) with zero runtime dependencies — it runs on anything from bare-metal embedded boards to servers, installs with one curl command, and updates itself with `--update`. --- ## Highlights -- **Real-time streaming** — `perf record` runs in ~8s rounds; each round is compressed with zstd and streamed over a 5-byte framed TCP protocol +- **Real-time streaming** — one continuous `perf record | perf script` pipeline on the target, cut into chunks every interval (8 s by default) or every 16 MB of text, zstd-compressed and streamed over a 5-byte framed TCP protocol; discrete rounds are the fallback where a target's perf cannot keep call chains through a pipe - **Live web UI** — Server-Sent Events push parsed function tables, flame graphs, and `perf stat` panels to the browser as new data arrives - **Source-level annotation** — `addr2line` maps samples back to source lines; the UI heat-colors hot lines red/amber/green - **Differential profiling** — snapshot a baseline (or pick a saved session) and the flame graph recolors by change (red grew, blue shrank) while the function table shows per-function Δ; did-my-fix-help in one glance @@ -48,10 +48,11 @@ No Docker, no sudo. A modern React + TypeScript UI shipped **prebuilt** inside t - **Cross-compilation toolchain support** — `--toolchain-prefix` derives addr2line and readelf from a single prefix; `--sysroot` resolves shared libraries and source files under a sysroot tree - **ARM + x86** — same agent code runs on aarch64, aarch64_be, armv7, armeb, x86_64 - **Session save / replay** — raw chunks saved to disk, replayed lazily on demand via the UI's session list +- **MCP server for AI agents** — `perflens mcp` (the optional `[mcp]` extra) lets Claude Code or any MCP client ask a running server for hot functions, hot stacks, source hot lines, IPC and miss rates, per-thread breakdowns and session comparisons - **Static C agent** — single binary with vendored zstd, no runtime dependencies; cross-compiles to aarch64, aarch64_be, armv7, armeb, x86_64; one-line curl install and built-in self-update - **Zero-friction server install** — `uvx perflens` (or `pipx` / `pip install --user`); everything resolves user-space, no sudo, corporate-machine friendly. Missing binutils? `perflens provision` downloads static addr2line/readelf into `~/.perflens/bin` - **Capability probing** — the agent discovers which perf events and call-graph modes (`fp` / `dwarf` / `lbr`) actually work on the target before collecting -- **Zstd compression** — typical perf script payloads compress 20–40× before hitting the wire +- **Zstd compression** — `perf script` payloads compress about 20× before hitting the wire (measured on x86_64 and ARM64 device captures) --- @@ -65,11 +66,11 @@ The pipeline in one sentence: **`perf record` → agent → TCP+zstd → server ### Target device -- The agent probes the kernel's `perf_event_paranoid`, enumerates candidate events (`cycles`, `instructions`, `cache-*`, `branch-*`, `page-faults`, `context-switches`, `cpu-migrations`), tries call-graph modes in order (`fp`, `dwarf`, `lbr`), and picks the first that produces non-empty stacks -- Each collection round runs `perf record` and `perf stat` in parallel for N seconds, then `perf script` to flatten the output +- The agent probes the kernel's `perf_event_paranoid`, enumerates candidate events (`cycles`, `instructions`, `cache-*`, `branch-*`, the `cpu-clock` and `task-clock` software events that PMU-less targets sample on, and the stat-only `page-faults`, `context-switches`, `cpu-migrations`), tries call-graph modes in order (`fp`, `dwarf`, `lbr`), and picks the first that produces call chains +- Collection is one continuous `perf record -o - | perf script -i -` pipeline, cut into chunks at sample boundaries every interval or every 16 MB of text, with `perf stat` rounds running back to back so every interval is counted. Where a target's perf drops call chains through a pipe (measured on perf 4.4), the agent falls back to discrete rounds, starting the next round's `perf record` while the previous round's `perf script` runs - The combined text is compressed with in-process zstd (level 1) and framed with a 5-byte header - Reconnects with exponential backoff if the server drops -- Single static binary — **no Python, no libc, no zstd needed on the target**. Suitable for old or minimal ARM/x86 Linux devices. +- Single static musl binary of about 0.6 MB — **no Python, no libc, no zstd needed on the target**. Suitable for old or minimal ARM/x86 Linux devices, big-endian ARM included. ### Local machine @@ -110,7 +111,7 @@ The protocol is bidirectional — data and health metrics flow agent → server, | `3` | agent → server | Command response / `hello` handshake (JSON) | | `4` | agent → server | Device health metrics (JSON, every 2s: CPU, memory, temperature, network, per-process stats; opt-in disk I/O and per-thread CPU via `configure_metrics`) | -The server reads the 5 header bytes first, then exactly `LEN` more. Compression is in-process zstd on both ends (vendored in the agent, the `zstandard` package on the server, external `zstd` binary as a fallback). Typical ratio on real `perf script` output is **20–40×**. +The server reads the 5 header bytes first, then exactly `LEN` more. Compression is in-process zstd on both ends (vendored in the agent, the `zstandard` package on the server, external `zstd` binary as a fallback). Measured on real device captures, `perf script` output compresses **about 20×**. ### Handshake @@ -292,7 +293,7 @@ Options: | `--pid PID` | — | PID of process to profile (required for `--output`; set via UI wizard in daemon modes) | | `--port PORT` | `9999` | TCP port (listen or connect) | | `--frequency HZ` | `99` | `perf record -F` sampling frequency | -| `--duration SECS` | `8` | Length of each collection round | +| `--duration SECS` | `8` | Chunk interval in continuous mode; the round length in the fallback round mode and `--output` | | `--rounds N` | `1` | Number of collection rounds (`--output` mode only) | | `--bind ADDR` | `0.0.0.0` | Address to listen on in `--listen` mode | | `--token SECRET` | — | Pairing code the server must present (or `PERFLENS_TOKEN`). In `--listen` mode one is generated and logged if you don't supply it. **Never sent over the wire.** | diff --git a/docs/architecture.html b/docs/architecture.html index a90636d..9d51ff4 100644 --- a/docs/architecture.html +++ b/docs/architecture.html @@ -73,23 +73,39 @@

Capability probing

- This costs roughly 6–12 seconds on first connection - and is a one-time hit. + The probe is batched — one perf stat over every candidate, one + perf record over the survivors — and took + 10.8 s on a hybrid x86_64 container and 12.7 s on an + 8-core ARM64 board when measured for 0.12.0. It runs once per + connection; switching to another process only re-checks that the new + pid can be recorded.

-

Collection rounds

+

Continuous collection

- Each round runs perf record and perf stat in parallel for - N seconds (default 8), then perf script flattens the trace. - The result — perf script text optionally followed by a - ### PERF_STAT ### section — is compressed with - in-process zstd (level 1, vendored in the agent) and pushed over TCP - with a 5-byte header. Typical compression: 20–40×. + Collection is one long-lived pipeline, perf record -o - | perf script -i -, + so there is no sampling dead time and symbol tables are parsed once. The agent + cuts its output into chunks at sample boundaries every N seconds + (default 8) or every 16 MB of text, whichever comes first, and runs + perf stat rounds back to back so every interval is counted. Each + chunk — perf script text optionally followed by a + ### PERF_STAT ### section — is compressed with in-process + zstd (level 1, vendored in the agent) and pushed over TCP with a 5-byte + header. Measured compression on device captures: about 20×. +

+

+ Some old perf builds produce samples through a pipe but drop + their call chains (measured on perf 4.4). The capability probe detects + that, and the agent falls back to discrete rounds: perf record + to a file for N seconds, then perf script on it, with the + next round's perf record started while the previous round's + perf script runs. perf script runs at + nice 5 in both modes, so the profiler yields to the workload.

Health metrics

@@ -255,7 +271,7 @@

Known limits