A Python CLI tool for CPU and memory profiling of Lighthouse.
WARNING: This tool is a work-in-progress and largely vibecoded. There will be bugs
- CPU profiling: via
perf recordwith automatic flamegraph generation - Memory profiling: via jemalloc heap dumps
- Epoch boundary isolation: capture profiles around specific epoch transitions
- Category-based analysis: configurable pattern matching to group samples by subsystem
- Custom flamegraph timeline: timeline of flamegraphs across an epoch boundary colour-coded by subsystem
- Comparison: side-by-side delta reports between two profiling runs
- Metrics scraping: Prometheus metrics captured at epoch boundaries for cache/timing analysis
- Python 3.11+
- uv (recommended) or pip
- Linux with
perfinstalled - inferno (
cargo install inferno) jeprof(from thejemallocpackage) (for memory profiling)
# Install uv if you don't have it
# Arch: sudo pacman -S uv
# Other: curl -LsSf https://astral.sh/uv/install.sh | sh
# Clone and install
git clone <repo-url> spyglass
cd spyglass
uv venv
uv pip install -e .
# Activate the virtual environment
source .venv/bin/activate # bash/zsh
source .venv/bin/activate.fish # fish
# Now `spyglass` is available directly
spyglass --versionIf you've installed with uv pip install -e ., you can use spyglass directly. Otherwise, use python3 -m spyglass instead.
# Configure (at minimum you will need to add the path to your Lighthouse directory)
cp configs/config.example.toml configs/config.toml
vim configs/config.toml
# Full workflow: build + run
spyglass profile --mode cpu -n my-experiment
# Or step by step
spyglass build --mode cpu
spyglass run --mode cpu -n baseline
# Then analyze
spyglass analyze baseline --filter allBuilds Lighthouse with profiling instrumentation.
python3 -m spyglass build --mode cpu # Frame pointers for perf
python3 -m spyglass build --mode memory # jemalloc profiling support- CPU mode: passes
RUSTFLAGS="-C force-frame-pointers=yes" - Memory mode: uses
--profile release-profilingwith--features jemalloc-profilingand setsJEMALLOC_SYS_WITH_MALLOC_CONFwithprof:true
Runs Lighthouse under a profiler with a mock execution layer.
spyglass run --mode cpu -n baseline
spyglass run --mode cpu -n my-test --runs 3 # three back-to-back runsThe tool automatically:
- Starts
lcli mock-elfor the execution layer - Enables the beacon HTTP API and metrics server
- Polls the beacon API to detect sync completion and epoch boundaries
- Scrapes Prometheus metrics at epoch boundaries (pre/post delta)
- Terminates cleanly on completion or Ctrl+C
Processes profiling output into flamegraphs and markdown reports.
spyglass analyze my-run --filter epoch-boundary
spyglass analyze my-run --filter all # runs all three filtersProduces (per filter, under views/<filter>/):
profile.collapsed— collapsed stack formatflamegraph.svg— interactive flamegraphanalysis.md— category breakdown + top functions
Compares two profiling runs.
spyglass compare baseline optimized --filter epoch-boundary
spyglass compare baseline optimized --filter all # compares all available views
spyglass compare --pr 6789 --filter all # config nickname vs the pr-6789 profileProduces a comparison.md with category-level and function-level deltas, plus a differential flamegraph. All numbers are per-run averages, so it's fine if one profile has more runs than the other. Deltas that clearly exceed the run-to-run noise are marked ✱.
If the two profiles were built from different commits, the report starts with a changed-code spotlight: spyglass finds the diff between the two commits and shows how much CPU time the changed functions cost on each side. This is the quickest way to see whether a PR did what it promised.
Useful flags:
--pr 6789— compare against thepr-6789profile; the baseline defaults to the config'snickname--focus <regex>— add extra functions to the spotlight--no-spotlight— skip the spotlight section
Convenience command: build + run + analyze in one step.
spyglass profile --mode cpu -n my-experimentExports profile data in formats suitable for external tools.
# Export filtered perf script text (for Firefox Profiler)
spyglass export baseline perf-script --filter epoch-boundary
# Export a filtered flamegraph SVG
spyglass export baseline flamegraph --filter epoch-boundary
# Generate an interactive HTML flame chart (epoch boundary timeline)
spyglass export baseline flamechart
spyglass export baseline flamechart --bin-size 0.25 # finer time resolutionThe perf-script format can be uploaded to Firefox Profiler for interactive analysis. The flamechart format produces a self-contained HTML file with a category-coloured timeline and per-bin flamegraphs.
Removes spyglass artifacts (PR checkouts and/or profiling results).
spyglass clean # Remove PR checkouts only (default)
spyglass clean all --force # Remove checkouts + profiles
spyglass clean profiles # Remove only profiling results (prompts for confirmation)Attach to an already-running Lighthouse node instead of building and managing the runtime. Spyglass finds the process automatically by scanning /proc for a lighthouse bn process matching the network and http_port in your config.
# Attach to the local lighthouse bn (auto-detected from config)
spyglass run --mode cpu --attach -n my-profile
# Explicit PID if auto-detection can't disambiguate
spyglass run --mode cpu --attach --pid 12345 -n my-profileAfter profiling completes, the lighthouse process is left running — spyglass only detaches perf.
When running two simultaneous lighthouse instances on the same network (e.g. for A/B comparison), use separate configs with different http_port values to distinguish them:
# configs/stable.toml
[lighthouse]
http_port = 5052
# configs/unstable.toml
[lighthouse]
http_port = 5053# Each command finds the right process by matching http_port
spyglass run --mode cpu --attach -c stable -n stable --runs 3
spyglass run --mode cpu --attach -c unstable -n unstable --runs 3
# Compare
spyglass analyze stable --filter all
spyglass analyze unstable --filter all
spyglass compare stable unstable --filter allFetch and profile a pull request directly from sigp/lighthouse:
# Profile PR #6789 (clones into checkouts/pr-6789/, builds, profiles)
spyglass profile --mode cpu --pr 6789
# Compare against your local branch
spyglass profile --mode cpu -n baseline
spyglass analyze baseline --filter all
spyglass analyze pr-6789 --filter all
spyglass compare baseline pr-6789 --filter allThe --pr flag works with any command (build, run, profile). It:
- Shallow-clones the Lighthouse repo into
checkouts/pr-<number>/ - Fetches the PR ref and checks it out
- Uses that checkout for building and profiling
- Defaults the nickname to
pr-<number>
profiles/
<nickname-or-branch>/
cpu/
config.toml # Resolved config snapshot shared by the runs (reusable with -c)
run-1/ # One directory per profiling run
perf.data # Raw perf recording
profile.collapsed # Full collapsed stacks (unfiltered)
epochs.json # Detected epoch boundaries with timestamps
run.json # Run metadata (config, timing, clock offset)
metrics/
epoch_<N>_pre.txt
epoch_<N>_post.txt
epoch_<N>_delta.json
views/
epoch_boundary/ # Filtered to epoch boundary windows
profile.collapsed
flamegraph.svg
analysis.md
mid_epoch/ # Filtered to mid-epoch periods
...
steady_state/ # Filtered to steady-state periods
...
run-2/
...
aggregate/ # Merged views across runs + variance stats (rebuilt by analyze)
...
memory/
run-1/
heap.*.heap
heap_analysis.md
Config files live in configs/. Copy the example to configs/config.toml and edit it:
that's the default config, loaded whenever no -c is given.
There are three ways to vary a run on top of that, from heaviest to lightest:
-
Full config files — complete TOML files in
configs/, selected with-c/--config. Bare names are resolved inconfigs/(with or without the.tomlextension), so-c gnosisfindsconfigs/gnosis.toml. A path outsideconfigs/also works. -
Experiments — named partial configs in
configs/experiments.toml, selected with-x/--experiment. Each experiment is a table of overrides merged over the base config, so it only needs the keys that change:# configs/experiments.toml [batch-t8.lighthouse] extra_flags = ["--subscribe-all-subnets", "--beacon-processor-attestation-batch-trigger", "8"]
spyglass profile --mode cpu -x batch-t8
The run nickname defaults to the experiment name unless the experiment sets
profiling.nicknameitself (a CLI-nbeats both). The experiments file is looked up next to whichever config-cselected. -
CLI overrides —
-n/--nicknameand-o/--output-diroverride the loaded config directly.
See configs/config.example.toml for all options:
[paths]
lighthouse_dir = "~/path/to/lighthouse_dir"
[lighthouse]
network = "mainnet"
checkpoint_sync_url = "https://mainnet.checkpoint.sigp.io"
extra_flags = ["--subscribe-all-subnets", "--import-all-attestations"]
http_port = 5052
metrics_port = 5054
[profiling]
perf_frequency = 1000
profile = "release"
disable_backfill = true
output_dir = "./profiles"
nickname = ""
start_slot = 16
end_slot = 15
safety_timeout = 7200
[filtering]
epoch_boundary_warmup = 6
epoch_boundary_cooldown = 6
[mock_el]
listen_address = "127.0.0.1"
listen_port = 8551The network field is passed to both lighthouse bn and lcli mock-el. Supported values: mainnet, gnosis, holesky, sepolia, etc.
The categories.toml file defines pattern-based sample classification. Categories are checked in priority order (first match wins). See the file for the default Lighthouse categories.
| Filter | Description |
|---|---|
all |
Runs all three filters below in sequence |
epoch-boundary |
Samples within warmup/cooldown of epoch transitions |
mid-epoch |
Samples NOT near epoch boundaries |
steady-state |
Samples after sync completes, excluding epoch boundaries |
Apache 2.0 — see LICENSE.
