Repository navigation
v0.8.0
PerfLens 0.8.0
Highlights
A stabilization release. The feature set was frozen on 2026-08-13 with the
MCP server as the last capability added; everything since has been hygiene,
CI hardening and documentation truth. It also publishes the large body of
work that accumulated after 0.7.0 — the server module split, the React
frontend, and API v2.
Upgrading: API v2 renamed every REST endpoint (see below), and
perflens.server has been removed. The web UI and the agent wire protocol
are unaffected.
Removed
perflens.server— a compatibility shim introduced in the 0.6.0-era
restructure and marked "kept for one release"; it has now outlived two.
Import from the modules that actually hold the code:perflens.app
(AppContext,build_context,main),perflens.config
(ServerConfig),perflens.state(ProfilingState,MetricsState).
This is a breaking change to a public import path. The repo-only
server/perflens_server.pywrapper, the orphanedrun_server.sh, and an
emptyrequirements-server.txtwent with it.
Added
- 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 realperfdata and drive a live run on a device.
Nineteen tools: hot functions (self and total), hot stacks, hardware
counters with derived IPC and miss rates, source hot lines, session
comparison, per-thread breakdowns, device health, agent lifecycle, and
file export. It is a client of the existing HTTP API — no core changes —
and the SDK is an optional extra (pip install 'perflens[mcp]'), so the
default install stays a four-dependency package.--read-onlyomits the
device-control and export tools; config mutation, session deletion and
the file browser are never exposed. Tools return ranked, capped views
with an explicit next-page call, because a single event's snapshot runs
from kilobytes to megabytes. skills/perflens-profiling/— a companion skill teaching an agent
the method the tools support: orient first, pick the event that matches
the question, read self and total, drill to hot source lines,
corroborate with IPC and device health, and the pitfalls that produce
confidently wrong answers.- API v2 — consistent REST surface (
/api/snapshot, paginated
/api/sessionswith DELETE,/api/sessions/{id}/export,
/api/live/export,/api/threads,/api/window,/api/agent,
unified/api/config), a uniform{"error": {code, message}}envelope
with real status codes, and SSE consolidated tostatus/agent/
data_version/perf_stat/metrics. - React 19 + TypeScript + Vite frontend replacing the vanilla-JS UI at
full feature parity, with types generated from the committed OpenAPI
schema and a self-contained Playwright E2E suite. The wheel ships the
prebuilt assets, souvx perflensstill needs no Node. - Typed Pydantic API models — the server split into
AppContext
modules (app,config,state,agentlink,sessions,export),
with the OpenAPI schema exported tofrontend/openapi.jsonand
drift-checked in CI. - UX polish: keyboard shortcuts with a help overlay, loading skeletons,
a diff legend, and accessibility roles on tabs and icon-only buttons.
Fixed
- The no-UI fallback answered 503 for every unmatched path. Once the
frontend became a gitignored Vite output, a source checkout (and the
Node-free CI job) tookcreate_app's fallback branch, whose catch-all
route returned the "UI not built" page for everything — so path
traversal probes got 503 instead of 404 and a mistyped API URL got HTML
instead of the error envelope. It now answers/only and stays out of
the OpenAPI schema; every other path 404s exactly as the static mount
does. - OpenAPI drift across Python versions. The 413 response carried no
explicit description, so FastAPI filled it from
http.client.responses[413]— whose phrase changed in Python 3.13
("Request Entity Too Large" → "Content Too Large"). Pinned, so the
exported schema no longer depends on the interpreter. __version__had drifted to 0.6.0 while the package was 0.7.0; all
three version locations now agree.- The test suite no longer behaves differently depending on whether the
frontend happened to be built locally. - Deep call stacks returned 500 from
/api/snapshot, blanking the UI.
orjson refuses to encode past a fixed nesting depth (254 containers), and
a flame graph level costs two of them — the node and its children list —
so a profile containing a stack deeper than ~126 frames could not be
serialized at all. Deeply recursive workloads really do produce such
stacks. Flame graph depth is now capped, with the cut point marked
truncated, so one very deep stack renders short instead of taking the
whole view down. Found by profiling a 25-thread local workload; the
committed test fixtures are too shallow to reach it. perflens_status(MCP) claimed source annotation was unavailable when
it wasn't. It keyed the warning onsymbols_loaded, which only counts
the eager pre-index pass that runs when a binary is configured at runtime
— so a server started with--binaryreported zero while resolving
symbols lazily and serving line-level annotation fine. The tool now also
reports the source-index size and warns only when nothing is resolvable,
so an agent is no longer told to skip a working feature.- The docs drawer displayed a version the build was not. It carried a
hand-typedv0.8.0string inside a package that shipped as 0.7.0, wired
to nothing and absent from the release checklist. It is now injected from
the canonicalVERSIONfile at build time, andtools/check_version.py
asserts every location agrees — including that no version literal has
crept back into the frontend source. Wired into CI. - The committed test fixtures carried device IP addresses in their
session_idandagentmetadata, against this project's own rule that
no addresses appear anywhere in the repo. Renamed to generic identifiers. - Replaying a saved session showed no hardware counters. Both fixture
materializers discarded the captured metadata and wrote a stub with
total_samples: 0and an emptyperf_stat, so a replayed session
rendered a single empty stat card instead of the full counter set.
Changed
- Docs screenshots and the demo GIF are regenerated from the React UI
and now cover the differential view, timeline scrubbing, keyboard
shortcuts, flame graph search and zoom, export, and the in-app docs
drawer — twelve stills, up from seven captured before the rewrite. They
come from a Playwright harness (frontend/docs-shots/) that CI
smoke-runs on every pull request, so it cannot silently rot the way the
puppeteer scripts it replaces did. - Every runtime dependency gained an upper bound (
fastapi<1.0,
uvicorn<1.0,orjson<4,zstandard<1.0,httpx<1.0). Floors alone
allowed a future major release to resolve into a freshuvx perflens
install and break it with no change here. Note the consequence: when
FastAPI 1.0 ships, installs pin to the last 0.x until a new release. - CI actions moved off the deprecated Node 20 runtime, lint now runs on
pull requests (it had only ever run post-merge), and the release path
verifies the schema and version before building.
Install
# Server (any machine with Python 3.10+; everything user-space):
uvx perflens # or: pipx install perflens
uvx --from ./perflens-0.8.0-py3-none-any.whl perflens # from this release
# Agent (on the target device — zero dependencies, no sudo):
curl -fsSL https://raw.githubusercontent.com/harshithsunku/perflens/master/install-agent.sh | shAssets
| Asset | What it is |
|---|---|
| perflens-0.8.0-py3-none-any.whl | Server — Python wheel |
| perflens-0.8.0.tar.gz | Server — Python sdist |
| perflens-agent-c-0.8.0-linux-aarch64.tar.gz | Agent — versioned tarball |
| perflens-agent-c-0.8.0-linux-aarch64_be.tar.gz | Agent — versioned tarball |
| perflens-agent-c-0.8.0-linux-armeb.tar.gz | Agent — versioned tarball |
| perflens-agent-c-0.8.0-linux-armv7l.tar.gz | Agent — versioned tarball |
| perflens-agent-c-0.8.0-linux-x86_64.tar.gz | Agent — versioned tarball |
| perflens-agent-linux-aarch64 | Agent — static binary (stable name; used by install-agent.sh and --update) |
| perflens-agent-linux-aarch64_be | Agent — static binary (stable name; used by install-agent.sh and --update) |
| perflens-agent-linux-armeb | Agent — static binary (stable name; used by install-agent.sh and --update) |
| perflens-agent-linux-armv7l | Agent — static binary (stable name; used by install-agent.sh and --update) |
| perflens-agent-linux-x86_64 | Agent — static binary (stable name; used by install-agent.sh and --update) |
| perflens-tools-linux-aarch64.tar.gz | Static addr2line+readelf for perflens provision |
| perflens-tools-linux-aarch64.tar.gz.sha256 | Tools bundle checksum |
| perflens-tools-linux-x86_64.tar.gz | Static addr2line+readelf for perflens provision |
| perflens-tools-linux-x86_64.tar.gz.sha256 | Tools bundle checksum |