From 62b97fabf79d848264c79f1fc44857b79851896f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 20 Jun 2026 13:52:16 -0700 Subject: [PATCH] merge selfdrive/debug/ into tools/scripts/ (#38195) * merge selfdrive/debug/ into tools/scripts/ * rm unused * single timings script * lil more * profiling is scripts quality --- conftest.py | 3 - selfdrive/car/tests/test_docs.py | 10 -- selfdrive/debug/README.md | 59 --------- selfdrive/debug/analyze-msg-size.py | 82 ------------ .../debug/check_can_parser_performance.py | 36 ------ selfdrive/debug/check_freq.py | 50 -------- selfdrive/debug/check_lag.py | 27 ---- selfdrive/debug/check_timings.py | 36 ------ selfdrive/debug/dump_car_docs.py | 18 --- selfdrive/debug/print_docs_diff.py | 120 ------------------ selfdrive/debug/touch_replay.py | 54 -------- selfdrive/test/test_onroad.py | 2 +- tools/CTF.md | 2 +- .../debug => tools/scripts}/__init__.py | 0 .../scripts/car}/can_print_changes.py | 2 +- .../scripts/car}/can_printer.py | 0 .../debug => tools/scripts/car}/can_table.py | 0 .../debug => tools/scripts}/car/clear_dtc.py | 0 .../scripts}/car/disable_ecu.py | 0 .../debug => tools/scripts}/car/ecu_addrs.py | 0 .../scripts}/car/fw_versions.py | 0 .../car/hyundai_enable_radar_points.py | 0 .../scripts/car}/max_lat_accel.py | 0 .../car}/measure_torque_time_to_max.py | 0 .../scripts/car}/read_dtc_status.py | 0 .../scripts}/car/toyota_eps_factor.py | 0 {selfdrive/debug => tools/scripts}/car/vin.py | 0 .../scripts}/car/vw_mqb_config.py | 0 .../debug => tools/scripts}/count_events.py | 0 .../debug => tools/scripts}/cpu_usage_stat.py | 2 +- .../debug => tools/scripts}/cycle_alerts.py | 0 .../debug_fw_fingerprinting_offline.py | 0 {selfdrive/debug => tools/scripts}/dump.py | 0 tools/scripts/fetch_image_from_route.py | 43 ------- .../scripts}/filter_log_message.py | 0 .../scripts}/fingerprint_from_route.py | 0 .../scripts}/fuzz_fw_fingerprint.py | 0 .../scripts}/get_fingerprint.py | 0 .../scripts}/live_cpu_and_temp.py | 0 .../debug => tools/scripts}/mem_usage.py | 0 .../debug => tools/scripts}/print_flags.py | 0 .../{ => scripts}/profiling/clpeak/.gitignore | 0 tools/{ => scripts}/profiling/clpeak/build.sh | 0 .../profiling/clpeak/no_print.patch | 0 .../profiling/clpeak/run_continuously.patch | 0 tools/{ => scripts}/profiling/ftrace.sh | 0 .../profiling/palanteer/.gitignore | 0 .../profiling/palanteer/setup.sh | 0 .../profiling/perfetto/.gitignore | 0 .../{ => scripts}/profiling/perfetto/build.sh | 0 .../{ => scripts}/profiling/perfetto/copy.sh | 0 .../profiling/perfetto/record.sh | 0 .../profiling/perfetto/server.sh | 0 .../profiling/perfetto/traces.sh | 0 .../{ => scripts}/profiling/py-spy/profile.sh | 0 .../profiling/snapdragon/.gitignore | 0 .../profiling/snapdragon/README.md | 0 .../profiling/snapdragon/setup-agnos.sh | 0 .../profiling/snapdragon/setup-profiler.sh | 0 tools/{ => scripts}/profiling/watch-irqs.sh | 0 .../debug => tools/scripts}/qlog_size.py | 0 .../scripts}/run_process_on_route.py | 0 tools/scripts/save_ubloxraw_stream.py | 47 ------- .../debug => tools/scripts}/set_car_params.py | 0 .../scripts}/test_fw_query_on_routes.py | 0 {selfdrive/debug => tools/scripts}/uiview.py | 0 tools/scripts/watch_timings.py | 102 +++++++++++++++ 67 files changed, 106 insertions(+), 589 deletions(-) delete mode 100644 selfdrive/debug/README.md delete mode 100755 selfdrive/debug/analyze-msg-size.py delete mode 100755 selfdrive/debug/check_can_parser_performance.py delete mode 100755 selfdrive/debug/check_freq.py delete mode 100755 selfdrive/debug/check_lag.py delete mode 100755 selfdrive/debug/check_timings.py delete mode 100755 selfdrive/debug/dump_car_docs.py delete mode 100755 selfdrive/debug/print_docs_diff.py delete mode 100755 selfdrive/debug/touch_replay.py rename {selfdrive/debug => tools/scripts}/__init__.py (100%) rename {selfdrive/debug => tools/scripts/car}/can_print_changes.py (98%) rename {selfdrive/debug => tools/scripts/car}/can_printer.py (100%) rename {selfdrive/debug => tools/scripts/car}/can_table.py (100%) rename {selfdrive/debug => tools/scripts}/car/clear_dtc.py (100%) rename {selfdrive/debug => tools/scripts}/car/disable_ecu.py (100%) rename {selfdrive/debug => tools/scripts}/car/ecu_addrs.py (100%) rename {selfdrive/debug => tools/scripts}/car/fw_versions.py (100%) rename {selfdrive/debug => tools/scripts}/car/hyundai_enable_radar_points.py (100%) rename {selfdrive/debug => tools/scripts/car}/max_lat_accel.py (100%) rename {selfdrive/debug => tools/scripts/car}/measure_torque_time_to_max.py (100%) rename {selfdrive/debug => tools/scripts/car}/read_dtc_status.py (100%) rename {selfdrive/debug => tools/scripts}/car/toyota_eps_factor.py (100%) rename {selfdrive/debug => tools/scripts}/car/vin.py (100%) rename {selfdrive/debug => tools/scripts}/car/vw_mqb_config.py (100%) rename {selfdrive/debug => tools/scripts}/count_events.py (100%) rename {selfdrive/debug => tools/scripts}/cpu_usage_stat.py (98%) rename {selfdrive/debug => tools/scripts}/cycle_alerts.py (100%) rename {selfdrive/debug => tools/scripts}/debug_fw_fingerprinting_offline.py (100%) rename {selfdrive/debug => tools/scripts}/dump.py (100%) delete mode 100755 tools/scripts/fetch_image_from_route.py rename {selfdrive/debug => tools/scripts}/filter_log_message.py (100%) rename {selfdrive/debug => tools/scripts}/fingerprint_from_route.py (100%) rename {selfdrive/debug => tools/scripts}/fuzz_fw_fingerprint.py (100%) rename {selfdrive/debug => tools/scripts}/get_fingerprint.py (100%) rename {selfdrive/debug => tools/scripts}/live_cpu_and_temp.py (100%) rename {selfdrive/debug => tools/scripts}/mem_usage.py (100%) rename {selfdrive/debug => tools/scripts}/print_flags.py (100%) rename tools/{ => scripts}/profiling/clpeak/.gitignore (100%) rename tools/{ => scripts}/profiling/clpeak/build.sh (100%) rename tools/{ => scripts}/profiling/clpeak/no_print.patch (100%) rename tools/{ => scripts}/profiling/clpeak/run_continuously.patch (100%) rename tools/{ => scripts}/profiling/ftrace.sh (100%) rename tools/{ => scripts}/profiling/palanteer/.gitignore (100%) rename tools/{ => scripts}/profiling/palanteer/setup.sh (100%) rename tools/{ => scripts}/profiling/perfetto/.gitignore (100%) rename tools/{ => scripts}/profiling/perfetto/build.sh (100%) rename tools/{ => scripts}/profiling/perfetto/copy.sh (100%) rename tools/{ => scripts}/profiling/perfetto/record.sh (100%) rename tools/{ => scripts}/profiling/perfetto/server.sh (100%) rename tools/{ => scripts}/profiling/perfetto/traces.sh (100%) rename tools/{ => scripts}/profiling/py-spy/profile.sh (100%) rename tools/{ => scripts}/profiling/snapdragon/.gitignore (100%) rename tools/{ => scripts}/profiling/snapdragon/README.md (100%) rename tools/{ => scripts}/profiling/snapdragon/setup-agnos.sh (100%) rename tools/{ => scripts}/profiling/snapdragon/setup-profiler.sh (100%) rename tools/{ => scripts}/profiling/watch-irqs.sh (100%) rename {selfdrive/debug => tools/scripts}/qlog_size.py (100%) rename {selfdrive/debug => tools/scripts}/run_process_on_route.py (100%) delete mode 100755 tools/scripts/save_ubloxraw_stream.py rename {selfdrive/debug => tools/scripts}/set_car_params.py (100%) rename {selfdrive/debug => tools/scripts}/test_fw_query_on_routes.py (100%) rename {selfdrive/debug => tools/scripts}/uiview.py (100%) create mode 100755 tools/scripts/watch_timings.py diff --git a/conftest.py b/conftest.py index e368b01e8..dd159129b 100644 --- a/conftest.py +++ b/conftest.py @@ -12,9 +12,6 @@ collect_ignore = [ "selfdrive/test/process_replay/test_processes.py", "selfdrive/test/process_replay/test_regen.py", ] -collect_ignore_glob = [ - "selfdrive/debug/*.py", -] def pytest_sessionstart(session): diff --git a/selfdrive/car/tests/test_docs.py b/selfdrive/car/tests/test_docs.py index 6e13d55b2..ef6795ef8 100644 --- a/selfdrive/car/tests/test_docs.py +++ b/selfdrive/car/tests/test_docs.py @@ -1,9 +1,5 @@ -import os -from openpilot.common.basedir import BASEDIR from opendbc.car.docs import generate_cars_md, get_all_car_docs -from openpilot.selfdrive.debug.dump_car_docs import dump_car_docs -from openpilot.selfdrive.debug.print_docs_diff import print_car_docs_diff from openpilot.selfdrive.car.docs import CARS_MD_TEMPLATE @@ -14,9 +10,3 @@ class TestCarDocs: def test_generator(self): generate_cars_md(self.all_cars, CARS_MD_TEMPLATE) - - def test_docs_diff(self): - dump_path = os.path.join(BASEDIR, "selfdrive", "car", "tests", "cars_dump") - dump_car_docs(dump_path) - print_car_docs_diff(dump_path) - os.remove(dump_path) diff --git a/selfdrive/debug/README.md b/selfdrive/debug/README.md deleted file mode 100644 index 83b8a994d..000000000 --- a/selfdrive/debug/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# debug scripts - -## [can_printer.py](can_printer.py) - -``` -usage: can_printer.py [-h] [--bus BUS] [--max_msg MAX_MSG] [--addr ADDR] - -simple CAN data viewer - -optional arguments: - -h, --help show this help message and exit - --bus BUS CAN bus to print out (default: 0) - --max_msg MAX_MSG max addr (default: None) - --addr ADDR -``` - -## [dump.py](dump.py) - -``` -usage: dump.py [-h] [--pipe] [--raw] [--json] [--dump-json] [--no-print] [--addr ADDR] [--values VALUES] [socket [socket ...]] - -Dump communication sockets. See cereal/services.py for a complete list of available sockets. - -positional arguments: - socket socket names to dump. defaults to all services defined in cereal - -optional arguments: - -h, --help show this help message and exit - --pipe - --raw - --json - --dump-json - --no-print - --addr ADDR - --values VALUES values to monitor (instead of entire event) -``` - -## [vw_mqb_config.py](vw_mqb_config.py) - -``` -usage: vw_mqb_config.py [-h] [--debug] {enable,show,disable} - -Shows Volkswagen EPS software and coding info, and enables or disables Heading Control -Assist (Lane Assist). Useful for enabling HCA on cars without factory Lane Assist that want -to use openpilot integrated at the CAN gateway (J533). - -positional arguments: - {enable,show,disable} - show or modify current EPS HCA config - -optional arguments: - -h, --help show this help message and exit - --debug enable ISO-TP/UDS stack debugging output - -This tool is meant to run directly on a vehicle-installed comma three, with -the openpilot/tmux processes stopped. It should also work on a separate PC with a USB- -attached comma panda. Vehicle ignition must be on. Recommend engine not be running when -making changes. Must turn ignition off and on again for any changes to take effect. -``` diff --git a/selfdrive/debug/analyze-msg-size.py b/selfdrive/debug/analyze-msg-size.py deleted file mode 100755 index 69015a6be..000000000 --- a/selfdrive/debug/analyze-msg-size.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 -import argparse -from tqdm import tqdm - -from cereal.services import SERVICE_LIST, QueueSize -from openpilot.tools.lib.logreader import LogReader - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Analyze message sizes from a log route") - parser.add_argument("route", nargs="?", default="98395b7c5b27882e/000000a8--f87e7cd255", - help="Log route to analyze (default: 98395b7c5b27882e/000000a8--f87e7cd255)") - args = parser.parse_args() - - lr = LogReader(args.route) - - szs = {} - for msg in tqdm(lr): - sz = len(msg.as_builder().to_bytes()) - msg_type = msg.which() - if msg_type not in szs: - szs[msg_type] = {'min': sz, 'max': sz, 'sum': sz, 'count': 1} - else: - szs[msg_type]['min'] = min(szs[msg_type]['min'], sz) - szs[msg_type]['max'] = max(szs[msg_type]['max'], sz) - szs[msg_type]['sum'] += sz - szs[msg_type]['count'] += 1 - - print() - print(f"{'Service':<36} {'Min (KB)':>12} {'Max (KB)':>12} {'Avg (KB)':>12} {'KB/min':>12} {'KB/sec':>12} {'Minutes in 10MB':>18} {'Seconds in Queue':>18}") - print("-" * 132) - def sort_key(x): - k, v = x - avg = v['sum'] / v['count'] - freq = SERVICE_LIST.get(k, None) - freq_val = freq.frequency if freq else 0.0 - kb_per_min = (avg * freq_val * 60) / 1024 if freq_val > 0 else 0.0 - return kb_per_min - total_kb_per_min = 0.0 - RINGBUFFER_SIZE_KB = 10 * 1024 # 10MB old default - for k, v in sorted(szs.items(), key=sort_key, reverse=True): - avg = v['sum'] / v['count'] - service = SERVICE_LIST.get(k, None) - freq_val = service.frequency if service else 0.0 - queue_size_kb = (service.queue_size / 1024) if service else 250 # default to SMALL - kb_per_min = (avg * freq_val * 60) / 1024 if freq_val > 0 else 0.0 - kb_per_sec = kb_per_min / 60 - minutes_in_buffer = RINGBUFFER_SIZE_KB / kb_per_min if kb_per_min > 0 else float('inf') - seconds_in_queue = (queue_size_kb / kb_per_sec) if kb_per_sec > 0 else float('inf') - total_kb_per_min += kb_per_min - min_str = f"{minutes_in_buffer:.2f}" if minutes_in_buffer != float('inf') else "inf" - sec_queue_str = f"{seconds_in_queue:.2f}" if seconds_in_queue != float('inf') else "inf" - print(f"{k:<36} {v['min']/1024:>12.2f} {v['max']/1024:>12.2f} {avg/1024:>12.2f} {kb_per_min:>12.2f} {kb_per_sec:>12.2f} {min_str:>18} {sec_queue_str:>18}") - - # Summary section - print() - print(f"Total usage: {total_kb_per_min / 1024:.2f} MB/min") - - # Calculate memory usage: old (10MB for all) vs new (from services.py) - OLD_SIZE = 10 * 1024 * 1024 # 10MB was the old default - old_total = len(SERVICE_LIST) * OLD_SIZE - - new_total = sum(s.queue_size for s in SERVICE_LIST.values()) - - # Count by queue size - size_counts = {QueueSize.BIG: 0, QueueSize.MEDIUM: 0, QueueSize.SMALL: 0} - for s in SERVICE_LIST.values(): - size_counts[s.queue_size] += 1 - - savings_pct = (1 - new_total / old_total) * 100 - - print() - print(f"{'Queue Size Comparison':<40}") - print("-" * 60) - print(f"{'Old (10MB default):':<30} {old_total / 1024 / 1024:>10.2f} MB") - print(f"{'New (from services.py):':<30} {new_total / 1024 / 1024:>10.2f} MB") - print(f"{'Savings:':<30} {savings_pct:>10.1f}%") - print() - print(f"{'Breakdown:':<30}") - print(f" BIG (10MB): {size_counts[QueueSize.BIG]:>3} services") - print(f" MEDIUM (2MB): {size_counts[QueueSize.MEDIUM]:>3} services") - print(f" SMALL (250KB): {size_counts[QueueSize.SMALL]:>3} services") diff --git a/selfdrive/debug/check_can_parser_performance.py b/selfdrive/debug/check_can_parser_performance.py deleted file mode 100755 index 20987b3cf..000000000 --- a/selfdrive/debug/check_can_parser_performance.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 -import numpy as np -import time -from tqdm import tqdm - -from cereal import car -from opendbc.car.tests.routes import CarTestRoute -from openpilot.selfdrive.car.tests.test_models import TestCarModelBase -from openpilot.tools.plotjuggler.juggle import DEMO_ROUTE - -N_RUNS = 10 - - -class CarModelTestCase(TestCarModelBase): - test_route = CarTestRoute(DEMO_ROUTE, None) - - -if __name__ == '__main__': - # Get CAN messages and parsers - tm = CarModelTestCase() - tm.setUpClass() - tm.setUp() - - CC = car.CarControl.new_message() - ets = [] - for _ in tqdm(range(N_RUNS)): - start_t = time.process_time_ns() - for msg in tm.can_msgs: - for cp in tm.CI.can_parsers.values(): - if cp is not None: - cp.update_strings(msg) - ets.append((time.process_time_ns() - start_t) * 1e-6) - - print(f'{len(tm.can_msgs)} CAN packets, {N_RUNS} runs') - print(f'{np.mean(ets):.2f} mean ms, {max(ets):.2f} max ms, {min(ets):.2f} min ms, {np.std(ets):.2f} std ms') - print(f'{np.mean(ets) / len(tm.can_msgs):.4f} mean ms / CAN packet') diff --git a/selfdrive/debug/check_freq.py b/selfdrive/debug/check_freq.py deleted file mode 100755 index 1765aeb86..000000000 --- a/selfdrive/debug/check_freq.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import numpy as np -import time -from collections import defaultdict, deque -from collections.abc import MutableSequence - -import cereal.messaging as messaging - - -if __name__ == "__main__": - context = messaging.Context() - poller = messaging.Poller() - - parser = argparse.ArgumentParser() - parser.add_argument("socket", type=str, nargs='*', help="socket name") - args = parser.parse_args() - - socket_names = args.socket - sockets = {} - - rcv_times: defaultdict[str, MutableSequence[float]] = defaultdict(lambda: deque(maxlen=100)) - valids: defaultdict[str, deque[bool]] = defaultdict(lambda: deque(maxlen=100)) - - t = time.monotonic() - for name in socket_names: - sock = messaging.sub_sock(name, poller=poller) - sockets[sock] = name - - prev_print = t - while True: - for socket in poller.poll(100): - msg = messaging.recv_one(socket) - if msg is None: - continue - - name = msg.which() - - t = time.monotonic() - rcv_times[name].append(msg.logMonoTime / 1e9) - valids[name].append(msg.valid) - - if t - prev_print > 1: - print() - for name in socket_names: - dts = np.diff(rcv_times[name]) - mean = np.mean(dts) - print(f"{name}: Freq {1.0 / mean:.2f} Hz, Min {np.min(dts) / mean * 100:.2f}%, Max {np.max(dts) / mean * 100:.2f}%, valid ", all(valids[name])) - - prev_print = t diff --git a/selfdrive/debug/check_lag.py b/selfdrive/debug/check_lag.py deleted file mode 100755 index 341ae79c8..000000000 --- a/selfdrive/debug/check_lag.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 - -import cereal.messaging as messaging -from cereal.services import SERVICE_LIST - -TO_CHECK = ['carState'] - - -if __name__ == "__main__": - sm = messaging.SubMaster(TO_CHECK) - - prev_t: dict[str, float] = {} - - while True: - sm.update() - - for s in TO_CHECK: - if sm.updated[s]: - t = sm.logMonoTime[s] / 1e9 - - if s in prev_t: - expected = 1.0 / (SERVICE_LIST[s].frequency) - dt = t - prev_t[s] - if dt > 10 * expected: - print(t, s, dt) - - prev_t[s] = t diff --git a/selfdrive/debug/check_timings.py b/selfdrive/debug/check_timings.py deleted file mode 100755 index fc527cd81..000000000 --- a/selfdrive/debug/check_timings.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 -import sys -import time -import numpy as np -import datetime -from collections.abc import MutableSequence -from collections import defaultdict - -import cereal.messaging as messaging - - -if __name__ == "__main__": - ts: defaultdict[str, MutableSequence[float]] = defaultdict(list) - socks = {s: messaging.sub_sock(s, conflate=False) for s in sys.argv[1:]} - try: - st = time.monotonic() - while True: - print() - for s, sock in socks.items(): - msgs = messaging.drain_sock(sock) - for m in msgs: - ts[s].append(m.logMonoTime / 1e6) - - if len(ts[s]) > 2: - d = np.diff(ts[s])[-100:] - print(f"{s:25} {np.mean(d):7.2f} {np.std(d):7.2f} {np.max(d):7.2f} {np.min(d):7.2f}") - time.sleep(1) - except KeyboardInterrupt: - print("\n") - print("="*5, "timing summary", "="*5) - for s, sock in socks.items(): - msgs = messaging.drain_sock(sock) - if len(ts[s]) > 2: - d = np.diff(ts[s]) - print(f"{s:25} {np.mean(d):7.2f} {np.std(d):7.2f} {np.max(d):7.2f} {np.min(d):7.2f}") - print("="*5, datetime.timedelta(seconds=time.monotonic()-st), "="*5) diff --git a/selfdrive/debug/dump_car_docs.py b/selfdrive/debug/dump_car_docs.py deleted file mode 100755 index f0e99cda2..000000000 --- a/selfdrive/debug/dump_car_docs.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import pickle - -from opendbc.car.docs import get_all_car_docs - - -def dump_car_docs(path): - with open(path, 'wb') as f: - pickle.dump(get_all_car_docs(), f) - print(f'Dumping car info to {path}') - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("--path", required=True) - args = parser.parse_args() - dump_car_docs(args.path) diff --git a/selfdrive/debug/print_docs_diff.py b/selfdrive/debug/print_docs_diff.py deleted file mode 100755 index c7850939f..000000000 --- a/selfdrive/debug/print_docs_diff.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env python3 -import argparse -from collections import defaultdict -import difflib -import pickle - -from opendbc.car.docs import get_all_car_docs -from opendbc.car.docs_definitions import Column - -FOOTNOTE_TAG = "{}" -STAR_ICON = '' -VIDEO_ICON = '' + \ - '' -COLUMNS = "|" + "|".join([column.value for column in Column]) + "|" -COLUMN_HEADER = "|---|---|---|{}|".format("|".join([":---:"] * (len(Column) - 3))) -ARROW_SYMBOL = "➡️" - - -def load_base_car_docs(path): - with open(path, "rb") as f: - return pickle.load(f) - - -def match_cars(base_cars, new_cars): - changes = [] - additions = [] - for new in new_cars: - # Addition if no close matches or close match already used - # Change if close match and not already used - matches = difflib.get_close_matches(new.name, [b.name for b in base_cars], cutoff=0.) - if not len(matches) or matches[0] in [c[1].name for c in changes]: - additions.append(new) - else: - changes.append((new, next(car for car in base_cars if car.name == matches[0]))) - - # Removal if base car not in changes - removals = [b for b in base_cars if b.name not in [c[1].name for c in changes]] - return changes, additions, removals - - -def build_column_diff(base_car, new_car): - row_builder = [] - for column in Column: - base_column = base_car.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) - new_column = new_car.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) - - if base_column != new_column: - row_builder.append(f"{base_column} {ARROW_SYMBOL} {new_column}") - else: - row_builder.append(new_column) - - return format_row(row_builder) - - -def format_row(builder): - return "|" + "|".join(builder) + "|" - - -def print_car_docs_diff(path): - base_car_docs = defaultdict(list) - new_car_docs = defaultdict(list) - - for car in load_base_car_docs(path): - base_car_docs[car.car_fingerprint].append(car) - for car in get_all_car_docs(): - new_car_docs[car.car_fingerprint].append(car) - - # Add new platforms to base cars so we can detect additions and removals in one pass - base_car_docs.update({car: [] for car in new_car_docs if car not in base_car_docs}) - - changes = defaultdict(list) - for base_car_model, base_cars in base_car_docs.items(): - # Match car info changes, and get additions and removals - new_cars = new_car_docs[base_car_model] - car_changes, car_additions, car_removals = match_cars(base_cars, new_cars) - - # Removals - for car_docs in car_removals: - changes["removals"].append(format_row([car_docs.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in Column])) - - # Additions - for car_docs in car_additions: - changes["additions"].append(format_row([car_docs.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in Column])) - - for new_car, base_car in car_changes: - # Column changes - row_diff = build_column_diff(base_car, new_car) - if ARROW_SYMBOL in row_diff: - changes["column"].append(row_diff) - - # Detail sentence changes - if base_car.detail_sentence != new_car.detail_sentence: - changes["detail"].append(f"- Sentence for {base_car.name} changed!\n" + - " ```diff\n" + - f" - {base_car.detail_sentence}\n" + - f" + {new_car.detail_sentence}\n" + - " ```") - - # Print diff - if any(len(c) for c in changes.values()): - markdown_builder = ["### ⚠️ This PR makes changes to [CARS.md](../blob/master/docs/CARS.md) ⚠️"] - - for title, category in (("## 🔀 Column Changes", "column"), ("## ❌ Removed", "removals"), - ("## ➕ Added", "additions"), ("## 📖 Detail Sentence Changes", "detail")): - if len(changes[category]): - markdown_builder.append(title) - if category not in ("detail",): - markdown_builder.append(COLUMNS) - markdown_builder.append(COLUMN_HEADER) - markdown_builder.extend(changes[category]) - - print("\n".join(markdown_builder)) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("--path", required=True) - args = parser.parse_args() - print_car_docs_diff(args.path) diff --git a/selfdrive/debug/touch_replay.py b/selfdrive/debug/touch_replay.py deleted file mode 100755 index 6e5ecbbe5..000000000 --- a/selfdrive/debug/touch_replay.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -import argparse - -import numpy as np -import matplotlib.pyplot as plt - -from openpilot.tools.lib.logreader import LogReader - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument('--width', default=2160, type=int) - parser.add_argument('--height', default=1080, type=int) - parser.add_argument('--route', default='rlog', type=str) - args = parser.parse_args() - - w = args.width - h = args.height - route = args.route - - fingers = [[-1, -1]] * 5 - touch_points = [] - current_slot = 0 - - lr = list(LogReader(route)) - for msg in lr: - if msg.which() == 'touch': - for event in msg.touch: - if event.type == 3 and event.code == 47: - current_slot = event.value - elif event.type == 3 and event.code == 57 and event.value == -1: - fingers[current_slot] = [-1, -1] - elif event.type == 3 and event.code == 53: - fingers[current_slot][1] = event.value - if fingers[current_slot][0] != -1: - touch_points.append(fingers[current_slot].copy()) - elif event.type == 3 and event.code == 54: - fingers[current_slot][0] = w - event.value - if fingers[current_slot][1] != -1: - touch_points.append(fingers[current_slot].copy()) - - if not touch_points: - print(f'No touch events found for {route}') - quit() - - unique_points, counts = np.unique(touch_points, axis=0, return_counts=True) - - plt.figure(figsize=(10, 3)) - plt.scatter(unique_points[:, 0], unique_points[:, 1], c=counts, s=counts * 20, edgecolors='red') - plt.colorbar() - plt.title(f'Touches for {route}') - plt.xlim(0, w) - plt.ylim(0, h) - plt.grid(True) - plt.show() diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 8161dca13..a08912fde 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -284,7 +284,7 @@ class TestOnroad: print("--------------- Memory Usage -------------------") print("------------------------------------------------") - from openpilot.selfdrive.debug.mem_usage import print_report + from openpilot.tools.scripts.mem_usage import print_report print_report(self.msgs['procLog'], self.msgs['deviceState']) offset = int(SERVICE_LIST['deviceState'].frequency * LOG_OFFSET) diff --git a/tools/CTF.md b/tools/CTF.md index 32891cd38..609c82ad5 100644 --- a/tools/CTF.md +++ b/tools/CTF.md @@ -6,7 +6,7 @@ Welcome to the first part of the comma CTF! * everything you'll need to find the flags is in the openpilot repo * grep is also your friend * first, [setup](https://github.com/commaai/openpilot/tree/master/tools#setup-your-pc) your PC - * read the docs & checkout out the tools in tools/ and selfdrive/debug/ + * read the docs & checkout out the tools in tools/ * tip: once you get the replay and UI up, start by familiarizing yourself with seeking in replay getting started diff --git a/selfdrive/debug/__init__.py b/tools/scripts/__init__.py similarity index 100% rename from selfdrive/debug/__init__.py rename to tools/scripts/__init__.py diff --git a/selfdrive/debug/can_print_changes.py b/tools/scripts/car/can_print_changes.py similarity index 98% rename from selfdrive/debug/can_print_changes.py rename to tools/scripts/car/can_print_changes.py index 97d60b2b0..c2e152e1e 100755 --- a/selfdrive/debug/can_print_changes.py +++ b/tools/scripts/car/can_print_changes.py @@ -5,7 +5,7 @@ import time from collections import defaultdict import cereal.messaging as messaging -from openpilot.selfdrive.debug.can_table import can_table +from openpilot.tools.scripts.can_table import can_table from openpilot.tools.lib.logreader import LogIterable, LogReader RED = '\033[91m' diff --git a/selfdrive/debug/can_printer.py b/tools/scripts/car/can_printer.py similarity index 100% rename from selfdrive/debug/can_printer.py rename to tools/scripts/car/can_printer.py diff --git a/selfdrive/debug/can_table.py b/tools/scripts/car/can_table.py similarity index 100% rename from selfdrive/debug/can_table.py rename to tools/scripts/car/can_table.py diff --git a/selfdrive/debug/car/clear_dtc.py b/tools/scripts/car/clear_dtc.py similarity index 100% rename from selfdrive/debug/car/clear_dtc.py rename to tools/scripts/car/clear_dtc.py diff --git a/selfdrive/debug/car/disable_ecu.py b/tools/scripts/car/disable_ecu.py similarity index 100% rename from selfdrive/debug/car/disable_ecu.py rename to tools/scripts/car/disable_ecu.py diff --git a/selfdrive/debug/car/ecu_addrs.py b/tools/scripts/car/ecu_addrs.py similarity index 100% rename from selfdrive/debug/car/ecu_addrs.py rename to tools/scripts/car/ecu_addrs.py diff --git a/selfdrive/debug/car/fw_versions.py b/tools/scripts/car/fw_versions.py similarity index 100% rename from selfdrive/debug/car/fw_versions.py rename to tools/scripts/car/fw_versions.py diff --git a/selfdrive/debug/car/hyundai_enable_radar_points.py b/tools/scripts/car/hyundai_enable_radar_points.py similarity index 100% rename from selfdrive/debug/car/hyundai_enable_radar_points.py rename to tools/scripts/car/hyundai_enable_radar_points.py diff --git a/selfdrive/debug/max_lat_accel.py b/tools/scripts/car/max_lat_accel.py similarity index 100% rename from selfdrive/debug/max_lat_accel.py rename to tools/scripts/car/max_lat_accel.py diff --git a/selfdrive/debug/measure_torque_time_to_max.py b/tools/scripts/car/measure_torque_time_to_max.py similarity index 100% rename from selfdrive/debug/measure_torque_time_to_max.py rename to tools/scripts/car/measure_torque_time_to_max.py diff --git a/selfdrive/debug/read_dtc_status.py b/tools/scripts/car/read_dtc_status.py similarity index 100% rename from selfdrive/debug/read_dtc_status.py rename to tools/scripts/car/read_dtc_status.py diff --git a/selfdrive/debug/car/toyota_eps_factor.py b/tools/scripts/car/toyota_eps_factor.py similarity index 100% rename from selfdrive/debug/car/toyota_eps_factor.py rename to tools/scripts/car/toyota_eps_factor.py diff --git a/selfdrive/debug/car/vin.py b/tools/scripts/car/vin.py similarity index 100% rename from selfdrive/debug/car/vin.py rename to tools/scripts/car/vin.py diff --git a/selfdrive/debug/car/vw_mqb_config.py b/tools/scripts/car/vw_mqb_config.py similarity index 100% rename from selfdrive/debug/car/vw_mqb_config.py rename to tools/scripts/car/vw_mqb_config.py diff --git a/selfdrive/debug/count_events.py b/tools/scripts/count_events.py similarity index 100% rename from selfdrive/debug/count_events.py rename to tools/scripts/count_events.py diff --git a/selfdrive/debug/cpu_usage_stat.py b/tools/scripts/cpu_usage_stat.py similarity index 98% rename from selfdrive/debug/cpu_usage_stat.py rename to tools/scripts/cpu_usage_stat.py index 089685103..5b72eacc6 100755 --- a/selfdrive/debug/cpu_usage_stat.py +++ b/tools/scripts/cpu_usage_stat.py @@ -8,7 +8,7 @@ System tools like top/htop can only show current cpu usage values, so I write th Calculate minumium/maximum/accumulated_average cpu usage as long term inspections. Monitor multiple processes simuteneously. Sample usage: - root@localhost:/data/openpilot$ python selfdrive/debug/cpu_usage_stat.py pandad,ubloxd + root@localhost:/data/openpilot$ python tools/scripts/cpu_usage_stat.py pandad,ubloxd ('Add monitored proc:', './pandad') ('Add monitored proc:', 'python locationd/ubloxd.py') pandad: 1.96%, min: 1.96%, max: 1.96%, acc: 1.96% diff --git a/selfdrive/debug/cycle_alerts.py b/tools/scripts/cycle_alerts.py similarity index 100% rename from selfdrive/debug/cycle_alerts.py rename to tools/scripts/cycle_alerts.py diff --git a/selfdrive/debug/debug_fw_fingerprinting_offline.py b/tools/scripts/debug_fw_fingerprinting_offline.py similarity index 100% rename from selfdrive/debug/debug_fw_fingerprinting_offline.py rename to tools/scripts/debug_fw_fingerprinting_offline.py diff --git a/selfdrive/debug/dump.py b/tools/scripts/dump.py similarity index 100% rename from selfdrive/debug/dump.py rename to tools/scripts/dump.py diff --git a/tools/scripts/fetch_image_from_route.py b/tools/scripts/fetch_image_from_route.py deleted file mode 100755 index a05371480..000000000 --- a/tools/scripts/fetch_image_from_route.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python3 -import sys - -if len(sys.argv) < 4: - print(f"{sys.argv[0]} [front|wide|driver]") - print('example: ./fetch_image_from_route.py "02c45f73a2e5c6e9|2020-06-01--18-03-08" 3 500 driver') - exit(0) - -cameras = { - "front": "cameras", - "wide": "ecameras", - "driver": "dcameras" -} - -import requests -from PIL import Image -from openpilot.tools.lib.auth_config import get_token -from openpilot.tools.lib.framereader import FrameReader - -jwt = get_token() - -route = sys.argv[1] -segment = int(sys.argv[2]) -frame = int(sys.argv[3]) -camera = cameras[sys.argv[4]] if len(sys.argv) > 4 and sys.argv[4] in cameras else "cameras" - -url = f'https://api.commadotai.com/v1/route/{route}/files' -r = requests.get(url, headers={"Authorization": f"JWT {jwt}"}, timeout=10) -assert r.status_code == 200 -print("got api response") - -segments = r.json()[camera] -if segment >= len(segments): - raise Exception(f"segment {segment} not found, got {len(segments)} segments") - -fr = FrameReader(segments[segment]) -if frame >= fr.frame_count: - raise Exception(f"frame {frame} not found, got {fr.frame_count} frames") - -im = Image.fromarray(fr.get(frame)) -fn = f"uxxx_{route.replace('|', '_')}_{segment}_{frame}.png" -im.save(fn) -print(f"saved {fn}") diff --git a/selfdrive/debug/filter_log_message.py b/tools/scripts/filter_log_message.py similarity index 100% rename from selfdrive/debug/filter_log_message.py rename to tools/scripts/filter_log_message.py diff --git a/selfdrive/debug/fingerprint_from_route.py b/tools/scripts/fingerprint_from_route.py similarity index 100% rename from selfdrive/debug/fingerprint_from_route.py rename to tools/scripts/fingerprint_from_route.py diff --git a/selfdrive/debug/fuzz_fw_fingerprint.py b/tools/scripts/fuzz_fw_fingerprint.py similarity index 100% rename from selfdrive/debug/fuzz_fw_fingerprint.py rename to tools/scripts/fuzz_fw_fingerprint.py diff --git a/selfdrive/debug/get_fingerprint.py b/tools/scripts/get_fingerprint.py similarity index 100% rename from selfdrive/debug/get_fingerprint.py rename to tools/scripts/get_fingerprint.py diff --git a/selfdrive/debug/live_cpu_and_temp.py b/tools/scripts/live_cpu_and_temp.py similarity index 100% rename from selfdrive/debug/live_cpu_and_temp.py rename to tools/scripts/live_cpu_and_temp.py diff --git a/selfdrive/debug/mem_usage.py b/tools/scripts/mem_usage.py similarity index 100% rename from selfdrive/debug/mem_usage.py rename to tools/scripts/mem_usage.py diff --git a/selfdrive/debug/print_flags.py b/tools/scripts/print_flags.py similarity index 100% rename from selfdrive/debug/print_flags.py rename to tools/scripts/print_flags.py diff --git a/tools/profiling/clpeak/.gitignore b/tools/scripts/profiling/clpeak/.gitignore similarity index 100% rename from tools/profiling/clpeak/.gitignore rename to tools/scripts/profiling/clpeak/.gitignore diff --git a/tools/profiling/clpeak/build.sh b/tools/scripts/profiling/clpeak/build.sh similarity index 100% rename from tools/profiling/clpeak/build.sh rename to tools/scripts/profiling/clpeak/build.sh diff --git a/tools/profiling/clpeak/no_print.patch b/tools/scripts/profiling/clpeak/no_print.patch similarity index 100% rename from tools/profiling/clpeak/no_print.patch rename to tools/scripts/profiling/clpeak/no_print.patch diff --git a/tools/profiling/clpeak/run_continuously.patch b/tools/scripts/profiling/clpeak/run_continuously.patch similarity index 100% rename from tools/profiling/clpeak/run_continuously.patch rename to tools/scripts/profiling/clpeak/run_continuously.patch diff --git a/tools/profiling/ftrace.sh b/tools/scripts/profiling/ftrace.sh similarity index 100% rename from tools/profiling/ftrace.sh rename to tools/scripts/profiling/ftrace.sh diff --git a/tools/profiling/palanteer/.gitignore b/tools/scripts/profiling/palanteer/.gitignore similarity index 100% rename from tools/profiling/palanteer/.gitignore rename to tools/scripts/profiling/palanteer/.gitignore diff --git a/tools/profiling/palanteer/setup.sh b/tools/scripts/profiling/palanteer/setup.sh similarity index 100% rename from tools/profiling/palanteer/setup.sh rename to tools/scripts/profiling/palanteer/setup.sh diff --git a/tools/profiling/perfetto/.gitignore b/tools/scripts/profiling/perfetto/.gitignore similarity index 100% rename from tools/profiling/perfetto/.gitignore rename to tools/scripts/profiling/perfetto/.gitignore diff --git a/tools/profiling/perfetto/build.sh b/tools/scripts/profiling/perfetto/build.sh similarity index 100% rename from tools/profiling/perfetto/build.sh rename to tools/scripts/profiling/perfetto/build.sh diff --git a/tools/profiling/perfetto/copy.sh b/tools/scripts/profiling/perfetto/copy.sh similarity index 100% rename from tools/profiling/perfetto/copy.sh rename to tools/scripts/profiling/perfetto/copy.sh diff --git a/tools/profiling/perfetto/record.sh b/tools/scripts/profiling/perfetto/record.sh similarity index 100% rename from tools/profiling/perfetto/record.sh rename to tools/scripts/profiling/perfetto/record.sh diff --git a/tools/profiling/perfetto/server.sh b/tools/scripts/profiling/perfetto/server.sh similarity index 100% rename from tools/profiling/perfetto/server.sh rename to tools/scripts/profiling/perfetto/server.sh diff --git a/tools/profiling/perfetto/traces.sh b/tools/scripts/profiling/perfetto/traces.sh similarity index 100% rename from tools/profiling/perfetto/traces.sh rename to tools/scripts/profiling/perfetto/traces.sh diff --git a/tools/profiling/py-spy/profile.sh b/tools/scripts/profiling/py-spy/profile.sh similarity index 100% rename from tools/profiling/py-spy/profile.sh rename to tools/scripts/profiling/py-spy/profile.sh diff --git a/tools/profiling/snapdragon/.gitignore b/tools/scripts/profiling/snapdragon/.gitignore similarity index 100% rename from tools/profiling/snapdragon/.gitignore rename to tools/scripts/profiling/snapdragon/.gitignore diff --git a/tools/profiling/snapdragon/README.md b/tools/scripts/profiling/snapdragon/README.md similarity index 100% rename from tools/profiling/snapdragon/README.md rename to tools/scripts/profiling/snapdragon/README.md diff --git a/tools/profiling/snapdragon/setup-agnos.sh b/tools/scripts/profiling/snapdragon/setup-agnos.sh similarity index 100% rename from tools/profiling/snapdragon/setup-agnos.sh rename to tools/scripts/profiling/snapdragon/setup-agnos.sh diff --git a/tools/profiling/snapdragon/setup-profiler.sh b/tools/scripts/profiling/snapdragon/setup-profiler.sh similarity index 100% rename from tools/profiling/snapdragon/setup-profiler.sh rename to tools/scripts/profiling/snapdragon/setup-profiler.sh diff --git a/tools/profiling/watch-irqs.sh b/tools/scripts/profiling/watch-irqs.sh similarity index 100% rename from tools/profiling/watch-irqs.sh rename to tools/scripts/profiling/watch-irqs.sh diff --git a/selfdrive/debug/qlog_size.py b/tools/scripts/qlog_size.py similarity index 100% rename from selfdrive/debug/qlog_size.py rename to tools/scripts/qlog_size.py diff --git a/selfdrive/debug/run_process_on_route.py b/tools/scripts/run_process_on_route.py similarity index 100% rename from selfdrive/debug/run_process_on_route.py rename to tools/scripts/run_process_on_route.py diff --git a/tools/scripts/save_ubloxraw_stream.py b/tools/scripts/save_ubloxraw_stream.py deleted file mode 100755 index b5354a783..000000000 --- a/tools/scripts/save_ubloxraw_stream.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -import sys -from openpilot.common.basedir import BASEDIR -from openpilot.tools.lib.logreader import LogReader - -os.environ['BASEDIR'] = BASEDIR - - -def get_arg_parser(): - parser = argparse.ArgumentParser( - description="Unlogging and save to file", - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - - parser.add_argument("route", type=(lambda x: x.replace("#", "|")), nargs="?", - help="The route whose messages will be published.") - parser.add_argument("--out_path", nargs='?', default='/data/ubloxRaw.stream', - help="Output pickle file path") - return parser - - -def main(): - args = get_arg_parser().parse_args(sys.argv[1:]) - - lr = LogReader(args.route) - - with open(args.out_path, 'wb') as f: - try: - done = False - i = 0 - while not done: - msg = next(lr) - if not msg: - break - smsg = msg.as_builder() - typ = smsg.which() - if typ == 'ubloxRaw': - f.write(smsg.to_bytes()) - i += 1 - except StopIteration: - print('All done') - print(f'Writed {i} msgs') - - -if __name__ == "__main__": - main() diff --git a/selfdrive/debug/set_car_params.py b/tools/scripts/set_car_params.py similarity index 100% rename from selfdrive/debug/set_car_params.py rename to tools/scripts/set_car_params.py diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/tools/scripts/test_fw_query_on_routes.py similarity index 100% rename from selfdrive/debug/test_fw_query_on_routes.py rename to tools/scripts/test_fw_query_on_routes.py diff --git a/selfdrive/debug/uiview.py b/tools/scripts/uiview.py similarity index 100% rename from selfdrive/debug/uiview.py rename to tools/scripts/uiview.py diff --git a/tools/scripts/watch_timings.py b/tools/scripts/watch_timings.py new file mode 100755 index 000000000..874720dd2 --- /dev/null +++ b/tools/scripts/watch_timings.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +import argparse +import datetime +import time +from collections import deque +from dataclasses import dataclass, field + +import numpy as np + +import cereal.messaging as messaging +from cereal.services import SERVICE_LIST + + +@dataclass +class ServiceTiming: + times: list[float] = field(default_factory=list) + window: deque[float] = field(default_factory=lambda: deque(maxlen=100)) + valids: deque[bool] = field(default_factory=lambda: deque(maxlen=100)) + lag_events: list[tuple[float, float]] = field(default_factory=list) + + def add(self, mono_time: float, valid: bool, expected_interval: float | None, lag_threshold: float) -> None: + if self.times: + dt = mono_time - self.times[-1] + self.window.append(dt) + if expected_interval is not None and dt > lag_threshold * expected_interval: + self.lag_events.append((mono_time, dt)) + + self.times.append(mono_time) + self.valids.append(valid) + + def intervals(self, latest_only: bool) -> np.ndarray: + if latest_only: + return np.array(self.window) + return np.diff(self.times) + + +def format_row(name: str, timing: ServiceTiming, latest_only: bool) -> str: + dts = timing.intervals(latest_only) + if len(dts) == 0: + return f"{name:25} waiting for messages" + + mean = np.mean(dts) + hz = 1.0 / mean if mean > 0 else 0.0 + valid = all(timing.valids) if timing.valids else False + return f"{name:25} {hz:8.2f}Hz {mean * 1e3:8.2f}ms {np.std(dts) * 1e3:8.2f}ms {np.max(dts) * 1e3:8.2f}ms {np.min(dts) * 1e3:8.2f}ms valid={valid}" + + +def print_lag_events(name: str, timing: ServiceTiming, printed_lags: dict[str, int]) -> None: + start = printed_lags.get(name, 0) + for mono_time, dt in timing.lag_events[start:]: + print(f"{mono_time:.3f} {name} lag {dt:.3f}s", flush=True) + printed_lags[name] = len(timing.lag_events) + + +def monitor_services(socket_names: list[str], print_interval: float, lag_threshold: float, lag_only: bool) -> None: + sockets = {name: messaging.sub_sock(name, conflate=False) for name in socket_names} + timings = {name: ServiceTiming() for name in socket_names} + printed_lags: dict[str, int] = {} + + start_time = time.monotonic() + last_print = start_time + + try: + while True: + for name, sock in sockets.items(): + for msg in messaging.drain_sock(sock): + expected_interval = 1.0 / SERVICE_LIST[name].frequency if name in SERVICE_LIST else None + timings[name].add(msg.logMonoTime / 1e9, msg.valid, expected_interval, lag_threshold) + + now = time.monotonic() + if now - last_print < print_interval: + time.sleep(0.01) + continue + + if not lag_only: + print(flush=True) + print(f"{'service':25} {'freq':>10} {'mean':>10} {'std':>10} {'max':>10} {'min':>10} valid", flush=True) + for name in socket_names: + print(format_row(name, timings[name], latest_only=True), flush=True) + + for name in socket_names: + print_lag_events(name, timings[name], printed_lags) + + last_print = now + except KeyboardInterrupt: + print("\n", flush=True) + print("=" * 5, "timing summary", "=" * 5, flush=True) + print(f"{'service':25} {'freq':>10} {'mean':>10} {'std':>10} {'max':>10} {'min':>10} valid", flush=True) + for name in socket_names: + print(format_row(name, timings[name], latest_only=False), flush=True) + print("=" * 5, datetime.timedelta(seconds=time.monotonic() - start_time), "=" * 5, flush=True) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Check live service timing, frequency, validity, and lag") + parser.add_argument("socket", nargs="*", default=["carState"], help="service/socket name") + parser.add_argument("--lag-threshold", type=float, default=10.0, help="report intervals above this multiple of the expected service interval") + parser.add_argument("--lag-only", action="store_true", help="only print lag events") + parser.add_argument("--print-interval", type=float, default=1.0, help="seconds between table updates") + args = parser.parse_args() + + monitor_services(args.socket, args.print_interval, args.lag_threshold, args.lag_only)