#!/usr/bin/env python3 import argparse import json import matplotlib.patches as mpatches import matplotlib.pyplot as plt 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]]) > 400: 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": []} colors = COLORS[:len(PLOT_SERVICES)] offset_services = True height = 0.9 if offset_services else 0.9 # offsets = [[0, -10 * j] for j in range(len(PLOT_SERVICES))] if offset_services else None 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 - j * 5, height - j * 5) for j in range(len(service_bars))], facecolors=(colors), alpha=0.5)#, offsets=offsets) scatter = ax.scatter(points['x'], points['y'], marker='d', edgecolor='black') # for lbl, x, y in zip(points['labels'], points['x'], points['y']): # ax.annotate(lbl, (x, y)) plt.legend(handles=[mpatches.Patch(color=colors[i], label=PLOT_SERVICES[i]) for i in range(len(PLOT_SERVICES))]) # plt.scatter([t[0] for t in times], [t[1] for t in times], marker='d', edgecolor='black') ax.set_xlabel('milliseconds') txt = ax.text(0, 0, '', ha='center', fontsize=8, color='red') def hover(event): txt.set_text("") status, pts = scatter.contains(event) txt.set_visible(status) if status: lbl = points['labels'][pts['ind'][0]] txt.set_text(lbl) 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 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/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 times, points = plot(lr)