初始化奇妙小屏幕控制器项目
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import statistics
|
||||
import time
|
||||
import urllib.request
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class MeasurementError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def fetch(url: str, *, timeout: float = 2.0) -> tuple[bytes, float]:
|
||||
started = time.monotonic()
|
||||
with urllib.request.urlopen(url, timeout=timeout) as response:
|
||||
body = response.read()
|
||||
return body, (time.monotonic() - started) * 1000.0
|
||||
|
||||
|
||||
def rgb_digest(body: bytes) -> str:
|
||||
with Image.open(BytesIO(body)) as image:
|
||||
rgb = image.convert("RGB")
|
||||
return hashlib.sha256(rgb.tobytes()).hexdigest()
|
||||
|
||||
|
||||
def percentile_95(values: list[float]) -> float:
|
||||
if not values:
|
||||
return 0.0
|
||||
ordered = sorted(values)
|
||||
return ordered[max(0, min(len(ordered) - 1, int(len(ordered) * 0.95 + 0.999999) - 1))]
|
||||
|
||||
|
||||
def driver_status(sample: dict[str, Any]) -> dict[str, Any]:
|
||||
driver = (sample.get("screen") or {}).get("driver_status")
|
||||
if not isinstance(driver, dict):
|
||||
raise MeasurementError("status does not contain screen.driver_status")
|
||||
return driver
|
||||
|
||||
|
||||
def validate_samples(
|
||||
samples: list[dict[str, Any]],
|
||||
*,
|
||||
performance: str,
|
||||
frame_before: str,
|
||||
frame_after: str,
|
||||
) -> dict[str, Any]:
|
||||
if len(samples) < 2:
|
||||
raise MeasurementError("at least two status samples are required")
|
||||
drivers = [driver_status(sample) for sample in samples]
|
||||
minimum_actual = min(float(item.get("actual_refresh_rate_hz") or 0) for item in drivers)
|
||||
minimum_scan = min(float(item.get("panel_scan_rate_hz") or 0) for item in drivers)
|
||||
if minimum_actual < 95.0 or minimum_scan < 95.0:
|
||||
raise MeasurementError("actual or panel scan refresh rate fell below 95Hz")
|
||||
if any(item.get("scans_per_frame") != 1 for item in drivers):
|
||||
raise MeasurementError("scans_per_frame was not 1 throughout the sample")
|
||||
|
||||
stalled_intervals = 0
|
||||
maximum_consecutive_stalls = 0
|
||||
for previous, current in zip(drivers, drivers[1:]):
|
||||
frame_delta = int(current.get("completed_frames") or 0) - int(previous.get("completed_frames") or 0)
|
||||
scan_delta = int(current.get("completed_scans") or 0) - int(previous.get("completed_scans") or 0)
|
||||
if frame_delta < 0 or scan_delta != frame_delta:
|
||||
raise MeasurementError("completed frame and scan deltas were negative or unequal")
|
||||
stalled_intervals = stalled_intervals + 1 if frame_delta == 0 else 0
|
||||
maximum_consecutive_stalls = max(maximum_consecutive_stalls, stalled_intervals)
|
||||
if maximum_consecutive_stalls >= 2:
|
||||
raise MeasurementError("completed frames stalled for two consecutive samples")
|
||||
|
||||
frame_delta = int(drivers[-1].get("completed_frames") or 0) - int(drivers[0].get("completed_frames") or 0)
|
||||
scan_delta = int(drivers[-1].get("completed_scans") or 0) - int(drivers[0].get("completed_scans") or 0)
|
||||
miss_delta = int(drivers[-1].get("deadline_misses") or 0) - int(drivers[0].get("deadline_misses") or 0)
|
||||
if frame_delta <= 0 or scan_delta != frame_delta:
|
||||
raise MeasurementError("completed frame/scan totals did not advance equally")
|
||||
if miss_delta < 0 or miss_delta / frame_delta > 0.001:
|
||||
raise MeasurementError("deadline miss rate exceeded 0.1%")
|
||||
|
||||
fault_fields = sorted(
|
||||
key for key, value in drivers[0].items()
|
||||
if "fault" in key and isinstance(value, (int, float)) and not isinstance(value, bool)
|
||||
)
|
||||
for field in fault_fields:
|
||||
if any(float(current.get(field) or 0) != float(drivers[0].get(field) or 0) for current in drivers[1:]):
|
||||
raise MeasurementError(f"{field} changed during the measurement")
|
||||
for field in ("driver_error", "oe_timing_error", "safeoff_error"):
|
||||
if any(item.get(field) for item in drivers):
|
||||
raise MeasurementError(f"{field} was not empty")
|
||||
for item in drivers:
|
||||
if item.get("cpu_affinity") != 3 or item.get("cpu_affinity_active") is not True:
|
||||
raise MeasurementError("CPU3 affinity was not continuously active")
|
||||
if item.get("realtime_priority") != 50 or item.get("realtime_priority_active") is not True:
|
||||
raise MeasurementError("SCHED_FIFO 50 was not continuously active")
|
||||
if item.get("memory_locked") is not True:
|
||||
raise MeasurementError("refresh memory was not continuously locked")
|
||||
|
||||
for sample in samples:
|
||||
status = ((sample.get("system") or {}).get("performance_mode") or {})
|
||||
if performance == "on":
|
||||
if status.get("requested") is not True or status.get("effective") is not True:
|
||||
raise MeasurementError("performance mode was not requested and effective")
|
||||
if not status.get("current_governors") or any(
|
||||
governor != "performance" for governor in status["current_governors"].values()
|
||||
):
|
||||
raise MeasurementError("not every cpufreq policy used the performance governor")
|
||||
elif performance == "off":
|
||||
if status.get("effective") is not False:
|
||||
raise MeasurementError("performance mode remained effective while required off")
|
||||
current = status.get("current_governors") or {}
|
||||
restore = status.get("restore_governors") or {}
|
||||
if current and current != restore:
|
||||
raise MeasurementError("governors were not restored while performance mode was off")
|
||||
|
||||
if frame_before != frame_after:
|
||||
raise MeasurementError("current RGB frame changed during thumbnail browsing")
|
||||
return {
|
||||
"ok": True,
|
||||
"samples": len(samples),
|
||||
"minimum_actual_refresh_rate_hz": minimum_actual,
|
||||
"minimum_panel_scan_rate_hz": minimum_scan,
|
||||
"completed_frames_delta": frame_delta,
|
||||
"completed_scans_delta": scan_delta,
|
||||
"deadline_misses_delta": miss_delta,
|
||||
"deadline_miss_rate": miss_delta / frame_delta,
|
||||
"maximum_consecutive_stalled_intervals": maximum_consecutive_stalls,
|
||||
"fault_fields_checked": fault_fields,
|
||||
"current_frame_rgb_sha256": frame_before,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Measure real HUB75 GPIO refresh while browsing")
|
||||
parser.add_argument("--base-url", default="http://127.0.0.1:8080")
|
||||
parser.add_argument("--seconds", type=int, default=60)
|
||||
parser.add_argument("--performance", choices=("on", "off", "ignore"), default="ignore")
|
||||
parser.add_argument("--output", type=Path)
|
||||
args = parser.parse_args()
|
||||
if not 2 <= args.seconds <= 600:
|
||||
parser.error("--seconds must be within 2..600")
|
||||
|
||||
frame_body, _ = fetch(f"{args.base_url}/api/display/current-frame")
|
||||
frame_before = rgb_digest(frame_body)
|
||||
samples: list[dict[str, Any]] = []
|
||||
latencies: list[float] = []
|
||||
started = time.monotonic()
|
||||
for second in range(args.seconds + 1):
|
||||
target = started + second
|
||||
if target > time.monotonic():
|
||||
time.sleep(target - time.monotonic())
|
||||
body, latency = fetch(f"{args.base_url}/api/status")
|
||||
samples.append(json.loads(body.decode("utf-8")))
|
||||
latencies.append(latency)
|
||||
frame_body, _ = fetch(f"{args.base_url}/api/display/current-frame")
|
||||
result = validate_samples(
|
||||
samples,
|
||||
performance=args.performance,
|
||||
frame_before=frame_before,
|
||||
frame_after=rgb_digest(frame_body),
|
||||
)
|
||||
result["status_latency_p95_ms"] = percentile_95(latencies)
|
||||
if result["status_latency_p95_ms"] > 500.0:
|
||||
raise MeasurementError("/api/status p95 latency exceeded 500ms")
|
||||
encoded = json.dumps(result, ensure_ascii=False, sort_keys=True, indent=2) + "\n"
|
||||
if args.output:
|
||||
args.output.write_text(encoded, encoding="utf-8", newline="\n")
|
||||
print(encoded, end="")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except MeasurementError as exc:
|
||||
print(json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False))
|
||||
raise SystemExit(1)
|
||||
Reference in New Issue
Block a user