Repository navigation
v0.11.0
PerfLens 0.11.0
Highlights
First hands-on validation on big-endian hardware — a big-endian ARMv7
embedded target (armv7b, single-core, kernel 4.4, no hardware PMU,
no SSH, no internet from the device).
Byte order turned out to be correct all along; the build flags and the
capability probe were not. Out of that pass came server-side naming of frames
the target's perf cannot symbolize, and a way to point the agent at a perf
that is not on PATH.
Upgrading — read this first. Views that render a single event — the
collapsed and svg exports, /api/threads, /api/threads/<tid>,
/api/window and /api/source — now require event when a profile holds
several, and answer 400 ambiguous_event naming the choices instead of merging
them. The UI and the MCP tools already send one; scripts calling the API
directly may need to.
Added
-
The server now names frames the target's
perfcould not. Aperfbuilt
without libelf resolves kernel frames from kallsyms but returns[unknown]
for every userspace frame, however good the binary is — measured at 99.6%
of samples on one device, which makes the profile unusable. The server holds
the unstripped binary and a matching toolchain, so it now recovers the name
from the address. On a replayed capture from that device, one 99.6%
[unknown]row became 64 named functions with every userspace frame
resolved.Additive by construction: a frame is renamed only when the address lands
inside a known symbol andaddr2lineindependently agrees. Anything less
certain keeps[unknown], because a confidently wrong name is worse than an
honest blank. Applies to live capture, session replay andperf.dataimport
alike, and needs no agent change. -
--module-map DEVICE=LOCALpoints a device module path at a local
binary, for the case--sysrootcannot cover: a firmware path that exists
nowhere on the controller. SameFROM=TOshape as--path-map, and settable
at runtime viaPATCH /api/config. -
/api/index/statusreports frame naming — how many userspace frames were
seen, how many are still unnamed, how many the server recovered, and a
one-line explanation. The UI shows it as a banner when symbolization is
degraded, so an all-[unknown]profile reads as a diagnosable condition
rather than a PerfLens bug.perflens_status(MCP) reports the measured
number instead of inferring from index counters. -
perf outside
PATH. Some targets install perf under a vendor prefix the
agent'sPATHdoes not cover, and launching the agent withPATH=was the
only workaround. The agent now takes--perf PATH(orPERFLENS_PERF), and
the server can set it on a running agent: the Live Debug wizard's Perf
Capabilities step and the control bar's settings each have a perf field, and
perflens_agent_connecttakesperf_path. On the wire this is an optional
perfargument toverify_perfand aperf_pathfield instatus— no new
command or frame type. A candidate must be absolute (or a bare name found on
PATH) and printperf version, and a change is refused mid-collection. An
explicit--perfthat does not work fails at startup instead of after a
probe.
Fixed
--binarywas attributed to every module in one remaining call site.
source_mapper.pyhad been fixed;aggregator.pyhad not, and it computed a
different binary thanmap_samples_to_linesdid — so its_addr2line_cache
lookup used a mismatched key and the per-file function lists silently
under-reported for every non-main module.addr2lineresults were discarded whenever the line was unknown, throwing
away a perfectly good function name. Ordinary for hand-written assembly,
which is exactly where a soft-float target spends its time.- The
armebrelease asset could not run on real big-endian ARM. It was
built hard-float, and without-march=armv7-athe toolchain defaults to
ARMv5 and emits BE-32 where ARMv6+ implements BE-8 only. Both faults are
independently fatal (SIGILLon the first instruction), measured on
hardware.armebnow builds witharmeb-linux-musleabi-(soft-float) and
-march=armv7-a. Soft-float still runs on VFP hardware, so one asset covers
both. CI note:build.ymlnow needsarmeb-linux-musleabi-cross.tgzon
thetoolchainsrelease. - A target with no hardware PMU could not be profiled at all. Every
hardware candidate failed and the three software events probed were all
stat-only, leaving zero record events and astartthat always failed with
"no perf record events available". The agent now falls back tocpu-clock
andtask-clockwhen the PMU offers no record event. This affects most
embedded hardware, not just big-endian targets. Targets that do
have working hardware counters are unaffected: the fallback is not probed at
all, so their event set is unchanged. - Record capability is now measured instead of inferred.
event_works()
probes withperf stat, and stat accepting an event does not meanperf recordwill take it. Events that pass the stat probe are now confirmed with
a short record probe before being advertised as record-capable. - Continuous pipe mode silently dropped call graphs. On perf 4.4 the same
capture yields ~10 frames per sample through a file and one leaf frame
throughrecord -o - | script -i -. The probe accepted any non-empty
output, so pipe mode was chosen and every flame graph collapsed to a single
level with no error reported. The probe now verifies call chains survive —
by their indented frame lines rather than by event name, so the
PMU-qualified names a hybrid CPU prints (cpu_core/cycles/) do not switch
continuous mode off. --toolchain-prefixsilently used the hostaddr2line. The check was
os.path.isfile(), which is False for the bare relative name the flag
documents, so the cross tool was replaced by the host's and logged only as
(system)— symbolizing a foreign binary with the wrong architecture's
tool. It now falls back toshutil.which, as readelf already did.- Exports ignored
eventfor two of their three formats.collapsed
summed every event into one profile — on a twelve-event hybrid capture the
file totalled exactly the sum of all twelve — so a flame graph built from it
added cycles to cache-misses.jsonreturned every event whatever was asked,
a bogus event name answered 200, a barecyclesin an SVG export merged both
PMUs of a hybrid CPU, and an emptyevent=produced a 65-byte SVG. Every
format now resolveseventexactly as/api/snapshotdoes: 400
ambiguous_eventnaming the candidates, 404 listing what exists.
Behaviour change:collapsedandsvgrender one event, so they now
requireeventwhen a profile holds several;jsonwithouteventstill
carries them all. The UI's collapsed-stacks download sends the selected
event, andperflens_exportpicks a default. - Thread, time-window and source views merged events.
/api/threads,
/api/threads/<tid>,/api/windowand/api/sourcedefaulted to
event=cyclesand matched on base names, so a barecyclesmerged both PMUs
of a hybrid CPU, an event that did not exist answered an empty and plausible
200, and on a PMU-less target the default did not exist at all. They now
resolveeventlike the snapshot and the exports: 400ambiguous_event
naming the candidates, 404 listing what exists, and an omitted event means
the only one present. With no samples yet they still return an empty view. perflens push-agentcould not pick the big-endian asset. It trusted
uname -m, which had noarmv7bentry and cannot tell a big-endian ARM
userland from a little-endian one. It now runs the same byte-order probe as
install-agent.sh.- The control bar never appeared after starting from the Live Debug wizard.
The profiling view stays mounted behind the wizard, and the bar re-read the
agent's state only when the connection flags changed — which they already had
at connect, while the agent was idle. Pause, resume, process switching and
profiling settings were unreachable until a page reload. Found driving the
wizard against a real device. - With mcp 2.2, every MCP tool error reached the agent as "Error executing
tool". The SDK now passes a tool's message through only for its own
ToolErrorand treats anything else as a crash, so the next-step hints the
tools are built around — which session ids exist, which events are real,
how to connect an agent — were replaced by a generic line.PerfLensError
is now aToolError. Themcp>=2,<3bound allowed 2.2, which is what a
fresh install resolves.
Known issues
- Whether a frame resolves depends on the address form perf emitted, not on
whether the module is a shared library. Per-round collection emits
file-relative offsets, which resolve for executables and.sofiles alike
given a local copy (--binary,--sysrootor--module-map). An
absolute runtime address cannot be resolved without the module's load
base, and that base can only be recovered by voting with named frames — of
which a libelf-less perf supplies none. Such frames stay[unknown]rather
than being guessed. Closing that case needs the agent to ship
/proc/PID/maps, deliberately deferred.
Install
# Server (any machine with Python 3.10+; everything user-space):
uvx perflens # or: pipx install perflens
uvx --from ./perflens-0.11.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.11.0-py3-none-any.whl | Server — Python wheel |
| perflens-0.11.0.tar.gz | Server — Python sdist |
| perflens-agent-c-0.11.0-linux-aarch64.tar.gz | Agent — versioned tarball |
| perflens-agent-c-0.11.0-linux-aarch64_be.tar.gz | Agent — versioned tarball |
| perflens-agent-c-0.11.0-linux-armeb.tar.gz | Agent — versioned tarball |
| perflens-agent-c-0.11.0-linux-armv7.tar.gz | Agent — versioned tarball |
| perflens-agent-c-0.11.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.sha256 | Agent binary checksum |
| perflens-agent-linux-aarch64_be | Agent — static binary (stable name; used by install-agent.sh and --update) |
| perflens-agent-linux-aarch64_be.sha256 | Agent binary checksum |
| perflens-agent-linux-armeb | Agent — static binary (stable name; used by install-agent.sh and --update) |
| perflens-agent-linux-armeb.sha256 | Agent binary checksum |
| perflens-agent-linux-armv7 | Agent — static binary (stable name; used by install-agent.sh and --update) |
| perflens-agent-linux-armv7.sha256 | Agent binary checksum |
| perflens-agent-linux-x86_64 | Agent — static binary (stable name; used by install-agent.sh and --update) |
| perflens-agent-linux-x86_64.sha256 | Agent binary checksum |
| 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 |