parent
534a357ee9
commit
364201cbeb
2 changed files with 6 additions and 301 deletions
@ -1,288 +0,0 @@ |
||||
#!/usr/bin/env python3 |
||||
import argparse |
||||
import json |
||||
import matplotlib.patches as mpatches |
||||
import matplotlib.pyplot as plt |
||||
import seaborn as sns |
||||
from tqdm import tqdm |
||||
import numpy as np |
||||
import sys |
||||
from collections import defaultdict |
||||
|
||||
from openpilot.tools.lib.logreader import LogReader |
||||
|
||||
DEMO_ROUTE = "9f583b1d93915c31|2022-05-18--10-49-51--0" |
||||
|
||||
COLORS = ['blue', 'green', 'red', 'yellow', 'orange', 'purple'] |
||||
PLOT_SERVICES = ['card', 'controlsd'] # , 'boardd'] |
||||
|
||||
|
||||
def plot(lr): |
||||
seen = set() |
||||
aligned = False |
||||
|
||||
start_time = None |
||||
# dict of services to events per inferred frame |
||||
times = {s: [[]] for s in PLOT_SERVICES} |
||||
|
||||
first_event = None |
||||
# temp_times = {s: [] for s in PLOT_SERVICES} # holds only current frame of services |
||||
|
||||
timestamps = [json.loads(msg.logMessage) for msg in lr if msg.which() == 'logMessage' and 'timestamp' in msg.logMessage] |
||||
# print(timestamps) |
||||
timestamps = sorted(timestamps, key=lambda m: float(m['msg']['timestamp']['time'])) |
||||
|
||||
# closely matches timestamp time |
||||
start_time = next(msg.logMonoTime for msg in lr) |
||||
|
||||
for jmsg in timestamps: |
||||
if len(times[PLOT_SERVICES[0]]) > 1400: |
||||
continue |
||||
|
||||
# print() |
||||
# print(msg.logMonoTime) |
||||
time = int(jmsg['msg']['timestamp']['time']) |
||||
service = jmsg['ctx']['daemon'] |
||||
event = jmsg['msg']['timestamp']['event'] |
||||
# print(jmsg) |
||||
# print(seen) |
||||
|
||||
if service in PLOT_SERVICES and first_event is None: |
||||
first_event = event |
||||
|
||||
# Align the best we can; wait for all to be seen and this is the first event |
||||
# TODO: detect first logMessage correctly by keeping track of events before aligned |
||||
aligned = aligned or (all(s in seen for s in PLOT_SERVICES) and event == first_event) |
||||
if not aligned: |
||||
seen.add(service) |
||||
continue |
||||
|
||||
if service in PLOT_SERVICES: |
||||
|
||||
# new frame when we've seen this event before |
||||
new_frame = event in {e[1] for e in times[service][-1]} |
||||
if new_frame: |
||||
times[service].append([]) |
||||
|
||||
# print(msg.logMonoTime, jmsg) |
||||
print('new_frame', new_frame) |
||||
times[service][-1].append(((time - start_time) * 1e-6, event)) |
||||
|
||||
points = {"x": [], "y": [], "labels": []} |
||||
height = 0.9 |
||||
offsets = [[0, -10 * j] for j in range(len(PLOT_SERVICES))] |
||||
|
||||
fig, ax = plt.subplots() |
||||
|
||||
for idx, service_times in enumerate(zip(*times.values())): |
||||
print() |
||||
print('idx', idx) |
||||
service_bars = [] |
||||
for j, (service, frame_times) in enumerate(zip(times.keys(), service_times)): |
||||
if idx + 1 == len(times[service]): |
||||
break |
||||
print(service, frame_times) |
||||
start = frame_times[0][0] |
||||
# use the first event time from next frame |
||||
end = times[service][idx + 1][0][0] # frame_times[-1][0] |
||||
print('start, end', start, end) |
||||
service_bars.append((start, end - start)) |
||||
for event in frame_times: |
||||
points['x'].append(event[0]) |
||||
points['y'].append(idx - j * 1) |
||||
points['labels'].append(event[1]) |
||||
print(service_bars) |
||||
|
||||
# offset = offset_services |
||||
# offset each service |
||||
for j, sb in enumerate(service_bars): |
||||
ax.broken_barh([sb], (idx - height / 2 - j * 1, height), facecolors=[COLORS[j]], alpha=0.5) # , offsets=offsets) |
||||
# ax.broken_barh(service_bars, (idx - height / 2, height), facecolors=COLORS, alpha=0.5, offsets=offsets) |
||||
|
||||
scatter = ax.scatter(points['x'], points['y'], marker='d', edgecolor='black') |
||||
txt = ax.text(0, 0, '', ha='center', fontsize=8, color='red') |
||||
ax.set_xlabel('milliseconds') |
||||
|
||||
plt.legend(handles=[mpatches.Patch(color=COLORS[i], label=PLOT_SERVICES[i]) for i in range(len(PLOT_SERVICES))]) |
||||
|
||||
def hover(event): |
||||
txt.set_text("") |
||||
status, pts = scatter.contains(event) |
||||
txt.set_visible(status) |
||||
if status: |
||||
pt_idx = pts['ind'][0] |
||||
txt.set_text(f"{points['labels'][pt_idx]} ({points['x'][pt_idx]:0.2f} ms)") |
||||
txt.set_position((event.xdata, event.ydata + 1)) |
||||
event.canvas.draw() |
||||
|
||||
fig.canvas.mpl_connect("motion_notify_event", hover) |
||||
|
||||
plt.show() |
||||
# plt.pause(1000) |
||||
return times, points |
||||
|
||||
|
||||
def plot_dist(lr, poll): |
||||
|
||||
lr = list(lr) |
||||
|
||||
# carState_service_times = [] |
||||
# prev_time = None |
||||
# for msg in lr: |
||||
# if msg.which() == 'carState': |
||||
# if prev_time is not None: |
||||
# carState_service_times.append((msg.logMonoTime - prev_time) * 1e-6) |
||||
# prev_time = msg.logMonoTime |
||||
|
||||
# logMonoTime is from logmessaged, not when the timestamp was created. some messages are out of order |
||||
timestamps = [json.loads(msg.logMessage) for msg in tqdm(lr) if msg.which() == 'logMessage' and '"timestamp"' in msg.logMessage] |
||||
timestamps = sorted(timestamps, key=lambda m: float(m['msg']['timestamp']['time'])) |
||||
timestamps = [m for m in tqdm(timestamps) if m['ctx']['daemon'] in PLOT_SERVICES] |
||||
|
||||
initialized = False |
||||
ready = False |
||||
|
||||
start_card_loop = None |
||||
received_can = None |
||||
state_updated = None |
||||
sent_carState = None |
||||
state_published = None |
||||
sent_carControl = None |
||||
|
||||
card_e2e_loop_times = [] |
||||
card_carInterface_update_times = [] |
||||
carState_recv_times = [] |
||||
carControl_recv_times = [] |
||||
carState_to_carControl_times = [] |
||||
card_controls_times = [] |
||||
card_loop_times = [] |
||||
|
||||
for jmsg in tqdm(timestamps): |
||||
time = int(jmsg['msg']['timestamp']['time']) |
||||
service = jmsg['ctx']['daemon'] |
||||
event = jmsg['msg']['timestamp']['event'] |
||||
|
||||
if event == 'Initialized' and service == 'card': |
||||
initialized = True |
||||
|
||||
if initialized and event == 'Start card': |
||||
ready = True |
||||
|
||||
if not ready: |
||||
continue |
||||
|
||||
if event == 'Start card' and service == 'card': |
||||
if start_card_loop is not None: |
||||
card_e2e_loop_times.append((time - start_card_loop) * 1e-6) |
||||
start_card_loop = time |
||||
|
||||
elif event == 'Received can' and service == 'card': |
||||
# measuring from this time does not include wait time for can packet, so this measures true card loop time taken |
||||
received_can = time |
||||
|
||||
elif event == 'State updated' and service == 'card': |
||||
state_updated = time |
||||
card_carInterface_update_times.append((time - received_can) * 1e-6) |
||||
|
||||
elif event == 'Sent carState' and service == 'card': |
||||
sent_carState = time |
||||
|
||||
elif event == 'Got carState' and service == 'controlsd': |
||||
# TODO why none |
||||
if sent_carState is not None: |
||||
carState_recv_times.append((time - sent_carState) * 1e-6) |
||||
|
||||
elif event == 'Logs published' and service == 'controlsd': |
||||
sent_carControl = time |
||||
|
||||
elif event == 'State published' and service == 'card': |
||||
state_published = time |
||||
if poll: # only makes sense when polling |
||||
# from carState sent to carControl received |
||||
carControl_recv_times.append((time - sent_carControl) * 1e-6) |
||||
carState_to_carControl_times.append((time - sent_carState) * 1e-6) |
||||
|
||||
elif event == 'Controls updated' and service == 'card': |
||||
card_controls_times.append((time - state_published) * 1e-6) |
||||
card_loop_times.append((time - received_can) * 1e-6) # this is time NOT spent waiting for can |
||||
|
||||
|
||||
fig, ax = plt.subplots(3) |
||||
|
||||
fig.suptitle('Polling/waiting on carControl from controlsd' if poll else 'Not polling on carControl') |
||||
|
||||
ax[0].set_title('cereal communication times') |
||||
ax[0].set_xlim(0, 16) |
||||
sns.histplot(carState_recv_times, kde=True, ax=ax[0], |
||||
label=f'carState->controlsd recv time: \n minmax: {min(carState_recv_times):0.2f}, {max(carState_recv_times):>5.2f}, ' + |
||||
f'med: {np.median(carState_recv_times):0.2f}, mean: {np.mean(carState_recv_times):0.2f}, ' + |
||||
f'95th: {np.percentile(carState_recv_times, 95):0.2f}') |
||||
if poll: |
||||
sns.histplot(carControl_recv_times, kde=True, ax=ax[0], |
||||
label=f'carControl->card recv time (polling on carControl): \n minmax: {min(carControl_recv_times):0.2f}, {max(carControl_recv_times):>5.2f}, ' + |
||||
f'med: {np.median(carControl_recv_times):0.2f}, mean: {np.mean(carControl_recv_times):0.2f}, ' + |
||||
f'95th: {np.percentile(carControl_recv_times, 95):0.2f}') |
||||
ax[0].legend() |
||||
|
||||
ax[1].set_title('card loop times') |
||||
ax[1].set_xlim(0, 16) |
||||
if poll: |
||||
sns.histplot(carState_to_carControl_times, kde=True, ax=ax[1], |
||||
label=f'waiting on carControl (polling on carControl): \n minmax: {min(carState_to_carControl_times):0.2f}, {max(carState_to_carControl_times):>5.2f}, ' + |
||||
f'med: {np.median(carState_to_carControl_times):0.2f}, mean: {np.mean(carState_to_carControl_times):0.2f}, ' + |
||||
f'95th: {np.percentile(carState_to_carControl_times, 95):0.2f}') |
||||
sns.histplot(card_controls_times, kde=True, ax=ax[1], label=f'CI.apply(): \n minmax: {min(card_controls_times):0.2f}, {max(card_controls_times):>6.2f}, ' + |
||||
f'med: {np.median(card_controls_times):0.2f}, mean: {np.mean(card_controls_times):0.2f}, ' + |
||||
f'95th: {np.percentile(card_controls_times, 95):0.2f}') |
||||
sns.histplot(card_carInterface_update_times, kde=True, ax=ax[1], |
||||
label=f'CI.update(): \n minmax: {min(card_carInterface_update_times):0.2f}, {max(card_carInterface_update_times):>5.2f}, ' + |
||||
f'med: {np.median(card_carInterface_update_times):0.2f}, mean: {np.mean(card_carInterface_update_times):0.2f}, ' + |
||||
f'95th: {np.percentile(card_carInterface_update_times, 95):0.2f}') |
||||
ax[1].legend() |
||||
|
||||
ax[2].set_title('total card loop time') |
||||
ax[2].set_xlim(0, 16) |
||||
sns.histplot(card_loop_times, kde=True, ax=ax[2], label=f'entire card loop time: \n minmax: {min(card_loop_times):0.2f}, {max(card_loop_times):>5.2f}, ' + |
||||
f'med: {np.median(card_loop_times):0.2f}, mean: {np.mean(card_loop_times):0.2f}, ' + |
||||
f'95th: {np.percentile(card_loop_times, 95):0.2f}') |
||||
ax[2].legend() |
||||
ax[2].set_xlabel('ms') |
||||
|
||||
return timestamps, card_loop_times, carState_recv_times, carState_to_carControl_times |
||||
|
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
# parser = argparse.ArgumentParser(description="A tool for analyzing openpilot's end-to-end latency", |
||||
# formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
||||
# parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one") |
||||
# 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(r, sort_by_time=True) |
||||
# lr = LogReader('08e4c2a99df165b1/00000016--c3a4ca99ec/0', sort_by_time=True) # normal |
||||
# lr = LogReader('08e4c2a99df165b1/00000017--e2d24ab118/0', sort_by_time=True) # polls on carControl |
||||
# lr = LogReader('08e4c2a99df165b1/00000018--cf65e47c24/0', sort_by_time=True) # polls on carControl, sends it earlier |
||||
# lr = LogReader('08e4c2a99df165b1/00000019--e73e3ab4df/0', sort_by_time=True) # polls on carControl, more logging |
||||
|
||||
# lr = LogReader('08e4c2a99df165b1/0000002c--b40eb82d6d/0:-1', sort_by_time=True) # polls on carControl |
||||
# lr = LogReader('08e4c2a99df165b1/0000002d--ccebe8b617/0:1', sort_by_time=True) # no poll on carControl |
||||
# lr = LogReader('08e4c2a99df165b1/0000002e--fd98f6603b/:7', sort_by_time=True) # no poll on carControl (no timestamps) |
||||
|
||||
POLL = False # carControl polling or not |
||||
if POLL: |
||||
# lr = LogReader('08e4c2a99df165b1/00000032--2c1d57d894/0', sort_by_time=True) # carControl poll, w/ reduced timestamps |
||||
# lr = LogReader('08e4c2a99df165b1/00000033--1e2720e55b/0', sort_by_time=True) # carControl poll, w/ poll flag (FINAL) |
||||
lr = LogReader('08e4c2a99df165b1/00000036--4eb8126f04', sort_by_time=True) # carControl poll, w/ poll flag & Received can (FINAL v2) |
||||
else: |
||||
# lr = LogReader('08e4c2a99df165b1/00000031--f6f38d1ccf/0', sort_by_time=True) # no carControl poll, w/ reduced timestamps |
||||
# lr = LogReader('08e4c2a99df165b1/00000035--0abfde9c4a/0', sort_by_time=True) # no carControl poll, w/ poll flag (FINAL) |
||||
lr = LogReader('08e4c2a99df165b1/00000037--f6294815ac', sort_by_time=True) # no carControl poll, w/ poll flag & Received can (FINAL v2) |
||||
|
||||
timestamps = plot_dist(lr, poll=POLL) |
||||
# times, points = plot(lr) |
Loading…
Reference in new issue