openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

189 lines
7.5 KiB

import argparse
import json
import mpld3
import sys
from collections import defaultdict
import matplotlib.pyplot as plt
from tools.lib.logreader import logreader_from_route_or_segment
DEMO_ROUTE = "9f583b1d93915c31|2022-04-01--17-51-29"
SERVICES = ['camerad', 'modeld', 'plannerd', 'controlsd', 'boardd']
# Retrive controlsd frameId from lateralPlan, mismatch with longitudinalPlan will be ignored
MONOTIME_KEYS = ['modelMonoTime', 'lateralPlanMonoTime']
MSGQ_TO_SERVICE = {
'roadCameraState': 'camerad',
'wideRoadCameraState': 'camerad',
'modelV2': 'modeld',
'lateralPlan': 'plannerd',
'longitudinalPlan': 'plannerd',
'sendcan': 'controlsd',
'controlsState': 'controlsd'
}
SERVICE_TO_DURATIONS = {
'camerad': ['processingTime'],
'modeld': ['modelExecutionTime', 'gpuExecutionTime'],
'plannerd': ["solverExecutionTime"],
}
def read_logs(lr):
data = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
mono_to_frame = {}
frame_mismatches = []
latest_sendcan_monotime = 0
for msg in lr:
if msg.which() == 'sendcan':
latest_sendcan_monotime = msg.logMonoTime
continue
if msg.which() in MSGQ_TO_SERVICE:
service = MSGQ_TO_SERVICE[msg.which()]
msg_obj = getattr(msg, msg.which())
frame_id = -1
if hasattr(msg_obj, "frameId"):
frame_id = msg_obj.frameId
else:
continue_outer = False
for key in MONOTIME_KEYS:
if hasattr(msg_obj, key):
if getattr(msg_obj, key) == 0:
# Filter out controlsd messages which arrive before the camera loop
continue_outer = True
elif getattr(msg_obj, key) in mono_to_frame:
frame_id = mono_to_frame[getattr(msg_obj, key)]
if continue_outer:
continue
mono_to_frame[msg.logMonoTime] = frame_id
data['timestamp'][frame_id][service].append((msg.which()+" published", msg.logMonoTime))
if service in SERVICE_TO_DURATIONS:
for duration in SERVICE_TO_DURATIONS[service]:
data['duration'][frame_id][service].append((msg.which()+"."+duration, getattr(msg_obj, duration)))
if service == SERVICES[0]:
data['timestamp'][frame_id][service].append((msg.which()+" start of frame", msg_obj.timestampSof))
elif msg.which() == 'controlsState':
# Sendcan is published before controlsState, but the frameId is retrived in CS
data['timestamp'][frame_id][service].append(("sendcan published", latest_sendcan_monotime))
elif msg.which() == 'modelV2':
if msg_obj.frameIdExtra != frame_id:
frame_mismatches.append(frame_id)
# Failed frameId fetches are stored in -1
assert sum([len(data['timestamp'][-1][service]) for service in data['timestamp'][-1].keys()]) < 20, "Too many frameId fetch fails"
del data['timestamp'][-1]
assert len(frame_mismatches) < 20, "Too many frame mismatches"
return (data, frame_mismatches)
def get_min_time(frame_id, service, timestamps, use_max=False):
return min(timestamps[frame_id][service], key=lambda x: x[1])[1] if not use_max else max(timestamps[frame_id][service], key=lambda x: x[1])[1]
def get_interval(frame_id, service, timestamps):
try:
service_max = get_min_time(frame_id, service, timestamps, use_max=True)
if service == SERVICES[0]:
service_min = get_min_time(frame_id, service, timestamps)
return (service_min, service_max)
prev_service = SERVICES[SERVICES.index(service)-1]
prev_service_max = get_min_time(frame_id, prev_service, timestamps, use_max=True)
return (prev_service_max, service_max)
except ValueError:
return (-1,-1)
def find_frame_id(time, service, timestamps):
for frame_id in reversed(timestamps):
start, end = get_interval(frame_id, service, timestamps)
if start <= time <= end:
return frame_id
return -1
## ASSUMES THAT AT LEAST ONE CLOUDLOG IS MADE IN CONTROLSD
def insert_cloudlogs(lr, timestamps):
t0 = get_min_time(min(timestamps.keys()), SERVICES[0], timestamps)
failed_inserts = 0
latest_controls_frameid = 0
for msg in lr:
if msg.which() == "logMessage":
jmsg = json.loads(msg.logMessage)
if "timestamp" in jmsg['msg']:
time = int(jmsg['msg']['timestamp']['time'])
service = jmsg['ctx']['daemon']
event = jmsg['msg']['timestamp']['event']
if time < t0:
# Filter out controlsd messages which arrive before the camera loop
continue
frame_id = latest_controls_frameid if service == "boardd" else find_frame_id(time, service, timestamps)
if frame_id > -1:
timestamps[frame_id][service].append((event, time))
if service == 'controlsd':
latest_controls_frameid = frame_id
else:
failed_inserts += 1
assert failed_inserts < len(timestamps), "Too many failed cloudlog inserts"
def print_timestamps(timestamps, durations ,relative):
t0 = get_min_time(min(timestamps.keys()), SERVICES[0], timestamps)
for frame_id in timestamps.keys():
print('='*80)
print("Frame ID:", frame_id)
if relative:
t0 = get_min_time(frame_id, SERVICES[0], timestamps)
for service in SERVICES:
print(" "+service)
events = timestamps[frame_id][service]
for event, time in sorted(events, key = lambda x: x[1]):
print(" "+'%-53s%-53s' %(event, str((time-t0)/1e6)))
for event, time in durations[frame_id][service]:
print(" "+'%-53s%-53s' %(event, str(time*1000)))
def graph_timestamps(timestamps, relative):
t0 = get_min_time(min(timestamps.keys()), SERVICES[0], timestamps)
fig, ax = plt.subplots()
ax.set_xlim(0, 150 if relative else 750)
ax.set_ylim(0, 15)
points = {"x": [], "y": [], "labels": []}
for frame_id, services in timestamps.items():
if relative:
t0 = get_min_time(frame_id, SERVICES[0], timestamps)
service_bars = []
for service, events in services.items():
start, end = get_interval(frame_id, service,timestamps)
service_bars.append(((start-t0)/1e6,(end-start)/1e6))
for event in events:
points["x"].append((event[1]-t0)/1e6)
points["y"].append(frame_id+0.45)
points["labels"].append(event[0])
ax.broken_barh(service_bars, (frame_id, 0.9), facecolors=(["blue", 'green', 'red', 'yellow', 'purple']))
scatter = ax.scatter(points['x'], points['y'], marker="d", edgecolor='black')
tooltip = mpld3.plugins.PointLabelTooltip(scatter, labels=points["labels"])
ax.legend()
mpld3.plugins.connect(fig, tooltip)
#mpld3.save_html(fig, 'test.html')
mpld3.show()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="A tool for analyzing openpilot's end-to-end latency",
formatter_class = argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("--relative", action="store_true", help="Make timestamps relative to the start of each frame")
parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one")
parser.add_argument("--plot", action="store_true", help="If a plot should be generated")
parser.add_argument("route_or_segment_name", nargs='?', help="The route to print")
if len(sys.argv) == 1:
parser.print_help()
sys.exit()
args = parser.parse_args()
r = DEMO_ROUTE if args.demo else args.route_or_segment_name.strip()
lr = logreader_from_route_or_segment(r, sort_by_time=True)
data, frame_mismatches = read_logs(lr)
lr.reset()
insert_cloudlogs(lr, data['timestamp'])
print_timestamps(data['timestamp'], data['duration'], args.relative)
if args.plot:
graph_timestamps(data['timestamp'], args.relative)