Reference

Reference

Server and agent flags, HTTP API endpoints, supported perf events, and the usual things to check when something looks off.

Server CLI

FlagDefaultDescription
--port PORT9999TCP port the agent connects to.
--http-port PORT8080HTTP port for the web UI.
--source-dir DIR.Root of the source tree for line annotation.
--binary PATH—Unstripped binary (enables addr2line).
--map PATH—GNU ld linker map file (optional symbol fallback).
--path-map FROM=TO—Rewrite compile-time paths (e.g. /build/src=/home/user/src). Comma-separated for several; a malformed entry is reported and skipped.
--module-map FROM=TO—Map a device module path to a local binary (e.g. /opt/app/foo=/build/foo.sym) — for the one shared object that does not live under a sysroot. Comma-separated for several.
--addr2line PATH—Custom addr2line binary (overrides bundled and PATH).
--readelf PATH—Custom readelf binary.
--toolchain-prefix PREFIX—Cross-compile prefix (e.g. arm-linux-gnueabihf-). Derives addr2line + readelf.
--sysroot DIR—Sysroot for resolving shared-library modules and source files.
--max-samples N500000Raw-sample ring buffer cap (aggregates always cover the full session). Roughly 1.7 KB of RSS per retained sample, so the default plateaus near 1.1 GB on a busy multi-threaded target.
--sessions-dir DIR~/.perflens/sessionsWhere saved sessions are stored (PERFLENS_HOME moves the whole ~/.perflens root).
--http-bind ADDR127.0.0.1Web UI bind address (0.0.0.0 to expose — the UI has no auth).
--browse-root DIR~Directory the wizard file picker is confined to.
--token SECRET—Pairing code to present to the agent (or PERFLENS_TOKEN). The Live Debug wizard can supply one per connection instead.
--inline / --no-inlineonEnable/disable inline-function resolution via addr2line -i.
--import FILE—Import a perf.data file at startup as a session.

Agent CLI

The agent runs in one of three modes — pick one.

ModeDescription
--listenDaemon: bind --port, wait for the server to connect in via the UI wizard.
--server HOSTDaemon: dial out to the server. Reconnects with exponential backoff.
--output FILEHeadless: collect once, write to file (- for stdout). Requires --pid.

Common options:

FlagDefaultDescription
--pid PID—Process to profile (required for --output; set via UI wizard in daemon modes).
--port PORT9999TCP port (listen or connect).
--frequency HZ99perf record -F sampling frequency.
--duration SECS8Chunk interval: how often collected data is sent in continuous mode, or the length of each round in the fallback round mode and --output.
--rounds N1Number of rounds (--output mode).
--bind ADDR0.0.0.0Address to listen on in --listen mode.
--token SECRET—Pairing code the server must present before any command runs (or PERFLENS_TOKEN). Generated and written to the agent log in --listen mode when not supplied. Never sent over the wire.
--perf PATHperf from PATHThe perf binary to run (or PERFLENS_PERF), for targets that install it outside PATH. Must print perf version. The server can also set it on a running agent from the wizard or the control bar.
--update—Self-update from the latest GitHub release, then exit.
--version—Print version and exit.

HTTP API

EndpointMethodDescription
/api/statusGETServer + agent connection state, sample totals.
/api/streamGETServer-Sent Events: status, agent, data_version (carries the event-type list), perf_stat, metrics (discriminated by payload type).
/api/snapshot?event=GETCached per-event snapshot (gzip); clients fetch it when SSE data_version bumps. Omit event for every event at once.
/api/sessions?offset=&limit=GETList saved sessions, paginated (metadata only).
/api/sessions/<id>GETLazy-replay a session (parses raw chunks on demand, cached).
/api/sessions/<id>DELETEDelete a saved session.
/api/sessions/<id>/export?format=&event=GETExport a session: collapsed stacks or an svg flame graph for one event, or json for every event unless event names one.
/api/sessions/importPOSTImport an uploaded perf.data file as a session (needs perf on the server).
/api/live/export?format=&event=GETExport the live in-memory profile in the same three formats.
/api/source?file=&event=&tid=GETAnnotated source for a single file (optionally per-thread).
/api/threads?event=GETThread overview with sample counts and top functions.
/api/threads/<tid>?event=GETPer-thread flame graph + function summary.
/api/window?event=&start=&end=&tid=GETFlame graph + function summary for samples received in a time range (timeline scrubbing).
/api/index/statusGETSource-index / DWARF file-list state (truncated preview).
/api/index/files?offset=&limit=&q=GETPaginated DWARF source-file list.
/api/metrics/currentGETLatest device-health snapshot per type.
/api/metrics/history?type=&start=GETHealth metrics time series.
/api/agentGETAgent connection info (address + hello/platform).
/api/agentDELETEDisconnect the active agent (triggers a normal session save).
/api/agent/connectPOSTConnect out to a --listen agent ({"host": …, "port": …, "token": …}).
/api/agent/commandPOSTRelay a command to the connected agent (start, stop, pause, resume, configure, configure_metrics, list_processes, reprobe, verify_pid, verify_perf, ping, status, update).
/api/wizardGET/PUTPersisted Live Debug wizard state.
/api/browse?path=GETFile picker listing (confined to --browse-root).
/api/configGET/PATCHRuntime binary / source dir / path map / toolchain / sysroot config, as one typed model.
/*GETStatic files from ui/.

Every failure renders as {"error": {"code": …, "message": …}} with a real status code — 400 validation, 403 permission, 404 missing, 409 wrong server state, 413 too large, 502 agent transport. The schema is published at /api/openapi.json.

Supported perf events

The agent probes each event before use and only emits the ones the kernel actually supports. On hybrid CPUs, events are reported per-cluster — e.g. cpu_core/cycles/ and cpu_atom/cycles/, and never a bare cycles. The names the agent's start response lists are the ones it asked for, so on that hardware they are not the keys the data arrives under: read the real names from the SSE data_version stamp's event_types. The server will resolve a bare name onto the per-cluster ones when that is unambiguous, and name the candidates when it is not.

The UI records one sampling event by default (cycles, or cpu-clock on a target without a PMU); the others are opt-in in the wizard's Perf step and the control bar's settings. Each extra event multiplies the data the device sends.

EventTypical useMode
cyclesCPU time / hot paths (the UI's default sampling event)record + stat
cpu-clockWall-clock sampling where there is no hardware PMU (embedded silicon; the default there)record + stat
task-clockTask CPU time; the other software sampling eventrecord + stat
instructionsIPC, retired instruction countrecord + stat
cache-missesLast-level cache missesrecord + stat
cache-referencesLLC accessesrecord + stat
branch-missesBranch prediction missesrecord + stat
branch-instructionsTotal branchesrecord + stat
page-faultsMinor/major page faultsstat only
context-switchesScheduling pressurestat only
cpu-migrationsInter-CPU movementstat only

MCP server

perflens mcp serves profiling data to LLM agents over the Model Context Protocol, so an agent can answer “why is this slow” against real perf data and drive a live run on a device. It is a client of the HTTP API above, so a perflens serve must be running. The SDK is an optional dependency:

pip install 'perflens[mcp]'          # or: uv tool install 'perflens[mcp]'
claude mcp add perflens -- perflens mcp
FlagDescription
--server-url URLPerfLens HTTP API to query (default $PERFLENS_MCP_URL or http://127.0.0.1:8080).
--read-onlyOmit the agent-control and export tools, so the agent can analyse but cannot touch a device or write files.

Nineteen tools. Analysis (read-only): perflens_status, perflens_list_sessions, perflens_hot_functions, perflens_hot_stacks, perflens_perf_stat, perflens_list_source_files, perflens_source_hotlines, perflens_compare, perflens_threads, perflens_thread_detail, perflens_device_metrics, perflens_metrics_history. Device control: perflens_agent_info, perflens_agent_connect, perflens_list_processes, perflens_start_profiling, perflens_stop_profiling, perflens_collection_pause. Plus perflens_export, which writes collapsed stacks / JSON / SVG to a file.

Profiles are large — a single event’s snapshot runs from kilobytes to megabytes — so every tool returns a ranked, capped view and tells the agent exactly how to page for more. Config mutation, session deletion and the file browser are deliberately not exposed. The companion skill at skills/perflens-profiling/ teaches the profiling method the tools support; install it with cp -r skills/perflens-profiling ~/.claude/skills/.

Building release packages

./build_package.sh              # server wheel/sdist + native C agent
./build_package.sh --server     # Python wheel + sdist only
./build_package.sh --agent-c    # C agent only (native static binary)

Outputs land in dist/:

dist/
├── perflens-<ver>-py3-none-any.whl     # server (uvx / pipx / pip)
├── perflens-<ver>.tar.gz               # server sdist
├── perflens-agent-c-<ver>.tar.gz       # agent tarball
└── perflens-agent-linux-<arch>         # agent raw binary (stable name)

CI

.github/workflows/test.yml runs the pytest suite on Python 3.10–3.13 (parser, aggregator differentials against device-captured fixtures, source mapper, HTTP API, MCP tools, provisioning against a fake release server, and the C-agent wire protocol driven through a fake framing server with a perf shim), and gates every PR on ruff, mypy and tools/check_version.py. Two more jobs run the protocol tests against the agent built with AddressSanitizer+UBSan and with ThreadSanitizer (make SANITIZE=…) and the C unit tests (make check); a shellcheck job covers the shell scripts. A frontend job adds the OpenAPI schema and TypeScript typegen drift checks, vitest unit tests, a self-contained Playwright browser E2E that replays a fixture session through the real UI, and a smoke run of the docs screenshot harness.

.github/workflows/build.yml lints (ruff), runs the tests, builds and smoke-runs the Python wheel, builds the static C agent for five architectures (x86_64, aarch64, aarch64_be, armv7, armeb), and builds static addr2line/readelf tools bundles for perflens provision. Every agent asset is a musl static build (static glibc cannot resolve names on a foreign device), from toolchains published on the repository's toolchains release; both 32-bit ARM assets are soft-float. CI asserts what each asset claims — statically linked, the float ABI, BE8 for big-endian — strips the release binary and keeps the unstripped one as a CI artifact. Tagged pushes (v*) create a GitHub Release and attach all artifacts — including raw perflens-agent-linux-<arch> binaries with stable names that install-agent.sh and the agent's --update fetch — and publish the package to PyPI via Trusted Publishing (OIDC, no stored tokens).

Troubleshooting

perf_event_paranoid too high

The agent warns at startup if /proc/sys/kernel/perf_event_paranoid > 1 and the UI may show a limited event set.

sudo sysctl -w kernel.perf_event_paranoid=1

No function names

Compile with -g and don't strip. file ./myprogram should say not stripped and with debug_info.

No source line mapping

Double-check --binary points at the exact unstripped binary running on the target and --source-dir contains the source files. If the build root differs from your checkout, rewrite paths with --path-map /build/src=/home/me/src.

Agent can't connect

The server must be reachable on --port. Sanity-check with nc -zv <server-ip> 9999.

Container: one of the two perf record modes fails

Which one depends on the container, so probe rather than assume. Some environments strip the perf capability set: -p <pid> returns empty and a system-wide perf record -a works. An unprivileged LXC container is the opposite case — at perf_event_paranoid=1, per-PID recording works and -a fails outright with “Failure to open any events for recording”. perf_event_paranoid is not namespaced, so it is read-only from inside the container and lowering it requires the host.

Call-graph probing hangs / slow startup

Capability probing checks events in one batched perf stat and one perf record, then tries fp, dwarf and lbr call graphs and the pipe mode — about 11–13 s on the devices measured for 0.12.0, longer on slow single-core targets. It runs once per connection, and the agent answers status (reporting probing) and stop while it runs; the agent log ends it with Probe finished in N s. Switching process only re-checks the new pid.

Project layout

perflens/
├── install-agent.sh              # curl-able agent installer (no sudo)
├── agent-c/
│   ├── src/                      # C agent modules (agent.h + 12 .c files)
│   ├── Makefile                  # native + cross-compile targets
│   └── vendor/zstd/              # vendored zstd amalgamation
├── pyproject.toml                # pip/uv package (console script: perflens)
├── src/perflens/                 # the server package
│   ├── app.py                    # AppContext + lifecycle + main()
│   ├── config.py                 # ServerConfig, CLI parsing, tool probing
│   ├── state.py                  # profiling/metrics state + rebuild worker
│   ├── agentlink.py              # agent TCP wire protocol + session
│   ├── sessions.py               # persistence, replay, perf.data import
│   ├── web.py                    # FastAPI/uvicorn HTTP layer + SSE hub
│   ├── api/                      # Pydantic v2 schemas + response helpers
│   ├── mcp/                      # MCP server (optional [mcp] extra)
│   ├── cli.py                    # serve/import/push-agent/provision/mcp
│   ├── parser.py                 # perf script / perf stat parser
│   ├── aggregator.py             # incremental per-event aggregation
│   ├── source_mapper.py          # addr2line pipeline + path remap
│   ├── symcache.py               # persistent caches (~/.perflens/cache)
│   ├── provision.py              # user-space static-tools download
│   └── ui/                       # built React app (Vite output, ships in the wheel)
├── frontend/                     # React 19 + TypeScript + Vite SPA source
│   ├── src/                      # api client, stores, flamegraph, components
│   ├── e2e/                      # Playwright browser E2E (self-contained)
│   └── openapi.json              # committed schema (CI drift-checked)
├── skills/perflens-profiling/    # agent skill for the MCP server
├── tests/
│   ├── test_*.py                 # pytest (parser, aggregator, HTTP, MCP, agent, ...)
│   ├── fixtures/                 # gzipped perf sessions from real devices
│   ├── sample_workload.c
│   └── Makefile
├── build_package.sh
├── .github/workflows/test.yml    # pytest matrix + vitest + playwright e2e
└── .github/workflows/build.yml   # lint + test + wheel + agents + release