#!/usr/bin/env python3 """Private current-production joined IPC bench with actual RadarD and bounded RAM capture. Normal current daemon mains; no private CPU/affinity/candidate overlays. """ import argparse import base64 import hashlib import importlib.util import json import os from pathlib import Path import signal import struct import subprocess import sys import time import uuid from bounded_capture_v1 import BoundedCapture HERE = Path(__file__).resolve().parent MAGIC = 0x3143415254425053 BINARY_OBSERVER_MAGIC = b'SPBIOB01' BINARY_OBSERVER_HEADER = struct.Struct('= 0 out.mkdir(mode=0o700, parents=True) begin, end = args.start_mono_ns, args.start_mono_ns + round(args.duration * 1e9) selected, can = [], [] can_sources = {'incoming': 0, 'historicalReturned': 0, 'historicalRejected': 0} for path in args.rlog: for msg in LogReader(str(path.resolve())): service = msg.which() if service not in PEERS | {'can'}: continue mono = int(msg.logMonoTime) - (args.source_boot_minus_mono_ns if service in BOOT_EVENTS else 0) if begin <= mono < end: if service == 'can': frames = [(int(f.address), int(f.src), bytes(f.dat)) for f in msg.can] for _, source, _ in frames: can_sources[can_source_kind(source)] += 1 can.append((mono - begin, frames)) else: selected.append((mono - begin, msg.as_builder().to_bytes())) can.sort(key=lambda row: row[0]) selected.sort(key=lambda row: row[0]) assert can and can[-1][0] - can[0][0] >= round((args.duration - 0.5) * 1e9) with (out / 'can.trace').open('xb') as stream: stream.write(struct.pack(' 0 setattr(body, keys[-1], value + delta) except AttributeError: pass # An absent field is not created by the bench. def reanchor(msg, offset, meta, mono_start, boot_start): service = msg.which() msg.logMonoTime = (boot_start if service in BOOT_EVENTS else mono_start) + offset body = getattr(msg, service) for path in MONO_PATHS.get(service, ()): shift(body, path, mono_start - meta['sourceStartMonoNs']) for path in BOOT_PATHS.get(service, ()): shift(body, path, boot_start - meta['sourceStartMonoNs'] - meta['sourceBootMinusMonoNs']) if service == 'deviceMotion': delta = ( mono_start - meta['sourceStartMonoNs'] if meta['deviceMotionTimestampClock'] == 'monotonic' else boot_start - meta['sourceStartMonoNs'] - meta['sourceBootMinusMonoNs'] ) shift(body, 'timestamp', delta) return msg def read_peers(path, messaging, suppressed): result = [] with path.open('rb') as stream: while header := stream.read(12): assert len(header) == 12 offset, size = struct.unpack('= deadline - 200_000_000: break raise RuntimeError(f'{name} exited early with {child.returncode}') if children['pandad'].poll() is not None: break checkpoint_complete = not args.steady_state_checkpoint or (output / 'checkpoint.json').exists() if not cp_verified and checkpoint_complete and (raw := params.get('CarParams')): with car.CarParams.from_bytes(raw) as actual: assert actual.to_dict() == expected_cp, 'Actual Card CP differs from live pin; refusing mixed configurations' if args.steady_state_checkpoint: assert raw == args.car_params.read_bytes() checkpoint = json.loads((output / 'checkpoint.json').read_text()) assert checkpoint['startupExcluded'] is True and checkpoint['cpSha256'] == args.car_params_sha256 assert checkpoint['routeCpSha256'] == args.route_car_params_sha256 assert checkpoint['carSchemaSha256'] == args.car_schema_sha256 assert checkpoint['paramsRoot'] == str(params_root) and checkpoint['prefix'] == prefix assert checkpoint['restoredFieldsAfter'] == { 'ci_initialized': True, 'ioniq6_long_selected': True, 'ioniq6_long_prearmed': True, 'ioniq6_long_pending': False, } assert checkpoint['restoredPrivateParamsAfter'] == {'FirmwareQueryDone': True, 'ControlsReady': True} if cruise_checkpoint is not None: restored = checkpoint['cruiseMemory'] assert restored['checkpointSha256'] == args.cruise_checkpoint_sha256 assert restored['sourceStartMonoNs'] == meta['sourceStartMonoNs'] assert restored['priorSetSpeedInitializationExcluded'] is True assert restored['restoredFieldsAfter'] == cruise_checkpoint['values'] cp_verified = True if gated_daemons: token = { 'kind': 'private-verified-car-params-main-gate-v1', 'verifiedMonoNs': time.monotonic_ns(), 'prefix': prefix, 'paramsRoot': str(params_root), 'carParamsSha256': args.car_params_sha256, } temporary_gate = daemon_gate.with_suffix('.tmp') assert not temporary_gate.exists() and not daemon_gate.exists() temporary_gate.write_text(json.dumps(token, sort_keys=True) + '\n') temporary_gate.replace(daemon_gate) start('radard', [sys.executable, '-B', str(HERE / 'actor.py'), 'radard'], repo) start('steerlearnerd', [sys.executable, '-B', '-m', 'openpilot.starpilot.flm.steerlearnerd'], repo) start('plannerd', [sys.executable, '-B', str(HERE / 'actor.py'), 'plannerd'], repo) if now >= manager_due: phase_start = time.monotonic_ns() manager = messaging.new_message('managerState', valid=True) manager.managerState.processes = [ {'name': name, 'pid': child.pid, 'running': child.poll() is None, 'shouldBeRunning': True} for name, child in children.items() ] peer_pm.send('managerState', manager) manager_due = now + 500_000_000 for name, child in children.items(): records[name] = child_record(child.pid) usage_samples.append( { 'monoNs': time.monotonic_ns(), 'processes': {name: process_usage(pid) for name, pid in [('collector', os.getpid()), *((name, child.pid) for name, child in children.items())]}, } ) phase_max['processSnapshot'] = max(phase_max['processSnapshot'], time.monotonic_ns() - phase_start) ready = False if time.monotonic_ns() >= proof_due: phase_start = time.monotonic_ns() observer.update(0) now = time.monotonic_ns() axis = current_axis(observer, now_ns=now) native = current_native(observer, pinned_cp, now_ns=now, axis_session_id=str(axis.sessionId) if axis is not None else None) ready = bool(cp_verified and axis is not None and native is not None) ever_ready |= ready active_samples += bool( ready and observer.valid['carControl'] and observer['carControl'].latActive and axis.lateralActive and axis.nativeAcknowledged and native.lateralAllowed ) authority_samples += 1 proof_due = now + proof_interval_ns phase_max['authorityObserver'] = max(phase_max['authorityObserver'], time.monotonic_ns() - phase_start) phase_start = time.monotonic_ns() socket_items = list(sockets.items()) no_data = 0 captured = 0 while captured < 64 and time.monotonic_ns() - phase_start < 500_000: service_index = capture_cursor name, socket = socket_items[service_index] capture_cursor = (capture_cursor + 1) % len(socket_items) raw = socket.receive(non_blocking=True) if raw is None: no_data += 1 if no_data >= len(socket_items): break continue no_data = 0 received = time.monotonic_ns() counters[name] += 1 captured += 1 assert 0 < len(raw) <= 8_000_000 capture.write(BINARY_OBSERVER_HEADER.pack(received, service_index, len(raw))) capture.write(raw) if captured == 64 or time.monotonic_ns() - phase_start >= 500_000: capture_budget_exhaustions += 1 phase_max['rawCapture'] = max(phase_max['rawCapture'], time.monotonic_ns() - phase_start) now = time.monotonic_ns() assert len(rows) < 180000, 'Bounded collector poll records full' rows.append((now, now - last_poll, ready)) last_poll = now time.sleep(0.001) except BaseException as error: failure = f'{type(error).__name__}: {error}' finally: pre_cleanup_exit_codes = {name: child.poll() for name, child in children.items()} near_source_end = 'deadline' in locals() and time.monotonic_ns() >= deadline - 200_000_000 unexpected = { name: code for name, code in pre_cleanup_exit_codes.items() if code is not None and not (name in ('pandad', 'peerfeeder', 'reserve') and code == 0 and near_source_end) } if unexpected: failure = failure or 'Production child exited before cleanup: ' + repr(unexpected) # Let a normally completed source feeder export its bounded receipt before # sending cleanup TERM. A failed/early source run still takes normal cleanup. if 'deadline' in locals() and time.monotonic_ns() >= deadline and 'peerfeeder' in children: try: children['peerfeeder'].wait(timeout=1) except subprocess.TimeoutExpired: pass for number in (signal.SIGTERM, signal.SIGHUP, signal.SIGINT): signal.signal(number, signal.SIG_IGN) for child in children.values(): if child.poll() is None: try: os.killpg(child.pid, signal.SIGTERM) except ProcessLookupError: pass for child in children.values(): try: child.wait(timeout=3) except subprocess.TimeoutExpired: try: os.killpg(child.pid, signal.SIGKILL) except ProcessLookupError: pass child.wait(timeout=3) for stream in logs: stream.close() # Producers are reaped before any raw observation file write. if (output / 'peer-feeder.json').exists(): feeder_receipt = json.loads((output / 'peer-feeder.json').read_text()) if feeder_receipt['failure'] is not None or feeder_receipt['published'] != feeder_receipt['events']: failure = failure or 'Independent peer feeder did not publish exact complete source window' peer_max_feed_late_ns = feeder_receipt['maximumLatenessNs'] else: failure = failure or 'Missing independent peer feeder receipt' capture_export = capture_memory.export() if capture_path.exists() and args.observer_format == 'binary': (output / 'observations-format.json').write_text( json.dumps( { 'format': 'SPBIOB01', 'header': '