mirror of
https://github.com/flectar/mail.git
synced 2026-11-11 17:57:54 +00:00
100 lines
4.7 KiB
Python
100 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Measure CPU-rendered welcome -> tray -> restored memory on isolated profiles.
|
|
|
|
Requires Linux /proc and a display. Unset WAYLAND_DISPLAY to force X11.
|
|
Uses the application's opt-in metrics and production tray lifecycle helpers.
|
|
"""
|
|
import argparse
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import queue
|
|
import statistics
|
|
import subprocess
|
|
import tempfile
|
|
import threading
|
|
import time
|
|
|
|
spec = importlib.util.spec_from_file_location("startup", Path(__file__).with_name("benchmark-startup.py"))
|
|
startup = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(startup)
|
|
|
|
|
|
def run(binary, screenshots, include_mappings=False):
|
|
with tempfile.TemporaryDirectory(prefix="flectar-tray-benchmark-") as root:
|
|
env = dict(os.environ, XDG_DATA_HOME=root + "/data", XDG_CACHE_HOME=root + "/cache",
|
|
FLECTAR_RENDERER="cpu", FLECTAR_STARTUP_METRICS="1",
|
|
FLECTAR_BENCHMARK_DISABLE_SYNC="1", FLECTAR_BENCHMARK_TRAY_INTERVAL_MS="6000",
|
|
FLECTAR_BENCHMARK_EXIT_AFTER_MS="18000")
|
|
if screenshots:
|
|
env["FLECTAR_BENCHMARK_SCREENSHOT_DIR"] = str(screenshots.resolve())
|
|
process = subprocess.Popen([str(binary)], env=env, stderr=subprocess.PIPE,
|
|
stdout=subprocess.DEVNULL, text=True)
|
|
messages = queue.Queue()
|
|
threading.Thread(target=startup.stderr_reader, args=(process.stderr, messages), daemon=True).start()
|
|
events, tail, phases, phase_mappings = {}, [], {}, {}
|
|
try:
|
|
for phase, event in [("visible", "core_ready_frame"), ("tray", "tray_hidden"),
|
|
("restored", "tray_restored_frame")]:
|
|
startup.wait_for_metric(process, messages, events, tail, event, 45)
|
|
if "render_error" in events[event]:
|
|
raise RuntimeError(events[event])
|
|
time.sleep(2)
|
|
cpu, began = startup.read_cpu_seconds(process.pid), time.monotonic()
|
|
time.sleep(2)
|
|
resources = startup.read_proc_resources(process.pid)
|
|
resources["idle_cpu_percent"] = (startup.read_cpu_seconds(process.pid) - cpu) / (time.monotonic() - began) * 100
|
|
phases[phase] = resources
|
|
if include_mappings:
|
|
phase_mappings[phase] = startup.read_proc_mappings(
|
|
process.pid, startup.runtime_executable(process.pid)
|
|
)
|
|
process.wait(timeout=8)
|
|
startup.drain_messages(messages, events, tail)
|
|
if process.returncode:
|
|
raise RuntimeError(tail)
|
|
renderer = events.get("renderer_selected", {})
|
|
if renderer.get("active") != "cpu" or renderer.get("wgpu_initialized") is not False:
|
|
raise RuntimeError(f"expected CPU renderer without WGPU, got {renderer}")
|
|
finally:
|
|
if process.poll() is None:
|
|
process.terminate()
|
|
process.wait(timeout=5)
|
|
result = dict(phases=phases, events=events)
|
|
if include_mappings:
|
|
result["phase_mappings"] = phase_mappings
|
|
return result
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--binary", type=Path, required=True)
|
|
parser.add_argument("--rounds", type=int, default=3)
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
parser.add_argument("--screenshots", type=Path)
|
|
parser.add_argument("--include-mappings", action="store_true",
|
|
help="Include anonymized per-mapping /proc smaps counters for each phase.")
|
|
args = parser.parse_args()
|
|
if args.rounds < 1:
|
|
parser.error("rounds must be positive")
|
|
binary = args.binary.resolve()
|
|
runs = []
|
|
for index in range(args.rounds):
|
|
result = run(binary, args.screenshots / str(index + 1) if args.screenshots else None,
|
|
args.include_mappings)
|
|
runs.append(result)
|
|
print(json.dumps(result["phases"]), flush=True)
|
|
report = dict(binary=str(binary), binary_sha256=startup.file_sha256(binary),
|
|
include_mappings=args.include_mappings,
|
|
display=os.environ.get("DISPLAY"), wayland_display=os.environ.get("WAYLAND_DISPLAY"),
|
|
platform=startup.platform.platform(), screenshots=bool(args.screenshots), runs=runs,
|
|
medians={phase: {key: statistics.median(r["phases"][phase][key] for r in runs)
|
|
for key in runs[0]["phases"][phase]} for phase in runs[0]["phases"]})
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.output.write_text(json.dumps(report, indent=2) + "\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|