Merge pull request #7937 from yanrayw/code_size_compare_improvement
code_size_compare.py: preparation work to show code size changes in PR comment
diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py
index 0ed2899..53d859e 100755
--- a/scripts/code_size_compare.py
+++ b/scripts/code_size_compare.py
@@ -24,15 +24,18 @@
# limitations under the License.
import argparse
+import logging
import os
import re
+import shutil
import subprocess
import sys
import typing
from enum import Enum
-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."""
@@ -42,13 +45,13 @@
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):
"""Supported configuration for code size measurement."""
DEFAULT = 'default'
TFM_MEDIUM = 'tfm-medium'
+
# Static library
MBEDTLS_STATIC_LIB = {
'CRYPTO': 'library/libmbedcrypto.a',
@@ -56,23 +59,111 @@
'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,
+ compiler: str,
+ opt_level: 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: 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.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 = ''
+
+ def get_info_indication(self):
+ """Return a unique string to indicate Code Size Distinct Information."""
+ return '{git_rev}-{arch}-{config}-{compiler}'.format(**self.__dict__)
+
+
+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
+
+ def get_info_indication(self):
+ """Return a unique string to indicate Code Size Common Information."""
+ 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."""
+ 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.
+ :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)
+ :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"
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 '__i386__' in cc_output:
return SupportedArch.X86.value
else:
print("Unknown host architecture, cannot auto-detect arch.")
sys.exit(1)
-class CodeSizeInfo: # pylint: disable=too-few-public-methods
+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.
It collects information about architecture, configuration in order to
@@ -80,90 +171,367 @@
"""
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__(self, arch: str, config: str, sys_arch: str) -> None:
+ def __init__(
+ self,
+ size_dist_info: CodeSizeDistinctInfo,
+ host_arch: str,
+ logger: logging.Logger,
+ ) -> 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).
+ :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.
+ - 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.arch = arch
- self.config = config
- self.sys_arch = sys_arch
- self.make_command = self.set_make_command()
+ 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
- def set_make_command(self) -> str:
- """Infer build command based on architecture and configuration."""
+ 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.sys_arch:
- return 'make -j lib CFLAGS=\'-Os \' '
+ self.arch == self.host_arch:
+ return True
+ # TF-M
elif self.arch == SupportedArch.ARMV8_M.value and \
self.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 + '\\\" \''
+ 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 {src} {dest}'
+ .format(src=TFM_MEDIUM_CONFIG_H, dest=CONFIG_H))
+ pre_make_cmd.append('cp {src} {dest}'
+ .format(src=TFM_MEDIUM_CRYPTO_CONFIG_H,
+ dest=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 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:
- print("Unsupported combination of architecture: {} and configuration: {}"
- .format(self.arch, self.config))
- print("\nPlease use supported combination of architecture and configuration:")
- for comb in CodeSizeInfo.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:
+ self.logger.error("Unsupported combination of architecture: {} " \
+ "and configuration: {}.\n"
+ .format(self.arch,
+ self.config))
+ self.logger.error("Please use supported combination of " \
+ "architecture and configuration:")
+ for comb in CodeSizeBuildInfo.SupportedArchConfig:
+ 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
- print(comb)
+ self.logger.error(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 CodeSizeCalculator:
+ """ A calculator to calculate code size of library/*.o based on
+ Git revision and code size measurement tool.
+ """
+
+ 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
+ """
+ self.repo_path = "."
+ self.git_command = "git"
+ 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
+
+ @staticmethod
+ def validate_git_revision(git_rev: str) -> str:
+ result = subprocess.check_output(["git", "rev-parse", "--verify",
+ git_rev + "^{commit}"],
+ shell=False, universal_newlines=True)
+ return result[: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':
+ self.logger.debug("Using current work directory.")
+ git_worktree_path = self.repo_path
+ else:
+ self.logger.debug("Creating git worktree for {}."
+ .format(self.git_rev))
+ git_worktree_path = os.path.join(self.repo_path,
+ "temp-" + self.git_rev)
+ subprocess.check_output(
+ [self.git_command, "worktree", "add", "--detach",
+ git_worktree_path, self.git_rev], cwd=self.repo_path,
+ stderr=subprocess.STDOUT
+ )
+
+ 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."""
+
+ self.logger.debug("Building library/*.o for {}."
+ .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,
+ universal_newlines=True
+ )
+ subprocess.check_output(
+ self.make_cmd, env=my_environment, shell=True,
+ 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)
+
+ 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.git_rev,
+ self.measure_cmd.strip().split(' ')[0]))
+
+ res = {}
+ for mod, st_lib in MBEDTLS_STATIC_LIB.items():
+ try:
+ result = subprocess.check_output(
+ [self.measure_cmd + ' ' + st_lib], cwd=git_worktree_path,
+ shell=True, 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:
+ 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,
+ 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
+ self.logger.error(e, exc_info=True)
+ 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[str, str]:
+ """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()
+ 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
+
+
+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
+ write_record and 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 write_record(
+ self,
+ git_rev: str,
+ code_size_text: typing.Dict[str, str],
+ output: typing_util.Writable
+ ) -> None:
+ """Write size record into a file.
+
+ :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]
+ :param output: output stream which the code size record is written to.
+ (Note: Normally write code size record into File)
+ """
+ raise NotImplementedError
+
+ def write_comparison( #pylint: disable=too-many-arguments
+ self,
+ old_rev: str,
+ new_rev: str,
+ output: typing_util.Writable,
+ with_markdown=False,
+ show_all=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: 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)
+ :param show_all: show all objects in comparison result. (Default False)
+ """
+ raise NotImplementedError
+
+
+class CodeSizeGeneratorWithSize(CodeSizeGenerator):
"""Code Size Base Class for size record saving and writing."""
- 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,
- etc ...
- },
- etc ...
- },
- etc ...
- }
- """
- self.code_size = {} #type: typing.Dict[str, typing.Dict]
+ class SizeEntry: # pylint: disable=too-few-public-methods
+ """Data Structure to only store information of code size."""
+ def __init__(self, text: int, data: int, bss: int, dec: int):
+ self.text = text
+ self.data = data
+ self.bss = bss
+ self.total = dec # total <=> dec
- def set_size_record(self, revision: str, mod: str, size_text: str) -> None:
- """Store size information for target revision and high-level module.
+ 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:
+ 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.
size_text Format: text data bss dec hex filename
"""
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])
- if revision in self.code_size:
- self.code_size[revision].update({mod: size_record})
- else:
- self.code_size[revision] = {mod: size_record}
+ 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(
+ 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, 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
@@ -179,232 +547,325 @@
continue
if mod:
- size_record[data[0]] = \
- SizeEntry(data[1], data[2], data[3], data[4])
+ # file_name: SizeEntry(text, data, bss, dec)
+ size_record[data[0]] = CodeSizeGeneratorWithSize.SizeEntry(
+ 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 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(
+ def write_record(
self,
- revision: str,
- output: typing_util.Writable
- ) -> 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")
- for fname, size_entry in file_size.items():
- yield mod, fname, size_entry
-
- def write_size_record(
- self,
- revision: str,
+ 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)
+ Writing Format: filename text data bss total(dec)
"""
- output.write("{:<30} {:>7} {:>7} {:>7} {:>7}\n"
- .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))
+ for mod, size_text in code_size_text.items():
+ self._set_size_record(git_rev, mod, size_text)
- def write_comparison(
+ format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n"
+ output.write(format_string.format("filename",
+ "text", "data", "bss", "total"))
+
+ 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-arguments
self,
old_rev: str,
new_rev: str,
- output: typing_util.Writable
+ output: typing_util.Writable,
+ with_markdown=False,
+ show_all=False
) -> None:
+ # pylint: disable=too-many-locals
"""Write comparison result into a file.
- Writing Format: file_name current(total) old(total) change(Byte) change_pct(%)
+ 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)
"""
- 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)
- # 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)
- 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))
+ header_line = ["filename", "new(text)", "old(text)", "change(text)",
+ "new(data)", "old(data)", "change(data)"]
+ if with_markdown:
+ 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:
+ # 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],
+ 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 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]
+ 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 = 'Removed'
+ old_attr = old_size.__dict__[sect]
+ delta = - old_attr
+ change_attr = '{0:{1}}'.format(delta, '+' if delta else '')
+ elif new_size:
+ new_attr = new_size.__dict__[sect]
+ old_attr = 'NotCreated'
+ delta = new_attr
+ change_attr = '{0:{1}}'.format(delta, '+' if delta else '')
else:
- output.write("{} {}\n".format(fname, new_size))
+ # Should never happen
+ new_attr = 'Error'
+ old_attr = 'Error'
+ change_attr = 'Error'
+ return [new_attr, old_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), 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):
+ 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 not show_all and text_sect[-1] == '0' and data_sect[-1] == '0':
+ continue
+
+ res.append([fname, *text_sect, *data_sect])
+ return res
+
+ # write header
+ output.write(line_format.format(*header_line))
+ if with_markdown:
+ 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(CodeSizeBase):
+class CodeSizeComparison:
"""Compare code size between two Git revisions."""
- def __init__(
+ def __init__( #pylint: disable=too-many-arguments
self,
- old_revision: str,
- new_revision: str,
- result_dir: str,
- code_size_info: CodeSizeInfo
+ old_size_dist_info: CodeSizeDistinctInfo,
+ new_size_dist_info: CodeSizeDistinctInfo,
+ size_common_info: CodeSizeCommonInfo,
+ result_options: CodeSizeResultInfo,
+ logger: logging.Logger,
) -> 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.
+ :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
"""
- super().__init__()
- self.repo_path = "."
- self.result_dir = os.path.abspath(result_dir)
- os.makedirs(self.result_dir, exist_ok=True)
- self.csv_dir = os.path.abspath("code_size_records/")
+ self.logger = logger
+
+ 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,
+ self.logger).infer_make_command()
+ 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)
- self.old_rev = old_revision
- self.new_rev = new_revision
- self.git_command = "git"
- self.make_clean = 'make clean'
- self.make_command = code_size_info.make_command
- self.fname_suffix = "-" + code_size_info.arch + "-" +\
- code_size_info.config
-
- @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
+ def __generate_size_parser(self):
+ """Generate a parser for the corresponding measurement tool."""
+ if re.match(r'size', self.size_common_info.measure_cmd.strip()):
+ return CodeSizeGeneratorWithSize(self.logger)
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
- )
+ self.logger.error("Unsupported measurement tool: `{}`."
+ .format(self.size_common_info.measure_cmd
+ .strip().split(' ')[0]))
+ sys.exit(1)
- return git_worktree_path
+ def cal_code_size(
+ self,
+ size_dist_info: CodeSizeDistinctInfo
+ ) -> typing.Dict[str, str]:
+ """Calculate code size of library/*.o in a UTF-8 encoding"""
- def _build_libraries(self, git_worktree_path: str) -> None:
- """Build libraries in the specified worktree."""
+ 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()
- 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_report(self, size_dist_info: CodeSizeDistinctInfo) -> None:
+ """Generate code size record and write it into a file."""
- def _gen_code_size_csv(self, revision: str, git_worktree_path: str) -> None:
- """Generate code size csv file."""
-
- if revision == "current":
- print("Measuring code size in current work directory")
- 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.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)
-
- 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 _get_code_size_for_rev(self, revision: str) -> None:
- """Generate code size csv file for the specified git revision."""
-
+ self.logger.info("Start to generate code size record for {}."
+ .format(size_dist_info.git_rev))
+ 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
- csv_fname = revision + self.fname_suffix + ".csv"
- 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))
+ 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_dist_info.git_rev))
+ self.code_size_generator.read_size_record(
+ size_dist_info.git_rev, output_file)
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)
+ # measure code size
+ code_size_text = self.cal_code_size(size_dist_info)
- 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."""
+ 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)
- res_file = open(os.path.join(self.result_dir, "compare-" +
- self.old_rev + "-" + self.new_rev +
- self.fname_suffix +
- ".csv"), "w")
+ def gen_code_size_comparison(self) -> None:
+ """Generate results of code size changes between two Git revisions,
+ old and new.
- print("\nGenerating comparison results between",\
- self.old_rev, "and", self.new_rev)
- self.write_comparison(self.old_rev, self.new_rev, res_file)
+ - Measured code size result of these two Git revisions must be available.
+ - The result is directed into either file / stdout depending on
+ the option, size_common_info.result_options.stdout. (Default: file)
+ """
- return 0
+ self.logger.info("Start to generate comparision result 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,
+ '{}-{}-{}.{}'
+ .format(self.old_size_dist_info.get_info_indication(),
+ self.new_size_dist_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")
- def get_comparision_results(self) -> int:
- """Compare size of library/*.o between self.old_rev and self.new_rev,
- and generate the result file."""
+ 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,
+ 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
+ self.old_size_dist_info 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()
-
- 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)
+ 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()
def main():
parser = argparse.ArgumentParser(description=(__doc__))
@@ -412,55 +873,92 @@
'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 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",
- help="directory where comparison result is stored, \
- default is 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(
- "-n", "--new-rev", type=str, default=None,
- help="new revision for comparison, default is the current work \
- directory, including uncommitted changes.")
+ '--comp-dir', type=str, default='comparison',
+ help='directory where comparison result is stored. '
+ '(Default: comparison)')
group_optional.add_argument(
- "-a", "--arch", type=str, default=detect_arch(),
+ '-n', '--new-rev', type=str, default='current',
+ help='new Git revision as comparison base. '
+ '(Default is the current work directory, including uncommitted '
+ 'changes.)')
+ group_optional.add_argument(
+ '-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).')
+ group_optional.add_argument(
+ '--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. '
+ '(Default: False)')
comp_args = parser.parse_args()
- if os.path.isfile(comp_args.result_dir):
- print("Error: {} is not a directory".format(comp_args.result_dir))
- parser.exit()
+ logger = logging.getLogger()
+ logging_util.configure_logger(logger, split_level=logging.NOTSET)
+ logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO)
- validate_res = CodeSizeComparison.validate_revision(comp_args.old_rev)
- old_revision = validate_res.decode().replace("\n", "")
+ 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("comparison directory: {} is not a directory"
+ .format(comp_args.comp_dir))
+ sys.exit(1)
- if comp_args.new_rev is not None:
- validate_res = CodeSizeComparison.validate_revision(comp_args.new_rev)
- new_revision = validate_res.decode().replace("\n", "")
- else:
- new_revision = "current"
+ 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)
- 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)
- return_code = size_compare.get_comparision_results()
- sys.exit(return_code)
+ # version, git_rev, arch, config, compiler, opt_level
+ old_size_dist_info = CodeSizeDistinctInfo(
+ 'old', comp_args.old_rev, comp_args.arch, comp_args.config, 'cc', '-Os')
+ new_size_dist_info = CodeSizeDistinctInfo(
+ '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')
+ # 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.show_all)
+ 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()
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 0000000..db1ebfe
--- /dev/null
+++ b/scripts/mbedtls_dev/logging_util.py
@@ -0,0 +1,57 @@
+"""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,
+ 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 >= 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
+ 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(log_format)
+
+ # set loglevel >= split_level to be printed to stderr
+ stderr_hdlr = logging.StreamHandler(sys.stderr)
+ stderr_hdlr.setLevel(split_level)
+ stderr_hdlr.setFormatter(log_formatter)
+
+ # set loglevel < split_level to be printed to stdout
+ stdout_hdlr = logging.StreamHandler(sys.stdout)
+ stdout_hdlr.addFilter(MaxLevelFilter(split_level - 1))
+ 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 5506e40..623fd23 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 @@
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 @@
# 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)