initial commit
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user