Skip to content

v0.11.0

Choose a tag to compare

@github-actions github-actions released this 13 Sep 10:08
· 20 commits to master since this release

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 perf could not. A perf built
    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 and addr2line independently agrees. Anything less
    certain keeps [unknown], because a confidently wrong name is worse than an
    honest blank. Applies to live capture, session replay and perf.data import
    alike, and needs no agent change.

  • --module-map DEVICE=LOCAL points a device module path at a local
    binary, for the case --sysroot cannot cover: a firmware path that exists
    nowhere on the controller. Same FROM=TO shape as --path-map, and settable
    at runtime via PATCH /api/config.

  • /api/index/status reports 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's PATH does not cover, and launching the agent with PATH= was the
    only workaround. The agent now takes --perf PATH (or PERFLENS_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_connect takes perf_path. On the wire this is an optional
    perf argument to verify_perf and a perf_path field in status — no new
    command or frame type. A candidate must be absolute (or a bare name found on
    PATH) and print perf version, and a change is refused mid-collection. An
    explicit --perf that does not work fails at startup instead of after a
    probe.

Fixed

  • --binary was attributed to every module in one remaining call site.
    source_mapper.py had been fixed; aggregator.py had not, and it computed a
    different binary than map_samples_to_lines did — so its _addr2line_cache
    lookup used a mismatched key and the per-file function lists silently
    under-reported for every non-main module.
  • addr2line results 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 armeb release asset could not run on real big-endian ARM. It was
    built hard-float, and without -march=armv7-a the toolchain defaults to
    ARMv5 and emits BE-32 where ARMv6+ implements BE-8 only. Both faults are
    independently fatal (SIGILL on the first instruction), measured on
    hardware. armeb now builds with armeb-linux-musleabi- (soft-float) and
    -march=armv7-a. Soft-float still runs on VFP hardware, so one asset covers
    both. CI note: build.yml now needs armeb-linux-musleabi-cross.tgz on
    the toolchains release.
  • 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 a start that always failed with
    "no perf record events available". The agent now falls back to cpu-clock
    and task-clock when 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 with perf stat, and stat accepting an event does not mean perf record will 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
    through record -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-prefix silently used the host addr2line. 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 to shutil.which, as readelf already did.
  • Exports ignored event for 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. json returned every event whatever was asked,
    a bogus event name answered 200, a bare cycles in an SVG export merged both
    PMUs of a hybrid CPU, and an empty event= produced a 65-byte SVG. Every
    format now resolves event exactly as /api/snapshot does: 400
    ambiguous_event naming the candidates, 404 listing what exists.
    Behaviour change: collapsed and svg render one event, so they now
    require event when a profile holds several; json without event still
    carries them all. The UI's collapsed-stacks download sends the selected
    event, and perflens_export picks a default.
  • Thread, time-window and source views merged events. /api/threads,
    /api/threads/<tid>, /api/window and /api/source defaulted to
    event=cycles and matched on base names, so a bare cycles merged 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
    resolve event like the snapshot and the exports: 400 ambiguous_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-agent could not pick the big-endian asset. It trusted
    uname -m, which had no armv7b entry 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
    ToolError and 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 a ToolError. The mcp>=2,<3 bound 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 .so files alike
    given a local copy (--binary, --sysroot or --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 | sh

Assets

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