Skip to content

Commit 152e625

Browse files
Omri Sarigkartben
authored andcommitted
scripts/logging: Handle partly read packets
The current implementation of the serial log parser is running in a loop - every 2 seconds it checks if there is any information ready to be read, and in this case, it reads this information and parses it. In case the last packet of the information is not fully available, the script will read only the first part of the packet, and will fail when parsing, ending the run with an error. This should not the be the case, as it is not an error to have only part of the packet in the buffer. This commit fixes this problem - now, instead of failing because the parser does not have enough information, the parser will parse all the full packets it have, and keep the last, partial packet in the queue, to be parsed together with the next chunk of data. This is done by updating the log parsers to return the amount of parsed data when trying to parse the information, and updating the calling scripts to correctly handle this new return value. Additionally, the parser is now quietly failing in case of having a partial message, and throw an exception for any other kind of error in the parsing (instead of returning a boolean return code). In addition to the partial packet handling, the current commit also do the following, minor improvements: * parserlib now fails by throwing an exception, instead of exiting - this is done to make it possible to choose a different handling for the errors from the calling code, if needed. * The dictionary and parser are now created before the parse operation. This makes the uart parser more efficient, and also removes the previously duplicated messages of build id, target architecture and endianess (which was printed every time new information was received from the UART). Signed-off-by: Omri Sarig <[email protected]>
1 parent cb94b1b commit 152e625

File tree

5 files changed

+144
-59
lines changed

5 files changed

+144
-59
lines changed

scripts/logging/dictionary/dictionary_parser/log_parser_v1.py

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,18 @@ def print_hexdump(hex_data, prefix_len, color):
255255
print(f"{color}%s%s%s|%s{Fore.RESET}" % ((" " * prefix_len),
256256
hex_vals, hex_padding, chr_vals))
257257

258+
def get_full_msg_hdr_size(self):
259+
"""Get the size of the full message header"""
260+
return struct.calcsize(self.fmt_msg_type) + \
261+
struct.calcsize(self.fmt_msg_hdr) + \
262+
struct.calcsize(self.fmt_msg_timestamp)
263+
264+
def get_normal_msg_size(self, logdata, offset):
265+
"""Get the needed size of the normal log message at offset"""
266+
log_desc, _ = struct.unpack_from(self.fmt_msg_hdr, logdata, offset)
267+
pkg_len = (log_desc >> 6) & int(math.pow(2, 10) - 1)
268+
data_len = (log_desc >> 16) & int(math.pow(2, 12) - 1)
269+
return self.get_full_msg_hdr_size() + pkg_len + data_len
258270

259271
def parse_one_normal_msg(self, logdata, offset):
260272
"""Parse one normal log message and print the encoded message"""
@@ -341,33 +353,53 @@ def parse_one_normal_msg(self, logdata, offset):
341353
# Point to next message
342354
return next_msg_offset
343355

356+
def parse_one_msg(self, logdata, offset):
357+
if offset + struct.calcsize(self.fmt_msg_type) > len(logdata):
358+
return False, offset
344359

345-
def parse_log_data(self, logdata, debug=False):
346-
"""Parse binary log data and print the encoded log messages"""
347-
offset = 0
360+
# Get message type
361+
msg_type = struct.unpack_from(self.fmt_msg_type, logdata, offset)[0]
348362

349-
while offset < len(logdata):
350-
# Get message type
351-
msg_type = struct.unpack_from(self.fmt_msg_type, logdata, offset)[0]
363+
if msg_type == MSG_TYPE_DROPPED:
364+
365+
if offset + struct.calcsize(self.fmt_dropped_cnt) > len(logdata):
366+
return False, offset
352367
offset += struct.calcsize(self.fmt_msg_type)
353368

354-
if msg_type == MSG_TYPE_DROPPED:
355-
num_dropped = struct.unpack_from(self.fmt_dropped_cnt, logdata, offset)
356-
offset += struct.calcsize(self.fmt_dropped_cnt)
369+
num_dropped = struct.unpack_from(self.fmt_dropped_cnt, logdata, offset)
370+
offset += struct.calcsize(self.fmt_dropped_cnt)
357371

358-
print(f"--- {num_dropped} messages dropped ---")
372+
print(f"--- {num_dropped} messages dropped ---")
359373

360-
elif msg_type == MSG_TYPE_NORMAL:
361-
ret = self.parse_one_normal_msg(logdata, offset)
362-
if ret is None:
363-
return False
374+
elif msg_type == MSG_TYPE_NORMAL:
364375

365-
offset = ret
376+
if ((offset + self.get_full_msg_hdr_size() > len(logdata)) or
377+
(offset + self.get_normal_msg_size(logdata, offset) > len(logdata))):
378+
return False, offset
366379

367-
else:
368-
logger.error("------ Unknown message type: %s", msg_type)
369-
return False
380+
offset += struct.calcsize(self.fmt_msg_type)
381+
382+
ret = self.parse_one_normal_msg(logdata, offset)
383+
if ret is None:
384+
raise ValueError("Error parsing normal log message")
385+
386+
offset = ret
387+
388+
else:
389+
logger.error("------ Unknown message type: %s", msg_type)
390+
raise ValueError(f"Unknown message type: {msg_type}")
391+
392+
return True, offset
393+
394+
def parse_log_data(self, logdata, debug=False):
395+
"""Parse binary log data and print the encoded log messages"""
396+
offset = 0
397+
still_parsing = True
398+
399+
while offset < len(logdata) and still_parsing:
400+
still_parsing, offset = self.parse_one_msg(logdata, offset)
401+
402+
return offset
370403

371-
return True
372404

373405
colorama.init()

scripts/logging/dictionary/dictionary_parser/log_parser_v3.py

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,20 @@ def print_hexdump(hex_data, prefix_len, color):
260260
print(f"{color}%s%s%s|%s{Fore.RESET}" % ((" " * prefix_len),
261261
hex_vals, hex_padding, chr_vals))
262262

263+
def get_full_msg_hdr_size(self):
264+
"""Get the size of the full message header"""
265+
return struct.calcsize(self.fmt_msg_type) + \
266+
struct.calcsize(self.fmt_msg_hdr) + \
267+
struct.calcsize(self.fmt_msg_timestamp)
268+
269+
def get_normal_msg_size(self, logdata, offset):
270+
"""Get the needed size of the normal log message at offset"""
271+
_, pkg_len, data_len, _ = struct.unpack_from(
272+
self.fmt_msg_hdr,
273+
logdata,
274+
offset + struct.calcsize(self.fmt_msg_type),
275+
)
276+
return self.get_full_msg_hdr_size() + pkg_len + data_len
263277

264278
def parse_one_normal_msg(self, logdata, offset):
265279
"""Parse one normal log message and print the encoded message"""
@@ -350,33 +364,54 @@ def parse_one_normal_msg(self, logdata, offset):
350364
# Point to next message
351365
return next_msg_offset
352366

367+
def parse_one_msg(self, logdata, offset):
368+
if offset + struct.calcsize(self.fmt_msg_type) > len(logdata):
369+
return False, offset
353370

354-
def parse_log_data(self, logdata, debug=False):
355-
"""Parse binary log data and print the encoded log messages"""
356-
offset = 0
371+
# Get message type
372+
msg_type = struct.unpack_from(self.fmt_msg_type, logdata, offset)[0]
373+
374+
if msg_type == MSG_TYPE_DROPPED:
375+
376+
if offset + struct.calcsize(self.fmt_dropped_cnt) > len(logdata):
377+
return False, offset
357378

358-
while offset < len(logdata):
359-
# Get message type
360-
msg_type = struct.unpack_from(self.fmt_msg_type, logdata, offset)[0]
361379
offset += struct.calcsize(self.fmt_msg_type)
362380

363-
if msg_type == MSG_TYPE_DROPPED:
364-
num_dropped = struct.unpack_from(self.fmt_dropped_cnt, logdata, offset)
365-
offset += struct.calcsize(self.fmt_dropped_cnt)
381+
num_dropped = struct.unpack_from(self.fmt_dropped_cnt, logdata, offset)
382+
offset += struct.calcsize(self.fmt_dropped_cnt)
366383

367-
print(f"--- {num_dropped} messages dropped ---")
384+
print(f"--- {num_dropped} messages dropped ---")
368385

369-
elif msg_type == MSG_TYPE_NORMAL:
370-
ret = self.parse_one_normal_msg(logdata, offset)
371-
if ret is None:
372-
return False
386+
elif msg_type == MSG_TYPE_NORMAL:
373387

374-
offset = ret
388+
if ((offset + self.get_full_msg_hdr_size() > len(logdata)) or
389+
(offset + self.get_normal_msg_size(logdata, offset) > len(logdata))):
390+
return False, offset
375391

376-
else:
377-
logger.error("------ Unknown message type: %s", msg_type)
378-
return False
392+
offset += struct.calcsize(self.fmt_msg_type)
393+
394+
ret = self.parse_one_normal_msg(logdata, offset)
395+
if ret is None:
396+
raise ValueError("Error parsing normal log message")
397+
398+
offset = ret
399+
400+
else:
401+
logger.error("------ Unknown message type: %s", msg_type)
402+
raise ValueError(f"Unknown message type: {msg_type}")
403+
404+
return True, offset
405+
406+
def parse_log_data(self, logdata, debug=False):
407+
"""Parse binary log data and print the encoded log messages"""
408+
offset = 0
409+
still_parsing = True
410+
411+
while offset < len(logdata) and still_parsing:
412+
still_parsing, offset = self.parse_one_msg(logdata, offset)
413+
414+
return offset
379415

380-
return True
381416

382417
colorama.init()

scripts/logging/dictionary/log_parser.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,20 @@ def main():
109109
else:
110110
logger.setLevel(logging.INFO)
111111

112+
log_parser = parserlib.get_log_parser(args.dbfile, logger)
113+
112114
logdata = read_log_file(args)
113115
if logdata is None:
114116
logger.error("ERROR: cannot read log from file: %s, exiting...", args.logfile)
115117
sys.exit(1)
116118

117-
parserlib.parser(logdata, args.dbfile, logger)
119+
parsed_data_offset = parserlib.parser(logdata, log_parser, logger)
120+
if parsed_data_offset != len(logdata):
121+
logger.error(
122+
'ERROR: Not all data was parsed, %d bytes left unparsed',
123+
len(logdata) - parsed_data_offset,
124+
)
125+
sys.exit(1)
118126

119127
if __name__ == "__main__":
120128
main()

scripts/logging/dictionary/log_parser_uart.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,18 @@ def main():
5050
else:
5151
logger.setLevel(logging.INFO)
5252

53+
log_parser = parserlib.get_log_parser(args.dbfile, logger)
54+
5355
# Parse the log every second from serial port
56+
data = b''
5457
with serial.Serial(args.serialPort, args.baudrate) as ser:
5558
ser.timeout = 2
5659
while True:
5760
size = ser.inWaiting()
5861
if size:
59-
data = ser.read(size)
60-
parserlib.parser(data, args.dbfile, logger)
62+
data += ser.read(size)
63+
parsed_data_offset = parserlib.parser(data, log_parser, logger)
64+
data = data[parsed_data_offset:]
6165
time.sleep(1)
6266

6367
if __name__ == "__main__":

scripts/logging/dictionary/parserlib.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,46 @@
1313
"""
1414

1515
import logging
16-
import sys
1716

1817
import dictionary_parser
1918
from dictionary_parser.log_database import LogDatabase
2019

2120

22-
def parser(logdata, dbfile, logger):
23-
"""function of serial parser"""
24-
# Read from database file
25-
database = LogDatabase.read_json_database(dbfile)
21+
def get_log_parser(dbfile, logger):
22+
"""Get the log parser for the given database.
2623
27-
if not isinstance(logger, logging.Logger):
28-
raise ValueError("Invalid logger instance. Please configure the logger!")
24+
In addition to creating the parser, the function prints general information about the parser.
25+
"""
26+
database = LogDatabase.read_json_database(dbfile)
2927

3028
if database is None:
3129
logger.error("ERROR: Cannot open database file: exiting...")
32-
sys.exit(1)
33-
34-
if logdata is None:
35-
logger.error("ERROR: cannot read log from file: exiting...")
36-
sys.exit(1)
37-
30+
raise ValueError(f"Cannot open database file: {dbfile}")
3831
log_parser = dictionary_parser.get_parser(database)
32+
3933
if log_parser is not None:
4034
logger.debug("# Build ID: %s", database.get_build_id())
4135
logger.debug("# Target: %s, %d-bit", database.get_arch(), database.get_tgt_bits())
4236
if database.is_tgt_little_endian():
4337
logger.debug("# Endianness: Little")
4438
else:
4539
logger.debug("# Endianness: Big")
46-
47-
ret = log_parser.parse_log_data(logdata)
48-
if not ret:
49-
logger.error("ERROR: there were error(s) parsing log data")
50-
sys.exit(1)
5140
else:
5241
logger.error("ERROR: Cannot find a suitable parser matching database version!")
42+
raise ValueError("Cannot create parser.")
43+
44+
return log_parser
45+
46+
47+
def parser(logdata, log_parser, logger):
48+
"""function of serial parser"""
49+
50+
if not isinstance(logger, logging.Logger):
51+
raise ValueError("Invalid logger instance. Please configure the logger!")
52+
53+
if logdata is None:
54+
logger.error("ERROR: cannot read log from file: exiting...")
55+
raise ValueError("Cannot read log data.")
56+
57+
ret = log_parser.parse_log_data(logdata)
58+
return ret

0 commit comments

Comments
 (0)