blob: b886a9e99055067579bc41c779dc412aeb65aec3 [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."""
95 return '{}-{}-{}-{}'\
96 .format(self.git_rev, self.arch, self.config, self.compiler)
97
Yanray Wang955671b2023-07-21 12:08:27 +080098
99class CodeSizeCommonInfo: # pylint: disable=too-few-public-methods
100 """Data structure to store common information for code size comparison."""
101 def __init__(
102 self,
103 host_arch: str,
104 measure_cmd: str,
105 ) -> None:
106 """
107 :param host_arch: host architecture.
108 :param measure_cmd: command to measure code size for library/*.o.
109 """
110 self.host_arch = host_arch
111 self.measure_cmd = measure_cmd
112
Yanray Wanga6cf6922023-07-24 15:20:42 +0800113 def get_info_indication(self):
114 """Return a unique string to indicate Code Size Common Information."""
115 return '{}'\
116 .format(self.measure_cmd.strip().split(' ')[0])
Yanray Wang955671b2023-07-21 12:08:27 +0800117
118class CodeSizeResultInfo: # pylint: disable=too-few-public-methods
119 """Data structure to store result options for code size comparison."""
120 def __init__(
121 self,
122 record_dir: str,
123 comp_dir: str,
124 with_markdown=False,
125 stdout=False,
126 ) -> 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)
134 """
135 self.record_dir = record_dir
136 self.comp_dir = comp_dir
137 self.with_markdown = with_markdown
138 self.stdout = stdout
139
140
Yanray Wang23bd5322023-05-24 11:03:59 +0800141DETECT_ARCH_CMD = "cc -dM -E - < /dev/null"
142def detect_arch() -> str:
143 """Auto-detect host architecture."""
144 cc_output = subprocess.check_output(DETECT_ARCH_CMD, shell=True).decode()
Yanray Wang386c2f92023-07-20 15:32:15 +0800145 if '__aarch64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800146 return SupportedArch.AARCH64.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800147 if '__arm__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800148 return SupportedArch.AARCH32.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800149 if '__x86_64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800150 return SupportedArch.X86_64.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800151 if '__x86__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800152 return SupportedArch.X86.value
153 else:
154 print("Unknown host architecture, cannot auto-detect arch.")
155 sys.exit(1)
Gilles Peskined9071e72022-09-18 21:17:09 +0200156
Yanray Wang5605c6f2023-07-21 16:09:00 +0800157TFM_MEDIUM_CONFIG_H = 'configs/tfm_mbedcrypto_config_profile_medium.h'
158TFM_MEDIUM_CRYPTO_CONFIG_H = 'configs/crypto_config_profile_medium.h'
159
160CONFIG_H = 'include/mbedtls/mbedtls_config.h'
161CRYPTO_CONFIG_H = 'include/psa/crypto_config.h'
162BACKUP_SUFFIX = '.code_size.bak'
163
Yanray Wang923f9432023-07-17 12:43:00 +0800164class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods
Yanray Wang6a862582023-05-24 12:24:38 +0800165 """Gather information used to measure code size.
166
167 It collects information about architecture, configuration in order to
168 infer build command for code size measurement.
169 """
170
Yanray Wangc18cd892023-05-31 11:08:04 +0800171 SupportedArchConfig = [
Yanray Wang386c2f92023-07-20 15:32:15 +0800172 '-a ' + SupportedArch.AARCH64.value + ' -c ' + SupportedConfig.DEFAULT.value,
173 '-a ' + SupportedArch.AARCH32.value + ' -c ' + SupportedConfig.DEFAULT.value,
174 '-a ' + SupportedArch.X86_64.value + ' -c ' + SupportedConfig.DEFAULT.value,
175 '-a ' + SupportedArch.X86.value + ' -c ' + SupportedConfig.DEFAULT.value,
176 '-a ' + SupportedArch.ARMV8_M.value + ' -c ' + SupportedConfig.TFM_MEDIUM.value,
Yanray Wangc18cd892023-05-31 11:08:04 +0800177 ]
178
Yanray Wang802af162023-07-17 14:04:30 +0800179 def __init__(
180 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800181 size_dist_info: CodeSizeDistinctInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800182 host_arch: str,
183 logger: logging.Logger,
Yanray Wang802af162023-07-17 14:04:30 +0800184 ) -> None:
Yanray Wang6a862582023-05-24 12:24:38 +0800185 """
Yanray Wang955671b2023-07-21 12:08:27 +0800186 :param size_dist_info:
187 CodeSizeDistinctInfo containing info for code size measurement.
188 - size_dist_info.arch: architecture to measure code size on.
189 - size_dist_info.config: configuration type to measure
190 code size with.
Yanray Wang5605c6f2023-07-21 16:09:00 +0800191 - size_dist_info.compiler: compiler used to build library/*.o.
192 - size_dist_info.opt_level: Options that control optimization.
193 (E.g. -Os)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800194 :param host_arch: host architecture.
195 :param logger: logging module
Yanray Wang6a862582023-05-24 12:24:38 +0800196 """
Yanray Wang5605c6f2023-07-21 16:09:00 +0800197 self.arch = size_dist_info.arch
198 self.config = size_dist_info.config
199 self.compiler = size_dist_info.compiler
200 self.opt_level = size_dist_info.opt_level
201
202 self.make_cmd = ['make', '-j', 'lib']
203
Yanray Wang802af162023-07-17 14:04:30 +0800204 self.host_arch = host_arch
Yanray Wang21127f72023-07-19 12:09:45 +0800205 self.logger = logger
Yanray Wang6a862582023-05-24 12:24:38 +0800206
Yanray Wang5605c6f2023-07-21 16:09:00 +0800207 def check_correctness(self) -> bool:
208 """Check whether we are using proper / supported combination
209 of information to build library/*.o."""
Yanray Wang6a862582023-05-24 12:24:38 +0800210
Yanray Wang5605c6f2023-07-21 16:09:00 +0800211 # default config
212 if self.config == SupportedConfig.DEFAULT.value and \
213 self.arch == self.host_arch:
214 return True
215 # TF-M
216 elif self.arch == SupportedArch.ARMV8_M.value and \
217 self.config == SupportedConfig.TFM_MEDIUM.value:
218 return True
219
220 return False
221
222 def infer_pre_make_command(self) -> typing.List[str]:
223 """Infer command to set up proper configuration before running make."""
224 pre_make_cmd = [] #type: typing.List[str]
225 if self.config == SupportedConfig.TFM_MEDIUM.value:
226 pre_make_cmd.append('cp -r {} {}'
227 .format(TFM_MEDIUM_CONFIG_H, CONFIG_H))
228 pre_make_cmd.append('cp -r {} {}'
229 .format(TFM_MEDIUM_CRYPTO_CONFIG_H,
230 CRYPTO_CONFIG_H))
231
232 return pre_make_cmd
233
234 def infer_make_cflags(self) -> str:
235 """Infer CFLAGS by instance attributes in CodeSizeDistinctInfo."""
236 cflags = [] #type: typing.List[str]
237
238 # set optimization level
239 cflags.append(self.opt_level)
240 # set compiler by config
241 if self.config == SupportedConfig.TFM_MEDIUM.value:
242 self.compiler = 'armclang'
243 cflags.append('-mcpu=cortex-m33')
244 # set target
245 if self.compiler == 'armclang':
246 cflags.append('--target=arm-arm-none-eabi')
247
248 return ' '.join(cflags)
249
250 def infer_make_command(self) -> str:
251 """Infer make command by CFLAGS and CC."""
252
253 if self.check_correctness():
254 # set CFLAGS=
255 self.make_cmd.append('CFLAGS=\'{}\''.format(self.infer_make_cflags()))
256 # set CC=
257 self.make_cmd.append('CC={}'.format(self.compiler))
258 return ' '.join(self.make_cmd)
Yanray Wang6a862582023-05-24 12:24:38 +0800259 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800260 self.logger.error("Unsupported combination of architecture: {} " \
261 "and configuration: {}.\n"
Yanray Wang5605c6f2023-07-21 16:09:00 +0800262 .format(self.arch,
263 self.config))
Yanray Wang21127f72023-07-19 12:09:45 +0800264 self.logger.info("Please use supported combination of " \
265 "architecture and configuration:")
Yanray Wang923f9432023-07-17 12:43:00 +0800266 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang21127f72023-07-19 12:09:45 +0800267 self.logger.info(comb)
268 self.logger.info("")
269 self.logger.info("For your system, please use:")
Yanray Wang923f9432023-07-17 12:43:00 +0800270 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang802af162023-07-17 14:04:30 +0800271 if "default" in comb and self.host_arch not in comb:
Yanray Wang21f17442023-06-01 11:29:06 +0800272 continue
Yanray Wang21127f72023-07-19 12:09:45 +0800273 self.logger.info(comb)
Yanray Wang6a862582023-05-24 12:24:38 +0800274 sys.exit(1)
275
276
Yanray Wange0e27602023-07-14 17:37:45 +0800277class CodeSizeCalculator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800278 """ A calculator to calculate code size of library/*.o based on
Yanray Wange0e27602023-07-14 17:37:45 +0800279 Git revision and code size measurement tool.
280 """
281
Yanray Wang5605c6f2023-07-21 16:09:00 +0800282 def __init__( #pylint: disable=too-many-arguments
Yanray Wange0e27602023-07-14 17:37:45 +0800283 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800284 git_rev: str,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800285 pre_make_cmd: typing.List[str],
Yanray Wange0e27602023-07-14 17:37:45 +0800286 make_cmd: str,
Yanray Wang21127f72023-07-19 12:09:45 +0800287 measure_cmd: str,
288 logger: logging.Logger,
Yanray Wange0e27602023-07-14 17:37:45 +0800289 ) -> None:
290 """
Yanray Wang955671b2023-07-21 12:08:27 +0800291 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5605c6f2023-07-21 16:09:00 +0800292 :param pre_make_cmd: command to set up proper config before running make.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800293 :param make_cmd: command to build library/*.o.
294 :param measure_cmd: command to measure code size for library/*.o.
295 :param logger: logging module
Yanray Wange0e27602023-07-14 17:37:45 +0800296 """
297 self.repo_path = "."
298 self.git_command = "git"
299 self.make_clean = 'make clean'
300
Yanray Wang955671b2023-07-21 12:08:27 +0800301 self.git_rev = git_rev
Yanray Wang5605c6f2023-07-21 16:09:00 +0800302 self.pre_make_cmd = pre_make_cmd
Yanray Wange0e27602023-07-14 17:37:45 +0800303 self.make_cmd = make_cmd
Yanray Wang802af162023-07-17 14:04:30 +0800304 self.measure_cmd = measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800305 self.logger = logger
Yanray Wange0e27602023-07-14 17:37:45 +0800306
307 @staticmethod
Yanray Wang955671b2023-07-21 12:08:27 +0800308 def validate_git_revision(git_rev: str) -> str:
Yanray Wange0e27602023-07-14 17:37:45 +0800309 result = subprocess.check_output(["git", "rev-parse", "--verify",
Yanray Wang955671b2023-07-21 12:08:27 +0800310 git_rev + "^{commit}"],
311 shell=False, universal_newlines=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800312 return result[:7]
Yanray Wange0e27602023-07-14 17:37:45 +0800313
Yanray Wang21127f72023-07-19 12:09:45 +0800314 def _create_git_worktree(self) -> str:
Yanray Wang955671b2023-07-21 12:08:27 +0800315 """Create a separate worktree for Git revision.
316 If Git revision is current, use current worktree instead."""
Yanray Wange0e27602023-07-14 17:37:45 +0800317
Yanray Wang5605c6f2023-07-21 16:09:00 +0800318 if self.git_rev == 'current':
Yanray Wang21127f72023-07-19 12:09:45 +0800319 self.logger.debug("Using current work directory.")
Yanray Wange0e27602023-07-14 17:37:45 +0800320 git_worktree_path = self.repo_path
321 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800322 self.logger.debug("Creating git worktree for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800323 .format(self.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800324 git_worktree_path = os.path.join(self.repo_path,
Yanray Wang955671b2023-07-21 12:08:27 +0800325 "temp-" + self.git_rev)
Yanray Wange0e27602023-07-14 17:37:45 +0800326 subprocess.check_output(
327 [self.git_command, "worktree", "add", "--detach",
Yanray Wang955671b2023-07-21 12:08:27 +0800328 git_worktree_path, self.git_rev], cwd=self.repo_path,
Yanray Wange0e27602023-07-14 17:37:45 +0800329 stderr=subprocess.STDOUT
330 )
331
332 return git_worktree_path
333
Yanray Wang5605c6f2023-07-21 16:09:00 +0800334 @staticmethod
335 def backup_config_files(restore: bool) -> None:
336 """Backup / Restore config files."""
337 if restore:
338 shutil.move(CONFIG_H + BACKUP_SUFFIX, CONFIG_H)
339 shutil.move(CRYPTO_CONFIG_H + BACKUP_SUFFIX, CRYPTO_CONFIG_H)
340 else:
341 shutil.copy(CONFIG_H, CONFIG_H + BACKUP_SUFFIX)
342 shutil.copy(CRYPTO_CONFIG_H, CRYPTO_CONFIG_H + BACKUP_SUFFIX)
343
Yanray Wange0e27602023-07-14 17:37:45 +0800344 def _build_libraries(self, git_worktree_path: str) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800345 """Build library/*.o in the specified worktree."""
Yanray Wange0e27602023-07-14 17:37:45 +0800346
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800347 self.logger.debug("Building library/*.o for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800348 .format(self.git_rev))
Yanray Wange0e27602023-07-14 17:37:45 +0800349 my_environment = os.environ.copy()
350 try:
Yanray Wang5605c6f2023-07-21 16:09:00 +0800351 if self.git_rev == 'current':
352 self.backup_config_files(restore=False)
353 for pre_cmd in self.pre_make_cmd:
354 subprocess.check_output(
355 pre_cmd, env=my_environment, shell=True,
356 cwd=git_worktree_path, stderr=subprocess.STDOUT,
357 universal_newlines=True
358 )
Yanray Wange0e27602023-07-14 17:37:45 +0800359 subprocess.check_output(
360 self.make_clean, env=my_environment, shell=True,
361 cwd=git_worktree_path, stderr=subprocess.STDOUT,
Yanray Wang386c2f92023-07-20 15:32:15 +0800362 universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800363 )
364 subprocess.check_output(
365 self.make_cmd, env=my_environment, shell=True,
366 cwd=git_worktree_path, stderr=subprocess.STDOUT,
Yanray Wang386c2f92023-07-20 15:32:15 +0800367 universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800368 )
Yanray Wang5605c6f2023-07-21 16:09:00 +0800369 if self.git_rev == 'current':
370 self.backup_config_files(restore=True)
Yanray Wange0e27602023-07-14 17:37:45 +0800371 except subprocess.CalledProcessError as e:
372 self._handle_called_process_error(e, git_worktree_path)
373
Yanray Wang386c2f92023-07-20 15:32:15 +0800374 def _gen_raw_code_size(self, git_worktree_path: str) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800375 """Measure code size by a tool and return in UTF-8 encoding."""
Yanray Wang21127f72023-07-19 12:09:45 +0800376
377 self.logger.debug("Measuring code size for {} by `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800378 .format(self.git_rev,
Yanray Wang21127f72023-07-19 12:09:45 +0800379 self.measure_cmd.strip().split(' ')[0]))
Yanray Wange0e27602023-07-14 17:37:45 +0800380
381 res = {}
382 for mod, st_lib in MBEDTLS_STATIC_LIB.items():
383 try:
384 result = subprocess.check_output(
Yanray Wang802af162023-07-17 14:04:30 +0800385 [self.measure_cmd + ' ' + st_lib], cwd=git_worktree_path,
386 shell=True, universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800387 )
388 res[mod] = result
389 except subprocess.CalledProcessError as e:
390 self._handle_called_process_error(e, git_worktree_path)
391
392 return res
393
394 def _remove_worktree(self, git_worktree_path: str) -> None:
395 """Remove temporary worktree."""
396 if git_worktree_path != self.repo_path:
Yanray Wang21127f72023-07-19 12:09:45 +0800397 self.logger.debug("Removing temporary worktree {}."
398 .format(git_worktree_path))
Yanray Wange0e27602023-07-14 17:37:45 +0800399 subprocess.check_output(
400 [self.git_command, "worktree", "remove", "--force",
401 git_worktree_path], cwd=self.repo_path,
402 stderr=subprocess.STDOUT
403 )
404
405 def _handle_called_process_error(self, e: subprocess.CalledProcessError,
406 git_worktree_path: str) -> None:
407 """Handle a CalledProcessError and quit the program gracefully.
408 Remove any extra worktrees so that the script may be called again."""
409
410 # Tell the user what went wrong
Yanray Wang21127f72023-07-19 12:09:45 +0800411 self.logger.error(e, exc_info=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800412 self.logger.error("Process output:\n {}".format(e.output))
Yanray Wange0e27602023-07-14 17:37:45 +0800413
414 # Quit gracefully by removing the existing worktree
415 self._remove_worktree(git_worktree_path)
416 sys.exit(-1)
417
Yanray Wang386c2f92023-07-20 15:32:15 +0800418 def cal_libraries_code_size(self) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800419 """Do a complete round to calculate code size of library/*.o
420 by measurement tool.
421
422 :return A dictionary of measured code size
423 - typing.Dict[mod: str]
424 """
Yanray Wange0e27602023-07-14 17:37:45 +0800425
Yanray Wang21127f72023-07-19 12:09:45 +0800426 git_worktree_path = self._create_git_worktree()
Yanray Wange0e27602023-07-14 17:37:45 +0800427 self._build_libraries(git_worktree_path)
Yanray Wang21127f72023-07-19 12:09:45 +0800428 res = self._gen_raw_code_size(git_worktree_path)
Yanray Wange0e27602023-07-14 17:37:45 +0800429 self._remove_worktree(git_worktree_path)
430
431 return res
432
433
Yanray Wang15c43f32023-07-17 11:17:12 +0800434class CodeSizeGenerator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800435 """ A generator based on size measurement tool for library/*.o.
Yanray Wang15c43f32023-07-17 11:17:12 +0800436
437 This is an abstract class. To use it, derive a class that implements
Yanray Wang95059002023-07-24 12:29:22 +0800438 write_record and write_comparison methods, then call both of them with
439 proper arguments.
Yanray Wang15c43f32023-07-17 11:17:12 +0800440 """
Yanray Wang21127f72023-07-19 12:09:45 +0800441 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800442 """
443 :param logger: logging module
444 """
Yanray Wang21127f72023-07-19 12:09:45 +0800445 self.logger = logger
446
Yanray Wang95059002023-07-24 12:29:22 +0800447 def write_record(
Yanray Wang15c43f32023-07-17 11:17:12 +0800448 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800449 git_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800450 code_size_text: typing.Dict[str, str],
451 output: typing_util.Writable
Yanray Wang15c43f32023-07-17 11:17:12 +0800452 ) -> None:
453 """Write size record into a file.
454
Yanray Wang955671b2023-07-21 12:08:27 +0800455 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800456 :param code_size_text:
457 string output (utf-8) from measurement tool of code size.
458 - typing.Dict[mod: str]
Yanray Wang95059002023-07-24 12:29:22 +0800459 :param output: output stream which the code size record is written to.
460 (Note: Normally write code size record into File)
Yanray Wang15c43f32023-07-17 11:17:12 +0800461 """
462 raise NotImplementedError
463
Yanray Wang95059002023-07-24 12:29:22 +0800464 def write_comparison(
Yanray Wang15c43f32023-07-17 11:17:12 +0800465 self,
466 old_rev: str,
467 new_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800468 output: typing_util.Writable,
469 with_markdown=False
Yanray Wang15c43f32023-07-17 11:17:12 +0800470 ) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800471 """Write a comparision result into a stream between two Git revisions.
Yanray Wang15c43f32023-07-17 11:17:12 +0800472
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800473 :param old_rev: old Git revision to compared with.
474 :param new_rev: new Git revision to compared with.
Yanray Wang95059002023-07-24 12:29:22 +0800475 :param output: output stream which the code size record is written to.
476 (File / sys.stdout)
477 :param with_markdown: write comparision result in a markdown table.
478 (Default: False)
Yanray Wang15c43f32023-07-17 11:17:12 +0800479 """
480 raise NotImplementedError
481
482
483class CodeSizeGeneratorWithSize(CodeSizeGenerator):
Yanray Wang16ebc572023-05-30 18:10:20 +0800484 """Code Size Base Class for size record saving and writing."""
485
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800486 class SizeEntry: # pylint: disable=too-few-public-methods
487 """Data Structure to only store information of code size."""
488 def __init__(self, text, data, bss, dec):
489 self.text = text
490 self.data = data
491 self.bss = bss
492 self.total = dec # total <=> dec
493
Yanray Wang21127f72023-07-19 12:09:45 +0800494 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800495 """ Variable code_size is used to store size info for any Git revisions.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800496 :param code_size:
497 Data Format as following:
Yanray Wang955671b2023-07-21 12:08:27 +0800498 {git_rev: {module: {file_name: [text, data, bss, dec],
499 etc ...
500 },
501 etc ...
502 },
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800503 etc ...
504 }
Yanray Wang16ebc572023-05-30 18:10:20 +0800505 """
Yanray Wang21127f72023-07-19 12:09:45 +0800506 super().__init__(logger)
Yanray Wang16ebc572023-05-30 18:10:20 +0800507 self.code_size = {} #type: typing.Dict[str, typing.Dict]
508
Yanray Wang955671b2023-07-21 12:08:27 +0800509 def _set_size_record(self, git_rev: str, mod: str, size_text: str) -> None:
510 """Store size information for target Git revision and high-level module.
Yanray Wang16ebc572023-05-30 18:10:20 +0800511
512 size_text Format: text data bss dec hex filename
513 """
514 size_record = {}
515 for line in size_text.splitlines()[1:]:
516 data = line.split()
Yanray Wang9b174e92023-07-17 17:59:53 +0800517 # file_name: SizeEntry(text, data, bss, dec)
518 size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry(
519 data[0], data[1], data[2], data[3])
Yanray Wang955671b2023-07-21 12:08:27 +0800520 if git_rev in self.code_size:
521 self.code_size[git_rev].update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800522 else:
Yanray Wang955671b2023-07-21 12:08:27 +0800523 self.code_size[git_rev] = {mod: size_record}
Yanray Wang16ebc572023-05-30 18:10:20 +0800524
Yanray Wang955671b2023-07-21 12:08:27 +0800525 def read_size_record(self, git_rev: str, fname: str) -> None:
Yanray Wang16ebc572023-05-30 18:10:20 +0800526 """Read size information from csv file and write it into code_size.
527
528 fname Format: filename text data bss dec
529 """
530 mod = ""
531 size_record = {}
532 with open(fname, 'r') as csv_file:
533 for line in csv_file:
534 data = line.strip().split()
535 # check if we find the beginning of a module
536 if data and data[0] in MBEDTLS_STATIC_LIB:
537 mod = data[0]
538 continue
539
540 if mod:
Yanray Wang9b174e92023-07-17 17:59:53 +0800541 # file_name: SizeEntry(text, data, bss, dec)
542 size_record[data[0]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800543 data[1], data[2], data[3], data[4])
Yanray Wang16ebc572023-05-30 18:10:20 +0800544
545 # check if we hit record for the end of a module
546 m = re.match(r'.?TOTALS', line)
547 if m:
Yanray Wang955671b2023-07-21 12:08:27 +0800548 if git_rev in self.code_size:
549 self.code_size[git_rev].update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800550 else:
Yanray Wang955671b2023-07-21 12:08:27 +0800551 self.code_size[git_rev] = {mod: size_record}
Yanray Wang16ebc572023-05-30 18:10:20 +0800552 mod = ""
553 size_record = {}
554
555 def _size_reader_helper(
556 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800557 git_rev: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800558 output: typing_util.Writable,
559 with_markdown=False
Yanray Wang16ebc572023-05-30 18:10:20 +0800560 ) -> typing.Iterator[tuple]:
Yanray Wang955671b2023-07-21 12:08:27 +0800561 """A helper function to peel code_size based on Git revision."""
562 for mod, file_size in self.code_size[git_rev].items():
Yanray Wangb664cb72023-07-18 12:28:35 +0800563 if not with_markdown:
564 output.write("\n" + mod + "\n")
Yanray Wang16ebc572023-05-30 18:10:20 +0800565 for fname, size_entry in file_size.items():
566 yield mod, fname, size_entry
567
Yanray Wang95059002023-07-24 12:29:22 +0800568 def write_record(
Yanray Wang16ebc572023-05-30 18:10:20 +0800569 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800570 git_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800571 code_size_text: typing.Dict[str, str],
Yanray Wang16ebc572023-05-30 18:10:20 +0800572 output: typing_util.Writable
573 ) -> None:
574 """Write size information to a file.
575
576 Writing Format: file_name text data bss total(dec)
577 """
Yanray Wang95059002023-07-24 12:29:22 +0800578 for mod, size_text in code_size_text.items():
579 self._set_size_record(git_rev, mod, size_text)
580
Yanray Wangb664cb72023-07-18 12:28:35 +0800581 format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n"
582 output.write(format_string.format("filename",
583 "text", "data", "bss", "total"))
Yanray Wang955671b2023-07-21 12:08:27 +0800584 for _, fname, size_entry in self._size_reader_helper(git_rev, output):
Yanray Wangb664cb72023-07-18 12:28:35 +0800585 output.write(format_string.format(fname,
586 size_entry.text, size_entry.data,
587 size_entry.bss, size_entry.total))
Yanray Wang16ebc572023-05-30 18:10:20 +0800588
Yanray Wang95059002023-07-24 12:29:22 +0800589 def write_comparison(
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 Wang95059002023-07-24 12:29:22 +0800594 with_markdown=False
Yanray Wang16ebc572023-05-30 18:10:20 +0800595 ) -> None:
596 """Write comparison result into a file.
597
Yanray Wang9b174e92023-07-17 17:59:53 +0800598 Writing Format: file_name current(text,data) old(text,data)\
599 change(text,data) change_pct%(text,data)
Yanray Wang16ebc572023-05-30 18:10:20 +0800600 """
Yanray Wang9b174e92023-07-17 17:59:53 +0800601
602 def cal_size_section_variation(mod, fname, size_entry, attr):
603 new_size = int(size_entry.__dict__[attr])
Yanray Wang955671b2023-07-21 12:08:27 +0800604 # check if we have the file in old Git revision
Yanray Wang16ebc572023-05-30 18:10:20 +0800605 if fname in self.code_size[old_rev][mod]:
Yanray Wang9b174e92023-07-17 17:59:53 +0800606 old_size = int(self.code_size[old_rev][mod][fname].__dict__[attr])
Yanray Wang16ebc572023-05-30 18:10:20 +0800607 change = new_size - old_size
608 if old_size != 0:
609 change_pct = change / old_size
610 else:
611 change_pct = 0
Yanray Wang9b174e92023-07-17 17:59:53 +0800612 return [new_size, old_size, change, change_pct]
Yanray Wang16ebc572023-05-30 18:10:20 +0800613 else:
Yanray Wang9b174e92023-07-17 17:59:53 +0800614 return [new_size]
615
Yanray Wangb664cb72023-07-18 12:28:35 +0800616 if with_markdown:
617 format_string = "| {:<30} | {:<18} | {:<14} | {:<17} | {:<18} |\n"
618 else:
619 format_string = "{:<30} {:<18} {:<14} {:<17} {:<18}\n"
620
Yanray Wang386c2f92023-07-20 15:32:15 +0800621 output.write(format_string
622 .format("filename",
623 "current(text,data)", "old(text,data)",
624 "change(text,data)", "change%(text,data)"))
Yanray Wangb664cb72023-07-18 12:28:35 +0800625 if with_markdown:
626 output.write(format_string
627 .format("----:", "----:", "----:", "----:", "----:"))
628
Yanray Wang386c2f92023-07-20 15:32:15 +0800629 for mod, fname, size_entry in \
Yanray Wangb664cb72023-07-18 12:28:35 +0800630 self._size_reader_helper(new_rev, output, with_markdown):
631 text_vari = cal_size_section_variation(mod, fname,
632 size_entry, 'text')
633 data_vari = cal_size_section_variation(mod, fname,
634 size_entry, 'data')
Yanray Wang9b174e92023-07-17 17:59:53 +0800635
636 if len(text_vari) != 1:
Yanray Wangb664cb72023-07-18 12:28:35 +0800637 # skip the files that haven't changed in code size if we write
638 # comparison result in a markdown table.
639 if with_markdown and text_vari[2] == 0 and data_vari[2] == 0:
640 continue
Yanray Wang386c2f92023-07-20 15:32:15 +0800641 output.write(
642 format_string
643 .format(fname,
644 str(text_vari[0]) + "," + str(data_vari[0]),
645 str(text_vari[1]) + "," + str(data_vari[1]),
646 str(text_vari[2]) + "," + str(data_vari[2]),
647 "{:.2%}".format(text_vari[3]) + ","
648 + "{:.2%}".format(data_vari[3])))
Yanray Wang9b174e92023-07-17 17:59:53 +0800649 else:
Yanray Wang386c2f92023-07-20 15:32:15 +0800650 output.write("{:<30} {:<18}\n"
651 .format(fname,
652 str(text_vari[0]) + "," + str(data_vari[0])))
Yanray Wang16ebc572023-05-30 18:10:20 +0800653
654
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800655class CodeSizeComparison:
Xiaofei Bai2400b502021-10-21 12:22:58 +0000656 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000657
Yanray Wang955671b2023-07-21 12:08:27 +0800658 def __init__( #pylint: disable=too-many-arguments
Yanray Wang72b105f2023-05-31 15:20:39 +0800659 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800660 old_size_dist_info: CodeSizeDistinctInfo,
661 new_size_dist_info: CodeSizeDistinctInfo,
662 size_common_info: CodeSizeCommonInfo,
663 result_options: CodeSizeResultInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800664 logger: logging.Logger,
Yanray Wang72b105f2023-05-31 15:20:39 +0800665 ) -> None:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000666 """
Yanray Wang955671b2023-07-21 12:08:27 +0800667 :param old_size_dist_info: CodeSizeDistinctInfo containing old distinct
668 info to compare code size with.
669 :param new_size_dist_info: CodeSizeDistinctInfo containing new distinct
670 info to take as comparision base.
671 :param size_common_info: CodeSizeCommonInfo containing common info for
672 both old and new size distinct info and
673 measurement tool.
674 :param result_options: CodeSizeResultInfo containing results options for
675 code size record and comparision.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800676 :param logger: logging module
Xiaofei Baibca03e52021-09-09 09:42:37 +0000677 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000678
Yanray Wang21127f72023-07-19 12:09:45 +0800679 self.logger = logger
680
Yanray Wang955671b2023-07-21 12:08:27 +0800681 self.old_size_dist_info = old_size_dist_info
682 self.new_size_dist_info = new_size_dist_info
683 self.size_common_info = size_common_info
Yanray Wang5605c6f2023-07-21 16:09:00 +0800684 # infer pre make command
685 self.old_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
686 self.old_size_dist_info, self.size_common_info.host_arch,
687 self.logger).infer_pre_make_command()
688 self.new_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
689 self.new_size_dist_info, self.size_common_info.host_arch,
690 self.logger).infer_pre_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800691 # infer make command
Yanray Wang955671b2023-07-21 12:08:27 +0800692 self.old_size_dist_info.make_cmd = CodeSizeBuildInfo(
693 self.old_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800694 self.logger).infer_make_command()
Yanray Wang955671b2023-07-21 12:08:27 +0800695 self.new_size_dist_info.make_cmd = CodeSizeBuildInfo(
696 self.new_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800697 self.logger).infer_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800698 # initialize size parser with corresponding measurement tool
Yanray Wang21127f72023-07-19 12:09:45 +0800699 self.code_size_generator = self.__generate_size_parser()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000700
Yanray Wang955671b2023-07-21 12:08:27 +0800701 self.result_options = result_options
702 self.csv_dir = os.path.abspath(self.result_options.record_dir)
703 os.makedirs(self.csv_dir, exist_ok=True)
704 self.comp_dir = os.path.abspath(self.result_options.comp_dir)
705 os.makedirs(self.comp_dir, exist_ok=True)
706
Yanray Wang21127f72023-07-19 12:09:45 +0800707 def __generate_size_parser(self):
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800708 """Generate a parser for the corresponding measurement tool."""
Yanray Wang955671b2023-07-21 12:08:27 +0800709 if re.match(r'size', self.size_common_info.measure_cmd.strip()):
Yanray Wang21127f72023-07-19 12:09:45 +0800710 return CodeSizeGeneratorWithSize(self.logger)
Yanray Wang802af162023-07-17 14:04:30 +0800711 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800712 self.logger.error("Unsupported measurement tool: `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800713 .format(self.size_common_info.measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800714 .strip().split(' ')[0]))
Yanray Wang802af162023-07-17 14:04:30 +0800715 sys.exit(1)
716
Yanray Wang386c2f92023-07-20 15:32:15 +0800717 def cal_code_size(
718 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800719 size_dist_info: CodeSizeDistinctInfo
Yanray Wang386c2f92023-07-20 15:32:15 +0800720 ) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800721 """Calculate code size of library/*.o in a UTF-8 encoding"""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000722
Yanray Wang955671b2023-07-21 12:08:27 +0800723 return CodeSizeCalculator(size_dist_info.git_rev,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800724 size_dist_info.pre_make_cmd,
Yanray Wang955671b2023-07-21 12:08:27 +0800725 size_dist_info.make_cmd,
726 self.size_common_info.measure_cmd,
Yanray Wang21127f72023-07-19 12:09:45 +0800727 self.logger).cal_libraries_code_size()
Yanray Wang8804db92023-05-30 18:18:18 +0800728
Yanray Wang955671b2023-07-21 12:08:27 +0800729 def gen_code_size_report(self, size_dist_info: CodeSizeDistinctInfo) -> None:
Yanray Wang5e9130a2023-07-17 11:55:54 +0800730 """Generate code size record and write it into a file."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000731
Yanray Wang21127f72023-07-19 12:09:45 +0800732 self.logger.info("Start to generate code size record for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800733 .format(size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800734 output_file = os.path.join(
735 self.csv_dir,
736 '{}-{}.csv'
737 .format(size_dist_info.get_info_indication(),
738 self.size_common_info.get_info_indication()))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000739 # Check if the corresponding record exists
Yanray Wang955671b2023-07-21 12:08:27 +0800740 if size_dist_info.git_rev != "current" and \
Yanray Wang21127f72023-07-19 12:09:45 +0800741 os.path.exists(output_file):
742 self.logger.debug("Code size csv file for {} already exists."
Yanray Wang955671b2023-07-21 12:08:27 +0800743 .format(size_dist_info.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800744 self.code_size_generator.read_size_record(
Yanray Wang955671b2023-07-21 12:08:27 +0800745 size_dist_info.git_rev, output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000746 else:
Yanray Wang95059002023-07-24 12:29:22 +0800747 # measure code size
748 code_size_text = self.cal_code_size(size_dist_info)
749
750 self.logger.debug("Generating code size csv for {}."
751 .format(size_dist_info.git_rev))
752 output = open(output_file, "w")
753 self.code_size_generator.write_record(
754 size_dist_info.git_rev, code_size_text, output)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000755
Yanray Wang386c2f92023-07-20 15:32:15 +0800756 def gen_code_size_comparison(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800757 """Generate results of code size changes between two Git revisions,
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800758 old and new.
759
Yanray Wang955671b2023-07-21 12:08:27 +0800760 - Measured code size result of these two Git revisions must be available.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800761 - The result is directed into either file / stdout depending on
Yanray Wang955671b2023-07-21 12:08:27 +0800762 the option, size_common_info.result_options.stdout. (Default: file)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800763 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000764
Yanray Wang21127f72023-07-19 12:09:45 +0800765 self.logger.info("Start to generate comparision result between "\
766 "{} and {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800767 .format(self.old_size_dist_info.git_rev,
768 self.new_size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800769 if self.result_options.stdout:
770 output = sys.stdout
Yanray Wang69262fc2023-07-24 16:36:40 +0800771 print("Measure code size between `{}` and `{}` by `{}`."
772 .format(self.old_size_dist_info.get_info_indication(),
773 self.new_size_dist_info.get_info_indication(),
774 self.size_common_info.get_info_indication()))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800775 else:
776 output_file = os.path.join(
777 self.comp_dir,
778 '{}-{}-{}.csv'
779 .format(self.old_size_dist_info.get_info_indication(),
780 self.new_size_dist_info.get_info_indication(),
781 self.size_common_info.get_info_indication()))
782 output = open(output_file, "w")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000783
Yanray Wang95059002023-07-24 12:29:22 +0800784 self.logger.debug("Generating comparison results between {} and {}."
785 .format(self.old_size_dist_info.git_rev,
786 self.new_size_dist_info.git_rev))
Yanray Wang95059002023-07-24 12:29:22 +0800787 self.code_size_generator.write_comparison(
Yanray Wang955671b2023-07-21 12:08:27 +0800788 self.old_size_dist_info.git_rev,
789 self.new_size_dist_info.git_rev,
Yanray Wang95059002023-07-24 12:29:22 +0800790 output, self.result_options.with_markdown)
Yanray Wang21127f72023-07-19 12:09:45 +0800791
Yanray Wang386c2f92023-07-20 15:32:15 +0800792 def get_comparision_results(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800793 """Compare size of library/*.o between self.old_size_dist_info and
794 self.old_size_dist_info and generate the result file."""
Gilles Peskined9071e72022-09-18 21:17:09 +0200795 build_tree.check_repo_path()
Yanray Wang955671b2023-07-21 12:08:27 +0800796 self.gen_code_size_report(self.old_size_dist_info)
797 self.gen_code_size_report(self.new_size_dist_info)
Yanray Wang386c2f92023-07-20 15:32:15 +0800798 self.gen_code_size_comparison()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000799
Xiaofei Bai2400b502021-10-21 12:22:58 +0000800def main():
Yanray Wang502c54f2023-05-31 11:41:36 +0800801 parser = argparse.ArgumentParser(description=(__doc__))
802 group_required = parser.add_argument_group(
803 'required arguments',
804 'required arguments to parse for running ' + os.path.basename(__file__))
805 group_required.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800806 '-o', '--old-rev', type=str, required=True,
Yanray Wang955671b2023-07-21 12:08:27 +0800807 help='old Git revision for comparison.')
Yanray Wang502c54f2023-05-31 11:41:36 +0800808
809 group_optional = parser.add_argument_group(
810 'optional arguments',
811 'optional arguments to parse for running ' + os.path.basename(__file__))
812 group_optional.add_argument(
Yanray Wang955671b2023-07-21 12:08:27 +0800813 '--record_dir', type=str, default='code_size_records',
814 help='directory where code size record is stored. '
815 '(Default: code_size_records)')
816 group_optional.add_argument(
817 '-r', '--comp-dir', type=str, default='comparison',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800818 help='directory where comparison result is stored. '
819 '(Default: comparison)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800820 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800821 '-n', '--new-rev', type=str, default=None,
Yanray Wang955671b2023-07-21 12:08:27 +0800822 help='new Git revision as comparison base. '
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800823 '(Default is the current work directory, including uncommitted '
824 'changes.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800825 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800826 '-a', '--arch', type=str, default=detect_arch(),
Yanray Wang23bd5322023-05-24 11:03:59 +0800827 choices=list(map(lambda s: s.value, SupportedArch)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800828 help='Specify architecture for code size comparison. '
829 '(Default is the host architecture.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800830 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800831 '-c', '--config', type=str, default=SupportedConfig.DEFAULT.value,
Yanray Wang6a862582023-05-24 12:24:38 +0800832 choices=list(map(lambda s: s.value, SupportedConfig)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800833 help='Specify configuration type for code size comparison. '
834 '(Default is the current MbedTLS configuration.)')
Yanray Wangb664cb72023-07-18 12:28:35 +0800835 group_optional.add_argument(
836 '--markdown', action='store_true', dest='markdown',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800837 help='Show comparision of code size in a markdown table. '
838 '(Only show the files that have changed).')
Yanray Wang227576a2023-07-18 14:35:05 +0800839 group_optional.add_argument(
840 '--stdout', action='store_true', dest='stdout',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800841 help='Set this option to direct comparison result into sys.stdout. '
842 '(Default: file)')
Yanray Wang21127f72023-07-19 12:09:45 +0800843 group_optional.add_argument(
844 '--verbose', action='store_true', dest='verbose',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800845 help='Show logs in detail for code size measurement. '
846 '(Default: False)')
Xiaofei Baibca03e52021-09-09 09:42:37 +0000847 comp_args = parser.parse_args()
848
Yanray Wang21127f72023-07-19 12:09:45 +0800849 logger = logging.getLogger()
850 logging_util.configure_logger(logger)
851 logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO)
852
Yanray Wang955671b2023-07-21 12:08:27 +0800853 if os.path.isfile(comp_args.comp_dir):
854 logger.error("{} is not a directory".format(comp_args.comp_dir))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000855 parser.exit()
856
Yanray Wang955671b2023-07-21 12:08:27 +0800857 old_revision = CodeSizeCalculator.validate_git_revision(comp_args.old_rev)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000858 if comp_args.new_rev is not None:
Yanray Wang955671b2023-07-21 12:08:27 +0800859 new_revision = CodeSizeCalculator.validate_git_revision(
860 comp_args.new_rev)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000861 else:
Yanray Wang5605c6f2023-07-21 16:09:00 +0800862 new_revision = 'current'
Xiaofei Bai2400b502021-10-21 12:22:58 +0000863
Yanray Wang5605c6f2023-07-21 16:09:00 +0800864 # version, git_rev, arch, config, compiler, opt_level
Yanray Wang955671b2023-07-21 12:08:27 +0800865 old_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang5605c6f2023-07-21 16:09:00 +0800866 'old', old_revision, comp_args.arch, comp_args.config, 'cc', '-Os')
Yanray Wang955671b2023-07-21 12:08:27 +0800867 new_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang5605c6f2023-07-21 16:09:00 +0800868 'new', new_revision, comp_args.arch, comp_args.config, 'cc', '-Os')
869 # host_arch, measure_cmd
Yanray Wang955671b2023-07-21 12:08:27 +0800870 size_common_info = CodeSizeCommonInfo(
871 detect_arch(), 'size -t')
Yanray Wang5605c6f2023-07-21 16:09:00 +0800872 # record_dir, comp_dir, with_markdown, stdout
Yanray Wang955671b2023-07-21 12:08:27 +0800873 result_options = CodeSizeResultInfo(
874 comp_args.record_dir, comp_args.comp_dir,
875 comp_args.markdown, comp_args.stdout)
Yanray Wang923f9432023-07-17 12:43:00 +0800876
Yanray Wanga6cf6922023-07-24 15:20:42 +0800877 logger.info("Measure code size between {} and {} by `{}`."
878 .format(old_size_dist_info.get_info_indication(),
879 new_size_dist_info.get_info_indication(),
880 size_common_info.get_info_indication()))
Yanray Wang955671b2023-07-21 12:08:27 +0800881 CodeSizeComparison(old_size_dist_info, new_size_dist_info,
882 size_common_info, result_options,
883 logger).get_comparision_results()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000884
Xiaofei Baibca03e52021-09-09 09:42:37 +0000885if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000886 main()