Files
StarPilot/tools/heatdeath/driver_aa_v47_fixture.py
T
2026-10-26 18:28:20 -05:00

1093 lines
48 KiB
Python

#!/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('<QHI') # receive MONO ns, service index, raw Event byte count
CAN_RETURNED_BUS_OFFSET = 0x80 # production panda.h / unpack_can_buffer
CAN_REJECTED_BUS_OFFSET = 0xC0
RADAR_OUTPUTS = {'radarState', 'starpilotRadarState'}
PLANNER_OUTPUTS = {'longitudinalPlan', 'driverAssistance', 'starpilotLongitudinalPlan', 'slcState', 'slcCruiseCommand'}
# These are declared inputs, never enabled or rewritten by this driver. Missing
# snapshot keys use production defaults; they do not establish device absence.
PLANNER_PREF_KEYS = (
tuple(
'''UseStarPilotLongitudinalPlanner ConditionalModeConfig SafeMode ExperimentalMode
CurveSpeedController CurveSpeedControllerNoLead CurveComfortData CurvatureData
CustomPersonalities LongitudinalPersonalityProfiles LongitudinalPersonality
LeadApproachBuffer FasterLeadTakeoff ForceStops ForceStopDistanceOffset
LaneChangePreferences PulseGlideSpeedDelta
SpeedLimitController ShowSpeedLimits IsMetric SLCPriority1 SLCPriority2 SLCFallback
SLCOverride SLCConfirmation SLCConfirmationHigher SLCConfirmationLower SLCOffsetSchedule
TrafficFollow TrafficJerkAcceleration TrafficJerkDeceleration TrafficJerkSpeed
TrafficJerkSpeedDecrease TrafficJerkDanger'''.split()
)
+ tuple(
personality + setting
for personality in ('Aggressive', 'Standard', 'Relaxed')
for setting in ('Follow', 'FollowHigh', 'JerkAcceleration', 'JerkDeceleration', 'JerkSpeed', 'JerkSpeedDecrease', 'JerkDanger')
)
+ tuple('Offset' + str(i) for i in range(1, 8))
)
OWNED = (
{
'can',
'pandaStates',
'peripheralState',
'aolSafetyWire',
'sendcan',
'carState',
'carParams',
'carOutput',
'radarTracks',
'starpilotCarState',
'slcCruiseEvent',
'slcDashboardObservation',
'aolIntentWire',
'selfdriveState',
'onroadEvents',
'aolAxisState',
'starpilotSelfdriveState',
'carControl',
'controlsState',
'starpilotLateralState',
'autoSteeringLearning',
}
| PLANNER_OUTPUTS
| RADAR_OUTPUTS
)
PEERS = {
'deviceState',
'modelV2',
'vehicleParameters',
'lateralDelay',
'lateralTorqueParameters',
'extrinsicsCalibration',
'driverMonitoringState',
'deviceMotion',
'narrowRoadCameraState',
'wideRoadCameraState',
'cabinCameraState',
'accelerometer',
'gyroscope',
'gpsLocation',
'gpsLocationExternal',
}
BOOT_EVENTS = {'can', 'narrowRoadCameraState', 'wideRoadCameraState', 'cabinCameraState'}
# Explicit reviewed clock paths. Values are shifted by a constant, preserving
# their original age and duration; zero/absent fields remain zero/absent.
MONO_PATHS = {
'radarState': ('mdMonoTime',),
'modelV2': ('meta.locationMonoTime',),
'deviceState': ('startedMonoTime',),
'accelerometer': ('timestamp',),
'gyroscope': ('timestamp',),
}
BOOT_PATHS = {
'modelV2': ('timestampEof',),
'narrowRoadCameraState': ('timestampSof', 'timestampEof'),
'wideRoadCameraState': ('timestampSof', 'timestampEof'),
'cabinCameraState': ('timestampSof', 'timestampEof'),
}
FORBIDDEN = (
'REPLAY',
'SIMULATION',
'CEREAL_FAKE',
'FAKESEND',
'STARTED',
'BOARDD_LOOPBACK',
'BOARDD_SKIP_FW_CHECK',
'PANDAD_MAXOUT',
'ZMQ',
'FINGERPRINT',
'SKIP_FW_QUERY',
'SLC_REPLAY_RUNTIME',
'CONDITIONAL_MODE_REPLAY_RUNTIME',
'CURVE_REPLAY_RUNTIME',
'LONG_PLANNER_REPLAY_RUNTIME',
'SLC_VISION_DEVELOPMENT',
'STARPILOT_PANDA_FIRMWARE_MAP',
'STARPILOT_PANDA_FIRMWARE_NAME',
)
def digest(path):
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
def can_source_kind(source):
if 0 <= source < 4:
return 'incoming'
if CAN_RETURNED_BUS_OFFSET <= source < CAN_RETURNED_BUS_OFFSET + 4:
return 'historicalReturned'
if CAN_REJECTED_BUS_OFFSET <= source < CAN_REJECTED_BUS_OFFSET + 4:
return 'historicalRejected'
raise AssertionError(f'Unknown CP1 CAN source {source}; additional Panda buses/remapping forbidden')
def configure_imports(repo):
repo = Path(repo).resolve()
assert (repo / 'openpilot/cereal/log.capnp').is_file()
sys.path[:0] = [str(repo / p) for p in ('', 'opendbc_repo', 'msgq_repo', 'rednose_repo', 'tinygrad_repo')]
return repo
def prepare(args):
repo = configure_imports(args.repo)
from openpilot.tools.lib.logreader import LogReader
from openpilot.cereal import messaging
out = args.output.resolve()
assert not out.exists() and not out.is_relative_to(repo)
assert 1 <= args.duration <= 120 and args.source_boot_minus_mono_ns >= 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('<QQI', MAGIC, end - begin, len(can)))
for offset, frames in can:
stream.write(struct.pack('<QI', offset, len(frames)))
for address, bus, data in frames:
stream.write(struct.pack('<IBB', address, bus, len(data)) + data)
with (out / 'peers.trace').open('xb') as stream:
for offset, raw in selected:
stream.write(struct.pack('<QI', offset, len(raw)) + raw)
metadata = {
'kind': 'private-realtime-inputs',
'sourceStartMonoNs': begin,
'durationNs': end - begin,
'sourceBootMinusMonoNs': args.source_boot_minus_mono_ns,
'deviceMotionTimestampClock': args.device_motion_clock,
'rlogs': [{'path': str(p.resolve()), 'sha256': digest(p)} for p in args.rlog],
'canBatches': len(can),
'peerEvents': len(selected),
'canSourceFrames': can_sources,
'canSourceContract': {
'incomingPhysicalBuses': [0, 1, 2, 3],
'returnedOffset': CAN_RETURNED_BUS_OFFSET,
'rejectedOffset': CAN_REJECTED_BUS_OFFSET,
'historicalFlagsPreserved': True,
'historicalFlagsFeedNativeRx': False,
},
'services': sorted({messaging.log_from_bytes(raw).which() for _, raw in selected}),
'clockPaths': {'monotonic': MONO_PATHS, 'boottime': BOOT_PATHS},
'limitations': [
'Recorded peer payloads are workload, not live sensors or present thermal telemetry.',
'Actual Card startup/ECU handoff must pass without fabricated replies.',
'This CP1 endpoint does not implement firmware heartbeat timeout-loop equivalence.',
],
}
metadata['files'] = {p.name: digest(p) for p in (out / 'can.trace', out / 'peers.trace')}
(out / 'inputs.json').write_text(json.dumps(metadata, indent=2) + '\n')
def shift(body, path, delta):
try:
keys = path.split('.')
for key in keys[:-1]:
body = getattr(body, key)
value = int(getattr(body, keys[-1]))
if value:
assert value + delta > 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('<QI', header)
assert 0 < size <= 4_000_000
raw = stream.read(size)
assert len(raw) == size
message = messaging.log_from_bytes(raw)
# A previously prepared v4 packet may contain these old recorded peers.
# Never publish them alongside the actual selected planner.
if message.which() in PLANNER_OUTPUTS | RADAR_OUTPUTS:
name = message.which()
suppressed[name] = suppressed.get(name, 0) + 1
continue
assert message.which() in PEERS and message.which() not in OWNED
result.append((offset, message))
return result
def child_record(pid):
task = Path(f'/proc/{pid}')
return {
'pid': pid,
'cmdline': (task / 'cmdline').read_bytes().replace(b'\0', b' ').decode(errors='replace'),
'exe': os.readlink(task / 'exe'),
'schedPolicy': os.sched_getscheduler(pid),
'priority': os.sched_getparam(pid).sched_priority,
'affinity': sorted(os.sched_getaffinity(pid)),
'startTicks': (task / 'stat').read_text().rsplit(')', 1)[1].split()[19],
}
def process_usage(pid):
task = Path(f'/proc/{pid}')
fields = (task / 'stat').read_text().rsplit(')', 1)[1].split()
sched = [int(value) for value in (task / 'schedstat').read_text().split()]
return {
'pid': pid,
'userTicks': int(fields[11]),
'systemTicks': int(fields[12]),
'startTicks': int(fields[19]),
'mainThreadRunNs': sched[0],
'mainThreadWaitNs': sched[1],
'mainThreadTimeslices': sched[2],
}
def private_native_core2(child, endpoint):
"""Observe real production setup, then move only this owned virtual child.
Main is pinned first, so subsequently created threads inherit core2. Every
already-existing thread is then pinned, followed by one extra enumeration.
No policy/priority or other process affinity is changed.
"""
deadline = time.monotonic_ns() + 3_000_000_000
expected_exe = str(endpoint.resolve())
while True:
assert child.poll() is None, 'Owned virtual native child exited before scheduler setup'
record = child_record(child.pid)
assert record['exe'] == expected_exe, 'Owned virtual endpoint executable changed'
if record['schedPolicy'] == os.SCHED_FIFO and record['priority'] == 54 and record['affinity'] == [3]:
break
assert time.monotonic_ns() < deadline, 'No actual production FIFO54/core3 setup within3s'
time.sleep(0.005)
pid, start_ticks = child.pid, record['startTicks']
def tasks():
result = {}
for task in sorted((Path(f'/proc/{pid}') / 'task').iterdir(), key=lambda p: int(p.name)):
tid = int(task.name)
fields = (task / 'stat').read_text().rsplit(')', 1)[1].split()
result[tid] = {
'tid': tid,
'startTicks': fields[19],
'policy': os.sched_getscheduler(tid),
'priority': os.sched_getparam(tid).sched_priority,
'affinity': sorted(os.sched_getaffinity(tid)),
}
return result
before = tasks()
assert pid in before and before[pid]['startTicks'] == start_ticks
assert time.monotonic_ns() < deadline
os.sched_setaffinity(pid, {2})
current = tasks()
for tid, task in current.items():
assert time.monotonic_ns() < deadline
assert child.poll() is None and child_record(pid)['startTicks'] == start_ticks
current_start = (Path(f'/proc/{pid}/task/{tid}') / 'stat').read_text().rsplit(')', 1)[1].split()[19]
assert current_start == task['startTicks'], 'Owned virtual thread exited/reused before pin'
if tid in before:
assert task['startTicks'] == before[tid]['startTicks'], 'Virtual child thread identity changed'
os.sched_setaffinity(tid, {2})
after = tasks()
assert after and all(task['affinity'] == [2] for task in after.values())
# Extra enumeration after yielding lets a concurrently created send thread
# appear; it must inherit core2 from main rather than requiring a new pin.
time.sleep(0.005)
verified = tasks()
assert time.monotonic_ns() < deadline
assert child.poll() is None and child_record(pid)['startTicks'] == start_ticks
assert verified and all(task['affinity'] == [2] for task in verified.values())
for tid, task in verified.items():
previous = after.get(tid, current.get(tid, before.get(tid)))
if previous:
assert task['startTicks'] == previous['startTicks'], 'Virtual child TID reused'
assert task['policy'] == previous['policy'] and task['priority'] == previous['priority']
assert verified[pid]['policy'] == os.SCHED_FIFO and verified[pid]['priority'] == 54
return {
'enabled': True,
'pid': pid,
'processStartTicks': start_ticks,
'observedProductionSetup': record,
'threadsBefore': list(before.values()),
'threadsAfterPin': list(after.values()),
'threadsAfterExtraEnumeration': list(verified.values()),
'newThreadsAfterPin': sorted(set(verified) - set(after)),
'policyAndPriorityChanged': False,
'onlyOwnedVirtualEndpoint': True,
'limitation': 'Virtual native workload moved to core2; production pandad normally uses core3. Real daemons untouched.',
}
def decode_observations(args):
repo = configure_imports(args.repo)
from openpilot.cereal import messaging
source, output = args.capture.resolve(), args.output.resolve()
assert not output.exists() and source != output and not output.is_relative_to(repo)
metadata = json.loads(args.metadata.read_text())
assert metadata['format'] == 'SPBIOB01' and digest(source) == metadata['sha256']
names, counts = metadata['services'], dict.fromkeys(metadata['services'], 0)
with source.open('rb') as capture, output.open('x') as decoded:
assert capture.read(len(BINARY_OBSERVER_MAGIC)) == BINARY_OBSERVER_MAGIC
while header := capture.read(BINARY_OBSERVER_HEADER.size):
assert len(header) == BINARY_OBSERVER_HEADER.size
received, service_index, size = BINARY_OBSERVER_HEADER.unpack(header)
assert service_index < len(names) and 0 < size <= 8_000_000
raw = capture.read(size)
assert len(raw) == size
event = messaging.log_from_bytes(raw)
name = names[service_index]
assert event.which() == name
decoded.write(
json.dumps(
{'receivedMonoNs': received, 'service': name, 'event': event.to_dict()},
separators=(',', ':'),
default=lambda value: (
{'base64': base64.b64encode(value).decode()} if isinstance(value, bytes) else (_ for _ in ()).throw(TypeError(type(value).__name__))
),
)
+ '\n'
)
counts[name] += 1
assert counts == metadata['messages'], 'Incomplete raw observation capture'
print(json.dumps({'capture': str(source), 'decoded': str(output), 'messages': counts, 'sourceSha256': metadata['sha256']}))
def run(args):
repo = configure_imports(args.repo)
helpers = args.helpers_dir.resolve() if args.helpers_dir is not None else HERE
assert helpers.is_dir()
assert 1 <= args.timing_limit <= 16000
def interrupted(signum, _frame):
raise SystemExit('private driver signal ' + str(signum))
for signum in (signal.SIGTERM, signal.SIGHUP, signal.SIGINT):
signal.signal(signum, interrupted)
assert sys.dont_write_bytecode and sys.platform == 'linux'
assert all(name not in os.environ for name in FORBIDDEN)
assert os.environ.get('AOL_REPLAY_RUNTIME', '0') == '0'
assert args.root_grant == 'execute-private-native-ipc-bench'
source_pins = json.loads((HERE / 'joined-source-pins.json').read_text())
assert (
subprocess.check_output(['git', '-c', 'safe.directory=' + str(repo), '-C', str(repo), 'rev-parse', 'HEAD'], text=True).strip() == source_pins['sourceHead']
)
assert subprocess.check_output(['git', '-c', 'safe.directory=' + str(repo), '-C', str(repo), 'status', '--porcelain']) == b''
assert all(digest(repo / name) == wanted for name, wanted in source_pins['sources'].items())
assert all(digest(HERE / name) == wanted for name, wanted in source_pins['helpers'].items())
assert args.observer_format == 'binary', 'Bounded RAM collector supports raw binary only'
from openpilot.cereal import messaging
from openpilot.common.params import Params
from openpilot.starpilot.schema_cache import put_cache
from opendbc.car.structs import car
from openpilot.starpilot.aol.runtime import current_native, current_axis
packet = args.inputs.resolve()
meta = json.loads((packet / 'inputs.json').read_text())
for name, expected in meta['files'].items():
assert digest(packet / name) == expected
assert digest(args.native_library) == args.native_library_sha256
assert digest(args.native_endpoint) == args.native_endpoint_sha256
assert digest(args.car_params) == args.car_params_sha256
assert (args.cruise_checkpoint is None) == (args.cruise_checkpoint_sha256 is None)
assert args.steady_state_checkpoint and args.cruise_checkpoint is not None, 'Joined packet requires explicit genuine cruise phase checkpoint'
cruise_checkpoint = None
if args.cruise_checkpoint is not None:
assert args.steady_state_checkpoint, 'Cruise memory requires explicit already-running checkpoint mode'
assert digest(args.cruise_checkpoint) == args.cruise_checkpoint_sha256
cruise_checkpoint = json.loads(args.cruise_checkpoint.read_text())
assert cruise_checkpoint['sourceStartMonoNs'] == meta['sourceStartMonoNs']
assert cruise_checkpoint['cpSha256'] == args.car_params_sha256
assert cruise_checkpoint['carSchemaSha256'] == args.car_schema_sha256
assert any(
item['path'] == str(Path(cruise_checkpoint['sourceRlog']).resolve()) and item['sha256'] == cruise_checkpoint['sourceRlogSha256'] for item in meta['rlogs']
)
if args.steady_state_checkpoint:
assert args.route_car_params is not None and args.route_car_params_sha256 and args.car_schema_sha256
assert digest(args.route_car_params) == args.route_car_params_sha256
assert args.car_params.read_bytes() == args.route_car_params.read_bytes()
assert digest(repo / 'opendbc_repo/opendbc/car/car.capnp') == args.car_schema_sha256
with car.CarParams.from_bytes(args.car_params.read_bytes()) as cp:
expected_cp = cp.to_dict()
assert cp.carFingerprint == 'HYUNDAI_IONIQ_6' and cp.brand == 'hyundai' and len(cp.safetyConfigs) == 1
assert not cp.passive and not cp.dashcamOnly and not cp.notCar
pinned_cp = cp.as_builder()
output = args.output.resolve()
assert not output.exists() and not output.is_relative_to(repo)
output.mkdir(mode=0o700, parents=True)
prefix = 'replay-bench-' + uuid.uuid4().hex[:12]
shm = Path('/dev/shm') / ('msgq_' + prefix)
shm.mkdir(mode=0o700)
from owned_msgq_cleanup_v2 import journal_created
journal_created(shm, prefix, output / 'shm-ownership.json')
params_root = output / 'params'
params_root.mkdir(mode=0o700)
env = dict(
os.environ,
OPENPILOT_PREFIX=prefix,
PARAMS_ROOT=str(params_root),
LOG_ROOT=str(output / 'logs'),
COMMA_CACHE=str(output / 'cache'),
SP_PRIVATE_NATIVE_BENCH='1',
AOL_REPLAY_RUNTIME='0',
PYTHONDONTWRITEBYTECODE='1',
PYTHONPATH=os.pathsep.join([str(HERE), *(str(repo / p) for p in ('', 'opendbc_repo', 'msgq_repo', 'rednose_repo', 'tinygrad_repo'))]),
)
if env.get('SP_PERF_CAN_VARIANT') in ('baseline', 'candidate', 'feedback', 'pid', 'optimized', 'digest', 'geometry'):
env['PYTHONPATH'] = str(HERE) + os.pathsep + env['PYTHONPATH']
if args.steady_state_checkpoint:
env['SP_STEADY_STATE_CHECKPOINT'] = '1'
else:
env.pop('SP_STEADY_STATE_CHECKPOINT', None)
os.environ.update({key: env[key] for key in ('OPENPILOT_PREFIX', 'PARAMS_ROOT', 'LOG_ROOT', 'COMMA_CACHE')})
# Source/admission configuration comes from the explicit root-provided live
# snapshot. It never sets readiness or writes a stale native receipt.
params = Params()
forbidden_keys = {
'CarParams',
'CarParamsCache',
'CarParamsPersistent',
'CarParamsPrevRoute',
'ControlsReady',
'FirmwareQueryDone',
'PandaSignatures',
'PandaDongleId',
}
provided_params = json.loads(args.params_json.read_text())
for key, value in provided_params.items():
assert key not in forbidden_keys
raw = base64.b64decode(value, validate=True)
target = Path(params.get_param_path(params.check_key(key)))
assert target.resolve().is_relative_to(params_root)
temporary = target.with_name(target.name + '.bench-tmp')
assert not temporary.exists()
temporary.write_bytes(raw)
temporary.replace(target)
assert target.read_bytes() == raw
assert params.get_bool('OpenpilotEnabledToggle')
assert not params.get_bool('IsOffroad')
params.put_bool('ObdMultiplexingEnabled', False, block=True)
if not args.steady_state_checkpoint:
put_cache(params, 'CarParamsCache', pinned_cp, block=True)
assert params.get('CarParams') is None and not params.get_bool('ControlsReady') and not params.get_bool('FirmwareQueryDone')
planner_configuration = {
'entrypoint': 'openpilot.selfdrive.controls.plannerd',
'selectionKey': 'UseStarPilotLongitudinalPlanner',
'selectionRule': 'Exact raw 0 selects upstream; absent/1 or invalid selects production StarPilot default.',
'paramsJsonSha256': digest(args.params_json),
'providedFeatureParamsBase64': {key: provided_params[key] for key in PLANNER_PREF_KEYS if key in provided_params},
'featureParamsNotProvided': [key for key in PLANNER_PREF_KEYS if key not in provided_params],
'missingInputMeaning': 'Not supplied in private snapshot; production defaults apply. Does not prove device key absence.',
'carParams': {
key: expected_cp[key] for key in ('carFingerprint', 'openpilotLongitudinalControl', 'passive', 'dashcamOnly', 'notCar', 'flags', 'alternativeExperience')
},
'nativeInputServices': ['carControl', 'carState', 'controlsState', 'vehicleParameters', 'radarState', 'modelV2', 'selfdriveState'],
'actualOptionalInputs': ['slcDashboardObservation', 'slcCruiseEvent', 'carState (nonconflated stop/resume)'],
'unprovidedOptionalInputs': ['slcVisionObservation', 'mapdOut', 'starpilotNavigation', 'slcAction'],
'optionalInputLimit': 'Missing optional services retain normal production unavailable/refusal behavior; no optional-source qualification.',
'recordedPlannerPublishersSuppressed': sorted(PLANNER_OUTPUTS),
'actualRadarFusion': True,
'recordedRadarPublishersSuppressed': sorted(RADAR_OUTPUTS),
'publicationLimit': 'Production upstream selection owns only longitudinalPlan/driverAssistance; other reserved output counters may remain zero.',
'timingEvidence': {
'onroadEvent81': 'selfdrivedLagging; raw onroadEvents capture',
'onroadEventsCadence': 'Emits on event-set changes or at 1Hz; presence/removal bounds episodes, not lagged-loop count.',
'rkMonitorIntervalsExported': True,
'rkObserverScope': 'Only actual SD/Controls Ratekeeper.monitor_time; no per-phase instrumentation.',
'plannerFields': ['longitudinalPlan.modelMonoTime', 'longitudinalPlan.processingDelay', 'longitudinalPlan.solverExecutionTime'],
'limit': 'Service source/receive cadence is not Ratekeeper.monitor_time intervals or avg_dt. Planner has no Ratekeeper.',
},
}
cruise_verification = None
if cruise_checkpoint is not None:
spec = importlib.util.spec_from_file_location('private_cruise_checkpoint', helpers / 'checkpoint_card_v2_cruise.py')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
checked = module.verify_cruise_evidence(args, repo, verify_source=True)
cruise_verification = output / 'cruise-source-verification.json'
cruise_verification.write_text(
json.dumps(
{
'phase': 'before-owned-actors',
'sourceMembershipVerified': True,
'prefix': prefix,
'paramsRoot': str(params_root),
'verifiedMonoNs': time.monotonic_ns(),
'evidence': checked,
},
indent=2,
)
+ '\n'
)
messaging.toggle_fake_events(False)
suppressed = {}
peers = read_peers(packet / 'peers.trace', messaging, suppressed)
planner_configuration['recordedPlannerEventsSuppressed'] = suppressed
peer_services = sorted({message.which() for _, message in peers})
assert not set(peer_services) & OWNED
del peers # Child alone owns peer payloads and publishers after validation.
peer_pm = messaging.PubMaster(['managerState'])
proof_names = ['carState', 'selfdriveState', 'onroadEvents', 'aolAxisState', 'aolSafetyWire', 'aolIntentWire', 'carControl', 'pandaStates']
observed_names = sorted(OWNED | PEERS | {'managerState'})
observer = messaging.SubMaster(proof_names)
sockets = {name: messaging.sub_sock(name, conflate=False) for name in observed_names}
children, logs, records = {}, [], {}
original_policy = os.sched_getscheduler(0)
if original_policy != os.SCHED_OTHER:
os.sched_setscheduler(0, os.SCHED_OTHER, os.sched_param(0))
os.sched_setaffinity(0, {0, 1})
from placement import apply
apply('observer')
def start(name, argv, cwd):
stream = (output / (name + '.log')).open('xb')
logs.append(stream)
if name != 'pandad':
argv = ['taskset', '-c', '0-7', *argv]
child = subprocess.Popen(argv, cwd=cwd, env=env, stdout=stream, stderr=subprocess.STDOUT, start_new_session=True)
children[name] = child
return child
failure, cp_verified, ever_ready, active_samples = None, False, False, 0
counters = dict.fromkeys(observed_names, 0)
rows = []
usage_samples = []
phase_max = {'peerDispatch': 0, 'processSnapshot': 0, 'authorityObserver': 0, 'rawCapture': 0}
peer_max_feed_late_ns, authority_samples = 0, 0
capture_path = output / ('observations.bin' if args.observer_format == 'binary' else 'observations.jsonl')
capture_memory = BoundedCapture(capture_path) # allocate before owned actor/native timers
capture_export = None
capture_cursor = 0
capture_budget_exhaustions = 0
placement = {'enabled': False, 'requested': args.private_native_core2}
checkpoint_hold = None
try:
start(
'peerfeeder',
[
sys.executable,
'-B',
str(HERE / 'peer_feeder_v7_protected.py'),
'--repo',
str(repo),
'--inputs',
str(packet),
'--release',
str(output / 'release.bin'),
'--clocks',
str(output / 'release-clocks.json'),
'--output',
str(output / 'peer-feeder.json'),
],
repo,
)
peer_ready_deadline = time.monotonic_ns() + 12_000_000_000
while not (output / 'peer-ready.json').exists():
assert children['peerfeeder'].poll() is None, 'Peer feeder exited before readiness'
assert time.monotonic_ns() < peer_ready_deadline, 'Peer feeder not ready within12s'
time.sleep(0.005)
peer_ready = json.loads((output / 'peer-ready.json').read_text())
assert peer_ready['services'] == peer_services
gated_daemons = cruise_checkpoint is not None
daemon_gate = output / 'normal-main-ready.json'
if gated_daemons:
assert not daemon_gate.exists()
env.update(SP_DAEMON_MAIN_GATE=str(daemon_gate), SP_DAEMON_MAIN_GATE_CP_SHA256=args.car_params_sha256)
# Cold imports overlap actual Card startup. The unchanged normal mains
# remain behind the parent CP-verification token below.
for name, module, relative in (
('selfdrived', 'openpilot.selfdrive.selfdrived.selfdrived', 'openpilot/selfdrive/selfdrived/selfdrived.py'),
('controlsd', 'openpilot.selfdrive.controls.controlsd', 'openpilot/selfdrive/controls/controlsd.py'),
):
start(
name,
[
sys.executable,
'-B',
str(HERE / 'normal_daemon_entry_v1.py'),
'--module',
module,
'--module-sha256',
source_pins['sources'][relative],
'--gate',
str(daemon_gate),
'--output',
str(output / (name + '-timing.json')),
'--limit',
str(args.timing_limit),
],
repo,
)
native_started_ns = time.monotonic_ns()
start(
'pandad',
[
str(args.native_endpoint.resolve()),
str(args.native_library.resolve()),
str(packet / 'can.trace'),
str(output / 'release.bin'),
str(output / 'native-receipt.json'),
],
repo / 'openpilot/selfdrive/pandad',
)
if args.steady_state_checkpoint:
checkpoint_entry = 'checkpoint_card_v2_cruise.py' if cruise_checkpoint is not None else 'checkpoint_card.py'
checkpoint_launch = helpers / checkpoint_entry
card_command = [
sys.executable,
'-B',
str(checkpoint_launch),
'--repo',
str(repo),
'--car-params',
str(args.car_params.resolve()),
'--car-params-sha256',
args.car_params_sha256,
'--route-car-params',
str(args.route_car_params.resolve()),
'--route-car-params-sha256',
args.route_car_params_sha256,
'--car-schema-sha256',
args.car_schema_sha256,
'--receipt',
str(output / 'checkpoint.json'),
]
if cruise_checkpoint is not None:
card_command += [
'--cruise-checkpoint',
str(args.cruise_checkpoint.resolve()),
'--cruise-checkpoint-sha256',
args.cruise_checkpoint_sha256,
'--cruise-verification',
str(cruise_verification),
'--cruise-verification-sha256',
digest(cruise_verification),
]
else:
card_command = [sys.executable, '-B', '-m', 'openpilot.selfdrive.car.card']
start('card', card_command, repo)
if args.private_native_core2:
assert not (output / 'release.bin').exists()
placement = private_native_core2(children['pandad'], args.native_endpoint)
(output / 'private-native-placement.json').write_text(json.dumps(placement, indent=2) + '\n')
if cruise_checkpoint is not None:
# The compiled endpoint's unchanged release wait is 15s. Bound the entire
# pre-release phase from native spawn to 12s, including scheduler setup.
hold_deadline = native_started_ns + 12_000_000_000
while True:
assert all(children[name].poll() is None for name in ('pandad', 'card')), 'Child exited before checkpoint release'
assert time.monotonic_ns() < hold_deadline, 'Checkpoint not ready within12s; native15s guard retained'
if (output / 'checkpoint.json').exists() and params.get('CarParams') == args.car_params.read_bytes():
break
time.sleep(0.005)
checkpoint_hold = {
'nativeStartedMonoNs': native_started_ns,
'checkpointReadyMonoNs': time.monotonic_ns(),
'boundNs': 12_000_000_000,
'nativeReleaseGuardNs': 15_000_000_000,
'sourceVerificationSha256': digest(cruise_verification),
}
reserve = json.loads(os.environ.get('HEATDEATH_RESERVE', 'null'))
if reserve is not None:
start(
'reserve',
[
sys.executable,
'-B',
str(HERE / 'reserve.py'),
'--core',
str(reserve['core']),
'--cpu-ms',
str(reserve['cpuMs']),
'--period-ms',
str(reserve['periodMs']),
'--duration-ns',
str(meta['durationNs']),
'--release',
str(output / 'release.bin'),
'--output',
str(output / 'reserve.json'),
],
repo,
)
mono_start = time.monotonic_ns() + 300_000_000
boot_start = time.clock_gettime_ns(time.CLOCK_BOOTTIME) + (mono_start - time.monotonic_ns())
(output / 'release-clocks.json').write_text(json.dumps({'prefix': prefix, 'monoStartNs': mono_start, 'bootStartNs': boot_start}, sort_keys=True) + '\n')
release_temporary = output / 'release.tmp'
release_temporary.write_bytes(struct.pack('<Q', mono_start))
release_temporary.replace(output / 'release.bin')
index, manager_due, proof_due = 0, 0, 0
proof_interval_ns = round(1e9 / args.authority_observer_hz)
deadline = mono_start + meta['durationNs']
last_poll = mono_start
with capture_memory as capture:
if args.observer_format == 'binary':
capture.write(BINARY_OBSERVER_MAGIC)
while time.monotonic_ns() < deadline:
now = time.monotonic_ns()
if now < mono_start:
time.sleep(min((mono_start - now) / 1e9, 0.005))
continue
for name, child in children.items():
if child.poll() is not None:
if name in ('pandad', 'peerfeeder', 'reserve') and child.returncode == 0 and now >= 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': '<QHI: receiveMonoNs,serviceIndex,rawEventLength',
'services': observed_names,
'messages': counters,
'sha256': digest(capture_path),
'sourceTimestampsPreserved': True,
'liveJsonSerialization': False,
},
indent=2,
)
+ '\n'
)
(output / 'process-usage.json').write_text(
json.dumps(
{'clockTicksPerSecond': os.sysconf('SC_CLK_TCK'), 'schedstatScope': 'main thread; user/system ticks are process totals', 'samples': usage_samples},
indent=2,
)
+ '\n'
)
result = {
'kind': 'private-native-ipc-bench',
'failure': failure,
'sourceDurationNs': meta['durationNs'],
'cpVerified': cp_verified,
'mode': 'steady-state-checkpoint' if args.steady_state_checkpoint else 'normal-startup',
'startupExcluded': args.steady_state_checkpoint,
'checkpointReceipt': str(output / 'checkpoint.json') if args.steady_state_checkpoint else None,
'everAuthorityReady': ever_ready,
'activeObserverSamples': active_samples,
'qualifiedActiveLatencyBench': False,
'qualificationNote': (
'Inspect sustained actual active output, native RX/TX proof, service cadence, source ages and scheduler records. No automatic acceptance label.'
),
'prefix': prefix,
'children': records,
'exitCodes': {name: child.returncode for name, child in children.items()},
'preCleanupExitCodes': pre_cleanup_exit_codes,
'messages': counters,
'collectorPollCount': len(rows),
'collectorMaxPollIntervalNs': max((row[1] for row in rows), default=None),
'harnessVersion': 'current-production-real-radard-v2-prewarmed-peer',
'observerFormat': args.observer_format,
'phaseTiming': {
'rowsPerPhaseMaximum': args.timing_limit,
'writesOnlyAtShutdown': True,
'observer': 'Ratekeeper.monitor_time only; normal mains/constructor/worker scheduling retained',
'sources': {
name: digest(HERE / name) for name in ('phase_timing.py', 'normal_daemon_entry_v1.py', 'bounded_capture_v1.py', 'peer_feeder_v7_protected.py')
},
'qualification': 'Fresh enabled source, collecting evidence, admitted profile and application remain distinct',
},
'placement': json.loads(os.environ.get('HEATDEATH_PLACEMENT', '{}')),
'syntheticReserve': json.loads(os.environ.get('HEATDEATH_RESERVE', 'null')),
'sourcePins': source_pins,
'normalProductionDaemons': True,
'boundedCapture': capture_export,
'captureBudgetExhaustions': capture_budget_exhaustions,
'captureBudgetNs': 500000,
'captureMessagesPerPollMaximum': 64,
'peerFeedMaximumAdmittedLatenessNs': 20000000,
'independentPeerFeeder': json.loads((output / 'peer-feeder.json').read_text()) if (output / 'peer-feeder.json').exists() else None,
'plannerConfiguration': planner_configuration,
'cruiseCheckpointSha256': args.cruise_checkpoint_sha256,
'cruiseCheckpointPreRelease': checkpoint_hold,
'privateNativePlacement': placement,
'authorityObserverHz': args.authority_observer_hz,
'authorityObserverSamples': authority_samples,
'collectorPhaseMaxNs': phase_max,
'peerMaxFeedLateNs': peer_max_feed_late_ns,
'capturePath': str(capture_path),
'captureSha256': digest(capture_path) if capture_path.exists() else None,
'inputSha256': digest(packet / 'inputs.json'),
'cpSha256': digest(args.car_params),
'endpointSha256': digest(args.native_endpoint),
'nativeLibrarySha256': digest(args.native_library),
'limits': [
'No physical CAN I/O; virtual native backend.',
'Card startup is unmodified and may refuse absent adaptive ECU responses.',
'Recorded peers and generated managerState are declared input stimuli.',
'Native safety test library excludes firmware heartbeat timeout-loop equivalence.',
'Bounded RAM raw capture,25Hz proof polling and private pandad consume hardware CPU; include their PIDs in analysis.',
'Authority observer sample counts depend on the explicitly reported observer frequency.',
'Optional private-native-core2 changes only virtual endpoint placement and is a benchmark limitation.',
],
}
(output / 'result.json').write_text(json.dumps(result, indent=2) + '\n')
if failure:
raise SystemExit(failure)
def main():
parser = argparse.ArgumentParser(description=__doc__)
commands = parser.add_subparsers(dest='command', required=True)
prep = commands.add_parser('prepare')
prep.add_argument('--repo', required=True)
prep.add_argument('--rlog', action='append', type=Path, required=True)
prep.add_argument('--start-mono-ns', type=int, required=True)
prep.add_argument('--duration', type=float, required=True)
prep.add_argument('--source-boot-minus-mono-ns', type=int, required=True)
prep.add_argument('--device-motion-clock', choices=('monotonic', 'boottime'), required=True)
prep.add_argument('--output', type=Path, required=True)
execute = commands.add_parser('run')
execute.add_argument('--repo', required=True)
execute.add_argument('--inputs', type=Path, required=True)
execute.add_argument('--helpers-dir', type=Path, help='Directory containing the unchanged v6 Card checkpoint helpers')
execute.add_argument('--timing-limit', type=int, default=12000)
execute.add_argument('--car-params', type=Path, required=True)
execute.add_argument('--car-params-sha256', required=True)
execute.add_argument('--steady-state-checkpoint', action='store_true')
execute.add_argument('--cruise-checkpoint', type=Path, help='Explicit recorded remembered-speed context; no engagement or authority is restored')
execute.add_argument('--cruise-checkpoint-sha256')
execute.add_argument('--route-car-params', type=Path)
execute.add_argument('--route-car-params-sha256')
execute.add_argument('--car-schema-sha256')
execute.add_argument('--params-json', type=Path, required=True)
execute.add_argument('--native-endpoint', type=Path, required=True)
execute.add_argument('--native-endpoint-sha256', required=True)
execute.add_argument('--native-library', type=Path, required=True)
execute.add_argument('--native-library-sha256', required=True)
execute.add_argument('--root-grant', required=True)
execute.add_argument('--output', type=Path, required=True)
execute.add_argument('--observer-format', choices=('binary', 'json'), default='binary')
execute.add_argument('--authority-observer-hz', type=int, choices=(25,), default=25)
execute.add_argument(
'--private-native-core2',
action='store_true',
help='Explicit private bench only: move owned virtual native threads after observing actual FIFO54/core3 setup',
)
decode = commands.add_parser('decode-observations')
decode.add_argument('--repo', required=True)
decode.add_argument('--capture', type=Path, required=True)
decode.add_argument('--metadata', type=Path, required=True)
decode.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
{'prepare': prepare, 'run': run, 'decode-observations': decode_observations}[args.command](args)
if __name__ == '__main__':
main()