#!/usr/bin/env python3
import argparse
import zstandard as zstd
from collections import defaultdict
import matplotlib . pyplot as plt
from cereal . services import SERVICE_LIST
from openpilot . system . loggerd . uploader import LOG_COMPRESSION_LEVEL
from openpilot . tools . lib . logreader import LogReader
from tqdm import tqdm
MIN_SIZE = 0.5 # Percent size of total to show as separate entry
def make_pie ( msgs , typ ) :
msgs_by_type = defaultdict ( list )
for m in msgs :
msgs_by_type [ m . which ( ) ] . append ( m . as_builder ( ) . to_bytes ( ) )
total = len ( zstd . compress ( b " " . join ( [ m . as_builder ( ) . to_bytes ( ) for m in msgs ] ) , LOG_COMPRESSION_LEVEL ) )
uncompressed_total = len ( b " " . join ( [ m . as_builder ( ) . to_bytes ( ) for m in msgs ] ) )
length_by_type = { k : len ( b " " . join ( v ) ) for k , v in msgs_by_type . items ( ) }
# calculate compressed size by calculating diff when removed from the segment
compressed_length_by_type = { }
for k in tqdm ( msgs_by_type . keys ( ) , desc = " Compressing " ) :
compressed_length_by_type [ k ] = total - len ( zstd . compress ( b " " . join ( [ m . as_builder ( ) . to_bytes ( ) for m in msgs if m . which ( ) != k ] ) , LOG_COMPRESSION_LEVEL ) )
sizes = sorted ( compressed_length_by_type . items ( ) , key = lambda kv : kv [ 1 ] )
print ( " name - comp. size (uncomp. size) " )
for ( name , sz ) in sizes :
print ( f " { name : <22 } - { sz / 1024 : .2f } kB ( { length_by_type [ name ] / 1024 : .2f } kB) " )
print ( )
print ( f " { typ } - Real total { total / 1024 : .2f } kB " )
print ( f " { typ } - Breakdown total { sum ( compressed_length_by_type . values ( ) ) / 1024 : .2f } kB " )
print ( f " { typ } - Uncompressed total { uncompressed_total / 1024 / 1024 : .2f } MB " )
sizes_large = [ ( k , sz ) for ( k , sz ) in sizes if sz > = total * MIN_SIZE / 100 ]
sizes_large + = [ ( ' other ' , sum ( sz for ( _ , sz ) in sizes if sz < total * MIN_SIZE / 100 ) ) ]
labels , sizes = zip ( * sizes_large , strict = True )
plt . figure ( )
plt . title ( f " { typ } " )
plt . pie ( sizes , labels = labels , autopct = ' %1.1f %% ' )
if __name__ == " __main__ " :
parser = argparse . ArgumentParser ( description = ' View log size breakdown by message type ' )
parser . add_argument ( ' route ' , help = ' route to use ' )
parser . add_argument ( ' --as-qlog ' , action = ' store_true ' , help = ' decimate rlog using latest decimation factors ' )
args = parser . parse_args ( )
msgs = list ( LogReader ( args . route ) )
if args . as_qlog :
new_msgs = [ ]
msg_cnts : dict [ str , int ] = defaultdict ( int )
for msg in msgs :
msg_which = msg . which ( )
if msg . which ( ) in ( " initData " , " sentinel " ) :
new_msgs . append ( msg )
continue
if msg_which not in SERVICE_LIST :
continue
decimation = SERVICE_LIST [ msg_which ] . decimation
if decimation is not None and msg_cnts [ msg_which ] % decimation == 0 :
new_msgs . append ( msg )
msg_cnts [ msg_which ] + = 1
msgs = new_msgs
make_pie ( msgs , ' qlog ' )
plt . show ( )