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)