|  |  |  | @ -1,8 +1,9 @@ | 
			
		
	
		
			
				
					|  |  |  |  | #!/usr/bin/env python3 | 
			
		
	
		
			
				
					|  |  |  |  | import bz2 | 
			
		
	
		
			
				
					|  |  |  |  | from functools import partial | 
			
		
	
		
			
				
					|  |  |  |  | import multiprocessing | 
			
		
	
		
			
				
					|  |  |  |  | import capnp | 
			
		
	
		
			
				
					|  |  |  |  | import enum | 
			
		
	
		
			
				
					|  |  |  |  | import itertools | 
			
		
	
		
			
				
					|  |  |  |  | import numpy as np | 
			
		
	
		
			
				
					|  |  |  |  | import os | 
			
		
	
		
			
				
					|  |  |  |  | import pathlib | 
			
		
	
	
		
			
				
					|  |  |  | @ -89,7 +90,7 @@ def parse_slice(sr: SegmentRange): | 
			
		
	
		
			
				
					|  |  |  |  |   s = create_slice_from_string(sr._slice) | 
			
		
	
		
			
				
					|  |  |  |  |   return segs[s] if isinstance(s, slice) else [segs[s]] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG, **kwargs): | 
			
		
	
		
			
				
					|  |  |  |  | def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG): | 
			
		
	
		
			
				
					|  |  |  |  |   segs = parse_slice(sr) | 
			
		
	
		
			
				
					|  |  |  |  |   route = Route(sr.route_name) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  |  | @ -99,40 +100,39 @@ def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG, **kwargs): | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   assert not len(invalid_segs), f"Some of the requested segments are not available: {invalid_segs}" | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   for seg in segs: | 
			
		
	
		
			
				
					|  |  |  |  |     yield _LogFileReader(log_paths[seg], **kwargs) | 
			
		
	
		
			
				
					|  |  |  |  |   return [(log_paths[seg]) for seg in segs] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def internal_source(sr: SegmentRange, mode=ReadMode.RLOG, **kwargs): | 
			
		
	
		
			
				
					|  |  |  |  | def internal_source(sr: SegmentRange, mode=ReadMode.RLOG): | 
			
		
	
		
			
				
					|  |  |  |  |   segs = parse_slice(sr) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   for seg in segs: | 
			
		
	
		
			
				
					|  |  |  |  |     yield _LogFileReader(f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2", **kwargs) | 
			
		
	
		
			
				
					|  |  |  |  |   return [f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2" for seg in segs] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG, **kwargs): | 
			
		
	
		
			
				
					|  |  |  |  | def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG): | 
			
		
	
		
			
				
					|  |  |  |  |   segs = parse_slice(sr) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   for seg in segs: | 
			
		
	
		
			
				
					|  |  |  |  |     yield _LogFileReader(get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog'), **kwargs) | 
			
		
	
		
			
				
					|  |  |  |  |   return [get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog') for seg in segs] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def direct_source(file_or_url, **kwargs): | 
			
		
	
		
			
				
					|  |  |  |  |   yield _LogFileReader(file_or_url, **kwargs) | 
			
		
	
		
			
				
					|  |  |  |  | def direct_source(file_or_url): | 
			
		
	
		
			
				
					|  |  |  |  |   return [file_or_url] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def auto_source(*args, **kwargs): | 
			
		
	
		
			
				
					|  |  |  |  | def auto_source(*args): | 
			
		
	
		
			
				
					|  |  |  |  |   # Automatically determine viable source | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   try: | 
			
		
	
		
			
				
					|  |  |  |  |     next(internal_source(*args, **kwargs)) | 
			
		
	
		
			
				
					|  |  |  |  |     return internal_source(*args, **kwargs) | 
			
		
	
		
			
				
					|  |  |  |  |     identifiers = internal_source(*args) | 
			
		
	
		
			
				
					|  |  |  |  |     _LogFileReader(identifiers[0]) | 
			
		
	
		
			
				
					|  |  |  |  |     return internal_source(*args) | 
			
		
	
		
			
				
					|  |  |  |  |   except Exception: | 
			
		
	
		
			
				
					|  |  |  |  |     pass | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   try: | 
			
		
	
		
			
				
					|  |  |  |  |     next(openpilotci_source(*args, **kwargs)) | 
			
		
	
		
			
				
					|  |  |  |  |     return openpilotci_source(*args, **kwargs) | 
			
		
	
		
			
				
					|  |  |  |  |     identifiers = openpilotci_source(*args) | 
			
		
	
		
			
				
					|  |  |  |  |     _LogFileReader(identifiers[0]) | 
			
		
	
		
			
				
					|  |  |  |  |     return openpilotci_source(*args) | 
			
		
	
		
			
				
					|  |  |  |  |   except Exception: | 
			
		
	
		
			
				
					|  |  |  |  |     pass | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   return comma_api_source(*args, **kwargs) | 
			
		
	
		
			
				
					|  |  |  |  |   return comma_api_source(*args) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def parse_useradmin(identifier): | 
			
		
	
		
			
				
					|  |  |  |  |   if "useradmin.comma.ai" in identifier: | 
			
		
	
	
		
			
				
					|  |  |  | @ -161,22 +161,22 @@ def parse_indirect(identifier): | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | class LogReader: | 
			
		
	
		
			
				
					|  |  |  |  |   def _logreaders_from_identifier(self, identifier: str | List[str]): | 
			
		
	
		
			
				
					|  |  |  |  |   def _parse_identifiers(self, identifier: str | List[str]): | 
			
		
	
		
			
				
					|  |  |  |  |     if isinstance(identifier, list): | 
			
		
	
		
			
				
					|  |  |  |  |       return [LogReader(i) for i in identifier] | 
			
		
	
		
			
				
					|  |  |  |  |       return [i for j in identifier for i in self._parse_identifiers(j)] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     parsed, source, is_indirect = parse_indirect(identifier) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     if not is_indirect: | 
			
		
	
		
			
				
					|  |  |  |  |       direct_parsed = parse_direct(identifier) | 
			
		
	
		
			
				
					|  |  |  |  |       if direct_parsed is not None: | 
			
		
	
		
			
				
					|  |  |  |  |         return direct_source(identifier, sort_by_time=self.sort_by_time) | 
			
		
	
		
			
				
					|  |  |  |  |         return direct_source(identifier) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     sr = SegmentRange(parsed) | 
			
		
	
		
			
				
					|  |  |  |  |     mode = self.default_mode if sr.selector is None else ReadMode(sr.selector) | 
			
		
	
		
			
				
					|  |  |  |  |     source = self.default_source if source is None else source | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     return source(sr, mode, sort_by_time=self.sort_by_time, only_union_types=self.only_union_types) | 
			
		
	
		
			
				
					|  |  |  |  |     return source(sr, mode) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   def __init__(self, identifier: str | List[str], default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False, only_union_types=False): | 
			
		
	
		
			
				
					|  |  |  |  |     self.default_mode = default_mode | 
			
		
	
	
		
			
				
					|  |  |  | @ -190,14 +190,22 @@ class LogReader: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   def __iter__(self): | 
			
		
	
		
			
				
					|  |  |  |  |     self.reset() | 
			
		
	
		
			
				
					|  |  |  |  |     return self | 
			
		
	
		
			
				
					|  |  |  |  |     for identifier in self.logreader_identifiers: | 
			
		
	
		
			
				
					|  |  |  |  |       yield from _LogFileReader(identifier) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   def __next__(self): | 
			
		
	
		
			
				
					|  |  |  |  |     return next(self.chain) | 
			
		
	
		
			
				
					|  |  |  |  |   def _run_on_segment(self, func, identifier): | 
			
		
	
		
			
				
					|  |  |  |  |     lr = _LogFileReader(identifier) | 
			
		
	
		
			
				
					|  |  |  |  |     return func(lr) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   def run_across_segments(self, num_processes, func): | 
			
		
	
		
			
				
					|  |  |  |  |     with multiprocessing.Pool(num_processes) as pool: | 
			
		
	
		
			
				
					|  |  |  |  |       ret = [] | 
			
		
	
		
			
				
					|  |  |  |  |       for p in pool.map(partial(self._run_on_segment, func), self.logreader_identifiers): | 
			
		
	
		
			
				
					|  |  |  |  |         ret.extend(p) | 
			
		
	
		
			
				
					|  |  |  |  |       return ret | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   def reset(self): | 
			
		
	
		
			
				
					|  |  |  |  |     self.lrs = self._logreaders_from_identifier(self.identifier) | 
			
		
	
		
			
				
					|  |  |  |  |     self.chain = itertools.chain(*self.lrs) | 
			
		
	
		
			
				
					|  |  |  |  |     self.logreader_identifiers = self._parse_identifiers(self.identifier) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   @staticmethod | 
			
		
	
		
			
				
					|  |  |  |  |   def from_bytes(dat): | 
			
		
	
	
		
			
				
					|  |  |  | 
 |