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.

190 lines
7.5 KiB

Latency logging 2 (#24058) * msg_order and gantt * frameId in long/lat planner * track frame id * controls frame id * graph tracked events * graph json * cloudlog timestamp * c++ cloudlog * add frame id * bug fixes * bug fixes * frame id visionicp * bug fixes and debug level * timestamp log placement * print timestamps in table * translate events * more logging * bug fixes * daemon boardd * print logs with boardd * more timestamp logs * cleanup * remove publish logs * bug fix * timestamp received * timestamp received * bug fixes * use json lib * ignore driver camera * prep for new timestamp pipeline * bug fix * read new pipeline unfinnished * read new pipeline * bug fix * add frame to controlsstate * remove controlsstate * print * cleanup * more cleanup + bug fix * clock build issue * remove unused imports * format durations * increase speed * pr comments fixes * conflicts * set MANAGER_DAEMON for boardd * clean script code * bug fix + argparse * remove rcv time * bug fixes * print without tabulate * fix pre-commits * plot gnatt * color bug fix * read without timestampextra * bump panda * mono time instead of frame id * finnish script * clean unused * clean unused logging * monotonic + json fixes * del test * remove whilelines * bump laika * cleanup * remove deps * logs nicer strings * remove plotting from scirpt * reset pipfile * reset pipfile * nicer strings * bug fix * bug fix * pr comments cleaning * remove plotting * bug fix * new demo route * bump opendbc and panda * cereal master * cereal master * script less komplex * assertions * matplotlib * readme * Update README.md * graph html * design fixes * more code design * Update common/logging_extra.py Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> * whitespace Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> * Update tools/latency_logger/latency_logger.py Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> * pr comments * bug fix * readme + env once * clean swaglog * bug fix * Update tools/latencylogger/README.md Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> * revert * revert * clean swaglog with error * remove typo file * revert graph * cereal * submodules * whitespaces * update refs Co-authored-by: Bruce Wayne <batman@workstation-openpilot2.internal> Co-authored-by: Comma Device <device@comma.ai> Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> old-commit-hash: 65fca83abed98f32993286dc5a66e3e583f06172
3 years ago
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)