163 lines
7.9 KiB
Python
163 lines
7.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Analyze one private fixed-geometry buffer run without assigning a frame to input."""
|
|
|
|
import hashlib
|
|
import json
|
|
import pathlib
|
|
import re
|
|
import statistics
|
|
import sys
|
|
|
|
from PIL import Image
|
|
|
|
if len(sys.argv) != 4:
|
|
raise SystemExit("usage: analyze.py LAB RUN_DIRECTORY BUFFER_DIRECTORY")
|
|
lab, run, buffers = map(pathlib.Path, sys.argv[1:])
|
|
lines = (run / "compositor.log").read_text(errors="replace").splitlines()
|
|
out = lab / "results"
|
|
out.mkdir(exist_ok=True)
|
|
|
|
|
|
def records(kind):
|
|
found = []
|
|
for line in lines:
|
|
if kind not in line:
|
|
continue
|
|
tail = line.split(kind, 1)[1]
|
|
values = dict(re.findall(r"([a-z_]+)=([^ ]+)", tail))
|
|
values["kind"] = kind
|
|
found.append(values)
|
|
return found
|
|
|
|
|
|
def save(name, value):
|
|
(out / (name + ".json")).write_text(json.dumps(value, indent=2) + "\n")
|
|
|
|
|
|
observed = records("BI_BUFFER")
|
|
direct = {int(x["id"]): x for x in records("BI_DIRECT")}
|
|
reads = {int(x["id"]): x for x in records("BI_READ")}
|
|
commits = {int(x["id"]): x for x in records("BI_COMMIT")}
|
|
event_buffers = {int(x["id"]): x for x in records("BI_EVENT_BUFFER")}
|
|
locked = {int(x["id"]): x for x in records("BI_LOCKED")}
|
|
held_present = {int(x["id"]): x for x in records("BI_PRESENT_HOLD")}
|
|
released = {int(x["id"]): x for x in records("BI_RELEASE")}
|
|
second_reads = {int(x["id"]): x for x in records("BI_PRESENT_READ")}
|
|
native_commits = records("PI_OUTPUT_COMMIT")
|
|
native_presents = records("PI_OUTPUT_PRESENT")
|
|
stages = records("BI_STAGE")
|
|
|
|
# The synchronous commit callback logs its scene observation immediately
|
|
# after the native event line. Do not attach a later logical IPC state.
|
|
scene = {}
|
|
for i, line in enumerate(lines):
|
|
if "PI_OUTPUT_COMMIT" not in line:
|
|
continue
|
|
v = dict(re.findall(r"([a-z_]+)=([^ ]+)", line.split("PI_OUTPUT_COMMIT", 1)[1]))
|
|
if i + 1 < len(lines) and "PI_SCENE" in lines[i + 1]:
|
|
z = dict(re.findall(r"([a-z_]+)=([^ ]+)", lines[i + 1].split("PI_SCENE", 1)[1]))
|
|
if z.get("output") == v.get("output"):
|
|
scene[(v["output"], v["commit_seq"])] = z
|
|
|
|
presents = {(v["output"], v["commit_seq"]): v for v in native_presents}
|
|
fingerprints = []
|
|
lifetimes = []
|
|
correlations = []
|
|
pixels = []
|
|
scene_states = []
|
|
for entry in observed:
|
|
ident = int(entry["id"])
|
|
commit = commits.get(ident, {})
|
|
key = (commit.get("output"), commit.get("commit_seq"))
|
|
current_scene = scene.get(key, {})
|
|
path = buffers / f"buffer-{ident}.xrgb"
|
|
readable = reads.get(ident, {}).get("status") == "READ" and path.is_file()
|
|
fp = {"id": ident, "read_status": reads.get(ident, {}).get("status", "UNOBSERVED"),
|
|
"direct_available": direct.get(ident, {}).get("available"),
|
|
"source_format": entry.get("dmabuf_format"), "read_format": "DRM_FORMAT_XRGB8888",
|
|
"width": 800, "height": 600, "stride": 3200,
|
|
"state_flags": entry.get("state"), "output_transform": "normal (fixed runner)",
|
|
"source_crop": entry.get("src"), "destination": entry.get("dst")}
|
|
if readable:
|
|
raw = path.read_bytes()
|
|
if len(raw) != 800 * 600 * 4:
|
|
raise ValueError(f"invalid snapshot length for {ident}: {len(raw)}")
|
|
# SceneFX FX glReadPixels rows start at the bottom. DRM XRGB8888 on
|
|
# this little-endian host stores B,G,R,X; normalize top-down RGB.
|
|
rgb = Image.frombytes("RGB", (800, 600), raw, "raw", "BGRX", 0, -1)
|
|
colors = dict((tuple(color), count) for count, color in rgb.getcolors(480001))
|
|
counts = {"A_yellow": colors.get((224, 192, 32), 0),
|
|
"B_green": colors.get((32, 208, 160), 0)}
|
|
fp.update(raw_sha256=hashlib.sha256(raw).hexdigest(),
|
|
rgb_sha256=hashlib.sha256(rgb.tobytes()).hexdigest(),
|
|
byte_count=len(raw), padding_bytes_per_row=0)
|
|
pixels.append({"id": ident, "output": key[0], "commit_seq": key[1],
|
|
"roi_output_pixels": [0, 0, 800, 600],
|
|
"scroll": current_scene.get("scroll"),
|
|
"tile_scene": current_scene.get("tile_scene"),
|
|
"clip": current_scene.get("clip"), "counts": counts})
|
|
later = buffers / f"buffer-{ident}-present.xrgb"
|
|
if later.exists():
|
|
later_raw = later.read_bytes()
|
|
fp["present_read_sha256"] = hashlib.sha256(later_raw).hexdigest()
|
|
fp["present_read_equal"] = later_raw == raw
|
|
fingerprints.append(fp)
|
|
lifetimes.append({"id": ident, "locked": ident in locked,
|
|
"present_while_locked": ident in held_present,
|
|
"release_reason": released.get(ident, {}).get("reason"),
|
|
"lock_age_ns": released.get(ident, {}).get("age_ns")})
|
|
correlations.append({"id": ident, "output": key[0], "commit_seq": key[1],
|
|
"accepted": commit.get("accepted") == "1",
|
|
"same_buffer_in_native_callback": event_buffers.get(ident, {}).get("same") == "1",
|
|
"native_present": key in presents,
|
|
"presented": presents.get(key, {}).get("presented"),
|
|
"read_status": fp["read_status"]})
|
|
scene_states.append({"id": ident, "output": key[0], "commit_seq": key[1], **current_scene})
|
|
|
|
summary = {"observed_buffers": len(observed), "read_buffers": sum(x["read_status"] == "READ" for x in fingerprints),
|
|
"direct_available": sum(x["direct_available"] == "1" for x in fingerprints),
|
|
"native_commits": len(native_commits), "native_presents": len(native_presents),
|
|
"accepted_sampled": sum(x["accepted"] for x in correlations),
|
|
"same_native_state_buffer": sum(x["same_buffer_in_native_callback"] for x in correlations),
|
|
"sampled_with_present": sum(x["native_present"] for x in correlations),
|
|
"sampled_released": sum(x["release_reason"] is not None for x in lifetimes),
|
|
"released_on_present": sum(x["release_reason"] == "present" for x in lifetimes),
|
|
"superseded_before_present": sum(x["release_reason"] == "superseded" for x in lifetimes),
|
|
"present_while_locked": sum(x["present_while_locked"] for x in lifetimes),
|
|
"second_reads": len(second_reads),
|
|
"second_reads_equal": sum(x.get("present_read_equal") is True for x in fingerprints),
|
|
"runner_failure": json.loads((run / "exit-status.json").read_text()).get("failure")}
|
|
|
|
build_ns, post_build_to_commit_ns, present_delay_ns = [], [], []
|
|
per_output = {}
|
|
for stage in stages:
|
|
name, phase = stage.get("output"), stage.get("phase")
|
|
now = int(stage["ns"])
|
|
if phase == "PRE_BUILD":
|
|
per_output[name] = {"pre": now}
|
|
elif phase == "POST_BUILD" and "pre" in per_output.get(name, {}):
|
|
per_output[name]["post"] = now
|
|
build_ns.append(now - per_output[name]["pre"])
|
|
elif phase == "COMMIT_ACCEPTED" and "post" in per_output.get(name, {}):
|
|
post_build_to_commit_ns.append(now - per_output[name]["post"])
|
|
for c in native_commits:
|
|
p = presents.get((c["output"], c["commit_seq"]))
|
|
if p:
|
|
present_delay_ns.append(int(p["ns"]) - int(c["ns"]))
|
|
summary["timing_median_ns"] = {
|
|
"build": int(statistics.median(build_ns)) if build_ns else None,
|
|
"post_build_to_commit": int(statistics.median(post_build_to_commit_ns)) if post_build_to_commit_ns else None,
|
|
"commit_to_present": int(statistics.median(present_delay_ns)) if present_delay_ns else None,
|
|
}
|
|
save("commits", native_commits)
|
|
save("presentations", native_presents)
|
|
save("buffer-lifetime", lifetimes)
|
|
save("buffer-fingerprints", fingerprints)
|
|
save("scene-states", {"stages": stages, "sampled_commits": scene_states})
|
|
save("correlation", correlations)
|
|
save("pixel-analysis", pixels)
|
|
save("matrix", {"fixed_geometry": "HEADLESS-1 (-800,0), HEADLESS-2 (0,0); 800x600; scale=1; normal",
|
|
"run": run.name, "sampled_snapshots": len(fingerprints),
|
|
"pointer_to_commit_identity": "UNOBSERVED", "physical_presentation": "NOT TESTED"})
|
|
save("summary", summary)
|
|
print(json.dumps(summary, indent=2))
|