mirror of
https://github.com/leanprover/lean4.git
synced 2026-03-17 18:34:06 +00:00
This PR adds tooling for profiling Lean programs with human-readable function names in Firefox Profiler: - **`script/lean_profile.sh`** — One-command pipeline: record with samply, symbolicate, demangle, and open in Firefox Profiler - **`script/profiler/lean_demangle.py`** — Faithful port of `Name.demangleAux` from `NameMangling.lean`, with a postprocessor that folds compiler suffixes into compact annotations (`[λ, arity↓]`, `spec at context[flags]`) - **`script/profiler/symbolicate_profile.py`** — Resolves raw addresses via samply's symbolication API - **`script/profiler/serve_profile.py`** — Serves demangled profiles to Firefox Profiler without re-symbolication - **`PROFILER_README.md`** — Documentation including a guide to reading demangled names ### Example output in Firefox Profiler | Raw C symbol | Demangled | |---|---| | `l_Lean_Meta_Sym_main` | `Lean.Meta.Sym.main` | | `l_Lean_Meta_foo___redArg___lam__0` | `Lean.Meta.foo [λ, arity↓]` | | `l_Lean_MVarId_withContext___at__...___spec__2___boxed` | `Lean.MVarId.withContext [boxed] spec at Lean.Meta.bar[λ, arity↓]` | Example: <img width="1145" height="570" alt="image" src="https://github.com/user-attachments/assets/8d23cc6a-1b89-4c60-9f4a-9f9f0f6e7697" /> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
95 lines
3.1 KiB
Python
95 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Serve a Firefox Profiler JSON file and open it in the browser.
|
|
|
|
Unlike `samply load`, this does NOT provide a symbolication API,
|
|
so Firefox Profiler will use the names already in the profile as-is.
|
|
"""
|
|
|
|
import argparse
|
|
import gzip
|
|
import http.server
|
|
import io
|
|
import sys
|
|
import threading
|
|
import webbrowser
|
|
import urllib.parse
|
|
|
|
|
|
class ProfileHandler(http.server.BaseHTTPRequestHandler):
|
|
"""Serve the profile JSON and handle CORS for Firefox Profiler."""
|
|
|
|
profile_data = None # set by main()
|
|
|
|
def do_GET(self):
|
|
if self.path == "/profile.json":
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.send_header("Content-Encoding", "gzip")
|
|
self.send_header("Access-Control-Allow-Origin", "*")
|
|
self.end_headers()
|
|
self.wfile.write(self.profile_data)
|
|
else:
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
|
|
def do_OPTIONS(self):
|
|
# CORS preflight
|
|
self.send_response(200)
|
|
self.send_header("Access-Control-Allow-Origin", "*")
|
|
self.send_header("Access-Control-Allow-Methods", "GET")
|
|
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
|
self.end_headers()
|
|
|
|
def log_message(self, format, *args):
|
|
pass # suppress request logs
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Serve a profile JSON for Firefox Profiler")
|
|
parser.add_argument("profile", help="Profile file (.json or .json.gz)")
|
|
parser.add_argument("-P", "--port", type=int, default=3457,
|
|
help="Port to serve on (default: 3457)")
|
|
parser.add_argument("-n", "--no-open", action="store_true",
|
|
help="Do not open the browser")
|
|
args = parser.parse_args()
|
|
|
|
# Read the profile data (keep it gzipped for efficient serving)
|
|
if args.profile.endswith(".gz"):
|
|
with open(args.profile, "rb") as f:
|
|
ProfileHandler.profile_data = f.read()
|
|
else:
|
|
with open(args.profile, "rb") as f:
|
|
raw = f.read()
|
|
buf = io.BytesIO()
|
|
with gzip.GzipFile(fileobj=buf, mode="wb") as gz:
|
|
gz.write(raw)
|
|
ProfileHandler.profile_data = buf.getvalue()
|
|
|
|
http.server.HTTPServer.allow_reuse_address = True
|
|
server = http.server.HTTPServer(("127.0.0.1", args.port), ProfileHandler)
|
|
profile_url = f"http://127.0.0.1:{args.port}/profile.json"
|
|
encoded = urllib.parse.quote(profile_url, safe="")
|
|
viewer_url = f"https://profiler.firefox.com/from-url/{encoded}"
|
|
|
|
if not args.no_open:
|
|
# Open browser after a short delay to let server start
|
|
def open_browser():
|
|
webbrowser.open(viewer_url)
|
|
threading.Timer(0.5, open_browser).start()
|
|
|
|
print(f"Serving profile at {profile_url}", file=sys.stderr)
|
|
print(f"Firefox Profiler: {viewer_url}", file=sys.stderr)
|
|
print("Press Ctrl+C to stop.", file=sys.stderr)
|
|
|
|
try:
|
|
server.serve_forever()
|
|
except KeyboardInterrupt:
|
|
print("\nStopped.", file=sys.stderr)
|
|
server.server_close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|