Open source · MIT

Real-time Linux profiling,
straight in your browser.

Drop a tiny agent on any Linux box, point it at a PID, and watch flame graphs, function tables, perf stat metrics, and line-level annotated source update live — no Node, no Docker, no sudo.

license agent arch wire install
PerfLens overview: perf counters, device health, sparklines, and per-core CPU all updating live in the browser
Live demo: function table updating in real time, flame graph, then source view

Counts update as each perf record round streams in. Flip to flame graph, click a function, land in source with line-level heat. Zero polling — Server-Sent Events.

What it does

One tool, the full perf pipeline.

Remote perf record, real-time SSE streaming, flame graphs, per-thread analysis, and source-level heat — without leaving the browser.

Real-time streaming

The agent runs perf record in 8-second rounds. Each round is zstd-compressed and pushed over TCP. Browser sees flame graphs update as new data arrives.

🔥

Interactive flame graphs

Hand-rolled SVG flame graphs — no charting library. Zoom into a frame, hover for sample counts, search by function name, breadcrumb back to root.

🔎

Line-level source heat

addr2line pipelined in batches of 500. Hot lines are heat-colored red/amber/green so you spot the cost without leaving the file.

📊

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.

Timeline scrubbing

Drag across a Device Health sparkline — a CPU spike, say — and the flame graph and function table rebuild from only the samples collected in that window.

🧑‍💻

Per-thread analysis

Filter flame graphs, function tables, and source annotations by thread. A dedicated Threads tab shows per-tid CPU breakdowns, top functions, and an optional real-time Live CPU column fed by the agent.

🛡

Static C agent

Single static C agent binary (~2 MB, vendored zstd, zero runtime deps) — runs on anything from bare-metal embedded boards to servers. One-line curl install, built-in self-update.

🧮

Capability probing

Agent enumerates which perf events the kernel actually supports, tries call-graph modes (fpdwarflbr), and picks the first that produces non-empty stacks.

Cross-compile aware

One --toolchain-prefix derives addr2line and readelf. --sysroot resolves shared libraries and source files under a target tree, like perf --symfs.

💾

Save & replay

Every session is saved as raw chunks on disk. Replay any past session lazily through the UI — or import a perf.data file directly with --import.

🤖

MCP server for AI agents

perflens mcp exposes the profile to any MCP client — hot functions, hot stacks, IPC and miss rates, source hot lines, session comparison, per-thread breakdowns — so an agent can find the bottleneck and, when asked, drive a live run on the device. See the Reference.

🔅

Two connect modes

--server: agent dials out to the server (reconnects with backoff). --listen: agent waits, server connects in through the UI's Live Debug wizard.

Tour

What you actually see.

Every screenshot below is captured from the real UI, by a Playwright harness that CI re-runs on every change — a live 199 Hz profile of tests/matrixlab, a 25-thread test workload, plus deterministic shots replayed from the committed regression fixtures.

Function table sorted by self-percent
Function table Self %, total %, sample counts, module column, live filter.
Interactive SVG flame graph
Flame graph Zoom, hover, search. Hand-rolled SVG layout — no d3.
Annotated source with line-level heat map
Source heat map Lines colored by sample share. Hot line jumps out red.
Per-thread CPU breakdown and top functions
Per-thread breakdown Each tid with sample count, CPU share, and its top functions.
Saved sessions list
Saved sessions Replay anything you've captured before, or import perf.data.
Function table in light theme
Light theme Dark or light — one click. CSS custom properties, no rebuild.
Flame graph zoomed into a call path with a breadcrumb trail
Zoom by ancestry Click a frame to focus its call path; breadcrumbs walk back out.
Flame graph with matching frames highlighted and the rest dimmed
Search the stack Matching frames highlight, everything else dims, with a match count.
Export menu offering SVG, collapsed stacks, and JSON
Export Standalone SVG, collapsed stacks for external tooling, or raw JSON.
Keyboard shortcut help overlay
Keyboard driven 15 for tabs, / to search, t for theme, ? for this overlay.
In-app documentation drawer
Docs in the app Setup, configuration, troubleshooting and architecture without leaving the tab.
Quick start

Profiling in under a minute.

The server installs from PyPI with uvx/pipx/pip; static agent binaries are attached to every release. Pick the install flavor and follow three steps.

1

Install the server on the machine where you view profiles

Python 3.10+, no sudo, everything user-space. pipx install perflens or pip install --user perflens work too.

uvx perflens serve \
    --source-dir /path/to/sources \
    --binary     /path/to/unstripped-binary
# UI: http://localhost:8080
2

Drop the agent on the Linux target

The agent is one static binary with zero dependencies — one-line install, no sudo. Two connect modes are supported.

curl -fsSL https://raw.githubusercontent.com/harshithsunku/perflens/master/install-agent.sh | sh

# Agent dials out to the server (with reconnect/backoff)
~/.perflens/bin/perflens-agent --server <server-ip>

# Or — agent listens, you connect from the UI's Live Debug wizard
~/.perflens/bin/perflens-agent --listen
3

Open the UI and start the wizard

Browse to http://<server-ip>:8080. With --server the UI switches as soon as the agent connects; with --listen, click Live Debug and point at the agent.

1

Build the static binary

Zero runtime dependencies; vendored zstd; cross-compiles from a single Makefile.

cd agent-c
make                                    # native x86_64
make CROSS=aarch64-linux-gnu-           # ARM64 little-endian
make CROSS=aarch64_be-linux-musl-       # ARM64 big-endian
make CROSS=arm-linux-gnueabihf-         # ARMv7 little-endian
make CROSS=armeb-linux-musleabihf-      # ARMv7 big-endian
2

Ship the single file

The output is one ~2 MB binary. No libc surprises, no Python needed.

scp perflens-agent user@device:/tmp/
ssh user@device /tmp/perflens-agent --server <server-ip>
3

Open the UI

Cross-compiles for five architectures; prebuilt binaries are attached to every release.

1

Clone & run the server

git clone https://github.com/harshithsunku/perflens.git
cd perflens
uv venv && uv pip install -e .
.venv/bin/perflens serve \
    --source-dir /path/to/source \
    --binary     /path/to/myprogram \
    --port       9999 \
    --http-port  8080
2

Copy the agent to the target and run

curl -fsSL https://raw.githubusercontent.com/harshithsunku/perflens/master/install-agent.sh | sh
~/.perflens/bin/perflens-agent --server <server-ip>
3

Open http://<server-ip>:8080

That's it. The UI auto-switches into the profiling view when samples start flowing.

Prerequisites. Target needs Linux + perf; local needs Python 3.10+ and uv/pip. addr2line/readelf come from binutils — if missing, perflens provision downloads static builds into ~/.perflens/bin (no sudo). For source-level annotation, your binary must be compiled with -g and not stripped.
Pipeline

How a sample travels.

perf record on the target. The agent flattens the trace with perf script, compresses with zstd, frames with a 5-byte header, and pushes over TCP. The server decompresses, parses per-event sample lists, builds flame graph trees, pipes addresses through addr2line in batches, and broadcasts the result to every connected browser via Server-Sent Events.

Typical zstd ratio on real perf script output: 20–40×.

Read the architecture →
PerfLens architecture diagram
Design rules

Built to a short list.

Simplicity first

A small, deliberate user-space stack — FastAPI/uvicorn/orjson/zstandard, installed with uvx. The React UI ships prebuilt inside the wheel, so Node is a build-time dependency only. No sudo, no Docker.

Defensive parsing

perf script format drifts across kernel versions. The parser handles 2.6 through 6.x output with optional [cpu], pid/tid, and flags fields.

No over-engineering

If a piece of code doesn't earn its complexity, it gets cut. The agent stays zero-dependency C; the server keeps a four-package install.

Generic & open

MIT-licensed. No proprietary names, IPs, credentials, or company-specific anything in the code or docs.