web/room/binding.js: the side table a page carries contentBinding in until a registry lists the member (decoder and timestamp, clone, transfer, reset, a bound; ambiguity refused), the capability report built in two passes so that only codec strings the WebCodecs Codec Registry lists are put to the user agent, and the state of the frame on screen. The room routes each track before it decodes it, binds every frame its video decoders output to the verified object and group receipt, and shows the state of each tile's frame with ?debug=binding. web/binding/ runs live-conformance/browser in the page, the wasm route's verified decode, the binding through clone, transfer, copyTo and re-encode, the metadata probe and a capability report from the user agent's answers. e2e: scenario binding, energy.py for the host's per-process counter, and the decode wasm built by run.sh. Co-Authored-By: Claude Opus 5.5 <[email protected]>
69 lines
2.7 KiB
Python
69 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""The CPU energy a browser's process tree has spent so far, for the end-to-end scenario
|
|
`binding` (WAI browser-sink §6): its figure beside the user agent's powerEfficient.
|
|
|
|
python3 -I energy.py <run-id>
|
|
|
|
Prints one JSON line: the processes counted, and the sum over them of the kernel's
|
|
per-process energy counter (`proc_pid_rusage`, RUSAGE_INFO_V6: `ri_energy_nj`), plus the
|
|
energy services were billed for work done for them (`ri_billed_energy`), less what they
|
|
did for others (`ri_serviced_energy`), all in nanojoules. The tree is every process whose
|
|
command line carries `moq-web-run=<run-id>` (the browser the test launched) and every
|
|
descendant of one. The counter covers CPU energy only: GPU and media-engine energy are not
|
|
in it, so a figure built from it covers part of a decode (`Partial`, fabrics `cpu`). A
|
|
counter is a reading, not an estimate (energy-measurement §2: `OnChipCounter`).
|
|
|
|
Exits 2 where the counter does not exist (a host other than macOS); the scenario then
|
|
reports the step as skipped. Standard library only.
|
|
"""
|
|
import ctypes
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
|
|
RUSAGE_INFO_V6 = 6
|
|
SIZE = 392 + 9 * 8 # struct rusage_info_v6
|
|
OFF_ENERGY = 336 # ri_energy_nj
|
|
OFF_BILLED = 264 # ri_billed_energy
|
|
OFF_SERVICED = 272 # ri_serviced_energy
|
|
|
|
|
|
def tree(run_id):
|
|
ps = subprocess.run(["ps", "-A", "-o", "pid=,ppid=,command="], capture_output=True, text=True, check=True).stdout
|
|
rows = []
|
|
for line in ps.splitlines():
|
|
parts = line.strip().split(None, 2)
|
|
if len(parts) >= 2:
|
|
rows.append((int(parts[0]), int(parts[1]), parts[2] if len(parts) > 2 else ""))
|
|
roots = {p for p, _, c in rows if "moq-web-run=" + run_id in c}
|
|
found = set(roots)
|
|
grew = True
|
|
while grew:
|
|
grew = False
|
|
for p, pp, _ in rows:
|
|
if pp in found and p not in found:
|
|
found.add(p)
|
|
grew = True
|
|
return sorted(found)
|
|
|
|
|
|
def main():
|
|
if sys.platform != "darwin" or len(sys.argv) != 2:
|
|
print(json.dumps({"error": "no per-process energy counter on this host"}))
|
|
return 2
|
|
libc = ctypes.CDLL("/usr/lib/libSystem.B.dylib", use_errno=True)
|
|
buf = ctypes.create_string_buffer(SIZE)
|
|
total, counted = 0, 0
|
|
for pid in tree(sys.argv[1]):
|
|
if libc.proc_pid_rusage(ctypes.c_int(pid), ctypes.c_int(RUSAGE_INFO_V6), buf) != 0:
|
|
continue # gone, or not ours
|
|
raw = buf.raw
|
|
get = lambda off: int.from_bytes(raw[off:off + 8], "little")
|
|
total += get(OFF_ENERGY) + get(OFF_BILLED) - get(OFF_SERVICED)
|
|
counted += 1
|
|
print(json.dumps({"processes": counted, "nj": total}))
|
|
return 0 if counted else 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|