96 lines
5.3 KiB
Python
96 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Run the opt-in production GUI T20 interaction workload and pin its evidence."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import platform
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
|
|
def hashed(path, label):
|
|
with path.open("rb") as stream:
|
|
digest = hashlib.file_digest(stream, "sha256").hexdigest()
|
|
return {"path": label, "size": path.stat().st_size, "sha256": digest}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--binary", type=Path, required=True)
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
source = Path(__file__).resolve().parents[1]
|
|
binary = args.binary.resolve()
|
|
output = args.output.resolve()
|
|
if not binary.is_file():
|
|
parser.error("--binary must be an existing test_app executable")
|
|
if output.exists():
|
|
parser.error("--output must be a new directory to preserve earlier evidence")
|
|
if os.environ.get("QTWEBENGINE_DISABLE_SANDBOX") or any(x in os.environ.get("QTWEBENGINE_CHROMIUM_FLAGS", "")
|
|
for x in ("--no-sandbox", "--disable-setuid-sandbox")):
|
|
parser.error("Sandbox disabling is not allowed for this measurement")
|
|
output.mkdir(parents=True)
|
|
suffix = ".exe" if platform.system() == "Windows" else ""
|
|
binaries = [binary, binary.parent / ("docview-pdf-worker" + suffix), binary.parent / ("docview-archive-worker" + suffix)]
|
|
app = binary.parent / ("docview" + suffix)
|
|
if app.exists():
|
|
binaries.append(app) # Recorded for comparison; the tested shell is test_app.
|
|
pdfium = source / ".deps/pdfium/lib/libpdfium.so"
|
|
if pdfium.exists():
|
|
binaries.append(pdfium)
|
|
source_files = [source / "CMakeLists.txt", source / "resources.qrc", source / "tests/test_app.cpp",
|
|
source / "tests/interaction_measurement.h", Path(__file__).resolve()]
|
|
for directory in ("src", "qml", "resources"):
|
|
source_files.extend(p for p in (source / directory).rglob("*") if p.is_file() and p.suffix in (".cpp", ".h", ".qml", ".js", ".toml"))
|
|
source_files = sorted(set(source_files))
|
|
before_binaries = [hashed(p, p.name) for p in binaries]
|
|
before_sources = [hashed(p, str(p.relative_to(source))) for p in source_files]
|
|
environment = os.environ.copy()
|
|
environment["DOCVIEW_INTERACTION_OUTPUT"] = str(output)
|
|
command = [str(binary), "measureInteractionWorkload"]
|
|
start = time.monotonic()
|
|
try:
|
|
run = subprocess.run(command, env=environment, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
timeout=180, check=False)
|
|
log, code = run.stdout, run.returncode
|
|
except subprocess.TimeoutExpired as error:
|
|
log, code = error.stdout or b"", 124
|
|
(output / "test.log").write_bytes(log)
|
|
after_binaries = [hashed(p, p.name) for p in binaries]
|
|
after_sources = [hashed(p, str(p.relative_to(source))) for p in source_files]
|
|
measurement_path = output / "measurement.json"
|
|
measurement = json.loads(measurement_path.read_text()) if measurement_path.exists() else {}
|
|
cpu = ""
|
|
if Path("/proc/cpuinfo").exists():
|
|
cpu = next((line.partition(":")[2].strip() for line in Path("/proc/cpuinfo").read_text().splitlines()
|
|
if line.startswith("model name")), "")
|
|
mem = ""
|
|
if Path("/proc/meminfo").exists():
|
|
mem = next((line.partition(":")[2].strip() for line in Path("/proc/meminfo").read_text().splitlines()
|
|
if line.startswith("MemTotal:")), "")
|
|
stable = before_binaries == after_binaries and before_sources == after_sources
|
|
record = {"schemaVersion": 1, "command": [binary.name, "measureInteractionWorkload"],
|
|
"testedExecutable": binary.name, "buildDirectory": binary.parent.name,
|
|
"exitCode": code, "elapsedSeconds": time.monotonic() - start,
|
|
"success": code == 0 and measurement.get("success") is True and stable,
|
|
"binaryAndSourceHashesStableDuringRun": stable, "binaries": before_binaries,
|
|
"sources": before_sources, "host": {"platform": platform.platform(), "cpu": cpu,
|
|
"logicalCpuCount": os.cpu_count(), "memTotal": mem},
|
|
"measurement": hashed(measurement_path, "measurement.json") if measurement_path.exists() else None,
|
|
"limitations": ["Development-host synthetic workload, not the provisional reference machine.",
|
|
"Xvfb/software rendering when selected by the caller; no native GPU or physical scanout acceptance.",
|
|
"frameSwapped is a presentation-queue notification; Web is not correlated to Chromium frame IDs.",
|
|
"GUI timer gaps include host scheduling and instrumentation; they are not a direct measurement of exclusively application-caused stalls.",
|
|
"No OS cache flush. No Windows/Ubuntu acceptance inferred.",
|
|
"Normal app CTest deliberately skips this opt-in slot; this dedicated run executes it."]}
|
|
(output / "record.json").write_text(json.dumps(record, indent=2, ensure_ascii=False) + "\n")
|
|
print(json.dumps({"success": record["success"], "output": str(output), "exitCode": code}, ensure_ascii=False))
|
|
return 0 if record["success"] else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|