blob: 171aafeec3444de8e3d1e2e958cf5e8a1a83f2c1 [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 Horstmannecd6d012024-05-10 16:58:31 +010024import framework_scripts_path # pylint: disable=unused-import
David Horstmanncd84bb22024-05-03 14:36:12 +010025from mbedtls_framework import build_tree
26from mbedtls_framework import logging_util
27from mbedtls_framework import typing_util
Gilles Peskined9071e72022-09-18 21:17:09 +020028
Yanray Wang23bd5322023-05-24 11:03:59 +080029class SupportedArch(Enum):
30 """Supported architecture for code size measurement."""
31 AARCH64 = 'aarch64'
32 AARCH32 = 'aarch32'
Yanray Wangaba71582023-05-29 16:45:56 +080033 ARMV8_M = 'armv8-m'
Yanray Wang23bd5322023-05-24 11:03:59 +080034 X86_64 = 'x86_64'
35 X86 = 'x86'
36
Yanray Wang955671b2023-07-21 12:08:27 +080037
Yanray Wang6a862582023-05-24 12:24:38 +080038class SupportedConfig(Enum):
39 """Supported configuration for code size measurement."""
40 DEFAULT = 'default'
41 TFM_MEDIUM = 'tfm-medium'
42
Yanray Wang955671b2023-07-21 12:08:27 +080043
Yanray Wang16ebc572023-05-30 18:10:20 +080044# Static library
45MBEDTLS_STATIC_LIB = {
46 'CRYPTO': 'library/libmbedcrypto.a',
47 'X509': 'library/libmbedx509.a',
48 'TLS': 'library/libmbedtls.a',
49}
50
Yanray Wang955671b2023-07-21 12:08:27 +080051class CodeSizeDistinctInfo: # pylint: disable=too-few-public-methods
52 """Data structure to store possibly distinct information for code size
53 comparison."""
54 def __init__( #pylint: disable=too-many-arguments
55 self,
56 version: str,
57 git_rev: str,
58 arch: str,
59 config: str,
Yanray Wang5605c6f2023-07-21 16:09:00 +080060 compiler: str,
61 opt_level: str,
Yanray Wang955671b2023-07-21 12:08:27 +080062 ) -> None:
63 """
64 :param: version: which version to compare with for code size.
65 :param: git_rev: Git revision to calculate code size.
66 :param: arch: architecture to measure code size on.
67 :param: config: Configuration type to calculate code size.
68 (See SupportedConfig)
Yanray Wang5605c6f2023-07-21 16:09:00 +080069 :param: compiler: compiler used to build library/*.o.
70 :param: opt_level: Options that control optimization. (E.g. -Os)
Yanray Wang955671b2023-07-21 12:08:27 +080071 """
72 self.version = version
73 self.git_rev = git_rev
74 self.arch = arch
75 self.config = config
Yanray Wang5605c6f2023-07-21 16:09:00 +080076 self.compiler = compiler
77 self.opt_level = opt_level
78 # Note: Variables below are not initialized by class instantiation.
79 self.pre_make_cmd = [] #type: typing.List[str]
80 self.make_cmd = ''
Yanray Wang955671b2023-07-21 12:08:27 +080081
Yanray Wanga6cf6922023-07-24 15:20:42 +080082 def get_info_indication(self):
83 """Return a unique string to indicate Code Size Distinct Information."""
Yanray Wang6ef50492023-07-26 14:59:37 +080084 return '{git_rev}-{arch}-{config}-{compiler}'.format(**self.__dict__)
Yanray Wanga6cf6922023-07-24 15:20:42 +080085
Yanray Wang955671b2023-07-21 12:08:27 +080086
87class CodeSizeCommonInfo: # pylint: disable=too-few-public-methods
88 """Data structure to store common information for code size comparison."""
89 def __init__(
90 self,
91 host_arch: str,
92 measure_cmd: str,
93 ) -> None:
94 """
95 :param host_arch: host architecture.
96 :param measure_cmd: command to measure code size for library/*.o.
97 """
98 self.host_arch = host_arch
99 self.measure_cmd = measure_cmd
100
Yanray Wanga6cf6922023-07-24 15:20:42 +0800101 def get_info_indication(self):
102 """Return a unique string to indicate Code Size Common Information."""
Yanray Wange4a36362023-07-25 10:37:11 +0800103 return '{measure_tool}'\
104 .format(measure_tool=self.measure_cmd.strip().split(' ')[0])
Yanray Wang955671b2023-07-21 12:08:27 +0800105
106class CodeSizeResultInfo: # pylint: disable=too-few-public-methods
107 """Data structure to store result options for code size comparison."""
Yanray Wangee07afa2023-07-28 16:34:05 +0800108 def __init__( #pylint: disable=too-many-arguments
Yanray Wang955671b2023-07-21 12:08:27 +0800109 self,
110 record_dir: str,
111 comp_dir: str,
112 with_markdown=False,
113 stdout=False,
Yanray Wangee07afa2023-07-28 16:34:05 +0800114 show_all=False,
Yanray Wang955671b2023-07-21 12:08:27 +0800115 ) -> None:
116 """
117 :param record_dir: directory to store code size record.
118 :param comp_dir: directory to store results of code size comparision.
119 :param with_markdown: write comparision result into a markdown table.
120 (Default: False)
121 :param stdout: direct comparison result into sys.stdout.
122 (Default False)
Yanray Wangee07afa2023-07-28 16:34:05 +0800123 :param show_all: show all objects in comparison result. (Default False)
Yanray Wang955671b2023-07-21 12:08:27 +0800124 """
125 self.record_dir = record_dir
126 self.comp_dir = comp_dir
127 self.with_markdown = with_markdown
128 self.stdout = stdout
Yanray Wangee07afa2023-07-28 16:34:05 +0800129 self.show_all = show_all
Yanray Wang955671b2023-07-21 12:08:27 +0800130
131
Yanray Wang23bd5322023-05-24 11:03:59 +0800132DETECT_ARCH_CMD = "cc -dM -E - < /dev/null"
133def detect_arch() -> str:
134 """Auto-detect host architecture."""
135 cc_output = subprocess.check_output(DETECT_ARCH_CMD, shell=True).decode()
Yanray Wang386c2f92023-07-20 15:32:15 +0800136 if '__aarch64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800137 return SupportedArch.AARCH64.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800138 if '__arm__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800139 return SupportedArch.AARCH32.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800140 if '__x86_64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800141 return SupportedArch.X86_64.value
Yanray Wangca9a3cb2023-07-26 17:16:29 +0800142 if '__i386__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800143 return SupportedArch.X86.value
144 else:
145 print("Unknown host architecture, cannot auto-detect arch.")
146 sys.exit(1)
Gilles Peskined9071e72022-09-18 21:17:09 +0200147
Yanray Wang28648232023-09-06 11:50:45 +0800148TFM_MEDIUM_CONFIG_H = 'configs/ext/tfm_mbedcrypto_config_profile_medium.h'
Harry Ramsey94c386a2025-01-16 16:08:34 +0000149TFM_MEDIUM_CRYPTO_CONFIG_H = 'tf-psa-crypto/configs/ext/crypto_config_profile_medium.h'
Yanray Wang5605c6f2023-07-21 16:09:00 +0800150
151CONFIG_H = 'include/mbedtls/mbedtls_config.h'
Ronald Cron7e5d61c2024-06-10 14:25:46 +0200152CRYPTO_CONFIG_H = 'tf-psa-crypto/include/psa/crypto_config.h'
Yanray Wang5605c6f2023-07-21 16:09:00 +0800153BACKUP_SUFFIX = '.code_size.bak'
154
Yanray Wang923f9432023-07-17 12:43:00 +0800155class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods
Yanray Wang6a862582023-05-24 12:24:38 +0800156 """Gather information used to measure code size.
157
158 It collects information about architecture, configuration in order to
159 infer build command for code size measurement.
160 """
161
Yanray Wangc18cd892023-05-31 11:08:04 +0800162 SupportedArchConfig = [
Yanray Wang386c2f92023-07-20 15:32:15 +0800163 '-a ' + SupportedArch.AARCH64.value + ' -c ' + SupportedConfig.DEFAULT.value,
164 '-a ' + SupportedArch.AARCH32.value + ' -c ' + SupportedConfig.DEFAULT.value,
165 '-a ' + SupportedArch.X86_64.value + ' -c ' + SupportedConfig.DEFAULT.value,
166 '-a ' + SupportedArch.X86.value + ' -c ' + SupportedConfig.DEFAULT.value,
167 '-a ' + SupportedArch.ARMV8_M.value + ' -c ' + SupportedConfig.TFM_MEDIUM.value,
Yanray Wangc18cd892023-05-31 11:08:04 +0800168 ]
169
Yanray Wang802af162023-07-17 14:04:30 +0800170 def __init__(
171 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800172 size_dist_info: CodeSizeDistinctInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800173 host_arch: str,
174 logger: logging.Logger,
Yanray Wang802af162023-07-17 14:04:30 +0800175 ) -> None:
Yanray Wang6a862582023-05-24 12:24:38 +0800176 """
Yanray Wang955671b2023-07-21 12:08:27 +0800177 :param size_dist_info:
178 CodeSizeDistinctInfo containing info for code size measurement.
179 - size_dist_info.arch: architecture to measure code size on.
180 - size_dist_info.config: configuration type to measure
181 code size with.
Yanray Wang5605c6f2023-07-21 16:09:00 +0800182 - size_dist_info.compiler: compiler used to build library/*.o.
183 - size_dist_info.opt_level: Options that control optimization.
184 (E.g. -Os)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800185 :param host_arch: host architecture.
186 :param logger: logging module
Yanray Wang6a862582023-05-24 12:24:38 +0800187 """
Yanray Wang5605c6f2023-07-21 16:09:00 +0800188 self.arch = size_dist_info.arch
189 self.config = size_dist_info.config
190 self.compiler = size_dist_info.compiler
191 self.opt_level = size_dist_info.opt_level
192
Ronald Cron3a252dd2024-07-03 17:00:50 +0200193 self.make_cmd = ['make', '-f', './scripts/legacy.make', '-j', 'lib']
Yanray Wang5605c6f2023-07-21 16:09:00 +0800194
Yanray Wang802af162023-07-17 14:04:30 +0800195 self.host_arch = host_arch
Yanray Wang21127f72023-07-19 12:09:45 +0800196 self.logger = logger
Yanray Wang6a862582023-05-24 12:24:38 +0800197
Yanray Wang5605c6f2023-07-21 16:09:00 +0800198 def check_correctness(self) -> bool:
199 """Check whether we are using proper / supported combination
200 of information to build library/*.o."""
Yanray Wang6a862582023-05-24 12:24:38 +0800201
Yanray Wang5605c6f2023-07-21 16:09:00 +0800202 # default config
203 if self.config == SupportedConfig.DEFAULT.value and \
204 self.arch == self.host_arch:
205 return True
206 # TF-M
207 elif self.arch == SupportedArch.ARMV8_M.value and \
208 self.config == SupportedConfig.TFM_MEDIUM.value:
209 return True
210
211 return False
212
213 def infer_pre_make_command(self) -> typing.List[str]:
214 """Infer command to set up proper configuration before running make."""
215 pre_make_cmd = [] #type: typing.List[str]
216 if self.config == SupportedConfig.TFM_MEDIUM.value:
Yanray Wanga279ca92023-07-26 15:01:10 +0800217 pre_make_cmd.append('cp {src} {dest}'
Yanray Wange4a36362023-07-25 10:37:11 +0800218 .format(src=TFM_MEDIUM_CONFIG_H, dest=CONFIG_H))
Yanray Wanga279ca92023-07-26 15:01:10 +0800219 pre_make_cmd.append('cp {src} {dest}'
Yanray Wange4a36362023-07-25 10:37:11 +0800220 .format(src=TFM_MEDIUM_CRYPTO_CONFIG_H,
221 dest=CRYPTO_CONFIG_H))
Yanray Wang5605c6f2023-07-21 16:09:00 +0800222
223 return pre_make_cmd
224
225 def infer_make_cflags(self) -> str:
226 """Infer CFLAGS by instance attributes in CodeSizeDistinctInfo."""
227 cflags = [] #type: typing.List[str]
228
229 # set optimization level
230 cflags.append(self.opt_level)
231 # set compiler by config
232 if self.config == SupportedConfig.TFM_MEDIUM.value:
233 self.compiler = 'armclang'
234 cflags.append('-mcpu=cortex-m33')
235 # set target
236 if self.compiler == 'armclang':
237 cflags.append('--target=arm-arm-none-eabi')
238
239 return ' '.join(cflags)
240
241 def infer_make_command(self) -> str:
242 """Infer make command by CFLAGS and CC."""
243
244 if self.check_correctness():
245 # set CFLAGS=
246 self.make_cmd.append('CFLAGS=\'{}\''.format(self.infer_make_cflags()))
247 # set CC=
248 self.make_cmd.append('CC={}'.format(self.compiler))
249 return ' '.join(self.make_cmd)
Yanray Wang6a862582023-05-24 12:24:38 +0800250 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800251 self.logger.error("Unsupported combination of architecture: {} " \
252 "and configuration: {}.\n"
Yanray Wang5605c6f2023-07-21 16:09:00 +0800253 .format(self.arch,
254 self.config))
Yanray Wang2ba9df22023-07-26 10:11:31 +0800255 self.logger.error("Please use supported combination of " \
Yanray Wang21127f72023-07-19 12:09:45 +0800256 "architecture and configuration:")
Yanray Wang923f9432023-07-17 12:43:00 +0800257 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang2ba9df22023-07-26 10:11:31 +0800258 self.logger.error(comb)
259 self.logger.error("")
260 self.logger.error("For your system, please use:")
Yanray Wang923f9432023-07-17 12:43:00 +0800261 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang802af162023-07-17 14:04:30 +0800262 if "default" in comb and self.host_arch not in comb:
Yanray Wang21f17442023-06-01 11:29:06 +0800263 continue
Yanray Wang2ba9df22023-07-26 10:11:31 +0800264 self.logger.error(comb)
Yanray Wang6a862582023-05-24 12:24:38 +0800265 sys.exit(1)
266
267
Yanray Wange0e27602023-07-14 17:37:45 +0800268class CodeSizeCalculator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800269 """ A calculator to calculate code size of library/*.o based on
Yanray Wange0e27602023-07-14 17:37:45 +0800270 Git revision and code size measurement tool.
271 """
272
Yanray Wang5605c6f2023-07-21 16:09:00 +0800273 def __init__( #pylint: disable=too-many-arguments
Yanray Wange0e27602023-07-14 17:37:45 +0800274 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800275 git_rev: str,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800276 pre_make_cmd: typing.List[str],
Yanray Wange0e27602023-07-14 17:37:45 +0800277 make_cmd: str,
Yanray Wang21127f72023-07-19 12:09:45 +0800278 measure_cmd: str,
279 logger: logging.Logger,
Yanray Wange0e27602023-07-14 17:37:45 +0800280 ) -> None:
281 """
Yanray Wang955671b2023-07-21 12:08:27 +0800282 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5605c6f2023-07-21 16:09:00 +0800283 :param pre_make_cmd: command to set up proper config before running make.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800284 :param make_cmd: command to build library/*.o.
285 :param measure_cmd: command to measure code size for library/*.o.
286 :param logger: logging module
Yanray Wange0e27602023-07-14 17:37:45 +0800287 """
288 self.repo_path = "."
289 self.git_command = "git"
Ronald Cron3a252dd2024-07-03 17:00:50 +0200290 self.make_clean = 'make -f ./scripts/legacy.make clean'
Yanray Wange0e27602023-07-14 17:37:45 +0800291
Yanray Wang955671b2023-07-21 12:08:27 +0800292 self.git_rev = git_rev
Yanray Wang5605c6f2023-07-21 16:09:00 +0800293 self.pre_make_cmd = pre_make_cmd
Yanray Wange0e27602023-07-14 17:37:45 +0800294 self.make_cmd = make_cmd
Yanray Wang802af162023-07-17 14:04:30 +0800295 self.measure_cmd = measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800296 self.logger = logger
Yanray Wange0e27602023-07-14 17:37:45 +0800297
298 @staticmethod
Yanray Wang955671b2023-07-21 12:08:27 +0800299 def validate_git_revision(git_rev: str) -> str:
Yanray Wange0e27602023-07-14 17:37:45 +0800300 result = subprocess.check_output(["git", "rev-parse", "--verify",
Yanray Wang955671b2023-07-21 12:08:27 +0800301 git_rev + "^{commit}"],
302 shell=False, universal_newlines=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800303 return result[:7]
Yanray Wange0e27602023-07-14 17:37:45 +0800304
Yanray Wang21127f72023-07-19 12:09:45 +0800305 def _create_git_worktree(self) -> str:
Yanray Wang955671b2023-07-21 12:08:27 +0800306 """Create a separate worktree for Git revision.
307 If Git revision is current, use current worktree instead."""
Yanray Wange0e27602023-07-14 17:37:45 +0800308
Yanray Wang5605c6f2023-07-21 16:09:00 +0800309 if self.git_rev == 'current':
Yanray Wang21127f72023-07-19 12:09:45 +0800310 self.logger.debug("Using current work directory.")
Yanray Wange0e27602023-07-14 17:37:45 +0800311 git_worktree_path = self.repo_path
312 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800313 self.logger.debug("Creating git worktree for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800314 .format(self.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800315 git_worktree_path = os.path.join(self.repo_path,
Yanray Wang955671b2023-07-21 12:08:27 +0800316 "temp-" + self.git_rev)
Yanray Wange0e27602023-07-14 17:37:45 +0800317 subprocess.check_output(
318 [self.git_command, "worktree", "add", "--detach",
Yanray Wang955671b2023-07-21 12:08:27 +0800319 git_worktree_path, self.git_rev], cwd=self.repo_path,
Yanray Wange0e27602023-07-14 17:37:45 +0800320 stderr=subprocess.STDOUT
321 )
Ronald Cron3a252dd2024-07-03 17:00:50 +0200322 subprocess.check_output(
323 [self.git_command, "submodule", "update", "--init", "--recursive"],
324 cwd=git_worktree_path, stderr=subprocess.STDOUT
325 )
Yanray Wange0e27602023-07-14 17:37:45 +0800326
327 return git_worktree_path
328
Yanray Wang5605c6f2023-07-21 16:09:00 +0800329 @staticmethod
330 def backup_config_files(restore: bool) -> None:
331 """Backup / Restore config files."""
332 if restore:
333 shutil.move(CONFIG_H + BACKUP_SUFFIX, CONFIG_H)
334 shutil.move(CRYPTO_CONFIG_H + BACKUP_SUFFIX, CRYPTO_CONFIG_H)
335 else:
336 shutil.copy(CONFIG_H, CONFIG_H + BACKUP_SUFFIX)
337 shutil.copy(CRYPTO_CONFIG_H, CRYPTO_CONFIG_H + BACKUP_SUFFIX)
338
Yanray Wange0e27602023-07-14 17:37:45 +0800339 def _build_libraries(self, git_worktree_path: str) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800340 """Build library/*.o in the specified worktree."""
Yanray Wange0e27602023-07-14 17:37:45 +0800341
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800342 self.logger.debug("Building library/*.o for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800343 .format(self.git_rev))
Yanray Wange0e27602023-07-14 17:37:45 +0800344 my_environment = os.environ.copy()
345 try:
Yanray Wang5605c6f2023-07-21 16:09:00 +0800346 if self.git_rev == 'current':
347 self.backup_config_files(restore=False)
348 for pre_cmd in self.pre_make_cmd:
349 subprocess.check_output(
350 pre_cmd, env=my_environment, shell=True,
351 cwd=git_worktree_path, stderr=subprocess.STDOUT,
352 universal_newlines=True
353 )
Yanray Wange0e27602023-07-14 17:37:45 +0800354 subprocess.check_output(
355 self.make_clean, 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 )
359 subprocess.check_output(
360 self.make_cmd, env=my_environment, shell=True,
361 cwd=git_worktree_path, stderr=subprocess.STDOUT,
Yanray Wang386c2f92023-07-20 15:32:15 +0800362 universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800363 )
Yanray Wang5605c6f2023-07-21 16:09:00 +0800364 if self.git_rev == 'current':
365 self.backup_config_files(restore=True)
Yanray Wange0e27602023-07-14 17:37:45 +0800366 except subprocess.CalledProcessError as e:
367 self._handle_called_process_error(e, git_worktree_path)
368
Yanray Wang386c2f92023-07-20 15:32:15 +0800369 def _gen_raw_code_size(self, git_worktree_path: str) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800370 """Measure code size by a tool and return in UTF-8 encoding."""
Yanray Wang21127f72023-07-19 12:09:45 +0800371
372 self.logger.debug("Measuring code size for {} by `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800373 .format(self.git_rev,
Yanray Wang21127f72023-07-19 12:09:45 +0800374 self.measure_cmd.strip().split(' ')[0]))
Yanray Wange0e27602023-07-14 17:37:45 +0800375
376 res = {}
377 for mod, st_lib in MBEDTLS_STATIC_LIB.items():
378 try:
379 result = subprocess.check_output(
Yanray Wang802af162023-07-17 14:04:30 +0800380 [self.measure_cmd + ' ' + st_lib], cwd=git_worktree_path,
381 shell=True, universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800382 )
383 res[mod] = result
384 except subprocess.CalledProcessError as e:
385 self._handle_called_process_error(e, git_worktree_path)
386
387 return res
388
389 def _remove_worktree(self, git_worktree_path: str) -> None:
390 """Remove temporary worktree."""
391 if git_worktree_path != self.repo_path:
Yanray Wang21127f72023-07-19 12:09:45 +0800392 self.logger.debug("Removing temporary worktree {}."
393 .format(git_worktree_path))
Yanray Wange0e27602023-07-14 17:37:45 +0800394 subprocess.check_output(
395 [self.git_command, "worktree", "remove", "--force",
396 git_worktree_path], cwd=self.repo_path,
397 stderr=subprocess.STDOUT
398 )
399
400 def _handle_called_process_error(self, e: subprocess.CalledProcessError,
401 git_worktree_path: str) -> None:
402 """Handle a CalledProcessError and quit the program gracefully.
403 Remove any extra worktrees so that the script may be called again."""
404
405 # Tell the user what went wrong
Yanray Wang21127f72023-07-19 12:09:45 +0800406 self.logger.error(e, exc_info=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800407 self.logger.error("Process output:\n {}".format(e.output))
Yanray Wange0e27602023-07-14 17:37:45 +0800408
409 # Quit gracefully by removing the existing worktree
410 self._remove_worktree(git_worktree_path)
411 sys.exit(-1)
412
Yanray Wang386c2f92023-07-20 15:32:15 +0800413 def cal_libraries_code_size(self) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800414 """Do a complete round to calculate code size of library/*.o
415 by measurement tool.
416
417 :return A dictionary of measured code size
418 - typing.Dict[mod: str]
419 """
Yanray Wange0e27602023-07-14 17:37:45 +0800420
Yanray Wang21127f72023-07-19 12:09:45 +0800421 git_worktree_path = self._create_git_worktree()
Yanray Wang6ae94a02023-07-26 17:12:57 +0800422 try:
423 self._build_libraries(git_worktree_path)
424 res = self._gen_raw_code_size(git_worktree_path)
425 finally:
426 self._remove_worktree(git_worktree_path)
Yanray Wange0e27602023-07-14 17:37:45 +0800427
428 return res
429
430
Yanray Wang15c43f32023-07-17 11:17:12 +0800431class CodeSizeGenerator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800432 """ A generator based on size measurement tool for library/*.o.
Yanray Wang15c43f32023-07-17 11:17:12 +0800433
434 This is an abstract class. To use it, derive a class that implements
Yanray Wang95059002023-07-24 12:29:22 +0800435 write_record and write_comparison methods, then call both of them with
436 proper arguments.
Yanray Wang15c43f32023-07-17 11:17:12 +0800437 """
Yanray Wang21127f72023-07-19 12:09:45 +0800438 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800439 """
440 :param logger: logging module
441 """
Yanray Wang21127f72023-07-19 12:09:45 +0800442 self.logger = logger
443
Yanray Wang95059002023-07-24 12:29:22 +0800444 def write_record(
Yanray Wang15c43f32023-07-17 11:17:12 +0800445 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800446 git_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800447 code_size_text: typing.Dict[str, str],
448 output: typing_util.Writable
Yanray Wang15c43f32023-07-17 11:17:12 +0800449 ) -> None:
450 """Write size record into a file.
451
Yanray Wang955671b2023-07-21 12:08:27 +0800452 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800453 :param code_size_text:
454 string output (utf-8) from measurement tool of code size.
455 - typing.Dict[mod: str]
Yanray Wang95059002023-07-24 12:29:22 +0800456 :param output: output stream which the code size record is written to.
457 (Note: Normally write code size record into File)
Yanray Wang15c43f32023-07-17 11:17:12 +0800458 """
459 raise NotImplementedError
460
Yanray Wangee07afa2023-07-28 16:34:05 +0800461 def write_comparison( #pylint: disable=too-many-arguments
Yanray Wang15c43f32023-07-17 11:17:12 +0800462 self,
463 old_rev: str,
464 new_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800465 output: typing_util.Writable,
Yanray Wangee07afa2023-07-28 16:34:05 +0800466 with_markdown=False,
467 show_all=False
Yanray Wang15c43f32023-07-17 11:17:12 +0800468 ) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800469 """Write a comparision result into a stream between two Git revisions.
Yanray Wang15c43f32023-07-17 11:17:12 +0800470
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800471 :param old_rev: old Git revision to compared with.
472 :param new_rev: new Git revision to compared with.
Yanray Wang95059002023-07-24 12:29:22 +0800473 :param output: output stream which the code size record is written to.
474 (File / sys.stdout)
475 :param with_markdown: write comparision result in a markdown table.
476 (Default: False)
Yanray Wangee07afa2023-07-28 16:34:05 +0800477 :param show_all: show all objects in comparison result. (Default False)
Yanray Wang15c43f32023-07-17 11:17:12 +0800478 """
479 raise NotImplementedError
480
481
482class CodeSizeGeneratorWithSize(CodeSizeGenerator):
Yanray Wang16ebc572023-05-30 18:10:20 +0800483 """Code Size Base Class for size record saving and writing."""
484
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800485 class SizeEntry: # pylint: disable=too-few-public-methods
486 """Data Structure to only store information of code size."""
Yanray Wangdcf360d2023-07-27 15:28:20 +0800487 def __init__(self, text: int, data: int, bss: int, dec: int):
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800488 self.text = text
489 self.data = data
490 self.bss = bss
491 self.total = dec # total <=> dec
492
Yanray Wang21127f72023-07-19 12:09:45 +0800493 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800494 """ Variable code_size is used to store size info for any Git revisions.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800495 :param code_size:
496 Data Format as following:
Yanray Wangdcf360d2023-07-27 15:28:20 +0800497 code_size = {
498 git_rev: {
499 module: {
500 file_name: SizeEntry,
501 ...
502 },
503 ...
504 },
505 ...
506 }
Yanray Wang16ebc572023-05-30 18:10:20 +0800507 """
Yanray Wang21127f72023-07-19 12:09:45 +0800508 super().__init__(logger)
Yanray Wang16ebc572023-05-30 18:10:20 +0800509 self.code_size = {} #type: typing.Dict[str, typing.Dict]
Yanray Wangdcf360d2023-07-27 15:28:20 +0800510 self.mod_total_suffix = '-' + 'TOTALS'
Yanray Wang16ebc572023-05-30 18:10:20 +0800511
Yanray Wang955671b2023-07-21 12:08:27 +0800512 def _set_size_record(self, git_rev: str, mod: str, size_text: str) -> None:
513 """Store size information for target Git revision and high-level module.
Yanray Wang16ebc572023-05-30 18:10:20 +0800514
515 size_text Format: text data bss dec hex filename
516 """
517 size_record = {}
518 for line in size_text.splitlines()[1:]:
519 data = line.split()
Yanray Wangdcf360d2023-07-27 15:28:20 +0800520 if re.match(r'\s*\(TOTALS\)', data[5]):
521 data[5] = mod + self.mod_total_suffix
Yanray Wang9b174e92023-07-17 17:59:53 +0800522 # file_name: SizeEntry(text, data, bss, dec)
523 size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangdcf360d2023-07-27 15:28:20 +0800524 int(data[0]), int(data[1]), int(data[2]), int(data[3]))
Yanray Wang6ef50492023-07-26 14:59:37 +0800525 self.code_size.setdefault(git_rev, {}).update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800526
Yanray Wang955671b2023-07-21 12:08:27 +0800527 def read_size_record(self, git_rev: str, fname: str) -> None:
Yanray Wang16ebc572023-05-30 18:10:20 +0800528 """Read size information from csv file and write it into code_size.
529
530 fname Format: filename text data bss dec
531 """
532 mod = ""
533 size_record = {}
534 with open(fname, 'r') as csv_file:
535 for line in csv_file:
536 data = line.strip().split()
537 # check if we find the beginning of a module
538 if data and data[0] in MBEDTLS_STATIC_LIB:
539 mod = data[0]
540 continue
541
542 if mod:
Yanray Wang9b174e92023-07-17 17:59:53 +0800543 # file_name: SizeEntry(text, data, bss, dec)
544 size_record[data[0]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangdcf360d2023-07-27 15:28:20 +0800545 int(data[1]), int(data[2]), int(data[3]), int(data[4]))
Yanray Wang16ebc572023-05-30 18:10:20 +0800546
547 # check if we hit record for the end of a module
Yanray Wangdcf360d2023-07-27 15:28:20 +0800548 m = re.match(r'\w+' + self.mod_total_suffix, line)
Yanray Wang16ebc572023-05-30 18:10:20 +0800549 if m:
Yanray Wang955671b2023-07-21 12:08:27 +0800550 if git_rev in self.code_size:
551 self.code_size[git_rev].update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800552 else:
Yanray Wang955671b2023-07-21 12:08:27 +0800553 self.code_size[git_rev] = {mod: size_record}
Yanray Wang16ebc572023-05-30 18:10:20 +0800554 mod = ""
555 size_record = {}
556
Yanray Wang95059002023-07-24 12:29:22 +0800557 def write_record(
Yanray Wang16ebc572023-05-30 18:10:20 +0800558 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800559 git_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800560 code_size_text: typing.Dict[str, str],
Yanray Wang16ebc572023-05-30 18:10:20 +0800561 output: typing_util.Writable
562 ) -> None:
563 """Write size information to a file.
564
Yanray Wangdcf360d2023-07-27 15:28:20 +0800565 Writing Format: filename text data bss total(dec)
Yanray Wang16ebc572023-05-30 18:10:20 +0800566 """
Yanray Wang95059002023-07-24 12:29:22 +0800567 for mod, size_text in code_size_text.items():
568 self._set_size_record(git_rev, mod, size_text)
569
Yanray Wangb664cb72023-07-18 12:28:35 +0800570 format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n"
571 output.write(format_string.format("filename",
572 "text", "data", "bss", "total"))
Yanray Wang16ebc572023-05-30 18:10:20 +0800573
Yanray Wangdcf360d2023-07-27 15:28:20 +0800574 for mod, f_size in self.code_size[git_rev].items():
575 output.write("\n" + mod + "\n")
576 for fname, size_entry in f_size.items():
577 output.write(format_string
578 .format(fname,
579 size_entry.text, size_entry.data,
580 size_entry.bss, size_entry.total))
581
Yanray Wangee07afa2023-07-28 16:34:05 +0800582 def write_comparison( #pylint: disable=too-many-arguments
Yanray Wang16ebc572023-05-30 18:10:20 +0800583 self,
584 old_rev: str,
585 new_rev: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800586 output: typing_util.Writable,
Yanray Wangee07afa2023-07-28 16:34:05 +0800587 with_markdown=False,
588 show_all=False
Yanray Wang16ebc572023-05-30 18:10:20 +0800589 ) -> None:
Yanray Wangee07afa2023-07-28 16:34:05 +0800590 # pylint: disable=too-many-locals
Yanray Wang16ebc572023-05-30 18:10:20 +0800591 """Write comparison result into a file.
592
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800593 Writing Format:
594 Markdown Output:
595 filename new(text) new(data) change(text) change(data)
596 CSV Output:
597 filename new(text) new(data) old(text) old(data) change(text) change(data)
Yanray Wang16ebc572023-05-30 18:10:20 +0800598 """
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800599 header_line = ["filename", "new(text)", "old(text)", "change(text)",
600 "new(data)", "old(data)", "change(data)"]
Yanray Wangb664cb72023-07-18 12:28:35 +0800601 if with_markdown:
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800602 dash_line = [":----", "----:", "----:", "----:",
603 "----:", "----:", "----:"]
604 # | filename | new(text) | new(data) | change(text) | change(data) |
605 line_format = "| {0:<30} | {1:>9} | {4:>9} | {3:>12} | {6:>12} |\n"
Yanray Wangdcf360d2023-07-27 15:28:20 +0800606 bold_text = lambda x: '**' + str(x) + '**'
Yanray Wangb664cb72023-07-18 12:28:35 +0800607 else:
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800608 # filename new(text) new(data) old(text) old(data) change(text) change(data)
609 line_format = "{0:<30} {1:>9} {4:>9} {2:>10} {5:>10} {3:>12} {6:>12}\n"
Yanray Wangb664cb72023-07-18 12:28:35 +0800610
Yanray Wangdcf360d2023-07-27 15:28:20 +0800611 def cal_sect_change(
612 old_size: typing.Optional[CodeSizeGeneratorWithSize.SizeEntry],
613 new_size: typing.Optional[CodeSizeGeneratorWithSize.SizeEntry],
614 sect: str
615 ) -> typing.List:
616 """Inner helper function to calculate size change for a section.
Yanray Wangb664cb72023-07-18 12:28:35 +0800617
Yanray Wangdcf360d2023-07-27 15:28:20 +0800618 Convention for special cases:
619 - If the object has been removed in new Git revision,
620 the size is minus code size of old Git revision;
621 the size change is marked as `Removed`,
622 - If the object only exists in new Git revision,
623 the size is code size of new Git revision;
624 the size change is marked as `None`,
Yanray Wang9b174e92023-07-17 17:59:53 +0800625
Yanray Wangdcf360d2023-07-27 15:28:20 +0800626 :param: old_size: code size for objects in old Git revision.
627 :param: new_size: code size for objects in new Git revision.
628 :param: sect: section to calculate from `size` tool. This could be
629 any instance variable in SizeEntry.
630 :return: List of [section size of objects for new Git revision,
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800631 section size of objects for old Git revision,
Yanray Wangdcf360d2023-07-27 15:28:20 +0800632 section size change of objects between two Git revisions]
633 """
634 if old_size and new_size:
635 new_attr = new_size.__dict__[sect]
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800636 old_attr = old_size.__dict__[sect]
637 delta = new_attr - old_attr
Yanray Wang0de11832023-08-14 11:54:47 +0800638 change_attr = '{0:{1}}'.format(delta, '+' if delta else '')
Yanray Wangdcf360d2023-07-27 15:28:20 +0800639 elif old_size:
Yanray Wangbc775c42023-08-16 15:59:55 +0800640 new_attr = 'Removed'
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800641 old_attr = old_size.__dict__[sect]
Yanray Wangbc775c42023-08-16 15:59:55 +0800642 delta = - old_attr
643 change_attr = '{0:{1}}'.format(delta, '+' if delta else '')
Yanray Wangdcf360d2023-07-27 15:28:20 +0800644 elif new_size:
645 new_attr = new_size.__dict__[sect]
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800646 old_attr = 'NotCreated'
Yanray Wangbc775c42023-08-16 15:59:55 +0800647 delta = new_attr
648 change_attr = '{0:{1}}'.format(delta, '+' if delta else '')
Yanray Wang9b174e92023-07-17 17:59:53 +0800649 else:
Yanray Wangdcf360d2023-07-27 15:28:20 +0800650 # Should never happen
651 new_attr = 'Error'
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800652 old_attr = 'Error'
Yanray Wangdcf360d2023-07-27 15:28:20 +0800653 change_attr = 'Error'
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800654 return [new_attr, old_attr, change_attr]
Yanray Wangdcf360d2023-07-27 15:28:20 +0800655
656 # sort dictionary by key
657 sort_by_k = lambda item: item[0].lower()
658 def get_results(
659 f_rev_size:
660 typing.Dict[str,
661 typing.Dict[str,
662 CodeSizeGeneratorWithSize.SizeEntry]]
663 ) -> typing.List:
664 """Return List of results in the format of:
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800665 [filename, new(text), old(text), change(text),
666 new(data), old(data), change(data)]
Yanray Wangdcf360d2023-07-27 15:28:20 +0800667 """
668 res = []
669 for fname, revs_size in sorted(f_rev_size.items(), key=sort_by_k):
670 old_size = revs_size.get(old_rev)
671 new_size = revs_size.get(new_rev)
672
673 text_sect = cal_sect_change(old_size, new_size, 'text')
674 data_sect = cal_sect_change(old_size, new_size, 'data')
675 # skip the files that haven't changed in code size
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800676 if not show_all and text_sect[-1] == '0' and data_sect[-1] == '0':
Yanray Wangdcf360d2023-07-27 15:28:20 +0800677 continue
678
679 res.append([fname, *text_sect, *data_sect])
680 return res
681
682 # write header
683 output.write(line_format.format(*header_line))
684 if with_markdown:
685 output.write(line_format.format(*dash_line))
686 for mod in MBEDTLS_STATIC_LIB:
687 # convert self.code_size to:
688 # {
689 # file_name: {
690 # old_rev: SizeEntry,
691 # new_rev: SizeEntry
692 # },
693 # ...
694 # }
695 f_rev_size = {} #type: typing.Dict[str, typing.Dict]
696 for fname, size_entry in self.code_size[old_rev][mod].items():
697 f_rev_size.setdefault(fname, {}).update({old_rev: size_entry})
698 for fname, size_entry in self.code_size[new_rev][mod].items():
699 f_rev_size.setdefault(fname, {}).update({new_rev: size_entry})
700
701 mod_total_sz = f_rev_size.pop(mod + self.mod_total_suffix)
702 res = get_results(f_rev_size)
703 total_clm = get_results({mod + self.mod_total_suffix: mod_total_sz})
704 if with_markdown:
705 # bold row of mod-TOTALS in markdown table
706 total_clm = [[bold_text(j) for j in i] for i in total_clm]
707 res += total_clm
708
709 # write comparison result
710 for line in res:
711 output.write(line_format.format(*line))
Yanray Wang16ebc572023-05-30 18:10:20 +0800712
713
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800714class CodeSizeComparison:
Xiaofei Bai2400b502021-10-21 12:22:58 +0000715 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000716
Yanray Wang955671b2023-07-21 12:08:27 +0800717 def __init__( #pylint: disable=too-many-arguments
Yanray Wang72b105f2023-05-31 15:20:39 +0800718 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800719 old_size_dist_info: CodeSizeDistinctInfo,
720 new_size_dist_info: CodeSizeDistinctInfo,
721 size_common_info: CodeSizeCommonInfo,
722 result_options: CodeSizeResultInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800723 logger: logging.Logger,
Yanray Wang72b105f2023-05-31 15:20:39 +0800724 ) -> None:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000725 """
Yanray Wang955671b2023-07-21 12:08:27 +0800726 :param old_size_dist_info: CodeSizeDistinctInfo containing old distinct
727 info to compare code size with.
728 :param new_size_dist_info: CodeSizeDistinctInfo containing new distinct
729 info to take as comparision base.
730 :param size_common_info: CodeSizeCommonInfo containing common info for
731 both old and new size distinct info and
732 measurement tool.
733 :param result_options: CodeSizeResultInfo containing results options for
734 code size record and comparision.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800735 :param logger: logging module
Xiaofei Baibca03e52021-09-09 09:42:37 +0000736 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000737
Yanray Wang21127f72023-07-19 12:09:45 +0800738 self.logger = logger
739
Yanray Wang955671b2023-07-21 12:08:27 +0800740 self.old_size_dist_info = old_size_dist_info
741 self.new_size_dist_info = new_size_dist_info
742 self.size_common_info = size_common_info
Yanray Wang5605c6f2023-07-21 16:09:00 +0800743 # infer pre make command
744 self.old_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
745 self.old_size_dist_info, self.size_common_info.host_arch,
746 self.logger).infer_pre_make_command()
747 self.new_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
748 self.new_size_dist_info, self.size_common_info.host_arch,
749 self.logger).infer_pre_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800750 # infer make command
Yanray Wang955671b2023-07-21 12:08:27 +0800751 self.old_size_dist_info.make_cmd = CodeSizeBuildInfo(
752 self.old_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800753 self.logger).infer_make_command()
Yanray Wang955671b2023-07-21 12:08:27 +0800754 self.new_size_dist_info.make_cmd = CodeSizeBuildInfo(
755 self.new_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800756 self.logger).infer_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800757 # initialize size parser with corresponding measurement tool
Yanray Wang21127f72023-07-19 12:09:45 +0800758 self.code_size_generator = self.__generate_size_parser()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000759
Yanray Wang955671b2023-07-21 12:08:27 +0800760 self.result_options = result_options
761 self.csv_dir = os.path.abspath(self.result_options.record_dir)
762 os.makedirs(self.csv_dir, exist_ok=True)
763 self.comp_dir = os.path.abspath(self.result_options.comp_dir)
764 os.makedirs(self.comp_dir, exist_ok=True)
765
Yanray Wang21127f72023-07-19 12:09:45 +0800766 def __generate_size_parser(self):
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800767 """Generate a parser for the corresponding measurement tool."""
Yanray Wang955671b2023-07-21 12:08:27 +0800768 if re.match(r'size', self.size_common_info.measure_cmd.strip()):
Yanray Wang21127f72023-07-19 12:09:45 +0800769 return CodeSizeGeneratorWithSize(self.logger)
Yanray Wang802af162023-07-17 14:04:30 +0800770 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800771 self.logger.error("Unsupported measurement tool: `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800772 .format(self.size_common_info.measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800773 .strip().split(' ')[0]))
Yanray Wang802af162023-07-17 14:04:30 +0800774 sys.exit(1)
775
Yanray Wang386c2f92023-07-20 15:32:15 +0800776 def cal_code_size(
777 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800778 size_dist_info: CodeSizeDistinctInfo
Yanray Wang386c2f92023-07-20 15:32:15 +0800779 ) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800780 """Calculate code size of library/*.o in a UTF-8 encoding"""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000781
Yanray Wang955671b2023-07-21 12:08:27 +0800782 return CodeSizeCalculator(size_dist_info.git_rev,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800783 size_dist_info.pre_make_cmd,
Yanray Wang955671b2023-07-21 12:08:27 +0800784 size_dist_info.make_cmd,
785 self.size_common_info.measure_cmd,
Yanray Wang21127f72023-07-19 12:09:45 +0800786 self.logger).cal_libraries_code_size()
Yanray Wang8804db92023-05-30 18:18:18 +0800787
Yanray Wang955671b2023-07-21 12:08:27 +0800788 def gen_code_size_report(self, size_dist_info: CodeSizeDistinctInfo) -> None:
Yanray Wang5e9130a2023-07-17 11:55:54 +0800789 """Generate code size record and write it into a file."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000790
Yanray Wang21127f72023-07-19 12:09:45 +0800791 self.logger.info("Start to generate code size record for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800792 .format(size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800793 output_file = os.path.join(
794 self.csv_dir,
795 '{}-{}.csv'
796 .format(size_dist_info.get_info_indication(),
797 self.size_common_info.get_info_indication()))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000798 # Check if the corresponding record exists
Yanray Wang955671b2023-07-21 12:08:27 +0800799 if size_dist_info.git_rev != "current" and \
Yanray Wang21127f72023-07-19 12:09:45 +0800800 os.path.exists(output_file):
801 self.logger.debug("Code size csv file for {} already exists."
Yanray Wang955671b2023-07-21 12:08:27 +0800802 .format(size_dist_info.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800803 self.code_size_generator.read_size_record(
Yanray Wang955671b2023-07-21 12:08:27 +0800804 size_dist_info.git_rev, output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000805 else:
Yanray Wang95059002023-07-24 12:29:22 +0800806 # measure code size
807 code_size_text = self.cal_code_size(size_dist_info)
808
809 self.logger.debug("Generating code size csv for {}."
810 .format(size_dist_info.git_rev))
811 output = open(output_file, "w")
812 self.code_size_generator.write_record(
813 size_dist_info.git_rev, code_size_text, output)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000814
Yanray Wang386c2f92023-07-20 15:32:15 +0800815 def gen_code_size_comparison(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800816 """Generate results of code size changes between two Git revisions,
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800817 old and new.
818
Yanray Wang955671b2023-07-21 12:08:27 +0800819 - Measured code size result of these two Git revisions must be available.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800820 - The result is directed into either file / stdout depending on
Yanray Wang955671b2023-07-21 12:08:27 +0800821 the option, size_common_info.result_options.stdout. (Default: file)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800822 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000823
Yanray Wang21127f72023-07-19 12:09:45 +0800824 self.logger.info("Start to generate comparision result between "\
825 "{} and {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800826 .format(self.old_size_dist_info.git_rev,
827 self.new_size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800828 if self.result_options.stdout:
829 output = sys.stdout
830 else:
831 output_file = os.path.join(
832 self.comp_dir,
Yanray Wangb1673202023-07-28 13:47:19 +0800833 '{}-{}-{}.{}'
Yanray Wanga6cf6922023-07-24 15:20:42 +0800834 .format(self.old_size_dist_info.get_info_indication(),
835 self.new_size_dist_info.get_info_indication(),
Yanray Wangb1673202023-07-28 13:47:19 +0800836 self.size_common_info.get_info_indication(),
837 'md' if self.result_options.with_markdown else 'csv'))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800838 output = open(output_file, "w")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000839
Yanray Wang95059002023-07-24 12:29:22 +0800840 self.logger.debug("Generating comparison results between {} and {}."
841 .format(self.old_size_dist_info.git_rev,
842 self.new_size_dist_info.git_rev))
Yanray Wangea842e72023-07-26 10:34:39 +0800843 if self.result_options.with_markdown or self.result_options.stdout:
844 print("Measure code size between {} and {} by `{}`."
845 .format(self.old_size_dist_info.get_info_indication(),
846 self.new_size_dist_info.get_info_indication(),
847 self.size_common_info.get_info_indication()),
848 file=output)
Yanray Wang95059002023-07-24 12:29:22 +0800849 self.code_size_generator.write_comparison(
Yanray Wang955671b2023-07-21 12:08:27 +0800850 self.old_size_dist_info.git_rev,
851 self.new_size_dist_info.git_rev,
Yanray Wangee07afa2023-07-28 16:34:05 +0800852 output, self.result_options.with_markdown,
853 self.result_options.show_all)
Yanray Wang21127f72023-07-19 12:09:45 +0800854
Yanray Wang386c2f92023-07-20 15:32:15 +0800855 def get_comparision_results(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800856 """Compare size of library/*.o between self.old_size_dist_info and
857 self.old_size_dist_info and generate the result file."""
Gilles Peskined9071e72022-09-18 21:17:09 +0200858 build_tree.check_repo_path()
Yanray Wang955671b2023-07-21 12:08:27 +0800859 self.gen_code_size_report(self.old_size_dist_info)
860 self.gen_code_size_report(self.new_size_dist_info)
Yanray Wang386c2f92023-07-20 15:32:15 +0800861 self.gen_code_size_comparison()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000862
Xiaofei Bai2400b502021-10-21 12:22:58 +0000863def main():
Yanray Wang502c54f2023-05-31 11:41:36 +0800864 parser = argparse.ArgumentParser(description=(__doc__))
865 group_required = parser.add_argument_group(
866 'required arguments',
867 'required arguments to parse for running ' + os.path.basename(__file__))
868 group_required.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800869 '-o', '--old-rev', type=str, required=True,
Yanray Wang955671b2023-07-21 12:08:27 +0800870 help='old Git revision for comparison.')
Yanray Wang502c54f2023-05-31 11:41:36 +0800871
872 group_optional = parser.add_argument_group(
873 'optional arguments',
874 'optional arguments to parse for running ' + os.path.basename(__file__))
875 group_optional.add_argument(
Yanray Wang9e8b6712023-07-26 15:37:26 +0800876 '--record-dir', type=str, default='code_size_records',
Yanray Wang955671b2023-07-21 12:08:27 +0800877 help='directory where code size record is stored. '
878 '(Default: code_size_records)')
879 group_optional.add_argument(
Yanray Wang9e8b6712023-07-26 15:37:26 +0800880 '--comp-dir', type=str, default='comparison',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800881 help='directory where comparison result is stored. '
882 '(Default: comparison)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800883 group_optional.add_argument(
Yanray Wang68265f42023-07-26 14:44:52 +0800884 '-n', '--new-rev', type=str, default='current',
Yanray Wang955671b2023-07-21 12:08:27 +0800885 help='new Git revision as comparison base. '
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800886 '(Default is the current work directory, including uncommitted '
887 'changes.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800888 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800889 '-a', '--arch', type=str, default=detect_arch(),
Yanray Wang23bd5322023-05-24 11:03:59 +0800890 choices=list(map(lambda s: s.value, SupportedArch)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800891 help='Specify architecture for code size comparison. '
892 '(Default is the host architecture.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800893 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800894 '-c', '--config', type=str, default=SupportedConfig.DEFAULT.value,
Yanray Wang6a862582023-05-24 12:24:38 +0800895 choices=list(map(lambda s: s.value, SupportedConfig)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800896 help='Specify configuration type for code size comparison. '
Thomas Daubney540324c2023-10-06 17:07:24 +0100897 '(Default is the current Mbed TLS configuration.)')
Yanray Wangb664cb72023-07-18 12:28:35 +0800898 group_optional.add_argument(
899 '--markdown', action='store_true', dest='markdown',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800900 help='Show comparision of code size in a markdown table. '
901 '(Only show the files that have changed).')
Yanray Wang227576a2023-07-18 14:35:05 +0800902 group_optional.add_argument(
903 '--stdout', action='store_true', dest='stdout',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800904 help='Set this option to direct comparison result into sys.stdout. '
905 '(Default: file)')
Yanray Wang21127f72023-07-19 12:09:45 +0800906 group_optional.add_argument(
Yanray Wangee07afa2023-07-28 16:34:05 +0800907 '--show-all', action='store_true', dest='show_all',
908 help='Show all the objects in comparison result, including the ones '
909 'that haven\'t changed in code size. (Default: False)')
910 group_optional.add_argument(
Yanray Wang21127f72023-07-19 12:09:45 +0800911 '--verbose', action='store_true', dest='verbose',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800912 help='Show logs in detail for code size measurement. '
913 '(Default: False)')
Xiaofei Baibca03e52021-09-09 09:42:37 +0000914 comp_args = parser.parse_args()
915
Yanray Wang21127f72023-07-19 12:09:45 +0800916 logger = logging.getLogger()
Yanray Wang1998aac2023-08-14 10:33:37 +0800917 logging_util.configure_logger(logger, split_level=logging.NOTSET)
918 logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO)
Yanray Wang21127f72023-07-19 12:09:45 +0800919
Yanray Wang9e8b6712023-07-26 15:37:26 +0800920 if os.path.isfile(comp_args.record_dir):
921 logger.error("record directory: {} is not a directory"
922 .format(comp_args.record_dir))
923 sys.exit(1)
Yanray Wang955671b2023-07-21 12:08:27 +0800924 if os.path.isfile(comp_args.comp_dir):
Yanray Wang9e8b6712023-07-26 15:37:26 +0800925 logger.error("comparison directory: {} is not a directory"
926 .format(comp_args.comp_dir))
927 sys.exit(1)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000928
Yanray Wang68265f42023-07-26 14:44:52 +0800929 comp_args.old_rev = CodeSizeCalculator.validate_git_revision(
930 comp_args.old_rev)
931 if comp_args.new_rev != 'current':
932 comp_args.new_rev = CodeSizeCalculator.validate_git_revision(
Yanray Wang955671b2023-07-21 12:08:27 +0800933 comp_args.new_rev)
Xiaofei Bai2400b502021-10-21 12:22:58 +0000934
Yanray Wang5605c6f2023-07-21 16:09:00 +0800935 # version, git_rev, arch, config, compiler, opt_level
Yanray Wang955671b2023-07-21 12:08:27 +0800936 old_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang68265f42023-07-26 14:44:52 +0800937 'old', comp_args.old_rev, comp_args.arch, comp_args.config, 'cc', '-Os')
Yanray Wang955671b2023-07-21 12:08:27 +0800938 new_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang68265f42023-07-26 14:44:52 +0800939 'new', comp_args.new_rev, comp_args.arch, comp_args.config, 'cc', '-Os')
Yanray Wang5605c6f2023-07-21 16:09:00 +0800940 # host_arch, measure_cmd
Yanray Wang955671b2023-07-21 12:08:27 +0800941 size_common_info = CodeSizeCommonInfo(
942 detect_arch(), 'size -t')
Yanray Wangee07afa2023-07-28 16:34:05 +0800943 # record_dir, comp_dir, with_markdown, stdout, show_all
Yanray Wang955671b2023-07-21 12:08:27 +0800944 result_options = CodeSizeResultInfo(
945 comp_args.record_dir, comp_args.comp_dir,
Yanray Wangee07afa2023-07-28 16:34:05 +0800946 comp_args.markdown, comp_args.stdout, comp_args.show_all)
Yanray Wang923f9432023-07-17 12:43:00 +0800947
Yanray Wanga6cf6922023-07-24 15:20:42 +0800948 logger.info("Measure code size between {} and {} by `{}`."
949 .format(old_size_dist_info.get_info_indication(),
950 new_size_dist_info.get_info_indication(),
951 size_common_info.get_info_indication()))
Yanray Wang955671b2023-07-21 12:08:27 +0800952 CodeSizeComparison(old_size_dist_info, new_size_dist_info,
953 size_common_info, result_options,
954 logger).get_comparision_results()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000955
Xiaofei Baibca03e52021-09-09 09:42:37 +0000956if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000957 main()