mirror of
https://github.com/flectar/mail.git
synced 2026-11-12 10:15:19 +00:00
159 lines
5.8 KiB
Python
159 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Measure an unsnapshotted, empty-profile Linux launch at a fixed time.
|
|
|
|
This intentionally does not enable FLECTAR_STARTUP_METRICS, which forces frame
|
|
snapshots in the regular startup benchmark. The sample is timed from process
|
|
launch, not from a rendered-frame event.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
import platform
|
|
import statistics
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
from pathlib import Path
|
|
|
|
|
|
spec = importlib.util.spec_from_file_location(
|
|
"startup_benchmark", Path(__file__).with_name("benchmark-startup.py")
|
|
)
|
|
startup = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(startup)
|
|
|
|
|
|
def run_once(binary: Path, sample_seconds: float, include_mappings: bool) -> dict:
|
|
with tempfile.TemporaryDirectory(prefix="flectar-normal-startup-") as temporary:
|
|
root = Path(temporary)
|
|
environment = os.environ.copy()
|
|
for name in (
|
|
"FLECTAR_STARTUP_METRICS",
|
|
"FLECTAR_BENCHMARK_EXIT_AFTER_MS",
|
|
"FLECTAR_BENCHMARK_SCREENSHOT_DIR",
|
|
"FLECTAR_BENCHMARK_TRAY_INTERVAL_MS",
|
|
):
|
|
environment.pop(name, None)
|
|
environment.update(
|
|
XDG_DATA_HOME=str(root / "data"),
|
|
XDG_CACHE_HOME=str(root / "cache"),
|
|
FLECTAR_RENDERER="cpu",
|
|
FLECTAR_BENCHMARK_DISABLE_SYNC="1",
|
|
)
|
|
with tempfile.TemporaryFile(mode="w+t") as log:
|
|
process = subprocess.Popen(
|
|
[str(binary)],
|
|
env=environment,
|
|
stdin=subprocess.DEVNULL,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=log,
|
|
text=True,
|
|
)
|
|
try:
|
|
started = time.monotonic()
|
|
time.sleep(sample_seconds)
|
|
if process.poll() is not None:
|
|
raise RuntimeError(f"application exited before sample: {process.returncode}")
|
|
resources = startup.read_proc_resources(process.pid)
|
|
mappings = (
|
|
startup.read_proc_mappings(process.pid, binary)
|
|
if include_mappings
|
|
else None
|
|
)
|
|
sampled_after = time.monotonic() - started
|
|
finally:
|
|
if process.poll() is None:
|
|
process.terminate()
|
|
try:
|
|
process.wait(timeout=5)
|
|
except subprocess.TimeoutExpired:
|
|
process.kill()
|
|
process.wait(timeout=5)
|
|
log.seek(0)
|
|
renderer = None
|
|
for line in log:
|
|
if line.startswith(startup.RENDERER_PREFIX):
|
|
payload = json.loads(line[len(startup.RENDERER_PREFIX) :])
|
|
if payload.get("event") == "selected":
|
|
renderer = payload
|
|
if not renderer or renderer.get("active") != "cpu" or renderer.get("wgpu_initialized") is not False:
|
|
raise RuntimeError(f"expected CPU renderer without WGPU, got {renderer}")
|
|
|
|
result = {
|
|
"idle": resources,
|
|
"sampled_after_seconds": sampled_after,
|
|
"renderer_selected": renderer,
|
|
}
|
|
if mappings is not None:
|
|
result["mapping_report"] = mappings
|
|
return result
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--binary", required=True, type=Path)
|
|
parser.add_argument("--rounds", type=int, default=3)
|
|
parser.add_argument("--sample-seconds", type=float, default=5.5)
|
|
parser.add_argument("--include-mappings", action="store_true")
|
|
parser.add_argument("--output", required=True, type=Path)
|
|
args = parser.parse_args()
|
|
if sys.platform != "linux":
|
|
parser.error("this benchmark requires Linux /proc")
|
|
if args.rounds < 1 or args.sample_seconds <= 0:
|
|
parser.error("rounds and sample seconds must be positive")
|
|
if not os.environ.get("DISPLAY") and not os.environ.get("WAYLAND_DISPLAY"):
|
|
parser.error("a display is required")
|
|
binary = args.binary.resolve()
|
|
if not binary.is_file() or not os.access(binary, os.X_OK):
|
|
parser.error(f"binary is not executable: {binary}")
|
|
|
|
runs = []
|
|
for index in range(args.rounds):
|
|
run = run_once(binary, args.sample_seconds, args.include_mappings)
|
|
run["round"] = index + 1
|
|
runs.append(run)
|
|
print(
|
|
f"round={index + 1} rss_kib={run['idle']['rss_kib']} "
|
|
f"pss_kib={run['idle']['pss_kib']}",
|
|
flush=True,
|
|
)
|
|
names = sorted({name for run in runs for name in run["idle"]})
|
|
report = {
|
|
"schema_version": 1,
|
|
"generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
"identity": {
|
|
"binary": str(binary),
|
|
"binary_sha256": startup.file_sha256(binary),
|
|
"git_commit": startup.command_output("git", "rev-parse", "HEAD"),
|
|
"platform": platform.platform(),
|
|
"display": os.environ.get("DISPLAY"),
|
|
"wayland_display": os.environ.get("WAYLAND_DISPLAY"),
|
|
},
|
|
"configuration": {
|
|
"rounds": args.rounds,
|
|
"sample_seconds": args.sample_seconds,
|
|
"include_mappings": args.include_mappings,
|
|
"forced_snapshots": False,
|
|
"provider_sync_disabled": True,
|
|
},
|
|
"summary": {
|
|
"median_idle": {
|
|
name: statistics.median(run["idle"][name] for run in runs)
|
|
for name in names
|
|
}
|
|
},
|
|
"runs": runs,
|
|
}
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n")
|
|
print(f"report={args.output.resolve()}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|