plotjuggler: auto fallback to qlogs with prompt (#31110)

* qlog fallback

* show a warning for that

* qlog fallback

* fix

* wording
old-commit-hash: 65490bb3c5
chrysler-long2
Justin Newberry 1 year ago committed by GitHub
parent 0ae82f96c6
commit c5c522c984
  1. 2
      tools/lib/helpers.py
  2. 62
      tools/lib/logreader.py
  3. 4
      tools/plotjuggler/juggle.py

@ -11,7 +11,7 @@ class RE:
SEGMENT_NAME = r'{}(?:--|/)(?P<segment_num>[0-9]+)'.format(ROUTE_NAME) SEGMENT_NAME = r'{}(?:--|/)(?P<segment_num>[0-9]+)'.format(ROUTE_NAME)
INDEX = r'-?[0-9]+' INDEX = r'-?[0-9]+'
SLICE = r'(?P<start>{})?:?(?P<end>{})?:?(?P<step>{})?'.format(INDEX, INDEX, INDEX) SLICE = r'(?P<start>{})?:?(?P<end>{})?:?(?P<step>{})?'.format(INDEX, INDEX, INDEX)
SEGMENT_RANGE = r'{}(?:--|/)?(?P<slice>({}))?/?(?P<selector>([qr]))?'.format(ROUTE_NAME, SLICE) SEGMENT_RANGE = r'{}(?:--|/)?(?P<slice>({}))?/?(?P<selector>([qra]))?'.format(ROUTE_NAME, SLICE)
BOOTLOG_NAME = ROUTE_NAME BOOTLOG_NAME = ROUTE_NAME
EXPLORER_FILE = r'^(?P<segment_name>{})--(?P<file_name>[a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME) EXPLORER_FILE = r'^(?P<segment_name>{})--(?P<file_name>[a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME)

@ -16,6 +16,7 @@ from typing import Iterable, Iterator, List, Type
from urllib.parse import parse_qs, urlparse from urllib.parse import parse_qs, urlparse
from cereal import log as capnp_log from cereal import log as capnp_log
from openpilot.common.swaglog import cloudlog
from openpilot.tools.lib.comma_car_segments import get_url as get_comma_segments_url from openpilot.tools.lib.comma_car_segments import get_url as get_comma_segments_url
from openpilot.tools.lib.openpilotci import get_url from openpilot.tools.lib.openpilotci import get_url
from openpilot.tools.lib.filereader import FileReader, file_exists from openpilot.tools.lib.filereader import FileReader, file_exists
@ -71,8 +72,8 @@ class _LogFileReader:
class ReadMode(enum.StrEnum): class ReadMode(enum.StrEnum):
RLOG = "r" # only read rlogs RLOG = "r" # only read rlogs
QLOG = "q" # only read qlogs QLOG = "q" # only read qlogs
#AUTO = "a" # default to rlogs, fallback to qlogs, not supported yet AUTO = "a" # default to rlogs, fallback to qlogs
AUTO_INTERACIVE = "i" # default to rlogs, fallback to qlogs with a prompt from the user
def create_slice_from_string(s: str): def create_slice_from_string(s: str):
m = re.fullmatch(RE.SLICE, s) m = re.fullmatch(RE.SLICE, s)
@ -86,31 +87,60 @@ def create_slice_from_string(s: str):
return start return start
return slice(start, end, step) return slice(start, end, step)
def auto_strategy(rlog_paths, qlog_paths, interactive):
# auto select logs based on availability
if any(rlog is None or not file_exists(rlog) for rlog in rlog_paths):
if interactive:
if input("Some rlogs were not found, would you like to fallback to qlogs for those segments? (y/n) ").lower() != "y":
return rlog_paths
else:
cloudlog.warning("Some rlogs were not found, falling back to qlogs for those segments...")
return [rlog if (rlog is not None and file_exists(rlog)) else (qlog if (qlog is not None and file_exists(qlog)) else None)
for (rlog, qlog) in zip(rlog_paths, qlog_paths, strict=True)]
return rlog_paths
def apply_strategy(mode: ReadMode, rlog_paths, qlog_paths):
if mode == ReadMode.RLOG:
return rlog_paths
elif mode == ReadMode.QLOG:
return qlog_paths
elif mode == ReadMode.AUTO:
return auto_strategy(rlog_paths, qlog_paths, False)
elif mode == ReadMode.AUTO_INTERACIVE:
return auto_strategy(rlog_paths, qlog_paths, True)
def parse_slice(sr: SegmentRange, route: Route): def parse_slice(sr: SegmentRange, route: Route):
segs = np.arange(route.max_seg_number+1) segs = np.arange(route.max_seg_number+1)
s = create_slice_from_string(sr._slice) s = create_slice_from_string(sr._slice)
return segs[s] if isinstance(s, slice) else [segs[s]] return segs[s] if isinstance(s, slice) else [segs[s]]
def comma_api_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): def comma_api_source(sr: SegmentRange, route: Route, mode: ReadMode):
segs = parse_slice(sr, route) segs = parse_slice(sr, route)
log_paths = route.log_paths() if mode == ReadMode.RLOG else route.qlog_paths() rlog_paths = [route.log_paths()[seg] for seg in segs]
qlog_paths = [route.log_paths()[seg] for seg in segs]
invalid_segs = [seg for seg in segs if log_paths[seg] is None] return apply_strategy(mode, rlog_paths, qlog_paths)
assert not len(invalid_segs), f"Some of the requested segments are not available: {invalid_segs}" def internal_source(sr: SegmentRange, route: Route, mode: ReadMode):
segs = parse_slice(sr, route)
return [(log_paths[seg]) for seg in segs] def get_internal_url(sr: SegmentRange, seg, file):
return f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{file}.bz2"
def internal_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): rlog_paths = [get_internal_url(sr, seg, "rlog") for seg in segs]
segs = parse_slice(sr, route) qlog_paths = [get_internal_url(sr, seg, "qlog") for seg in segs]
return [f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2" for seg in segs] return apply_strategy(mode, rlog_paths, qlog_paths)
def openpilotci_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): def openpilotci_source(sr: SegmentRange, route: Route, mode: ReadMode):
segs = parse_slice(sr, route) segs = parse_slice(sr, route)
return [get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog') for seg in segs] rlog_paths = [get_url(sr.route_name, seg, "rlog") for seg in segs]
qlog_paths = [get_url(sr.route_name, seg, "qlog") for seg in segs]
return apply_strategy(mode, rlog_paths, qlog_paths)
def comma_car_segments_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): def comma_car_segments_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG):
segs = parse_slice(sr, route) segs = parse_slice(sr, route)
@ -120,10 +150,13 @@ def comma_car_segments_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG
def direct_source(file_or_url): def direct_source(file_or_url):
return [file_or_url] return [file_or_url]
def get_invalid_files(files):
return [f for f in files if f is None or not file_exists(f)]
def check_source(source, *args): def check_source(source, *args):
try: try:
files = source(*args) files = source(*args)
assert all(file_exists(f) for f in files) assert len(get_invalid_files(files)) == 0
return True, files return True, files
except Exception: except Exception:
return False, None return False, None
@ -209,6 +242,9 @@ class LogReader:
def reset(self): def reset(self):
self.logreader_identifiers = self._parse_identifiers(self.identifier) self.logreader_identifiers = self._parse_identifiers(self.identifier)
invalid_count = len(get_invalid_files(self.logreader_identifiers))
assert invalid_count == 0, f"{invalid_count}/{len(self.logreader_identifiers)} invalid log(s) found, please ensure all logs \
are uploaded or auto fallback to qlogs with '/a' selector at the end of the route name."
@staticmethod @staticmethod
def from_bytes(dat): def from_bytes(dat):

@ -13,7 +13,7 @@ from functools import partial
from openpilot.common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
from openpilot.tools.lib.helpers import save_log from openpilot.tools.lib.helpers import save_log
from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.logreader import LogReader, ReadMode
juggle_dir = os.path.dirname(os.path.realpath(__file__)) juggle_dir = os.path.dirname(os.path.realpath(__file__))
@ -73,7 +73,7 @@ def process(can, lr):
return [d for d in lr if can or d.which() not in ['can', 'sendcan']] return [d for d in lr if can or d.which() not in ['can', 'sendcan']]
def juggle_route(route_or_segment_name, can, layout, dbc=None): def juggle_route(route_or_segment_name, can, layout, dbc=None):
sr = LogReader(route_or_segment_name) sr = LogReader(route_or_segment_name, default_mode=ReadMode.AUTO_INTERACIVE)
all_data = sr.run_across_segments(24, partial(process, can)) all_data = sr.run_across_segments(24, partial(process, can))

Loading…
Cancel
Save