blob: e79b379085b2b71ef9ff0b9af7c36c48295f3cf3 [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 Wangf2cd7172023-07-24 16:56:46 +0800650 output.write(
651 format_string
652 .format(fname,
653 str(text_vari[0]) + "," + str(data_vari[0]),
654 'None', 'None', 'None'))
Yanray Wang16ebc572023-05-30 18:10:20 +0800655
656
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800657class CodeSizeComparison:
Xiaofei Bai2400b502021-10-21 12:22:58 +0000658 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000659
Yanray Wang955671b2023-07-21 12:08:27 +0800660 def __init__( #pylint: disable=too-many-arguments
Yanray Wang72b105f2023-05-31 15:20:39 +0800661 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800662 old_size_dist_info: CodeSizeDistinctInfo,
663 new_size_dist_info: CodeSizeDistinctInfo,
664 size_common_info: CodeSizeCommonInfo,
665 result_options: CodeSizeResultInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800666 logger: logging.Logger,
Yanray Wang72b105f2023-05-31 15:20:39 +0800667 ) -> None:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000668 """
Yanray Wang955671b2023-07-21 12:08:27 +0800669 :param old_size_dist_info: CodeSizeDistinctInfo containing old distinct
670 info to compare code size with.
671 :param new_size_dist_info: CodeSizeDistinctInfo containing new distinct
672 info to take as comparision base.
673 :param size_common_info: CodeSizeCommonInfo containing common info for
674 both old and new size distinct info and
675 measurement tool.
676 :param result_options: CodeSizeResultInfo containing results options for
677 code size record and comparision.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800678 :param logger: logging module
Xiaofei Baibca03e52021-09-09 09:42:37 +0000679 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000680
Yanray Wang21127f72023-07-19 12:09:45 +0800681 self.logger = logger
682
Yanray Wang955671b2023-07-21 12:08:27 +0800683 self.old_size_dist_info = old_size_dist_info
684 self.new_size_dist_info = new_size_dist_info
685 self.size_common_info = size_common_info
Yanray Wang5605c6f2023-07-21 16:09:00 +0800686 # infer pre make command
687 self.old_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
688 self.old_size_dist_info, self.size_common_info.host_arch,
689 self.logger).infer_pre_make_command()
690 self.new_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
691 self.new_size_dist_info, self.size_common_info.host_arch,
692 self.logger).infer_pre_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800693 # infer make command
Yanray Wang955671b2023-07-21 12:08:27 +0800694 self.old_size_dist_info.make_cmd = CodeSizeBuildInfo(
695 self.old_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800696 self.logger).infer_make_command()
Yanray Wang955671b2023-07-21 12:08:27 +0800697 self.new_size_dist_info.make_cmd = CodeSizeBuildInfo(
698 self.new_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800699 self.logger).infer_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800700 # initialize size parser with corresponding measurement tool
Yanray Wang21127f72023-07-19 12:09:45 +0800701 self.code_size_generator = self.__generate_size_parser()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000702
Yanray Wang955671b2023-07-21 12:08:27 +0800703 self.result_options = result_options
704 self.csv_dir = os.path.abspath(self.result_options.record_dir)
705 os.makedirs(self.csv_dir, exist_ok=True)
706 self.comp_dir = os.path.abspath(self.result_options.comp_dir)
707 os.makedirs(self.comp_dir, exist_ok=True)
708
Yanray Wang21127f72023-07-19 12:09:45 +0800709 def __generate_size_parser(self):
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800710 """Generate a parser for the corresponding measurement tool."""
Yanray Wang955671b2023-07-21 12:08:27 +0800711 if re.match(r'size', self.size_common_info.measure_cmd.strip()):
Yanray Wang21127f72023-07-19 12:09:45 +0800712 return CodeSizeGeneratorWithSize(self.logger)
Yanray Wang802af162023-07-17 14:04:30 +0800713 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800714 self.logger.error("Unsupported measurement tool: `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800715 .format(self.size_common_info.measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800716 .strip().split(' ')[0]))
Yanray Wang802af162023-07-17 14:04:30 +0800717 sys.exit(1)
718
Yanray Wang386c2f92023-07-20 15:32:15 +0800719 def cal_code_size(
720 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800721 size_dist_info: CodeSizeDistinctInfo
Yanray Wang386c2f92023-07-20 15:32:15 +0800722 ) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800723 """Calculate code size of library/*.o in a UTF-8 encoding"""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000724
Yanray Wang955671b2023-07-21 12:08:27 +0800725 return CodeSizeCalculator(size_dist_info.git_rev,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800726 size_dist_info.pre_make_cmd,
Yanray Wang955671b2023-07-21 12:08:27 +0800727 size_dist_info.make_cmd,
728 self.size_common_info.measure_cmd,
Yanray Wang21127f72023-07-19 12:09:45 +0800729 self.logger).cal_libraries_code_size()
Yanray Wang8804db92023-05-30 18:18:18 +0800730
Yanray Wang955671b2023-07-21 12:08:27 +0800731 def gen_code_size_report(self, size_dist_info: CodeSizeDistinctInfo) -> None:
Yanray Wang5e9130a2023-07-17 11:55:54 +0800732 """Generate code size record and write it into a file."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000733
Yanray Wang21127f72023-07-19 12:09:45 +0800734 self.logger.info("Start to generate code size record for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800735 .format(size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800736 output_file = os.path.join(
737 self.csv_dir,
738 '{}-{}.csv'
739 .format(size_dist_info.get_info_indication(),
740 self.size_common_info.get_info_indication()))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000741 # Check if the corresponding record exists
Yanray Wang955671b2023-07-21 12:08:27 +0800742 if size_dist_info.git_rev != "current" and \
Yanray Wang21127f72023-07-19 12:09:45 +0800743 os.path.exists(output_file):
744 self.logger.debug("Code size csv file for {} already exists."
Yanray Wang955671b2023-07-21 12:08:27 +0800745 .format(size_dist_info.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800746 self.code_size_generator.read_size_record(
Yanray Wang955671b2023-07-21 12:08:27 +0800747 size_dist_info.git_rev, output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000748 else:
Yanray Wang95059002023-07-24 12:29:22 +0800749 # measure code size
750 code_size_text = self.cal_code_size(size_dist_info)
751
752 self.logger.debug("Generating code size csv for {}."
753 .format(size_dist_info.git_rev))
754 output = open(output_file, "w")
755 self.code_size_generator.write_record(
756 size_dist_info.git_rev, code_size_text, output)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000757
Yanray Wang386c2f92023-07-20 15:32:15 +0800758 def gen_code_size_comparison(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800759 """Generate results of code size changes between two Git revisions,
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800760 old and new.
761
Yanray Wang955671b2023-07-21 12:08:27 +0800762 - Measured code size result of these two Git revisions must be available.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800763 - The result is directed into either file / stdout depending on
Yanray Wang955671b2023-07-21 12:08:27 +0800764 the option, size_common_info.result_options.stdout. (Default: file)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800765 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000766
Yanray Wang21127f72023-07-19 12:09:45 +0800767 self.logger.info("Start to generate comparision result between "\
768 "{} and {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800769 .format(self.old_size_dist_info.git_rev,
770 self.new_size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800771 if self.result_options.stdout:
772 output = sys.stdout
Yanray Wang69262fc2023-07-24 16:36:40 +0800773 print("Measure code size between `{}` and `{}` by `{}`."
774 .format(self.old_size_dist_info.get_info_indication(),
775 self.new_size_dist_info.get_info_indication(),
776 self.size_common_info.get_info_indication()))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800777 else:
778 output_file = os.path.join(
779 self.comp_dir,
780 '{}-{}-{}.csv'
781 .format(self.old_size_dist_info.get_info_indication(),
782 self.new_size_dist_info.get_info_indication(),
783 self.size_common_info.get_info_indication()))
784 output = open(output_file, "w")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000785
Yanray Wang95059002023-07-24 12:29:22 +0800786 self.logger.debug("Generating comparison results between {} and {}."
787 .format(self.old_size_dist_info.git_rev,
788 self.new_size_dist_info.git_rev))
Yanray Wang95059002023-07-24 12:29:22 +0800789 self.code_size_generator.write_comparison(
Yanray Wang955671b2023-07-21 12:08:27 +0800790 self.old_size_dist_info.git_rev,
791 self.new_size_dist_info.git_rev,
Yanray Wang95059002023-07-24 12:29:22 +0800792 output, self.result_options.with_markdown)
Yanray Wang21127f72023-07-19 12:09:45 +0800793
Yanray Wang386c2f92023-07-20 15:32:15 +0800794 def get_comparision_results(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800795 """Compare size of library/*.o between self.old_size_dist_info and
796 self.old_size_dist_info and generate the result file."""
Gilles Peskined9071e72022-09-18 21:17:09 +0200797 build_tree.check_repo_path()
Yanray Wang955671b2023-07-21 12:08:27 +0800798 self.gen_code_size_report(self.old_size_dist_info)
799 self.gen_code_size_report(self.new_size_dist_info)
Yanray Wang386c2f92023-07-20 15:32:15 +0800800 self.gen_code_size_comparison()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000801
Xiaofei Bai2400b502021-10-21 12:22:58 +0000802def main():
Yanray Wang502c54f2023-05-31 11:41:36 +0800803 parser = argparse.ArgumentParser(description=(__doc__))
804 group_required = parser.add_argument_group(
805 'required arguments',
806 'required arguments to parse for running ' + os.path.basename(__file__))
807 group_required.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800808 '-o', '--old-rev', type=str, required=True,
Yanray Wang955671b2023-07-21 12:08:27 +0800809 help='old Git revision for comparison.')
Yanray Wang502c54f2023-05-31 11:41:36 +0800810
811 group_optional = parser.add_argument_group(
812 'optional arguments',
813 'optional arguments to parse for running ' + os.path.basename(__file__))
814 group_optional.add_argument(
Yanray Wang955671b2023-07-21 12:08:27 +0800815 '--record_dir', type=str, default='code_size_records',
816 help='directory where code size record is stored. '
817 '(Default: code_size_records)')
818 group_optional.add_argument(
819 '-r', '--comp-dir', type=str, default='comparison',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800820 help='directory where comparison result is stored. '
821 '(Default: comparison)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800822 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800823 '-n', '--new-rev', type=str, default=None,
Yanray Wang955671b2023-07-21 12:08:27 +0800824 help='new Git revision as comparison base. '
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800825 '(Default is the current work directory, including uncommitted '
826 'changes.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800827 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800828 '-a', '--arch', type=str, default=detect_arch(),
Yanray Wang23bd5322023-05-24 11:03:59 +0800829 choices=list(map(lambda s: s.value, SupportedArch)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800830 help='Specify architecture for code size comparison. '
831 '(Default is the host architecture.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800832 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800833 '-c', '--config', type=str, default=SupportedConfig.DEFAULT.value,
Yanray Wang6a862582023-05-24 12:24:38 +0800834 choices=list(map(lambda s: s.value, SupportedConfig)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800835 help='Specify configuration type for code size comparison. '
836 '(Default is the current MbedTLS configuration.)')
Yanray Wangb664cb72023-07-18 12:28:35 +0800837 group_optional.add_argument(
838 '--markdown', action='store_true', dest='markdown',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800839 help='Show comparision of code size in a markdown table. '
840 '(Only show the files that have changed).')
Yanray Wang227576a2023-07-18 14:35:05 +0800841 group_optional.add_argument(
842 '--stdout', action='store_true', dest='stdout',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800843 help='Set this option to direct comparison result into sys.stdout. '
844 '(Default: file)')
Yanray Wang21127f72023-07-19 12:09:45 +0800845 group_optional.add_argument(
846 '--verbose', action='store_true', dest='verbose',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800847 help='Show logs in detail for code size measurement. '
848 '(Default: False)')
Xiaofei Baibca03e52021-09-09 09:42:37 +0000849 comp_args = parser.parse_args()
850
Yanray Wang21127f72023-07-19 12:09:45 +0800851 logger = logging.getLogger()
852 logging_util.configure_logger(logger)
853 logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO)
854
Yanray Wang955671b2023-07-21 12:08:27 +0800855 if os.path.isfile(comp_args.comp_dir):
856 logger.error("{} is not a directory".format(comp_args.comp_dir))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000857 parser.exit()
858
Yanray Wang955671b2023-07-21 12:08:27 +0800859 old_revision = CodeSizeCalculator.validate_git_revision(comp_args.old_rev)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000860 if comp_args.new_rev is not None:
Yanray Wang955671b2023-07-21 12:08:27 +0800861 new_revision = CodeSizeCalculator.validate_git_revision(
862 comp_args.new_rev)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000863 else:
Yanray Wang5605c6f2023-07-21 16:09:00 +0800864 new_revision = 'current'
Xiaofei Bai2400b502021-10-21 12:22:58 +0000865
Yanray Wang5605c6f2023-07-21 16:09:00 +0800866 # version, git_rev, arch, config, compiler, opt_level
Yanray Wang955671b2023-07-21 12:08:27 +0800867 old_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang5605c6f2023-07-21 16:09:00 +0800868 'old', old_revision, comp_args.arch, comp_args.config, 'cc', '-Os')
Yanray Wang955671b2023-07-21 12:08:27 +0800869 new_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang5605c6f2023-07-21 16:09:00 +0800870 'new', new_revision, comp_args.arch, comp_args.config, 'cc', '-Os')
871 # host_arch, measure_cmd
Yanray Wang955671b2023-07-21 12:08:27 +0800872 size_common_info = CodeSizeCommonInfo(
873 detect_arch(), 'size -t')
Yanray Wang5605c6f2023-07-21 16:09:00 +0800874 # record_dir, comp_dir, with_markdown, stdout
Yanray Wang955671b2023-07-21 12:08:27 +0800875 result_options = CodeSizeResultInfo(
876 comp_args.record_dir, comp_args.comp_dir,
877 comp_args.markdown, comp_args.stdout)
Yanray Wang923f9432023-07-17 12:43:00 +0800878
Yanray Wanga6cf6922023-07-24 15:20:42 +0800879 logger.info("Measure code size between {} and {} by `{}`."
880 .format(old_size_dist_info.get_info_indication(),
881 new_size_dist_info.get_info_indication(),
882 size_common_info.get_info_indication()))
Yanray Wang955671b2023-07-21 12:08:27 +0800883 CodeSizeComparison(old_size_dist_info, new_size_dist_info,
884 size_common_info, result_options,
885 logger).get_comparision_results()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000886
Xiaofei Baibca03e52021-09-09 09:42:37 +0000887if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000888 main()