1
0
mirror of https://github.com/lvgl/lvgl.git synced 2025-01-14 06:42:58 +08:00
lvgl/scripts/perf_report.py
Daniel Rossier d9496c7979
First CI action for performance check of LVGL code base with SO3 (#4122)
Co-authored-by: Anthony I. Jaccard <anthony.jaccard@heig-vd.ch>
Co-authored-by: AnthoJack <to.jaccard@hotmail.ch>
2024-06-17 22:56:51 +02:00

189 lines
6.3 KiB
Python

"""
Compares the execution time of the functions in a file with the thresholds given in another file.
Optionnaly compares the execution times with previous executions
Prints 3 types of messages:
- FAIL: A function failed to respect the threshold set
- WARN:
- No threshold was set for a function
- No perfect match for a function-parent pair was found and no default threshold (function but no parent) was set
- A function slowed down since last time it was executed
- INFO:
- Using a default threshold for a function
- No previous execution found for a function-parent pair
"""
import os
import sys
class MalformedInputError(RuntimeError):
pass
class FuncData:
def __init__(self, name: str = "", parent: str = "", info: str = "", time: float = .0):
self.name = name # Function's name
self.parent = parent # Function parent's name
self.info = info # Optionnal supplementary data (currently unused)
self.time = time # Function's execution time
def __str__(self) -> str:
my_str = ""
if len(parent) != 0:
my_str += f'{parent}/'
my_str += f'self.name '
if len(self.info) > 0:
my_str += f'({self.info.replace(" ", "_")}) '
my_str += f'| {self.time}'
return my_str
def from_string(string: str) -> 'FuncData':
func_data = FuncData()
symbol_idx = 0
# Separate symbols of string
data = string.strip().split(" ")
try:
# Retrieve function's name and parent (if given)
func = data[symbol_idx].split("/")
if len(func) > 1:
func_data.name = func[1]
func_data.parent = func[0]
else:
func_data.name = func[0]
symbol_idx += 1
# Retrieve infos
if "(" in data[symbol_idx]:
func_data.info = data[symbol_idx].strip("()")
symbol_idx += 1
# Check that | separator is used before data
if data[symbol_idx] == "|": symbol_idx += 1
else: raise MalformedInputError("Required '|' separator is missing")
# Retrieve data
func_data.time = float(data[symbol_idx])
except IndexError:
raise MalformedInputError("Some required data is missing")
except ValueError:
raise MalformedInputError("Given data value cannot be converted to float")
except OverflowError:
raise MalformedInputError("Given data value is too big")
return func_data
"""
Input files are expected to have the following format:
1 header line with no defined format
N lines with function data following the "[parent/]<name> [(info)] | <time>" format
Parent and info are optionnal.
Only one time is supported per function-parent pair
Time is expected to be a single value convertible to float. Eventual excess values will be discarded
"""
def get_func_data_from_file(file: str) -> dict[dict[float]]:
data: dict[dict[float]] = dict()
data_file = open(file)
# Discard header line
data_file.readline()
# Parse data
for line in data_file:
# Parse line
new_data: FuncData = FuncData.from_string(line)
# Check if function already exists
if new_data.name in data.keys():
#Check that function wasn't already called from same parent
if new_data.parent in data[new_data.name].keys():
raise NotImplementedError("Current script does not support more than one execution time per parent-func pair")
else:
data[new_data.name] = dict()
#Store data
data[new_data.name][new_data.parent] = new_data.time
return data
"""
Compares the current execution time with the selected threshold
Threshold is selected based on correspondance with the execution time's function-parent pair:
- If a perfect match (function-parent) exists, select it
- If a default threshold is given for this function select it
- Otherwise, print a WARN message
"""
def check_threshold(exec_times: dict[dict[float]],
thresholds: dict[dict[float]],
func: str,
parent: str) -> bool:
if func not in thresholds.keys():
print(f'WARN: No threshold set for function {func}')
return True
# Select threshold
if parent in thresholds[func].keys():
threshold = thresholds[func][parent]
elif "" in thresholds[func].keys():
print(f'INFO: Using default threshold for function {func} called from {parent}')
threshold = thresholds[func][""]
else:
print(f'WARN: No default threshold set for function {func} called from {parent}')
return True
# Compare set threshold to calculated execution time
exec_time = exec_times[func][parent]
if exec_time > threshold:
print(f'FAIL: Function {func} called from {parent} executed in {exec_time}s but threshold is set to {threshold}s')
return False
return True
"""
Compares the current and previous execution time of the given function-parent pair
Function-parent pair must match exactely otherwise the script considers that no previous execution exists
"""
def check_prev_exec(exec_times: dict[dict[float]],
prv_exec_times: dict[dict[float]],
func: str,
parent: str):
try:
# Compare Current to previous execution
exec_time = exec_times[func][parent]
prev_exec_time = prev_exec_times[func][parent]
if exec_time > prev_exec_time:
print(f'WARN: Function {func} called from {parent} slowed from {prev_exec_time}s to {exec_time}s')
except KeyError: # No record of similar previous execution
print(f'INFO: No previous execution of function {func} called from {parent}')
return
if __name__ == "__main__":
if len(sys.argv) < 3 or len(sys.argv) > 4:
print(f'Usage: python3 {sys.argv[0]} <execution_time_file> <threshold_file> [previous_execution_time_file]')
exit(-1)
EXEC_TIME_FILE = sys.argv[1]
THRESHOLD_FILE = sys.argv[2]
PREV_EXEC_TIME_FILE = "" if len(sys.argv) < 4 else sys.argv[3]
prev_perf_exists = os.path.exists(PREV_EXEC_TIME_FILE)
if len(sys.argv) < 4 or not prev_perf_exists:
print("INFO: No previous performance data provided. Only threshold overshoot will be tested")
func_over_threshold = 0
thresholds = get_func_data_from_file(THRESHOLD_FILE)
exec_times = get_func_data_from_file(EXEC_TIME_FILE)
if prev_perf_exists:
prev_exec_times = get_func_data_from_file(PREV_EXEC_TIME_FILE)
for func in exec_times.keys():
for parent in exec_times[func].keys():
if not check_threshold(exec_times, thresholds, func, parent):
func_over_threshold += 1
continue
if prev_perf_exists:
check_prev_exec(exec_times, prev_exec_times, func, parent)
if func_over_threshold > 0:
print(f'Performance check failed: {func_over_threshold} functions over set threshold')
sys.exit(func_over_threshold)