blob: 1bcc731949a681701e324fc154c2cc3ec3dfbcfa [file] [log] [blame]
Xiaofei Baibca03e52021-09-09 09:42:37 +00001#!/usr/bin/env python3
2
3"""
Xiaofei Baibca03e52021-09-09 09:42:37 +00004This script is for comparing the size of the library files from two
5different Git revisions within an Mbed TLS repository.
6The results of the comparison is formatted as csv and stored at a
7configurable location.
8Note: must be run from Mbed TLS root.
9"""
10
11# Copyright The Mbed TLS Contributors
12# SPDX-License-Identifier: Apache-2.0
13#
14# Licensed under the Apache License, Version 2.0 (the "License"); you may
15# not use this file except in compliance with the License.
16# You may obtain a copy of the License at
17#
18# http://www.apache.org/licenses/LICENSE-2.0
19#
20# Unless required by applicable law or agreed to in writing, software
21# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
22# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23# See the License for the specific language governing permissions and
24# limitations under the License.
25
26import argparse
Yanray Wang21127f72023-07-19 12:09:45 +080027import logging
Xiaofei Baibca03e52021-09-09 09:42:37 +000028import os
Yanray Wang16ebc572023-05-30 18:10:20 +080029import re
Yanray Wang5605c6f2023-07-21 16:09:00 +080030import shutil
Xiaofei Baibca03e52021-09-09 09:42:37 +000031import subprocess
32import sys
Yanray Wang16ebc572023-05-30 18:10:20 +080033import typing
Yanray Wang23bd5322023-05-24 11:03:59 +080034from enum import Enum
Xiaofei Baibca03e52021-09-09 09:42:37 +000035
Gilles Peskined9071e72022-09-18 21:17:09 +020036from mbedtls_dev import build_tree
Yanray Wang21127f72023-07-19 12:09:45 +080037from mbedtls_dev import logging_util
38from mbedtls_dev import typing_util
Gilles Peskined9071e72022-09-18 21:17:09 +020039
Yanray Wang23bd5322023-05-24 11:03:59 +080040class SupportedArch(Enum):
41 """Supported architecture for code size measurement."""
42 AARCH64 = 'aarch64'
43 AARCH32 = 'aarch32'
Yanray Wangaba71582023-05-29 16:45:56 +080044 ARMV8_M = 'armv8-m'
Yanray Wang23bd5322023-05-24 11:03:59 +080045 X86_64 = 'x86_64'
46 X86 = 'x86'
47
Yanray Wang955671b2023-07-21 12:08:27 +080048
Yanray Wang6a862582023-05-24 12:24:38 +080049class SupportedConfig(Enum):
50 """Supported configuration for code size measurement."""
51 DEFAULT = 'default'
52 TFM_MEDIUM = 'tfm-medium'
53
Yanray Wang955671b2023-07-21 12:08:27 +080054
Yanray Wang16ebc572023-05-30 18:10:20 +080055# Static library
56MBEDTLS_STATIC_LIB = {
57 'CRYPTO': 'library/libmbedcrypto.a',
58 'X509': 'library/libmbedx509.a',
59 'TLS': 'library/libmbedtls.a',
60}
61
Yanray Wang955671b2023-07-21 12:08:27 +080062class CodeSizeDistinctInfo: # pylint: disable=too-few-public-methods
63 """Data structure to store possibly distinct information for code size
64 comparison."""
65 def __init__( #pylint: disable=too-many-arguments
66 self,
67 version: str,
68 git_rev: str,
69 arch: str,
70 config: str,
Yanray Wang5605c6f2023-07-21 16:09:00 +080071 compiler: str,
72 opt_level: str,
Yanray Wang955671b2023-07-21 12:08:27 +080073 ) -> None:
74 """
75 :param: version: which version to compare with for code size.
76 :param: git_rev: Git revision to calculate code size.
77 :param: arch: architecture to measure code size on.
78 :param: config: Configuration type to calculate code size.
79 (See SupportedConfig)
Yanray Wang5605c6f2023-07-21 16:09:00 +080080 :param: compiler: compiler used to build library/*.o.
81 :param: opt_level: Options that control optimization. (E.g. -Os)
Yanray Wang955671b2023-07-21 12:08:27 +080082 """
83 self.version = version
84 self.git_rev = git_rev
85 self.arch = arch
86 self.config = config
Yanray Wang5605c6f2023-07-21 16:09:00 +080087 self.compiler = compiler
88 self.opt_level = opt_level
89 # Note: Variables below are not initialized by class instantiation.
90 self.pre_make_cmd = [] #type: typing.List[str]
91 self.make_cmd = ''
Yanray Wang955671b2023-07-21 12:08:27 +080092
Yanray Wanga6cf6922023-07-24 15:20:42 +080093 def get_info_indication(self):
94 """Return a unique string to indicate Code Size Distinct Information."""
Yanray Wang6ef50492023-07-26 14:59:37 +080095 return '{git_rev}-{arch}-{config}-{compiler}'.format(**self.__dict__)
Yanray Wanga6cf6922023-07-24 15:20:42 +080096
Yanray Wang955671b2023-07-21 12:08:27 +080097
98class CodeSizeCommonInfo: # pylint: disable=too-few-public-methods
99 """Data structure to store common information for code size comparison."""
100 def __init__(
101 self,
102 host_arch: str,
103 measure_cmd: str,
104 ) -> None:
105 """
106 :param host_arch: host architecture.
107 :param measure_cmd: command to measure code size for library/*.o.
108 """
109 self.host_arch = host_arch
110 self.measure_cmd = measure_cmd
111
Yanray Wanga6cf6922023-07-24 15:20:42 +0800112 def get_info_indication(self):
113 """Return a unique string to indicate Code Size Common Information."""
Yanray Wange4a36362023-07-25 10:37:11 +0800114 return '{measure_tool}'\
115 .format(measure_tool=self.measure_cmd.strip().split(' ')[0])
Yanray Wang955671b2023-07-21 12:08:27 +0800116
117class CodeSizeResultInfo: # pylint: disable=too-few-public-methods
118 """Data structure to store result options for code size comparison."""
119 def __init__(
120 self,
121 record_dir: str,
122 comp_dir: str,
123 with_markdown=False,
124 stdout=False,
125 ) -> None:
126 """
127 :param record_dir: directory to store code size record.
128 :param comp_dir: directory to store results of code size comparision.
129 :param with_markdown: write comparision result into a markdown table.
130 (Default: False)
131 :param stdout: direct comparison result into sys.stdout.
132 (Default False)
133 """
134 self.record_dir = record_dir
135 self.comp_dir = comp_dir
136 self.with_markdown = with_markdown
137 self.stdout = stdout
138
139
Yanray Wang23bd5322023-05-24 11:03:59 +0800140DETECT_ARCH_CMD = "cc -dM -E - < /dev/null"
141def detect_arch() -> str:
142 """Auto-detect host architecture."""
143 cc_output = subprocess.check_output(DETECT_ARCH_CMD, shell=True).decode()
Yanray Wang386c2f92023-07-20 15:32:15 +0800144 if '__aarch64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800145 return SupportedArch.AARCH64.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800146 if '__arm__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800147 return SupportedArch.AARCH32.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800148 if '__x86_64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800149 return SupportedArch.X86_64.value
Yanray Wangca9a3cb2023-07-26 17:16:29 +0800150 if '__i386__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800151 return SupportedArch.X86.value
152 else:
153 print("Unknown host architecture, cannot auto-detect arch.")
154 sys.exit(1)
Gilles Peskined9071e72022-09-18 21:17:09 +0200155
Yanray Wang5605c6f2023-07-21 16:09:00 +0800156TFM_MEDIUM_CONFIG_H = 'configs/tfm_mbedcrypto_config_profile_medium.h'
157TFM_MEDIUM_CRYPTO_CONFIG_H = 'configs/crypto_config_profile_medium.h'
158
159CONFIG_H = 'include/mbedtls/mbedtls_config.h'
160CRYPTO_CONFIG_H = 'include/psa/crypto_config.h'
161BACKUP_SUFFIX = '.code_size.bak'
162
Yanray Wang923f9432023-07-17 12:43:00 +0800163class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods
Yanray Wang6a862582023-05-24 12:24:38 +0800164 """Gather information used to measure code size.
165
166 It collects information about architecture, configuration in order to
167 infer build command for code size measurement.
168 """
169
Yanray Wangc18cd892023-05-31 11:08:04 +0800170 SupportedArchConfig = [
Yanray Wang386c2f92023-07-20 15:32:15 +0800171 '-a ' + SupportedArch.AARCH64.value + ' -c ' + SupportedConfig.DEFAULT.value,
172 '-a ' + SupportedArch.AARCH32.value + ' -c ' + SupportedConfig.DEFAULT.value,
173 '-a ' + SupportedArch.X86_64.value + ' -c ' + SupportedConfig.DEFAULT.value,
174 '-a ' + SupportedArch.X86.value + ' -c ' + SupportedConfig.DEFAULT.value,
175 '-a ' + SupportedArch.ARMV8_M.value + ' -c ' + SupportedConfig.TFM_MEDIUM.value,
Yanray Wangc18cd892023-05-31 11:08:04 +0800176 ]
177
Yanray Wang802af162023-07-17 14:04:30 +0800178 def __init__(
179 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800180 size_dist_info: CodeSizeDistinctInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800181 host_arch: str,
182 logger: logging.Logger,
Yanray Wang802af162023-07-17 14:04:30 +0800183 ) -> None:
Yanray Wang6a862582023-05-24 12:24:38 +0800184 """
Yanray Wang955671b2023-07-21 12:08:27 +0800185 :param size_dist_info:
186 CodeSizeDistinctInfo containing info for code size measurement.
187 - size_dist_info.arch: architecture to measure code size on.
188 - size_dist_info.config: configuration type to measure
189 code size with.
Yanray Wang5605c6f2023-07-21 16:09:00 +0800190 - size_dist_info.compiler: compiler used to build library/*.o.
191 - size_dist_info.opt_level: Options that control optimization.
192 (E.g. -Os)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800193 :param host_arch: host architecture.
194 :param logger: logging module
Yanray Wang6a862582023-05-24 12:24:38 +0800195 """
Yanray Wang5605c6f2023-07-21 16:09:00 +0800196 self.arch = size_dist_info.arch
197 self.config = size_dist_info.config
198 self.compiler = size_dist_info.compiler
199 self.opt_level = size_dist_info.opt_level
200
201 self.make_cmd = ['make', '-j', 'lib']
202
Yanray Wang802af162023-07-17 14:04:30 +0800203 self.host_arch = host_arch
Yanray Wang21127f72023-07-19 12:09:45 +0800204 self.logger = logger
Yanray Wang6a862582023-05-24 12:24:38 +0800205
Yanray Wang5605c6f2023-07-21 16:09:00 +0800206 def check_correctness(self) -> bool:
207 """Check whether we are using proper / supported combination
208 of information to build library/*.o."""
Yanray Wang6a862582023-05-24 12:24:38 +0800209
Yanray Wang5605c6f2023-07-21 16:09:00 +0800210 # default config
211 if self.config == SupportedConfig.DEFAULT.value and \
212 self.arch == self.host_arch:
213 return True
214 # TF-M
215 elif self.arch == SupportedArch.ARMV8_M.value and \
216 self.config == SupportedConfig.TFM_MEDIUM.value:
217 return True
218
219 return False
220
221 def infer_pre_make_command(self) -> typing.List[str]:
222 """Infer command to set up proper configuration before running make."""
223 pre_make_cmd = [] #type: typing.List[str]
224 if self.config == SupportedConfig.TFM_MEDIUM.value:
Yanray Wanga279ca92023-07-26 15:01:10 +0800225 pre_make_cmd.append('cp {src} {dest}'
Yanray Wange4a36362023-07-25 10:37:11 +0800226 .format(src=TFM_MEDIUM_CONFIG_H, dest=CONFIG_H))
Yanray Wanga279ca92023-07-26 15:01:10 +0800227 pre_make_cmd.append('cp {src} {dest}'
Yanray Wange4a36362023-07-25 10:37:11 +0800228 .format(src=TFM_MEDIUM_CRYPTO_CONFIG_H,
229 dest=CRYPTO_CONFIG_H))
Yanray Wang5605c6f2023-07-21 16:09:00 +0800230
231 return pre_make_cmd
232
233 def infer_make_cflags(self) -> str:
234 """Infer CFLAGS by instance attributes in CodeSizeDistinctInfo."""
235 cflags = [] #type: typing.List[str]
236
237 # set optimization level
238 cflags.append(self.opt_level)
239 # set compiler by config
240 if self.config == SupportedConfig.TFM_MEDIUM.value:
241 self.compiler = 'armclang'
242 cflags.append('-mcpu=cortex-m33')
243 # set target
244 if self.compiler == 'armclang':
245 cflags.append('--target=arm-arm-none-eabi')
246
247 return ' '.join(cflags)
248
249 def infer_make_command(self) -> str:
250 """Infer make command by CFLAGS and CC."""
251
252 if self.check_correctness():
253 # set CFLAGS=
254 self.make_cmd.append('CFLAGS=\'{}\''.format(self.infer_make_cflags()))
255 # set CC=
256 self.make_cmd.append('CC={}'.format(self.compiler))
257 return ' '.join(self.make_cmd)
Yanray Wang6a862582023-05-24 12:24:38 +0800258 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800259 self.logger.error("Unsupported combination of architecture: {} " \
260 "and configuration: {}.\n"
Yanray Wang5605c6f2023-07-21 16:09:00 +0800261 .format(self.arch,
262 self.config))
Yanray Wang2ba9df22023-07-26 10:11:31 +0800263 self.logger.error("Please use supported combination of " \
Yanray Wang21127f72023-07-19 12:09:45 +0800264 "architecture and configuration:")
Yanray Wang923f9432023-07-17 12:43:00 +0800265 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang2ba9df22023-07-26 10:11:31 +0800266 self.logger.error(comb)
267 self.logger.error("")
268 self.logger.error("For your system, please use:")
Yanray Wang923f9432023-07-17 12:43:00 +0800269 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang802af162023-07-17 14:04:30 +0800270 if "default" in comb and self.host_arch not in comb:
Yanray Wang21f17442023-06-01 11:29:06 +0800271 continue
Yanray Wang2ba9df22023-07-26 10:11:31 +0800272 self.logger.error(comb)
Yanray Wang6a862582023-05-24 12:24:38 +0800273 sys.exit(1)
274
275
Yanray Wange0e27602023-07-14 17:37:45 +0800276class CodeSizeCalculator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800277 """ A calculator to calculate code size of library/*.o based on
Yanray Wange0e27602023-07-14 17:37:45 +0800278 Git revision and code size measurement tool.
279 """
280
Yanray Wang5605c6f2023-07-21 16:09:00 +0800281 def __init__( #pylint: disable=too-many-arguments
Yanray Wange0e27602023-07-14 17:37:45 +0800282 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800283 git_rev: str,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800284 pre_make_cmd: typing.List[str],
Yanray Wange0e27602023-07-14 17:37:45 +0800285 make_cmd: str,
Yanray Wang21127f72023-07-19 12:09:45 +0800286 measure_cmd: str,
287 logger: logging.Logger,
Yanray Wange0e27602023-07-14 17:37:45 +0800288 ) -> None:
289 """
Yanray Wang955671b2023-07-21 12:08:27 +0800290 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5605c6f2023-07-21 16:09:00 +0800291 :param pre_make_cmd: command to set up proper config before running make.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800292 :param make_cmd: command to build library/*.o.
293 :param measure_cmd: command to measure code size for library/*.o.
294 :param logger: logging module
Yanray Wange0e27602023-07-14 17:37:45 +0800295 """
296 self.repo_path = "."
297 self.git_command = "git"
298 self.make_clean = 'make clean'
299
Yanray Wang955671b2023-07-21 12:08:27 +0800300 self.git_rev = git_rev
Yanray Wang5605c6f2023-07-21 16:09:00 +0800301 self.pre_make_cmd = pre_make_cmd
Yanray Wange0e27602023-07-14 17:37:45 +0800302 self.make_cmd = make_cmd
Yanray Wang802af162023-07-17 14:04:30 +0800303 self.measure_cmd = measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800304 self.logger = logger
Yanray Wange0e27602023-07-14 17:37:45 +0800305
306 @staticmethod
Yanray Wang955671b2023-07-21 12:08:27 +0800307 def validate_git_revision(git_rev: str) -> str:
Yanray Wange0e27602023-07-14 17:37:45 +0800308 result = subprocess.check_output(["git", "rev-parse", "--verify",
Yanray Wang955671b2023-07-21 12:08:27 +0800309 git_rev + "^{commit}"],
310 shell=False, universal_newlines=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800311 return result[:7]
Yanray Wange0e27602023-07-14 17:37:45 +0800312
Yanray Wang21127f72023-07-19 12:09:45 +0800313 def _create_git_worktree(self) -> str:
Yanray Wang955671b2023-07-21 12:08:27 +0800314 """Create a separate worktree for Git revision.
315 If Git revision is current, use current worktree instead."""
Yanray Wange0e27602023-07-14 17:37:45 +0800316
Yanray Wang5605c6f2023-07-21 16:09:00 +0800317 if self.git_rev == 'current':
Yanray Wang21127f72023-07-19 12:09:45 +0800318 self.logger.debug("Using current work directory.")
Yanray Wange0e27602023-07-14 17:37:45 +0800319 git_worktree_path = self.repo_path
320 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800321 self.logger.debug("Creating git worktree for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800322 .format(self.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800323 git_worktree_path = os.path.join(self.repo_path,
Yanray Wang955671b2023-07-21 12:08:27 +0800324 "temp-" + self.git_rev)
Yanray Wange0e27602023-07-14 17:37:45 +0800325 subprocess.check_output(
326 [self.git_command, "worktree", "add", "--detach",
Yanray Wang955671b2023-07-21 12:08:27 +0800327 git_worktree_path, self.git_rev], cwd=self.repo_path,
Yanray Wange0e27602023-07-14 17:37:45 +0800328 stderr=subprocess.STDOUT
329 )
330
331 return git_worktree_path
332
Yanray Wang5605c6f2023-07-21 16:09:00 +0800333 @staticmethod
334 def backup_config_files(restore: bool) -> None:
335 """Backup / Restore config files."""
336 if restore:
337 shutil.move(CONFIG_H + BACKUP_SUFFIX, CONFIG_H)
338 shutil.move(CRYPTO_CONFIG_H + BACKUP_SUFFIX, CRYPTO_CONFIG_H)
339 else:
340 shutil.copy(CONFIG_H, CONFIG_H + BACKUP_SUFFIX)
341 shutil.copy(CRYPTO_CONFIG_H, CRYPTO_CONFIG_H + BACKUP_SUFFIX)
342
Yanray Wange0e27602023-07-14 17:37:45 +0800343 def _build_libraries(self, git_worktree_path: str) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800344 """Build library/*.o in the specified worktree."""
Yanray Wange0e27602023-07-14 17:37:45 +0800345
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800346 self.logger.debug("Building library/*.o for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800347 .format(self.git_rev))
Yanray Wange0e27602023-07-14 17:37:45 +0800348 my_environment = os.environ.copy()
349 try:
Yanray Wang5605c6f2023-07-21 16:09:00 +0800350 if self.git_rev == 'current':
351 self.backup_config_files(restore=False)
352 for pre_cmd in self.pre_make_cmd:
353 subprocess.check_output(
354 pre_cmd, env=my_environment, shell=True,
355 cwd=git_worktree_path, stderr=subprocess.STDOUT,
356 universal_newlines=True
357 )
Yanray Wange0e27602023-07-14 17:37:45 +0800358 subprocess.check_output(
359 self.make_clean, env=my_environment, shell=True,
360 cwd=git_worktree_path, stderr=subprocess.STDOUT,
Yanray Wang386c2f92023-07-20 15:32:15 +0800361 universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800362 )
363 subprocess.check_output(
364 self.make_cmd, env=my_environment, shell=True,
365 cwd=git_worktree_path, stderr=subprocess.STDOUT,
Yanray Wang386c2f92023-07-20 15:32:15 +0800366 universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800367 )
Yanray Wang5605c6f2023-07-21 16:09:00 +0800368 if self.git_rev == 'current':
369 self.backup_config_files(restore=True)
Yanray Wange0e27602023-07-14 17:37:45 +0800370 except subprocess.CalledProcessError as e:
371 self._handle_called_process_error(e, git_worktree_path)
372
Yanray Wang386c2f92023-07-20 15:32:15 +0800373 def _gen_raw_code_size(self, git_worktree_path: str) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800374 """Measure code size by a tool and return in UTF-8 encoding."""
Yanray Wang21127f72023-07-19 12:09:45 +0800375
376 self.logger.debug("Measuring code size for {} by `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800377 .format(self.git_rev,
Yanray Wang21127f72023-07-19 12:09:45 +0800378 self.measure_cmd.strip().split(' ')[0]))
Yanray Wange0e27602023-07-14 17:37:45 +0800379
380 res = {}
381 for mod, st_lib in MBEDTLS_STATIC_LIB.items():
382 try:
383 result = subprocess.check_output(
Yanray Wang802af162023-07-17 14:04:30 +0800384 [self.measure_cmd + ' ' + st_lib], cwd=git_worktree_path,
385 shell=True, universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800386 )
387 res[mod] = result
388 except subprocess.CalledProcessError as e:
389 self._handle_called_process_error(e, git_worktree_path)
390
391 return res
392
393 def _remove_worktree(self, git_worktree_path: str) -> None:
394 """Remove temporary worktree."""
395 if git_worktree_path != self.repo_path:
Yanray Wang21127f72023-07-19 12:09:45 +0800396 self.logger.debug("Removing temporary worktree {}."
397 .format(git_worktree_path))
Yanray Wange0e27602023-07-14 17:37:45 +0800398 subprocess.check_output(
399 [self.git_command, "worktree", "remove", "--force",
400 git_worktree_path], cwd=self.repo_path,
401 stderr=subprocess.STDOUT
402 )
403
404 def _handle_called_process_error(self, e: subprocess.CalledProcessError,
405 git_worktree_path: str) -> None:
406 """Handle a CalledProcessError and quit the program gracefully.
407 Remove any extra worktrees so that the script may be called again."""
408
409 # Tell the user what went wrong
Yanray Wang21127f72023-07-19 12:09:45 +0800410 self.logger.error(e, exc_info=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800411 self.logger.error("Process output:\n {}".format(e.output))
Yanray Wange0e27602023-07-14 17:37:45 +0800412
413 # Quit gracefully by removing the existing worktree
414 self._remove_worktree(git_worktree_path)
415 sys.exit(-1)
416
Yanray Wang386c2f92023-07-20 15:32:15 +0800417 def cal_libraries_code_size(self) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800418 """Do a complete round to calculate code size of library/*.o
419 by measurement tool.
420
421 :return A dictionary of measured code size
422 - typing.Dict[mod: str]
423 """
Yanray Wange0e27602023-07-14 17:37:45 +0800424
Yanray Wang21127f72023-07-19 12:09:45 +0800425 git_worktree_path = self._create_git_worktree()
Yanray Wang6ae94a02023-07-26 17:12:57 +0800426 try:
427 self._build_libraries(git_worktree_path)
428 res = self._gen_raw_code_size(git_worktree_path)
429 finally:
430 self._remove_worktree(git_worktree_path)
Yanray Wange0e27602023-07-14 17:37:45 +0800431
432 return res
433
434
Yanray Wang15c43f32023-07-17 11:17:12 +0800435class CodeSizeGenerator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800436 """ A generator based on size measurement tool for library/*.o.
Yanray Wang15c43f32023-07-17 11:17:12 +0800437
438 This is an abstract class. To use it, derive a class that implements
Yanray Wang95059002023-07-24 12:29:22 +0800439 write_record and write_comparison methods, then call both of them with
440 proper arguments.
Yanray Wang15c43f32023-07-17 11:17:12 +0800441 """
Yanray Wang21127f72023-07-19 12:09:45 +0800442 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800443 """
444 :param logger: logging module
445 """
Yanray Wang21127f72023-07-19 12:09:45 +0800446 self.logger = logger
447
Yanray Wang95059002023-07-24 12:29:22 +0800448 def write_record(
Yanray Wang15c43f32023-07-17 11:17:12 +0800449 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800450 git_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800451 code_size_text: typing.Dict[str, str],
452 output: typing_util.Writable
Yanray Wang15c43f32023-07-17 11:17:12 +0800453 ) -> None:
454 """Write size record into a file.
455
Yanray Wang955671b2023-07-21 12:08:27 +0800456 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800457 :param code_size_text:
458 string output (utf-8) from measurement tool of code size.
459 - typing.Dict[mod: str]
Yanray Wang95059002023-07-24 12:29:22 +0800460 :param output: output stream which the code size record is written to.
461 (Note: Normally write code size record into File)
Yanray Wang15c43f32023-07-17 11:17:12 +0800462 """
463 raise NotImplementedError
464
Yanray Wang95059002023-07-24 12:29:22 +0800465 def write_comparison(
Yanray Wang15c43f32023-07-17 11:17:12 +0800466 self,
467 old_rev: str,
468 new_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800469 output: typing_util.Writable,
470 with_markdown=False
Yanray Wang15c43f32023-07-17 11:17:12 +0800471 ) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800472 """Write a comparision result into a stream between two Git revisions.
Yanray Wang15c43f32023-07-17 11:17:12 +0800473
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800474 :param old_rev: old Git revision to compared with.
475 :param new_rev: new Git revision to compared with.
Yanray Wang95059002023-07-24 12:29:22 +0800476 :param output: output stream which the code size record is written to.
477 (File / sys.stdout)
478 :param with_markdown: write comparision result in a markdown table.
479 (Default: False)
Yanray Wang15c43f32023-07-17 11:17:12 +0800480 """
481 raise NotImplementedError
482
483
484class CodeSizeGeneratorWithSize(CodeSizeGenerator):
Yanray Wang16ebc572023-05-30 18:10:20 +0800485 """Code Size Base Class for size record saving and writing."""
486
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800487 class SizeEntry: # pylint: disable=too-few-public-methods
488 """Data Structure to only store information of code size."""
Yanray Wangdcf360d2023-07-27 15:28:20 +0800489 def __init__(self, text: int, data: int, bss: int, dec: int):
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800490 self.text = text
491 self.data = data
492 self.bss = bss
493 self.total = dec # total <=> dec
494
Yanray Wang21127f72023-07-19 12:09:45 +0800495 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800496 """ Variable code_size is used to store size info for any Git revisions.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800497 :param code_size:
498 Data Format as following:
Yanray Wangdcf360d2023-07-27 15:28:20 +0800499 code_size = {
500 git_rev: {
501 module: {
502 file_name: SizeEntry,
503 ...
504 },
505 ...
506 },
507 ...
508 }
Yanray Wang16ebc572023-05-30 18:10:20 +0800509 """
Yanray Wang21127f72023-07-19 12:09:45 +0800510 super().__init__(logger)
Yanray Wang16ebc572023-05-30 18:10:20 +0800511 self.code_size = {} #type: typing.Dict[str, typing.Dict]
Yanray Wangdcf360d2023-07-27 15:28:20 +0800512 self.mod_total_suffix = '-' + 'TOTALS'
Yanray Wang16ebc572023-05-30 18:10:20 +0800513
Yanray Wang955671b2023-07-21 12:08:27 +0800514 def _set_size_record(self, git_rev: str, mod: str, size_text: str) -> None:
515 """Store size information for target Git revision and high-level module.
Yanray Wang16ebc572023-05-30 18:10:20 +0800516
517 size_text Format: text data bss dec hex filename
518 """
519 size_record = {}
520 for line in size_text.splitlines()[1:]:
521 data = line.split()
Yanray Wangdcf360d2023-07-27 15:28:20 +0800522 if re.match(r'\s*\(TOTALS\)', data[5]):
523 data[5] = mod + self.mod_total_suffix
Yanray Wang9b174e92023-07-17 17:59:53 +0800524 # file_name: SizeEntry(text, data, bss, dec)
525 size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangdcf360d2023-07-27 15:28:20 +0800526 int(data[0]), int(data[1]), int(data[2]), int(data[3]))
Yanray Wang6ef50492023-07-26 14:59:37 +0800527 self.code_size.setdefault(git_rev, {}).update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800528
Yanray Wang955671b2023-07-21 12:08:27 +0800529 def read_size_record(self, git_rev: str, fname: str) -> None:
Yanray Wang16ebc572023-05-30 18:10:20 +0800530 """Read size information from csv file and write it into code_size.
531
532 fname Format: filename text data bss dec
533 """
534 mod = ""
535 size_record = {}
536 with open(fname, 'r') as csv_file:
537 for line in csv_file:
538 data = line.strip().split()
539 # check if we find the beginning of a module
540 if data and data[0] in MBEDTLS_STATIC_LIB:
541 mod = data[0]
542 continue
543
544 if mod:
Yanray Wang9b174e92023-07-17 17:59:53 +0800545 # file_name: SizeEntry(text, data, bss, dec)
546 size_record[data[0]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangdcf360d2023-07-27 15:28:20 +0800547 int(data[1]), int(data[2]), int(data[3]), int(data[4]))
Yanray Wang16ebc572023-05-30 18:10:20 +0800548
549 # check if we hit record for the end of a module
Yanray Wangdcf360d2023-07-27 15:28:20 +0800550 m = re.match(r'\w+' + self.mod_total_suffix, line)
Yanray Wang16ebc572023-05-30 18:10:20 +0800551 if m:
Yanray Wang955671b2023-07-21 12:08:27 +0800552 if git_rev in self.code_size:
553 self.code_size[git_rev].update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800554 else:
Yanray Wang955671b2023-07-21 12:08:27 +0800555 self.code_size[git_rev] = {mod: size_record}
Yanray Wang16ebc572023-05-30 18:10:20 +0800556 mod = ""
557 size_record = {}
558
Yanray Wang95059002023-07-24 12:29:22 +0800559 def write_record(
Yanray Wang16ebc572023-05-30 18:10:20 +0800560 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800561 git_rev: str,
Yanray Wang95059002023-07-24 12:29:22 +0800562 code_size_text: typing.Dict[str, str],
Yanray Wang16ebc572023-05-30 18:10:20 +0800563 output: typing_util.Writable
564 ) -> None:
565 """Write size information to a file.
566
Yanray Wangdcf360d2023-07-27 15:28:20 +0800567 Writing Format: filename text data bss total(dec)
Yanray Wang16ebc572023-05-30 18:10:20 +0800568 """
Yanray Wang95059002023-07-24 12:29:22 +0800569 for mod, size_text in code_size_text.items():
570 self._set_size_record(git_rev, mod, size_text)
571
Yanray Wangb664cb72023-07-18 12:28:35 +0800572 format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n"
573 output.write(format_string.format("filename",
574 "text", "data", "bss", "total"))
Yanray Wang16ebc572023-05-30 18:10:20 +0800575
Yanray Wangdcf360d2023-07-27 15:28:20 +0800576 for mod, f_size in self.code_size[git_rev].items():
577 output.write("\n" + mod + "\n")
578 for fname, size_entry in f_size.items():
579 output.write(format_string
580 .format(fname,
581 size_entry.text, size_entry.data,
582 size_entry.bss, size_entry.total))
583
584 def write_comparison( # pylint: disable=too-many-locals
Yanray Wang16ebc572023-05-30 18:10:20 +0800585 self,
586 old_rev: str,
587 new_rev: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800588 output: typing_util.Writable,
Yanray Wang95059002023-07-24 12:29:22 +0800589 with_markdown=False
Yanray Wang16ebc572023-05-30 18:10:20 +0800590 ) -> None:
591 """Write comparison result into a file.
592
Yanray Wangdcf360d2023-07-27 15:28:20 +0800593 Writing Format: filename new(text) new(data) change(text) change(data)
Yanray Wang16ebc572023-05-30 18:10:20 +0800594 """
Yanray Wangdcf360d2023-07-27 15:28:20 +0800595 header_line = ["filename", "new(text)", "change(text)", "new(data)",
596 "change(data)"]
Yanray Wang9b174e92023-07-17 17:59:53 +0800597
Yanray Wangb664cb72023-07-18 12:28:35 +0800598 if with_markdown:
Yanray Wangdcf360d2023-07-27 15:28:20 +0800599 dash_line = [":----", "----:", "----:", "----:", "----:"]
600 line_format = "| {0:<30} | {1:<10} | {3:<10} | {2:<12} | {4:<12} |\n"
601 bold_text = lambda x: '**' + str(x) + '**'
Yanray Wangb664cb72023-07-18 12:28:35 +0800602 else:
Yanray Wangdcf360d2023-07-27 15:28:20 +0800603 line_format = "{0:<30} {1:<10} {3:<10} {2:<12} {4:<12}\n"
Yanray Wangb664cb72023-07-18 12:28:35 +0800604
Yanray Wangdcf360d2023-07-27 15:28:20 +0800605 def cal_sect_change(
606 old_size: typing.Optional[CodeSizeGeneratorWithSize.SizeEntry],
607 new_size: typing.Optional[CodeSizeGeneratorWithSize.SizeEntry],
608 sect: str
609 ) -> typing.List:
610 """Inner helper function to calculate size change for a section.
Yanray Wangb664cb72023-07-18 12:28:35 +0800611
Yanray Wangdcf360d2023-07-27 15:28:20 +0800612 Convention for special cases:
613 - If the object has been removed in new Git revision,
614 the size is minus code size of old Git revision;
615 the size change is marked as `Removed`,
616 - If the object only exists in new Git revision,
617 the size is code size of new Git revision;
618 the size change is marked as `None`,
Yanray Wang9b174e92023-07-17 17:59:53 +0800619
Yanray Wangdcf360d2023-07-27 15:28:20 +0800620 :param: old_size: code size for objects in old Git revision.
621 :param: new_size: code size for objects in new Git revision.
622 :param: sect: section to calculate from `size` tool. This could be
623 any instance variable in SizeEntry.
624 :return: List of [section size of objects for new Git revision,
625 section size change of objects between two Git revisions]
626 """
627 if old_size and new_size:
628 new_attr = new_size.__dict__[sect]
629 change_attr = new_size.__dict__[sect] - old_size.__dict__[sect]
630 elif old_size:
631 new_attr = - old_size.__dict__[sect]
632 change_attr = 'Removed'
633 elif new_size:
634 new_attr = new_size.__dict__[sect]
635 change_attr = 'None'
Yanray Wang9b174e92023-07-17 17:59:53 +0800636 else:
Yanray Wangdcf360d2023-07-27 15:28:20 +0800637 # Should never happen
638 new_attr = 'Error'
639 change_attr = 'Error'
640 return [new_attr, change_attr]
641
642 # sort dictionary by key
643 sort_by_k = lambda item: item[0].lower()
644 def get_results(
645 f_rev_size:
646 typing.Dict[str,
647 typing.Dict[str,
648 CodeSizeGeneratorWithSize.SizeEntry]]
649 ) -> typing.List:
650 """Return List of results in the format of:
651 [filename, new(text), change(text), new(data), change(data)]
652 """
653 res = []
654 for fname, revs_size in sorted(f_rev_size.items(), key=sort_by_k):
655 old_size = revs_size.get(old_rev)
656 new_size = revs_size.get(new_rev)
657
658 text_sect = cal_sect_change(old_size, new_size, 'text')
659 data_sect = cal_sect_change(old_size, new_size, 'data')
660 # skip the files that haven't changed in code size
661 if text_sect[1] == 0 and data_sect[1] == 0:
662 continue
663
664 res.append([fname, *text_sect, *data_sect])
665 return res
666
667 # write header
668 output.write(line_format.format(*header_line))
669 if with_markdown:
670 output.write(line_format.format(*dash_line))
671 for mod in MBEDTLS_STATIC_LIB:
672 # convert self.code_size to:
673 # {
674 # file_name: {
675 # old_rev: SizeEntry,
676 # new_rev: SizeEntry
677 # },
678 # ...
679 # }
680 f_rev_size = {} #type: typing.Dict[str, typing.Dict]
681 for fname, size_entry in self.code_size[old_rev][mod].items():
682 f_rev_size.setdefault(fname, {}).update({old_rev: size_entry})
683 for fname, size_entry in self.code_size[new_rev][mod].items():
684 f_rev_size.setdefault(fname, {}).update({new_rev: size_entry})
685
686 mod_total_sz = f_rev_size.pop(mod + self.mod_total_suffix)
687 res = get_results(f_rev_size)
688 total_clm = get_results({mod + self.mod_total_suffix: mod_total_sz})
689 if with_markdown:
690 # bold row of mod-TOTALS in markdown table
691 total_clm = [[bold_text(j) for j in i] for i in total_clm]
692 res += total_clm
693
694 # write comparison result
695 for line in res:
696 output.write(line_format.format(*line))
Yanray Wang16ebc572023-05-30 18:10:20 +0800697
698
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800699class CodeSizeComparison:
Xiaofei Bai2400b502021-10-21 12:22:58 +0000700 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000701
Yanray Wang955671b2023-07-21 12:08:27 +0800702 def __init__( #pylint: disable=too-many-arguments
Yanray Wang72b105f2023-05-31 15:20:39 +0800703 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800704 old_size_dist_info: CodeSizeDistinctInfo,
705 new_size_dist_info: CodeSizeDistinctInfo,
706 size_common_info: CodeSizeCommonInfo,
707 result_options: CodeSizeResultInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800708 logger: logging.Logger,
Yanray Wang72b105f2023-05-31 15:20:39 +0800709 ) -> None:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000710 """
Yanray Wang955671b2023-07-21 12:08:27 +0800711 :param old_size_dist_info: CodeSizeDistinctInfo containing old distinct
712 info to compare code size with.
713 :param new_size_dist_info: CodeSizeDistinctInfo containing new distinct
714 info to take as comparision base.
715 :param size_common_info: CodeSizeCommonInfo containing common info for
716 both old and new size distinct info and
717 measurement tool.
718 :param result_options: CodeSizeResultInfo containing results options for
719 code size record and comparision.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800720 :param logger: logging module
Xiaofei Baibca03e52021-09-09 09:42:37 +0000721 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000722
Yanray Wang21127f72023-07-19 12:09:45 +0800723 self.logger = logger
724
Yanray Wang955671b2023-07-21 12:08:27 +0800725 self.old_size_dist_info = old_size_dist_info
726 self.new_size_dist_info = new_size_dist_info
727 self.size_common_info = size_common_info
Yanray Wang5605c6f2023-07-21 16:09:00 +0800728 # infer pre make command
729 self.old_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
730 self.old_size_dist_info, self.size_common_info.host_arch,
731 self.logger).infer_pre_make_command()
732 self.new_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
733 self.new_size_dist_info, self.size_common_info.host_arch,
734 self.logger).infer_pre_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800735 # infer make command
Yanray Wang955671b2023-07-21 12:08:27 +0800736 self.old_size_dist_info.make_cmd = CodeSizeBuildInfo(
737 self.old_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800738 self.logger).infer_make_command()
Yanray Wang955671b2023-07-21 12:08:27 +0800739 self.new_size_dist_info.make_cmd = CodeSizeBuildInfo(
740 self.new_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800741 self.logger).infer_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800742 # initialize size parser with corresponding measurement tool
Yanray Wang21127f72023-07-19 12:09:45 +0800743 self.code_size_generator = self.__generate_size_parser()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000744
Yanray Wang955671b2023-07-21 12:08:27 +0800745 self.result_options = result_options
746 self.csv_dir = os.path.abspath(self.result_options.record_dir)
747 os.makedirs(self.csv_dir, exist_ok=True)
748 self.comp_dir = os.path.abspath(self.result_options.comp_dir)
749 os.makedirs(self.comp_dir, exist_ok=True)
750
Yanray Wang21127f72023-07-19 12:09:45 +0800751 def __generate_size_parser(self):
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800752 """Generate a parser for the corresponding measurement tool."""
Yanray Wang955671b2023-07-21 12:08:27 +0800753 if re.match(r'size', self.size_common_info.measure_cmd.strip()):
Yanray Wang21127f72023-07-19 12:09:45 +0800754 return CodeSizeGeneratorWithSize(self.logger)
Yanray Wang802af162023-07-17 14:04:30 +0800755 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800756 self.logger.error("Unsupported measurement tool: `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800757 .format(self.size_common_info.measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800758 .strip().split(' ')[0]))
Yanray Wang802af162023-07-17 14:04:30 +0800759 sys.exit(1)
760
Yanray Wang386c2f92023-07-20 15:32:15 +0800761 def cal_code_size(
762 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800763 size_dist_info: CodeSizeDistinctInfo
Yanray Wang386c2f92023-07-20 15:32:15 +0800764 ) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800765 """Calculate code size of library/*.o in a UTF-8 encoding"""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000766
Yanray Wang955671b2023-07-21 12:08:27 +0800767 return CodeSizeCalculator(size_dist_info.git_rev,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800768 size_dist_info.pre_make_cmd,
Yanray Wang955671b2023-07-21 12:08:27 +0800769 size_dist_info.make_cmd,
770 self.size_common_info.measure_cmd,
Yanray Wang21127f72023-07-19 12:09:45 +0800771 self.logger).cal_libraries_code_size()
Yanray Wang8804db92023-05-30 18:18:18 +0800772
Yanray Wang955671b2023-07-21 12:08:27 +0800773 def gen_code_size_report(self, size_dist_info: CodeSizeDistinctInfo) -> None:
Yanray Wang5e9130a2023-07-17 11:55:54 +0800774 """Generate code size record and write it into a file."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000775
Yanray Wang21127f72023-07-19 12:09:45 +0800776 self.logger.info("Start to generate code size record for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800777 .format(size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800778 output_file = os.path.join(
779 self.csv_dir,
780 '{}-{}.csv'
781 .format(size_dist_info.get_info_indication(),
782 self.size_common_info.get_info_indication()))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000783 # Check if the corresponding record exists
Yanray Wang955671b2023-07-21 12:08:27 +0800784 if size_dist_info.git_rev != "current" and \
Yanray Wang21127f72023-07-19 12:09:45 +0800785 os.path.exists(output_file):
786 self.logger.debug("Code size csv file for {} already exists."
Yanray Wang955671b2023-07-21 12:08:27 +0800787 .format(size_dist_info.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800788 self.code_size_generator.read_size_record(
Yanray Wang955671b2023-07-21 12:08:27 +0800789 size_dist_info.git_rev, output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000790 else:
Yanray Wang95059002023-07-24 12:29:22 +0800791 # measure code size
792 code_size_text = self.cal_code_size(size_dist_info)
793
794 self.logger.debug("Generating code size csv for {}."
795 .format(size_dist_info.git_rev))
796 output = open(output_file, "w")
797 self.code_size_generator.write_record(
798 size_dist_info.git_rev, code_size_text, output)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000799
Yanray Wang386c2f92023-07-20 15:32:15 +0800800 def gen_code_size_comparison(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800801 """Generate results of code size changes between two Git revisions,
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800802 old and new.
803
Yanray Wang955671b2023-07-21 12:08:27 +0800804 - Measured code size result of these two Git revisions must be available.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800805 - The result is directed into either file / stdout depending on
Yanray Wang955671b2023-07-21 12:08:27 +0800806 the option, size_common_info.result_options.stdout. (Default: file)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800807 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000808
Yanray Wang21127f72023-07-19 12:09:45 +0800809 self.logger.info("Start to generate comparision result between "\
810 "{} and {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800811 .format(self.old_size_dist_info.git_rev,
812 self.new_size_dist_info.git_rev))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800813 if self.result_options.stdout:
814 output = sys.stdout
815 else:
816 output_file = os.path.join(
817 self.comp_dir,
Yanray Wangb1673202023-07-28 13:47:19 +0800818 '{}-{}-{}.{}'
Yanray Wanga6cf6922023-07-24 15:20:42 +0800819 .format(self.old_size_dist_info.get_info_indication(),
820 self.new_size_dist_info.get_info_indication(),
Yanray Wangb1673202023-07-28 13:47:19 +0800821 self.size_common_info.get_info_indication(),
822 'md' if self.result_options.with_markdown else 'csv'))
Yanray Wanga6cf6922023-07-24 15:20:42 +0800823 output = open(output_file, "w")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000824
Yanray Wang95059002023-07-24 12:29:22 +0800825 self.logger.debug("Generating comparison results between {} and {}."
826 .format(self.old_size_dist_info.git_rev,
827 self.new_size_dist_info.git_rev))
Yanray Wangea842e72023-07-26 10:34:39 +0800828 if self.result_options.with_markdown or self.result_options.stdout:
829 print("Measure code size between {} and {} by `{}`."
830 .format(self.old_size_dist_info.get_info_indication(),
831 self.new_size_dist_info.get_info_indication(),
832 self.size_common_info.get_info_indication()),
833 file=output)
Yanray Wang95059002023-07-24 12:29:22 +0800834 self.code_size_generator.write_comparison(
Yanray Wang955671b2023-07-21 12:08:27 +0800835 self.old_size_dist_info.git_rev,
836 self.new_size_dist_info.git_rev,
Yanray Wang95059002023-07-24 12:29:22 +0800837 output, self.result_options.with_markdown)
Yanray Wang21127f72023-07-19 12:09:45 +0800838
Yanray Wang386c2f92023-07-20 15:32:15 +0800839 def get_comparision_results(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800840 """Compare size of library/*.o between self.old_size_dist_info and
841 self.old_size_dist_info and generate the result file."""
Gilles Peskined9071e72022-09-18 21:17:09 +0200842 build_tree.check_repo_path()
Yanray Wang955671b2023-07-21 12:08:27 +0800843 self.gen_code_size_report(self.old_size_dist_info)
844 self.gen_code_size_report(self.new_size_dist_info)
Yanray Wang386c2f92023-07-20 15:32:15 +0800845 self.gen_code_size_comparison()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000846
Xiaofei Bai2400b502021-10-21 12:22:58 +0000847def main():
Yanray Wang502c54f2023-05-31 11:41:36 +0800848 parser = argparse.ArgumentParser(description=(__doc__))
849 group_required = parser.add_argument_group(
850 'required arguments',
851 'required arguments to parse for running ' + os.path.basename(__file__))
852 group_required.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800853 '-o', '--old-rev', type=str, required=True,
Yanray Wang955671b2023-07-21 12:08:27 +0800854 help='old Git revision for comparison.')
Yanray Wang502c54f2023-05-31 11:41:36 +0800855
856 group_optional = parser.add_argument_group(
857 'optional arguments',
858 'optional arguments to parse for running ' + os.path.basename(__file__))
859 group_optional.add_argument(
Yanray Wang9e8b6712023-07-26 15:37:26 +0800860 '--record-dir', type=str, default='code_size_records',
Yanray Wang955671b2023-07-21 12:08:27 +0800861 help='directory where code size record is stored. '
862 '(Default: code_size_records)')
863 group_optional.add_argument(
Yanray Wang9e8b6712023-07-26 15:37:26 +0800864 '--comp-dir', type=str, default='comparison',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800865 help='directory where comparison result is stored. '
866 '(Default: comparison)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800867 group_optional.add_argument(
Yanray Wang68265f42023-07-26 14:44:52 +0800868 '-n', '--new-rev', type=str, default='current',
Yanray Wang955671b2023-07-21 12:08:27 +0800869 help='new Git revision as comparison base. '
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800870 '(Default is the current work directory, including uncommitted '
871 'changes.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800872 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800873 '-a', '--arch', type=str, default=detect_arch(),
Yanray Wang23bd5322023-05-24 11:03:59 +0800874 choices=list(map(lambda s: s.value, SupportedArch)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800875 help='Specify architecture for code size comparison. '
876 '(Default is the host architecture.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800877 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800878 '-c', '--config', type=str, default=SupportedConfig.DEFAULT.value,
Yanray Wang6a862582023-05-24 12:24:38 +0800879 choices=list(map(lambda s: s.value, SupportedConfig)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800880 help='Specify configuration type for code size comparison. '
881 '(Default is the current MbedTLS configuration.)')
Yanray Wangb664cb72023-07-18 12:28:35 +0800882 group_optional.add_argument(
883 '--markdown', action='store_true', dest='markdown',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800884 help='Show comparision of code size in a markdown table. '
885 '(Only show the files that have changed).')
Yanray Wang227576a2023-07-18 14:35:05 +0800886 group_optional.add_argument(
887 '--stdout', action='store_true', dest='stdout',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800888 help='Set this option to direct comparison result into sys.stdout. '
889 '(Default: file)')
Yanray Wang21127f72023-07-19 12:09:45 +0800890 group_optional.add_argument(
891 '--verbose', action='store_true', dest='verbose',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800892 help='Show logs in detail for code size measurement. '
893 '(Default: False)')
Xiaofei Baibca03e52021-09-09 09:42:37 +0000894 comp_args = parser.parse_args()
895
Yanray Wang21127f72023-07-19 12:09:45 +0800896 logger = logging.getLogger()
897 logging_util.configure_logger(logger)
Yanray Wang533cde22023-07-26 10:17:17 +0800898 if comp_args.stdout and not comp_args.verbose:
899 logger.setLevel(logging.ERROR)
900 else:
901 logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO)
Yanray Wang21127f72023-07-19 12:09:45 +0800902
Yanray Wang9e8b6712023-07-26 15:37:26 +0800903 if os.path.isfile(comp_args.record_dir):
904 logger.error("record directory: {} is not a directory"
905 .format(comp_args.record_dir))
906 sys.exit(1)
Yanray Wang955671b2023-07-21 12:08:27 +0800907 if os.path.isfile(comp_args.comp_dir):
Yanray Wang9e8b6712023-07-26 15:37:26 +0800908 logger.error("comparison directory: {} is not a directory"
909 .format(comp_args.comp_dir))
910 sys.exit(1)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000911
Yanray Wang68265f42023-07-26 14:44:52 +0800912 comp_args.old_rev = CodeSizeCalculator.validate_git_revision(
913 comp_args.old_rev)
914 if comp_args.new_rev != 'current':
915 comp_args.new_rev = CodeSizeCalculator.validate_git_revision(
Yanray Wang955671b2023-07-21 12:08:27 +0800916 comp_args.new_rev)
Xiaofei Bai2400b502021-10-21 12:22:58 +0000917
Yanray Wang5605c6f2023-07-21 16:09:00 +0800918 # version, git_rev, arch, config, compiler, opt_level
Yanray Wang955671b2023-07-21 12:08:27 +0800919 old_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang68265f42023-07-26 14:44:52 +0800920 'old', comp_args.old_rev, comp_args.arch, comp_args.config, 'cc', '-Os')
Yanray Wang955671b2023-07-21 12:08:27 +0800921 new_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang68265f42023-07-26 14:44:52 +0800922 'new', comp_args.new_rev, comp_args.arch, comp_args.config, 'cc', '-Os')
Yanray Wang5605c6f2023-07-21 16:09:00 +0800923 # host_arch, measure_cmd
Yanray Wang955671b2023-07-21 12:08:27 +0800924 size_common_info = CodeSizeCommonInfo(
925 detect_arch(), 'size -t')
Yanray Wang5605c6f2023-07-21 16:09:00 +0800926 # record_dir, comp_dir, with_markdown, stdout
Yanray Wang955671b2023-07-21 12:08:27 +0800927 result_options = CodeSizeResultInfo(
928 comp_args.record_dir, comp_args.comp_dir,
929 comp_args.markdown, comp_args.stdout)
Yanray Wang923f9432023-07-17 12:43:00 +0800930
Yanray Wanga6cf6922023-07-24 15:20:42 +0800931 logger.info("Measure code size between {} and {} by `{}`."
932 .format(old_size_dist_info.get_info_indication(),
933 new_size_dist_info.get_info_indication(),
934 size_common_info.get_info_indication()))
Yanray Wang955671b2023-07-21 12:08:27 +0800935 CodeSizeComparison(old_size_dist_info, new_size_dist_info,
936 size_common_info, result_options,
937 logger).get_comparision_results()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000938
Xiaofei Baibca03e52021-09-09 09:42:37 +0000939if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000940 main()