blob: 53d859edfa655c3ccef1b100b90879dd7a8d3672 [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
12# SPDX-License-Identifier: Apache-2.0
13#
14# Licensed under the Apache License, Version 2.0 (the "License"); you may
15# not use this file except in compliance with the License.
16# You may obtain a copy of the License at
17#
18# http://www.apache.org/licenses/LICENSE-2.0
19#
20# Unless required by applicable law or agreed to in writing, software
21# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
22# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23# See the License for the specific language governing permissions and
24# limitations under the License.
25
26import argparse
Yanray Wang21127f72023-07-19 12:09:45 +080027import logging
Xiaofei Baibca03e52021-09-09 09:42:37 +000028import os
Yanray Wang16ebc572023-05-30 18:10:20 +080029import re
Yanray Wang5605c6f2023-07-21 16:09:00 +080030import shutil
Xiaofei Baibca03e52021-09-09 09:42:37 +000031import subprocess
32import sys
Yanray Wang16ebc572023-05-30 18:10:20 +080033import typing
Yanray Wang23bd5322023-05-24 11:03:59 +080034from enum import Enum
Xiaofei Baibca03e52021-09-09 09:42:37 +000035
Gilles Peskined9071e72022-09-18 21:17:09 +020036from mbedtls_dev import build_tree
Yanray Wang21127f72023-07-19 12:09:45 +080037from mbedtls_dev import logging_util
38from mbedtls_dev import typing_util
Gilles Peskined9071e72022-09-18 21:17:09 +020039
Yanray Wang23bd5322023-05-24 11:03:59 +080040class SupportedArch(Enum):
41 """Supported architecture for code size measurement."""
42 AARCH64 = 'aarch64'
43 AARCH32 = 'aarch32'
Yanray Wangaba71582023-05-29 16:45:56 +080044 ARMV8_M = 'armv8-m'
Yanray Wang23bd5322023-05-24 11:03:59 +080045 X86_64 = 'x86_64'
46 X86 = 'x86'
47
Yanray Wang955671b2023-07-21 12:08:27 +080048
Yanray Wang6a862582023-05-24 12:24:38 +080049class SupportedConfig(Enum):
50 """Supported configuration for code size measurement."""
51 DEFAULT = 'default'
52 TFM_MEDIUM = 'tfm-medium'
53
Yanray Wang955671b2023-07-21 12:08:27 +080054
Yanray Wang16ebc572023-05-30 18:10:20 +080055# Static library
56MBEDTLS_STATIC_LIB = {
57 'CRYPTO': 'library/libmbedcrypto.a',
58 'X509': 'library/libmbedx509.a',
59 'TLS': 'library/libmbedtls.a',
60}
61
Yanray Wang955671b2023-07-21 12:08:27 +080062class CodeSizeDistinctInfo: # pylint: disable=too-few-public-methods
63 """Data structure to store possibly distinct information for code size
64 comparison."""
65 def __init__( #pylint: disable=too-many-arguments
66 self,
67 version: str,
68 git_rev: str,
69 arch: str,
70 config: str,
Yanray Wang5605c6f2023-07-21 16:09:00 +080071 compiler: str,
72 opt_level: str,
Yanray Wang955671b2023-07-21 12:08:27 +080073 ) -> None:
74 """
75 :param: version: which version to compare with for code size.
76 :param: git_rev: Git revision to calculate code size.
77 :param: arch: architecture to measure code size on.
78 :param: config: Configuration type to calculate code size.
79 (See SupportedConfig)
Yanray Wang5605c6f2023-07-21 16:09:00 +080080 :param: compiler: compiler used to build library/*.o.
81 :param: opt_level: Options that control optimization. (E.g. -Os)
Yanray Wang955671b2023-07-21 12:08:27 +080082 """
83 self.version = version
84 self.git_rev = git_rev
85 self.arch = arch
86 self.config = config
Yanray Wang5605c6f2023-07-21 16:09:00 +080087 self.compiler = compiler
88 self.opt_level = opt_level
89 # Note: Variables below are not initialized by class instantiation.
90 self.pre_make_cmd = [] #type: typing.List[str]
91 self.make_cmd = ''
Yanray Wang955671b2023-07-21 12:08:27 +080092
Yanray Wanga6cf6922023-07-24 15:20:42 +080093 def get_info_indication(self):
94 """Return a unique string to indicate Code Size Distinct Information."""
Yanray Wang6ef50492023-07-26 14:59:37 +080095 return '{git_rev}-{arch}-{config}-{compiler}'.format(**self.__dict__)
Yanray Wanga6cf6922023-07-24 15:20:42 +080096
Yanray Wang955671b2023-07-21 12:08:27 +080097
98class CodeSizeCommonInfo: # pylint: disable=too-few-public-methods
99 """Data structure to store common information for code size comparison."""
100 def __init__(
101 self,
102 host_arch: str,
103 measure_cmd: str,
104 ) -> None:
105 """
106 :param host_arch: host architecture.
107 :param measure_cmd: command to measure code size for library/*.o.
108 """
109 self.host_arch = host_arch
110 self.measure_cmd = measure_cmd
111
Yanray Wanga6cf6922023-07-24 15:20:42 +0800112 def get_info_indication(self):
113 """Return a unique string to indicate Code Size Common Information."""
Yanray Wange4a36362023-07-25 10:37:11 +0800114 return '{measure_tool}'\
115 .format(measure_tool=self.measure_cmd.strip().split(' ')[0])
Yanray Wang955671b2023-07-21 12:08:27 +0800116
117class CodeSizeResultInfo: # pylint: disable=too-few-public-methods
118 """Data structure to store result options for code size comparison."""
Yanray Wangee07afa2023-07-28 16:34:05 +0800119 def __init__( #pylint: disable=too-many-arguments
Yanray Wang955671b2023-07-21 12:08:27 +0800120 self,
121 record_dir: str,
122 comp_dir: str,
123 with_markdown=False,
124 stdout=False,
Yanray Wangee07afa2023-07-28 16:34:05 +0800125 show_all=False,
Yanray Wang955671b2023-07-21 12:08:27 +0800126 ) -> None:
127 """
128 :param record_dir: directory to store code size record.
129 :param comp_dir: directory to store results of code size comparision.
130 :param with_markdown: write comparision result into a markdown table.
131 (Default: False)
132 :param stdout: direct comparison result into sys.stdout.
133 (Default False)
Yanray Wangee07afa2023-07-28 16:34:05 +0800134 :param show_all: show all objects in comparison result. (Default False)
Yanray Wang955671b2023-07-21 12:08:27 +0800135 """
136 self.record_dir = record_dir
137 self.comp_dir = comp_dir
138 self.with_markdown = with_markdown
139 self.stdout = stdout
Yanray Wangee07afa2023-07-28 16:34:05 +0800140 self.show_all = show_all
Yanray Wang955671b2023-07-21 12:08:27 +0800141
142
Yanray Wang23bd5322023-05-24 11:03:59 +0800143DETECT_ARCH_CMD = "cc -dM -E - < /dev/null"
144def detect_arch() -> str:
145 """Auto-detect host architecture."""
146 cc_output = subprocess.check_output(DETECT_ARCH_CMD, shell=True).decode()
Yanray Wang386c2f92023-07-20 15:32:15 +0800147 if '__aarch64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800148 return SupportedArch.AARCH64.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800149 if '__arm__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800150 return SupportedArch.AARCH32.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800151 if '__x86_64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800152 return SupportedArch.X86_64.value
Yanray Wangca9a3cb2023-07-26 17:16:29 +0800153 if '__i386__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800154 return SupportedArch.X86.value
155 else:
156 print("Unknown host architecture, cannot auto-detect arch.")
157 sys.exit(1)
Gilles Peskined9071e72022-09-18 21:17:09 +0200158
Yanray Wang5605c6f2023-07-21 16:09:00 +0800159TFM_MEDIUM_CONFIG_H = 'configs/tfm_mbedcrypto_config_profile_medium.h'
160TFM_MEDIUM_CRYPTO_CONFIG_H = 'configs/crypto_config_profile_medium.h'
161
162CONFIG_H = 'include/mbedtls/mbedtls_config.h'
163CRYPTO_CONFIG_H = 'include/psa/crypto_config.h'
164BACKUP_SUFFIX = '.code_size.bak'
165
Yanray Wang923f9432023-07-17 12:43:00 +0800166class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods
Yanray Wang6a862582023-05-24 12:24:38 +0800167 """Gather information used to measure code size.
168
169 It collects information about architecture, configuration in order to
170 infer build command for code size measurement.
171 """
172
Yanray Wangc18cd892023-05-31 11:08:04 +0800173 SupportedArchConfig = [
Yanray Wang386c2f92023-07-20 15:32:15 +0800174 '-a ' + SupportedArch.AARCH64.value + ' -c ' + SupportedConfig.DEFAULT.value,
175 '-a ' + SupportedArch.AARCH32.value + ' -c ' + SupportedConfig.DEFAULT.value,
176 '-a ' + SupportedArch.X86_64.value + ' -c ' + SupportedConfig.DEFAULT.value,
177 '-a ' + SupportedArch.X86.value + ' -c ' + SupportedConfig.DEFAULT.value,
178 '-a ' + SupportedArch.ARMV8_M.value + ' -c ' + SupportedConfig.TFM_MEDIUM.value,
Yanray Wangc18cd892023-05-31 11:08:04 +0800179 ]
180
Yanray Wang802af162023-07-17 14:04:30 +0800181 def __init__(
182 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800183 size_dist_info: CodeSizeDistinctInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800184 host_arch: str,
185 logger: logging.Logger,
Yanray Wang802af162023-07-17 14:04:30 +0800186 ) -> None:
Yanray Wang6a862582023-05-24 12:24:38 +0800187 """
Yanray Wang955671b2023-07-21 12:08:27 +0800188 :param size_dist_info:
189 CodeSizeDistinctInfo containing info for code size measurement.
190 - size_dist_info.arch: architecture to measure code size on.
191 - size_dist_info.config: configuration type to measure
192 code size with.
Yanray Wang5605c6f2023-07-21 16:09:00 +0800193 - size_dist_info.compiler: compiler used to build library/*.o.
194 - size_dist_info.opt_level: Options that control optimization.
195 (E.g. -Os)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800196 :param host_arch: host architecture.
197 :param logger: logging module
Yanray Wang6a862582023-05-24 12:24:38 +0800198 """
Yanray Wang5605c6f2023-07-21 16:09:00 +0800199 self.arch = size_dist_info.arch
200 self.config = size_dist_info.config
201 self.compiler = size_dist_info.compiler
202 self.opt_level = size_dist_info.opt_level
203
204 self.make_cmd = ['make', '-j', 'lib']
205
Yanray Wang802af162023-07-17 14:04:30 +0800206 self.host_arch = host_arch
Yanray Wang21127f72023-07-19 12:09:45 +0800207 self.logger = logger
Yanray Wang6a862582023-05-24 12:24:38 +0800208
Yanray Wang5605c6f2023-07-21 16:09:00 +0800209 def check_correctness(self) -> bool:
210 """Check whether we are using proper / supported combination
211 of information to build library/*.o."""
Yanray Wang6a862582023-05-24 12:24:38 +0800212
Yanray Wang5605c6f2023-07-21 16:09:00 +0800213 # default config
214 if self.config == SupportedConfig.DEFAULT.value and \
215 self.arch == self.host_arch:
216 return True
217 # TF-M
218 elif self.arch == SupportedArch.ARMV8_M.value and \
219 self.config == SupportedConfig.TFM_MEDIUM.value:
220 return True
221
222 return False
223
224 def infer_pre_make_command(self) -> typing.List[str]:
225 """Infer command to set up proper configuration before running make."""
226 pre_make_cmd = [] #type: typing.List[str]
227 if self.config == SupportedConfig.TFM_MEDIUM.value:
Yanray Wanga279ca92023-07-26 15:01:10 +0800228 pre_make_cmd.append('cp {src} {dest}'
Yanray Wange4a36362023-07-25 10:37:11 +0800229 .format(src=TFM_MEDIUM_CONFIG_H, dest=CONFIG_H))
Yanray Wanga279ca92023-07-26 15:01:10 +0800230 pre_make_cmd.append('cp {src} {dest}'
Yanray Wange4a36362023-07-25 10:37:11 +0800231 .format(src=TFM_MEDIUM_CRYPTO_CONFIG_H,
232 dest=CRYPTO_CONFIG_H))
Yanray Wang5605c6f2023-07-21 16:09:00 +0800233
234 return pre_make_cmd
235
236 def infer_make_cflags(self) -> str:
237 """Infer CFLAGS by instance attributes in CodeSizeDistinctInfo."""
238 cflags = [] #type: typing.List[str]
239
240 # set optimization level
241 cflags.append(self.opt_level)
242 # set compiler by config
243 if self.config == SupportedConfig.TFM_MEDIUM.value:
244 self.compiler = 'armclang'
245 cflags.append('-mcpu=cortex-m33')
246 # set target
247 if self.compiler == 'armclang':
248 cflags.append('--target=arm-arm-none-eabi')
249
250 return ' '.join(cflags)
251
252 def infer_make_command(self) -> str:
253 """Infer make command by CFLAGS and CC."""
254
255 if self.check_correctness():
256 # set CFLAGS=
257 self.make_cmd.append('CFLAGS=\'{}\''.format(self.infer_make_cflags()))
258 # set CC=
259 self.make_cmd.append('CC={}'.format(self.compiler))
260 return ' '.join(self.make_cmd)
Yanray Wang6a862582023-05-24 12:24:38 +0800261 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800262 self.logger.error("Unsupported combination of architecture: {} " \
263 "and configuration: {}.\n"
Yanray Wang5605c6f2023-07-21 16:09:00 +0800264 .format(self.arch,
265 self.config))
Yanray Wang2ba9df22023-07-26 10:11:31 +0800266 self.logger.error("Please use supported combination of " \
Yanray Wang21127f72023-07-19 12:09:45 +0800267 "architecture and configuration:")
Yanray Wang923f9432023-07-17 12:43:00 +0800268 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang2ba9df22023-07-26 10:11:31 +0800269 self.logger.error(comb)
270 self.logger.error("")
271 self.logger.error("For your system, please use:")
Yanray Wang923f9432023-07-17 12:43:00 +0800272 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang802af162023-07-17 14:04:30 +0800273 if "default" in comb and self.host_arch not in comb:
Yanray Wang21f17442023-06-01 11:29:06 +0800274 continue
Yanray Wang2ba9df22023-07-26 10:11:31 +0800275 self.logger.error(comb)
Yanray Wang6a862582023-05-24 12:24:38 +0800276 sys.exit(1)
277
278
Yanray Wange0e27602023-07-14 17:37:45 +0800279class CodeSizeCalculator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800280 """ A calculator to calculate code size of library/*.o based on
Yanray Wange0e27602023-07-14 17:37:45 +0800281 Git revision and code size measurement tool.
282 """
283
Yanray Wang5605c6f2023-07-21 16:09:00 +0800284 def __init__( #pylint: disable=too-many-arguments
Yanray Wange0e27602023-07-14 17:37:45 +0800285 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800286 git_rev: str,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800287 pre_make_cmd: typing.List[str],
Yanray Wange0e27602023-07-14 17:37:45 +0800288 make_cmd: str,
Yanray Wang21127f72023-07-19 12:09:45 +0800289 measure_cmd: str,
290 logger: logging.Logger,
Yanray Wange0e27602023-07-14 17:37:45 +0800291 ) -> None:
292 """
Yanray Wang955671b2023-07-21 12:08:27 +0800293 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5605c6f2023-07-21 16:09:00 +0800294 :param pre_make_cmd: command to set up proper config before running make.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800295 :param make_cmd: command to build library/*.o.
296 :param measure_cmd: command to measure code size for library/*.o.
297 :param logger: logging module
Yanray Wange0e27602023-07-14 17:37:45 +0800298 """
299 self.repo_path = "."
300 self.git_command = "git"
301 self.make_clean = 'make clean'
302
Yanray Wang955671b2023-07-21 12:08:27 +0800303 self.git_rev = git_rev
Yanray Wang5605c6f2023-07-21 16:09:00 +0800304 self.pre_make_cmd = pre_make_cmd
Yanray Wange0e27602023-07-14 17:37:45 +0800305 self.make_cmd = make_cmd
Yanray Wang802af162023-07-17 14:04:30 +0800306 self.measure_cmd = measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800307 self.logger = logger
Yanray Wange0e27602023-07-14 17:37:45 +0800308
309 @staticmethod
Yanray Wang955671b2023-07-21 12:08:27 +0800310 def validate_git_revision(git_rev: str) -> str:
Yanray Wange0e27602023-07-14 17:37:45 +0800311 result = subprocess.check_output(["git", "rev-parse", "--verify",
Yanray Wang955671b2023-07-21 12:08:27 +0800312 git_rev + "^{commit}"],
313 shell=False, universal_newlines=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800314 return result[:7]
Yanray Wange0e27602023-07-14 17:37:45 +0800315
Yanray Wang21127f72023-07-19 12:09:45 +0800316 def _create_git_worktree(self) -> str:
Yanray Wang955671b2023-07-21 12:08:27 +0800317 """Create a separate worktree for Git revision.
318 If Git revision is current, use current worktree instead."""
Yanray Wange0e27602023-07-14 17:37:45 +0800319
Yanray Wang5605c6f2023-07-21 16:09:00 +0800320 if self.git_rev == 'current':
Yanray Wang21127f72023-07-19 12:09:45 +0800321 self.logger.debug("Using current work directory.")
Yanray Wange0e27602023-07-14 17:37:45 +0800322 git_worktree_path = self.repo_path
323 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800324 self.logger.debug("Creating git worktree for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800325 .format(self.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800326 git_worktree_path = os.path.join(self.repo_path,
Yanray Wang955671b2023-07-21 12:08:27 +0800327 "temp-" + self.git_rev)
Yanray Wange0e27602023-07-14 17:37:45 +0800328 subprocess.check_output(
329 [self.git_command, "worktree", "add", "--detach",
Yanray Wang955671b2023-07-21 12:08:27 +0800330 git_worktree_path, self.git_rev], cwd=self.repo_path,
Yanray Wange0e27602023-07-14 17:37:45 +0800331 stderr=subprocess.STDOUT
332 )
333
334 return git_worktree_path
335
Yanray Wang5605c6f2023-07-21 16:09:00 +0800336 @staticmethod
337 def backup_config_files(restore: bool) -> None:
338 """Backup / Restore config files."""
339 if restore:
340 shutil.move(CONFIG_H + BACKUP_SUFFIX, CONFIG_H)
341 shutil.move(CRYPTO_CONFIG_H + BACKUP_SUFFIX, CRYPTO_CONFIG_H)
342 else:
343 shutil.copy(CONFIG_H, CONFIG_H + BACKUP_SUFFIX)
344 shutil.copy(CRYPTO_CONFIG_H, CRYPTO_CONFIG_H + BACKUP_SUFFIX)
345
Yanray Wange0e27602023-07-14 17:37:45 +0800346 def _build_libraries(self, git_worktree_path: str) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800347 """Build library/*.o in the specified worktree."""
Yanray Wange0e27602023-07-14 17:37:45 +0800348
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800349 self.logger.debug("Building library/*.o for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800350 .format(self.git_rev))
Yanray Wange0e27602023-07-14 17:37:45 +0800351 my_environment = os.environ.copy()
352 try:
Yanray Wang5605c6f2023-07-21 16:09:00 +0800353 if self.git_rev == 'current':
354 self.backup_config_files(restore=False)
355 for pre_cmd in self.pre_make_cmd:
356 subprocess.check_output(
357 pre_cmd, env=my_environment, shell=True,
358 cwd=git_worktree_path, stderr=subprocess.STDOUT,
359 universal_newlines=True
360 )
Yanray Wange0e27602023-07-14 17:37:45 +0800361 subprocess.check_output(
362 self.make_clean, env=my_environment, shell=True,
363 cwd=git_worktree_path, stderr=subprocess.STDOUT,
Yanray Wang386c2f92023-07-20 15:32:15 +0800364 universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800365 )
366 subprocess.check_output(
367 self.make_cmd, env=my_environment, shell=True,
368 cwd=git_worktree_path, stderr=subprocess.STDOUT,
Yanray Wang386c2f92023-07-20 15:32:15 +0800369 universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800370 )
Yanray Wang5605c6f2023-07-21 16:09:00 +0800371 if self.git_rev == 'current':
372 self.backup_config_files(restore=True)
Yanray Wange0e27602023-07-14 17:37:45 +0800373 except subprocess.CalledProcessError as e:
374 self._handle_called_process_error(e, git_worktree_path)
375
Yanray Wang386c2f92023-07-20 15:32:15 +0800376 def _gen_raw_code_size(self, git_worktree_path: str) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800377 """Measure code size by a tool and return in UTF-8 encoding."""
Yanray Wang21127f72023-07-19 12:09:45 +0800378
379 self.logger.debug("Measuring code size for {} by `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800380 .format(self.git_rev,
Yanray Wang21127f72023-07-19 12:09:45 +0800381 self.measure_cmd.strip().split(' ')[0]))
Yanray Wange0e27602023-07-14 17:37:45 +0800382
383 res = {}
384 for mod, st_lib in MBEDTLS_STATIC_LIB.items():
385 try:
386 result = subprocess.check_output(
Yanray Wang802af162023-07-17 14:04:30 +0800387 [self.measure_cmd + ' ' + st_lib], cwd=git_worktree_path,
388 shell=True, universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800389 )
390 res[mod] = result
391 except subprocess.CalledProcessError as e:
392 self._handle_called_process_error(e, git_worktree_path)
393
394 return res
395
396 def _remove_worktree(self, git_worktree_path: str) -> None:
397 """Remove temporary worktree."""
398 if git_worktree_path != self.repo_path:
Yanray Wang21127f72023-07-19 12:09:45 +0800399 self.logger.debug("Removing temporary worktree {}."
400 .format(git_worktree_path))
Yanray Wange0e27602023-07-14 17:37:45 +0800401 subprocess.check_output(
402 [self.git_command, "worktree", "remove", "--force",
403 git_worktree_path], cwd=self.repo_path,
404 stderr=subprocess.STDOUT
405 )
406
407 def _handle_called_process_error(self, e: subprocess.CalledProcessError,
408 git_worktree_path: str) -> None:
409 """Handle a CalledProcessError and quit the program gracefully.
410 Remove any extra worktrees so that the script may be called again."""
411
412 # Tell the user what went wrong
Yanray Wang21127f72023-07-19 12:09:45 +0800413 self.logger.error(e, exc_info=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800414 self.logger.error("Process output:\n {}".format(e.output))
Yanray Wange0e27602023-07-14 17:37:45 +0800415
416 # Quit gracefully by removing the existing worktree
417 self._remove_worktree(git_worktree_path)
418 sys.exit(-1)
419
Yanray Wang386c2f92023-07-20 15:32:15 +0800420 def cal_libraries_code_size(self) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800421 """Do a complete round to calculate code size of library/*.o
422 by measurement tool.
423
424 :return A dictionary of measured code size
425 - typing.Dict[mod: str]
426 """
Yanray Wange0e27602023-07-14 17:37:45 +0800427
Yanray Wang21127f72023-07-19 12:09:45 +0800428 git_worktree_path = self._create_git_worktree()
Yanray Wang6ae94a02023-07-26 17:12:57 +0800429 try:
430 self._build_libraries(git_worktree_path)
431 res = self._gen_raw_code_size(git_worktree_path)
432 finally:
433 self._remove_worktree(git_worktree_path)
Yanray Wange0e27602023-07-14 17:37:45 +0800434
435 return res
436
437
Yanray Wang15c43f32023-07-17 11:17:12 +0800438class CodeSizeGenerator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800439 """ A generator based on size measurement tool for library/*.o.
Yanray Wang15c43f32023-07-17 11:17:12 +0800440
441 This is an abstract class. To use it, derive a class that implements
Yanray Wang95059002023-07-24 12:29:22 +0800442 write_record and write_comparison methods, then call both of them with
443 proper arguments.
Yanray Wang15c43f32023-07-17 11:17:12 +0800444 """
Yanray Wang21127f72023-07-19 12:09:45 +0800445 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800446 """
447 :param logger: logging module
448 """
Yanray Wang21127f72023-07-19 12:09:45 +0800449 self.logger = logger
450
Yanray Wang95059002023-07-24 12:29:22 +0800451 def write_record(
Yanray Wang15c43f32023-07-17 11:17:12 +0800452 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800453 git_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800454 code_size_text: typing.Dict[str, str],
455 output: typing_util.Writable
Yanray Wang15c43f32023-07-17 11:17:12 +0800456 ) -> None:
457 """Write size record into a file.
458
Yanray Wang955671b2023-07-21 12:08:27 +0800459 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800460 :param code_size_text:
461 string output (utf-8) from measurement tool of code size.
462 - typing.Dict[mod: str]
Yanray Wang95059002023-07-24 12:29:22 +0800463 :param output: output stream which the code size record is written to.
464 (Note: Normally write code size record into File)
Yanray Wang15c43f32023-07-17 11:17:12 +0800465 """
466 raise NotImplementedError
467
Yanray Wangee07afa2023-07-28 16:34:05 +0800468 def write_comparison( #pylint: disable=too-many-arguments
Yanray Wang15c43f32023-07-17 11:17:12 +0800469 self,
470 old_rev: str,
471 new_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800472 output: typing_util.Writable,
Yanray Wangee07afa2023-07-28 16:34:05 +0800473 with_markdown=False,
474 show_all=False
Yanray Wang15c43f32023-07-17 11:17:12 +0800475 ) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800476 """Write a comparision result into a stream between two Git revisions.
Yanray Wang15c43f32023-07-17 11:17:12 +0800477
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800478 :param old_rev: old Git revision to compared with.
479 :param new_rev: new Git revision to compared with.
Yanray Wang95059002023-07-24 12:29:22 +0800480 :param output: output stream which the code size record is written to.
481 (File / sys.stdout)
482 :param with_markdown: write comparision result in a markdown table.
483 (Default: False)
Yanray Wangee07afa2023-07-28 16:34:05 +0800484 :param show_all: show all objects in comparison result. (Default False)
Yanray Wang15c43f32023-07-17 11:17:12 +0800485 """
486 raise NotImplementedError
487
488
489class CodeSizeGeneratorWithSize(CodeSizeGenerator):
Yanray Wang16ebc572023-05-30 18:10:20 +0800490 """Code Size Base Class for size record saving and writing."""
491
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800492 class SizeEntry: # pylint: disable=too-few-public-methods
493 """Data Structure to only store information of code size."""
Yanray Wangdcf360d2023-07-27 15:28:20 +0800494 def __init__(self, text: int, data: int, bss: int, dec: int):
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800495 self.text = text
496 self.data = data
497 self.bss = bss
498 self.total = dec # total <=> dec
499
Yanray Wang21127f72023-07-19 12:09:45 +0800500 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800501 """ Variable code_size is used to store size info for any Git revisions.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800502 :param code_size:
503 Data Format as following:
Yanray Wangdcf360d2023-07-27 15:28:20 +0800504 code_size = {
505 git_rev: {
506 module: {
507 file_name: SizeEntry,
508 ...
509 },
510 ...
511 },
512 ...
513 }
Yanray Wang16ebc572023-05-30 18:10:20 +0800514 """
Yanray Wang21127f72023-07-19 12:09:45 +0800515 super().__init__(logger)
Yanray Wang16ebc572023-05-30 18:10:20 +0800516 self.code_size = {} #type: typing.Dict[str, typing.Dict]
Yanray Wangdcf360d2023-07-27 15:28:20 +0800517 self.mod_total_suffix = '-' + 'TOTALS'
Yanray Wang16ebc572023-05-30 18:10:20 +0800518
Yanray Wang955671b2023-07-21 12:08:27 +0800519 def _set_size_record(self, git_rev: str, mod: str, size_text: str) -> None:
520 """Store size information for target Git revision and high-level module.
Yanray Wang16ebc572023-05-30 18:10:20 +0800521
522 size_text Format: text data bss dec hex filename
523 """
524 size_record = {}
525 for line in size_text.splitlines()[1:]:
526 data = line.split()
Yanray Wangdcf360d2023-07-27 15:28:20 +0800527 if re.match(r'\s*\(TOTALS\)', data[5]):
528 data[5] = mod + self.mod_total_suffix
Yanray Wang9b174e92023-07-17 17:59:53 +0800529 # file_name: SizeEntry(text, data, bss, dec)
530 size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangdcf360d2023-07-27 15:28:20 +0800531 int(data[0]), int(data[1]), int(data[2]), int(data[3]))
Yanray Wang6ef50492023-07-26 14:59:37 +0800532 self.code_size.setdefault(git_rev, {}).update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800533
Yanray Wang955671b2023-07-21 12:08:27 +0800534 def read_size_record(self, git_rev: str, fname: str) -> None:
Yanray Wang16ebc572023-05-30 18:10:20 +0800535 """Read size information from csv file and write it into code_size.
536
537 fname Format: filename text data bss dec
538 """
539 mod = ""
540 size_record = {}
541 with open(fname, 'r') as csv_file:
542 for line in csv_file:
543 data = line.strip().split()
544 # check if we find the beginning of a module
545 if data and data[0] in MBEDTLS_STATIC_LIB:
546 mod = data[0]
547 continue
548
549 if mod:
Yanray Wang9b174e92023-07-17 17:59:53 +0800550 # file_name: SizeEntry(text, data, bss, dec)
551 size_record[data[0]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangdcf360d2023-07-27 15:28:20 +0800552 int(data[1]), int(data[2]), int(data[3]), int(data[4]))
Yanray Wang16ebc572023-05-30 18:10:20 +0800553
554 # check if we hit record for the end of a module
Yanray Wangdcf360d2023-07-27 15:28:20 +0800555 m = re.match(r'\w+' + self.mod_total_suffix, line)
Yanray Wang16ebc572023-05-30 18:10:20 +0800556 if m:
Yanray Wang955671b2023-07-21 12:08:27 +0800557 if git_rev in self.code_size:
558 self.code_size[git_rev].update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800559 else:
Yanray Wang955671b2023-07-21 12:08:27 +0800560 self.code_size[git_rev] = {mod: size_record}
Yanray Wang16ebc572023-05-30 18:10:20 +0800561 mod = ""
562 size_record = {}
563
Yanray Wang95059002023-07-24 12:29:22 +0800564 def write_record(
Yanray Wang16ebc572023-05-30 18:10:20 +0800565 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800566 git_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800567 code_size_text: typing.Dict[str, str],
Yanray Wang16ebc572023-05-30 18:10:20 +0800568 output: typing_util.Writable
569 ) -> None:
570 """Write size information to a file.
571
Yanray Wangdcf360d2023-07-27 15:28:20 +0800572 Writing Format: filename text data bss total(dec)
Yanray Wang16ebc572023-05-30 18:10:20 +0800573 """
Yanray Wang95059002023-07-24 12:29:22 +0800574 for mod, size_text in code_size_text.items():
575 self._set_size_record(git_rev, mod, size_text)
576
Yanray Wangb664cb72023-07-18 12:28:35 +0800577 format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n"
578 output.write(format_string.format("filename",
579 "text", "data", "bss", "total"))
Yanray Wang16ebc572023-05-30 18:10:20 +0800580
Yanray Wangdcf360d2023-07-27 15:28:20 +0800581 for mod, f_size in self.code_size[git_rev].items():
582 output.write("\n" + mod + "\n")
583 for fname, size_entry in f_size.items():
584 output.write(format_string
585 .format(fname,
586 size_entry.text, size_entry.data,
587 size_entry.bss, size_entry.total))
588
Yanray Wangee07afa2023-07-28 16:34:05 +0800589 def write_comparison( #pylint: disable=too-many-arguments
Yanray Wang16ebc572023-05-30 18:10:20 +0800590 self,
591 old_rev: str,
592 new_rev: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800593 output: typing_util.Writable,
Yanray Wangee07afa2023-07-28 16:34:05 +0800594 with_markdown=False,
595 show_all=False
Yanray Wang16ebc572023-05-30 18:10:20 +0800596 ) -> None:
Yanray Wangee07afa2023-07-28 16:34:05 +0800597 # pylint: disable=too-many-locals
Yanray Wang16ebc572023-05-30 18:10:20 +0800598 """Write comparison result into a file.
599
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800600 Writing Format:
601 Markdown Output:
602 filename new(text) new(data) change(text) change(data)
603 CSV Output:
604 filename new(text) new(data) old(text) old(data) change(text) change(data)
Yanray Wang16ebc572023-05-30 18:10:20 +0800605 """
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800606 header_line = ["filename", "new(text)", "old(text)", "change(text)",
607 "new(data)", "old(data)", "change(data)"]
Yanray Wangb664cb72023-07-18 12:28:35 +0800608 if with_markdown:
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800609 dash_line = [":----", "----:", "----:", "----:",
610 "----:", "----:", "----:"]
611 # | filename | new(text) | new(data) | change(text) | change(data) |
612 line_format = "| {0:<30} | {1:>9} | {4:>9} | {3:>12} | {6:>12} |\n"
Yanray Wangdcf360d2023-07-27 15:28:20 +0800613 bold_text = lambda x: '**' + str(x) + '**'
Yanray Wangb664cb72023-07-18 12:28:35 +0800614 else:
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800615 # filename new(text) new(data) old(text) old(data) change(text) change(data)
616 line_format = "{0:<30} {1:>9} {4:>9} {2:>10} {5:>10} {3:>12} {6:>12}\n"
Yanray Wangb664cb72023-07-18 12:28:35 +0800617
Yanray Wangdcf360d2023-07-27 15:28:20 +0800618 def cal_sect_change(
619 old_size: typing.Optional[CodeSizeGeneratorWithSize.SizeEntry],
620 new_size: typing.Optional[CodeSizeGeneratorWithSize.SizeEntry],
621 sect: str
622 ) -> typing.List:
623 """Inner helper function to calculate size change for a section.
Yanray Wangb664cb72023-07-18 12:28:35 +0800624
Yanray Wangdcf360d2023-07-27 15:28:20 +0800625 Convention for special cases:
626 - If the object has been removed in new Git revision,
627 the size is minus code size of old Git revision;
628 the size change is marked as `Removed`,
629 - If the object only exists in new Git revision,
630 the size is code size of new Git revision;
631 the size change is marked as `None`,
Yanray Wang9b174e92023-07-17 17:59:53 +0800632
Yanray Wangdcf360d2023-07-27 15:28:20 +0800633 :param: old_size: code size for objects in old Git revision.
634 :param: new_size: code size for objects in new Git revision.
635 :param: sect: section to calculate from `size` tool. This could be
636 any instance variable in SizeEntry.
637 :return: List of [section size of objects for new Git revision,
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800638 section size of objects for old Git revision,
Yanray Wangdcf360d2023-07-27 15:28:20 +0800639 section size change of objects between two Git revisions]
640 """
641 if old_size and new_size:
642 new_attr = new_size.__dict__[sect]
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800643 old_attr = old_size.__dict__[sect]
644 delta = new_attr - old_attr
Yanray Wang0de11832023-08-14 11:54:47 +0800645 change_attr = '{0:{1}}'.format(delta, '+' if delta else '')
Yanray Wangdcf360d2023-07-27 15:28:20 +0800646 elif old_size:
Yanray Wangbc775c42023-08-16 15:59:55 +0800647 new_attr = 'Removed'
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800648 old_attr = old_size.__dict__[sect]
Yanray Wangbc775c42023-08-16 15:59:55 +0800649 delta = - old_attr
650 change_attr = '{0:{1}}'.format(delta, '+' if delta else '')
Yanray Wangdcf360d2023-07-27 15:28:20 +0800651 elif new_size:
652 new_attr = new_size.__dict__[sect]
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800653 old_attr = 'NotCreated'
Yanray Wangbc775c42023-08-16 15:59:55 +0800654 delta = new_attr
655 change_attr = '{0:{1}}'.format(delta, '+' if delta else '')
Yanray Wang9b174e92023-07-17 17:59:53 +0800656 else:
Yanray Wangdcf360d2023-07-27 15:28:20 +0800657 # Should never happen
658 new_attr = 'Error'
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800659 old_attr = 'Error'
Yanray Wangdcf360d2023-07-27 15:28:20 +0800660 change_attr = 'Error'
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800661 return [new_attr, old_attr, change_attr]
Yanray Wangdcf360d2023-07-27 15:28:20 +0800662
663 # sort dictionary by key
664 sort_by_k = lambda item: item[0].lower()
665 def get_results(
666 f_rev_size:
667 typing.Dict[str,
668 typing.Dict[str,
669 CodeSizeGeneratorWithSize.SizeEntry]]
670 ) -> typing.List:
671 """Return List of results in the format of:
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800672 [filename, new(text), old(text), change(text),
673 new(data), old(data), change(data)]
Yanray Wangdcf360d2023-07-27 15:28:20 +0800674 """
675 res = []
676 for fname, revs_size in sorted(f_rev_size.items(), key=sort_by_k):
677 old_size = revs_size.get(old_rev)
678 new_size = revs_size.get(new_rev)
679
680 text_sect = cal_sect_change(old_size, new_size, 'text')
681 data_sect = cal_sect_change(old_size, new_size, 'data')
682 # skip the files that haven't changed in code size
Yanray Wang8a25e6f2023-08-14 14:38:36 +0800683 if not show_all and text_sect[-1] == '0' and data_sect[-1] == '0':
Yanray Wangdcf360d2023-07-27 15:28:20 +0800684 continue
685
686 res.append([fname, *text_sect, *data_sect])
687 return res
688
689 # write header
690 output.write(line_format.format(*header_line))
691 if with_markdown:
692 output.write(line_format.format(*dash_line))
693 for mod in MBEDTLS_STATIC_LIB:
694 # convert self.code_size to:
695 # {
696 # file_name: {
697 # old_rev: SizeEntry,
698 # new_rev: SizeEntry
699 # },
700 # ...
701 # }
702 f_rev_size = {} #type: typing.Dict[str, typing.Dict]
703 for fname, size_entry in self.code_size[old_rev][mod].items():
704 f_rev_size.setdefault(fname, {}).update({old_rev: size_entry})
705 for fname, size_entry in self.code_size[new_rev][mod].items():
706 f_rev_size.setdefault(fname, {}).update({new_rev: size_entry})
707
708 mod_total_sz = f_rev_size.pop(mod + self.mod_total_suffix)
709 res = get_results(f_rev_size)
710 total_clm = get_results({mod + self.mod_total_suffix: mod_total_sz})
711 if with_markdown:
712 # bold row of mod-TOTALS in markdown table
713 total_clm = [[bold_text(j) for j in i] for i in total_clm]
714 res += total_clm
715
716 # write comparison result
717 for line in res:
718 output.write(line_format.format(*line))
Yanray Wang16ebc572023-05-30 18:10:20 +0800719
720
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800721class CodeSizeComparison:
Xiaofei Bai2400b502021-10-21 12:22:58 +0000722 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000723
Yanray Wang955671b2023-07-21 12:08:27 +0800724 def __init__( #pylint: disable=too-many-arguments
Yanray Wang72b105f2023-05-31 15:20:39 +0800725 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800726 old_size_dist_info: CodeSizeDistinctInfo,
727 new_size_dist_info: CodeSizeDistinctInfo,
728 size_common_info: CodeSizeCommonInfo,
729 result_options: CodeSizeResultInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800730 logger: logging.Logger,
Yanray Wang72b105f2023-05-31 15:20:39 +0800731 ) -> None:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000732 """
Yanray Wang955671b2023-07-21 12:08:27 +0800733 :param old_size_dist_info: CodeSizeDistinctInfo containing old distinct
734 info to compare code size with.
735 :param new_size_dist_info: CodeSizeDistinctInfo containing new distinct
736 info to take as comparision base.
737 :param size_common_info: CodeSizeCommonInfo containing common info for
738 both old and new size distinct info and
739 measurement tool.
740 :param result_options: CodeSizeResultInfo containing results options for
741 code size record and comparision.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800742 :param logger: logging module
Xiaofei Baibca03e52021-09-09 09:42:37 +0000743 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000744
Yanray Wang21127f72023-07-19 12:09:45 +0800745 self.logger = logger
746
Yanray Wang955671b2023-07-21 12:08:27 +0800747 self.old_size_dist_info = old_size_dist_info
748 self.new_size_dist_info = new_size_dist_info
749 self.size_common_info = size_common_info
Yanray Wang5605c6f2023-07-21 16:09:00 +0800750 # infer pre make command
751 self.old_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
752 self.old_size_dist_info, self.size_common_info.host_arch,
753 self.logger).infer_pre_make_command()
754 self.new_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
755 self.new_size_dist_info, self.size_common_info.host_arch,
756 self.logger).infer_pre_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800757 # infer make command
Yanray Wang955671b2023-07-21 12:08:27 +0800758 self.old_size_dist_info.make_cmd = CodeSizeBuildInfo(
759 self.old_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800760 self.logger).infer_make_command()
Yanray Wang955671b2023-07-21 12:08:27 +0800761 self.new_size_dist_info.make_cmd = CodeSizeBuildInfo(
762 self.new_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800763 self.logger).infer_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800764 # initialize size parser with corresponding measurement tool
Yanray Wang21127f72023-07-19 12:09:45 +0800765 self.code_size_generator = self.__generate_size_parser()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000766
Yanray Wang955671b2023-07-21 12:08:27 +0800767 self.result_options = result_options
768 self.csv_dir = os.path.abspath(self.result_options.record_dir)
769 os.makedirs(self.csv_dir, exist_ok=True)
770 self.comp_dir = os.path.abspath(self.result_options.comp_dir)
771 os.makedirs(self.comp_dir, exist_ok=True)
772
Yanray Wang21127f72023-07-19 12:09:45 +0800773 def __generate_size_parser(self):
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800774 """Generate a parser for the corresponding measurement tool."""
Yanray Wang955671b2023-07-21 12:08:27 +0800775 if re.match(r'size', self.size_common_info.measure_cmd.strip()):
Yanray Wang21127f72023-07-19 12:09:45 +0800776 return CodeSizeGeneratorWithSize(self.logger)
Yanray Wang802af162023-07-17 14:04:30 +0800777 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800778 self.logger.error("Unsupported measurement tool: `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800779 .format(self.size_common_info.measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800780 .strip().split(' ')[0]))
Yanray Wang802af162023-07-17 14:04:30 +0800781 sys.exit(1)
782
Yanray Wang386c2f92023-07-20 15:32:15 +0800783 def cal_code_size(
784 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800785 size_dist_info: CodeSizeDistinctInfo
Yanray Wang386c2f92023-07-20 15:32:15 +0800786 ) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800787 """Calculate code size of library/*.o in a UTF-8 encoding"""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000788
Yanray Wang955671b2023-07-21 12:08:27 +0800789 return CodeSizeCalculator(size_dist_info.git_rev,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800790 size_dist_info.pre_make_cmd,
Yanray Wang955671b2023-07-21 12:08:27 +0800791 size_dist_info.make_cmd,
792 self.size_common_info.measure_cmd,
Yanray Wang21127f72023-07-19 12:09:45 +0800793 self.logger).cal_libraries_code_size()
Yanray Wang8804db92023-05-30 18:18:18 +0800794
Yanray Wang955671b2023-07-21 12:08:27 +0800795 def gen_code_size_report(self, size_dist_info: CodeSizeDistinctInfo) -> None:
Yanray Wang5e9130a2023-07-17 11:55:54 +0800796 """Generate code size record and write it into a file."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000797
Yanray Wang21127f72023-07-19 12:09:45 +0800798 self.logger.info("Start to generate code size record for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800799 .format(size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800800 output_file = os.path.join(
801 self.csv_dir,
802 '{}-{}.csv'
803 .format(size_dist_info.get_info_indication(),
804 self.size_common_info.get_info_indication()))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000805 # Check if the corresponding record exists
Yanray Wang955671b2023-07-21 12:08:27 +0800806 if size_dist_info.git_rev != "current" and \
Yanray Wang21127f72023-07-19 12:09:45 +0800807 os.path.exists(output_file):
808 self.logger.debug("Code size csv file for {} already exists."
Yanray Wang955671b2023-07-21 12:08:27 +0800809 .format(size_dist_info.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800810 self.code_size_generator.read_size_record(
Yanray Wang955671b2023-07-21 12:08:27 +0800811 size_dist_info.git_rev, output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000812 else:
Yanray Wang95059002023-07-24 12:29:22 +0800813 # measure code size
814 code_size_text = self.cal_code_size(size_dist_info)
815
816 self.logger.debug("Generating code size csv for {}."
817 .format(size_dist_info.git_rev))
818 output = open(output_file, "w")
819 self.code_size_generator.write_record(
820 size_dist_info.git_rev, code_size_text, output)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000821
Yanray Wang386c2f92023-07-20 15:32:15 +0800822 def gen_code_size_comparison(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800823 """Generate results of code size changes between two Git revisions,
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800824 old and new.
825
Yanray Wang955671b2023-07-21 12:08:27 +0800826 - Measured code size result of these two Git revisions must be available.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800827 - The result is directed into either file / stdout depending on
Yanray Wang955671b2023-07-21 12:08:27 +0800828 the option, size_common_info.result_options.stdout. (Default: file)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800829 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000830
Yanray Wang21127f72023-07-19 12:09:45 +0800831 self.logger.info("Start to generate comparision result between "\
832 "{} and {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800833 .format(self.old_size_dist_info.git_rev,
834 self.new_size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800835 if self.result_options.stdout:
836 output = sys.stdout
837 else:
838 output_file = os.path.join(
839 self.comp_dir,
Yanray Wangb1673202023-07-28 13:47:19 +0800840 '{}-{}-{}.{}'
Yanray Wanga6cf6922023-07-24 15:20:42 +0800841 .format(self.old_size_dist_info.get_info_indication(),
842 self.new_size_dist_info.get_info_indication(),
Yanray Wangb1673202023-07-28 13:47:19 +0800843 self.size_common_info.get_info_indication(),
844 'md' if self.result_options.with_markdown else 'csv'))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800845 output = open(output_file, "w")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000846
Yanray Wang95059002023-07-24 12:29:22 +0800847 self.logger.debug("Generating comparison results between {} and {}."
848 .format(self.old_size_dist_info.git_rev,
849 self.new_size_dist_info.git_rev))
Yanray Wangea842e72023-07-26 10:34:39 +0800850 if self.result_options.with_markdown or self.result_options.stdout:
851 print("Measure code size between {} and {} by `{}`."
852 .format(self.old_size_dist_info.get_info_indication(),
853 self.new_size_dist_info.get_info_indication(),
854 self.size_common_info.get_info_indication()),
855 file=output)
Yanray Wang95059002023-07-24 12:29:22 +0800856 self.code_size_generator.write_comparison(
Yanray Wang955671b2023-07-21 12:08:27 +0800857 self.old_size_dist_info.git_rev,
858 self.new_size_dist_info.git_rev,
Yanray Wangee07afa2023-07-28 16:34:05 +0800859 output, self.result_options.with_markdown,
860 self.result_options.show_all)
Yanray Wang21127f72023-07-19 12:09:45 +0800861
Yanray Wang386c2f92023-07-20 15:32:15 +0800862 def get_comparision_results(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800863 """Compare size of library/*.o between self.old_size_dist_info and
864 self.old_size_dist_info and generate the result file."""
Gilles Peskined9071e72022-09-18 21:17:09 +0200865 build_tree.check_repo_path()
Yanray Wang955671b2023-07-21 12:08:27 +0800866 self.gen_code_size_report(self.old_size_dist_info)
867 self.gen_code_size_report(self.new_size_dist_info)
Yanray Wang386c2f92023-07-20 15:32:15 +0800868 self.gen_code_size_comparison()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000869
Xiaofei Bai2400b502021-10-21 12:22:58 +0000870def main():
Yanray Wang502c54f2023-05-31 11:41:36 +0800871 parser = argparse.ArgumentParser(description=(__doc__))
872 group_required = parser.add_argument_group(
873 'required arguments',
874 'required arguments to parse for running ' + os.path.basename(__file__))
875 group_required.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800876 '-o', '--old-rev', type=str, required=True,
Yanray Wang955671b2023-07-21 12:08:27 +0800877 help='old Git revision for comparison.')
Yanray Wang502c54f2023-05-31 11:41:36 +0800878
879 group_optional = parser.add_argument_group(
880 'optional arguments',
881 'optional arguments to parse for running ' + os.path.basename(__file__))
882 group_optional.add_argument(
Yanray Wang9e8b6712023-07-26 15:37:26 +0800883 '--record-dir', type=str, default='code_size_records',
Yanray Wang955671b2023-07-21 12:08:27 +0800884 help='directory where code size record is stored. '
885 '(Default: code_size_records)')
886 group_optional.add_argument(
Yanray Wang9e8b6712023-07-26 15:37:26 +0800887 '--comp-dir', type=str, default='comparison',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800888 help='directory where comparison result is stored. '
889 '(Default: comparison)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800890 group_optional.add_argument(
Yanray Wang68265f42023-07-26 14:44:52 +0800891 '-n', '--new-rev', type=str, default='current',
Yanray Wang955671b2023-07-21 12:08:27 +0800892 help='new Git revision as comparison base. '
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800893 '(Default is the current work directory, including uncommitted '
894 'changes.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800895 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800896 '-a', '--arch', type=str, default=detect_arch(),
Yanray Wang23bd5322023-05-24 11:03:59 +0800897 choices=list(map(lambda s: s.value, SupportedArch)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800898 help='Specify architecture for code size comparison. '
899 '(Default is the host architecture.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800900 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800901 '-c', '--config', type=str, default=SupportedConfig.DEFAULT.value,
Yanray Wang6a862582023-05-24 12:24:38 +0800902 choices=list(map(lambda s: s.value, SupportedConfig)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800903 help='Specify configuration type for code size comparison. '
904 '(Default is the current MbedTLS configuration.)')
Yanray Wangb664cb72023-07-18 12:28:35 +0800905 group_optional.add_argument(
906 '--markdown', action='store_true', dest='markdown',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800907 help='Show comparision of code size in a markdown table. '
908 '(Only show the files that have changed).')
Yanray Wang227576a2023-07-18 14:35:05 +0800909 group_optional.add_argument(
910 '--stdout', action='store_true', dest='stdout',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800911 help='Set this option to direct comparison result into sys.stdout. '
912 '(Default: file)')
Yanray Wang21127f72023-07-19 12:09:45 +0800913 group_optional.add_argument(
Yanray Wangee07afa2023-07-28 16:34:05 +0800914 '--show-all', action='store_true', dest='show_all',
915 help='Show all the objects in comparison result, including the ones '
916 'that haven\'t changed in code size. (Default: False)')
917 group_optional.add_argument(
Yanray Wang21127f72023-07-19 12:09:45 +0800918 '--verbose', action='store_true', dest='verbose',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800919 help='Show logs in detail for code size measurement. '
920 '(Default: False)')
Xiaofei Baibca03e52021-09-09 09:42:37 +0000921 comp_args = parser.parse_args()
922
Yanray Wang21127f72023-07-19 12:09:45 +0800923 logger = logging.getLogger()
Yanray Wang1998aac2023-08-14 10:33:37 +0800924 logging_util.configure_logger(logger, split_level=logging.NOTSET)
925 logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO)
Yanray Wang21127f72023-07-19 12:09:45 +0800926
Yanray Wang9e8b6712023-07-26 15:37:26 +0800927 if os.path.isfile(comp_args.record_dir):
928 logger.error("record directory: {} is not a directory"
929 .format(comp_args.record_dir))
930 sys.exit(1)
Yanray Wang955671b2023-07-21 12:08:27 +0800931 if os.path.isfile(comp_args.comp_dir):
Yanray Wang9e8b6712023-07-26 15:37:26 +0800932 logger.error("comparison directory: {} is not a directory"
933 .format(comp_args.comp_dir))
934 sys.exit(1)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000935
Yanray Wang68265f42023-07-26 14:44:52 +0800936 comp_args.old_rev = CodeSizeCalculator.validate_git_revision(
937 comp_args.old_rev)
938 if comp_args.new_rev != 'current':
939 comp_args.new_rev = CodeSizeCalculator.validate_git_revision(
Yanray Wang955671b2023-07-21 12:08:27 +0800940 comp_args.new_rev)
Xiaofei Bai2400b502021-10-21 12:22:58 +0000941
Yanray Wang5605c6f2023-07-21 16:09:00 +0800942 # version, git_rev, arch, config, compiler, opt_level
Yanray Wang955671b2023-07-21 12:08:27 +0800943 old_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang68265f42023-07-26 14:44:52 +0800944 'old', comp_args.old_rev, comp_args.arch, comp_args.config, 'cc', '-Os')
Yanray Wang955671b2023-07-21 12:08:27 +0800945 new_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang68265f42023-07-26 14:44:52 +0800946 'new', comp_args.new_rev, comp_args.arch, comp_args.config, 'cc', '-Os')
Yanray Wang5605c6f2023-07-21 16:09:00 +0800947 # host_arch, measure_cmd
Yanray Wang955671b2023-07-21 12:08:27 +0800948 size_common_info = CodeSizeCommonInfo(
949 detect_arch(), 'size -t')
Yanray Wangee07afa2023-07-28 16:34:05 +0800950 # record_dir, comp_dir, with_markdown, stdout, show_all
Yanray Wang955671b2023-07-21 12:08:27 +0800951 result_options = CodeSizeResultInfo(
952 comp_args.record_dir, comp_args.comp_dir,
Yanray Wangee07afa2023-07-28 16:34:05 +0800953 comp_args.markdown, comp_args.stdout, comp_args.show_all)
Yanray Wang923f9432023-07-17 12:43:00 +0800954
Yanray Wanga6cf6922023-07-24 15:20:42 +0800955 logger.info("Measure code size between {} and {} by `{}`."
956 .format(old_size_dist_info.get_info_indication(),
957 new_size_dist_info.get_info_indication(),
958 size_common_info.get_info_indication()))
Yanray Wang955671b2023-07-21 12:08:27 +0800959 CodeSizeComparison(old_size_dist_info, new_size_dist_info,
960 size_common_info, result_options,
961 logger).get_comparision_results()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000962
Xiaofei Baibca03e52021-09-09 09:42:37 +0000963if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000964 main()