#!/usr/bin/env python3 import argparse import json import matplotlib.patches as mpatches import matplotlib.pyplot as plt import mpld3 import sys from bisect import bisect_left, bisect_right 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'] def plot(lr): PLOT_SERVICES = ['card', 'controlsd', 'boardd'] seen = set() aligned = False start_time = None # dict of services to events per inferred frame times = {s: [[]] for s in PLOT_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'] # Align the best we can; all seen and this is the first service # 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 service == PLOT_SERVICES[0] and event == 'Sent carState') if not aligned: seen.add(service) continue # if not all(s in seen for s in PLOT_SERVICES): # 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)) # for msg in lr: # if len(times[PLOT_SERVICES[0]]) > 100: # continue # if start_time is None: # # closely matches timestamp time # start_time = msg.logMonoTime # # if msg.which() == 'logMessage': # # print() # # print(msg.logMonoTime) # 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'] # # # Align the best we can; all seen and this is the first service # aligned = aligned or (all(s in seen for s in PLOT_SERVICES) and service == PLOT_SERVICES[0]) # if not aligned: # seen.add(service) # continue # # if not all(s in seen for s in PLOT_SERVICES): # # 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)) # tooltip = mpld3.plugins.PointLabelTooltip(scatter, labels=points['labels']) # mpld3.plugins.connect(fig, tooltip) 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') 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)