# SPDX-License-Identifier: LicenseRef-CSSL-1.0 import re import os import logging from collections import deque class Default: def run(file, opt=None): success = True logs = [] with open(file, "r") as f: for line in f.readlines(): result = re.search('[Aa]ssertion', line) if result: logs.append(line) success = False return success, "\n".join(logs) class ContainsString: def run(file, needle): with open(file, "r") as f: for line in f.readlines(): if re.search(needle, line): return True, "" return False, f"could not find '{needle}' in logs" class LastLineContains: def run(file, string): with open(file, "r") as f: last_line = deque(f, maxlen=1).pop() success = string in last_line log = "" if success else f"could not find '{string}' in last line" return success, log class EndsWithBye: def run(file, option): # no option: we ignore any with open(file, "r") as f: n = 0 for line in reversed(f.readlines()): if re.search(r'^Bye.\n', line, re.MULTILINE) is not None: return True, "" n = n + 1 if n > 50: # if not found in last 50 lines, assume it does not exist break return False, "No Bye. message found, did not stop properly" class RetxCheck(): def _parseList(s): if not s: return [] return [int(n) for n in s.split(",")] def _get_ul_dl(s): dl, ul = (None, None) for o in s.split(";", 1): if o.startswith("dl="): dl = o[3:] elif o.startswith("ul="): ul = o[3:] else: raise ValueError(f"unrecognized option {s}: needs format '[ul=,dl=]X,Y,...[;ul=Z,V,...]'") return dl, ul def _parseRetxCheckers(options): if not "=" in options: dlul = RetxCheck._parseList(options) return dlul, dlul dl, ul = RetxCheck._get_ul_dl(options) return RetxCheck._parseList(dl), RetxCheck._parseList(ul) def _analyzeUeRetx(rounds, checkers, regex): perc = list(0 for i in checkers) # results in % stats = list(False for i in checkers) # status if succeeded tmp = re.match(regex, rounds) if tmp is None: logging.error('_analyzeUeRetx: did not match regex for DL retx analysis') return stats retx_data = [float(x) for x in tmp.groups()] for i in range(0, len(perc)): #case where numerator > denumerator with denum ==0 is disregarded, cannot hapen in principle, will lead to 0% perc[i] = 0 if (retx_data[i] == 0) else 100 * retx_data[i + 1] / retx_data[i] #treating % > 100 , % > requirement stats[i] = perc[i] <= 100 and perc[i] <= checkers[i] return stats def _filter_retx_rounds(l): # map to round number if False, else None l = [i+1 if x is False else None for i, x in enumerate(l)] # filter out None return [x for x in l if x is not None] def run(file, options): # aggregate statistics per UE dlsch_ulsch_stats = {} status = True with open(file, "r") as f: for line in f.readlines(): keys = {'dlsch_rounds','ulsch_rounds'} for k in keys: result = re.search(k, line) if result is None: continue result = re.search('UE (?:RNTI )?([0-9a-f]{4})', line) if result is None: logging.error(f'did not find RNTI while matching key {k}') status = False continue rnti = result.group(1) if not rnti in dlsch_ulsch_stats: dlsch_ulsch_stats[rnti] = {} dlsch_ulsch_stats[rnti][k]=re.sub(r'^.*\]\s+', r'' , line.rstrip()) dl, ul = RetxCheck._parseRetxCheckers(options) msgs = [] for ue in dlsch_ulsch_stats: dlulstat = dlsch_ulsch_stats[ue] for key in dlulstat: msgs.append(dlulstat[key]) retx_dl = RetxCheck._analyzeUeRetx(dlulstat['dlsch_rounds'], dl, r'^.*dlsch_rounds\s+(\d+)\/(\d+)\/(\d+)\/(\d+),\s+dlsch_errors\s+(\d+)') retx_ul = RetxCheck._analyzeUeRetx(dlulstat['ulsch_rounds'], ul, r'^.*ulsch_rounds\s+(\d+)\/(\d+)\/(\d+)\/(\d+),\s+ulsch_errors\s+(\d+)') if False in retx_dl or False in retx_ul: dlstr = RetxCheck._filter_retx_rounds(retx_dl) ulstr = RetxCheck._filter_retx_rounds(retx_ul) msg = f"UE {ue}: retx rounds" if dlstr: msg = f"{msg} DL {dlstr}" if ulstr: msg = f"{msg} UL {ulstr}" msg = f"{msg} exceeded threshold" msgs.append('!!! Failure: ' + msg) logging.error(f'\u001B[1;37;41m {msg}\u001B[0m') status = False else: msg = f"UE {ue} retransmissions within bounds" logging.debug(msg) return status, "\n".join(msgs)