blob: 0c29c41faa9fbc36ae638f8aab9fcd6171acbb83 [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 Wange4a36362023-07-25 10:37:11 +080095 return '{rev}-{arch}-{config}-{cc}'\
96 .format(rev=self.git_rev, arch=self.arch, config=self.config,
97 cc=self.compiler)
Yanray Wanga6cf6922023-07-24 15:20:42 +080098
Yanray Wang955671b2023-07-21 12:08:27 +080099
100class CodeSizeCommonInfo: # pylint: disable=too-few-public-methods
101 """Data structure to store common information for code size comparison."""
102 def __init__(
103 self,
104 host_arch: str,
105 measure_cmd: str,
106 ) -> None:
107 """
108 :param host_arch: host architecture.
109 :param measure_cmd: command to measure code size for library/*.o.
110 """
111 self.host_arch = host_arch
112 self.measure_cmd = measure_cmd
113
Yanray Wanga6cf6922023-07-24 15:20:42 +0800114 def get_info_indication(self):
115 """Return a unique string to indicate Code Size Common Information."""
Yanray Wange4a36362023-07-25 10:37:11 +0800116 return '{measure_tool}'\
117 .format(measure_tool=self.measure_cmd.strip().split(' ')[0])
Yanray Wang955671b2023-07-21 12:08:27 +0800118
119class CodeSizeResultInfo: # pylint: disable=too-few-public-methods
120 """Data structure to store result options for code size comparison."""
121 def __init__(
122 self,
123 record_dir: str,
124 comp_dir: str,
125 with_markdown=False,
126 stdout=False,
127 ) -> None:
128 """
129 :param record_dir: directory to store code size record.
130 :param comp_dir: directory to store results of code size comparision.
131 :param with_markdown: write comparision result into a markdown table.
132 (Default: False)
133 :param stdout: direct comparison result into sys.stdout.
134 (Default False)
135 """
136 self.record_dir = record_dir
137 self.comp_dir = comp_dir
138 self.with_markdown = with_markdown
139 self.stdout = stdout
140
141
Yanray Wang23bd5322023-05-24 11:03:59 +0800142DETECT_ARCH_CMD = "cc -dM -E - < /dev/null"
143def detect_arch() -> str:
144 """Auto-detect host architecture."""
145 cc_output = subprocess.check_output(DETECT_ARCH_CMD, shell=True).decode()
Yanray Wang386c2f92023-07-20 15:32:15 +0800146 if '__aarch64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800147 return SupportedArch.AARCH64.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800148 if '__arm__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800149 return SupportedArch.AARCH32.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800150 if '__x86_64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800151 return SupportedArch.X86_64.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800152 if '__x86__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800153 return SupportedArch.X86.value
154 else:
155 print("Unknown host architecture, cannot auto-detect arch.")
156 sys.exit(1)
Gilles Peskined9071e72022-09-18 21:17:09 +0200157
Yanray Wang5605c6f2023-07-21 16:09:00 +0800158TFM_MEDIUM_CONFIG_H = 'configs/tfm_mbedcrypto_config_profile_medium.h'
159TFM_MEDIUM_CRYPTO_CONFIG_H = 'configs/crypto_config_profile_medium.h'
160
161CONFIG_H = 'include/mbedtls/mbedtls_config.h'
162CRYPTO_CONFIG_H = 'include/psa/crypto_config.h'
163BACKUP_SUFFIX = '.code_size.bak'
164
Yanray Wang923f9432023-07-17 12:43:00 +0800165class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods
Yanray Wang6a862582023-05-24 12:24:38 +0800166 """Gather information used to measure code size.
167
168 It collects information about architecture, configuration in order to
169 infer build command for code size measurement.
170 """
171
Yanray Wangc18cd892023-05-31 11:08:04 +0800172 SupportedArchConfig = [
Yanray Wang386c2f92023-07-20 15:32:15 +0800173 '-a ' + SupportedArch.AARCH64.value + ' -c ' + SupportedConfig.DEFAULT.value,
174 '-a ' + SupportedArch.AARCH32.value + ' -c ' + SupportedConfig.DEFAULT.value,
175 '-a ' + SupportedArch.X86_64.value + ' -c ' + SupportedConfig.DEFAULT.value,
176 '-a ' + SupportedArch.X86.value + ' -c ' + SupportedConfig.DEFAULT.value,
177 '-a ' + SupportedArch.ARMV8_M.value + ' -c ' + SupportedConfig.TFM_MEDIUM.value,
Yanray Wangc18cd892023-05-31 11:08:04 +0800178 ]
179
Yanray Wang802af162023-07-17 14:04:30 +0800180 def __init__(
181 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800182 size_dist_info: CodeSizeDistinctInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800183 host_arch: str,
184 logger: logging.Logger,
Yanray Wang802af162023-07-17 14:04:30 +0800185 ) -> None:
Yanray Wang6a862582023-05-24 12:24:38 +0800186 """
Yanray Wang955671b2023-07-21 12:08:27 +0800187 :param size_dist_info:
188 CodeSizeDistinctInfo containing info for code size measurement.
189 - size_dist_info.arch: architecture to measure code size on.
190 - size_dist_info.config: configuration type to measure
191 code size with.
Yanray Wang5605c6f2023-07-21 16:09:00 +0800192 - size_dist_info.compiler: compiler used to build library/*.o.
193 - size_dist_info.opt_level: Options that control optimization.
194 (E.g. -Os)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800195 :param host_arch: host architecture.
196 :param logger: logging module
Yanray Wang6a862582023-05-24 12:24:38 +0800197 """
Yanray Wang5605c6f2023-07-21 16:09:00 +0800198 self.arch = size_dist_info.arch
199 self.config = size_dist_info.config
200 self.compiler = size_dist_info.compiler
201 self.opt_level = size_dist_info.opt_level
202
203 self.make_cmd = ['make', '-j', 'lib']
204
Yanray Wang802af162023-07-17 14:04:30 +0800205 self.host_arch = host_arch
Yanray Wang21127f72023-07-19 12:09:45 +0800206 self.logger = logger
Yanray Wang6a862582023-05-24 12:24:38 +0800207
Yanray Wang5605c6f2023-07-21 16:09:00 +0800208 def check_correctness(self) -> bool:
209 """Check whether we are using proper / supported combination
210 of information to build library/*.o."""
Yanray Wang6a862582023-05-24 12:24:38 +0800211
Yanray Wang5605c6f2023-07-21 16:09:00 +0800212 # default config
213 if self.config == SupportedConfig.DEFAULT.value and \
214 self.arch == self.host_arch:
215 return True
216 # TF-M
217 elif self.arch == SupportedArch.ARMV8_M.value and \
218 self.config == SupportedConfig.TFM_MEDIUM.value:
219 return True
220
221 return False
222
223 def infer_pre_make_command(self) -> typing.List[str]:
224 """Infer command to set up proper configuration before running make."""
225 pre_make_cmd = [] #type: typing.List[str]
226 if self.config == SupportedConfig.TFM_MEDIUM.value:
Yanray Wange4a36362023-07-25 10:37:11 +0800227 pre_make_cmd.append('cp -r {src} {dest}'
228 .format(src=TFM_MEDIUM_CONFIG_H, dest=CONFIG_H))
229 pre_make_cmd.append('cp -r {src} {dest}'
230 .format(src=TFM_MEDIUM_CRYPTO_CONFIG_H,
231 dest=CRYPTO_CONFIG_H))
Yanray Wang5605c6f2023-07-21 16:09:00 +0800232
233 return pre_make_cmd
234
235 def infer_make_cflags(self) -> str:
236 """Infer CFLAGS by instance attributes in CodeSizeDistinctInfo."""
237 cflags = [] #type: typing.List[str]
238
239 # set optimization level
240 cflags.append(self.opt_level)
241 # set compiler by config
242 if self.config == SupportedConfig.TFM_MEDIUM.value:
243 self.compiler = 'armclang'
244 cflags.append('-mcpu=cortex-m33')
245 # set target
246 if self.compiler == 'armclang':
247 cflags.append('--target=arm-arm-none-eabi')
248
249 return ' '.join(cflags)
250
251 def infer_make_command(self) -> str:
252 """Infer make command by CFLAGS and CC."""
253
254 if self.check_correctness():
255 # set CFLAGS=
256 self.make_cmd.append('CFLAGS=\'{}\''.format(self.infer_make_cflags()))
257 # set CC=
258 self.make_cmd.append('CC={}'.format(self.compiler))
259 return ' '.join(self.make_cmd)
Yanray Wang6a862582023-05-24 12:24:38 +0800260 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800261 self.logger.error("Unsupported combination of architecture: {} " \
262 "and configuration: {}.\n"
Yanray Wang5605c6f2023-07-21 16:09:00 +0800263 .format(self.arch,
264 self.config))
Yanray Wang21127f72023-07-19 12:09:45 +0800265 self.logger.info("Please use supported combination of " \
266 "architecture and configuration:")
Yanray Wang923f9432023-07-17 12:43:00 +0800267 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang21127f72023-07-19 12:09:45 +0800268 self.logger.info(comb)
269 self.logger.info("")
270 self.logger.info("For your system, please use:")
Yanray Wang923f9432023-07-17 12:43:00 +0800271 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang802af162023-07-17 14:04:30 +0800272 if "default" in comb and self.host_arch not in comb:
Yanray Wang21f17442023-06-01 11:29:06 +0800273 continue
Yanray Wang21127f72023-07-19 12:09:45 +0800274 self.logger.info(comb)
Yanray Wang6a862582023-05-24 12:24:38 +0800275 sys.exit(1)
276
277
Yanray Wange0e27602023-07-14 17:37:45 +0800278class CodeSizeCalculator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800279 """ A calculator to calculate code size of library/*.o based on
Yanray Wange0e27602023-07-14 17:37:45 +0800280 Git revision and code size measurement tool.
281 """
282
Yanray Wang5605c6f2023-07-21 16:09:00 +0800283 def __init__( #pylint: disable=too-many-arguments
Yanray Wange0e27602023-07-14 17:37:45 +0800284 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800285 git_rev: str,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800286 pre_make_cmd: typing.List[str],
Yanray Wange0e27602023-07-14 17:37:45 +0800287 make_cmd: str,
Yanray Wang21127f72023-07-19 12:09:45 +0800288 measure_cmd: str,
289 logger: logging.Logger,
Yanray Wange0e27602023-07-14 17:37:45 +0800290 ) -> None:
291 """
Yanray Wang955671b2023-07-21 12:08:27 +0800292 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5605c6f2023-07-21 16:09:00 +0800293 :param pre_make_cmd: command to set up proper config before running make.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800294 :param make_cmd: command to build library/*.o.
295 :param measure_cmd: command to measure code size for library/*.o.
296 :param logger: logging module
Yanray Wange0e27602023-07-14 17:37:45 +0800297 """
298 self.repo_path = "."
299 self.git_command = "git"
300 self.make_clean = 'make clean'
301
Yanray Wang955671b2023-07-21 12:08:27 +0800302 self.git_rev = git_rev
Yanray Wang5605c6f2023-07-21 16:09:00 +0800303 self.pre_make_cmd = pre_make_cmd
Yanray Wange0e27602023-07-14 17:37:45 +0800304 self.make_cmd = make_cmd
Yanray Wang802af162023-07-17 14:04:30 +0800305 self.measure_cmd = measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800306 self.logger = logger
Yanray Wange0e27602023-07-14 17:37:45 +0800307
308 @staticmethod
Yanray Wang955671b2023-07-21 12:08:27 +0800309 def validate_git_revision(git_rev: str) -> str:
Yanray Wange0e27602023-07-14 17:37:45 +0800310 result = subprocess.check_output(["git", "rev-parse", "--verify",
Yanray Wang955671b2023-07-21 12:08:27 +0800311 git_rev + "^{commit}"],
312 shell=False, universal_newlines=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800313 return result[:7]
Yanray Wange0e27602023-07-14 17:37:45 +0800314
Yanray Wang21127f72023-07-19 12:09:45 +0800315 def _create_git_worktree(self) -> str:
Yanray Wang955671b2023-07-21 12:08:27 +0800316 """Create a separate worktree for Git revision.
317 If Git revision is current, use current worktree instead."""
Yanray Wange0e27602023-07-14 17:37:45 +0800318
Yanray Wang5605c6f2023-07-21 16:09:00 +0800319 if self.git_rev == 'current':
Yanray Wang21127f72023-07-19 12:09:45 +0800320 self.logger.debug("Using current work directory.")
Yanray Wange0e27602023-07-14 17:37:45 +0800321 git_worktree_path = self.repo_path
322 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800323 self.logger.debug("Creating git worktree for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800324 .format(self.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800325 git_worktree_path = os.path.join(self.repo_path,
Yanray Wang955671b2023-07-21 12:08:27 +0800326 "temp-" + self.git_rev)
Yanray Wange0e27602023-07-14 17:37:45 +0800327 subprocess.check_output(
328 [self.git_command, "worktree", "add", "--detach",
Yanray Wang955671b2023-07-21 12:08:27 +0800329 git_worktree_path, self.git_rev], cwd=self.repo_path,
Yanray Wange0e27602023-07-14 17:37:45 +0800330 stderr=subprocess.STDOUT
331 )
332
333 return git_worktree_path
334
Yanray Wang5605c6f2023-07-21 16:09:00 +0800335 @staticmethod
336 def backup_config_files(restore: bool) -> None:
337 """Backup / Restore config files."""
338 if restore:
339 shutil.move(CONFIG_H + BACKUP_SUFFIX, CONFIG_H)
340 shutil.move(CRYPTO_CONFIG_H + BACKUP_SUFFIX, CRYPTO_CONFIG_H)
341 else:
342 shutil.copy(CONFIG_H, CONFIG_H + BACKUP_SUFFIX)
343 shutil.copy(CRYPTO_CONFIG_H, CRYPTO_CONFIG_H + BACKUP_SUFFIX)
344
Yanray Wange0e27602023-07-14 17:37:45 +0800345 def _build_libraries(self, git_worktree_path: str) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800346 """Build library/*.o in the specified worktree."""
Yanray Wange0e27602023-07-14 17:37:45 +0800347
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800348 self.logger.debug("Building library/*.o for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800349 .format(self.git_rev))
Yanray Wange0e27602023-07-14 17:37:45 +0800350 my_environment = os.environ.copy()
351 try:
Yanray Wang5605c6f2023-07-21 16:09:00 +0800352 if self.git_rev == 'current':
353 self.backup_config_files(restore=False)
354 for pre_cmd in self.pre_make_cmd:
355 subprocess.check_output(
356 pre_cmd, env=my_environment, shell=True,
357 cwd=git_worktree_path, stderr=subprocess.STDOUT,
358 universal_newlines=True
359 )
Yanray Wange0e27602023-07-14 17:37:45 +0800360 subprocess.check_output(
361 self.make_clean, env=my_environment, shell=True,
362 cwd=git_worktree_path, stderr=subprocess.STDOUT,
Yanray Wang386c2f92023-07-20 15:32:15 +0800363 universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800364 )
365 subprocess.check_output(
366 self.make_cmd, env=my_environment, shell=True,
367 cwd=git_worktree_path, stderr=subprocess.STDOUT,
Yanray Wang386c2f92023-07-20 15:32:15 +0800368 universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800369 )
Yanray Wang5605c6f2023-07-21 16:09:00 +0800370 if self.git_rev == 'current':
371 self.backup_config_files(restore=True)
Yanray Wange0e27602023-07-14 17:37:45 +0800372 except subprocess.CalledProcessError as e:
373 self._handle_called_process_error(e, git_worktree_path)
374
Yanray Wang386c2f92023-07-20 15:32:15 +0800375 def _gen_raw_code_size(self, git_worktree_path: str) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800376 """Measure code size by a tool and return in UTF-8 encoding."""
Yanray Wang21127f72023-07-19 12:09:45 +0800377
378 self.logger.debug("Measuring code size for {} by `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800379 .format(self.git_rev,
Yanray Wang21127f72023-07-19 12:09:45 +0800380 self.measure_cmd.strip().split(' ')[0]))
Yanray Wange0e27602023-07-14 17:37:45 +0800381
382 res = {}
383 for mod, st_lib in MBEDTLS_STATIC_LIB.items():
384 try:
385 result = subprocess.check_output(
Yanray Wang802af162023-07-17 14:04:30 +0800386 [self.measure_cmd + ' ' + st_lib], cwd=git_worktree_path,
387 shell=True, universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800388 )
389 res[mod] = result
390 except subprocess.CalledProcessError as e:
391 self._handle_called_process_error(e, git_worktree_path)
392
393 return res
394
395 def _remove_worktree(self, git_worktree_path: str) -> None:
396 """Remove temporary worktree."""
397 if git_worktree_path != self.repo_path:
Yanray Wang21127f72023-07-19 12:09:45 +0800398 self.logger.debug("Removing temporary worktree {}."
399 .format(git_worktree_path))
Yanray Wange0e27602023-07-14 17:37:45 +0800400 subprocess.check_output(
401 [self.git_command, "worktree", "remove", "--force",
402 git_worktree_path], cwd=self.repo_path,
403 stderr=subprocess.STDOUT
404 )
405
406 def _handle_called_process_error(self, e: subprocess.CalledProcessError,
407 git_worktree_path: str) -> None:
408 """Handle a CalledProcessError and quit the program gracefully.
409 Remove any extra worktrees so that the script may be called again."""
410
411 # Tell the user what went wrong
Yanray Wang21127f72023-07-19 12:09:45 +0800412 self.logger.error(e, exc_info=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800413 self.logger.error("Process output:\n {}".format(e.output))
Yanray Wange0e27602023-07-14 17:37:45 +0800414
415 # Quit gracefully by removing the existing worktree
416 self._remove_worktree(git_worktree_path)
417 sys.exit(-1)
418
Yanray Wang386c2f92023-07-20 15:32:15 +0800419 def cal_libraries_code_size(self) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800420 """Do a complete round to calculate code size of library/*.o
421 by measurement tool.
422
423 :return A dictionary of measured code size
424 - typing.Dict[mod: str]
425 """
Yanray Wange0e27602023-07-14 17:37:45 +0800426
Yanray Wang21127f72023-07-19 12:09:45 +0800427 git_worktree_path = self._create_git_worktree()
Yanray Wange0e27602023-07-14 17:37:45 +0800428 self._build_libraries(git_worktree_path)
Yanray Wang21127f72023-07-19 12:09:45 +0800429 res = self._gen_raw_code_size(git_worktree_path)
Yanray Wange0e27602023-07-14 17:37:45 +0800430 self._remove_worktree(git_worktree_path)
431
432 return res
433
434
Yanray Wang15c43f32023-07-17 11:17:12 +0800435class CodeSizeGenerator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800436 """ A generator based on size measurement tool for library/*.o.
Yanray Wang15c43f32023-07-17 11:17:12 +0800437
438 This is an abstract class. To use it, derive a class that implements
Yanray Wang95059002023-07-24 12:29:22 +0800439 write_record and write_comparison methods, then call both of them with
440 proper arguments.
Yanray Wang15c43f32023-07-17 11:17:12 +0800441 """
Yanray Wang21127f72023-07-19 12:09:45 +0800442 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800443 """
444 :param logger: logging module
445 """
Yanray Wang21127f72023-07-19 12:09:45 +0800446 self.logger = logger
447
Yanray Wang95059002023-07-24 12:29:22 +0800448 def write_record(
Yanray Wang15c43f32023-07-17 11:17:12 +0800449 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800450 git_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800451 code_size_text: typing.Dict[str, str],
452 output: typing_util.Writable
Yanray Wang15c43f32023-07-17 11:17:12 +0800453 ) -> None:
454 """Write size record into a file.
455
Yanray Wang955671b2023-07-21 12:08:27 +0800456 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800457 :param code_size_text:
458 string output (utf-8) from measurement tool of code size.
459 - typing.Dict[mod: str]
Yanray Wang95059002023-07-24 12:29:22 +0800460 :param output: output stream which the code size record is written to.
461 (Note: Normally write code size record into File)
Yanray Wang15c43f32023-07-17 11:17:12 +0800462 """
463 raise NotImplementedError
464
Yanray Wang95059002023-07-24 12:29:22 +0800465 def write_comparison(
Yanray Wang15c43f32023-07-17 11:17:12 +0800466 self,
467 old_rev: str,
468 new_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800469 output: typing_util.Writable,
470 with_markdown=False
Yanray Wang15c43f32023-07-17 11:17:12 +0800471 ) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800472 """Write a comparision result into a stream between two Git revisions.
Yanray Wang15c43f32023-07-17 11:17:12 +0800473
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800474 :param old_rev: old Git revision to compared with.
475 :param new_rev: new Git revision to compared with.
Yanray Wang95059002023-07-24 12:29:22 +0800476 :param output: output stream which the code size record is written to.
477 (File / sys.stdout)
478 :param with_markdown: write comparision result in a markdown table.
479 (Default: False)
Yanray Wang15c43f32023-07-17 11:17:12 +0800480 """
481 raise NotImplementedError
482
483
484class CodeSizeGeneratorWithSize(CodeSizeGenerator):
Yanray Wang16ebc572023-05-30 18:10:20 +0800485 """Code Size Base Class for size record saving and writing."""
486
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800487 class SizeEntry: # pylint: disable=too-few-public-methods
488 """Data Structure to only store information of code size."""
489 def __init__(self, text, data, bss, dec):
490 self.text = text
491 self.data = data
492 self.bss = bss
493 self.total = dec # total <=> dec
494
Yanray Wang21127f72023-07-19 12:09:45 +0800495 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800496 """ Variable code_size is used to store size info for any Git revisions.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800497 :param code_size:
498 Data Format as following:
Yanray Wang955671b2023-07-21 12:08:27 +0800499 {git_rev: {module: {file_name: [text, data, bss, dec],
500 etc ...
501 },
502 etc ...
503 },
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800504 etc ...
505 }
Yanray Wang16ebc572023-05-30 18:10:20 +0800506 """
Yanray Wang21127f72023-07-19 12:09:45 +0800507 super().__init__(logger)
Yanray Wang16ebc572023-05-30 18:10:20 +0800508 self.code_size = {} #type: typing.Dict[str, typing.Dict]
509
Yanray Wang955671b2023-07-21 12:08:27 +0800510 def _set_size_record(self, git_rev: str, mod: str, size_text: str) -> None:
511 """Store size information for target Git revision and high-level module.
Yanray Wang16ebc572023-05-30 18:10:20 +0800512
513 size_text Format: text data bss dec hex filename
514 """
515 size_record = {}
516 for line in size_text.splitlines()[1:]:
517 data = line.split()
Yanray Wang9b174e92023-07-17 17:59:53 +0800518 # file_name: SizeEntry(text, data, bss, dec)
519 size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry(
520 data[0], data[1], data[2], data[3])
Yanray Wang955671b2023-07-21 12:08:27 +0800521 if git_rev in self.code_size:
522 self.code_size[git_rev].update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800523 else:
Yanray Wang955671b2023-07-21 12:08:27 +0800524 self.code_size[git_rev] = {mod: size_record}
Yanray Wang16ebc572023-05-30 18:10:20 +0800525
Yanray Wang955671b2023-07-21 12:08:27 +0800526 def read_size_record(self, git_rev: str, fname: str) -> None:
Yanray Wang16ebc572023-05-30 18:10:20 +0800527 """Read size information from csv file and write it into code_size.
528
529 fname Format: filename text data bss dec
530 """
531 mod = ""
532 size_record = {}
533 with open(fname, 'r') as csv_file:
534 for line in csv_file:
535 data = line.strip().split()
536 # check if we find the beginning of a module
537 if data and data[0] in MBEDTLS_STATIC_LIB:
538 mod = data[0]
539 continue
540
541 if mod:
Yanray Wang9b174e92023-07-17 17:59:53 +0800542 # file_name: SizeEntry(text, data, bss, dec)
543 size_record[data[0]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800544 data[1], data[2], data[3], data[4])
Yanray Wang16ebc572023-05-30 18:10:20 +0800545
546 # check if we hit record for the end of a module
547 m = re.match(r'.?TOTALS', line)
548 if m:
Yanray Wang955671b2023-07-21 12:08:27 +0800549 if git_rev in self.code_size:
550 self.code_size[git_rev].update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800551 else:
Yanray Wang955671b2023-07-21 12:08:27 +0800552 self.code_size[git_rev] = {mod: size_record}
Yanray Wang16ebc572023-05-30 18:10:20 +0800553 mod = ""
554 size_record = {}
555
556 def _size_reader_helper(
557 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800558 git_rev: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800559 output: typing_util.Writable,
560 with_markdown=False
Yanray Wang16ebc572023-05-30 18:10:20 +0800561 ) -> typing.Iterator[tuple]:
Yanray Wang955671b2023-07-21 12:08:27 +0800562 """A helper function to peel code_size based on Git revision."""
563 for mod, file_size in self.code_size[git_rev].items():
Yanray Wangb664cb72023-07-18 12:28:35 +0800564 if not with_markdown:
565 output.write("\n" + mod + "\n")
Yanray Wang16ebc572023-05-30 18:10:20 +0800566 for fname, size_entry in file_size.items():
567 yield mod, fname, size_entry
568
Yanray Wang95059002023-07-24 12:29:22 +0800569 def write_record(
Yanray Wang16ebc572023-05-30 18:10:20 +0800570 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800571 git_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800572 code_size_text: typing.Dict[str, str],
Yanray Wang16ebc572023-05-30 18:10:20 +0800573 output: typing_util.Writable
574 ) -> None:
575 """Write size information to a file.
576
577 Writing Format: file_name text data bss total(dec)
578 """
Yanray Wang95059002023-07-24 12:29:22 +0800579 for mod, size_text in code_size_text.items():
580 self._set_size_record(git_rev, mod, size_text)
581
Yanray Wangb664cb72023-07-18 12:28:35 +0800582 format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n"
583 output.write(format_string.format("filename",
584 "text", "data", "bss", "total"))
Yanray Wang955671b2023-07-21 12:08:27 +0800585 for _, fname, size_entry in self._size_reader_helper(git_rev, output):
Yanray Wangb664cb72023-07-18 12:28:35 +0800586 output.write(format_string.format(fname,
587 size_entry.text, size_entry.data,
588 size_entry.bss, size_entry.total))
Yanray Wang16ebc572023-05-30 18:10:20 +0800589
Yanray Wang95059002023-07-24 12:29:22 +0800590 def write_comparison(
Yanray Wang16ebc572023-05-30 18:10:20 +0800591 self,
592 old_rev: str,
593 new_rev: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800594 output: typing_util.Writable,
Yanray Wang95059002023-07-24 12:29:22 +0800595 with_markdown=False
Yanray Wang16ebc572023-05-30 18:10:20 +0800596 ) -> None:
597 """Write comparison result into a file.
598
Yanray Wang9b174e92023-07-17 17:59:53 +0800599 Writing Format: file_name current(text,data) old(text,data)\
600 change(text,data) change_pct%(text,data)
Yanray Wang16ebc572023-05-30 18:10:20 +0800601 """
Yanray Wang9b174e92023-07-17 17:59:53 +0800602
603 def cal_size_section_variation(mod, fname, size_entry, attr):
604 new_size = int(size_entry.__dict__[attr])
Yanray Wang955671b2023-07-21 12:08:27 +0800605 # check if we have the file in old Git revision
Yanray Wang16ebc572023-05-30 18:10:20 +0800606 if fname in self.code_size[old_rev][mod]:
Yanray Wang9b174e92023-07-17 17:59:53 +0800607 old_size = int(self.code_size[old_rev][mod][fname].__dict__[attr])
Yanray Wang16ebc572023-05-30 18:10:20 +0800608 change = new_size - old_size
609 if old_size != 0:
610 change_pct = change / old_size
611 else:
612 change_pct = 0
Yanray Wang9b174e92023-07-17 17:59:53 +0800613 return [new_size, old_size, change, change_pct]
Yanray Wang16ebc572023-05-30 18:10:20 +0800614 else:
Yanray Wang9b174e92023-07-17 17:59:53 +0800615 return [new_size]
616
Yanray Wangb664cb72023-07-18 12:28:35 +0800617 if with_markdown:
618 format_string = "| {:<30} | {:<18} | {:<14} | {:<17} | {:<18} |\n"
619 else:
620 format_string = "{:<30} {:<18} {:<14} {:<17} {:<18}\n"
621
Yanray Wang386c2f92023-07-20 15:32:15 +0800622 output.write(format_string
623 .format("filename",
624 "current(text,data)", "old(text,data)",
625 "change(text,data)", "change%(text,data)"))
Yanray Wangb664cb72023-07-18 12:28:35 +0800626 if with_markdown:
627 output.write(format_string
628 .format("----:", "----:", "----:", "----:", "----:"))
629
Yanray Wang386c2f92023-07-20 15:32:15 +0800630 for mod, fname, size_entry in \
Yanray Wangb664cb72023-07-18 12:28:35 +0800631 self._size_reader_helper(new_rev, output, with_markdown):
632 text_vari = cal_size_section_variation(mod, fname,
633 size_entry, 'text')
634 data_vari = cal_size_section_variation(mod, fname,
635 size_entry, 'data')
Yanray Wang9b174e92023-07-17 17:59:53 +0800636
637 if len(text_vari) != 1:
Yanray Wangb664cb72023-07-18 12:28:35 +0800638 # skip the files that haven't changed in code size if we write
639 # comparison result in a markdown table.
640 if with_markdown and text_vari[2] == 0 and data_vari[2] == 0:
641 continue
Yanray Wang386c2f92023-07-20 15:32:15 +0800642 output.write(
643 format_string
644 .format(fname,
Yanray Wange4a36362023-07-25 10:37:11 +0800645 # current(text,data)
Yanray Wang386c2f92023-07-20 15:32:15 +0800646 str(text_vari[0]) + "," + str(data_vari[0]),
Yanray Wange4a36362023-07-25 10:37:11 +0800647 # old(text,data)
Yanray Wang386c2f92023-07-20 15:32:15 +0800648 str(text_vari[1]) + "," + str(data_vari[1]),
Yanray Wange4a36362023-07-25 10:37:11 +0800649 # change(text,data)
Yanray Wang386c2f92023-07-20 15:32:15 +0800650 str(text_vari[2]) + "," + str(data_vari[2]),
Yanray Wange4a36362023-07-25 10:37:11 +0800651 # change%(text,data)
Yanray Wang25bd3312023-07-25 10:24:20 +0800652 "{:.0%}".format(text_vari[3]) + ","
653 + "{:.0%}".format(data_vari[3])))
Yanray Wang9b174e92023-07-17 17:59:53 +0800654 else:
Yanray Wangf2cd7172023-07-24 16:56:46 +0800655 output.write(
656 format_string
657 .format(fname,
Yanray Wange4a36362023-07-25 10:37:11 +0800658 # current(text,data)
Yanray Wangf2cd7172023-07-24 16:56:46 +0800659 str(text_vari[0]) + "," + str(data_vari[0]),
660 'None', 'None', 'None'))
Yanray Wang16ebc572023-05-30 18:10:20 +0800661
662
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800663class CodeSizeComparison:
Xiaofei Bai2400b502021-10-21 12:22:58 +0000664 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000665
Yanray Wang955671b2023-07-21 12:08:27 +0800666 def __init__( #pylint: disable=too-many-arguments
Yanray Wang72b105f2023-05-31 15:20:39 +0800667 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800668 old_size_dist_info: CodeSizeDistinctInfo,
669 new_size_dist_info: CodeSizeDistinctInfo,
670 size_common_info: CodeSizeCommonInfo,
671 result_options: CodeSizeResultInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800672 logger: logging.Logger,
Yanray Wang72b105f2023-05-31 15:20:39 +0800673 ) -> None:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000674 """
Yanray Wang955671b2023-07-21 12:08:27 +0800675 :param old_size_dist_info: CodeSizeDistinctInfo containing old distinct
676 info to compare code size with.
677 :param new_size_dist_info: CodeSizeDistinctInfo containing new distinct
678 info to take as comparision base.
679 :param size_common_info: CodeSizeCommonInfo containing common info for
680 both old and new size distinct info and
681 measurement tool.
682 :param result_options: CodeSizeResultInfo containing results options for
683 code size record and comparision.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800684 :param logger: logging module
Xiaofei Baibca03e52021-09-09 09:42:37 +0000685 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000686
Yanray Wang21127f72023-07-19 12:09:45 +0800687 self.logger = logger
688
Yanray Wang955671b2023-07-21 12:08:27 +0800689 self.old_size_dist_info = old_size_dist_info
690 self.new_size_dist_info = new_size_dist_info
691 self.size_common_info = size_common_info
Yanray Wang5605c6f2023-07-21 16:09:00 +0800692 # infer pre make command
693 self.old_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
694 self.old_size_dist_info, self.size_common_info.host_arch,
695 self.logger).infer_pre_make_command()
696 self.new_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
697 self.new_size_dist_info, self.size_common_info.host_arch,
698 self.logger).infer_pre_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800699 # infer make command
Yanray Wang955671b2023-07-21 12:08:27 +0800700 self.old_size_dist_info.make_cmd = CodeSizeBuildInfo(
701 self.old_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800702 self.logger).infer_make_command()
Yanray Wang955671b2023-07-21 12:08:27 +0800703 self.new_size_dist_info.make_cmd = CodeSizeBuildInfo(
704 self.new_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800705 self.logger).infer_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800706 # initialize size parser with corresponding measurement tool
Yanray Wang21127f72023-07-19 12:09:45 +0800707 self.code_size_generator = self.__generate_size_parser()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000708
Yanray Wang955671b2023-07-21 12:08:27 +0800709 self.result_options = result_options
710 self.csv_dir = os.path.abspath(self.result_options.record_dir)
711 os.makedirs(self.csv_dir, exist_ok=True)
712 self.comp_dir = os.path.abspath(self.result_options.comp_dir)
713 os.makedirs(self.comp_dir, exist_ok=True)
714
Yanray Wang21127f72023-07-19 12:09:45 +0800715 def __generate_size_parser(self):
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800716 """Generate a parser for the corresponding measurement tool."""
Yanray Wang955671b2023-07-21 12:08:27 +0800717 if re.match(r'size', self.size_common_info.measure_cmd.strip()):
Yanray Wang21127f72023-07-19 12:09:45 +0800718 return CodeSizeGeneratorWithSize(self.logger)
Yanray Wang802af162023-07-17 14:04:30 +0800719 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800720 self.logger.error("Unsupported measurement tool: `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800721 .format(self.size_common_info.measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800722 .strip().split(' ')[0]))
Yanray Wang802af162023-07-17 14:04:30 +0800723 sys.exit(1)
724
Yanray Wang386c2f92023-07-20 15:32:15 +0800725 def cal_code_size(
726 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800727 size_dist_info: CodeSizeDistinctInfo
Yanray Wang386c2f92023-07-20 15:32:15 +0800728 ) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800729 """Calculate code size of library/*.o in a UTF-8 encoding"""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000730
Yanray Wang955671b2023-07-21 12:08:27 +0800731 return CodeSizeCalculator(size_dist_info.git_rev,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800732 size_dist_info.pre_make_cmd,
Yanray Wang955671b2023-07-21 12:08:27 +0800733 size_dist_info.make_cmd,
734 self.size_common_info.measure_cmd,
Yanray Wang21127f72023-07-19 12:09:45 +0800735 self.logger).cal_libraries_code_size()
Yanray Wang8804db92023-05-30 18:18:18 +0800736
Yanray Wang955671b2023-07-21 12:08:27 +0800737 def gen_code_size_report(self, size_dist_info: CodeSizeDistinctInfo) -> None:
Yanray Wang5e9130a2023-07-17 11:55:54 +0800738 """Generate code size record and write it into a file."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000739
Yanray Wang21127f72023-07-19 12:09:45 +0800740 self.logger.info("Start to generate code size record for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800741 .format(size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800742 output_file = os.path.join(
743 self.csv_dir,
744 '{}-{}.csv'
745 .format(size_dist_info.get_info_indication(),
746 self.size_common_info.get_info_indication()))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000747 # Check if the corresponding record exists
Yanray Wang955671b2023-07-21 12:08:27 +0800748 if size_dist_info.git_rev != "current" and \
Yanray Wang21127f72023-07-19 12:09:45 +0800749 os.path.exists(output_file):
750 self.logger.debug("Code size csv file for {} already exists."
Yanray Wang955671b2023-07-21 12:08:27 +0800751 .format(size_dist_info.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800752 self.code_size_generator.read_size_record(
Yanray Wang955671b2023-07-21 12:08:27 +0800753 size_dist_info.git_rev, output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000754 else:
Yanray Wang95059002023-07-24 12:29:22 +0800755 # measure code size
756 code_size_text = self.cal_code_size(size_dist_info)
757
758 self.logger.debug("Generating code size csv for {}."
759 .format(size_dist_info.git_rev))
760 output = open(output_file, "w")
761 self.code_size_generator.write_record(
762 size_dist_info.git_rev, code_size_text, output)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000763
Yanray Wang386c2f92023-07-20 15:32:15 +0800764 def gen_code_size_comparison(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800765 """Generate results of code size changes between two Git revisions,
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800766 old and new.
767
Yanray Wang955671b2023-07-21 12:08:27 +0800768 - Measured code size result of these two Git revisions must be available.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800769 - The result is directed into either file / stdout depending on
Yanray Wang955671b2023-07-21 12:08:27 +0800770 the option, size_common_info.result_options.stdout. (Default: file)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800771 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000772
Yanray Wang21127f72023-07-19 12:09:45 +0800773 self.logger.info("Start to generate comparision result between "\
774 "{} and {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800775 .format(self.old_size_dist_info.git_rev,
776 self.new_size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800777 if self.result_options.stdout:
778 output = sys.stdout
Yanray Wang69262fc2023-07-24 16:36:40 +0800779 print("Measure code size between `{}` and `{}` by `{}`."
780 .format(self.old_size_dist_info.get_info_indication(),
781 self.new_size_dist_info.get_info_indication(),
782 self.size_common_info.get_info_indication()))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800783 else:
784 output_file = os.path.join(
785 self.comp_dir,
786 '{}-{}-{}.csv'
787 .format(self.old_size_dist_info.get_info_indication(),
788 self.new_size_dist_info.get_info_indication(),
789 self.size_common_info.get_info_indication()))
790 output = open(output_file, "w")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000791
Yanray Wang95059002023-07-24 12:29:22 +0800792 self.logger.debug("Generating comparison results between {} and {}."
793 .format(self.old_size_dist_info.git_rev,
794 self.new_size_dist_info.git_rev))
Yanray Wang95059002023-07-24 12:29:22 +0800795 self.code_size_generator.write_comparison(
Yanray Wang955671b2023-07-21 12:08:27 +0800796 self.old_size_dist_info.git_rev,
797 self.new_size_dist_info.git_rev,
Yanray Wang95059002023-07-24 12:29:22 +0800798 output, self.result_options.with_markdown)
Yanray Wang21127f72023-07-19 12:09:45 +0800799
Yanray Wang386c2f92023-07-20 15:32:15 +0800800 def get_comparision_results(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800801 """Compare size of library/*.o between self.old_size_dist_info and
802 self.old_size_dist_info and generate the result file."""
Gilles Peskined9071e72022-09-18 21:17:09 +0200803 build_tree.check_repo_path()
Yanray Wang955671b2023-07-21 12:08:27 +0800804 self.gen_code_size_report(self.old_size_dist_info)
805 self.gen_code_size_report(self.new_size_dist_info)
Yanray Wang386c2f92023-07-20 15:32:15 +0800806 self.gen_code_size_comparison()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000807
Xiaofei Bai2400b502021-10-21 12:22:58 +0000808def main():
Yanray Wang502c54f2023-05-31 11:41:36 +0800809 parser = argparse.ArgumentParser(description=(__doc__))
810 group_required = parser.add_argument_group(
811 'required arguments',
812 'required arguments to parse for running ' + os.path.basename(__file__))
813 group_required.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800814 '-o', '--old-rev', type=str, required=True,
Yanray Wang955671b2023-07-21 12:08:27 +0800815 help='old Git revision for comparison.')
Yanray Wang502c54f2023-05-31 11:41:36 +0800816
817 group_optional = parser.add_argument_group(
818 'optional arguments',
819 'optional arguments to parse for running ' + os.path.basename(__file__))
820 group_optional.add_argument(
Yanray Wang955671b2023-07-21 12:08:27 +0800821 '--record_dir', type=str, default='code_size_records',
822 help='directory where code size record is stored. '
823 '(Default: code_size_records)')
824 group_optional.add_argument(
825 '-r', '--comp-dir', type=str, default='comparison',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800826 help='directory where comparison result is stored. '
827 '(Default: comparison)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800828 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800829 '-n', '--new-rev', type=str, default=None,
Yanray Wang955671b2023-07-21 12:08:27 +0800830 help='new Git revision as comparison base. '
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800831 '(Default is the current work directory, including uncommitted '
832 'changes.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800833 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800834 '-a', '--arch', type=str, default=detect_arch(),
Yanray Wang23bd5322023-05-24 11:03:59 +0800835 choices=list(map(lambda s: s.value, SupportedArch)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800836 help='Specify architecture for code size comparison. '
837 '(Default is the host architecture.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800838 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800839 '-c', '--config', type=str, default=SupportedConfig.DEFAULT.value,
Yanray Wang6a862582023-05-24 12:24:38 +0800840 choices=list(map(lambda s: s.value, SupportedConfig)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800841 help='Specify configuration type for code size comparison. '
842 '(Default is the current MbedTLS configuration.)')
Yanray Wangb664cb72023-07-18 12:28:35 +0800843 group_optional.add_argument(
844 '--markdown', action='store_true', dest='markdown',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800845 help='Show comparision of code size in a markdown table. '
846 '(Only show the files that have changed).')
Yanray Wang227576a2023-07-18 14:35:05 +0800847 group_optional.add_argument(
848 '--stdout', action='store_true', dest='stdout',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800849 help='Set this option to direct comparison result into sys.stdout. '
850 '(Default: file)')
Yanray Wang21127f72023-07-19 12:09:45 +0800851 group_optional.add_argument(
852 '--verbose', action='store_true', dest='verbose',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800853 help='Show logs in detail for code size measurement. '
854 '(Default: False)')
Xiaofei Baibca03e52021-09-09 09:42:37 +0000855 comp_args = parser.parse_args()
856
Yanray Wang21127f72023-07-19 12:09:45 +0800857 logger = logging.getLogger()
858 logging_util.configure_logger(logger)
859 logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO)
860
Yanray Wang955671b2023-07-21 12:08:27 +0800861 if os.path.isfile(comp_args.comp_dir):
862 logger.error("{} is not a directory".format(comp_args.comp_dir))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000863 parser.exit()
864
Yanray Wang955671b2023-07-21 12:08:27 +0800865 old_revision = CodeSizeCalculator.validate_git_revision(comp_args.old_rev)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000866 if comp_args.new_rev is not None:
Yanray Wang955671b2023-07-21 12:08:27 +0800867 new_revision = CodeSizeCalculator.validate_git_revision(
868 comp_args.new_rev)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000869 else:
Yanray Wang5605c6f2023-07-21 16:09:00 +0800870 new_revision = 'current'
Xiaofei Bai2400b502021-10-21 12:22:58 +0000871
Yanray Wang5605c6f2023-07-21 16:09:00 +0800872 # version, git_rev, arch, config, compiler, opt_level
Yanray Wang955671b2023-07-21 12:08:27 +0800873 old_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang5605c6f2023-07-21 16:09:00 +0800874 'old', old_revision, comp_args.arch, comp_args.config, 'cc', '-Os')
Yanray Wang955671b2023-07-21 12:08:27 +0800875 new_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang5605c6f2023-07-21 16:09:00 +0800876 'new', new_revision, comp_args.arch, comp_args.config, 'cc', '-Os')
877 # host_arch, measure_cmd
Yanray Wang955671b2023-07-21 12:08:27 +0800878 size_common_info = CodeSizeCommonInfo(
879 detect_arch(), 'size -t')
Yanray Wang5605c6f2023-07-21 16:09:00 +0800880 # record_dir, comp_dir, with_markdown, stdout
Yanray Wang955671b2023-07-21 12:08:27 +0800881 result_options = CodeSizeResultInfo(
882 comp_args.record_dir, comp_args.comp_dir,
883 comp_args.markdown, comp_args.stdout)
Yanray Wang923f9432023-07-17 12:43:00 +0800884
Yanray Wanga6cf6922023-07-24 15:20:42 +0800885 logger.info("Measure code size between {} and {} by `{}`."
886 .format(old_size_dist_info.get_info_indication(),
887 new_size_dist_info.get_info_indication(),
888 size_common_info.get_info_indication()))
Yanray Wang955671b2023-07-21 12:08:27 +0800889 CodeSizeComparison(old_size_dist_info, new_size_dist_info,
890 size_common_info, result_options,
891 logger).get_comparision_results()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000892
Xiaofei Baibca03e52021-09-09 09:42:37 +0000893if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000894 main()