blob: eefda311a01e4451e29075754cf74e6748dc48ad [file] [log] [blame]
Xiaofei Baibca03e52021-09-09 09:42:37 +00001#!/usr/bin/env python3
2
3"""
Xiaofei Baibca03e52021-09-09 09:42:37 +00004This script is for comparing the size of the library files from two
5different Git revisions within an Mbed TLS repository.
6The results of the comparison is formatted as csv and stored at a
7configurable location.
8Note: must be run from Mbed TLS root.
9"""
10
11# Copyright The Mbed TLS Contributors
Dave Rodgman16799db2023-11-02 19:47:20 +000012# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Xiaofei Baibca03e52021-09-09 09:42:37 +000013
14import argparse
Yanray Wang21127f72023-07-19 12:09:45 +080015import logging
Xiaofei Baibca03e52021-09-09 09:42:37 +000016import os
Yanray Wang16ebc572023-05-30 18:10:20 +080017import re
Yanray Wang5605c6f2023-07-21 16:09:00 +080018import shutil
Xiaofei Baibca03e52021-09-09 09:42:37 +000019import subprocess
20import sys
Yanray Wang16ebc572023-05-30 18:10:20 +080021import typing
Yanray Wang23bd5322023-05-24 11:03:59 +080022from enum import Enum
Xiaofei Baibca03e52021-09-09 09:42:37 +000023
David Horstmanncd84bb22024-05-03 14:36:12 +010024from mbedtls_framework import build_tree
25from mbedtls_framework import logging_util
26from mbedtls_framework import typing_util
Gilles Peskined9071e72022-09-18 21:17:09 +020027
Yanray Wang23bd5322023-05-24 11:03:59 +080028class SupportedArch(Enum):
29 """Supported architecture for code size measurement."""
30 AARCH64 = 'aarch64'
31 AARCH32 = 'aarch32'
Yanray Wangaba71582023-05-29 16:45:56 +080032 ARMV8_M = 'armv8-m'
Yanray Wang23bd5322023-05-24 11:03:59 +080033 X86_64 = 'x86_64'
34 X86 = 'x86'
35
Yanray Wang955671b2023-07-21 12:08:27 +080036
Yanray Wang6a862582023-05-24 12:24:38 +080037class SupportedConfig(Enum):
38 """Supported configuration for code size measurement."""
39 DEFAULT = 'default'
40 TFM_MEDIUM = 'tfm-medium'
41
Yanray Wang955671b2023-07-21 12:08:27 +080042
Yanray Wang16ebc572023-05-30 18:10:20 +080043# Static library
44MBEDTLS_STATIC_LIB = {
45 'CRYPTO': 'library/libmbedcrypto.a',
46 'X509': 'library/libmbedx509.a',
47 'TLS': 'library/libmbedtls.a',
48}
49
Yanray Wang955671b2023-07-21 12:08:27 +080050class CodeSizeDistinctInfo: # pylint: disable=too-few-public-methods
51 """Data structure to store possibly distinct information for code size
52 comparison."""
53 def __init__( #pylint: disable=too-many-arguments
54 self,
55 version: str,
56 git_rev: str,
57 arch: str,
58 config: str,
Yanray Wang5605c6f2023-07-21 16:09:00 +080059 compiler: str,
60 opt_level: str,
Yanray Wang955671b2023-07-21 12:08:27 +080061 ) -> None:
62 """
63 :param: version: which version to compare with for code size.
64 :param: git_rev: Git revision to calculate code size.
65 :param: arch: architecture to measure code size on.
66 :param: config: Configuration type to calculate code size.
67 (See SupportedConfig)
Yanray Wang5605c6f2023-07-21 16:09:00 +080068 :param: compiler: compiler used to build library/*.o.
69 :param: opt_level: Options that control optimization. (E.g. -Os)
Yanray Wang955671b2023-07-21 12:08:27 +080070 """
71 self.version = version
72 self.git_rev = git_rev
73 self.arch = arch
74 self.config = config
Yanray Wang5605c6f2023-07-21 16:09:00 +080075 self.compiler = compiler
76 self.opt_level = opt_level
77 # Note: Variables below are not initialized by class instantiation.
78 self.pre_make_cmd = [] #type: typing.List[str]
79 self.make_cmd = ''
Yanray Wang955671b2023-07-21 12:08:27 +080080
Yanray Wanga6cf6922023-07-24 15:20:42 +080081 def get_info_indication(self):
82 """Return a unique string to indicate Code Size Distinct Information."""
Yanray Wang6ef50492023-07-26 14:59:37 +080083 return '{git_rev}-{arch}-{config}-{compiler}'.format(**self.__dict__)
Yanray Wanga6cf6922023-07-24 15:20:42 +080084
Yanray Wang955671b2023-07-21 12:08:27 +080085
86class CodeSizeCommonInfo: # pylint: disable=too-few-public-methods
87 """Data structure to store common information for code size comparison."""
88 def __init__(
89 self,
90 host_arch: str,
91 measure_cmd: str,
92 ) -> None:
93 """
94 :param host_arch: host architecture.
95 :param measure_cmd: command to measure code size for library/*.o.
96 """
97 self.host_arch = host_arch
98 self.measure_cmd = measure_cmd
99
Yanray Wanga6cf6922023-07-24 15:20:42 +0800100 def get_info_indication(self):
101 """Return a unique string to indicate Code Size Common Information."""
Yanray Wange4a36362023-07-25 10:37:11 +0800102 return '{measure_tool}'\
103 .format(measure_tool=self.measure_cmd.strip().split(' ')[0])
Yanray Wang955671b2023-07-21 12:08:27 +0800104
105class CodeSizeResultInfo: # pylint: disable=too-few-public-methods
106 """Data structure to store result options for code size comparison."""
Yanray Wangee07afa2023-07-28 16:34:05 +0800107 def __init__( #pylint: disable=too-many-arguments
Yanray Wang955671b2023-07-21 12:08:27 +0800108 self,
109 record_dir: str,
110 comp_dir: str,
111 with_markdown=False,
112 stdout=False,
Yanray Wangee07afa2023-07-28 16:34:05 +0800113 show_all=False,
Yanray Wang955671b2023-07-21 12:08:27 +0800114 ) -> None:
115 """
116 :param record_dir: directory to store code size record.
117 :param comp_dir: directory to store results of code size comparision.
118 :param with_markdown: write comparision result into a markdown table.
119 (Default: False)
120 :param stdout: direct comparison result into sys.stdout.
121 (Default False)
Yanray Wangee07afa2023-07-28 16:34:05 +0800122 :param show_all: show all objects in comparison result. (Default False)
Yanray Wang955671b2023-07-21 12:08:27 +0800123 """
124 self.record_dir = record_dir
125 self.comp_dir = comp_dir
126 self.with_markdown = with_markdown
127 self.stdout = stdout
Yanray Wangee07afa2023-07-28 16:34:05 +0800128 self.show_all = show_all
Yanray Wang955671b2023-07-21 12:08:27 +0800129
130
Yanray Wang23bd5322023-05-24 11:03:59 +0800131DETECT_ARCH_CMD = "cc -dM -E - < /dev/null"
132def detect_arch() -> str:
133 """Auto-detect host architecture."""
134 cc_output = subprocess.check_output(DETECT_ARCH_CMD, shell=True).decode()
Yanray Wang386c2f92023-07-20 15:32:15 +0800135 if '__aarch64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800136 return SupportedArch.AARCH64.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800137 if '__arm__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800138 return SupportedArch.AARCH32.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800139 if '__x86_64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800140 return SupportedArch.X86_64.value
Yanray Wangca9a3cb2023-07-26 17:16:29 +0800141 if '__i386__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800142 return SupportedArch.X86.value
143 else:
144 print("Unknown host architecture, cannot auto-detect arch.")
145 sys.exit(1)
Gilles Peskined9071e72022-09-18 21:17:09 +0200146
Yanray Wang28648232023-09-06 11:50:45 +0800147TFM_MEDIUM_CONFIG_H = 'configs/ext/tfm_mbedcrypto_config_profile_medium.h'
148TFM_MEDIUM_CRYPTO_CONFIG_H = 'configs/ext/crypto_config_profile_medium.h'
Yanray Wang5605c6f2023-07-21 16:09:00 +0800149
150CONFIG_H = 'include/mbedtls/mbedtls_config.h'
151CRYPTO_CONFIG_H = 'include/psa/crypto_config.h'
152BACKUP_SUFFIX = '.code_size.bak'
153
Yanray Wang923f9432023-07-17 12:43:00 +0800154class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods
Yanray Wang6a862582023-05-24 12:24:38 +0800155 """Gather information used to measure code size.
156
157 It collects information about architecture, configuration in order to
158 infer build command for code size measurement.
159 """
160
Yanray Wangc18cd892023-05-31 11:08:04 +0800161 SupportedArchConfig = [
Yanray Wang386c2f92023-07-20 15:32:15 +0800162 '-a ' + SupportedArch.AARCH64.value + ' -c ' + SupportedConfig.DEFAULT.value,
163 '-a ' + SupportedArch.AARCH32.value + ' -c ' + SupportedConfig.DEFAULT.value,
164 '-a ' + SupportedArch.X86_64.value + ' -c ' + SupportedConfig.DEFAULT.value,
165 '-a ' + SupportedArch.X86.value + ' -c ' + SupportedConfig.DEFAULT.value,
166 '-a ' + SupportedArch.ARMV8_M.value + ' -c ' + SupportedConfig.TFM_MEDIUM.value,
Yanray Wangc18cd892023-05-31 11:08:04 +0800167 ]
168
Yanray Wang802af162023-07-17 14:04:30 +0800169 def __init__(
170 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800171 size_dist_info: CodeSizeDistinctInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800172 host_arch: str,
173 logger: logging.Logger,
Yanray Wang802af162023-07-17 14:04:30 +0800174 ) -> None:
Yanray Wang6a862582023-05-24 12:24:38 +0800175 """
Yanray Wang955671b2023-07-21 12:08:27 +0800176 :param size_dist_info:
177 CodeSizeDistinctInfo containing info for code size measurement.
178 - size_dist_info.arch: architecture to measure code size on.
179 - size_dist_info.config: configuration type to measure
180 code size with.
Yanray Wang5605c6f2023-07-21 16:09:00 +0800181 - size_dist_info.compiler: compiler used to build library/*.o.
182 - size_dist_info.opt_level: Options that control optimization.
183 (E.g. -Os)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800184 :param host_arch: host architecture.
185 :param logger: logging module
Yanray Wang6a862582023-05-24 12:24:38 +0800186 """
Yanray Wang5605c6f2023-07-21 16:09:00 +0800187 self.arch = size_dist_info.arch
188 self.config = size_dist_info.config
189 self.compiler = size_dist_info.compiler
190 self.opt_level = size_dist_info.opt_level
191
192 self.make_cmd = ['make', '-j', 'lib']
193
Yanray Wang802af162023-07-17 14:04:30 +0800194 self.host_arch = host_arch
Yanray Wang21127f72023-07-19 12:09:45 +0800195 self.logger = logger
Yanray Wang6a862582023-05-24 12:24:38 +0800196
Yanray Wang5605c6f2023-07-21 16:09:00 +0800197 def check_correctness(self) -> bool:
198 """Check whether we are using proper / supported combination
199 of information to build library/*.o."""
Yanray Wang6a862582023-05-24 12:24:38 +0800200
Yanray Wang5605c6f2023-07-21 16:09:00 +0800201 # default config
202 if self.config == SupportedConfig.DEFAULT.value and \
203 self.arch == self.host_arch:
204 return True
205 # TF-M
206 elif self.arch == SupportedArch.ARMV8_M.value and \
207 self.config == SupportedConfig.TFM_MEDIUM.value:
208 return True
209
210 return False
211
212 def infer_pre_make_command(self) -> typing.List[str]:
213 """Infer command to set up proper configuration before running make."""
214 pre_make_cmd = [] #type: typing.List[str]
215 if self.config == SupportedConfig.TFM_MEDIUM.value:
Yanray Wanga279ca92023-07-26 15:01:10 +0800216 pre_make_cmd.append('cp {src} {dest}'
Yanray Wange4a36362023-07-25 10:37:11 +0800217 .format(src=TFM_MEDIUM_CONFIG_H, dest=CONFIG_H))
Yanray Wanga279ca92023-07-26 15:01:10 +0800218 pre_make_cmd.append('cp {src} {dest}'
Yanray Wange4a36362023-07-25 10:37:11 +0800219 .format(src=TFM_MEDIUM_CRYPTO_CONFIG_H,
220 dest=CRYPTO_CONFIG_H))
Yanray Wang5605c6f2023-07-21 16:09:00 +0800221
222 return pre_make_cmd
223
224 def infer_make_cflags(self) -> str:
225 """Infer CFLAGS by instance attributes in CodeSizeDistinctInfo."""
226 cflags = [] #type: typing.List[str]
227
228 # set optimization level
229 cflags.append(self.opt_level)
230 # set compiler by config
231 if self.config == SupportedConfig.TFM_MEDIUM.value:
232 self.compiler = 'armclang'
233 cflags.append('-mcpu=cortex-m33')
234 # set target
235 if self.compiler == 'armclang':
236 cflags.append('--target=arm-arm-none-eabi')
237
238 return ' '.join(cflags)
239
240 def infer_make_command(self) -> str:
241 """Infer make command by CFLAGS and CC."""
242
243 if self.check_correctness():
244 # set CFLAGS=
245 self.make_cmd.append('CFLAGS=\'{}\''.format(self.infer_make_cflags()))
246 # set CC=
247 self.make_cmd.append('CC={}'.format(self.compiler))
248 return ' '.join(self.make_cmd)
Yanray Wang6a862582023-05-24 12:24:38 +0800249 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800250 self.logger.error("Unsupported combination of architecture: {} " \
251 "and configuration: {}.\n"
Yanray Wang5605c6f2023-07-21 16:09:00 +0800252 .format(self.arch,
253 self.config))
Yanray Wang2ba9df22023-07-26 10:11:31 +0800254 self.logger.error("Please use supported combination of " \
Yanray Wang21127f72023-07-19 12:09:45 +0800255 "architecture and configuration:")
Yanray Wang923f9432023-07-17 12:43:00 +0800256 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang2ba9df22023-07-26 10:11:31 +0800257 self.logger.error(comb)
258 self.logger.error("")
259 self.logger.error("For your system, please use:")
Yanray Wang923f9432023-07-17 12:43:00 +0800260 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang802af162023-07-17 14:04:30 +0800261 if "default" in comb and self.host_arch not in comb:
Yanray Wang21f17442023-06-01 11:29:06 +0800262 continue
Yanray Wang2ba9df22023-07-26 10:11:31 +0800263 self.logger.error(comb)
Yanray Wang6a862582023-05-24 12:24:38 +0800264 sys.exit(1)
265
266
Yanray Wange0e27602023-07-14 17:37:45 +0800267class CodeSizeCalculator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800268 """ A calculator to calculate code size of library/*.o based on
Yanray Wange0e27602023-07-14 17:37:45 +0800269 Git revision and code size measurement tool.
270 """
271
Yanray Wang5605c6f2023-07-21 16:09:00 +0800272 def __init__( #pylint: disable=too-many-arguments
Yanray Wange0e27602023-07-14 17:37:45 +0800273 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800274 git_rev: str,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800275 pre_make_cmd: typing.List[str],
Yanray Wange0e27602023-07-14 17:37:45 +0800276 make_cmd: str,
Yanray Wang21127f72023-07-19 12:09:45 +0800277 measure_cmd: str,
278 logger: logging.Logger,
Yanray Wange0e27602023-07-14 17:37:45 +0800279 ) -> None:
280 """
Yanray Wang955671b2023-07-21 12:08:27 +0800281 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5605c6f2023-07-21 16:09:00 +0800282 :param pre_make_cmd: command to set up proper config before running make.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800283 :param make_cmd: command to build library/*.o.
284 :param measure_cmd: command to measure code size for library/*.o.
285 :param logger: logging module
Yanray Wange0e27602023-07-14 17:37:45 +0800286 """
287 self.repo_path = "."
288 self.git_command = "git"
289 self.make_clean = 'make clean'
290
Yanray Wang955671b2023-07-21 12:08:27 +0800291 self.git_rev = git_rev
Yanray Wang5605c6f2023-07-21 16:09:00 +0800292 self.pre_make_cmd = pre_make_cmd
Yanray Wange0e27602023-07-14 17:37:45 +0800293 self.make_cmd = make_cmd
Yanray Wang802af162023-07-17 14:04:30 +0800294 self.measure_cmd = measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800295 self.logger = logger
Yanray Wange0e27602023-07-14 17:37:45 +0800296
297 @staticmethod
Yanray Wang955671b2023-07-21 12:08:27 +0800298 def validate_git_revision(git_rev: str) -> str:
Yanray Wange0e27602023-07-14 17:37:45 +0800299 result = subprocess.check_output(["git", "rev-parse", "--verify",
Yanray Wang955671b2023-07-21 12:08:27 +0800300 git_rev + "^{commit}"],
301 shell=False, universal_newlines=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800302 return result[:7]
Yanray Wange0e27602023-07-14 17:37:45 +0800303
Yanray Wang21127f72023-07-19 12:09:45 +0800304 def _create_git_worktree(self) -> str:
Yanray Wang955671b2023-07-21 12:08:27 +0800305 """Create a separate worktree for Git revision.
306 If Git revision is current, use current worktree instead."""
Yanray Wange0e27602023-07-14 17:37:45 +0800307
Yanray Wang5605c6f2023-07-21 16:09:00 +0800308 if self.git_rev == 'current':
Yanray Wang21127f72023-07-19 12:09:45 +0800309 self.logger.debug("Using current work directory.")
Yanray Wange0e27602023-07-14 17:37:45 +0800310 git_worktree_path = self.repo_path
311 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800312 self.logger.debug("Creating git worktree for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800313 .format(self.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800314 git_worktree_path = os.path.join(self.repo_path,
Yanray Wang955671b2023-07-21 12:08:27 +0800315 "temp-" + self.git_rev)
Yanray Wange0e27602023-07-14 17:37:45 +0800316 subprocess.check_output(
317 [self.git_command, "worktree", "add", "--detach",
Yanray Wang955671b2023-07-21 12:08:27 +0800318 git_worktree_path, self.git_rev], cwd=self.repo_path,
Yanray Wange0e27602023-07-14 17:37:45 +0800319 stderr=subprocess.STDOUT
320 )
321
322 return git_worktree_path
323
Yanray Wang5605c6f2023-07-21 16:09:00 +0800324 @staticmethod
325 def backup_config_files(restore: bool) -> None:
326 """Backup / Restore config files."""
327 if restore:
328 shutil.move(CONFIG_H + BACKUP_SUFFIX, CONFIG_H)
329 shutil.move(CRYPTO_CONFIG_H + BACKUP_SUFFIX, CRYPTO_CONFIG_H)
330 else:
331 shutil.copy(CONFIG_H, CONFIG_H + BACKUP_SUFFIX)
332 shutil.copy(CRYPTO_CONFIG_H, CRYPTO_CONFIG_H + BACKUP_SUFFIX)
333
Yanray Wange0e27602023-07-14 17:37:45 +0800334 def _build_libraries(self, git_worktree_path: str) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800335 """Build library/*.o in the specified worktree."""
Yanray Wange0e27602023-07-14 17:37:45 +0800336
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800337 self.logger.debug("Building library/*.o for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800338 .format(self.git_rev))
Yanray Wange0e27602023-07-14 17:37:45 +0800339 my_environment = os.environ.copy()
340 try:
Yanray Wang5605c6f2023-07-21 16:09:00 +0800341 if self.git_rev == 'current':
342 self.backup_config_files(restore=False)
343 for pre_cmd in self.pre_make_cmd:
344 subprocess.check_output(
345 pre_cmd, env=my_environment, shell=True,
346 cwd=git_worktree_path, stderr=subprocess.STDOUT,
347 universal_newlines=True
348 )
Yanray Wange0e27602023-07-14 17:37:45 +0800349 subprocess.check_output(
350 self.make_clean, env=my_environment, shell=True,
351 cwd=git_worktree_path, stderr=subprocess.STDOUT,
Yanray Wang386c2f92023-07-20 15:32:15 +0800352 universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800353 )
354 subprocess.check_output(
355 self.make_cmd, env=my_environment, shell=True,
356 cwd=git_worktree_path, stderr=subprocess.STDOUT,
Yanray Wang386c2f92023-07-20 15:32:15 +0800357 universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800358 )
Yanray Wang5605c6f2023-07-21 16:09:00 +0800359 if self.git_rev == 'current':
360 self.backup_config_files(restore=True)
Yanray Wange0e27602023-07-14 17:37:45 +0800361 except subprocess.CalledProcessError as e:
362 self._handle_called_process_error(e, git_worktree_path)
363
Yanray Wang386c2f92023-07-20 15:32:15 +0800364 def _gen_raw_code_size(self, git_worktree_path: str) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800365 """Measure code size by a tool and return in UTF-8 encoding."""
Yanray Wang21127f72023-07-19 12:09:45 +0800366
367 self.logger.debug("Measuring code size for {} by `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800368 .format(self.git_rev,
Yanray Wang21127f72023-07-19 12:09:45 +0800369 self.measure_cmd.strip().split(' ')[0]))
Yanray Wange0e27602023-07-14 17:37:45 +0800370
371 res = {}
372 for mod, st_lib in MBEDTLS_STATIC_LIB.items():
373 try:
374 result = subprocess.check_output(
Yanray Wang802af162023-07-17 14:04:30 +0800375 [self.measure_cmd + ' ' + st_lib], cwd=git_worktree_path,
376 shell=True, universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800377 )
378 res[mod] = result
379 except subprocess.CalledProcessError as e:
380 self._handle_called_process_error(e, git_worktree_path)
381
382 return res
383
384 def _remove_worktree(self, git_worktree_path: str) -> None:
385 """Remove temporary worktree."""
386 if git_worktree_path != self.repo_path:
Yanray Wang21127f72023-07-19 12:09:45 +0800387 self.logger.debug("Removing temporary worktree {}."
388 .format(git_worktree_path))
Yanray Wange0e27602023-07-14 17:37:45 +0800389 subprocess.check_output(
390 [self.git_command, "worktree", "remove", "--force",
391 git_worktree_path], cwd=self.repo_path,
392 stderr=subprocess.STDOUT
393 )
394
395 def _handle_called_process_error(self, e: subprocess.CalledProcessError,
396 git_worktree_path: str) -> None:
397 """Handle a CalledProcessError and quit the program gracefully.
398 Remove any extra worktrees so that the script may be called again."""
399
400 # Tell the user what went wrong
Yanray Wang21127f72023-07-19 12:09:45 +0800401 self.logger.error(e, exc_info=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800402 self.logger.error("Process output:\n {}".format(e.output))
Yanray Wange0e27602023-07-14 17:37:45 +0800403
404 # Quit gracefully by removing the existing worktree
405 self._remove_worktree(git_worktree_path)
406 sys.exit(-1)
407
Yanray Wang386c2f92023-07-20 15:32:15 +0800408 def cal_libraries_code_size(self) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800409 """Do a complete round to calculate code size of library/*.o
410 by measurement tool.
411
412 :return A dictionary of measured code size
413 - typing.Dict[mod: str]
414 """
Yanray Wange0e27602023-07-14 17:37:45 +0800415
Yanray Wang21127f72023-07-19 12:09:45 +0800416 git_worktree_path = self._create_git_worktree()
Yanray Wang6ae94a02023-07-26 17:12:57 +0800417 try:
418 self._build_libraries(git_worktree_path)
419 res = self._gen_raw_code_size(git_worktree_path)
420 finally:
421 self._remove_worktree(git_worktree_path)
Yanray Wange0e27602023-07-14 17:37:45 +0800422
423 return res
424
425
Yanray Wang15c43f32023-07-17 11:17:12 +0800426class CodeSizeGenerator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800427 """ A generator based on size measurement tool for library/*.o.
Yanray Wang15c43f32023-07-17 11:17:12 +0800428
429 This is an abstract class. To use it, derive a class that implements
Yanray Wang95059002023-07-24 12:29:22 +0800430 write_record and write_comparison methods, then call both of them with
431 proper arguments.
Yanray Wang15c43f32023-07-17 11:17:12 +0800432 """
Yanray Wang21127f72023-07-19 12:09:45 +0800433 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800434 """
435 :param logger: logging module
436 """
Yanray Wang21127f72023-07-19 12:09:45 +0800437 self.logger = logger
438
Yanray Wang95059002023-07-24 12:29:22 +0800439 def write_record(
Yanray Wang15c43f32023-07-17 11:17:12 +0800440 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800441 git_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800442 code_size_text: typing.Dict[str, str],
443 output: typing_util.Writable
Yanray Wang15c43f32023-07-17 11:17:12 +0800444 ) -> None:
445 """Write size record into a file.
446
Yanray Wang955671b2023-07-21 12:08:27 +0800447 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800448 :param code_size_text:
449 string output (utf-8) from measurement tool of code size.
450 - typing.Dict[mod: str]
Yanray Wang95059002023-07-24 12:29:22 +0800451 :param output: output stream which the code size record is written to.
452 (Note: Normally write code size record into File)
Yanray Wang15c43f32023-07-17 11:17:12 +0800453 """
454 raise NotImplementedError
455
Yanray Wangee07afa2023-07-28 16:34:05 +0800456 def write_comparison( #pylint: disable=too-many-arguments
Yanray Wang15c43f32023-07-17 11:17:12 +0800457 self,
458 old_rev: str,
459 new_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800460 output: typing_util.Writable,
Yanray Wangee07afa2023-07-28 16:34:05 +0800461 with_markdown=False,
462 show_all=False
Yanray Wang15c43f32023-07-17 11:17:12 +0800463 ) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800464 """Write a comparision result into a stream between two Git revisions.
Yanray Wang15c43f32023-07-17 11:17:12 +0800465
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800466 :param old_rev: old Git revision to compared with.
467 :param new_rev: new Git revision to compared with.
Yanray Wang95059002023-07-24 12:29:22 +0800468 :param output: output stream which the code size record is written to.
469 (File / sys.stdout)
470 :param with_markdown: write comparision result in a markdown table.
471 (Default: False)
Yanray Wangee07afa2023-07-28 16:34:05 +0800472 :param show_all: show all objects in comparison result. (Default False)
Yanray Wang15c43f32023-07-17 11:17:12 +0800473 """
474 raise NotImplementedError
475
476
477class CodeSizeGeneratorWithSize(CodeSizeGenerator):
Yanray Wang16ebc572023-05-30 18:10:20 +0800478 """Code Size Base Class for size record saving and writing."""
479
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800480 class SizeEntry: # pylint: disable=too-few-public-methods
481 """Data Structure to only store information of code size."""
Yanray Wangdcf360d2023-07-27 15:28:20 +0800482 def __init__(self, text: int, data: int, bss: int, dec: int):
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800483 self.text = text
484 self.data = data
485 self.bss = bss
486 self.total = dec # total <=> dec
487
Yanray Wang21127f72023-07-19 12:09:45 +0800488 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800489 """ Variable code_size is used to store size info for any Git revisions.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800490 :param code_size:
491 Data Format as following:
Yanray Wangdcf360d2023-07-27 15:28:20 +0800492 code_size = {
493 git_rev: {
494 module: {
495 file_name: SizeEntry,
496 ...
497 },
498 ...
499 },
500 ...
501 }
Yanray Wang16ebc572023-05-30 18:10:20 +0800502 """
Yanray Wang21127f72023-07-19 12:09:45 +0800503 super().__init__(logger)
Yanray Wang16ebc572023-05-30 18:10:20 +0800504 self.code_size = {} #type: typing.Dict[str, typing.Dict]
Yanray Wangdcf360d2023-07-27 15:28:20 +0800505 self.mod_total_suffix = '-' + 'TOTALS'
Yanray Wang16ebc572023-05-30 18:10:20 +0800506
Yanray Wang955671b2023-07-21 12:08:27 +0800507 def _set_size_record(self, git_rev: str, mod: str, size_text: str) -> None:
508 """Store size information for target Git revision and high-level module.
Yanray Wang16ebc572023-05-30 18:10:20 +0800509
510 size_text Format: text data bss dec hex filename
511 """
512 size_record = {}
513 for line in size_text.splitlines()[1:]:
514 data = line.split()
Yanray Wangdcf360d2023-07-27 15:28:20 +0800515 if re.match(r'\s*\(TOTALS\)', data[5]):
516 data[5] = mod + self.mod_total_suffix
Yanray Wang9b174e92023-07-17 17:59:53 +0800517 # file_name: SizeEntry(text, data, bss, dec)
518 size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangdcf360d2023-07-27 15:28:20 +0800519 int(data[0]), int(data[1]), int(data[2]), int(data[3]))
Yanray Wang6ef50492023-07-26 14:59:37 +0800520 self.code_size.setdefault(git_rev, {}).update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800521
Yanray Wang955671b2023-07-21 12:08:27 +0800522 def read_size_record(self, git_rev: str, fname: str) -> None:
Yanray Wang16ebc572023-05-30 18:10:20 +0800523 """Read size information from csv file and write it into code_size.
524
525 fname Format: filename text data bss dec
526 """
527 mod = ""
528 size_record = {}
529 with open(fname, 'r') as csv_file:
530 for line in csv_file:
531 data = line.strip().split()
532 # check if we find the beginning of a module
533 if data and data[0] in MBEDTLS_STATIC_LIB:
534 mod = data[0]
535 continue
536
537 if mod:
Yanray Wang9b174e92023-07-17 17:59:53 +0800538 # file_name: SizeEntry(text, data, bss, dec)
539 size_record[data[0]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangdcf360d2023-07-27 15:28:20 +0800540 int(data[1]), int(data[2]), int(data[3]), int(data[4]))
Yanray Wang16ebc572023-05-30 18:10:20 +0800541
542 # check if we hit record for the end of a module
Yanray Wangdcf360d2023-07-27 15:28:20 +0800543 m = re.match(r'\w+' + self.mod_total_suffix, line)
Yanray Wang16ebc572023-05-30 18:10:20 +0800544 if m:
Yanray Wang955671b2023-07-21 12:08:27 +0800545 if git_rev in self.code_size:
546 self.code_size[git_rev].update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800547 else:
Yanray Wang955671b2023-07-21 12:08:27 +0800548 self.code_size[git_rev] = {mod: size_record}
Yanray Wang16ebc572023-05-30 18:10:20 +0800549 mod = ""
550 size_record = {}
551
Yanray Wang95059002023-07-24 12:29:22 +0800552 def write_record(
Yanray Wang16ebc572023-05-30 18:10:20 +0800553 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800554 git_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800555 code_size_text: typing.Dict[str, str],
Yanray Wang16ebc572023-05-30 18:10:20 +0800556 output: typing_util.Writable
557 ) -> None:
558 """Write size information to a file.
559
Yanray Wangdcf360d2023-07-27 15:28:20 +0800560 Writing Format: filename text data bss total(dec)
Yanray Wang16ebc572023-05-30 18:10:20 +0800561 """
Yanray Wang95059002023-07-24 12:29:22 +0800562 for mod, size_text in code_size_text.items():
563 self._set_size_record(git_rev, mod, size_text)
564
Yanray Wangb664cb72023-07-18 12:28:35 +0800565 format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n"
566 output.write(format_string.format("filename",
567 "text", "data", "bss", "total"))
Yanray Wang16ebc572023-05-30 18:10:20 +0800568
Yanray Wangdcf360d2023-07-27 15:28:20 +0800569 for mod, f_size in self.code_size[git_rev].items():
570 output.write("\n" + mod + "\n")
571 for fname, size_entry in f_size.items():
572 output.write(format_string
573 .format(fname,
574 size_entry.text, size_entry.data,
575 size_entry.bss, size_entry.total))
576
Yanray Wangee07afa2023-07-28 16:34:05 +0800577 def write_comparison( #pylint: disable=too-many-arguments
Yanray Wang16ebc572023-05-30 18:10:20 +0800578 self,
579 old_rev: str,
580 new_rev: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800581 output: typing_util.Writable,
Yanray Wangee07afa2023-07-28 16:34:05 +0800582 with_markdown=False,
583 show_all=False
Yanray Wang16ebc572023-05-30 18:10:20 +0800584 ) -> None:
Yanray Wangee07afa2023-07-28 16:34:05 +0800585 # pylint: disable=too-many-locals
Yanray Wang16ebc572023-05-30 18:10:20 +0800586 """Write comparison result into a file.
587
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800588 Writing Format:
589 Markdown Output:
590 filename new(text) new(data) change(text) change(data)
591 CSV Output:
592 filename new(text) new(data) old(text) old(data) change(text) change(data)
Yanray Wang16ebc572023-05-30 18:10:20 +0800593 """
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800594 header_line = ["filename", "new(text)", "old(text)", "change(text)",
595 "new(data)", "old(data)", "change(data)"]
Yanray Wangb664cb72023-07-18 12:28:35 +0800596 if with_markdown:
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800597 dash_line = [":----", "----:", "----:", "----:",
598 "----:", "----:", "----:"]
599 # | filename | new(text) | new(data) | change(text) | change(data) |
600 line_format = "| {0:<30} | {1:>9} | {4:>9} | {3:>12} | {6:>12} |\n"
Yanray Wangdcf360d2023-07-27 15:28:20 +0800601 bold_text = lambda x: '**' + str(x) + '**'
Yanray Wangb664cb72023-07-18 12:28:35 +0800602 else:
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800603 # filename new(text) new(data) old(text) old(data) change(text) change(data)
604 line_format = "{0:<30} {1:>9} {4:>9} {2:>10} {5:>10} {3:>12} {6:>12}\n"
Yanray Wangb664cb72023-07-18 12:28:35 +0800605
Yanray Wangdcf360d2023-07-27 15:28:20 +0800606 def cal_sect_change(
607 old_size: typing.Optional[CodeSizeGeneratorWithSize.SizeEntry],
608 new_size: typing.Optional[CodeSizeGeneratorWithSize.SizeEntry],
609 sect: str
610 ) -> typing.List:
611 """Inner helper function to calculate size change for a section.
Yanray Wangb664cb72023-07-18 12:28:35 +0800612
Yanray Wangdcf360d2023-07-27 15:28:20 +0800613 Convention for special cases:
614 - If the object has been removed in new Git revision,
615 the size is minus code size of old Git revision;
616 the size change is marked as `Removed`,
617 - If the object only exists in new Git revision,
618 the size is code size of new Git revision;
619 the size change is marked as `None`,
Yanray Wang9b174e92023-07-17 17:59:53 +0800620
Yanray Wangdcf360d2023-07-27 15:28:20 +0800621 :param: old_size: code size for objects in old Git revision.
622 :param: new_size: code size for objects in new Git revision.
623 :param: sect: section to calculate from `size` tool. This could be
624 any instance variable in SizeEntry.
625 :return: List of [section size of objects for new Git revision,
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800626 section size of objects for old Git revision,
Yanray Wangdcf360d2023-07-27 15:28:20 +0800627 section size change of objects between two Git revisions]
628 """
629 if old_size and new_size:
630 new_attr = new_size.__dict__[sect]
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800631 old_attr = old_size.__dict__[sect]
632 delta = new_attr - old_attr
Yanray Wang0de11832023-08-14 11:54:47 +0800633 change_attr = '{0:{1}}'.format(delta, '+' if delta else '')
Yanray Wangdcf360d2023-07-27 15:28:20 +0800634 elif old_size:
Yanray Wangbc775c42023-08-16 15:59:55 +0800635 new_attr = 'Removed'
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800636 old_attr = old_size.__dict__[sect]
Yanray Wangbc775c42023-08-16 15:59:55 +0800637 delta = - old_attr
638 change_attr = '{0:{1}}'.format(delta, '+' if delta else '')
Yanray Wangdcf360d2023-07-27 15:28:20 +0800639 elif new_size:
640 new_attr = new_size.__dict__[sect]
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800641 old_attr = 'NotCreated'
Yanray Wangbc775c42023-08-16 15:59:55 +0800642 delta = new_attr
643 change_attr = '{0:{1}}'.format(delta, '+' if delta else '')
Yanray Wang9b174e92023-07-17 17:59:53 +0800644 else:
Yanray Wangdcf360d2023-07-27 15:28:20 +0800645 # Should never happen
646 new_attr = 'Error'
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800647 old_attr = 'Error'
Yanray Wangdcf360d2023-07-27 15:28:20 +0800648 change_attr = 'Error'
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800649 return [new_attr, old_attr, change_attr]
Yanray Wangdcf360d2023-07-27 15:28:20 +0800650
651 # sort dictionary by key
652 sort_by_k = lambda item: item[0].lower()
653 def get_results(
654 f_rev_size:
655 typing.Dict[str,
656 typing.Dict[str,
657 CodeSizeGeneratorWithSize.SizeEntry]]
658 ) -> typing.List:
659 """Return List of results in the format of:
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800660 [filename, new(text), old(text), change(text),
661 new(data), old(data), change(data)]
Yanray Wangdcf360d2023-07-27 15:28:20 +0800662 """
663 res = []
664 for fname, revs_size in sorted(f_rev_size.items(), key=sort_by_k):
665 old_size = revs_size.get(old_rev)
666 new_size = revs_size.get(new_rev)
667
668 text_sect = cal_sect_change(old_size, new_size, 'text')
669 data_sect = cal_sect_change(old_size, new_size, 'data')
670 # skip the files that haven't changed in code size
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800671 if not show_all and text_sect[-1] == '0' and data_sect[-1] == '0':
Yanray Wangdcf360d2023-07-27 15:28:20 +0800672 continue
673
674 res.append([fname, *text_sect, *data_sect])
675 return res
676
677 # write header
678 output.write(line_format.format(*header_line))
679 if with_markdown:
680 output.write(line_format.format(*dash_line))
681 for mod in MBEDTLS_STATIC_LIB:
682 # convert self.code_size to:
683 # {
684 # file_name: {
685 # old_rev: SizeEntry,
686 # new_rev: SizeEntry
687 # },
688 # ...
689 # }
690 f_rev_size = {} #type: typing.Dict[str, typing.Dict]
691 for fname, size_entry in self.code_size[old_rev][mod].items():
692 f_rev_size.setdefault(fname, {}).update({old_rev: size_entry})
693 for fname, size_entry in self.code_size[new_rev][mod].items():
694 f_rev_size.setdefault(fname, {}).update({new_rev: size_entry})
695
696 mod_total_sz = f_rev_size.pop(mod + self.mod_total_suffix)
697 res = get_results(f_rev_size)
698 total_clm = get_results({mod + self.mod_total_suffix: mod_total_sz})
699 if with_markdown:
700 # bold row of mod-TOTALS in markdown table
701 total_clm = [[bold_text(j) for j in i] for i in total_clm]
702 res += total_clm
703
704 # write comparison result
705 for line in res:
706 output.write(line_format.format(*line))
Yanray Wang16ebc572023-05-30 18:10:20 +0800707
708
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800709class CodeSizeComparison:
Xiaofei Bai2400b502021-10-21 12:22:58 +0000710 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000711
Yanray Wang955671b2023-07-21 12:08:27 +0800712 def __init__( #pylint: disable=too-many-arguments
Yanray Wang72b105f2023-05-31 15:20:39 +0800713 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800714 old_size_dist_info: CodeSizeDistinctInfo,
715 new_size_dist_info: CodeSizeDistinctInfo,
716 size_common_info: CodeSizeCommonInfo,
717 result_options: CodeSizeResultInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800718 logger: logging.Logger,
Yanray Wang72b105f2023-05-31 15:20:39 +0800719 ) -> None:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000720 """
Yanray Wang955671b2023-07-21 12:08:27 +0800721 :param old_size_dist_info: CodeSizeDistinctInfo containing old distinct
722 info to compare code size with.
723 :param new_size_dist_info: CodeSizeDistinctInfo containing new distinct
724 info to take as comparision base.
725 :param size_common_info: CodeSizeCommonInfo containing common info for
726 both old and new size distinct info and
727 measurement tool.
728 :param result_options: CodeSizeResultInfo containing results options for
729 code size record and comparision.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800730 :param logger: logging module
Xiaofei Baibca03e52021-09-09 09:42:37 +0000731 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000732
Yanray Wang21127f72023-07-19 12:09:45 +0800733 self.logger = logger
734
Yanray Wang955671b2023-07-21 12:08:27 +0800735 self.old_size_dist_info = old_size_dist_info
736 self.new_size_dist_info = new_size_dist_info
737 self.size_common_info = size_common_info
Yanray Wang5605c6f2023-07-21 16:09:00 +0800738 # infer pre make command
739 self.old_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
740 self.old_size_dist_info, self.size_common_info.host_arch,
741 self.logger).infer_pre_make_command()
742 self.new_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
743 self.new_size_dist_info, self.size_common_info.host_arch,
744 self.logger).infer_pre_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800745 # infer make command
Yanray Wang955671b2023-07-21 12:08:27 +0800746 self.old_size_dist_info.make_cmd = CodeSizeBuildInfo(
747 self.old_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800748 self.logger).infer_make_command()
Yanray Wang955671b2023-07-21 12:08:27 +0800749 self.new_size_dist_info.make_cmd = CodeSizeBuildInfo(
750 self.new_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800751 self.logger).infer_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800752 # initialize size parser with corresponding measurement tool
Yanray Wang21127f72023-07-19 12:09:45 +0800753 self.code_size_generator = self.__generate_size_parser()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000754
Yanray Wang955671b2023-07-21 12:08:27 +0800755 self.result_options = result_options
756 self.csv_dir = os.path.abspath(self.result_options.record_dir)
757 os.makedirs(self.csv_dir, exist_ok=True)
758 self.comp_dir = os.path.abspath(self.result_options.comp_dir)
759 os.makedirs(self.comp_dir, exist_ok=True)
760
Yanray Wang21127f72023-07-19 12:09:45 +0800761 def __generate_size_parser(self):
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800762 """Generate a parser for the corresponding measurement tool."""
Yanray Wang955671b2023-07-21 12:08:27 +0800763 if re.match(r'size', self.size_common_info.measure_cmd.strip()):
Yanray Wang21127f72023-07-19 12:09:45 +0800764 return CodeSizeGeneratorWithSize(self.logger)
Yanray Wang802af162023-07-17 14:04:30 +0800765 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800766 self.logger.error("Unsupported measurement tool: `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800767 .format(self.size_common_info.measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800768 .strip().split(' ')[0]))
Yanray Wang802af162023-07-17 14:04:30 +0800769 sys.exit(1)
770
Yanray Wang386c2f92023-07-20 15:32:15 +0800771 def cal_code_size(
772 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800773 size_dist_info: CodeSizeDistinctInfo
Yanray Wang386c2f92023-07-20 15:32:15 +0800774 ) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800775 """Calculate code size of library/*.o in a UTF-8 encoding"""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000776
Yanray Wang955671b2023-07-21 12:08:27 +0800777 return CodeSizeCalculator(size_dist_info.git_rev,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800778 size_dist_info.pre_make_cmd,
Yanray Wang955671b2023-07-21 12:08:27 +0800779 size_dist_info.make_cmd,
780 self.size_common_info.measure_cmd,
Yanray Wang21127f72023-07-19 12:09:45 +0800781 self.logger).cal_libraries_code_size()
Yanray Wang8804db92023-05-30 18:18:18 +0800782
Yanray Wang955671b2023-07-21 12:08:27 +0800783 def gen_code_size_report(self, size_dist_info: CodeSizeDistinctInfo) -> None:
Yanray Wang5e9130a2023-07-17 11:55:54 +0800784 """Generate code size record and write it into a file."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000785
Yanray Wang21127f72023-07-19 12:09:45 +0800786 self.logger.info("Start to generate code size record for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800787 .format(size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800788 output_file = os.path.join(
789 self.csv_dir,
790 '{}-{}.csv'
791 .format(size_dist_info.get_info_indication(),
792 self.size_common_info.get_info_indication()))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000793 # Check if the corresponding record exists
Yanray Wang955671b2023-07-21 12:08:27 +0800794 if size_dist_info.git_rev != "current" and \
Yanray Wang21127f72023-07-19 12:09:45 +0800795 os.path.exists(output_file):
796 self.logger.debug("Code size csv file for {} already exists."
Yanray Wang955671b2023-07-21 12:08:27 +0800797 .format(size_dist_info.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800798 self.code_size_generator.read_size_record(
Yanray Wang955671b2023-07-21 12:08:27 +0800799 size_dist_info.git_rev, output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000800 else:
Yanray Wang95059002023-07-24 12:29:22 +0800801 # measure code size
802 code_size_text = self.cal_code_size(size_dist_info)
803
804 self.logger.debug("Generating code size csv for {}."
805 .format(size_dist_info.git_rev))
806 output = open(output_file, "w")
807 self.code_size_generator.write_record(
808 size_dist_info.git_rev, code_size_text, output)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000809
Yanray Wang386c2f92023-07-20 15:32:15 +0800810 def gen_code_size_comparison(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800811 """Generate results of code size changes between two Git revisions,
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800812 old and new.
813
Yanray Wang955671b2023-07-21 12:08:27 +0800814 - Measured code size result of these two Git revisions must be available.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800815 - The result is directed into either file / stdout depending on
Yanray Wang955671b2023-07-21 12:08:27 +0800816 the option, size_common_info.result_options.stdout. (Default: file)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800817 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000818
Yanray Wang21127f72023-07-19 12:09:45 +0800819 self.logger.info("Start to generate comparision result between "\
820 "{} and {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800821 .format(self.old_size_dist_info.git_rev,
822 self.new_size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800823 if self.result_options.stdout:
824 output = sys.stdout
825 else:
826 output_file = os.path.join(
827 self.comp_dir,
Yanray Wangb1673202023-07-28 13:47:19 +0800828 '{}-{}-{}.{}'
Yanray Wanga6cf6922023-07-24 15:20:42 +0800829 .format(self.old_size_dist_info.get_info_indication(),
830 self.new_size_dist_info.get_info_indication(),
Yanray Wangb1673202023-07-28 13:47:19 +0800831 self.size_common_info.get_info_indication(),
832 'md' if self.result_options.with_markdown else 'csv'))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800833 output = open(output_file, "w")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000834
Yanray Wang95059002023-07-24 12:29:22 +0800835 self.logger.debug("Generating comparison results between {} and {}."
836 .format(self.old_size_dist_info.git_rev,
837 self.new_size_dist_info.git_rev))
Yanray Wangea842e72023-07-26 10:34:39 +0800838 if self.result_options.with_markdown or self.result_options.stdout:
839 print("Measure code size between {} and {} by `{}`."
840 .format(self.old_size_dist_info.get_info_indication(),
841 self.new_size_dist_info.get_info_indication(),
842 self.size_common_info.get_info_indication()),
843 file=output)
Yanray Wang95059002023-07-24 12:29:22 +0800844 self.code_size_generator.write_comparison(
Yanray Wang955671b2023-07-21 12:08:27 +0800845 self.old_size_dist_info.git_rev,
846 self.new_size_dist_info.git_rev,
Yanray Wangee07afa2023-07-28 16:34:05 +0800847 output, self.result_options.with_markdown,
848 self.result_options.show_all)
Yanray Wang21127f72023-07-19 12:09:45 +0800849
Yanray Wang386c2f92023-07-20 15:32:15 +0800850 def get_comparision_results(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800851 """Compare size of library/*.o between self.old_size_dist_info and
852 self.old_size_dist_info and generate the result file."""
Gilles Peskined9071e72022-09-18 21:17:09 +0200853 build_tree.check_repo_path()
Yanray Wang955671b2023-07-21 12:08:27 +0800854 self.gen_code_size_report(self.old_size_dist_info)
855 self.gen_code_size_report(self.new_size_dist_info)
Yanray Wang386c2f92023-07-20 15:32:15 +0800856 self.gen_code_size_comparison()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000857
Xiaofei Bai2400b502021-10-21 12:22:58 +0000858def main():
Yanray Wang502c54f2023-05-31 11:41:36 +0800859 parser = argparse.ArgumentParser(description=(__doc__))
860 group_required = parser.add_argument_group(
861 'required arguments',
862 'required arguments to parse for running ' + os.path.basename(__file__))
863 group_required.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800864 '-o', '--old-rev', type=str, required=True,
Yanray Wang955671b2023-07-21 12:08:27 +0800865 help='old Git revision for comparison.')
Yanray Wang502c54f2023-05-31 11:41:36 +0800866
867 group_optional = parser.add_argument_group(
868 'optional arguments',
869 'optional arguments to parse for running ' + os.path.basename(__file__))
870 group_optional.add_argument(
Yanray Wang9e8b6712023-07-26 15:37:26 +0800871 '--record-dir', type=str, default='code_size_records',
Yanray Wang955671b2023-07-21 12:08:27 +0800872 help='directory where code size record is stored. '
873 '(Default: code_size_records)')
874 group_optional.add_argument(
Yanray Wang9e8b6712023-07-26 15:37:26 +0800875 '--comp-dir', type=str, default='comparison',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800876 help='directory where comparison result is stored. '
877 '(Default: comparison)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800878 group_optional.add_argument(
Yanray Wang68265f42023-07-26 14:44:52 +0800879 '-n', '--new-rev', type=str, default='current',
Yanray Wang955671b2023-07-21 12:08:27 +0800880 help='new Git revision as comparison base. '
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800881 '(Default is the current work directory, including uncommitted '
882 'changes.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800883 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800884 '-a', '--arch', type=str, default=detect_arch(),
Yanray Wang23bd5322023-05-24 11:03:59 +0800885 choices=list(map(lambda s: s.value, SupportedArch)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800886 help='Specify architecture for code size comparison. '
887 '(Default is the host architecture.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800888 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800889 '-c', '--config', type=str, default=SupportedConfig.DEFAULT.value,
Yanray Wang6a862582023-05-24 12:24:38 +0800890 choices=list(map(lambda s: s.value, SupportedConfig)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800891 help='Specify configuration type for code size comparison. '
Thomas Daubney540324c2023-10-06 17:07:24 +0100892 '(Default is the current Mbed TLS configuration.)')
Yanray Wangb664cb72023-07-18 12:28:35 +0800893 group_optional.add_argument(
894 '--markdown', action='store_true', dest='markdown',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800895 help='Show comparision of code size in a markdown table. '
896 '(Only show the files that have changed).')
Yanray Wang227576a2023-07-18 14:35:05 +0800897 group_optional.add_argument(
898 '--stdout', action='store_true', dest='stdout',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800899 help='Set this option to direct comparison result into sys.stdout. '
900 '(Default: file)')
Yanray Wang21127f72023-07-19 12:09:45 +0800901 group_optional.add_argument(
Yanray Wangee07afa2023-07-28 16:34:05 +0800902 '--show-all', action='store_true', dest='show_all',
903 help='Show all the objects in comparison result, including the ones '
904 'that haven\'t changed in code size. (Default: False)')
905 group_optional.add_argument(
Yanray Wang21127f72023-07-19 12:09:45 +0800906 '--verbose', action='store_true', dest='verbose',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800907 help='Show logs in detail for code size measurement. '
908 '(Default: False)')
Xiaofei Baibca03e52021-09-09 09:42:37 +0000909 comp_args = parser.parse_args()
910
Yanray Wang21127f72023-07-19 12:09:45 +0800911 logger = logging.getLogger()
Yanray Wang1998aac2023-08-14 10:33:37 +0800912 logging_util.configure_logger(logger, split_level=logging.NOTSET)
913 logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO)
Yanray Wang21127f72023-07-19 12:09:45 +0800914
Yanray Wang9e8b6712023-07-26 15:37:26 +0800915 if os.path.isfile(comp_args.record_dir):
916 logger.error("record directory: {} is not a directory"
917 .format(comp_args.record_dir))
918 sys.exit(1)
Yanray Wang955671b2023-07-21 12:08:27 +0800919 if os.path.isfile(comp_args.comp_dir):
Yanray Wang9e8b6712023-07-26 15:37:26 +0800920 logger.error("comparison directory: {} is not a directory"
921 .format(comp_args.comp_dir))
922 sys.exit(1)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000923
Yanray Wang68265f42023-07-26 14:44:52 +0800924 comp_args.old_rev = CodeSizeCalculator.validate_git_revision(
925 comp_args.old_rev)
926 if comp_args.new_rev != 'current':
927 comp_args.new_rev = CodeSizeCalculator.validate_git_revision(
Yanray Wang955671b2023-07-21 12:08:27 +0800928 comp_args.new_rev)
Xiaofei Bai2400b502021-10-21 12:22:58 +0000929
Yanray Wang5605c6f2023-07-21 16:09:00 +0800930 # version, git_rev, arch, config, compiler, opt_level
Yanray Wang955671b2023-07-21 12:08:27 +0800931 old_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang68265f42023-07-26 14:44:52 +0800932 'old', comp_args.old_rev, comp_args.arch, comp_args.config, 'cc', '-Os')
Yanray Wang955671b2023-07-21 12:08:27 +0800933 new_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang68265f42023-07-26 14:44:52 +0800934 'new', comp_args.new_rev, comp_args.arch, comp_args.config, 'cc', '-Os')
Yanray Wang5605c6f2023-07-21 16:09:00 +0800935 # host_arch, measure_cmd
Yanray Wang955671b2023-07-21 12:08:27 +0800936 size_common_info = CodeSizeCommonInfo(
937 detect_arch(), 'size -t')
Yanray Wangee07afa2023-07-28 16:34:05 +0800938 # record_dir, comp_dir, with_markdown, stdout, show_all
Yanray Wang955671b2023-07-21 12:08:27 +0800939 result_options = CodeSizeResultInfo(
940 comp_args.record_dir, comp_args.comp_dir,
Yanray Wangee07afa2023-07-28 16:34:05 +0800941 comp_args.markdown, comp_args.stdout, comp_args.show_all)
Yanray Wang923f9432023-07-17 12:43:00 +0800942
Yanray Wanga6cf6922023-07-24 15:20:42 +0800943 logger.info("Measure code size between {} and {} by `{}`."
944 .format(old_size_dist_info.get_info_indication(),
945 new_size_dist_info.get_info_indication(),
946 size_common_info.get_info_indication()))
Yanray Wang955671b2023-07-21 12:08:27 +0800947 CodeSizeComparison(old_size_dist_info, new_size_dist_info,
948 size_common_info, result_options,
949 logger).get_comparision_results()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000950
Xiaofei Baibca03e52021-09-09 09:42:37 +0000951if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000952 main()