Files
jellyfin-plugin-enigma2/experiments/run-continuity-e2e.py

73 lines
3.5 KiB
Python

"""Run the isolated synthetic C# harness and measure its own source FFmpeg only.
No Jellyfin configuration changes, restarts, API keys or receiver connections.
"""
import argparse
import json
import os
from pathlib import Path
import signal
import subprocess
import time
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--harness', required=True)
parser.add_argument('--ffmpeg', default='/usr/lib/jellyfin-ffmpeg/ffmpeg')
parser.add_argument('--output', required=True)
parser.add_argument('--long', action='store_true')
parser.add_argument('--native', action='store_true')
args = parser.parse_args()
root = Path(args.output).resolve()
if root.exists():
raise ValueError('Choose a new output directory')
samples, previous = [], {}
with Path(str(root) + '.log').open('x') as log:
process = subprocess.Popen([args.harness, '--native-continuity-e2e' if args.native else '--long-continuity-e2e' if args.long else '--continuity-e2e', args.ffmpeg, str(root)], stdout=log,
stderr=subprocess.STDOUT, start_new_session=True)
deadline = time.monotonic() + 240
try:
while process.poll() is None:
if time.monotonic() > deadline:
raise TimeoutError('Synthetic harness deadline exceeded')
now = time.monotonic()
for proc in Path('/proc').glob('[0-9]*'):
try:
if not str((proc / 'cwd').resolve()).startswith(str(root / 'cache' / 'source-')):
continue
value = (proc / 'stat').read_text()
fields = value[value.rfind(')') + 2:].split()
cpu = (int(fields[11]) + int(fields[12])) / os.sysconf('SC_CLK_TCK')
rss = int(fields[21]) * os.sysconf('SC_PAGE_SIZE') / 1048576
if proc.name in previous:
before, at = previous[proc.name]
samples.append({'one_core_percent': round((cpu-before)/(now-at)*100, 1),
'rss_mib': round(rss, 1)})
previous[proc.name] = (cpu, now)
except OSError:
pass
time.sleep(1)
finally:
if process.poll() is None:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait()
report = {'harness_exit_code': process.returncode, 'samples': samples,
'scope': 'Only synthetic plugin source encoding; excludes fixture and player decoders'}
(root / 'cpu-sample.json').write_text(json.dumps(report, indent=2))
if process.returncode == 0 and (args.long or args.native):
subprocess.run(['python3', str(Path(__file__).with_name('verify-continuity-recording.py')), '--output', str(root), '--ffmpeg', args.ffmpeg], check=True)
print(Path(str(root) + '.log').read_text()[-3500:])
print(json.dumps({'exit_code': process.returncode, 'cpu_samples': len(samples),
'maximum_one_core_percent': max((s['one_core_percent'] for s in samples), default=None),
'maximum_rss_mib': max((s['rss_mib'] for s in samples), default=None)}))
raise SystemExit(0 if process.returncode == 0 else 1)
if __name__ == '__main__':
main()