summaryrefslogtreecommitdiff
path: root/scripts/code_size_compare.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/code_size_compare.py')
-rwxr-xr-xscripts/code_size_compare.py952
1 files changed, 0 insertions, 952 deletions
diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py
deleted file mode 100755
index abd13df240f..00000000000
--- a/scripts/code_size_compare.py
+++ /dev/null
@@ -1,952 +0,0 @@
-#!/usr/bin/env python3
-
-"""
-This script is for comparing the size of the library files from two
-different Git revisions within an Mbed TLS repository.
-The results of the comparison is formatted as csv and stored at a
-configurable location.
-Note: must be run from Mbed TLS root.
-"""
-
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-
-import argparse
-import logging
-import os
-import re
-import shutil
-import subprocess
-import sys
-import typing
-from enum import Enum
-
-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."""
- AARCH64 = 'aarch64'
- AARCH32 = 'aarch32'
- ARMV8_M = 'armv8-m'
- X86_64 = 'x86_64'
- X86 = 'x86'
-
-
-class SupportedConfig(Enum):
- """Supported configuration for code size measurement."""
- DEFAULT = 'default'
- TFM_MEDIUM = 'tfm-medium'
-
-
-# Static library
-MBEDTLS_STATIC_LIB = {
- 'CRYPTO': 'library/libmbedcrypto.a',
- 'X509': 'library/libmbedx509.a',
- '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:
- return SupportedArch.AARCH64.value
- if '__arm__' in cc_output:
- return SupportedArch.AARCH32.value
- if '__x86_64__' in cc_output:
- return SupportedArch.X86_64.value
- if '__i386__' in cc_output:
- return SupportedArch.X86.value
- else:
- print("Unknown host architecture, cannot auto-detect arch.")
- sys.exit(1)
-
-TFM_MEDIUM_CONFIG_H = 'configs/ext/tfm_mbedcrypto_config_profile_medium.h'
-TFM_MEDIUM_CRYPTO_CONFIG_H = 'configs/ext/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
- infer build command for code size measurement.
- """
-
- 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,
- ]
-
- def __init__(
- self,
- size_dist_info: CodeSizeDistinctInfo,
- host_arch: str,
- logger: logging.Logger,
- ) -> None:
- """
- :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 = 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 {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:
- 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
- self.logger.error(comb)
- sys.exit(1)
-
-
-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."""
-
- 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 __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()
- 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, 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
- """
- mod = ""
- size_record = {}
- with open(fname, 'r') as csv_file:
- for line in csv_file:
- data = line.strip().split()
- # check if we find the beginning of a module
- if data and data[0] in MBEDTLS_STATIC_LIB:
- mod = data[0]
- continue
-
- if mod:
- # 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'\w+' + self.mod_total_suffix, line)
- if m:
- 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}
- mod = ""
- 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: filename 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"))
-
- 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,
- with_markdown=False,
- show_all=False
- ) -> None:
- # pylint: disable=too-many-locals
- """Write comparison result into a file.
-
- 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)", "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:
- # 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:
- """Compare code size between two Git revisions."""
-
- def __init__( #pylint: disable=too-many-arguments
- self,
- old_size_dist_info: CodeSizeDistinctInfo,
- new_size_dist_info: CodeSizeDistinctInfo,
- size_common_info: CodeSizeCommonInfo,
- result_options: CodeSizeResultInfo,
- logger: logging.Logger,
- ) -> None:
- """
- :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.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)
-
- 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:
- self.logger.error("Unsupported measurement tool: `{}`."
- .format(self.size_common_info.measure_cmd
- .strip().split(' ')[0]))
- sys.exit(1)
-
- def cal_code_size(
- self,
- size_dist_info: CodeSizeDistinctInfo
- ) -> typing.Dict[str, str]:
- """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()
-
- 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,
- '{}-{}.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):
- 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:
- # 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,
- old and new.
-
- - 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)
- """
-
- 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")
-
- 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.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__))
- group_required = parser.add_argument_group(
- '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 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(
- '--record-dir', type=str, default='code_size_records',
- help='directory where code size record is stored. '
- '(Default: code_size_records)')
- group_optional.add_argument(
- '--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='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.)')
- group_optional.add_argument(
- '-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 Mbed TLS 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()
-
- logger = logging.getLogger()
- 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"
- .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)
-
- 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)
-
- # 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()