From fc6ed4ddad41e6ca10e807bb1b5aac34752ece87 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Fri, 14 Jul 2023 17:33:09 +0800 Subject: [PATCH 01/41] code_size_compare: add a parser to generate code size with size tool This commit splits CodeSizeBase as a separate class to prepare a parser as CodeSizeGenerator. The benefit is we can extend the tool of code size measurement in order to generate more types of code size record. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 37 ++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 0ed28999b321..3c0f83d9a41e 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -125,17 +125,18 @@ def set_make_command(self) -> str: print(comb) sys.exit(1) -class SizeEntry: # pylint: disable=too-few-public-methods - """Data Structure to only store information of code size.""" - def __init__(self, text, data, bss, dec): - self.text = text - self.data = data - self.bss = bss - self.total = dec # total <=> dec - -class CodeSizeBase: + +class CodeSizeGeneratorWithSize: """Code Size Base Class for size record saving and writing.""" + class SizeEntry: # pylint: disable=too-few-public-methods + """Data Structure to only store information of code size.""" + def __init__(self, text, data, bss, dec): + self.text = text + self.data = data + self.bss = bss + self.total = dec # total <=> dec + def __init__(self) -> None: """ Variable code_size is used to store size info for any revisions. code_size: (data format) @@ -157,7 +158,8 @@ def set_size_record(self, revision: str, mod: str, size_text: str) -> None: size_record = {} for line in size_text.splitlines()[1:]: data = line.split() - size_record[data[5]] = SizeEntry(data[0], data[1], data[2], data[3]) + size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry(\ + data[0], data[1], data[2], data[3]) if revision in self.code_size: self.code_size[revision].update({mod: size_record}) else: @@ -180,7 +182,8 @@ def read_size_record(self, revision: str, fname: str) -> None: if mod: size_record[data[0]] = \ - SizeEntry(data[1], data[2], data[3], data[4]) + CodeSizeGeneratorWithSize.SizeEntry(\ + data[1], data[2], data[3], data[4]) # check if we hit record for the end of a module m = re.match(r'.?TOTALS', line) @@ -247,7 +250,7 @@ def write_comparison( output.write("{} {}\n".format(fname, new_size)) -class CodeSizeComparison(CodeSizeBase): +class CodeSizeComparison: """Compare code size between two Git revisions.""" def __init__( @@ -278,6 +281,7 @@ def __init__( self.make_command = code_size_info.make_command self.fname_suffix = "-" + code_size_info.arch + "-" +\ code_size_info.config + self.code_size_generator = CodeSizeGeneratorWithSize() @staticmethod def validate_revision(revision: str) -> bytes: @@ -336,12 +340,12 @@ def _gen_code_size_csv(self, revision: str, git_worktree_path: str) -> None: self._handle_called_process_error(e, git_worktree_path) size_text = result.decode("utf-8") - self.set_size_record(revision, mod, size_text) + self.code_size_generator.set_size_record(revision, mod, size_text) print("Generating code size csv for", revision) csv_file = open(os.path.join(self.csv_dir, revision + self.fname_suffix + ".csv"), "w") - self.write_size_record(revision, csv_file) + self.code_size_generator.write_size_record(revision, csv_file) def _remove_worktree(self, git_worktree_path: str) -> None: """Remove temporary worktree.""" @@ -361,7 +365,8 @@ def _get_code_size_for_rev(self, revision: str) -> None: if (revision != "current") and \ os.path.exists(os.path.join(self.csv_dir, csv_fname)): print("Code size csv file for", revision, "already exists.") - self.read_size_record(revision, os.path.join(self.csv_dir, csv_fname)) + self.code_size_generator.read_size_record(revision,\ + os.path.join(self.csv_dir, csv_fname)) else: git_worktree_path = self._create_git_worktree(revision) self._build_libraries(git_worktree_path) @@ -380,7 +385,7 @@ def _gen_code_size_comparison(self) -> int: print("\nGenerating comparison results between",\ self.old_rev, "and", self.new_rev) - self.write_comparison(self.old_rev, self.new_rev, res_file) + self.code_size_generator.write_comparison(self.old_rev, self.new_rev, res_file) return 0 From 15c43f34073f6315bc006de4c992ab19a6cbaa28 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Mon, 17 Jul 2023 11:17:12 +0800 Subject: [PATCH 02/41] code_size_compare: add a base class as CodeSizeGenerator Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 64 +++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 3c0f83d9a41e..a5625c32a14b 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -126,7 +126,44 @@ def set_make_command(self) -> str: sys.exit(1) -class CodeSizeGeneratorWithSize: +class CodeSizeGenerator: + """ A generator based on size measurement tool for library objects. + + This is an abstract class. To use it, derive a class that implements + size_generator_write_record and size_generator_write_comparison methods, + then call both of them with proper arguments. + """ + def size_generator_write_record( + self, + revision: str, + code_size_text: typing.Dict, + output_file: str + ) -> None: + """Write size record into a file. + + revision: Git revision.(E.g: commit) + code_size_text: text output (utf-8) from code size measurement tool. + output_file: file which the code size record is written to. + """ + raise NotImplementedError + + def size_generator_write_comparison( + self, + old_rev: str, + new_rev: str, + output_stream + ) -> None: + """Write a comparision result into a stream between two revisions. + + old_rev: old git revision to compared with. + new_rev: new git revision to compared with. + output_stream: stream which the code size record is written to. + (E.g: file / sys.stdout) + """ + raise NotImplementedError + + +class CodeSizeGeneratorWithSize(CodeSizeGenerator): """Code Size Base Class for size record saving and writing.""" class SizeEntry: # pylint: disable=too-few-public-methods @@ -249,6 +286,31 @@ def write_comparison( else: output.write("{} {}\n".format(fname, new_size)) + def size_generator_write_record( + self, + revision: str, + code_size_text: typing.Dict, + output_file: str + ) -> None: + """Write size record into a specified file based on Git revision and + output from `size` tool.""" + for mod, size_text in code_size_text.items(): + self.set_size_record(revision, mod, size_text) + + print("Generating code size csv for", revision) + output = open(output_file, "w") + self.write_size_record(revision, output) + + def size_generator_write_comparison( + self, + old_rev: str, + new_rev: str, + output_stream + ) -> None: + """Write a comparision result into a stream between two revisions.""" + output = open(output_stream, "w") + self.write_comparison(old_rev, new_rev, output) + class CodeSizeComparison: """Compare code size between two Git revisions.""" From e0e276046bda6a1feb8121b44a565cee2bfd9543 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Fri, 14 Jul 2023 17:37:45 +0800 Subject: [PATCH 03/41] code_size_compare: add CodeSizeCalculator to calculate code size CodeSizeCalculator is aimed to calculate code size based on a Git revision and code size measurement tool. The output of code size is in utf-8 encoding. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 223 +++++++++++++++++++++-------------- 1 file changed, 132 insertions(+), 91 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index a5625c32a14b..01d93cad0fc8 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -126,6 +126,123 @@ def set_make_command(self) -> str: sys.exit(1) +class CodeSizeCalculator: + """ A calculator to calculate code size of library objects based on + Git revision and code size measurement tool. + """ + + def __init__( + self, + revision: str, + make_cmd: str, + ) -> None: + """ + revision: Git revision.(E.g: commit) + make_cmd: command to build library objects. + """ + self.repo_path = "." + self.git_command = "git" + self.make_clean = 'make clean' + + self.revision = revision + self.make_cmd = make_cmd + + @staticmethod + def validate_revision(revision: str) -> bytes: + result = subprocess.check_output(["git", "rev-parse", "--verify", + revision + "^{commit}"], shell=False) + return result + + def _create_git_worktree(self, revision: str) -> str: + """Make a separate worktree for revision. + Do not modify the current worktree.""" + + if revision == "current": + print("Using current work directory") + git_worktree_path = self.repo_path + else: + print("Creating git worktree for", revision) + git_worktree_path = os.path.join(self.repo_path, "temp-" + revision) + subprocess.check_output( + [self.git_command, "worktree", "add", "--detach", + git_worktree_path, revision], cwd=self.repo_path, + stderr=subprocess.STDOUT + ) + + return git_worktree_path + + def _build_libraries(self, git_worktree_path: str) -> None: + """Build libraries in the specified worktree.""" + + my_environment = os.environ.copy() + try: + subprocess.check_output( + self.make_clean, env=my_environment, shell=True, + cwd=git_worktree_path, stderr=subprocess.STDOUT, + ) + subprocess.check_output( + self.make_cmd, env=my_environment, shell=True, + cwd=git_worktree_path, stderr=subprocess.STDOUT, + ) + except subprocess.CalledProcessError as e: + self._handle_called_process_error(e, git_worktree_path) + + def _gen_raw_code_size(self, revision, git_worktree_path): + """Calculate code size with measurement tool in UTF-8 encoding.""" + if revision == "current": + print("Measuring code size in current work directory") + else: + print("Measuring code size for", revision) + + res = {} + for mod, st_lib in MBEDTLS_STATIC_LIB.items(): + try: + result = subprocess.check_output( + ["size", st_lib, "-t"], cwd=git_worktree_path, + universal_newlines=True + ) + res[mod] = result + except subprocess.CalledProcessError as e: + self._handle_called_process_error(e, git_worktree_path) + + return res + + def _remove_worktree(self, git_worktree_path: str) -> None: + """Remove temporary worktree.""" + if git_worktree_path != self.repo_path: + print("Removing temporary worktree", git_worktree_path) + subprocess.check_output( + [self.git_command, "worktree", "remove", "--force", + git_worktree_path], cwd=self.repo_path, + stderr=subprocess.STDOUT + ) + + def _handle_called_process_error(self, e: subprocess.CalledProcessError, + git_worktree_path: str) -> None: + """Handle a CalledProcessError and quit the program gracefully. + Remove any extra worktrees so that the script may be called again.""" + + # Tell the user what went wrong + print("The following command: {} failed and exited with code {}" + .format(e.cmd, e.returncode)) + print("Process output:\n {}".format(str(e.output, "utf-8"))) + + # Quit gracefully by removing the existing worktree + self._remove_worktree(git_worktree_path) + sys.exit(-1) + + def cal_libraries_code_size(self) -> typing.Dict: + """Calculate code size of libraries by measurement tool.""" + + revision = self.revision + git_worktree_path = self._create_git_worktree(revision) + self._build_libraries(git_worktree_path) + res = self._gen_raw_code_size(revision, git_worktree_path) + self._remove_worktree(git_worktree_path) + + return res + + class CodeSizeGenerator: """ A generator based on size measurement tool for library objects. @@ -328,7 +445,6 @@ def __init__( result_dir: directory for comparison result. code_size_info: an object containing information to build library. """ - super().__init__() self.repo_path = "." self.result_dir = os.path.abspath(result_dir) os.makedirs(self.result_dir, exist_ok=True) @@ -345,47 +461,7 @@ def __init__( code_size_info.config self.code_size_generator = CodeSizeGeneratorWithSize() - @staticmethod - def validate_revision(revision: str) -> bytes: - result = subprocess.check_output(["git", "rev-parse", "--verify", - revision + "^{commit}"], shell=False) - return result - - def _create_git_worktree(self, revision: str) -> str: - """Make a separate worktree for revision. - Do not modify the current worktree.""" - - if revision == "current": - print("Using current work directory") - git_worktree_path = self.repo_path - else: - print("Creating git worktree for", revision) - git_worktree_path = os.path.join(self.repo_path, "temp-" + revision) - subprocess.check_output( - [self.git_command, "worktree", "add", "--detach", - git_worktree_path, revision], cwd=self.repo_path, - stderr=subprocess.STDOUT - ) - - return git_worktree_path - - def _build_libraries(self, git_worktree_path: str) -> None: - """Build libraries in the specified worktree.""" - - my_environment = os.environ.copy() - try: - subprocess.check_output( - self.make_clean, env=my_environment, shell=True, - cwd=git_worktree_path, stderr=subprocess.STDOUT, - ) - subprocess.check_output( - self.make_command, env=my_environment, shell=True, - cwd=git_worktree_path, stderr=subprocess.STDOUT, - ) - except subprocess.CalledProcessError as e: - self._handle_called_process_error(e, git_worktree_path) - - def _gen_code_size_csv(self, revision: str, git_worktree_path: str) -> None: + def _gen_code_size_csv(self, revision: str) -> None: """Generate code size csv file.""" if revision == "current": @@ -393,31 +469,13 @@ def _gen_code_size_csv(self, revision: str, git_worktree_path: str) -> None: else: print("Measuring code size for", revision) - for mod, st_lib in MBEDTLS_STATIC_LIB.items(): - try: - result = subprocess.check_output( - ["size", st_lib, "-t"], cwd=git_worktree_path - ) - except subprocess.CalledProcessError as e: - self._handle_called_process_error(e, git_worktree_path) - size_text = result.decode("utf-8") - - self.code_size_generator.set_size_record(revision, mod, size_text) + code_size_text = CodeSizeCalculator(revision, self.make_command).\ + cal_libraries_code_size() - print("Generating code size csv for", revision) - csv_file = open(os.path.join(self.csv_dir, revision + - self.fname_suffix + ".csv"), "w") - self.code_size_generator.write_size_record(revision, csv_file) - - def _remove_worktree(self, git_worktree_path: str) -> None: - """Remove temporary worktree.""" - if git_worktree_path != self.repo_path: - print("Removing temporary worktree", git_worktree_path) - subprocess.check_output( - [self.git_command, "worktree", "remove", "--force", - git_worktree_path], cwd=self.repo_path, - stderr=subprocess.STDOUT - ) + csv_file = os.path.join(self.csv_dir, revision + + self.fname_suffix + ".csv") + self.code_size_generator.size_generator_write_record(revision,\ + code_size_text, csv_file) def _get_code_size_for_rev(self, revision: str) -> None: """Generate code size csv file for the specified git revision.""" @@ -430,24 +488,21 @@ def _get_code_size_for_rev(self, revision: str) -> None: self.code_size_generator.read_size_record(revision,\ os.path.join(self.csv_dir, csv_fname)) else: - git_worktree_path = self._create_git_worktree(revision) - self._build_libraries(git_worktree_path) - self._gen_code_size_csv(revision, git_worktree_path) - self._remove_worktree(git_worktree_path) + self._gen_code_size_csv(revision) def _gen_code_size_comparison(self) -> int: """Generate results of the size changes between two revisions, old and new. Measured code size results of these two revisions must be available.""" - res_file = open(os.path.join(self.result_dir, "compare-" + - self.old_rev + "-" + self.new_rev + - self.fname_suffix + - ".csv"), "w") + res_file = os.path.join(self.result_dir, "compare-" + + self.old_rev + "-" + self.new_rev + + self.fname_suffix + ".csv") print("\nGenerating comparison results between",\ self.old_rev, "and", self.new_rev) - self.code_size_generator.write_comparison(self.old_rev, self.new_rev, res_file) + self.code_size_generator.size_generator_write_comparison(\ + self.old_rev, self.new_rev, res_file) return 0 @@ -459,20 +514,6 @@ def get_comparision_results(self) -> int: self._get_code_size_for_rev(self.new_rev) return self._gen_code_size_comparison() - def _handle_called_process_error(self, e: subprocess.CalledProcessError, - git_worktree_path: str) -> None: - """Handle a CalledProcessError and quit the program gracefully. - Remove any extra worktrees so that the script may be called again.""" - - # Tell the user what went wrong - print("The following command: {} failed and exited with code {}" - .format(e.cmd, e.returncode)) - print("Process output:\n {}".format(str(e.output, "utf-8"))) - - # Quit gracefully by removing the existing worktree - self._remove_worktree(git_worktree_path) - sys.exit(-1) - def main(): parser = argparse.ArgumentParser(description=(__doc__)) group_required = parser.add_argument_group( @@ -509,11 +550,11 @@ def main(): print("Error: {} is not a directory".format(comp_args.result_dir)) parser.exit() - validate_res = CodeSizeComparison.validate_revision(comp_args.old_rev) + validate_res = CodeSizeCalculator.validate_revision(comp_args.old_rev) old_revision = validate_res.decode().replace("\n", "") if comp_args.new_rev is not None: - validate_res = CodeSizeComparison.validate_revision(comp_args.new_rev) + validate_res = CodeSizeCalculator.validate_revision(comp_args.new_rev) new_revision = validate_res.decode().replace("\n", "") else: new_revision = "current" From 5e9130a5e9ed156400ce56efc4a0e7c86c59185a Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Mon, 17 Jul 2023 11:55:54 +0800 Subject: [PATCH 04/41] code_size_compare: simplify methods in CodeSizeComparison Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 56 ++++++++++++++---------------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 01d93cad0fc8..8cd1b27751e0 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -92,12 +92,11 @@ def __init__(self, arch: str, config: str, sys_arch: str) -> None: arch: architecture to measure code size on. config: configuration type to measure code size with. sys_arch: host architecture. - make_command: command to build library (Inferred from arch and config). """ self.arch = arch self.config = config self.sys_arch = sys_arch - self.make_command = self.set_make_command() + self.make_cmd = self.set_make_command() def set_make_command(self) -> str: """Infer build command based on architecture and configuration.""" @@ -456,63 +455,52 @@ def __init__( self.new_rev = new_revision self.git_command = "git" self.make_clean = 'make clean' - self.make_command = code_size_info.make_command + self.make_cmd = code_size_info.make_cmd self.fname_suffix = "-" + code_size_info.arch + "-" +\ code_size_info.config self.code_size_generator = CodeSizeGeneratorWithSize() - def _gen_code_size_csv(self, revision: str) -> None: - """Generate code size csv file.""" + def cal_code_size(self, revision: str): + """Calculate code size of library objects in a UTF-8 encoding""" - if revision == "current": - print("Measuring code size in current work directory") - else: - print("Measuring code size for", revision) - - code_size_text = CodeSizeCalculator(revision, self.make_command).\ + return CodeSizeCalculator(revision, self.make_cmd).\ cal_libraries_code_size() - csv_file = os.path.join(self.csv_dir, revision + - self.fname_suffix + ".csv") - self.code_size_generator.size_generator_write_record(revision,\ - code_size_text, csv_file) - - def _get_code_size_for_rev(self, revision: str) -> None: - """Generate code size csv file for the specified git revision.""" + def gen_code_size_report(self, revision): + """Generate code size record and write it into a file.""" + output_file = os.path.join(self.csv_dir,\ + revision + self.fname_suffix + ".csv") # Check if the corresponding record exists - csv_fname = revision + self.fname_suffix + ".csv" - if (revision != "current") and \ - os.path.exists(os.path.join(self.csv_dir, csv_fname)): + if (revision != "current") and os.path.exists(output_file): print("Code size csv file for", revision, "already exists.") - self.code_size_generator.read_size_record(revision,\ - os.path.join(self.csv_dir, csv_fname)) + self.code_size_generator.read_size_record(revision, output_file) else: - self._gen_code_size_csv(revision) + self.code_size_generator.size_generator_write_record(revision,\ + self.cal_code_size(revision), output_file) - def _gen_code_size_comparison(self) -> int: - """Generate results of the size changes between two revisions, + def gen_code_size_comparison(self) -> int: + """Generate results of code size changes between two revisions, old and new. Measured code size results of these two revisions must be available.""" - res_file = os.path.join(self.result_dir, "compare-" + - self.old_rev + "-" + self.new_rev + - self.fname_suffix + ".csv") + output_file = os.path.join(self.result_dir, "compare-" + + self.old_rev + "-" + self.new_rev + + self.fname_suffix + ".csv") print("\nGenerating comparison results between",\ self.old_rev, "and", self.new_rev) self.code_size_generator.size_generator_write_comparison(\ - self.old_rev, self.new_rev, res_file) - + self.old_rev, self.new_rev, output_file) return 0 def get_comparision_results(self) -> int: """Compare size of library/*.o between self.old_rev and self.new_rev, and generate the result file.""" build_tree.check_repo_path() - self._get_code_size_for_rev(self.old_rev) - self._get_code_size_for_rev(self.new_rev) - return self._gen_code_size_comparison() + self.gen_code_size_report(self.old_rev) + self.gen_code_size_report(self.new_rev) + return self.gen_code_size_comparison() def main(): parser = argparse.ArgumentParser(description=(__doc__)) From 923f943a3e992fda89ea6c31ac611085ceda9783 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Mon, 17 Jul 2023 12:43:00 +0800 Subject: [PATCH 05/41] code_size_compare: introduce SimpleNamespace to store info We use SimpleNamespace class to store all the information used to measure code size of objects in library. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 127 +++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 51 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 8cd1b27751e0..8f3730f240ad 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -31,6 +31,7 @@ import typing from enum import Enum +from types import SimpleNamespace from mbedtls_dev import typing_util from mbedtls_dev import build_tree @@ -72,7 +73,7 @@ def detect_arch() -> str: print("Unknown host architecture, cannot auto-detect arch.") sys.exit(1) -class CodeSizeInfo: # pylint: disable=too-few-public-methods +class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods """Gather information used to measure code size. It collects information about architecture, configuration in order to @@ -87,25 +88,23 @@ class CodeSizeInfo: # pylint: disable=too-few-public-methods "-a " + SupportedArch.ARMV8_M.value + " -c " + SupportedConfig.TFM_MEDIUM.value, ] - def __init__(self, arch: str, config: str, sys_arch: str) -> None: + def __init__(self, size_version: SimpleNamespace) -> None: """ - arch: architecture to measure code size on. - config: configuration type to measure code size with. - sys_arch: host architecture. + size_version: SimpleNamespace containing info for code size measurement. + size_version.arch: architecture to measure code size on. + size_version.config: configuration type to measure code size with. + size_version.host_arch: host architecture. """ - self.arch = arch - self.config = config - self.sys_arch = sys_arch - self.make_cmd = self.set_make_command() + self.size_version = size_version - def set_make_command(self) -> str: + def infer_make_command(self) -> str: """Infer build command based on architecture and configuration.""" - if self.config == SupportedConfig.DEFAULT.value and \ - self.arch == self.sys_arch: + if self.size_version.config == SupportedConfig.DEFAULT.value and \ + self.size_version.arch == self.size_version.host_arch: return 'make -j lib CFLAGS=\'-Os \' ' - elif self.arch == SupportedArch.ARMV8_M.value and \ - self.config == SupportedConfig.TFM_MEDIUM.value: + elif self.size_version.arch == SupportedArch.ARMV8_M.value and \ + self.size_version.config == SupportedConfig.TFM_MEDIUM.value: return \ 'make -j lib CC=armclang \ CFLAGS=\'--target=arm-arm-none-eabi -mcpu=cortex-m33 -Os \ @@ -113,13 +112,13 @@ def set_make_command(self) -> str: -DMBEDTLS_PSA_CRYPTO_CONFIG_FILE=\\\"' + CONFIG_TFM_MEDIUM_PSA_CRYPTO_H + '\\\" \'' else: print("Unsupported combination of architecture: {} and configuration: {}" - .format(self.arch, self.config)) + .format(self.size_version.arch, self.size_version.config)) print("\nPlease use supported combination of architecture and configuration:") - for comb in CodeSizeInfo.SupportedArchConfig: + for comb in CodeSizeBuildInfo.SupportedArchConfig: print(comb) print("\nFor your system, please use:") - for comb in CodeSizeInfo.SupportedArchConfig: - if "default" in comb and self.sys_arch not in comb: + for comb in CodeSizeBuildInfo.SupportedArchConfig: + if "default" in comb and self.size_version.host_arch not in comb: continue print(comb) sys.exit(1) @@ -433,16 +432,14 @@ class CodeSizeComparison: def __init__( self, - old_revision: str, - new_revision: str, + old_size_version: SimpleNamespace, + new_size_version: SimpleNamespace, result_dir: str, - code_size_info: CodeSizeInfo ) -> None: """ old_revision: revision to compare against. new_revision: result_dir: directory for comparison result. - code_size_info: an object containing information to build library. """ self.repo_path = "." self.result_dir = os.path.abspath(result_dir) @@ -451,57 +448,73 @@ def __init__( self.csv_dir = os.path.abspath("code_size_records/") os.makedirs(self.csv_dir, exist_ok=True) - self.old_rev = old_revision - self.new_rev = new_revision + self.old_size_version = old_size_version + self.new_size_version = new_size_version + self.old_size_version.make_cmd = \ + CodeSizeBuildInfo(self.old_size_version).infer_make_command() + self.new_size_version.make_cmd = \ + CodeSizeBuildInfo(self.new_size_version).infer_make_command() self.git_command = "git" self.make_clean = 'make clean' - self.make_cmd = code_size_info.make_cmd - self.fname_suffix = "-" + code_size_info.arch + "-" +\ - code_size_info.config self.code_size_generator = CodeSizeGeneratorWithSize() - def cal_code_size(self, revision: str): + @staticmethod + def cal_code_size(size_version: SimpleNamespace): """Calculate code size of library objects in a UTF-8 encoding""" - return CodeSizeCalculator(revision, self.make_cmd).\ + return CodeSizeCalculator(size_version.revision, size_version.make_cmd).\ cal_libraries_code_size() - def gen_code_size_report(self, revision): + @staticmethod + def gen_file_name(old_size_version, new_size_version=None): + if new_size_version: + return '{}-{}-{}-{}-{}-{}.csv'\ + .format(old_size_version.revision[:7], + old_size_version.arch, old_size_version.config, + new_size_version.revision[:7], + new_size_version.arch, new_size_version.config) + else: + return '{}-{}-{}.csv'\ + .format(old_size_version.revision[:7], + old_size_version.arch, old_size_version.config) + + def gen_code_size_report(self, size_version: SimpleNamespace): """Generate code size record and write it into a file.""" - output_file = os.path.join(self.csv_dir,\ - revision + self.fname_suffix + ".csv") + output_file = os.path.join(self.csv_dir, self.gen_file_name(size_version)) # Check if the corresponding record exists - if (revision != "current") and os.path.exists(output_file): - print("Code size csv file for", revision, "already exists.") - self.code_size_generator.read_size_record(revision, output_file) + if (size_version.revision != "current") and os.path.exists(output_file): + print("Code size csv file for", size_version.revision, "already exists.") + self.code_size_generator.read_size_record(size_version.revision, output_file) else: - self.code_size_generator.size_generator_write_record(revision,\ - self.cal_code_size(revision), output_file) + self.code_size_generator.size_generator_write_record(\ + size_version.revision, self.cal_code_size(size_version), + output_file) def gen_code_size_comparison(self) -> int: """Generate results of code size changes between two revisions, old and new. Measured code size results of these two revisions must be available.""" - output_file = os.path.join(self.result_dir, "compare-" + - self.old_rev + "-" + self.new_rev + - self.fname_suffix + ".csv") + output_file = os.path.join(self.result_dir,\ + self.gen_file_name(self.old_size_version, self.new_size_version)) print("\nGenerating comparison results between",\ - self.old_rev, "and", self.new_rev) + self.old_size_version.revision, "and", self.new_size_version.revision) self.code_size_generator.size_generator_write_comparison(\ - self.old_rev, self.new_rev, output_file) + self.old_size_version.revision, self.new_size_version.revision,\ + output_file) return 0 def get_comparision_results(self) -> int: """Compare size of library/*.o between self.old_rev and self.new_rev, and generate the result file.""" build_tree.check_repo_path() - self.gen_code_size_report(self.old_rev) - self.gen_code_size_report(self.new_rev) + self.gen_code_size_report(self.old_size_version) + self.gen_code_size_report(self.new_size_version) return self.gen_code_size_comparison() + def main(): parser = argparse.ArgumentParser(description=(__doc__)) group_required = parser.add_argument_group( @@ -547,13 +560,25 @@ def main(): else: new_revision = "current" - code_size_info = CodeSizeInfo(comp_args.arch, comp_args.config, - detect_arch()) - print("Measure code size for architecture: {}, configuration: {}\n" - .format(code_size_info.arch, code_size_info.config)) - result_dir = comp_args.result_dir - size_compare = CodeSizeComparison(old_revision, new_revision, result_dir, - code_size_info) + old_size_version = SimpleNamespace( + version="old", + revision=old_revision, + config=comp_args.config, + arch=comp_args.arch, + host_arch=detect_arch(), + make_cmd='', + ) + new_size_version = SimpleNamespace( + version="new", + revision=new_revision, + config=comp_args.config, + arch=comp_args.arch, + host_arch=detect_arch(), + make_cmd='', + ) + + size_compare = CodeSizeComparison(old_size_version, new_size_version,\ + comp_args.result_dir) return_code = size_compare.get_comparision_results() sys.exit(return_code) From 802af160b44b1e5993043aa7a0d0468c8a641d01 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Mon, 17 Jul 2023 14:04:30 +0800 Subject: [PATCH 06/41] code_size_compare: support to measure code size with multiple tools For time being, code_size_compare.py only supports `size`. This commit builds up foundation to extend code size measurement with other tools. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 68 ++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 8f3730f240ad..6b2b3a9c4c7e 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -88,20 +88,25 @@ class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods "-a " + SupportedArch.ARMV8_M.value + " -c " + SupportedConfig.TFM_MEDIUM.value, ] - def __init__(self, size_version: SimpleNamespace) -> None: + def __init__( + self, + size_version: SimpleNamespace, + host_arch: str + ) -> None: """ size_version: SimpleNamespace containing info for code size measurement. size_version.arch: architecture to measure code size on. size_version.config: configuration type to measure code size with. - size_version.host_arch: host architecture. + host_arch: host architecture. """ self.size_version = size_version + self.host_arch = host_arch def infer_make_command(self) -> str: """Infer build command based on architecture and configuration.""" if self.size_version.config == SupportedConfig.DEFAULT.value and \ - self.size_version.arch == self.size_version.host_arch: + self.size_version.arch == self.host_arch: return 'make -j lib CFLAGS=\'-Os \' ' elif self.size_version.arch == SupportedArch.ARMV8_M.value and \ self.size_version.config == SupportedConfig.TFM_MEDIUM.value: @@ -118,7 +123,7 @@ def infer_make_command(self) -> str: print(comb) print("\nFor your system, please use:") for comb in CodeSizeBuildInfo.SupportedArchConfig: - if "default" in comb and self.size_version.host_arch not in comb: + if "default" in comb and self.host_arch not in comb: continue print(comb) sys.exit(1) @@ -133,10 +138,12 @@ def __init__( self, revision: str, make_cmd: str, + measure_cmd: str ) -> None: """ revision: Git revision.(E.g: commit) - make_cmd: command to build library objects. + make_cmd: command to build objects in library. + measure_cmd: command to measure code size for objects in library. """ self.repo_path = "." self.git_command = "git" @@ -144,6 +151,7 @@ def __init__( self.revision = revision self.make_cmd = make_cmd + self.measure_cmd = measure_cmd @staticmethod def validate_revision(revision: str) -> bytes: @@ -196,8 +204,8 @@ def _gen_raw_code_size(self, revision, git_worktree_path): for mod, st_lib in MBEDTLS_STATIC_LIB.items(): try: result = subprocess.check_output( - ["size", st_lib, "-t"], cwd=git_worktree_path, - universal_newlines=True + [self.measure_cmd + ' ' + st_lib], cwd=git_worktree_path, + shell=True, universal_newlines=True ) res[mod] = result except subprocess.CalledProcessError as e: @@ -434,6 +442,7 @@ def __init__( self, old_size_version: SimpleNamespace, new_size_version: SimpleNamespace, + code_size_common: SimpleNamespace, result_dir: str, ) -> None: """ @@ -450,33 +459,46 @@ def __init__( self.old_size_version = old_size_version self.new_size_version = new_size_version + self.code_size_common = code_size_common self.old_size_version.make_cmd = \ - CodeSizeBuildInfo(self.old_size_version).infer_make_command() + CodeSizeBuildInfo(self.old_size_version,\ + self.code_size_common.host_arch).infer_make_command() self.new_size_version.make_cmd = \ - CodeSizeBuildInfo(self.new_size_version).infer_make_command() + CodeSizeBuildInfo(self.new_size_version,\ + self.code_size_common.host_arch).infer_make_command() self.git_command = "git" self.make_clean = 'make clean' - self.code_size_generator = CodeSizeGeneratorWithSize() + self.code_size_generator = self.__init_code_size_generator__(\ + self.code_size_common.measure_cmd) @staticmethod - def cal_code_size(size_version: SimpleNamespace): + def __init_code_size_generator__(measure_cmd): + if re.match(r'size', measure_cmd.strip()): + return CodeSizeGeneratorWithSize() + else: + print("Error: unsupported tool:", measure_cmd.strip().split(' ')[0]) + sys.exit(1) + + + def cal_code_size(self, size_version: SimpleNamespace): """Calculate code size of library objects in a UTF-8 encoding""" - return CodeSizeCalculator(size_version.revision, size_version.make_cmd).\ - cal_libraries_code_size() + return CodeSizeCalculator(size_version.revision, size_version.make_cmd,\ + self.code_size_common.measure_cmd).cal_libraries_code_size() - @staticmethod - def gen_file_name(old_size_version, new_size_version=None): + def gen_file_name(self, old_size_version, new_size_version=None): if new_size_version: - return '{}-{}-{}-{}-{}-{}.csv'\ + return '{}-{}-{}-{}-{}-{}-{}.csv'\ .format(old_size_version.revision[:7], old_size_version.arch, old_size_version.config, new_size_version.revision[:7], - new_size_version.arch, new_size_version.config) + new_size_version.arch, new_size_version.config, + self.code_size_common.measure_cmd.strip().split(' ')[0]) else: - return '{}-{}-{}.csv'\ + return '{}-{}-{}-{}.csv'\ .format(old_size_version.revision[:7], - old_size_version.arch, old_size_version.config) + old_size_version.arch, old_size_version.config, + self.code_size_common.measure_cmd.strip().split(' ')[0]) def gen_code_size_report(self, size_version: SimpleNamespace): """Generate code size record and write it into a file.""" @@ -565,7 +587,6 @@ def main(): revision=old_revision, config=comp_args.config, arch=comp_args.arch, - host_arch=detect_arch(), make_cmd='', ) new_size_version = SimpleNamespace( @@ -573,12 +594,15 @@ def main(): revision=new_revision, config=comp_args.config, arch=comp_args.arch, - host_arch=detect_arch(), make_cmd='', ) + code_size_common = SimpleNamespace( + host_arch=detect_arch(), + measure_cmd='size -t', + ) size_compare = CodeSizeComparison(old_size_version, new_size_version,\ - comp_args.result_dir) + code_size_common, comp_args.result_dir) return_code = size_compare.get_comparision_results() sys.exit(return_code) From 9b174e90d3786ba6ef3f14c822e39d2f9ad8a7f8 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Mon, 17 Jul 2023 17:59:53 +0800 Subject: [PATCH 07/41] code_size_compare: generate text,data as comparison result Previously we used dec(total) as comparison result of code size measurement. However, it's not accurate because dec(total) is the sum of text, data and bss. Now we show text,data instead since those are sections we care about in code size perspective specifically for TF-M. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 48 +++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 6b2b3a9c4c7e..e679af0a5fa6 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -300,7 +300,7 @@ def __init__(self, text, data, bss, dec): def __init__(self) -> None: """ Variable code_size is used to store size info for any revisions. code_size: (data format) - {revision: {module: {file_name: SizeEntry, + {revision: {module: {file_name: [text, data, bss, dec], etc ... }, etc ... @@ -318,8 +318,9 @@ def set_size_record(self, revision: str, mod: str, size_text: str) -> None: size_record = {} for line in size_text.splitlines()[1:]: data = line.split() - size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry(\ - data[0], data[1], data[2], data[3]) + # file_name: SizeEntry(text, data, bss, dec) + size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry( + data[0], data[1], data[2], data[3]) if revision in self.code_size: self.code_size[revision].update({mod: size_record}) else: @@ -341,8 +342,8 @@ def read_size_record(self, revision: str, fname: str) -> None: continue if mod: - size_record[data[0]] = \ - CodeSizeGeneratorWithSize.SizeEntry(\ + # file_name: SizeEntry(text, data, bss, dec) + size_record[data[0]] = CodeSizeGeneratorWithSize.SizeEntry( data[1], data[2], data[3], data[4]) # check if we hit record for the end of a module @@ -390,24 +391,43 @@ def write_comparison( ) -> None: """Write comparison result into a file. - Writing Format: file_name current(total) old(total) change(Byte) change_pct(%) + Writing Format: file_name current(text,data) old(text,data)\ + change(text,data) change_pct%(text,data) """ - output.write("{:<30} {:>7} {:>7} {:>7} {:>7}\n" - .format("filename", "current", "old", "change", "change%")) - for mod, fname, size_entry in self._size_reader_helper(new_rev, output): - new_size = int(size_entry.total) + + def cal_size_section_variation(mod, fname, size_entry, attr): + new_size = int(size_entry.__dict__[attr]) # check if we have the file in old revision if fname in self.code_size[old_rev][mod]: - old_size = int(self.code_size[old_rev][mod][fname].total) + old_size = int(self.code_size[old_rev][mod][fname].__dict__[attr]) change = new_size - old_size if old_size != 0: change_pct = change / old_size else: change_pct = 0 - output.write("{:<30} {:>7} {:>7} {:>7} {:>7.2%}\n" - .format(fname, new_size, old_size, change, change_pct)) + return [new_size, old_size, change, change_pct] + else: + return [new_size] + + output.write("{:<30} {:<18} {:<14} {:<17} {:<18}\n" + .format("filename", "current(text,data)", "old(text,data)",\ + "change(text,data)", "change%(text,data)")) + for mod, fname, size_entry in self._size_reader_helper(new_rev, output): + text_vari = cal_size_section_variation(mod, fname, size_entry, 'text') + data_vari = cal_size_section_variation(mod, fname, size_entry, 'data') + + if len(text_vari) != 1: + output.write("{:<30} {:<18} {:<14} {:<17} {:<18}\n" + .format(fname,\ + str(text_vari[0]) + "," + str(data_vari[0]),\ + str(text_vari[1]) + "," + str(data_vari[1]),\ + str(text_vari[2]) + "," + str(data_vari[2]),\ + "{:.2%}".format(text_vari[3]) + "," +\ + "{:.2%}".format(data_vari[3]))) else: - output.write("{} {}\n".format(fname, new_size)) + output.write("{:<30} {:<18}\n" + .format(fname,\ + str(text_vari[0]) + "," + str(data_vari[0]))) def size_generator_write_record( self, From b664cb75695869800b24eef629175ee5d223eec0 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Tue, 18 Jul 2023 12:28:35 +0800 Subject: [PATCH 08/41] code_size_compare: add --markdown to show result in a markdown table The option --markdown supports to only show the files that have changed in a markdown table between two commits. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 82 ++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index e679af0a5fa6..e42a6603bfc3 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -274,7 +274,8 @@ def size_generator_write_comparison( self, old_rev: str, new_rev: str, - output_stream + output_stream, + with_markdown=False ) -> None: """Write a comparision result into a stream between two revisions. @@ -282,6 +283,8 @@ def size_generator_write_comparison( new_rev: new git revision to compared with. output_stream: stream which the code size record is written to. (E.g: file / sys.stdout) + with_markdown: write comparision result in a markdown table. + (Default: False) """ raise NotImplementedError @@ -359,11 +362,13 @@ def read_size_record(self, revision: str, fname: str) -> None: def _size_reader_helper( self, revision: str, - output: typing_util.Writable + output: typing_util.Writable, + with_markdown=False ) -> typing.Iterator[tuple]: """A helper function to peel code_size based on revision.""" for mod, file_size in self.code_size[revision].items(): - output.write("\n" + mod + "\n") + if not with_markdown: + output.write("\n" + mod + "\n") for fname, size_entry in file_size.items(): yield mod, fname, size_entry @@ -376,18 +381,20 @@ def write_size_record( Writing Format: file_name text data bss total(dec) """ - output.write("{:<30} {:>7} {:>7} {:>7} {:>7}\n" - .format("filename", "text", "data", "bss", "total")) + format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n" + output.write(format_string.format("filename", + "text", "data", "bss", "total")) for _, fname, size_entry in self._size_reader_helper(revision, output): - output.write("{:<30} {:>7} {:>7} {:>7} {:>7}\n" - .format(fname, size_entry.text, size_entry.data,\ - size_entry.bss, size_entry.total)) + output.write(format_string.format(fname, + size_entry.text, size_entry.data, + size_entry.bss, size_entry.total)) def write_comparison( self, old_rev: str, new_rev: str, - output: typing_util.Writable + output: typing_util.Writable, + with_markdown: bool ) -> None: """Write comparison result into a file. @@ -409,25 +416,38 @@ def cal_size_section_variation(mod, fname, size_entry, attr): else: return [new_size] - output.write("{:<30} {:<18} {:<14} {:<17} {:<18}\n" - .format("filename", "current(text,data)", "old(text,data)",\ - "change(text,data)", "change%(text,data)")) - for mod, fname, size_entry in self._size_reader_helper(new_rev, output): - text_vari = cal_size_section_variation(mod, fname, size_entry, 'text') - data_vari = cal_size_section_variation(mod, fname, size_entry, 'data') + if with_markdown: + format_string = "| {:<30} | {:<18} | {:<14} | {:<17} | {:<18} |\n" + else: + format_string = "{:<30} {:<18} {:<14} {:<17} {:<18}\n" + + output.write(format_string.format("filename", "current(text,data)",\ + "old(text,data)", "change(text,data)", "change%(text,data)")) + if with_markdown: + output.write(format_string + .format("----:", "----:", "----:", "----:", "----:")) + + for mod, fname, size_entry in\ + self._size_reader_helper(new_rev, output, with_markdown): + text_vari = cal_size_section_variation(mod, fname, + size_entry, 'text') + data_vari = cal_size_section_variation(mod, fname, + size_entry, 'data') if len(text_vari) != 1: - output.write("{:<30} {:<18} {:<14} {:<17} {:<18}\n" - .format(fname,\ - str(text_vari[0]) + "," + str(data_vari[0]),\ - str(text_vari[1]) + "," + str(data_vari[1]),\ - str(text_vari[2]) + "," + str(data_vari[2]),\ - "{:.2%}".format(text_vari[3]) + "," +\ - "{:.2%}".format(data_vari[3]))) + # skip the files that haven't changed in code size if we write + # comparison result in a markdown table. + if with_markdown and text_vari[2] == 0 and data_vari[2] == 0: + continue + output.write(format_string.format(fname,\ + str(text_vari[0]) + "," + str(data_vari[0]),\ + str(text_vari[1]) + "," + str(data_vari[1]),\ + str(text_vari[2]) + "," + str(data_vari[2]),\ + "{:.2%}".format(text_vari[3]) + "," +\ + "{:.2%}".format(data_vari[3]))) else: - output.write("{:<30} {:<18}\n" - .format(fname,\ - str(text_vari[0]) + "," + str(data_vari[0]))) + output.write("{:<30} {:<18}\n".format(fname,\ + str(text_vari[0]) + "," + str(data_vari[0]))) def size_generator_write_record( self, @@ -448,11 +468,12 @@ def size_generator_write_comparison( self, old_rev: str, new_rev: str, - output_stream + output_stream, + with_markdown=False ) -> None: """Write a comparision result into a stream between two revisions.""" output = open(output_stream, "w") - self.write_comparison(old_rev, new_rev, output) + self.write_comparison(old_rev, new_rev, output, with_markdown) class CodeSizeComparison: @@ -545,7 +566,7 @@ def gen_code_size_comparison(self) -> int: self.old_size_version.revision, "and", self.new_size_version.revision) self.code_size_generator.size_generator_write_comparison(\ self.old_size_version.revision, self.new_size_version.revision,\ - output_file) + output_file, self.code_size_common.with_markdown) return 0 def get_comparision_results(self) -> int: @@ -587,6 +608,10 @@ def main(): choices=list(map(lambda s: s.value, SupportedConfig)), help="specify configuration type for code size comparison,\ default is the current MbedTLS configuration.") + group_optional.add_argument( + '--markdown', action='store_true', dest='markdown', + help="Show comparision of code size in a markdown table\ + (only show the files that have changed).") comp_args = parser.parse_args() if os.path.isfile(comp_args.result_dir): @@ -619,6 +644,7 @@ def main(): code_size_common = SimpleNamespace( host_arch=detect_arch(), measure_cmd='size -t', + with_markdown=comp_args.markdown ) size_compare = CodeSizeComparison(old_size_version, new_size_version,\ From 227576aaa4b18bd8e7408fdac9ad9df824535fc9 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Tue, 18 Jul 2023 14:35:05 +0800 Subject: [PATCH 09/41] code_size_compare: add option --stdout to show result in sys.stdout Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index e42a6603bfc3..0bd914396148 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -275,7 +275,7 @@ def size_generator_write_comparison( old_rev: str, new_rev: str, output_stream, - with_markdown=False + result_options: SimpleNamespace ) -> None: """Write a comparision result into a stream between two revisions. @@ -283,8 +283,9 @@ def size_generator_write_comparison( new_rev: new git revision to compared with. output_stream: stream which the code size record is written to. (E.g: file / sys.stdout) - with_markdown: write comparision result in a markdown table. - (Default: False) + result_options: SimpleNamespace containing options for comparison result. + with_markdown: write comparision result in a markdown table. (Default: False) + stdout: direct comparison result into sys.stdout. (Default: False) """ raise NotImplementedError @@ -469,11 +470,14 @@ def size_generator_write_comparison( old_rev: str, new_rev: str, output_stream, - with_markdown=False + result_options: SimpleNamespace ) -> None: """Write a comparision result into a stream between two revisions.""" - output = open(output_stream, "w") - self.write_comparison(old_rev, new_rev, output, with_markdown) + if result_options.stdout: + output = sys.stdout + else: + output = open(output_stream, "w") + self.write_comparison(old_rev, new_rev, output, result_options.with_markdown) class CodeSizeComparison: @@ -484,7 +488,6 @@ def __init__( old_size_version: SimpleNamespace, new_size_version: SimpleNamespace, code_size_common: SimpleNamespace, - result_dir: str, ) -> None: """ old_revision: revision to compare against. @@ -492,7 +495,7 @@ def __init__( result_dir: directory for comparison result. """ self.repo_path = "." - self.result_dir = os.path.abspath(result_dir) + self.result_dir = os.path.abspath(code_size_common.result_options.result_dir) os.makedirs(self.result_dir, exist_ok=True) self.csv_dir = os.path.abspath("code_size_records/") @@ -566,7 +569,7 @@ def gen_code_size_comparison(self) -> int: self.old_size_version.revision, "and", self.new_size_version.revision) self.code_size_generator.size_generator_write_comparison(\ self.old_size_version.revision, self.new_size_version.revision,\ - output_file, self.code_size_common.with_markdown) + output_file, self.code_size_common.result_options) return 0 def get_comparision_results(self) -> int: @@ -612,6 +615,10 @@ def main(): '--markdown', action='store_true', dest='markdown', help="Show comparision of code size in a markdown table\ (only show the files that have changed).") + group_optional.add_argument( + '--stdout', action='store_true', dest='stdout', + help="Set this option to direct comparison result into sys.stdout.\ + (Default: file)") comp_args = parser.parse_args() if os.path.isfile(comp_args.result_dir): @@ -642,13 +649,17 @@ def main(): make_cmd='', ) code_size_common = SimpleNamespace( + result_options=SimpleNamespace( + result_dir=comp_args.result_dir, + with_markdown=comp_args.markdown, + stdout=comp_args.stdout, + ), host_arch=detect_arch(), measure_cmd='size -t', - with_markdown=comp_args.markdown ) size_compare = CodeSizeComparison(old_size_version, new_size_version,\ - code_size_common, comp_args.result_dir) + code_size_common) return_code = size_compare.get_comparision_results() sys.exit(return_code) From 21127f709546fe5d08d6f683e5ebc21a3a214510 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Wed, 19 Jul 2023 12:09:45 +0800 Subject: [PATCH 10/41] code_size_compare: add logging module and tweak prompt message Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 164 +++++++++++++++++--------- scripts/mbedtls_dev/logging_util.py | 55 +++++++++ tests/scripts/audit-validity-dates.py | 36 +----- 3 files changed, 163 insertions(+), 92 deletions(-) create mode 100644 scripts/mbedtls_dev/logging_util.py diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 0bd914396148..dc41d262d5ae 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -24,6 +24,7 @@ # limitations under the License. import argparse +import logging import os import re import subprocess @@ -32,8 +33,9 @@ from enum import Enum from types import SimpleNamespace -from mbedtls_dev import typing_util from mbedtls_dev import build_tree +from mbedtls_dev import logging_util +from mbedtls_dev import typing_util class SupportedArch(Enum): """Supported architecture for code size measurement.""" @@ -91,7 +93,8 @@ class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods def __init__( self, size_version: SimpleNamespace, - host_arch: str + host_arch: str, + logger: logging.Logger, ) -> None: """ size_version: SimpleNamespace containing info for code size measurement. @@ -101,6 +104,7 @@ def __init__( """ self.size_version = size_version self.host_arch = host_arch + self.logger = logger def infer_make_command(self) -> str: """Infer build command based on architecture and configuration.""" @@ -116,16 +120,20 @@ def infer_make_command(self) -> str: -DMBEDTLS_CONFIG_FILE=\\\"' + CONFIG_TFM_MEDIUM_MBEDCRYPTO_H + '\\\" \ -DMBEDTLS_PSA_CRYPTO_CONFIG_FILE=\\\"' + CONFIG_TFM_MEDIUM_PSA_CRYPTO_H + '\\\" \'' else: - print("Unsupported combination of architecture: {} and configuration: {}" - .format(self.size_version.arch, self.size_version.config)) - print("\nPlease use supported combination of architecture and configuration:") + self.logger.error("Unsupported combination of architecture: {} " \ + "and configuration: {}.\n" + .format(self.size_version.arch, + self.size_version.config)) + self.logger.info("Please use supported combination of " \ + "architecture and configuration:") for comb in CodeSizeBuildInfo.SupportedArchConfig: - print(comb) - print("\nFor your system, please use:") + self.logger.info(comb) + self.logger.info("") + self.logger.info("For your system, please use:") for comb in CodeSizeBuildInfo.SupportedArchConfig: if "default" in comb and self.host_arch not in comb: continue - print(comb) + self.logger.info(comb) sys.exit(1) @@ -138,7 +146,8 @@ def __init__( self, revision: str, make_cmd: str, - measure_cmd: str + measure_cmd: str, + logger: logging.Logger, ) -> None: """ revision: Git revision.(E.g: commit) @@ -152,6 +161,7 @@ def __init__( self.revision = revision self.make_cmd = make_cmd self.measure_cmd = measure_cmd + self.logger = logger @staticmethod def validate_revision(revision: str) -> bytes: @@ -159,19 +169,21 @@ def validate_revision(revision: str) -> bytes: revision + "^{commit}"], shell=False) return result - def _create_git_worktree(self, revision: str) -> str: + def _create_git_worktree(self) -> str: """Make a separate worktree for revision. Do not modify the current worktree.""" - if revision == "current": - print("Using current work directory") + if self.revision == "current": + self.logger.debug("Using current work directory.") git_worktree_path = self.repo_path else: - print("Creating git worktree for", revision) - git_worktree_path = os.path.join(self.repo_path, "temp-" + revision) + self.logger.debug("Creating git worktree for {}." + .format(self.revision)) + git_worktree_path = os.path.join(self.repo_path, + "temp-" + self.revision) subprocess.check_output( [self.git_command, "worktree", "add", "--detach", - git_worktree_path, revision], cwd=self.repo_path, + git_worktree_path, self.revision], cwd=self.repo_path, stderr=subprocess.STDOUT ) @@ -180,6 +192,8 @@ def _create_git_worktree(self, revision: str) -> str: def _build_libraries(self, git_worktree_path: str) -> None: """Build libraries in the specified worktree.""" + self.logger.debug("Building objects of library for {}." + .format(self.revision)) my_environment = os.environ.copy() try: subprocess.check_output( @@ -193,12 +207,12 @@ def _build_libraries(self, git_worktree_path: str) -> None: except subprocess.CalledProcessError as e: self._handle_called_process_error(e, git_worktree_path) - def _gen_raw_code_size(self, revision, git_worktree_path): + def _gen_raw_code_size(self, git_worktree_path: str) -> typing.Dict: """Calculate code size with measurement tool in UTF-8 encoding.""" - if revision == "current": - print("Measuring code size in current work directory") - else: - print("Measuring code size for", revision) + + self.logger.debug("Measuring code size for {} by `{}`." + .format(self.revision, + self.measure_cmd.strip().split(' ')[0])) res = {} for mod, st_lib in MBEDTLS_STATIC_LIB.items(): @@ -216,7 +230,8 @@ def _gen_raw_code_size(self, revision, git_worktree_path): def _remove_worktree(self, git_worktree_path: str) -> None: """Remove temporary worktree.""" if git_worktree_path != self.repo_path: - print("Removing temporary worktree", git_worktree_path) + self.logger.debug("Removing temporary worktree {}." + .format(git_worktree_path)) subprocess.check_output( [self.git_command, "worktree", "remove", "--force", git_worktree_path], cwd=self.repo_path, @@ -229,9 +244,8 @@ def _handle_called_process_error(self, e: subprocess.CalledProcessError, Remove any extra worktrees so that the script may be called again.""" # Tell the user what went wrong - print("The following command: {} failed and exited with code {}" - .format(e.cmd, e.returncode)) - print("Process output:\n {}".format(str(e.output, "utf-8"))) + self.logger.error(e, exc_info=True) + self.logger.error("Process output:\n {}".format(str(e.output, "utf-8"))) # Quit gracefully by removing the existing worktree self._remove_worktree(git_worktree_path) @@ -240,10 +254,9 @@ def _handle_called_process_error(self, e: subprocess.CalledProcessError, def cal_libraries_code_size(self) -> typing.Dict: """Calculate code size of libraries by measurement tool.""" - revision = self.revision - git_worktree_path = self._create_git_worktree(revision) + git_worktree_path = self._create_git_worktree() self._build_libraries(git_worktree_path) - res = self._gen_raw_code_size(revision, git_worktree_path) + res = self._gen_raw_code_size(git_worktree_path) self._remove_worktree(git_worktree_path) return res @@ -256,6 +269,9 @@ class CodeSizeGenerator: size_generator_write_record and size_generator_write_comparison methods, then call both of them with proper arguments. """ + def __init__(self, logger: logging.Logger) -> None: + self.logger = logger + def size_generator_write_record( self, revision: str, @@ -301,7 +317,7 @@ def __init__(self, text, data, bss, dec): self.bss = bss self.total = dec # total <=> dec - def __init__(self) -> None: + def __init__(self, logger: logging.Logger) -> None: """ Variable code_size is used to store size info for any revisions. code_size: (data format) {revision: {module: {file_name: [text, data, bss, dec], @@ -312,6 +328,7 @@ def __init__(self) -> None: etc ... } """ + super().__init__(logger) self.code_size = {} #type: typing.Dict[str, typing.Dict] def set_size_record(self, revision: str, mod: str, size_text: str) -> None: @@ -458,10 +475,11 @@ def size_generator_write_record( ) -> None: """Write size record into a specified file based on Git revision and output from `size` tool.""" + self.logger.debug("Generating code size csv for {}.".format(revision)) + for mod, size_text in code_size_text.items(): self.set_size_record(revision, mod, size_text) - print("Generating code size csv for", revision) output = open(output_file, "w") self.write_size_record(revision, output) @@ -473,6 +491,9 @@ def size_generator_write_comparison( result_options: SimpleNamespace ) -> None: """Write a comparision result into a stream between two revisions.""" + self.logger.debug("Generating comparison results between {} and {}." + .format(old_rev, new_rev)) + if result_options.stdout: output = sys.stdout else: @@ -488,6 +509,7 @@ def __init__( old_size_version: SimpleNamespace, new_size_version: SimpleNamespace, code_size_common: SimpleNamespace, + logger: logging.Logger, ) -> None: """ old_revision: revision to compare against. @@ -501,36 +523,40 @@ def __init__( self.csv_dir = os.path.abspath("code_size_records/") os.makedirs(self.csv_dir, exist_ok=True) + self.logger = logger + self.old_size_version = old_size_version self.new_size_version = new_size_version self.code_size_common = code_size_common - self.old_size_version.make_cmd = \ - CodeSizeBuildInfo(self.old_size_version,\ - self.code_size_common.host_arch).infer_make_command() - self.new_size_version.make_cmd = \ - CodeSizeBuildInfo(self.new_size_version,\ - self.code_size_common.host_arch).infer_make_command() + self.old_size_version.make_cmd = CodeSizeBuildInfo( + self.old_size_version, self.code_size_common.host_arch, + self.logger).infer_make_command() + self.new_size_version.make_cmd = CodeSizeBuildInfo( + self.new_size_version, self.code_size_common.host_arch, + self.logger).infer_make_command() self.git_command = "git" self.make_clean = 'make clean' - self.code_size_generator = self.__init_code_size_generator__(\ - self.code_size_common.measure_cmd) + self.code_size_generator = self.__generate_size_parser() - @staticmethod - def __init_code_size_generator__(measure_cmd): - if re.match(r'size', measure_cmd.strip()): - return CodeSizeGeneratorWithSize() + def __generate_size_parser(self): + if re.match(r'size', self.code_size_common.measure_cmd.strip()): + return CodeSizeGeneratorWithSize(self.logger) else: - print("Error: unsupported tool:", measure_cmd.strip().split(' ')[0]) + self.logger.error("Unsupported measurement tool: `{}`." + .format(self.code_size_common.measure_cmd + .strip().split(' ')[0])) sys.exit(1) def cal_code_size(self, size_version: SimpleNamespace): """Calculate code size of library objects in a UTF-8 encoding""" - return CodeSizeCalculator(size_version.revision, size_version.make_cmd,\ - self.code_size_common.measure_cmd).cal_libraries_code_size() + return CodeSizeCalculator(size_version.revision, size_version.make_cmd, + self.code_size_common.measure_cmd, + self.logger).cal_libraries_code_size() def gen_file_name(self, old_size_version, new_size_version=None): + """Generate a literal string as csv file name.""" if new_size_version: return '{}-{}-{}-{}-{}-{}-{}.csv'\ .format(old_size_version.revision[:7], @@ -547,11 +573,17 @@ def gen_file_name(self, old_size_version, new_size_version=None): def gen_code_size_report(self, size_version: SimpleNamespace): """Generate code size record and write it into a file.""" - output_file = os.path.join(self.csv_dir, self.gen_file_name(size_version)) + self.logger.info("Start to generate code size record for {}." + .format(size_version.revision)) + output_file = os.path.join(self.csv_dir, + self.gen_file_name(size_version)) # Check if the corresponding record exists - if (size_version.revision != "current") and os.path.exists(output_file): - print("Code size csv file for", size_version.revision, "already exists.") - self.code_size_generator.read_size_record(size_version.revision, output_file) + if size_version.revision != "current" and \ + os.path.exists(output_file): + self.logger.debug("Code size csv file for {} already exists." + .format(size_version.revision)) + self.code_size_generator.read_size_record( + size_version.revision, output_file) else: self.code_size_generator.size_generator_write_record(\ size_version.revision, self.cal_code_size(size_version), @@ -562,14 +594,18 @@ def gen_code_size_comparison(self) -> int: old and new. Measured code size results of these two revisions must be available.""" - output_file = os.path.join(self.result_dir,\ - self.gen_file_name(self.old_size_version, self.new_size_version)) + self.logger.info("Start to generate comparision result between "\ + "{} and {}." + .format(self.old_size_version.revision, + self.new_size_version.revision)) + output_file = os.path.join( + self.result_dir, + self.gen_file_name(self.old_size_version, self.new_size_version)) + + self.code_size_generator.size_generator_write_comparison( + self.old_size_version.revision, self.new_size_version.revision, + output_file, self.code_size_common.result_options) - print("\nGenerating comparison results between",\ - self.old_size_version.revision, "and", self.new_size_version.revision) - self.code_size_generator.size_generator_write_comparison(\ - self.old_size_version.revision, self.new_size_version.revision,\ - output_file, self.code_size_common.result_options) return 0 def get_comparision_results(self) -> int: @@ -619,10 +655,17 @@ def main(): '--stdout', action='store_true', dest='stdout', help="Set this option to direct comparison result into sys.stdout.\ (Default: file)") + group_optional.add_argument( + '--verbose', action='store_true', dest='verbose', + help="Show logs in detail for code size measurement. (Default: False)") comp_args = parser.parse_args() + logger = logging.getLogger() + logging_util.configure_logger(logger) + logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO) + if os.path.isfile(comp_args.result_dir): - print("Error: {} is not a directory".format(comp_args.result_dir)) + logger.error("{} is not a directory".format(comp_args.result_dir)) parser.exit() validate_res = CodeSizeCalculator.validate_revision(comp_args.old_rev) @@ -658,11 +701,16 @@ def main(): measure_cmd='size -t', ) + logger.info("Measure code size between {}:{}-{} and {}:{}-{} by `{}`." + .format(old_size_version.revision, old_size_version.config, + old_size_version.arch, + new_size_version.revision, old_size_version.config, + new_size_version.arch, + code_size_common.measure_cmd.strip().split(' ')[0])) size_compare = CodeSizeComparison(old_size_version, new_size_version,\ - code_size_common) + code_size_common, logger) return_code = size_compare.get_comparision_results() sys.exit(return_code) - if __name__ == "__main__": main() diff --git a/scripts/mbedtls_dev/logging_util.py b/scripts/mbedtls_dev/logging_util.py new file mode 100644 index 000000000000..962361a49532 --- /dev/null +++ b/scripts/mbedtls_dev/logging_util.py @@ -0,0 +1,55 @@ +"""Auxiliary functions used for logging module. +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import sys + +def configure_logger( + logger: logging.Logger, + logger_format="[%(levelname)s]: %(message)s" + ) -> None: + """ + Configure the logging.Logger instance so that: + - Format is set to any logger_format. + Default: "[%(levelname)s]: %(message)s" + - loglevel >= WARNING are printed to stderr. + - loglevel < WARNING are printed to stdout. + """ + class MaxLevelFilter(logging.Filter): + # pylint: disable=too-few-public-methods + def __init__(self, max_level, name=''): + super().__init__(name) + self.max_level = max_level + + def filter(self, record: logging.LogRecord) -> bool: + return record.levelno <= self.max_level + + log_formatter = logging.Formatter(logger_format) + + # set loglevel >= WARNING to be printed to stderr + stderr_hdlr = logging.StreamHandler(sys.stderr) + stderr_hdlr.setLevel(logging.WARNING) + stderr_hdlr.setFormatter(log_formatter) + + # set loglevel <= INFO to be printed to stdout + stdout_hdlr = logging.StreamHandler(sys.stdout) + stdout_hdlr.addFilter(MaxLevelFilter(logging.INFO)) + stdout_hdlr.setFormatter(log_formatter) + + logger.addHandler(stderr_hdlr) + logger.addHandler(stdout_hdlr) diff --git a/tests/scripts/audit-validity-dates.py b/tests/scripts/audit-validity-dates.py index 5506e40e7f83..623fd23523a4 100755 --- a/tests/scripts/audit-validity-dates.py +++ b/tests/scripts/audit-validity-dates.py @@ -24,7 +24,6 @@ """ import os -import sys import re import typing import argparse @@ -43,6 +42,7 @@ import scripts_path # pylint: disable=unused-import from mbedtls_dev import build_tree +from mbedtls_dev import logging_util def check_cryptography_version(): match = re.match(r'^[0-9]+', cryptography.__version__) @@ -393,38 +393,6 @@ def list_all(audit_data: AuditData): loc)) -def configure_logger(logger: logging.Logger) -> None: - """ - Configure the logging.Logger instance so that: - - Format is set to "[%(levelname)s]: %(message)s". - - loglevel >= WARNING are printed to stderr. - - loglevel < WARNING are printed to stdout. - """ - class MaxLevelFilter(logging.Filter): - # pylint: disable=too-few-public-methods - def __init__(self, max_level, name=''): - super().__init__(name) - self.max_level = max_level - - def filter(self, record: logging.LogRecord) -> bool: - return record.levelno <= self.max_level - - log_formatter = logging.Formatter("[%(levelname)s]: %(message)s") - - # set loglevel >= WARNING to be printed to stderr - stderr_hdlr = logging.StreamHandler(sys.stderr) - stderr_hdlr.setLevel(logging.WARNING) - stderr_hdlr.setFormatter(log_formatter) - - # set loglevel <= INFO to be printed to stdout - stdout_hdlr = logging.StreamHandler(sys.stdout) - stdout_hdlr.addFilter(MaxLevelFilter(logging.INFO)) - stdout_hdlr.setFormatter(log_formatter) - - logger.addHandler(stderr_hdlr) - logger.addHandler(stdout_hdlr) - - def main(): """ Perform argument parsing. @@ -457,7 +425,7 @@ def main(): # start main routine # setup logger logger = logging.getLogger() - configure_logger(logger) + logging_util.configure_logger(logger) logger.setLevel(logging.DEBUG if args.verbose else logging.ERROR) td_auditor = TestDataAuditor(logger) From 386c2f9e93745d8fb06b894f2c96533f519e29ab Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Thu, 20 Jul 2023 15:32:15 +0800 Subject: [PATCH 11/41] code_size_compare: clean up code to make it more readable Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 158 +++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 72 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index dc41d262d5ae..01d7b165c651 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -45,8 +45,8 @@ class SupportedArch(Enum): X86_64 = 'x86_64' X86 = 'x86' -CONFIG_TFM_MEDIUM_MBEDCRYPTO_H = "../configs/tfm_mbedcrypto_config_profile_medium.h" -CONFIG_TFM_MEDIUM_PSA_CRYPTO_H = "../configs/crypto_config_profile_medium.h" +CONFIG_TFM_MEDIUM_MBEDCRYPTO_H = '../configs/tfm_mbedcrypto_config_profile_medium.h' +CONFIG_TFM_MEDIUM_PSA_CRYPTO_H = '../configs/crypto_config_profile_medium.h' class SupportedConfig(Enum): """Supported configuration for code size measurement.""" DEFAULT = 'default' @@ -63,13 +63,13 @@ class SupportedConfig(Enum): def detect_arch() -> str: """Auto-detect host architecture.""" cc_output = subprocess.check_output(DETECT_ARCH_CMD, shell=True).decode() - if "__aarch64__" in cc_output: + if '__aarch64__' in cc_output: return SupportedArch.AARCH64.value - if "__arm__" in cc_output: + if '__arm__' in cc_output: return SupportedArch.AARCH32.value - if "__x86_64__" in cc_output: + if '__x86_64__' in cc_output: return SupportedArch.X86_64.value - if "__x86__" in cc_output: + if '__x86__' in cc_output: return SupportedArch.X86.value else: print("Unknown host architecture, cannot auto-detect arch.") @@ -83,11 +83,11 @@ class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods """ SupportedArchConfig = [ - "-a " + SupportedArch.AARCH64.value + " -c " + SupportedConfig.DEFAULT.value, - "-a " + SupportedArch.AARCH32.value + " -c " + SupportedConfig.DEFAULT.value, - "-a " + SupportedArch.X86_64.value + " -c " + SupportedConfig.DEFAULT.value, - "-a " + SupportedArch.X86.value + " -c " + SupportedConfig.DEFAULT.value, - "-a " + SupportedArch.ARMV8_M.value + " -c " + SupportedConfig.TFM_MEDIUM.value, + '-a ' + SupportedArch.AARCH64.value + ' -c ' + SupportedConfig.DEFAULT.value, + '-a ' + SupportedArch.AARCH32.value + ' -c ' + SupportedConfig.DEFAULT.value, + '-a ' + SupportedArch.X86_64.value + ' -c ' + SupportedConfig.DEFAULT.value, + '-a ' + SupportedArch.X86.value + ' -c ' + SupportedConfig.DEFAULT.value, + '-a ' + SupportedArch.ARMV8_M.value + ' -c ' + SupportedConfig.TFM_MEDIUM.value, ] def __init__( @@ -107,11 +107,13 @@ def __init__( self.logger = logger def infer_make_command(self) -> str: - """Infer build command based on architecture and configuration.""" + """Infer make command based on architecture and configuration.""" + # make command by default if self.size_version.config == SupportedConfig.DEFAULT.value and \ - self.size_version.arch == self.host_arch: + self.size_version.arch == self.host_arch: return 'make -j lib CFLAGS=\'-Os \' ' + # make command for TF-M elif self.size_version.arch == SupportedArch.ARMV8_M.value and \ self.size_version.config == SupportedConfig.TFM_MEDIUM.value: return \ @@ -119,6 +121,7 @@ def infer_make_command(self) -> str: CFLAGS=\'--target=arm-arm-none-eabi -mcpu=cortex-m33 -Os \ -DMBEDTLS_CONFIG_FILE=\\\"' + CONFIG_TFM_MEDIUM_MBEDCRYPTO_H + '\\\" \ -DMBEDTLS_PSA_CRYPTO_CONFIG_FILE=\\\"' + CONFIG_TFM_MEDIUM_PSA_CRYPTO_H + '\\\" \'' + # unsupported combinations else: self.logger.error("Unsupported combination of architecture: {} " \ "and configuration: {}.\n" @@ -164,10 +167,11 @@ def __init__( self.logger = logger @staticmethod - def validate_revision(revision: str) -> bytes: + def validate_revision(revision: str) -> str: result = subprocess.check_output(["git", "rev-parse", "--verify", - revision + "^{commit}"], shell=False) - return result + revision + "^{commit}"], shell=False, + universal_newlines=True) + return result[:7] def _create_git_worktree(self) -> str: """Make a separate worktree for revision. @@ -199,15 +203,17 @@ def _build_libraries(self, git_worktree_path: str) -> None: subprocess.check_output( self.make_clean, env=my_environment, shell=True, cwd=git_worktree_path, stderr=subprocess.STDOUT, + universal_newlines=True ) subprocess.check_output( self.make_cmd, env=my_environment, shell=True, cwd=git_worktree_path, stderr=subprocess.STDOUT, + universal_newlines=True ) except subprocess.CalledProcessError as e: self._handle_called_process_error(e, git_worktree_path) - def _gen_raw_code_size(self, git_worktree_path: str) -> typing.Dict: + def _gen_raw_code_size(self, git_worktree_path: str) -> typing.Dict[str, str]: """Calculate code size with measurement tool in UTF-8 encoding.""" self.logger.debug("Measuring code size for {} by `{}`." @@ -245,13 +251,13 @@ def _handle_called_process_error(self, e: subprocess.CalledProcessError, # Tell the user what went wrong self.logger.error(e, exc_info=True) - self.logger.error("Process output:\n {}".format(str(e.output, "utf-8"))) + self.logger.error("Process output:\n {}".format(e.output)) # Quit gracefully by removing the existing worktree self._remove_worktree(git_worktree_path) sys.exit(-1) - def cal_libraries_code_size(self) -> typing.Dict: + def cal_libraries_code_size(self) -> typing.Dict[str, str]: """Calculate code size of libraries by measurement tool.""" git_worktree_path = self._create_git_worktree() @@ -290,7 +296,7 @@ def size_generator_write_comparison( self, old_rev: str, new_rev: str, - output_stream, + output_stream: str, result_options: SimpleNamespace ) -> None: """Write a comparision result into a stream between two revisions. @@ -331,7 +337,7 @@ def __init__(self, logger: logging.Logger) -> None: super().__init__(logger) self.code_size = {} #type: typing.Dict[str, typing.Dict] - def set_size_record(self, revision: str, mod: str, size_text: str) -> None: + def _set_size_record(self, revision: str, mod: str, size_text: str) -> None: """Store size information for target revision and high-level module. size_text Format: text data bss dec hex filename @@ -390,7 +396,7 @@ def _size_reader_helper( for fname, size_entry in file_size.items(): yield mod, fname, size_entry - def write_size_record( + def _write_size_record( self, revision: str, output: typing_util.Writable @@ -407,7 +413,7 @@ def write_size_record( size_entry.text, size_entry.data, size_entry.bss, size_entry.total)) - def write_comparison( + def _write_comparison( self, old_rev: str, new_rev: str, @@ -439,13 +445,15 @@ def cal_size_section_variation(mod, fname, size_entry, attr): else: format_string = "{:<30} {:<18} {:<14} {:<17} {:<18}\n" - output.write(format_string.format("filename", "current(text,data)",\ - "old(text,data)", "change(text,data)", "change%(text,data)")) + output.write(format_string + .format("filename", + "current(text,data)", "old(text,data)", + "change(text,data)", "change%(text,data)")) if with_markdown: output.write(format_string .format("----:", "----:", "----:", "----:", "----:")) - for mod, fname, size_entry in\ + for mod, fname, size_entry in \ self._size_reader_helper(new_rev, output, with_markdown): text_vari = cal_size_section_variation(mod, fname, size_entry, 'text') @@ -457,15 +465,18 @@ def cal_size_section_variation(mod, fname, size_entry, attr): # comparison result in a markdown table. if with_markdown and text_vari[2] == 0 and data_vari[2] == 0: continue - output.write(format_string.format(fname,\ - str(text_vari[0]) + "," + str(data_vari[0]),\ - str(text_vari[1]) + "," + str(data_vari[1]),\ - str(text_vari[2]) + "," + str(data_vari[2]),\ - "{:.2%}".format(text_vari[3]) + "," +\ - "{:.2%}".format(data_vari[3]))) + output.write( + format_string + .format(fname, + str(text_vari[0]) + "," + str(data_vari[0]), + str(text_vari[1]) + "," + str(data_vari[1]), + str(text_vari[2]) + "," + str(data_vari[2]), + "{:.2%}".format(text_vari[3]) + "," + + "{:.2%}".format(data_vari[3]))) else: - output.write("{:<30} {:<18}\n".format(fname,\ - str(text_vari[0]) + "," + str(data_vari[0]))) + output.write("{:<30} {:<18}\n" + .format(fname, + str(text_vari[0]) + "," + str(data_vari[0]))) def size_generator_write_record( self, @@ -478,16 +489,16 @@ def size_generator_write_record( self.logger.debug("Generating code size csv for {}.".format(revision)) for mod, size_text in code_size_text.items(): - self.set_size_record(revision, mod, size_text) + self._set_size_record(revision, mod, size_text) output = open(output_file, "w") - self.write_size_record(revision, output) + self._write_size_record(revision, output) def size_generator_write_comparison( self, old_rev: str, new_rev: str, - output_stream, + output_stream: str, result_options: SimpleNamespace ) -> None: """Write a comparision result into a stream between two revisions.""" @@ -498,7 +509,8 @@ def size_generator_write_comparison( output = sys.stdout else: output = open(output_stream, "w") - self.write_comparison(old_rev, new_rev, output, result_options.with_markdown) + self._write_comparison(old_rev, new_rev, output, + result_options.with_markdown) class CodeSizeComparison: @@ -516,8 +528,8 @@ def __init__( new_revision: result_dir: directory for comparison result. """ - self.repo_path = "." - self.result_dir = os.path.abspath(code_size_common.result_options.result_dir) + self.result_dir = os.path.abspath( + code_size_common.result_options.result_dir) os.makedirs(self.result_dir, exist_ok=True) self.csv_dir = os.path.abspath("code_size_records/") @@ -528,14 +540,14 @@ def __init__( self.old_size_version = old_size_version self.new_size_version = new_size_version self.code_size_common = code_size_common + # infer make command self.old_size_version.make_cmd = CodeSizeBuildInfo( self.old_size_version, self.code_size_common.host_arch, self.logger).infer_make_command() self.new_size_version.make_cmd = CodeSizeBuildInfo( self.new_size_version, self.code_size_common.host_arch, self.logger).infer_make_command() - self.git_command = "git" - self.make_clean = 'make clean' + # initialize size parser with corresponding measurement tool self.code_size_generator = self.__generate_size_parser() def __generate_size_parser(self): @@ -548,29 +560,38 @@ def __generate_size_parser(self): sys.exit(1) - def cal_code_size(self, size_version: SimpleNamespace): + def cal_code_size( + self, + size_version: SimpleNamespace + ) -> typing.Dict[str, str]: """Calculate code size of library objects in a UTF-8 encoding""" return CodeSizeCalculator(size_version.revision, size_version.make_cmd, self.code_size_common.measure_cmd, self.logger).cal_libraries_code_size() - def gen_file_name(self, old_size_version, new_size_version=None): + def gen_file_name( + self, + old_size_version: SimpleNamespace, + new_size_version=None + ) -> str: """Generate a literal string as csv file name.""" if new_size_version: return '{}-{}-{}-{}-{}-{}-{}.csv'\ - .format(old_size_version.revision[:7], - old_size_version.arch, old_size_version.config, - new_size_version.revision[:7], - new_size_version.arch, new_size_version.config, - self.code_size_common.measure_cmd.strip().split(' ')[0]) + .format(old_size_version.revision, old_size_version.arch, + old_size_version.config, + new_size_version.revision, new_size_version.arch, + new_size_version.config, + self.code_size_common.measure_cmd.strip()\ + .split(' ')[0]) else: return '{}-{}-{}-{}.csv'\ - .format(old_size_version.revision[:7], - old_size_version.arch, old_size_version.config, - self.code_size_common.measure_cmd.strip().split(' ')[0]) + .format(old_size_version.revision, old_size_version.arch, + old_size_version.config, + self.code_size_common.measure_cmd.strip()\ + .split(' ')[0]) - def gen_code_size_report(self, size_version: SimpleNamespace): + def gen_code_size_report(self, size_version: SimpleNamespace) -> None: """Generate code size record and write it into a file.""" self.logger.info("Start to generate code size record for {}." @@ -585,11 +606,11 @@ def gen_code_size_report(self, size_version: SimpleNamespace): self.code_size_generator.read_size_record( size_version.revision, output_file) else: - self.code_size_generator.size_generator_write_record(\ - size_version.revision, self.cal_code_size(size_version), - output_file) + self.code_size_generator.size_generator_write_record( + size_version.revision, self.cal_code_size(size_version), + output_file) - def gen_code_size_comparison(self) -> int: + def gen_code_size_comparison(self) -> None: """Generate results of code size changes between two revisions, old and new. Measured code size results of these two revisions must be available.""" @@ -606,15 +627,13 @@ def gen_code_size_comparison(self) -> int: self.old_size_version.revision, self.new_size_version.revision, output_file, self.code_size_common.result_options) - return 0 - - def get_comparision_results(self) -> int: + def get_comparision_results(self) -> None: """Compare size of library/*.o between self.old_rev and self.new_rev, and generate the result file.""" build_tree.check_repo_path() self.gen_code_size_report(self.old_size_version) self.gen_code_size_report(self.new_size_version) - return self.gen_code_size_comparison() + self.gen_code_size_comparison() def main(): @@ -668,24 +687,21 @@ def main(): logger.error("{} is not a directory".format(comp_args.result_dir)) parser.exit() - validate_res = CodeSizeCalculator.validate_revision(comp_args.old_rev) - old_revision = validate_res.decode().replace("\n", "") - + old_revision = CodeSizeCalculator.validate_revision(comp_args.old_rev) if comp_args.new_rev is not None: - validate_res = CodeSizeCalculator.validate_revision(comp_args.new_rev) - new_revision = validate_res.decode().replace("\n", "") + new_revision = CodeSizeCalculator.validate_revision(comp_args.new_rev) else: new_revision = "current" old_size_version = SimpleNamespace( - version="old", + version='old', revision=old_revision, config=comp_args.config, arch=comp_args.arch, make_cmd='', ) new_size_version = SimpleNamespace( - version="new", + version='new', revision=new_revision, config=comp_args.config, arch=comp_args.arch, @@ -707,10 +723,8 @@ def main(): new_size_version.revision, old_size_version.config, new_size_version.arch, code_size_common.measure_cmd.strip().split(' ')[0])) - size_compare = CodeSizeComparison(old_size_version, new_size_version,\ - code_size_common, logger) - return_code = size_compare.get_comparision_results() - sys.exit(return_code) + CodeSizeComparison(old_size_version, new_size_version, + code_size_common, logger).get_comparision_results() if __name__ == "__main__": main() From 5b64e4c7e0bdbc71ab3c0cb546ac19b674f51e96 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Thu, 20 Jul 2023 15:09:51 +0800 Subject: [PATCH 12/41] code_size_compare: clean up comments Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 155 +++++++++++++++++++++-------------- 1 file changed, 94 insertions(+), 61 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 01d7b165c651..7141fb277096 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -97,10 +97,13 @@ def __init__( logger: logging.Logger, ) -> None: """ - size_version: SimpleNamespace containing info for code size measurement. - size_version.arch: architecture to measure code size on. - size_version.config: configuration type to measure code size with. - host_arch: host architecture. + :param size_version: + SimpleNamespace containing info for code size measurement. + - size_version.arch: architecture to measure code size on. + - size_version.config: configuration type to measure code size + with. + :param host_arch: host architecture. + :param logger: logging module """ self.size_version = size_version self.host_arch = host_arch @@ -141,7 +144,7 @@ def infer_make_command(self) -> str: class CodeSizeCalculator: - """ A calculator to calculate code size of library objects based on + """ A calculator to calculate code size of library/*.o based on Git revision and code size measurement tool. """ @@ -153,9 +156,10 @@ def __init__( logger: logging.Logger, ) -> None: """ - revision: Git revision.(E.g: commit) - make_cmd: command to build objects in library. - measure_cmd: command to measure code size for objects in library. + :param revision: Git revision.(E.g: commit) + :param make_cmd: command to build library/*.o. + :param measure_cmd: command to measure code size for library/*.o. + :param logger: logging module """ self.repo_path = "." self.git_command = "git" @@ -174,8 +178,8 @@ def validate_revision(revision: str) -> str: return result[:7] def _create_git_worktree(self) -> str: - """Make a separate worktree for revision. - Do not modify the current worktree.""" + """Create a separate worktree for revision. + If revision is current, use current worktree instead.""" if self.revision == "current": self.logger.debug("Using current work directory.") @@ -194,9 +198,9 @@ def _create_git_worktree(self) -> str: return git_worktree_path def _build_libraries(self, git_worktree_path: str) -> None: - """Build libraries in the specified worktree.""" + """Build library/*.o in the specified worktree.""" - self.logger.debug("Building objects of library for {}." + self.logger.debug("Building library/*.o for {}." .format(self.revision)) my_environment = os.environ.copy() try: @@ -214,7 +218,7 @@ def _build_libraries(self, git_worktree_path: str) -> None: self._handle_called_process_error(e, git_worktree_path) def _gen_raw_code_size(self, git_worktree_path: str) -> typing.Dict[str, str]: - """Calculate code size with measurement tool in UTF-8 encoding.""" + """Measure code size by a tool and return in UTF-8 encoding.""" self.logger.debug("Measuring code size for {} by `{}`." .format(self.revision, @@ -258,7 +262,12 @@ def _handle_called_process_error(self, e: subprocess.CalledProcessError, sys.exit(-1) def cal_libraries_code_size(self) -> typing.Dict[str, str]: - """Calculate code size of libraries by measurement tool.""" + """Do a complete round to calculate code size of library/*.o + by measurement tool. + + :return A dictionary of measured code size + - typing.Dict[mod: str] + """ git_worktree_path = self._create_git_worktree() self._build_libraries(git_worktree_path) @@ -269,13 +278,16 @@ def cal_libraries_code_size(self) -> typing.Dict[str, str]: class CodeSizeGenerator: - """ A generator based on size measurement tool for library objects. + """ A generator based on size measurement tool for library/*.o. This is an abstract class. To use it, derive a class that implements size_generator_write_record and size_generator_write_comparison methods, then call both of them with proper arguments. """ def __init__(self, logger: logging.Logger) -> None: + """ + :param logger: logging module + """ self.logger = logger def size_generator_write_record( @@ -286,9 +298,11 @@ def size_generator_write_record( ) -> None: """Write size record into a file. - revision: Git revision.(E.g: commit) - code_size_text: text output (utf-8) from code size measurement tool. - output_file: file which the code size record is written to. + :param revision: Git revision.(E.g: commit) + :param code_size_text: + string output (utf-8) from measurement tool of code size. + - typing.Dict[mod: str] + :param output_file: file which the code size record is written to. """ raise NotImplementedError @@ -301,13 +315,15 @@ def size_generator_write_comparison( ) -> None: """Write a comparision result into a stream between two revisions. - old_rev: old git revision to compared with. - new_rev: new git revision to compared with. - output_stream: stream which the code size record is written to. - (E.g: file / sys.stdout) - result_options: SimpleNamespace containing options for comparison result. - with_markdown: write comparision result in a markdown table. (Default: False) - stdout: direct comparison result into sys.stdout. (Default: False) + :param old_rev: old Git revision to compared with. + :param new_rev: new Git revision to compared with. + :param output_stream: stream which the code size record is written to. + :param result_options: + SimpleNamespace containing options for comparison result. + - result_options.with_markdown: write comparision result in a + markdown table. (Default: False) + - result_options.stdout: direct comparison result into + sys.stdout. (Default: False) """ raise NotImplementedError @@ -325,14 +341,15 @@ def __init__(self, text, data, bss, dec): def __init__(self, logger: logging.Logger) -> None: """ Variable code_size is used to store size info for any revisions. - code_size: (data format) - {revision: {module: {file_name: [text, data, bss, dec], - etc ... - }, - etc ... - }, - etc ... - } + :param code_size: + Data Format as following: + {revision: {module: {file_name: [text, data, bss, dec], + etc ... + }, + etc ... + }, + etc ... + } """ super().__init__(logger) self.code_size = {} #type: typing.Dict[str, typing.Dict] @@ -501,7 +518,11 @@ def size_generator_write_comparison( output_stream: str, result_options: SimpleNamespace ) -> None: - """Write a comparision result into a stream between two revisions.""" + """Write a comparision result into a stream between two revisions. + + By default, it's written into a file called output_stream. + Once result_options.stdout is set, it's written into sys.stdout instead. + """ self.logger.debug("Generating comparison results between {} and {}." .format(old_rev, new_rev)) @@ -524,9 +545,14 @@ def __init__( logger: logging.Logger, ) -> None: """ - old_revision: revision to compare against. - new_revision: - result_dir: directory for comparison result. + :param old_size_version: SimpleNamespace containing old version info + to compare code size with. + :param new_size_version: SimpleNamespace containing new version info + to take as comparision base. + :param code_size_common: SimpleNamespace containing common info for + both old and new size version, + measurement tool and result options. + :param logger: logging module """ self.result_dir = os.path.abspath( code_size_common.result_options.result_dir) @@ -551,6 +577,7 @@ def __init__( self.code_size_generator = self.__generate_size_parser() def __generate_size_parser(self): + """Generate a parser for the corresponding measurement tool.""" if re.match(r'size', self.code_size_common.measure_cmd.strip()): return CodeSizeGeneratorWithSize(self.logger) else: @@ -564,7 +591,7 @@ def cal_code_size( self, size_version: SimpleNamespace ) -> typing.Dict[str, str]: - """Calculate code size of library objects in a UTF-8 encoding""" + """Calculate code size of library/*.o in a UTF-8 encoding""" return CodeSizeCalculator(size_version.revision, size_version.make_cmd, self.code_size_common.measure_cmd, @@ -612,8 +639,12 @@ def gen_code_size_report(self, size_version: SimpleNamespace) -> None: def gen_code_size_comparison(self) -> None: """Generate results of code size changes between two revisions, - old and new. Measured code size results of these two revisions - must be available.""" + old and new. + + - Measured code size results of these two revisions must be available. + - The result is directed into either file / stdout depending on + the option, code_size_common.result_options.stdout. (Default: file) + """ self.logger.info("Start to generate comparision result between "\ "{} and {}." @@ -628,8 +659,8 @@ def gen_code_size_comparison(self) -> None: output_file, self.code_size_common.result_options) def get_comparision_results(self) -> None: - """Compare size of library/*.o between self.old_rev and self.new_rev, - and generate the result file.""" + """Compare size of library/*.o between self.old_size_version and + self.old_size_version and generate the result file.""" build_tree.check_repo_path() self.gen_code_size_report(self.old_size_version) self.gen_code_size_report(self.new_size_version) @@ -642,41 +673,43 @@ def main(): 'required arguments', 'required arguments to parse for running ' + os.path.basename(__file__)) group_required.add_argument( - "-o", "--old-rev", type=str, required=True, - help="old revision for comparison.") + '-o', '--old-rev', type=str, required=True, + help='old revision for comparison.') group_optional = parser.add_argument_group( 'optional arguments', 'optional arguments to parse for running ' + os.path.basename(__file__)) group_optional.add_argument( - "-r", "--result-dir", type=str, default="comparison", - help="directory where comparison result is stored, \ - default is comparison") + '-r', '--result-dir', type=str, default='comparison', + help='directory where comparison result is stored. ' + '(Default: comparison)') group_optional.add_argument( - "-n", "--new-rev", type=str, default=None, - help="new revision for comparison, default is the current work \ - directory, including uncommitted changes.") + '-n', '--new-rev', type=str, default=None, + help='new revision as comparison base. ' + '(Default is the current work directory, including uncommitted ' + 'changes.)') group_optional.add_argument( - "-a", "--arch", type=str, default=detect_arch(), + '-a', '--arch', type=str, default=detect_arch(), choices=list(map(lambda s: s.value, SupportedArch)), - help="specify architecture for code size comparison, default is the\ - host architecture.") + help='Specify architecture for code size comparison. ' + '(Default is the host architecture.)') group_optional.add_argument( - "-c", "--config", type=str, default=SupportedConfig.DEFAULT.value, + '-c', '--config', type=str, default=SupportedConfig.DEFAULT.value, choices=list(map(lambda s: s.value, SupportedConfig)), - help="specify configuration type for code size comparison,\ - default is the current MbedTLS configuration.") + help='Specify configuration type for code size comparison. ' + '(Default is the current MbedTLS configuration.)') group_optional.add_argument( '--markdown', action='store_true', dest='markdown', - help="Show comparision of code size in a markdown table\ - (only show the files that have changed).") + help='Show comparision of code size in a markdown table. ' + '(Only show the files that have changed).') group_optional.add_argument( '--stdout', action='store_true', dest='stdout', - help="Set this option to direct comparison result into sys.stdout.\ - (Default: file)") + help='Set this option to direct comparison result into sys.stdout. ' + '(Default: file)') group_optional.add_argument( '--verbose', action='store_true', dest='verbose', - help="Show logs in detail for code size measurement. (Default: False)") + help='Show logs in detail for code size measurement. ' + '(Default: False)') comp_args = parser.parse_args() logger = logging.getLogger() From 955671b0ef2b7faed11054c718189d7d3cd029e9 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Fri, 21 Jul 2023 12:08:27 +0800 Subject: [PATCH 13/41] code_size_compare: replace SimpleNameSpace to a clearer data struct Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 377 ++++++++++++++++++++--------------- 1 file changed, 220 insertions(+), 157 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 7141fb277096..9b58d5093cfe 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -32,7 +32,6 @@ import typing from enum import Enum -from types import SimpleNamespace from mbedtls_dev import build_tree from mbedtls_dev import logging_util from mbedtls_dev import typing_util @@ -45,6 +44,7 @@ class SupportedArch(Enum): X86_64 = 'x86_64' X86 = 'x86' + CONFIG_TFM_MEDIUM_MBEDCRYPTO_H = '../configs/tfm_mbedcrypto_config_profile_medium.h' CONFIG_TFM_MEDIUM_PSA_CRYPTO_H = '../configs/crypto_config_profile_medium.h' class SupportedConfig(Enum): @@ -52,6 +52,7 @@ class SupportedConfig(Enum): DEFAULT = 'default' TFM_MEDIUM = 'tfm-medium' + # Static library MBEDTLS_STATIC_LIB = { 'CRYPTO': 'library/libmbedcrypto.a', @@ -59,6 +60,70 @@ class SupportedConfig(Enum): 'TLS': 'library/libmbedtls.a', } +class CodeSizeDistinctInfo: # pylint: disable=too-few-public-methods + """Data structure to store possibly distinct information for code size + comparison.""" + def __init__( #pylint: disable=too-many-arguments + self, + version: str, + git_rev: str, + arch: str, + config: str, + make_cmd: str, + ) -> None: + """ + :param: version: which version to compare with for code size. + :param: git_rev: Git revision to calculate code size. + :param: arch: architecture to measure code size on. + :param: config: Configuration type to calculate code size. + (See SupportedConfig) + :param: make_cmd: make command to build library/*.o. + """ + self.version = version + self.git_rev = git_rev + self.arch = arch + self.config = config + self.make_cmd = make_cmd + + +class CodeSizeCommonInfo: # pylint: disable=too-few-public-methods + """Data structure to store common information for code size comparison.""" + def __init__( + self, + host_arch: str, + measure_cmd: str, + ) -> None: + """ + :param host_arch: host architecture. + :param measure_cmd: command to measure code size for library/*.o. + """ + self.host_arch = host_arch + self.measure_cmd = measure_cmd + + +class CodeSizeResultInfo: # pylint: disable=too-few-public-methods + """Data structure to store result options for code size comparison.""" + def __init__( + self, + record_dir: str, + comp_dir: str, + with_markdown=False, + stdout=False, + ) -> None: + """ + :param record_dir: directory to store code size record. + :param comp_dir: directory to store results of code size comparision. + :param with_markdown: write comparision result into a markdown table. + (Default: False) + :param stdout: direct comparison result into sys.stdout. + (Default False) + """ + self.record_dir = record_dir + self.comp_dir = comp_dir + self.with_markdown = with_markdown + self.stdout = stdout + + DETECT_ARCH_CMD = "cc -dM -E - < /dev/null" def detect_arch() -> str: """Auto-detect host architecture.""" @@ -92,20 +157,20 @@ class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods def __init__( self, - size_version: SimpleNamespace, + size_dist_info: CodeSizeDistinctInfo, host_arch: str, logger: logging.Logger, ) -> None: """ - :param size_version: - SimpleNamespace containing info for code size measurement. - - size_version.arch: architecture to measure code size on. - - size_version.config: configuration type to measure code size - with. + :param size_dist_info: + CodeSizeDistinctInfo containing info for code size measurement. + - size_dist_info.arch: architecture to measure code size on. + - size_dist_info.config: configuration type to measure + code size with. :param host_arch: host architecture. :param logger: logging module """ - self.size_version = size_version + self.size_dist_info = size_dist_info self.host_arch = host_arch self.logger = logger @@ -113,12 +178,12 @@ def infer_make_command(self) -> str: """Infer make command based on architecture and configuration.""" # make command by default - if self.size_version.config == SupportedConfig.DEFAULT.value and \ - self.size_version.arch == self.host_arch: + if self.size_dist_info.config == SupportedConfig.DEFAULT.value and \ + self.size_dist_info.arch == self.host_arch: return 'make -j lib CFLAGS=\'-Os \' ' # make command for TF-M - elif self.size_version.arch == SupportedArch.ARMV8_M.value and \ - self.size_version.config == SupportedConfig.TFM_MEDIUM.value: + elif self.size_dist_info.arch == SupportedArch.ARMV8_M.value and \ + self.size_dist_info.config == SupportedConfig.TFM_MEDIUM.value: return \ 'make -j lib CC=armclang \ CFLAGS=\'--target=arm-arm-none-eabi -mcpu=cortex-m33 -Os \ @@ -128,8 +193,8 @@ def infer_make_command(self) -> str: else: self.logger.error("Unsupported combination of architecture: {} " \ "and configuration: {}.\n" - .format(self.size_version.arch, - self.size_version.config)) + .format(self.size_dist_info.arch, + self.size_dist_info.config)) self.logger.info("Please use supported combination of " \ "architecture and configuration:") for comb in CodeSizeBuildInfo.SupportedArchConfig: @@ -150,13 +215,13 @@ class CodeSizeCalculator: def __init__( self, - revision: str, + git_rev: str, make_cmd: str, measure_cmd: str, logger: logging.Logger, ) -> None: """ - :param revision: Git revision.(E.g: commit) + :param git_rev: Git revision. (E.g: commit) :param make_cmd: command to build library/*.o. :param measure_cmd: command to measure code size for library/*.o. :param logger: logging module @@ -165,33 +230,33 @@ def __init__( self.git_command = "git" self.make_clean = 'make clean' - self.revision = revision + self.git_rev = git_rev self.make_cmd = make_cmd self.measure_cmd = measure_cmd self.logger = logger @staticmethod - def validate_revision(revision: str) -> str: + def validate_git_revision(git_rev: str) -> str: result = subprocess.check_output(["git", "rev-parse", "--verify", - revision + "^{commit}"], shell=False, - universal_newlines=True) + git_rev + "^{commit}"], + shell=False, universal_newlines=True) return result[:7] def _create_git_worktree(self) -> str: - """Create a separate worktree for revision. - If revision is current, use current worktree instead.""" + """Create a separate worktree for Git revision. + If Git revision is current, use current worktree instead.""" - if self.revision == "current": + if self.git_rev == "current": self.logger.debug("Using current work directory.") git_worktree_path = self.repo_path else: self.logger.debug("Creating git worktree for {}." - .format(self.revision)) + .format(self.git_rev)) git_worktree_path = os.path.join(self.repo_path, - "temp-" + self.revision) + "temp-" + self.git_rev) subprocess.check_output( [self.git_command, "worktree", "add", "--detach", - git_worktree_path, self.revision], cwd=self.repo_path, + git_worktree_path, self.git_rev], cwd=self.repo_path, stderr=subprocess.STDOUT ) @@ -201,7 +266,7 @@ def _build_libraries(self, git_worktree_path: str) -> None: """Build library/*.o in the specified worktree.""" self.logger.debug("Building library/*.o for {}." - .format(self.revision)) + .format(self.git_rev)) my_environment = os.environ.copy() try: subprocess.check_output( @@ -221,7 +286,7 @@ def _gen_raw_code_size(self, git_worktree_path: str) -> typing.Dict[str, str]: """Measure code size by a tool and return in UTF-8 encoding.""" self.logger.debug("Measuring code size for {} by `{}`." - .format(self.revision, + .format(self.git_rev, self.measure_cmd.strip().split(' ')[0])) res = {} @@ -292,13 +357,13 @@ def __init__(self, logger: logging.Logger) -> None: def size_generator_write_record( self, - revision: str, + git_rev: str, code_size_text: typing.Dict, output_file: str ) -> None: """Write size record into a file. - :param revision: Git revision.(E.g: commit) + :param git_rev: Git revision. (E.g: commit) :param code_size_text: string output (utf-8) from measurement tool of code size. - typing.Dict[mod: str] @@ -311,15 +376,15 @@ def size_generator_write_comparison( old_rev: str, new_rev: str, output_stream: str, - result_options: SimpleNamespace + result_options: CodeSizeResultInfo ) -> None: - """Write a comparision result into a stream between two revisions. + """Write a comparision result into a stream between two Git revisions. :param old_rev: old Git revision to compared with. :param new_rev: new Git revision to compared with. :param output_stream: stream which the code size record is written to. :param result_options: - SimpleNamespace containing options for comparison result. + CodeSizeResultInfo containing options for comparison result. - result_options.with_markdown: write comparision result in a markdown table. (Default: False) - result_options.stdout: direct comparison result into @@ -340,22 +405,22 @@ def __init__(self, text, data, bss, dec): self.total = dec # total <=> dec def __init__(self, logger: logging.Logger) -> None: - """ Variable code_size is used to store size info for any revisions. + """ Variable code_size is used to store size info for any Git revisions. :param code_size: Data Format as following: - {revision: {module: {file_name: [text, data, bss, dec], - etc ... - }, - etc ... - }, + {git_rev: {module: {file_name: [text, data, bss, dec], + etc ... + }, + etc ... + }, etc ... } """ super().__init__(logger) self.code_size = {} #type: typing.Dict[str, typing.Dict] - def _set_size_record(self, revision: str, mod: str, size_text: str) -> None: - """Store size information for target revision and high-level module. + def _set_size_record(self, git_rev: str, mod: str, size_text: str) -> None: + """Store size information for target Git revision and high-level module. size_text Format: text data bss dec hex filename """ @@ -365,12 +430,12 @@ def _set_size_record(self, revision: str, mod: str, size_text: str) -> None: # file_name: SizeEntry(text, data, bss, dec) size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry( data[0], data[1], data[2], data[3]) - if revision in self.code_size: - self.code_size[revision].update({mod: size_record}) + if git_rev in self.code_size: + self.code_size[git_rev].update({mod: size_record}) else: - self.code_size[revision] = {mod: size_record} + self.code_size[git_rev] = {mod: size_record} - def read_size_record(self, revision: str, fname: str) -> None: + def read_size_record(self, git_rev: str, fname: str) -> None: """Read size information from csv file and write it into code_size. fname Format: filename text data bss dec @@ -393,21 +458,21 @@ def read_size_record(self, revision: str, fname: str) -> None: # check if we hit record for the end of a module m = re.match(r'.?TOTALS', line) if m: - if revision in self.code_size: - self.code_size[revision].update({mod: size_record}) + if git_rev in self.code_size: + self.code_size[git_rev].update({mod: size_record}) else: - self.code_size[revision] = {mod: size_record} + self.code_size[git_rev] = {mod: size_record} mod = "" size_record = {} def _size_reader_helper( self, - revision: str, + git_rev: str, output: typing_util.Writable, with_markdown=False ) -> typing.Iterator[tuple]: - """A helper function to peel code_size based on revision.""" - for mod, file_size in self.code_size[revision].items(): + """A helper function to peel code_size based on Git revision.""" + for mod, file_size in self.code_size[git_rev].items(): if not with_markdown: output.write("\n" + mod + "\n") for fname, size_entry in file_size.items(): @@ -415,7 +480,7 @@ def _size_reader_helper( def _write_size_record( self, - revision: str, + git_rev: str, output: typing_util.Writable ) -> None: """Write size information to a file. @@ -425,7 +490,7 @@ def _write_size_record( format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n" output.write(format_string.format("filename", "text", "data", "bss", "total")) - for _, fname, size_entry in self._size_reader_helper(revision, output): + for _, fname, size_entry in self._size_reader_helper(git_rev, output): output.write(format_string.format(fname, size_entry.text, size_entry.data, size_entry.bss, size_entry.total)) @@ -445,7 +510,7 @@ def _write_comparison( def cal_size_section_variation(mod, fname, size_entry, attr): new_size = int(size_entry.__dict__[attr]) - # check if we have the file in old revision + # check if we have the file in old Git revision if fname in self.code_size[old_rev][mod]: old_size = int(self.code_size[old_rev][mod][fname].__dict__[attr]) change = new_size - old_size @@ -497,28 +562,28 @@ def cal_size_section_variation(mod, fname, size_entry, attr): def size_generator_write_record( self, - revision: str, + git_rev: str, code_size_text: typing.Dict, output_file: str ) -> None: """Write size record into a specified file based on Git revision and output from `size` tool.""" - self.logger.debug("Generating code size csv for {}.".format(revision)) + self.logger.debug("Generating code size csv for {}.".format(git_rev)) for mod, size_text in code_size_text.items(): - self._set_size_record(revision, mod, size_text) + self._set_size_record(git_rev, mod, size_text) output = open(output_file, "w") - self._write_size_record(revision, output) + self._write_size_record(git_rev, output) def size_generator_write_comparison( self, old_rev: str, new_rev: str, output_stream: str, - result_options: SimpleNamespace + result_options: CodeSizeResultInfo ) -> None: - """Write a comparision result into a stream between two revisions. + """Write a comparision result into a stream between two Git revisions. By default, it's written into a file called output_stream. Once result_options.stdout is set, it's written into sys.stdout instead. @@ -537,133 +602,139 @@ def size_generator_write_comparison( class CodeSizeComparison: """Compare code size between two Git revisions.""" - def __init__( + def __init__( #pylint: disable=too-many-arguments self, - old_size_version: SimpleNamespace, - new_size_version: SimpleNamespace, - code_size_common: SimpleNamespace, + old_size_dist_info: CodeSizeDistinctInfo, + new_size_dist_info: CodeSizeDistinctInfo, + size_common_info: CodeSizeCommonInfo, + result_options: CodeSizeResultInfo, logger: logging.Logger, ) -> None: """ - :param old_size_version: SimpleNamespace containing old version info - to compare code size with. - :param new_size_version: SimpleNamespace containing new version info - to take as comparision base. - :param code_size_common: SimpleNamespace containing common info for - both old and new size version, - measurement tool and result options. + :param old_size_dist_info: CodeSizeDistinctInfo containing old distinct + info to compare code size with. + :param new_size_dist_info: CodeSizeDistinctInfo containing new distinct + info to take as comparision base. + :param size_common_info: CodeSizeCommonInfo containing common info for + both old and new size distinct info and + measurement tool. + :param result_options: CodeSizeResultInfo containing results options for + code size record and comparision. :param logger: logging module """ - self.result_dir = os.path.abspath( - code_size_common.result_options.result_dir) - os.makedirs(self.result_dir, exist_ok=True) - - self.csv_dir = os.path.abspath("code_size_records/") - os.makedirs(self.csv_dir, exist_ok=True) self.logger = logger - self.old_size_version = old_size_version - self.new_size_version = new_size_version - self.code_size_common = code_size_common + self.old_size_dist_info = old_size_dist_info + self.new_size_dist_info = new_size_dist_info + self.size_common_info = size_common_info # infer make command - self.old_size_version.make_cmd = CodeSizeBuildInfo( - self.old_size_version, self.code_size_common.host_arch, + self.old_size_dist_info.make_cmd = CodeSizeBuildInfo( + self.old_size_dist_info, self.size_common_info.host_arch, self.logger).infer_make_command() - self.new_size_version.make_cmd = CodeSizeBuildInfo( - self.new_size_version, self.code_size_common.host_arch, + self.new_size_dist_info.make_cmd = CodeSizeBuildInfo( + self.new_size_dist_info, self.size_common_info.host_arch, self.logger).infer_make_command() # initialize size parser with corresponding measurement tool self.code_size_generator = self.__generate_size_parser() + self.result_options = result_options + self.csv_dir = os.path.abspath(self.result_options.record_dir) + os.makedirs(self.csv_dir, exist_ok=True) + self.comp_dir = os.path.abspath(self.result_options.comp_dir) + os.makedirs(self.comp_dir, exist_ok=True) + def __generate_size_parser(self): """Generate a parser for the corresponding measurement tool.""" - if re.match(r'size', self.code_size_common.measure_cmd.strip()): + if re.match(r'size', self.size_common_info.measure_cmd.strip()): return CodeSizeGeneratorWithSize(self.logger) else: self.logger.error("Unsupported measurement tool: `{}`." - .format(self.code_size_common.measure_cmd + .format(self.size_common_info.measure_cmd .strip().split(' ')[0])) sys.exit(1) def cal_code_size( self, - size_version: SimpleNamespace + size_dist_info: CodeSizeDistinctInfo ) -> typing.Dict[str, str]: """Calculate code size of library/*.o in a UTF-8 encoding""" - return CodeSizeCalculator(size_version.revision, size_version.make_cmd, - self.code_size_common.measure_cmd, + return CodeSizeCalculator(size_dist_info.git_rev, + size_dist_info.make_cmd, + self.size_common_info.measure_cmd, self.logger).cal_libraries_code_size() def gen_file_name( self, - old_size_version: SimpleNamespace, - new_size_version=None + old_size_dist_info: CodeSizeDistinctInfo, + new_size_dist_info=None ) -> str: """Generate a literal string as csv file name.""" - if new_size_version: + if new_size_dist_info: return '{}-{}-{}-{}-{}-{}-{}.csv'\ - .format(old_size_version.revision, old_size_version.arch, - old_size_version.config, - new_size_version.revision, new_size_version.arch, - new_size_version.config, - self.code_size_common.measure_cmd.strip()\ + .format(old_size_dist_info.git_rev, old_size_dist_info.arch, + old_size_dist_info.config, + new_size_dist_info.git_rev, new_size_dist_info.arch, + new_size_dist_info.config, + self.size_common_info.measure_cmd.strip()\ .split(' ')[0]) else: return '{}-{}-{}-{}.csv'\ - .format(old_size_version.revision, old_size_version.arch, - old_size_version.config, - self.code_size_common.measure_cmd.strip()\ + .format(old_size_dist_info.git_rev, + old_size_dist_info.arch, + old_size_dist_info.config, + self.size_common_info.measure_cmd.strip()\ .split(' ')[0]) - def gen_code_size_report(self, size_version: SimpleNamespace) -> None: + def gen_code_size_report(self, size_dist_info: CodeSizeDistinctInfo) -> None: """Generate code size record and write it into a file.""" self.logger.info("Start to generate code size record for {}." - .format(size_version.revision)) + .format(size_dist_info.git_rev)) output_file = os.path.join(self.csv_dir, - self.gen_file_name(size_version)) + self.gen_file_name(size_dist_info)) # Check if the corresponding record exists - if size_version.revision != "current" and \ + if size_dist_info.git_rev != "current" and \ os.path.exists(output_file): self.logger.debug("Code size csv file for {} already exists." - .format(size_version.revision)) + .format(size_dist_info.git_rev)) self.code_size_generator.read_size_record( - size_version.revision, output_file) + size_dist_info.git_rev, output_file) else: self.code_size_generator.size_generator_write_record( - size_version.revision, self.cal_code_size(size_version), + size_dist_info.git_rev, self.cal_code_size(size_dist_info), output_file) def gen_code_size_comparison(self) -> None: - """Generate results of code size changes between two revisions, + """Generate results of code size changes between two Git revisions, old and new. - - Measured code size results of these two revisions must be available. + - Measured code size result of these two Git revisions must be available. - The result is directed into either file / stdout depending on - the option, code_size_common.result_options.stdout. (Default: file) + the option, size_common_info.result_options.stdout. (Default: file) """ self.logger.info("Start to generate comparision result between "\ "{} and {}." - .format(self.old_size_version.revision, - self.new_size_version.revision)) + .format(self.old_size_dist_info.git_rev, + self.new_size_dist_info.git_rev)) output_file = os.path.join( - self.result_dir, - self.gen_file_name(self.old_size_version, self.new_size_version)) + self.comp_dir, + self.gen_file_name(self.old_size_dist_info, self.new_size_dist_info)) self.code_size_generator.size_generator_write_comparison( - self.old_size_version.revision, self.new_size_version.revision, - output_file, self.code_size_common.result_options) + self.old_size_dist_info.git_rev, + self.new_size_dist_info.git_rev, + output_file, self.result_options) def get_comparision_results(self) -> None: - """Compare size of library/*.o between self.old_size_version and - self.old_size_version and generate the result file.""" + """Compare size of library/*.o between self.old_size_dist_info and + self.old_size_dist_info and generate the result file.""" build_tree.check_repo_path() - self.gen_code_size_report(self.old_size_version) - self.gen_code_size_report(self.new_size_version) + self.gen_code_size_report(self.old_size_dist_info) + self.gen_code_size_report(self.new_size_dist_info) self.gen_code_size_comparison() @@ -674,18 +745,22 @@ def main(): 'required arguments to parse for running ' + os.path.basename(__file__)) group_required.add_argument( '-o', '--old-rev', type=str, required=True, - help='old revision for comparison.') + help='old Git revision for comparison.') group_optional = parser.add_argument_group( 'optional arguments', 'optional arguments to parse for running ' + os.path.basename(__file__)) group_optional.add_argument( - '-r', '--result-dir', type=str, default='comparison', + '--record_dir', type=str, default='code_size_records', + help='directory where code size record is stored. ' + '(Default: code_size_records)') + group_optional.add_argument( + '-r', '--comp-dir', type=str, default='comparison', help='directory where comparison result is stored. ' '(Default: comparison)') group_optional.add_argument( '-n', '--new-rev', type=str, default=None, - help='new revision as comparison base. ' + help='new Git revision as comparison base. ' '(Default is the current work directory, including uncommitted ' 'changes.)') group_optional.add_argument( @@ -716,48 +791,36 @@ def main(): logging_util.configure_logger(logger) logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO) - if os.path.isfile(comp_args.result_dir): - logger.error("{} is not a directory".format(comp_args.result_dir)) + if os.path.isfile(comp_args.comp_dir): + logger.error("{} is not a directory".format(comp_args.comp_dir)) parser.exit() - old_revision = CodeSizeCalculator.validate_revision(comp_args.old_rev) + old_revision = CodeSizeCalculator.validate_git_revision(comp_args.old_rev) if comp_args.new_rev is not None: - new_revision = CodeSizeCalculator.validate_revision(comp_args.new_rev) + new_revision = CodeSizeCalculator.validate_git_revision( + comp_args.new_rev) else: new_revision = "current" - old_size_version = SimpleNamespace( - version='old', - revision=old_revision, - config=comp_args.config, - arch=comp_args.arch, - make_cmd='', - ) - new_size_version = SimpleNamespace( - version='new', - revision=new_revision, - config=comp_args.config, - arch=comp_args.arch, - make_cmd='', - ) - code_size_common = SimpleNamespace( - result_options=SimpleNamespace( - result_dir=comp_args.result_dir, - with_markdown=comp_args.markdown, - stdout=comp_args.stdout, - ), - host_arch=detect_arch(), - measure_cmd='size -t', - ) + old_size_dist_info = CodeSizeDistinctInfo( + 'old', old_revision, comp_args.arch, comp_args.config, '') + new_size_dist_info = CodeSizeDistinctInfo( + 'new', new_revision, comp_args.arch, comp_args.config, '') + size_common_info = CodeSizeCommonInfo( + detect_arch(), 'size -t') + result_options = CodeSizeResultInfo( + comp_args.record_dir, comp_args.comp_dir, + comp_args.markdown, comp_args.stdout) logger.info("Measure code size between {}:{}-{} and {}:{}-{} by `{}`." - .format(old_size_version.revision, old_size_version.config, - old_size_version.arch, - new_size_version.revision, old_size_version.config, - new_size_version.arch, - code_size_common.measure_cmd.strip().split(' ')[0])) - CodeSizeComparison(old_size_version, new_size_version, - code_size_common, logger).get_comparision_results() + .format(old_size_dist_info.git_rev, old_size_dist_info.config, + old_size_dist_info.arch, + new_size_dist_info.git_rev, old_size_dist_info.config, + new_size_dist_info.arch, + size_common_info.measure_cmd.strip().split(' ')[0])) + CodeSizeComparison(old_size_dist_info, new_size_dist_info, + size_common_info, result_options, + logger).get_comparision_results() if __name__ == "__main__": main() From 5605c6f58fce180d10d398c6ce62edee619ddb64 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Fri, 21 Jul 2023 16:09:00 +0800 Subject: [PATCH 14/41] code_size_compare: make CodeSizeBuildInfo more flexible This commit changes how to infer make command. Although we haven't supported to pass more options in command line, this is the preparation work to support those features. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 149 ++++++++++++++++++++++++++++------- 1 file changed, 119 insertions(+), 30 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 9b58d5093cfe..30251835cdc4 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -27,6 +27,7 @@ import logging import os import re +import shutil import subprocess import sys import typing @@ -45,8 +46,6 @@ class SupportedArch(Enum): X86 = 'x86' -CONFIG_TFM_MEDIUM_MBEDCRYPTO_H = '../configs/tfm_mbedcrypto_config_profile_medium.h' -CONFIG_TFM_MEDIUM_PSA_CRYPTO_H = '../configs/crypto_config_profile_medium.h' class SupportedConfig(Enum): """Supported configuration for code size measurement.""" DEFAULT = 'default' @@ -69,7 +68,8 @@ def __init__( #pylint: disable=too-many-arguments git_rev: str, arch: str, config: str, - make_cmd: str, + compiler: str, + opt_level: str, ) -> None: """ :param: version: which version to compare with for code size. @@ -77,13 +77,18 @@ def __init__( #pylint: disable=too-many-arguments :param: arch: architecture to measure code size on. :param: config: Configuration type to calculate code size. (See SupportedConfig) - :param: make_cmd: make command to build library/*.o. + :param: compiler: compiler used to build library/*.o. + :param: opt_level: Options that control optimization. (E.g. -Os) """ self.version = version self.git_rev = git_rev self.arch = arch self.config = config - self.make_cmd = make_cmd + self.compiler = compiler + self.opt_level = opt_level + # Note: Variables below are not initialized by class instantiation. + self.pre_make_cmd = [] #type: typing.List[str] + self.make_cmd = '' class CodeSizeCommonInfo: # pylint: disable=too-few-public-methods @@ -140,6 +145,13 @@ def detect_arch() -> str: print("Unknown host architecture, cannot auto-detect arch.") sys.exit(1) +TFM_MEDIUM_CONFIG_H = 'configs/tfm_mbedcrypto_config_profile_medium.h' +TFM_MEDIUM_CRYPTO_CONFIG_H = 'configs/crypto_config_profile_medium.h' + +CONFIG_H = 'include/mbedtls/mbedtls_config.h' +CRYPTO_CONFIG_H = 'include/psa/crypto_config.h' +BACKUP_SUFFIX = '.code_size.bak' + class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods """Gather information used to measure code size. @@ -167,34 +179,79 @@ def __init__( - size_dist_info.arch: architecture to measure code size on. - size_dist_info.config: configuration type to measure code size with. + - size_dist_info.compiler: compiler used to build library/*.o. + - size_dist_info.opt_level: Options that control optimization. + (E.g. -Os) :param host_arch: host architecture. :param logger: logging module """ - self.size_dist_info = size_dist_info + self.arch = size_dist_info.arch + self.config = size_dist_info.config + self.compiler = size_dist_info.compiler + self.opt_level = size_dist_info.opt_level + + self.make_cmd = ['make', '-j', 'lib'] + self.host_arch = host_arch self.logger = logger + def check_correctness(self) -> bool: + """Check whether we are using proper / supported combination + of information to build library/*.o.""" + + # default config + if self.config == SupportedConfig.DEFAULT.value and \ + self.arch == self.host_arch: + return True + # TF-M + elif self.arch == SupportedArch.ARMV8_M.value and \ + self.config == SupportedConfig.TFM_MEDIUM.value: + return True + + return False + + def infer_pre_make_command(self) -> typing.List[str]: + """Infer command to set up proper configuration before running make.""" + pre_make_cmd = [] #type: typing.List[str] + if self.config == SupportedConfig.TFM_MEDIUM.value: + pre_make_cmd.append('cp -r {} {}' + .format(TFM_MEDIUM_CONFIG_H, CONFIG_H)) + pre_make_cmd.append('cp -r {} {}' + .format(TFM_MEDIUM_CRYPTO_CONFIG_H, + CRYPTO_CONFIG_H)) + + return pre_make_cmd + + def infer_make_cflags(self) -> str: + """Infer CFLAGS by instance attributes in CodeSizeDistinctInfo.""" + cflags = [] #type: typing.List[str] + + # set optimization level + cflags.append(self.opt_level) + # set compiler by config + if self.config == SupportedConfig.TFM_MEDIUM.value: + self.compiler = 'armclang' + cflags.append('-mcpu=cortex-m33') + # set target + if self.compiler == 'armclang': + cflags.append('--target=arm-arm-none-eabi') + + return ' '.join(cflags) + def infer_make_command(self) -> str: - """Infer make command based on architecture and configuration.""" - - # make command by default - if self.size_dist_info.config == SupportedConfig.DEFAULT.value and \ - self.size_dist_info.arch == self.host_arch: - return 'make -j lib CFLAGS=\'-Os \' ' - # make command for TF-M - elif self.size_dist_info.arch == SupportedArch.ARMV8_M.value and \ - self.size_dist_info.config == SupportedConfig.TFM_MEDIUM.value: - return \ - 'make -j lib CC=armclang \ - CFLAGS=\'--target=arm-arm-none-eabi -mcpu=cortex-m33 -Os \ - -DMBEDTLS_CONFIG_FILE=\\\"' + CONFIG_TFM_MEDIUM_MBEDCRYPTO_H + '\\\" \ - -DMBEDTLS_PSA_CRYPTO_CONFIG_FILE=\\\"' + CONFIG_TFM_MEDIUM_PSA_CRYPTO_H + '\\\" \'' - # unsupported combinations + """Infer make command by CFLAGS and CC.""" + + if self.check_correctness(): + # set CFLAGS= + self.make_cmd.append('CFLAGS=\'{}\''.format(self.infer_make_cflags())) + # set CC= + self.make_cmd.append('CC={}'.format(self.compiler)) + return ' '.join(self.make_cmd) else: self.logger.error("Unsupported combination of architecture: {} " \ "and configuration: {}.\n" - .format(self.size_dist_info.arch, - self.size_dist_info.config)) + .format(self.arch, + self.config)) self.logger.info("Please use supported combination of " \ "architecture and configuration:") for comb in CodeSizeBuildInfo.SupportedArchConfig: @@ -213,15 +270,17 @@ class CodeSizeCalculator: Git revision and code size measurement tool. """ - def __init__( + def __init__( #pylint: disable=too-many-arguments self, git_rev: str, + pre_make_cmd: typing.List[str], make_cmd: str, measure_cmd: str, logger: logging.Logger, ) -> None: """ :param git_rev: Git revision. (E.g: commit) + :param pre_make_cmd: command to set up proper config before running make. :param make_cmd: command to build library/*.o. :param measure_cmd: command to measure code size for library/*.o. :param logger: logging module @@ -231,6 +290,7 @@ def __init__( self.make_clean = 'make clean' self.git_rev = git_rev + self.pre_make_cmd = pre_make_cmd self.make_cmd = make_cmd self.measure_cmd = measure_cmd self.logger = logger @@ -246,7 +306,7 @@ def _create_git_worktree(self) -> str: """Create a separate worktree for Git revision. If Git revision is current, use current worktree instead.""" - if self.git_rev == "current": + if self.git_rev == 'current': self.logger.debug("Using current work directory.") git_worktree_path = self.repo_path else: @@ -262,6 +322,16 @@ def _create_git_worktree(self) -> str: return git_worktree_path + @staticmethod + def backup_config_files(restore: bool) -> None: + """Backup / Restore config files.""" + if restore: + shutil.move(CONFIG_H + BACKUP_SUFFIX, CONFIG_H) + shutil.move(CRYPTO_CONFIG_H + BACKUP_SUFFIX, CRYPTO_CONFIG_H) + else: + shutil.copy(CONFIG_H, CONFIG_H + BACKUP_SUFFIX) + shutil.copy(CRYPTO_CONFIG_H, CRYPTO_CONFIG_H + BACKUP_SUFFIX) + def _build_libraries(self, git_worktree_path: str) -> None: """Build library/*.o in the specified worktree.""" @@ -269,6 +339,14 @@ def _build_libraries(self, git_worktree_path: str) -> None: .format(self.git_rev)) my_environment = os.environ.copy() try: + if self.git_rev == 'current': + self.backup_config_files(restore=False) + for pre_cmd in self.pre_make_cmd: + subprocess.check_output( + pre_cmd, env=my_environment, shell=True, + cwd=git_worktree_path, stderr=subprocess.STDOUT, + universal_newlines=True + ) subprocess.check_output( self.make_clean, env=my_environment, shell=True, cwd=git_worktree_path, stderr=subprocess.STDOUT, @@ -279,6 +357,8 @@ def _build_libraries(self, git_worktree_path: str) -> None: cwd=git_worktree_path, stderr=subprocess.STDOUT, universal_newlines=True ) + if self.git_rev == 'current': + self.backup_config_files(restore=True) except subprocess.CalledProcessError as e: self._handle_called_process_error(e, git_worktree_path) @@ -628,6 +708,13 @@ def __init__( #pylint: disable=too-many-arguments self.old_size_dist_info = old_size_dist_info self.new_size_dist_info = new_size_dist_info self.size_common_info = size_common_info + # infer pre make command + self.old_size_dist_info.pre_make_cmd = CodeSizeBuildInfo( + self.old_size_dist_info, self.size_common_info.host_arch, + self.logger).infer_pre_make_command() + self.new_size_dist_info.pre_make_cmd = CodeSizeBuildInfo( + self.new_size_dist_info, self.size_common_info.host_arch, + self.logger).infer_pre_make_command() # infer make command self.old_size_dist_info.make_cmd = CodeSizeBuildInfo( self.old_size_dist_info, self.size_common_info.host_arch, @@ -654,7 +741,6 @@ def __generate_size_parser(self): .strip().split(' ')[0])) sys.exit(1) - def cal_code_size( self, size_dist_info: CodeSizeDistinctInfo @@ -662,6 +748,7 @@ def cal_code_size( """Calculate code size of library/*.o in a UTF-8 encoding""" return CodeSizeCalculator(size_dist_info.git_rev, + size_dist_info.pre_make_cmd, size_dist_info.make_cmd, self.size_common_info.measure_cmd, self.logger).cal_libraries_code_size() @@ -737,7 +824,6 @@ def get_comparision_results(self) -> None: self.gen_code_size_report(self.new_size_dist_info) self.gen_code_size_comparison() - def main(): parser = argparse.ArgumentParser(description=(__doc__)) group_required = parser.add_argument_group( @@ -800,14 +886,17 @@ def main(): new_revision = CodeSizeCalculator.validate_git_revision( comp_args.new_rev) else: - new_revision = "current" + new_revision = 'current' + # version, git_rev, arch, config, compiler, opt_level old_size_dist_info = CodeSizeDistinctInfo( - 'old', old_revision, comp_args.arch, comp_args.config, '') + 'old', old_revision, comp_args.arch, comp_args.config, 'cc', '-Os') new_size_dist_info = CodeSizeDistinctInfo( - 'new', new_revision, comp_args.arch, comp_args.config, '') + 'new', new_revision, comp_args.arch, comp_args.config, 'cc', '-Os') + # host_arch, measure_cmd size_common_info = CodeSizeCommonInfo( detect_arch(), 'size -t') + # record_dir, comp_dir, with_markdown, stdout result_options = CodeSizeResultInfo( comp_args.record_dir, comp_args.comp_dir, comp_args.markdown, comp_args.stdout) From 950590099dbe9b815987e0d30039edc249e54da7 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Mon, 24 Jul 2023 12:29:22 +0800 Subject: [PATCH 15/41] code_size_compare: simplify CodeSizeGeneratorWithSize Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 100 +++++++++++++---------------------- 1 file changed, 38 insertions(+), 62 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 30251835cdc4..4ac798a9f76a 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -426,8 +426,8 @@ class CodeSizeGenerator: """ A generator based on size measurement tool for library/*.o. This is an abstract class. To use it, derive a class that implements - size_generator_write_record and size_generator_write_comparison methods, - then call both of them with proper arguments. + write_record and write_comparison methods, then call both of them with + proper arguments. """ def __init__(self, logger: logging.Logger) -> None: """ @@ -435,11 +435,11 @@ def __init__(self, logger: logging.Logger) -> None: """ self.logger = logger - def size_generator_write_record( + def write_record( self, git_rev: str, - code_size_text: typing.Dict, - output_file: str + code_size_text: typing.Dict[str, str], + output: typing_util.Writable ) -> None: """Write size record into a file. @@ -447,28 +447,26 @@ def size_generator_write_record( :param code_size_text: string output (utf-8) from measurement tool of code size. - typing.Dict[mod: str] - :param output_file: file which the code size record is written to. + :param output: output stream which the code size record is written to. + (Note: Normally write code size record into File) """ raise NotImplementedError - def size_generator_write_comparison( + def write_comparison( self, old_rev: str, new_rev: str, - output_stream: str, - result_options: CodeSizeResultInfo + output: typing_util.Writable, + with_markdown=False ) -> None: """Write a comparision result into a stream between two Git revisions. :param old_rev: old Git revision to compared with. :param new_rev: new Git revision to compared with. - :param output_stream: stream which the code size record is written to. - :param result_options: - CodeSizeResultInfo containing options for comparison result. - - result_options.with_markdown: write comparision result in a - markdown table. (Default: False) - - result_options.stdout: direct comparison result into - sys.stdout. (Default: False) + :param output: output stream which the code size record is written to. + (File / sys.stdout) + :param with_markdown: write comparision result in a markdown table. + (Default: False) """ raise NotImplementedError @@ -558,15 +556,19 @@ def _size_reader_helper( for fname, size_entry in file_size.items(): yield mod, fname, size_entry - def _write_size_record( + def write_record( self, git_rev: str, + code_size_text: typing.Dict[str, str], output: typing_util.Writable ) -> None: """Write size information to a file. Writing Format: file_name text data bss total(dec) """ + for mod, size_text in code_size_text.items(): + self._set_size_record(git_rev, mod, size_text) + format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n" output.write(format_string.format("filename", "text", "data", "bss", "total")) @@ -575,12 +577,12 @@ def _write_size_record( size_entry.text, size_entry.data, size_entry.bss, size_entry.total)) - def _write_comparison( + def write_comparison( self, old_rev: str, new_rev: str, output: typing_util.Writable, - with_markdown: bool + with_markdown=False ) -> None: """Write comparison result into a file. @@ -640,44 +642,6 @@ def cal_size_section_variation(mod, fname, size_entry, attr): .format(fname, str(text_vari[0]) + "," + str(data_vari[0]))) - def size_generator_write_record( - self, - git_rev: str, - code_size_text: typing.Dict, - output_file: str - ) -> None: - """Write size record into a specified file based on Git revision and - output from `size` tool.""" - self.logger.debug("Generating code size csv for {}.".format(git_rev)) - - for mod, size_text in code_size_text.items(): - self._set_size_record(git_rev, mod, size_text) - - output = open(output_file, "w") - self._write_size_record(git_rev, output) - - def size_generator_write_comparison( - self, - old_rev: str, - new_rev: str, - output_stream: str, - result_options: CodeSizeResultInfo - ) -> None: - """Write a comparision result into a stream between two Git revisions. - - By default, it's written into a file called output_stream. - Once result_options.stdout is set, it's written into sys.stdout instead. - """ - self.logger.debug("Generating comparison results between {} and {}." - .format(old_rev, new_rev)) - - if result_options.stdout: - output = sys.stdout - else: - output = open(output_stream, "w") - self._write_comparison(old_rev, new_rev, output, - result_options.with_markdown) - class CodeSizeComparison: """Compare code size between two Git revisions.""" @@ -790,9 +754,14 @@ def gen_code_size_report(self, size_dist_info: CodeSizeDistinctInfo) -> None: self.code_size_generator.read_size_record( size_dist_info.git_rev, output_file) else: - self.code_size_generator.size_generator_write_record( - size_dist_info.git_rev, self.cal_code_size(size_dist_info), - output_file) + # measure code size + code_size_text = self.cal_code_size(size_dist_info) + + self.logger.debug("Generating code size csv for {}." + .format(size_dist_info.git_rev)) + output = open(output_file, "w") + self.code_size_generator.write_record( + size_dist_info.git_rev, code_size_text, output) def gen_code_size_comparison(self) -> None: """Generate results of code size changes between two Git revisions, @@ -811,10 +780,17 @@ def gen_code_size_comparison(self) -> None: self.comp_dir, self.gen_file_name(self.old_size_dist_info, self.new_size_dist_info)) - self.code_size_generator.size_generator_write_comparison( + self.logger.debug("Generating comparison results between {} and {}." + .format(self.old_size_dist_info.git_rev, + self.new_size_dist_info.git_rev)) + if self.result_options.stdout: + output = sys.stdout + else: + output = open(output_file, "w") + self.code_size_generator.write_comparison( self.old_size_dist_info.git_rev, self.new_size_dist_info.git_rev, - output_file, self.result_options) + output, self.result_options.with_markdown) def get_comparision_results(self) -> None: """Compare size of library/*.o between self.old_size_dist_info and From a6cf692e2a6470a194103e4e2fbc480749a6a4c6 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Mon, 24 Jul 2023 15:20:42 +0800 Subject: [PATCH 16/41] code_size_compare: simplify how to generate file name of code size Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 65 ++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 4ac798a9f76a..a8c8c9641e71 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -90,6 +90,11 @@ def __init__( #pylint: disable=too-many-arguments self.pre_make_cmd = [] #type: typing.List[str] self.make_cmd = '' + def get_info_indication(self): + """Return a unique string to indicate Code Size Distinct Information.""" + return '{}-{}-{}-{}'\ + .format(self.git_rev, self.arch, self.config, self.compiler) + class CodeSizeCommonInfo: # pylint: disable=too-few-public-methods """Data structure to store common information for code size comparison.""" @@ -105,6 +110,10 @@ def __init__( self.host_arch = host_arch self.measure_cmd = measure_cmd + def get_info_indication(self): + """Return a unique string to indicate Code Size Common Information.""" + return '{}'\ + .format(self.measure_cmd.strip().split(' ')[0]) class CodeSizeResultInfo: # pylint: disable=too-few-public-methods """Data structure to store result options for code size comparison.""" @@ -717,35 +726,16 @@ def cal_code_size( self.size_common_info.measure_cmd, self.logger).cal_libraries_code_size() - def gen_file_name( - self, - old_size_dist_info: CodeSizeDistinctInfo, - new_size_dist_info=None - ) -> str: - """Generate a literal string as csv file name.""" - if new_size_dist_info: - return '{}-{}-{}-{}-{}-{}-{}.csv'\ - .format(old_size_dist_info.git_rev, old_size_dist_info.arch, - old_size_dist_info.config, - new_size_dist_info.git_rev, new_size_dist_info.arch, - new_size_dist_info.config, - self.size_common_info.measure_cmd.strip()\ - .split(' ')[0]) - else: - return '{}-{}-{}-{}.csv'\ - .format(old_size_dist_info.git_rev, - old_size_dist_info.arch, - old_size_dist_info.config, - self.size_common_info.measure_cmd.strip()\ - .split(' ')[0]) - def gen_code_size_report(self, size_dist_info: CodeSizeDistinctInfo) -> None: """Generate code size record and write it into a file.""" self.logger.info("Start to generate code size record for {}." .format(size_dist_info.git_rev)) - output_file = os.path.join(self.csv_dir, - self.gen_file_name(size_dist_info)) + output_file = os.path.join( + self.csv_dir, + '{}-{}.csv' + .format(size_dist_info.get_info_indication(), + self.size_common_info.get_info_indication())) # Check if the corresponding record exists if size_dist_info.git_rev != "current" and \ os.path.exists(output_file): @@ -776,17 +766,20 @@ def gen_code_size_comparison(self) -> None: "{} and {}." .format(self.old_size_dist_info.git_rev, self.new_size_dist_info.git_rev)) - output_file = os.path.join( - self.comp_dir, - self.gen_file_name(self.old_size_dist_info, self.new_size_dist_info)) - - self.logger.debug("Generating comparison results between {} and {}." - .format(self.old_size_dist_info.git_rev, - self.new_size_dist_info.git_rev)) if self.result_options.stdout: output = sys.stdout else: + output_file = os.path.join( + self.comp_dir, + '{}-{}-{}.csv' + .format(self.old_size_dist_info.get_info_indication(), + self.new_size_dist_info.get_info_indication(), + self.size_common_info.get_info_indication())) output = open(output_file, "w") + + self.logger.debug("Generating comparison results between {} and {}." + .format(self.old_size_dist_info.git_rev, + self.new_size_dist_info.git_rev)) self.code_size_generator.write_comparison( self.old_size_dist_info.git_rev, self.new_size_dist_info.git_rev, @@ -877,12 +870,10 @@ def main(): comp_args.record_dir, comp_args.comp_dir, comp_args.markdown, comp_args.stdout) - logger.info("Measure code size between {}:{}-{} and {}:{}-{} by `{}`." - .format(old_size_dist_info.git_rev, old_size_dist_info.config, - old_size_dist_info.arch, - new_size_dist_info.git_rev, old_size_dist_info.config, - new_size_dist_info.arch, - size_common_info.measure_cmd.strip().split(' ')[0])) + logger.info("Measure code size between {} and {} by `{}`." + .format(old_size_dist_info.get_info_indication(), + new_size_dist_info.get_info_indication(), + size_common_info.get_info_indication())) CodeSizeComparison(old_size_dist_info, new_size_dist_info, size_common_info, result_options, logger).get_comparision_results() From 69262fc087ca62255a6a78e61657460416e75f79 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Mon, 24 Jul 2023 16:36:40 +0800 Subject: [PATCH 17/41] code_size_compare: add extra indication if print to sys.stdout If we output comparison result into sys.stdout, it will print an extra line to show information we used for code size comparison in detail. This would be helpful if we copy & paste code size changes in Github comment. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index a8c8c9641e71..b886a9e99055 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -768,6 +768,10 @@ def gen_code_size_comparison(self) -> None: self.new_size_dist_info.git_rev)) if self.result_options.stdout: output = sys.stdout + print("Measure code size between `{}` and `{}` by `{}`." + .format(self.old_size_dist_info.get_info_indication(), + self.new_size_dist_info.get_info_indication(), + self.size_common_info.get_info_indication())) else: output_file = os.path.join( self.comp_dir, From f2cd717952bc97a23dcfdfd85ba75aef43e3c6af Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Mon, 24 Jul 2023 16:56:46 +0800 Subject: [PATCH 18/41] code_size_compare: print 'None' if comparing size for a new file Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index b886a9e99055..e79b379085b2 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -647,9 +647,11 @@ def cal_size_section_variation(mod, fname, size_entry, attr): "{:.2%}".format(text_vari[3]) + "," + "{:.2%}".format(data_vari[3]))) else: - output.write("{:<30} {:<18}\n" - .format(fname, - str(text_vari[0]) + "," + str(data_vari[0]))) + output.write( + format_string + .format(fname, + str(text_vari[0]) + "," + str(data_vari[0]), + 'None', 'None', 'None')) class CodeSizeComparison: From 25bd33189927a2741124afb45fb719e3e87ecce3 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Tue, 25 Jul 2023 10:24:20 +0800 Subject: [PATCH 19/41] code_size_compare: round percentage to an integer value Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index e79b379085b2..88578feb4a73 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -644,8 +644,8 @@ def cal_size_section_variation(mod, fname, size_entry, attr): str(text_vari[0]) + "," + str(data_vari[0]), str(text_vari[1]) + "," + str(data_vari[1]), str(text_vari[2]) + "," + str(data_vari[2]), - "{:.2%}".format(text_vari[3]) + "," - + "{:.2%}".format(data_vari[3]))) + "{:.0%}".format(text_vari[3]) + "," + + "{:.0%}".format(data_vari[3]))) else: output.write( format_string From e4a3636fac45317323c4132450368f824a945d27 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Tue, 25 Jul 2023 10:37:11 +0800 Subject: [PATCH 20/41] code_size_compare: add comments to make code more readable Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 88578feb4a73..0c29c41faa9f 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -92,8 +92,9 @@ def __init__( #pylint: disable=too-many-arguments def get_info_indication(self): """Return a unique string to indicate Code Size Distinct Information.""" - return '{}-{}-{}-{}'\ - .format(self.git_rev, self.arch, self.config, self.compiler) + return '{rev}-{arch}-{config}-{cc}'\ + .format(rev=self.git_rev, arch=self.arch, config=self.config, + cc=self.compiler) class CodeSizeCommonInfo: # pylint: disable=too-few-public-methods @@ -112,8 +113,8 @@ def __init__( def get_info_indication(self): """Return a unique string to indicate Code Size Common Information.""" - return '{}'\ - .format(self.measure_cmd.strip().split(' ')[0]) + return '{measure_tool}'\ + .format(measure_tool=self.measure_cmd.strip().split(' ')[0]) class CodeSizeResultInfo: # pylint: disable=too-few-public-methods """Data structure to store result options for code size comparison.""" @@ -223,11 +224,11 @@ def infer_pre_make_command(self) -> typing.List[str]: """Infer command to set up proper configuration before running make.""" pre_make_cmd = [] #type: typing.List[str] if self.config == SupportedConfig.TFM_MEDIUM.value: - pre_make_cmd.append('cp -r {} {}' - .format(TFM_MEDIUM_CONFIG_H, CONFIG_H)) - pre_make_cmd.append('cp -r {} {}' - .format(TFM_MEDIUM_CRYPTO_CONFIG_H, - CRYPTO_CONFIG_H)) + pre_make_cmd.append('cp -r {src} {dest}' + .format(src=TFM_MEDIUM_CONFIG_H, dest=CONFIG_H)) + pre_make_cmd.append('cp -r {src} {dest}' + .format(src=TFM_MEDIUM_CRYPTO_CONFIG_H, + dest=CRYPTO_CONFIG_H)) return pre_make_cmd @@ -641,15 +642,20 @@ def cal_size_section_variation(mod, fname, size_entry, attr): output.write( format_string .format(fname, + # current(text,data) str(text_vari[0]) + "," + str(data_vari[0]), + # old(text,data) str(text_vari[1]) + "," + str(data_vari[1]), + # change(text,data) str(text_vari[2]) + "," + str(data_vari[2]), + # change%(text,data) "{:.0%}".format(text_vari[3]) + "," + "{:.0%}".format(data_vari[3]))) else: output.write( format_string .format(fname, + # current(text,data) str(text_vari[0]) + "," + str(data_vari[0]), 'None', 'None', 'None')) From 2ba9df2c1b7873507d7316b37a793a6feaf64056 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Wed, 26 Jul 2023 10:11:31 +0800 Subject: [PATCH 21/41] code_size_compare: direct error message by logger.error Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 0c29c41faa9f..53d4e3b6d94b 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -262,16 +262,16 @@ def infer_make_command(self) -> str: "and configuration: {}.\n" .format(self.arch, self.config)) - self.logger.info("Please use supported combination of " \ + self.logger.error("Please use supported combination of " \ "architecture and configuration:") for comb in CodeSizeBuildInfo.SupportedArchConfig: - self.logger.info(comb) - self.logger.info("") - self.logger.info("For your system, please use:") + self.logger.error(comb) + self.logger.error("") + self.logger.error("For your system, please use:") for comb in CodeSizeBuildInfo.SupportedArchConfig: if "default" in comb and self.host_arch not in comb: continue - self.logger.info(comb) + self.logger.error(comb) sys.exit(1) From 533cde22c02fe31145d857c76889b999bdd70d06 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Wed, 26 Jul 2023 10:17:17 +0800 Subject: [PATCH 22/41] code_size_compare: set log level as ERROR in option --stdout If we use option --stdout, the logging level is set as logging.ERROR. But --verbose is able to overwrite logging level as logging.INFO if we want to display intermediate log in the process of code size comparison. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 53d4e3b6d94b..2bb8b0e2a666 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -856,7 +856,10 @@ def main(): logger = logging.getLogger() logging_util.configure_logger(logger) - logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO) + if comp_args.stdout and not comp_args.verbose: + logger.setLevel(logging.ERROR) + else: + logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO) if os.path.isfile(comp_args.comp_dir): logger.error("{} is not a directory".format(comp_args.comp_dir)) From ea842e791bc3d5aea864365356f0c141444b6586 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Wed, 26 Jul 2023 10:34:39 +0800 Subject: [PATCH 23/41] code_size_compare: print prompt message under correct condition Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 2bb8b0e2a666..55d116e8fa88 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -776,10 +776,6 @@ def gen_code_size_comparison(self) -> None: self.new_size_dist_info.git_rev)) if self.result_options.stdout: output = sys.stdout - print("Measure code size between `{}` and `{}` by `{}`." - .format(self.old_size_dist_info.get_info_indication(), - self.new_size_dist_info.get_info_indication(), - self.size_common_info.get_info_indication())) else: output_file = os.path.join( self.comp_dir, @@ -792,6 +788,12 @@ def gen_code_size_comparison(self) -> None: self.logger.debug("Generating comparison results between {} and {}." .format(self.old_size_dist_info.git_rev, self.new_size_dist_info.git_rev)) + if self.result_options.with_markdown or self.result_options.stdout: + print("Measure code size between {} and {} by `{}`." + .format(self.old_size_dist_info.get_info_indication(), + self.new_size_dist_info.get_info_indication(), + self.size_common_info.get_info_indication()), + file=output) self.code_size_generator.write_comparison( self.old_size_dist_info.git_rev, self.new_size_dist_info.git_rev, From bef1acd7b805c42f388bad3000efe9b1a616dc74 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Wed, 26 Jul 2023 10:45:11 +0800 Subject: [PATCH 24/41] code_size_compare: left align file names in markdown table Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 55d116e8fa88..464290644696 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -625,7 +625,7 @@ def cal_size_section_variation(mod, fname, size_entry, attr): "change(text,data)", "change%(text,data)")) if with_markdown: output.write(format_string - .format("----:", "----:", "----:", "----:", "----:")) + .format(":----", "----:", "----:", "----:", "----:")) for mod, fname, size_entry in \ self._size_reader_helper(new_rev, output, with_markdown): From 68265f41d7b805727fdf1126cd8789999dea4fca Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Wed, 26 Jul 2023 14:44:52 +0800 Subject: [PATCH 25/41] code_size_compare: use `current` as default new Git revision Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 464290644696..d4285fd61070 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -828,7 +828,7 @@ def main(): help='directory where comparison result is stored. ' '(Default: comparison)') group_optional.add_argument( - '-n', '--new-rev', type=str, default=None, + '-n', '--new-rev', type=str, default='current', help='new Git revision as comparison base. ' '(Default is the current work directory, including uncommitted ' 'changes.)') @@ -867,18 +867,17 @@ def main(): logger.error("{} is not a directory".format(comp_args.comp_dir)) parser.exit() - old_revision = CodeSizeCalculator.validate_git_revision(comp_args.old_rev) - if comp_args.new_rev is not None: - new_revision = CodeSizeCalculator.validate_git_revision( + comp_args.old_rev = CodeSizeCalculator.validate_git_revision( + comp_args.old_rev) + if comp_args.new_rev != 'current': + comp_args.new_rev = CodeSizeCalculator.validate_git_revision( comp_args.new_rev) - else: - new_revision = 'current' # version, git_rev, arch, config, compiler, opt_level old_size_dist_info = CodeSizeDistinctInfo( - 'old', old_revision, comp_args.arch, comp_args.config, 'cc', '-Os') + 'old', comp_args.old_rev, comp_args.arch, comp_args.config, 'cc', '-Os') new_size_dist_info = CodeSizeDistinctInfo( - 'new', new_revision, comp_args.arch, comp_args.config, 'cc', '-Os') + 'new', comp_args.new_rev, comp_args.arch, comp_args.config, 'cc', '-Os') # host_arch, measure_cmd size_common_info = CodeSizeCommonInfo( detect_arch(), 'size -t') From 15b1358f220139f7705aed74c541e6c3fd8d5a2c Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Wed, 26 Jul 2023 14:48:08 +0800 Subject: [PATCH 26/41] logging_util: rename argument Signed-off-by: Yanray Wang --- scripts/mbedtls_dev/logging_util.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/mbedtls_dev/logging_util.py b/scripts/mbedtls_dev/logging_util.py index 962361a49532..85a3f19ace7b 100644 --- a/scripts/mbedtls_dev/logging_util.py +++ b/scripts/mbedtls_dev/logging_util.py @@ -21,11 +21,11 @@ def configure_logger( logger: logging.Logger, - logger_format="[%(levelname)s]: %(message)s" + log_format="[%(levelname)s]: %(message)s" ) -> None: """ Configure the logging.Logger instance so that: - - Format is set to any logger_format. + - Format is set to any log_format. Default: "[%(levelname)s]: %(message)s" - loglevel >= WARNING are printed to stderr. - loglevel < WARNING are printed to stdout. @@ -39,7 +39,7 @@ def __init__(self, max_level, name=''): def filter(self, record: logging.LogRecord) -> bool: return record.levelno <= self.max_level - log_formatter = logging.Formatter(logger_format) + log_formatter = logging.Formatter(log_format) # set loglevel >= WARNING to be printed to stderr stderr_hdlr = logging.StreamHandler(sys.stderr) From 6ef5049b9ffb9ce6688e7ca9deac22595e4b1b64 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Wed, 26 Jul 2023 14:59:37 +0800 Subject: [PATCH 27/41] code_size_compare: simplify some code for python dictionary Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index d4285fd61070..9b81b82f1d38 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -92,9 +92,7 @@ def __init__( #pylint: disable=too-many-arguments def get_info_indication(self): """Return a unique string to indicate Code Size Distinct Information.""" - return '{rev}-{arch}-{config}-{cc}'\ - .format(rev=self.git_rev, arch=self.arch, config=self.config, - cc=self.compiler) + return '{git_rev}-{arch}-{config}-{compiler}'.format(**self.__dict__) class CodeSizeCommonInfo: # pylint: disable=too-few-public-methods @@ -518,10 +516,7 @@ def _set_size_record(self, git_rev: str, mod: str, size_text: str) -> None: # file_name: SizeEntry(text, data, bss, dec) size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry( data[0], data[1], data[2], data[3]) - if git_rev in self.code_size: - self.code_size[git_rev].update({mod: size_record}) - else: - self.code_size[git_rev] = {mod: size_record} + self.code_size.setdefault(git_rev, {}).update({mod: size_record}) def read_size_record(self, git_rev: str, fname: str) -> None: """Read size information from csv file and write it into code_size. From a279ca9ff8dcfcfa6fa0317fdda9efe27f65f3d9 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Wed, 26 Jul 2023 15:01:10 +0800 Subject: [PATCH 28/41] code_size_compare: remove unnecessary -r in cp command Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 9b81b82f1d38..5fa6d8f7871d 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -222,9 +222,9 @@ def infer_pre_make_command(self) -> typing.List[str]: """Infer command to set up proper configuration before running make.""" pre_make_cmd = [] #type: typing.List[str] if self.config == SupportedConfig.TFM_MEDIUM.value: - pre_make_cmd.append('cp -r {src} {dest}' + pre_make_cmd.append('cp {src} {dest}' .format(src=TFM_MEDIUM_CONFIG_H, dest=CONFIG_H)) - pre_make_cmd.append('cp -r {src} {dest}' + pre_make_cmd.append('cp {src} {dest}' .format(src=TFM_MEDIUM_CRYPTO_CONFIG_H, dest=CRYPTO_CONFIG_H)) From 9e8b671b1c52d29227feb770c65730a01ca7e5e7 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Wed, 26 Jul 2023 15:37:26 +0800 Subject: [PATCH 29/41] code_size_compare: check --record-dir properly Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 5fa6d8f7871d..89d8fe92ce84 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -815,11 +815,11 @@ def main(): 'optional arguments', 'optional arguments to parse for running ' + os.path.basename(__file__)) group_optional.add_argument( - '--record_dir', type=str, default='code_size_records', + '--record-dir', type=str, default='code_size_records', help='directory where code size record is stored. ' '(Default: code_size_records)') group_optional.add_argument( - '-r', '--comp-dir', type=str, default='comparison', + '--comp-dir', type=str, default='comparison', help='directory where comparison result is stored. ' '(Default: comparison)') group_optional.add_argument( @@ -858,9 +858,14 @@ def main(): else: logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO) + if os.path.isfile(comp_args.record_dir): + logger.error("record directory: {} is not a directory" + .format(comp_args.record_dir)) + sys.exit(1) if os.path.isfile(comp_args.comp_dir): - logger.error("{} is not a directory".format(comp_args.comp_dir)) - parser.exit() + logger.error("comparison directory: {} is not a directory" + .format(comp_args.comp_dir)) + sys.exit(1) comp_args.old_rev = CodeSizeCalculator.validate_git_revision( comp_args.old_rev) From 6ae94a0a72b6345b07727ad0713c081352425719 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Wed, 26 Jul 2023 17:12:57 +0800 Subject: [PATCH 30/41] code_size_compare: make sure _remove_worktree executed Add try and finally to make sure we remove worktree as expected even if we hit errors by accident. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 89d8fe92ce84..cc43dc75d7a2 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -423,9 +423,11 @@ def cal_libraries_code_size(self) -> typing.Dict[str, str]: """ git_worktree_path = self._create_git_worktree() - self._build_libraries(git_worktree_path) - res = self._gen_raw_code_size(git_worktree_path) - self._remove_worktree(git_worktree_path) + try: + self._build_libraries(git_worktree_path) + res = self._gen_raw_code_size(git_worktree_path) + finally: + self._remove_worktree(git_worktree_path) return res From ca9a3cbc1de8961707f078cab9ec0138eaeed84f Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Wed, 26 Jul 2023 17:16:29 +0800 Subject: [PATCH 31/41] code_size_compare: detect architecture of x86_32 properly Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index cc43dc75d7a2..3b988a623129 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -147,7 +147,7 @@ def detect_arch() -> str: return SupportedArch.AARCH32.value if '__x86_64__' in cc_output: return SupportedArch.X86_64.value - if '__x86__' in cc_output: + if '__i386__' in cc_output: return SupportedArch.X86.value else: print("Unknown host architecture, cannot auto-detect arch.") From 6f09267646f0e8ec0dacdb81dbf701860f9efeb8 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Thu, 27 Jul 2023 10:15:13 +0800 Subject: [PATCH 32/41] code_size_compare: remove column of percentage for code size change Percentage is not a useful number when looking at code size changes. Since it depends on the base of the code size. It might give misleading information by simply looking at the numbers. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 3b988a623129..48e129bcc9f8 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -594,7 +594,7 @@ def write_comparison( """Write comparison result into a file. Writing Format: file_name current(text,data) old(text,data)\ - change(text,data) change_pct%(text,data) + change(text,data) """ def cal_size_section_variation(mod, fname, size_entry, attr): @@ -603,26 +603,22 @@ def cal_size_section_variation(mod, fname, size_entry, attr): if fname in self.code_size[old_rev][mod]: old_size = int(self.code_size[old_rev][mod][fname].__dict__[attr]) change = new_size - old_size - if old_size != 0: - change_pct = change / old_size - else: - change_pct = 0 - return [new_size, old_size, change, change_pct] + return [new_size, old_size, change] else: return [new_size] if with_markdown: - format_string = "| {:<30} | {:<18} | {:<14} | {:<17} | {:<18} |\n" + format_string = "| {:<30} | {:<18} | {:<14} | {:<17} |\n" else: - format_string = "{:<30} {:<18} {:<14} {:<17} {:<18}\n" + format_string = "{:<30} {:<18} {:<14} {:<17}\n" output.write(format_string .format("filename", "current(text,data)", "old(text,data)", - "change(text,data)", "change%(text,data)")) + "change(text,data)")) if with_markdown: output.write(format_string - .format(":----", "----:", "----:", "----:", "----:")) + .format(":----", "----:", "----:", "----:")) for mod, fname, size_entry in \ self._size_reader_helper(new_rev, output, with_markdown): @@ -644,17 +640,14 @@ def cal_size_section_variation(mod, fname, size_entry, attr): # old(text,data) str(text_vari[1]) + "," + str(data_vari[1]), # change(text,data) - str(text_vari[2]) + "," + str(data_vari[2]), - # change%(text,data) - "{:.0%}".format(text_vari[3]) + "," - + "{:.0%}".format(data_vari[3]))) + str(text_vari[2]) + "," + str(data_vari[2]))) else: output.write( format_string .format(fname, # current(text,data) str(text_vari[0]) + "," + str(data_vari[0]), - 'None', 'None', 'None')) + 'None', 'None')) class CodeSizeComparison: From 4dfc132bcbc8e1eb528ab94aaa27827071ff5cbd Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Thu, 27 Jul 2023 10:44:50 +0800 Subject: [PATCH 33/41] code_size_compare: change format for comparison result The result format for code size comparison is: filename new(text) new(data) change(text) change(data) yyy.o xxx xxx xx xx The numbers followed are in bytes. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 48e129bcc9f8..4a50c5b334e3 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -593,7 +593,7 @@ def write_comparison( ) -> None: """Write comparison result into a file. - Writing Format: file_name current(text,data) old(text,data)\ + Writing Format: file_name new(text,data) old(text,data)\ change(text,data) """ @@ -608,17 +608,17 @@ def cal_size_section_variation(mod, fname, size_entry, attr): return [new_size] if with_markdown: - format_string = "| {:<30} | {:<18} | {:<14} | {:<17} |\n" + format_string = "| {:<30} | {:<9} | {:<9} | {:<12} | {:<12} |\n" else: - format_string = "{:<30} {:<18} {:<14} {:<17}\n" + format_string = "{:<30} {:<9} {:<9} {:<12} {:<12}\n" output.write(format_string .format("filename", - "current(text,data)", "old(text,data)", - "change(text,data)")) + "new(text)", "new(data)", "change(text)", + "change(data)")) if with_markdown: output.write(format_string - .format(":----", "----:", "----:", "----:")) + .format(":----", "----:", "----:", "----:", "----:")) for mod, fname, size_entry in \ self._size_reader_helper(new_rev, output, with_markdown): @@ -635,18 +635,17 @@ def cal_size_section_variation(mod, fname, size_entry, attr): output.write( format_string .format(fname, - # current(text,data) - str(text_vari[0]) + "," + str(data_vari[0]), - # old(text,data) - str(text_vari[1]) + "," + str(data_vari[1]), - # change(text,data) - str(text_vari[2]) + "," + str(data_vari[2]))) + # new(text), new(data) + str(text_vari[0]), str(data_vari[0]), + # change(text), change(data) + str(text_vari[2]), str(data_vari[2]))) else: output.write( format_string .format(fname, - # current(text,data) - str(text_vari[0]) + "," + str(data_vari[0]), + # new(text), new(data) + str(text_vari[0]), str(data_vari[0]), + # change(text), change(data) 'None', 'None')) From dcf360dd722ef93c1bd10b9216b83620a204055a Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Thu, 27 Jul 2023 15:28:20 +0800 Subject: [PATCH 34/41] code_size_compare: track removed object as well It makes sense to display code size changes if a file has been removed in our library. With this commit we track old objects as well. If a file is not present in the new Git revision, we display -old_size in the new_size column. The size change is marked as `Removed` to indicate the file has been removed. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 203 +++++++++++++++++++++-------------- 1 file changed, 125 insertions(+), 78 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 4a50c5b334e3..95d46b81c0db 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -486,7 +486,7 @@ class CodeSizeGeneratorWithSize(CodeSizeGenerator): class SizeEntry: # pylint: disable=too-few-public-methods """Data Structure to only store information of code size.""" - def __init__(self, text, data, bss, dec): + def __init__(self, text: int, data: int, bss: int, dec: int): self.text = text self.data = data self.bss = bss @@ -496,16 +496,20 @@ def __init__(self, logger: logging.Logger) -> None: """ Variable code_size is used to store size info for any Git revisions. :param code_size: Data Format as following: - {git_rev: {module: {file_name: [text, data, bss, dec], - etc ... - }, - etc ... - }, - etc ... - } + code_size = { + git_rev: { + module: { + file_name: SizeEntry, + ... + }, + ... + }, + ... + } """ super().__init__(logger) self.code_size = {} #type: typing.Dict[str, typing.Dict] + self.mod_total_suffix = '-' + 'TOTALS' def _set_size_record(self, git_rev: str, mod: str, size_text: str) -> None: """Store size information for target Git revision and high-level module. @@ -515,9 +519,11 @@ def _set_size_record(self, git_rev: str, mod: str, size_text: str) -> None: size_record = {} for line in size_text.splitlines()[1:]: data = line.split() + if re.match(r'\s*\(TOTALS\)', data[5]): + data[5] = mod + self.mod_total_suffix # file_name: SizeEntry(text, data, bss, dec) size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry( - data[0], data[1], data[2], data[3]) + int(data[0]), int(data[1]), int(data[2]), int(data[3])) self.code_size.setdefault(git_rev, {}).update({mod: size_record}) def read_size_record(self, git_rev: str, fname: str) -> None: @@ -538,10 +544,10 @@ def read_size_record(self, git_rev: str, fname: str) -> None: if mod: # file_name: SizeEntry(text, data, bss, dec) size_record[data[0]] = CodeSizeGeneratorWithSize.SizeEntry( - data[1], data[2], data[3], data[4]) + int(data[1]), int(data[2]), int(data[3]), int(data[4])) # check if we hit record for the end of a module - m = re.match(r'.?TOTALS', line) + m = re.match(r'\w+' + self.mod_total_suffix, line) if m: if git_rev in self.code_size: self.code_size[git_rev].update({mod: size_record}) @@ -550,19 +556,6 @@ def read_size_record(self, git_rev: str, fname: str) -> None: mod = "" size_record = {} - def _size_reader_helper( - self, - git_rev: str, - output: typing_util.Writable, - with_markdown=False - ) -> typing.Iterator[tuple]: - """A helper function to peel code_size based on Git revision.""" - for mod, file_size in self.code_size[git_rev].items(): - if not with_markdown: - output.write("\n" + mod + "\n") - for fname, size_entry in file_size.items(): - yield mod, fname, size_entry - def write_record( self, git_rev: str, @@ -571,7 +564,7 @@ def write_record( ) -> None: """Write size information to a file. - Writing Format: file_name text data bss total(dec) + Writing Format: filename text data bss total(dec) """ for mod, size_text in code_size_text.items(): self._set_size_record(git_rev, mod, size_text) @@ -579,12 +572,16 @@ def write_record( format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n" output.write(format_string.format("filename", "text", "data", "bss", "total")) - for _, fname, size_entry in self._size_reader_helper(git_rev, output): - output.write(format_string.format(fname, - size_entry.text, size_entry.data, - size_entry.bss, size_entry.total)) - def write_comparison( + for mod, f_size in self.code_size[git_rev].items(): + output.write("\n" + mod + "\n") + for fname, size_entry in f_size.items(): + output.write(format_string + .format(fname, + size_entry.text, size_entry.data, + size_entry.bss, size_entry.total)) + + def write_comparison( # pylint: disable=too-many-locals self, old_rev: str, new_rev: str, @@ -593,60 +590,110 @@ def write_comparison( ) -> None: """Write comparison result into a file. - Writing Format: file_name new(text,data) old(text,data)\ - change(text,data) + Writing Format: filename new(text) new(data) change(text) change(data) """ - - def cal_size_section_variation(mod, fname, size_entry, attr): - new_size = int(size_entry.__dict__[attr]) - # check if we have the file in old Git revision - if fname in self.code_size[old_rev][mod]: - old_size = int(self.code_size[old_rev][mod][fname].__dict__[attr]) - change = new_size - old_size - return [new_size, old_size, change] - else: - return [new_size] + header_line = ["filename", "new(text)", "change(text)", "new(data)", + "change(data)"] if with_markdown: - format_string = "| {:<30} | {:<9} | {:<9} | {:<12} | {:<12} |\n" + dash_line = [":----", "----:", "----:", "----:", "----:"] + line_format = "| {0:<30} | {1:<10} | {3:<10} | {2:<12} | {4:<12} |\n" + bold_text = lambda x: '**' + str(x) + '**' else: - format_string = "{:<30} {:<9} {:<9} {:<12} {:<12}\n" + line_format = "{0:<30} {1:<10} {3:<10} {2:<12} {4:<12}\n" + + def cal_sect_change( + old_size: typing.Optional[CodeSizeGeneratorWithSize.SizeEntry], + new_size: typing.Optional[CodeSizeGeneratorWithSize.SizeEntry], + sect: str + ) -> typing.List: + """Inner helper function to calculate size change for a section. + + Convention for special cases: + - If the object has been removed in new Git revision, + the size is minus code size of old Git revision; + the size change is marked as `Removed`, + - If the object only exists in new Git revision, + the size is code size of new Git revision; + the size change is marked as `None`, + + :param: old_size: code size for objects in old Git revision. + :param: new_size: code size for objects in new Git revision. + :param: sect: section to calculate from `size` tool. This could be + any instance variable in SizeEntry. + :return: List of [section size of objects for new Git revision, + section size change of objects between two Git revisions] + """ + if old_size and new_size: + new_attr = new_size.__dict__[sect] + change_attr = new_size.__dict__[sect] - old_size.__dict__[sect] + elif old_size: + new_attr = - old_size.__dict__[sect] + change_attr = 'Removed' + elif new_size: + new_attr = new_size.__dict__[sect] + change_attr = 'None' + else: + # Should never happen + new_attr = 'Error' + change_attr = 'Error' + return [new_attr, change_attr] + + # sort dictionary by key + sort_by_k = lambda item: item[0].lower() + def get_results( + f_rev_size: + typing.Dict[str, + typing.Dict[str, + CodeSizeGeneratorWithSize.SizeEntry]] + ) -> typing.List: + """Return List of results in the format of: + [filename, new(text), change(text), new(data), change(data)] + """ + res = [] + for fname, revs_size in sorted(f_rev_size.items(), key=sort_by_k): + old_size = revs_size.get(old_rev) + new_size = revs_size.get(new_rev) + + text_sect = cal_sect_change(old_size, new_size, 'text') + data_sect = cal_sect_change(old_size, new_size, 'data') + # skip the files that haven't changed in code size + if text_sect[1] == 0 and data_sect[1] == 0: + continue - output.write(format_string - .format("filename", - "new(text)", "new(data)", "change(text)", - "change(data)")) + res.append([fname, *text_sect, *data_sect]) + return res + + # write header + output.write(line_format.format(*header_line)) if with_markdown: - output.write(format_string - .format(":----", "----:", "----:", "----:", "----:")) - - for mod, fname, size_entry in \ - self._size_reader_helper(new_rev, output, with_markdown): - text_vari = cal_size_section_variation(mod, fname, - size_entry, 'text') - data_vari = cal_size_section_variation(mod, fname, - size_entry, 'data') - - if len(text_vari) != 1: - # skip the files that haven't changed in code size if we write - # comparison result in a markdown table. - if with_markdown and text_vari[2] == 0 and data_vari[2] == 0: - continue - output.write( - format_string - .format(fname, - # new(text), new(data) - str(text_vari[0]), str(data_vari[0]), - # change(text), change(data) - str(text_vari[2]), str(data_vari[2]))) - else: - output.write( - format_string - .format(fname, - # new(text), new(data) - str(text_vari[0]), str(data_vari[0]), - # change(text), change(data) - 'None', 'None')) + output.write(line_format.format(*dash_line)) + for mod in MBEDTLS_STATIC_LIB: + # convert self.code_size to: + # { + # file_name: { + # old_rev: SizeEntry, + # new_rev: SizeEntry + # }, + # ... + # } + f_rev_size = {} #type: typing.Dict[str, typing.Dict] + for fname, size_entry in self.code_size[old_rev][mod].items(): + f_rev_size.setdefault(fname, {}).update({old_rev: size_entry}) + for fname, size_entry in self.code_size[new_rev][mod].items(): + f_rev_size.setdefault(fname, {}).update({new_rev: size_entry}) + + mod_total_sz = f_rev_size.pop(mod + self.mod_total_suffix) + res = get_results(f_rev_size) + total_clm = get_results({mod + self.mod_total_suffix: mod_total_sz}) + if with_markdown: + # bold row of mod-TOTALS in markdown table + total_clm = [[bold_text(j) for j in i] for i in total_clm] + res += total_clm + + # write comparison result + for line in res: + output.write(line_format.format(*line)) class CodeSizeComparison: From b167320e2761319c103ef7d6afc3292a81d45117 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Fri, 28 Jul 2023 13:47:19 +0800 Subject: [PATCH 35/41] code_size_compare: use '.md' suffix if '--markdown' enabled Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 95d46b81c0db..1bcc731949a6 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -815,10 +815,11 @@ def gen_code_size_comparison(self) -> None: else: output_file = os.path.join( self.comp_dir, - '{}-{}-{}.csv' + '{}-{}-{}.{}' .format(self.old_size_dist_info.get_info_indication(), self.new_size_dist_info.get_info_indication(), - self.size_common_info.get_info_indication())) + self.size_common_info.get_info_indication(), + 'md' if self.result_options.with_markdown else 'csv')) output = open(output_file, "w") self.logger.debug("Generating comparison results between {} and {}." From ee07afa2051a1d7d8a20e46035b3cc8e4c94fc80 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Fri, 28 Jul 2023 16:34:05 +0800 Subject: [PATCH 36/41] code_size_compare: add option '--show-all' When '--show-all' is enabled, all the objects will be displayed in comparison result no matter if there is code size change or not. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 1bcc731949a6..72c69e488b36 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -116,12 +116,13 @@ def get_info_indication(self): class CodeSizeResultInfo: # pylint: disable=too-few-public-methods """Data structure to store result options for code size comparison.""" - def __init__( + def __init__( #pylint: disable=too-many-arguments self, record_dir: str, comp_dir: str, with_markdown=False, stdout=False, + show_all=False, ) -> None: """ :param record_dir: directory to store code size record. @@ -130,11 +131,13 @@ def __init__( (Default: False) :param stdout: direct comparison result into sys.stdout. (Default False) + :param show_all: show all objects in comparison result. (Default False) """ self.record_dir = record_dir self.comp_dir = comp_dir self.with_markdown = with_markdown self.stdout = stdout + self.show_all = show_all DETECT_ARCH_CMD = "cc -dM -E - < /dev/null" @@ -462,12 +465,13 @@ def write_record( """ raise NotImplementedError - def write_comparison( + def write_comparison( #pylint: disable=too-many-arguments self, old_rev: str, new_rev: str, output: typing_util.Writable, - with_markdown=False + with_markdown=False, + show_all=False ) -> None: """Write a comparision result into a stream between two Git revisions. @@ -477,6 +481,7 @@ def write_comparison( (File / sys.stdout) :param with_markdown: write comparision result in a markdown table. (Default: False) + :param show_all: show all objects in comparison result. (Default False) """ raise NotImplementedError @@ -581,13 +586,15 @@ def write_record( size_entry.text, size_entry.data, size_entry.bss, size_entry.total)) - def write_comparison( # pylint: disable=too-many-locals + def write_comparison( #pylint: disable=too-many-arguments self, old_rev: str, new_rev: str, output: typing_util.Writable, - with_markdown=False + with_markdown=False, + show_all=False ) -> None: + # pylint: disable=too-many-locals """Write comparison result into a file. Writing Format: filename new(text) new(data) change(text) change(data) @@ -658,7 +665,7 @@ def get_results( text_sect = cal_sect_change(old_size, new_size, 'text') data_sect = cal_sect_change(old_size, new_size, 'data') # skip the files that haven't changed in code size - if text_sect[1] == 0 and data_sect[1] == 0: + if not show_all and text_sect[1] == 0 and data_sect[1] == 0: continue res.append([fname, *text_sect, *data_sect]) @@ -834,7 +841,8 @@ def gen_code_size_comparison(self) -> None: self.code_size_generator.write_comparison( self.old_size_dist_info.git_rev, self.new_size_dist_info.git_rev, - output, self.result_options.with_markdown) + output, self.result_options.with_markdown, + self.result_options.show_all) def get_comparision_results(self) -> None: """Compare size of library/*.o between self.old_size_dist_info and @@ -887,6 +895,10 @@ def main(): '--stdout', action='store_true', dest='stdout', help='Set this option to direct comparison result into sys.stdout. ' '(Default: file)') + group_optional.add_argument( + '--show-all', action='store_true', dest='show_all', + help='Show all the objects in comparison result, including the ones ' + 'that haven\'t changed in code size. (Default: False)') group_optional.add_argument( '--verbose', action='store_true', dest='verbose', help='Show logs in detail for code size measurement. ' @@ -923,10 +935,10 @@ def main(): # host_arch, measure_cmd size_common_info = CodeSizeCommonInfo( detect_arch(), 'size -t') - # record_dir, comp_dir, with_markdown, stdout + # record_dir, comp_dir, with_markdown, stdout, show_all result_options = CodeSizeResultInfo( comp_args.record_dir, comp_args.comp_dir, - comp_args.markdown, comp_args.stdout) + comp_args.markdown, comp_args.stdout, comp_args.show_all) logger.info("Measure code size between {} and {} by `{}`." .format(old_size_dist_info.get_info_indication(), From 1998aac349a36f4a810e1f07f271eab55e80c7b4 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Mon, 14 Aug 2023 10:33:37 +0800 Subject: [PATCH 37/41] logging_util: support to tweak loglevel directed to stderr/stdout Previously we set loglevel >= WARNING printed to stderr and loglevel < WARNING printed to stdout. To be more flexible, we replace this `WARNING` value with an argument: split_level and leave `WARNING` as default split_level if not set. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 7 ++----- scripts/mbedtls_dev/logging_util.py | 16 +++++++++------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 72c69e488b36..672b80366f35 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -906,11 +906,8 @@ def main(): comp_args = parser.parse_args() logger = logging.getLogger() - logging_util.configure_logger(logger) - if comp_args.stdout and not comp_args.verbose: - logger.setLevel(logging.ERROR) - else: - logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO) + logging_util.configure_logger(logger, split_level=logging.NOTSET) + logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO) if os.path.isfile(comp_args.record_dir): logger.error("record directory: {} is not a directory" diff --git a/scripts/mbedtls_dev/logging_util.py b/scripts/mbedtls_dev/logging_util.py index 85a3f19ace7b..db1ebfe5cf48 100644 --- a/scripts/mbedtls_dev/logging_util.py +++ b/scripts/mbedtls_dev/logging_util.py @@ -21,14 +21,16 @@ def configure_logger( logger: logging.Logger, - log_format="[%(levelname)s]: %(message)s" + log_format="[%(levelname)s]: %(message)s", + split_level=logging.WARNING ) -> None: """ Configure the logging.Logger instance so that: - Format is set to any log_format. Default: "[%(levelname)s]: %(message)s" - - loglevel >= WARNING are printed to stderr. - - loglevel < WARNING are printed to stdout. + - loglevel >= split_level are printed to stderr. + - loglevel < split_level are printed to stdout. + Default: logging.WARNING """ class MaxLevelFilter(logging.Filter): # pylint: disable=too-few-public-methods @@ -41,14 +43,14 @@ def filter(self, record: logging.LogRecord) -> bool: log_formatter = logging.Formatter(log_format) - # set loglevel >= WARNING to be printed to stderr + # set loglevel >= split_level to be printed to stderr stderr_hdlr = logging.StreamHandler(sys.stderr) - stderr_hdlr.setLevel(logging.WARNING) + stderr_hdlr.setLevel(split_level) stderr_hdlr.setFormatter(log_formatter) - # set loglevel <= INFO to be printed to stdout + # set loglevel < split_level to be printed to stdout stdout_hdlr = logging.StreamHandler(sys.stdout) - stdout_hdlr.addFilter(MaxLevelFilter(logging.INFO)) + stdout_hdlr.addFilter(MaxLevelFilter(split_level - 1)) stdout_hdlr.setFormatter(log_formatter) logger.addHandler(stderr_hdlr) From 9a6ee71f6fe3c6c7283f3f3ac0be1116fc24449b Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Mon, 14 Aug 2023 11:30:24 +0800 Subject: [PATCH 38/41] code_size_compare: right-align numbers in the comparison result Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 672b80366f35..d1e8a1b71b2b 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -604,10 +604,10 @@ def write_comparison( #pylint: disable=too-many-arguments if with_markdown: dash_line = [":----", "----:", "----:", "----:", "----:"] - line_format = "| {0:<30} | {1:<10} | {3:<10} | {2:<12} | {4:<12} |\n" + line_format = "| {0:<30} | {1:>10} | {3:>10} | {2:>12} | {4:>12} |\n" bold_text = lambda x: '**' + str(x) + '**' else: - line_format = "{0:<30} {1:<10} {3:<10} {2:<12} {4:<12}\n" + line_format = "{0:<30} {1:>10} {3:>10} {2:>12} {4:>12}\n" def cal_sect_change( old_size: typing.Optional[CodeSizeGeneratorWithSize.SizeEntry], From 0de1183e4cdc71c5f7a8524fbe669eee01853b2c Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Mon, 14 Aug 2023 11:54:47 +0800 Subject: [PATCH 39/41] code_size_compare: add `+` in front of positive values In comparison result, to indicate it's a delta value, we add `+` in front of positive values. For unchanged attributes, it's still shown as `0'. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index d1e8a1b71b2b..841eb47d538f 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -633,7 +633,8 @@ def cal_sect_change( """ if old_size and new_size: new_attr = new_size.__dict__[sect] - change_attr = new_size.__dict__[sect] - old_size.__dict__[sect] + delta = new_size.__dict__[sect] - old_size.__dict__[sect] + change_attr = '{0:{1}}'.format(delta, '+' if delta else '') elif old_size: new_attr = - old_size.__dict__[sect] change_attr = 'Removed' @@ -665,7 +666,7 @@ def get_results( text_sect = cal_sect_change(old_size, new_size, 'text') data_sect = cal_sect_change(old_size, new_size, 'data') # skip the files that haven't changed in code size - if not show_all and text_sect[1] == 0 and data_sect[1] == 0: + if not show_all and text_sect[1] == '0' and data_sect[1] == '0': continue res.append([fname, *text_sect, *data_sect]) From 8a25e6fdb2b3b34811a4fd3b77faf0dbe4408936 Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Mon, 14 Aug 2023 14:38:36 +0800 Subject: [PATCH 40/41] code_size_compare: add old text and data section in CSV output To keep a concise markdown table, we don't list text and data section from old Git revision. However, it should be ideal to keep those two sections in CSV output. Therefore, we list comparison result for CSV output in following format: filename new(text) new(data) old(text) old(data) change(text) change(data) Additionally, if a file only exits in new Git revision not in old Git revision, it's marked as `NotCreated` as we haven't created this file yet from perspective of old Git revision. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 841eb47d538f..52e0345c338d 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -597,17 +597,23 @@ def write_comparison( #pylint: disable=too-many-arguments # pylint: disable=too-many-locals """Write comparison result into a file. - Writing Format: filename new(text) new(data) change(text) change(data) + Writing Format: + Markdown Output: + filename new(text) new(data) change(text) change(data) + CSV Output: + filename new(text) new(data) old(text) old(data) change(text) change(data) """ - header_line = ["filename", "new(text)", "change(text)", "new(data)", - "change(data)"] - + header_line = ["filename", "new(text)", "old(text)", "change(text)", + "new(data)", "old(data)", "change(data)"] if with_markdown: - dash_line = [":----", "----:", "----:", "----:", "----:"] - line_format = "| {0:<30} | {1:>10} | {3:>10} | {2:>12} | {4:>12} |\n" + dash_line = [":----", "----:", "----:", "----:", + "----:", "----:", "----:"] + # | filename | new(text) | new(data) | change(text) | change(data) | + line_format = "| {0:<30} | {1:>9} | {4:>9} | {3:>12} | {6:>12} |\n" bold_text = lambda x: '**' + str(x) + '**' else: - line_format = "{0:<30} {1:>10} {3:>10} {2:>12} {4:>12}\n" + # filename new(text) new(data) old(text) old(data) change(text) change(data) + line_format = "{0:<30} {1:>9} {4:>9} {2:>10} {5:>10} {3:>12} {6:>12}\n" def cal_sect_change( old_size: typing.Optional[CodeSizeGeneratorWithSize.SizeEntry], @@ -629,23 +635,28 @@ def cal_sect_change( :param: sect: section to calculate from `size` tool. This could be any instance variable in SizeEntry. :return: List of [section size of objects for new Git revision, + section size of objects for old Git revision, section size change of objects between two Git revisions] """ if old_size and new_size: new_attr = new_size.__dict__[sect] - delta = new_size.__dict__[sect] - old_size.__dict__[sect] + old_attr = old_size.__dict__[sect] + delta = new_attr - old_attr change_attr = '{0:{1}}'.format(delta, '+' if delta else '') elif old_size: new_attr = - old_size.__dict__[sect] + old_attr = old_size.__dict__[sect] change_attr = 'Removed' elif new_size: new_attr = new_size.__dict__[sect] + old_attr = 'NotCreated' change_attr = 'None' else: # Should never happen new_attr = 'Error' + old_attr = 'Error' change_attr = 'Error' - return [new_attr, change_attr] + return [new_attr, old_attr, change_attr] # sort dictionary by key sort_by_k = lambda item: item[0].lower() @@ -656,7 +667,8 @@ def get_results( CodeSizeGeneratorWithSize.SizeEntry]] ) -> typing.List: """Return List of results in the format of: - [filename, new(text), change(text), new(data), change(data)] + [filename, new(text), old(text), change(text), + new(data), old(data), change(data)] """ res = [] for fname, revs_size in sorted(f_rev_size.items(), key=sort_by_k): @@ -666,7 +678,7 @@ def get_results( text_sect = cal_sect_change(old_size, new_size, 'text') data_sect = cal_sect_change(old_size, new_size, 'data') # skip the files that haven't changed in code size - if not show_all and text_sect[1] == '0' and data_sect[1] == '0': + if not show_all and text_sect[-1] == '0' and data_sect[-1] == '0': continue res.append([fname, *text_sect, *data_sect]) From bc775c48c92bc524384dc0e31f39022073aa70af Mon Sep 17 00:00:00 2001 From: Yanray Wang Date: Wed, 16 Aug 2023 15:59:55 +0800 Subject: [PATCH 41/41] code_size_compare: handle deleted files and new files properly 'Removed' and 'NotCreated' should be displayed in new and old column respectively. The value of delta is reflected on change column. This commit handles the corner cases properly. Signed-off-by: Yanray Wang --- scripts/code_size_compare.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py index 52e0345c338d..53d859edfa65 100755 --- a/scripts/code_size_compare.py +++ b/scripts/code_size_compare.py @@ -644,13 +644,15 @@ def cal_sect_change( delta = new_attr - old_attr change_attr = '{0:{1}}'.format(delta, '+' if delta else '') elif old_size: - new_attr = - old_size.__dict__[sect] + new_attr = 'Removed' old_attr = old_size.__dict__[sect] - change_attr = 'Removed' + delta = - old_attr + change_attr = '{0:{1}}'.format(delta, '+' if delta else '') elif new_size: new_attr = new_size.__dict__[sect] old_attr = 'NotCreated' - change_attr = 'None' + delta = new_attr + change_attr = '{0:{1}}'.format(delta, '+' if delta else '') else: # Should never happen new_attr = 'Error'