blob: 30251835cdc4e058f578d08ad240e81f32fea525 [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
93
94class CodeSizeCommonInfo: # pylint: disable=too-few-public-methods
95 """Data structure to store common information for code size comparison."""
96 def __init__(
97 self,
98 host_arch: str,
99 measure_cmd: str,
100 ) -> None:
101 """
102 :param host_arch: host architecture.
103 :param measure_cmd: command to measure code size for library/*.o.
104 """
105 self.host_arch = host_arch
106 self.measure_cmd = measure_cmd
107
108
109class CodeSizeResultInfo: # pylint: disable=too-few-public-methods
110 """Data structure to store result options for code size comparison."""
111 def __init__(
112 self,
113 record_dir: str,
114 comp_dir: str,
115 with_markdown=False,
116 stdout=False,
117 ) -> None:
118 """
119 :param record_dir: directory to store code size record.
120 :param comp_dir: directory to store results of code size comparision.
121 :param with_markdown: write comparision result into a markdown table.
122 (Default: False)
123 :param stdout: direct comparison result into sys.stdout.
124 (Default False)
125 """
126 self.record_dir = record_dir
127 self.comp_dir = comp_dir
128 self.with_markdown = with_markdown
129 self.stdout = stdout
130
131
Yanray Wang23bd5322023-05-24 11:03:59 +0800132DETECT_ARCH_CMD = "cc -dM -E - < /dev/null"
133def detect_arch() -> str:
134 """Auto-detect host architecture."""
135 cc_output = subprocess.check_output(DETECT_ARCH_CMD, shell=True).decode()
Yanray Wang386c2f92023-07-20 15:32:15 +0800136 if '__aarch64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800137 return SupportedArch.AARCH64.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800138 if '__arm__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800139 return SupportedArch.AARCH32.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800140 if '__x86_64__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800141 return SupportedArch.X86_64.value
Yanray Wang386c2f92023-07-20 15:32:15 +0800142 if '__x86__' in cc_output:
Yanray Wang23bd5322023-05-24 11:03:59 +0800143 return SupportedArch.X86.value
144 else:
145 print("Unknown host architecture, cannot auto-detect arch.")
146 sys.exit(1)
Gilles Peskined9071e72022-09-18 21:17:09 +0200147
Yanray Wang5605c6f2023-07-21 16:09:00 +0800148TFM_MEDIUM_CONFIG_H = 'configs/tfm_mbedcrypto_config_profile_medium.h'
149TFM_MEDIUM_CRYPTO_CONFIG_H = 'configs/crypto_config_profile_medium.h'
150
151CONFIG_H = 'include/mbedtls/mbedtls_config.h'
152CRYPTO_CONFIG_H = 'include/psa/crypto_config.h'
153BACKUP_SUFFIX = '.code_size.bak'
154
Yanray Wang923f9432023-07-17 12:43:00 +0800155class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods
Yanray Wang6a862582023-05-24 12:24:38 +0800156 """Gather information used to measure code size.
157
158 It collects information about architecture, configuration in order to
159 infer build command for code size measurement.
160 """
161
Yanray Wangc18cd892023-05-31 11:08:04 +0800162 SupportedArchConfig = [
Yanray Wang386c2f92023-07-20 15:32:15 +0800163 '-a ' + SupportedArch.AARCH64.value + ' -c ' + SupportedConfig.DEFAULT.value,
164 '-a ' + SupportedArch.AARCH32.value + ' -c ' + SupportedConfig.DEFAULT.value,
165 '-a ' + SupportedArch.X86_64.value + ' -c ' + SupportedConfig.DEFAULT.value,
166 '-a ' + SupportedArch.X86.value + ' -c ' + SupportedConfig.DEFAULT.value,
167 '-a ' + SupportedArch.ARMV8_M.value + ' -c ' + SupportedConfig.TFM_MEDIUM.value,
Yanray Wangc18cd892023-05-31 11:08:04 +0800168 ]
169
Yanray Wang802af162023-07-17 14:04:30 +0800170 def __init__(
171 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800172 size_dist_info: CodeSizeDistinctInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800173 host_arch: str,
174 logger: logging.Logger,
Yanray Wang802af162023-07-17 14:04:30 +0800175 ) -> None:
Yanray Wang6a862582023-05-24 12:24:38 +0800176 """
Yanray Wang955671b2023-07-21 12:08:27 +0800177 :param size_dist_info:
178 CodeSizeDistinctInfo containing info for code size measurement.
179 - size_dist_info.arch: architecture to measure code size on.
180 - size_dist_info.config: configuration type to measure
181 code size with.
Yanray Wang5605c6f2023-07-21 16:09:00 +0800182 - size_dist_info.compiler: compiler used to build library/*.o.
183 - size_dist_info.opt_level: Options that control optimization.
184 (E.g. -Os)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800185 :param host_arch: host architecture.
186 :param logger: logging module
Yanray Wang6a862582023-05-24 12:24:38 +0800187 """
Yanray Wang5605c6f2023-07-21 16:09:00 +0800188 self.arch = size_dist_info.arch
189 self.config = size_dist_info.config
190 self.compiler = size_dist_info.compiler
191 self.opt_level = size_dist_info.opt_level
192
193 self.make_cmd = ['make', '-j', 'lib']
194
Yanray Wang802af162023-07-17 14:04:30 +0800195 self.host_arch = host_arch
Yanray Wang21127f72023-07-19 12:09:45 +0800196 self.logger = logger
Yanray Wang6a862582023-05-24 12:24:38 +0800197
Yanray Wang5605c6f2023-07-21 16:09:00 +0800198 def check_correctness(self) -> bool:
199 """Check whether we are using proper / supported combination
200 of information to build library/*.o."""
Yanray Wang6a862582023-05-24 12:24:38 +0800201
Yanray Wang5605c6f2023-07-21 16:09:00 +0800202 # default config
203 if self.config == SupportedConfig.DEFAULT.value and \
204 self.arch == self.host_arch:
205 return True
206 # TF-M
207 elif self.arch == SupportedArch.ARMV8_M.value and \
208 self.config == SupportedConfig.TFM_MEDIUM.value:
209 return True
210
211 return False
212
213 def infer_pre_make_command(self) -> typing.List[str]:
214 """Infer command to set up proper configuration before running make."""
215 pre_make_cmd = [] #type: typing.List[str]
216 if self.config == SupportedConfig.TFM_MEDIUM.value:
217 pre_make_cmd.append('cp -r {} {}'
218 .format(TFM_MEDIUM_CONFIG_H, CONFIG_H))
219 pre_make_cmd.append('cp -r {} {}'
220 .format(TFM_MEDIUM_CRYPTO_CONFIG_H,
221 CRYPTO_CONFIG_H))
222
223 return pre_make_cmd
224
225 def infer_make_cflags(self) -> str:
226 """Infer CFLAGS by instance attributes in CodeSizeDistinctInfo."""
227 cflags = [] #type: typing.List[str]
228
229 # set optimization level
230 cflags.append(self.opt_level)
231 # set compiler by config
232 if self.config == SupportedConfig.TFM_MEDIUM.value:
233 self.compiler = 'armclang'
234 cflags.append('-mcpu=cortex-m33')
235 # set target
236 if self.compiler == 'armclang':
237 cflags.append('--target=arm-arm-none-eabi')
238
239 return ' '.join(cflags)
240
241 def infer_make_command(self) -> str:
242 """Infer make command by CFLAGS and CC."""
243
244 if self.check_correctness():
245 # set CFLAGS=
246 self.make_cmd.append('CFLAGS=\'{}\''.format(self.infer_make_cflags()))
247 # set CC=
248 self.make_cmd.append('CC={}'.format(self.compiler))
249 return ' '.join(self.make_cmd)
Yanray Wang6a862582023-05-24 12:24:38 +0800250 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800251 self.logger.error("Unsupported combination of architecture: {} " \
252 "and configuration: {}.\n"
Yanray Wang5605c6f2023-07-21 16:09:00 +0800253 .format(self.arch,
254 self.config))
Yanray Wang21127f72023-07-19 12:09:45 +0800255 self.logger.info("Please use supported combination of " \
256 "architecture and configuration:")
Yanray Wang923f9432023-07-17 12:43:00 +0800257 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang21127f72023-07-19 12:09:45 +0800258 self.logger.info(comb)
259 self.logger.info("")
260 self.logger.info("For your system, please use:")
Yanray Wang923f9432023-07-17 12:43:00 +0800261 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang802af162023-07-17 14:04:30 +0800262 if "default" in comb and self.host_arch not in comb:
Yanray Wang21f17442023-06-01 11:29:06 +0800263 continue
Yanray Wang21127f72023-07-19 12:09:45 +0800264 self.logger.info(comb)
Yanray Wang6a862582023-05-24 12:24:38 +0800265 sys.exit(1)
266
267
Yanray Wange0e27602023-07-14 17:37:45 +0800268class CodeSizeCalculator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800269 """ A calculator to calculate code size of library/*.o based on
Yanray Wange0e27602023-07-14 17:37:45 +0800270 Git revision and code size measurement tool.
271 """
272
Yanray Wang5605c6f2023-07-21 16:09:00 +0800273 def __init__( #pylint: disable=too-many-arguments
Yanray Wange0e27602023-07-14 17:37:45 +0800274 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800275 git_rev: str,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800276 pre_make_cmd: typing.List[str],
Yanray Wange0e27602023-07-14 17:37:45 +0800277 make_cmd: str,
Yanray Wang21127f72023-07-19 12:09:45 +0800278 measure_cmd: str,
279 logger: logging.Logger,
Yanray Wange0e27602023-07-14 17:37:45 +0800280 ) -> None:
281 """
Yanray Wang955671b2023-07-21 12:08:27 +0800282 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5605c6f2023-07-21 16:09:00 +0800283 :param pre_make_cmd: command to set up proper config before running make.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800284 :param make_cmd: command to build library/*.o.
285 :param measure_cmd: command to measure code size for library/*.o.
286 :param logger: logging module
Yanray Wange0e27602023-07-14 17:37:45 +0800287 """
288 self.repo_path = "."
289 self.git_command = "git"
290 self.make_clean = 'make clean'
291
Yanray Wang955671b2023-07-21 12:08:27 +0800292 self.git_rev = git_rev
Yanray Wang5605c6f2023-07-21 16:09:00 +0800293 self.pre_make_cmd = pre_make_cmd
Yanray Wange0e27602023-07-14 17:37:45 +0800294 self.make_cmd = make_cmd
Yanray Wang802af162023-07-17 14:04:30 +0800295 self.measure_cmd = measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800296 self.logger = logger
Yanray Wange0e27602023-07-14 17:37:45 +0800297
298 @staticmethod
Yanray Wang955671b2023-07-21 12:08:27 +0800299 def validate_git_revision(git_rev: str) -> str:
Yanray Wange0e27602023-07-14 17:37:45 +0800300 result = subprocess.check_output(["git", "rev-parse", "--verify",
Yanray Wang955671b2023-07-21 12:08:27 +0800301 git_rev + "^{commit}"],
302 shell=False, universal_newlines=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800303 return result[:7]
Yanray Wange0e27602023-07-14 17:37:45 +0800304
Yanray Wang21127f72023-07-19 12:09:45 +0800305 def _create_git_worktree(self) -> str:
Yanray Wang955671b2023-07-21 12:08:27 +0800306 """Create a separate worktree for Git revision.
307 If Git revision is current, use current worktree instead."""
Yanray Wange0e27602023-07-14 17:37:45 +0800308
Yanray Wang5605c6f2023-07-21 16:09:00 +0800309 if self.git_rev == 'current':
Yanray Wang21127f72023-07-19 12:09:45 +0800310 self.logger.debug("Using current work directory.")
Yanray Wange0e27602023-07-14 17:37:45 +0800311 git_worktree_path = self.repo_path
312 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800313 self.logger.debug("Creating git worktree for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800314 .format(self.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800315 git_worktree_path = os.path.join(self.repo_path,
Yanray Wang955671b2023-07-21 12:08:27 +0800316 "temp-" + self.git_rev)
Yanray Wange0e27602023-07-14 17:37:45 +0800317 subprocess.check_output(
318 [self.git_command, "worktree", "add", "--detach",
Yanray Wang955671b2023-07-21 12:08:27 +0800319 git_worktree_path, self.git_rev], cwd=self.repo_path,
Yanray Wange0e27602023-07-14 17:37:45 +0800320 stderr=subprocess.STDOUT
321 )
322
323 return git_worktree_path
324
Yanray Wang5605c6f2023-07-21 16:09:00 +0800325 @staticmethod
326 def backup_config_files(restore: bool) -> None:
327 """Backup / Restore config files."""
328 if restore:
329 shutil.move(CONFIG_H + BACKUP_SUFFIX, CONFIG_H)
330 shutil.move(CRYPTO_CONFIG_H + BACKUP_SUFFIX, CRYPTO_CONFIG_H)
331 else:
332 shutil.copy(CONFIG_H, CONFIG_H + BACKUP_SUFFIX)
333 shutil.copy(CRYPTO_CONFIG_H, CRYPTO_CONFIG_H + BACKUP_SUFFIX)
334
Yanray Wange0e27602023-07-14 17:37:45 +0800335 def _build_libraries(self, git_worktree_path: str) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800336 """Build library/*.o in the specified worktree."""
Yanray Wange0e27602023-07-14 17:37:45 +0800337
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800338 self.logger.debug("Building library/*.o for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800339 .format(self.git_rev))
Yanray Wange0e27602023-07-14 17:37:45 +0800340 my_environment = os.environ.copy()
341 try:
Yanray Wang5605c6f2023-07-21 16:09:00 +0800342 if self.git_rev == 'current':
343 self.backup_config_files(restore=False)
344 for pre_cmd in self.pre_make_cmd:
345 subprocess.check_output(
346 pre_cmd, env=my_environment, shell=True,
347 cwd=git_worktree_path, stderr=subprocess.STDOUT,
348 universal_newlines=True
349 )
Yanray Wange0e27602023-07-14 17:37:45 +0800350 subprocess.check_output(
351 self.make_clean, env=my_environment, shell=True,
352 cwd=git_worktree_path, stderr=subprocess.STDOUT,
Yanray Wang386c2f92023-07-20 15:32:15 +0800353 universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800354 )
355 subprocess.check_output(
356 self.make_cmd, env=my_environment, shell=True,
357 cwd=git_worktree_path, stderr=subprocess.STDOUT,
Yanray Wang386c2f92023-07-20 15:32:15 +0800358 universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800359 )
Yanray Wang5605c6f2023-07-21 16:09:00 +0800360 if self.git_rev == 'current':
361 self.backup_config_files(restore=True)
Yanray Wange0e27602023-07-14 17:37:45 +0800362 except subprocess.CalledProcessError as e:
363 self._handle_called_process_error(e, git_worktree_path)
364
Yanray Wang386c2f92023-07-20 15:32:15 +0800365 def _gen_raw_code_size(self, git_worktree_path: str) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800366 """Measure code size by a tool and return in UTF-8 encoding."""
Yanray Wang21127f72023-07-19 12:09:45 +0800367
368 self.logger.debug("Measuring code size for {} by `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800369 .format(self.git_rev,
Yanray Wang21127f72023-07-19 12:09:45 +0800370 self.measure_cmd.strip().split(' ')[0]))
Yanray Wange0e27602023-07-14 17:37:45 +0800371
372 res = {}
373 for mod, st_lib in MBEDTLS_STATIC_LIB.items():
374 try:
375 result = subprocess.check_output(
Yanray Wang802af162023-07-17 14:04:30 +0800376 [self.measure_cmd + ' ' + st_lib], cwd=git_worktree_path,
377 shell=True, universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800378 )
379 res[mod] = result
380 except subprocess.CalledProcessError as e:
381 self._handle_called_process_error(e, git_worktree_path)
382
383 return res
384
385 def _remove_worktree(self, git_worktree_path: str) -> None:
386 """Remove temporary worktree."""
387 if git_worktree_path != self.repo_path:
Yanray Wang21127f72023-07-19 12:09:45 +0800388 self.logger.debug("Removing temporary worktree {}."
389 .format(git_worktree_path))
Yanray Wange0e27602023-07-14 17:37:45 +0800390 subprocess.check_output(
391 [self.git_command, "worktree", "remove", "--force",
392 git_worktree_path], cwd=self.repo_path,
393 stderr=subprocess.STDOUT
394 )
395
396 def _handle_called_process_error(self, e: subprocess.CalledProcessError,
397 git_worktree_path: str) -> None:
398 """Handle a CalledProcessError and quit the program gracefully.
399 Remove any extra worktrees so that the script may be called again."""
400
401 # Tell the user what went wrong
Yanray Wang21127f72023-07-19 12:09:45 +0800402 self.logger.error(e, exc_info=True)
Yanray Wang386c2f92023-07-20 15:32:15 +0800403 self.logger.error("Process output:\n {}".format(e.output))
Yanray Wange0e27602023-07-14 17:37:45 +0800404
405 # Quit gracefully by removing the existing worktree
406 self._remove_worktree(git_worktree_path)
407 sys.exit(-1)
408
Yanray Wang386c2f92023-07-20 15:32:15 +0800409 def cal_libraries_code_size(self) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800410 """Do a complete round to calculate code size of library/*.o
411 by measurement tool.
412
413 :return A dictionary of measured code size
414 - typing.Dict[mod: str]
415 """
Yanray Wange0e27602023-07-14 17:37:45 +0800416
Yanray Wang21127f72023-07-19 12:09:45 +0800417 git_worktree_path = self._create_git_worktree()
Yanray Wange0e27602023-07-14 17:37:45 +0800418 self._build_libraries(git_worktree_path)
Yanray Wang21127f72023-07-19 12:09:45 +0800419 res = self._gen_raw_code_size(git_worktree_path)
Yanray Wange0e27602023-07-14 17:37:45 +0800420 self._remove_worktree(git_worktree_path)
421
422 return res
423
424
Yanray Wang15c43f32023-07-17 11:17:12 +0800425class CodeSizeGenerator:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800426 """ A generator based on size measurement tool for library/*.o.
Yanray Wang15c43f32023-07-17 11:17:12 +0800427
428 This is an abstract class. To use it, derive a class that implements
429 size_generator_write_record and size_generator_write_comparison methods,
430 then call both of them with proper arguments.
431 """
Yanray Wang21127f72023-07-19 12:09:45 +0800432 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800433 """
434 :param logger: logging module
435 """
Yanray Wang21127f72023-07-19 12:09:45 +0800436 self.logger = logger
437
Yanray Wang15c43f32023-07-17 11:17:12 +0800438 def size_generator_write_record(
439 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800440 git_rev: str,
Yanray Wang15c43f32023-07-17 11:17:12 +0800441 code_size_text: typing.Dict,
442 output_file: str
443 ) -> None:
444 """Write size record into a file.
445
Yanray Wang955671b2023-07-21 12:08:27 +0800446 :param git_rev: Git revision. (E.g: commit)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800447 :param code_size_text:
448 string output (utf-8) from measurement tool of code size.
449 - typing.Dict[mod: str]
450 :param output_file: file which the code size record is written to.
Yanray Wang15c43f32023-07-17 11:17:12 +0800451 """
452 raise NotImplementedError
453
454 def size_generator_write_comparison(
455 self,
456 old_rev: str,
457 new_rev: str,
Yanray Wang386c2f92023-07-20 15:32:15 +0800458 output_stream: str,
Yanray Wang955671b2023-07-21 12:08:27 +0800459 result_options: CodeSizeResultInfo
Yanray Wang15c43f32023-07-17 11:17:12 +0800460 ) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800461 """Write a comparision result into a stream between two Git revisions.
Yanray Wang15c43f32023-07-17 11:17:12 +0800462
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800463 :param old_rev: old Git revision to compared with.
464 :param new_rev: new Git revision to compared with.
465 :param output_stream: stream which the code size record is written to.
466 :param result_options:
Yanray Wang955671b2023-07-21 12:08:27 +0800467 CodeSizeResultInfo containing options for comparison result.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800468 - result_options.with_markdown: write comparision result in a
469 markdown table. (Default: False)
470 - result_options.stdout: direct comparison result into
471 sys.stdout. (Default: False)
Yanray Wang15c43f32023-07-17 11:17:12 +0800472 """
473 raise NotImplementedError
474
475
476class CodeSizeGeneratorWithSize(CodeSizeGenerator):
Yanray Wang16ebc572023-05-30 18:10:20 +0800477 """Code Size Base Class for size record saving and writing."""
478
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800479 class SizeEntry: # pylint: disable=too-few-public-methods
480 """Data Structure to only store information of code size."""
481 def __init__(self, text, data, bss, dec):
482 self.text = text
483 self.data = data
484 self.bss = bss
485 self.total = dec # total <=> dec
486
Yanray Wang21127f72023-07-19 12:09:45 +0800487 def __init__(self, logger: logging.Logger) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800488 """ Variable code_size is used to store size info for any Git revisions.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800489 :param code_size:
490 Data Format as following:
Yanray Wang955671b2023-07-21 12:08:27 +0800491 {git_rev: {module: {file_name: [text, data, bss, dec],
492 etc ...
493 },
494 etc ...
495 },
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800496 etc ...
497 }
Yanray Wang16ebc572023-05-30 18:10:20 +0800498 """
Yanray Wang21127f72023-07-19 12:09:45 +0800499 super().__init__(logger)
Yanray Wang16ebc572023-05-30 18:10:20 +0800500 self.code_size = {} #type: typing.Dict[str, typing.Dict]
501
Yanray Wang955671b2023-07-21 12:08:27 +0800502 def _set_size_record(self, git_rev: str, mod: str, size_text: str) -> None:
503 """Store size information for target Git revision and high-level module.
Yanray Wang16ebc572023-05-30 18:10:20 +0800504
505 size_text Format: text data bss dec hex filename
506 """
507 size_record = {}
508 for line in size_text.splitlines()[1:]:
509 data = line.split()
Yanray Wang9b174e92023-07-17 17:59:53 +0800510 # file_name: SizeEntry(text, data, bss, dec)
511 size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry(
512 data[0], data[1], data[2], data[3])
Yanray Wang955671b2023-07-21 12:08:27 +0800513 if git_rev in self.code_size:
514 self.code_size[git_rev].update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800515 else:
Yanray Wang955671b2023-07-21 12:08:27 +0800516 self.code_size[git_rev] = {mod: size_record}
Yanray Wang16ebc572023-05-30 18:10:20 +0800517
Yanray Wang955671b2023-07-21 12:08:27 +0800518 def read_size_record(self, git_rev: str, fname: str) -> None:
Yanray Wang16ebc572023-05-30 18:10:20 +0800519 """Read size information from csv file and write it into code_size.
520
521 fname Format: filename text data bss dec
522 """
523 mod = ""
524 size_record = {}
525 with open(fname, 'r') as csv_file:
526 for line in csv_file:
527 data = line.strip().split()
528 # check if we find the beginning of a module
529 if data and data[0] in MBEDTLS_STATIC_LIB:
530 mod = data[0]
531 continue
532
533 if mod:
Yanray Wang9b174e92023-07-17 17:59:53 +0800534 # file_name: SizeEntry(text, data, bss, dec)
535 size_record[data[0]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800536 data[1], data[2], data[3], data[4])
Yanray Wang16ebc572023-05-30 18:10:20 +0800537
538 # check if we hit record for the end of a module
539 m = re.match(r'.?TOTALS', line)
540 if m:
Yanray Wang955671b2023-07-21 12:08:27 +0800541 if git_rev in self.code_size:
542 self.code_size[git_rev].update({mod: size_record})
Yanray Wang16ebc572023-05-30 18:10:20 +0800543 else:
Yanray Wang955671b2023-07-21 12:08:27 +0800544 self.code_size[git_rev] = {mod: size_record}
Yanray Wang16ebc572023-05-30 18:10:20 +0800545 mod = ""
546 size_record = {}
547
548 def _size_reader_helper(
549 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800550 git_rev: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800551 output: typing_util.Writable,
552 with_markdown=False
Yanray Wang16ebc572023-05-30 18:10:20 +0800553 ) -> typing.Iterator[tuple]:
Yanray Wang955671b2023-07-21 12:08:27 +0800554 """A helper function to peel code_size based on Git revision."""
555 for mod, file_size in self.code_size[git_rev].items():
Yanray Wangb664cb72023-07-18 12:28:35 +0800556 if not with_markdown:
557 output.write("\n" + mod + "\n")
Yanray Wang16ebc572023-05-30 18:10:20 +0800558 for fname, size_entry in file_size.items():
559 yield mod, fname, size_entry
560
Yanray Wang386c2f92023-07-20 15:32:15 +0800561 def _write_size_record(
Yanray Wang16ebc572023-05-30 18:10:20 +0800562 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800563 git_rev: str,
Yanray Wang16ebc572023-05-30 18:10:20 +0800564 output: typing_util.Writable
565 ) -> None:
566 """Write size information to a file.
567
568 Writing Format: file_name text data bss total(dec)
569 """
Yanray Wangb664cb72023-07-18 12:28:35 +0800570 format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n"
571 output.write(format_string.format("filename",
572 "text", "data", "bss", "total"))
Yanray Wang955671b2023-07-21 12:08:27 +0800573 for _, fname, size_entry in self._size_reader_helper(git_rev, output):
Yanray Wangb664cb72023-07-18 12:28:35 +0800574 output.write(format_string.format(fname,
575 size_entry.text, size_entry.data,
576 size_entry.bss, size_entry.total))
Yanray Wang16ebc572023-05-30 18:10:20 +0800577
Yanray Wang386c2f92023-07-20 15:32:15 +0800578 def _write_comparison(
Yanray Wang16ebc572023-05-30 18:10:20 +0800579 self,
580 old_rev: str,
581 new_rev: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800582 output: typing_util.Writable,
583 with_markdown: bool
Yanray Wang16ebc572023-05-30 18:10:20 +0800584 ) -> None:
585 """Write comparison result into a file.
586
Yanray Wang9b174e92023-07-17 17:59:53 +0800587 Writing Format: file_name current(text,data) old(text,data)\
588 change(text,data) change_pct%(text,data)
Yanray Wang16ebc572023-05-30 18:10:20 +0800589 """
Yanray Wang9b174e92023-07-17 17:59:53 +0800590
591 def cal_size_section_variation(mod, fname, size_entry, attr):
592 new_size = int(size_entry.__dict__[attr])
Yanray Wang955671b2023-07-21 12:08:27 +0800593 # check if we have the file in old Git revision
Yanray Wang16ebc572023-05-30 18:10:20 +0800594 if fname in self.code_size[old_rev][mod]:
Yanray Wang9b174e92023-07-17 17:59:53 +0800595 old_size = int(self.code_size[old_rev][mod][fname].__dict__[attr])
Yanray Wang16ebc572023-05-30 18:10:20 +0800596 change = new_size - old_size
597 if old_size != 0:
598 change_pct = change / old_size
599 else:
600 change_pct = 0
Yanray Wang9b174e92023-07-17 17:59:53 +0800601 return [new_size, old_size, change, change_pct]
Yanray Wang16ebc572023-05-30 18:10:20 +0800602 else:
Yanray Wang9b174e92023-07-17 17:59:53 +0800603 return [new_size]
604
Yanray Wangb664cb72023-07-18 12:28:35 +0800605 if with_markdown:
606 format_string = "| {:<30} | {:<18} | {:<14} | {:<17} | {:<18} |\n"
607 else:
608 format_string = "{:<30} {:<18} {:<14} {:<17} {:<18}\n"
609
Yanray Wang386c2f92023-07-20 15:32:15 +0800610 output.write(format_string
611 .format("filename",
612 "current(text,data)", "old(text,data)",
613 "change(text,data)", "change%(text,data)"))
Yanray Wangb664cb72023-07-18 12:28:35 +0800614 if with_markdown:
615 output.write(format_string
616 .format("----:", "----:", "----:", "----:", "----:"))
617
Yanray Wang386c2f92023-07-20 15:32:15 +0800618 for mod, fname, size_entry in \
Yanray Wangb664cb72023-07-18 12:28:35 +0800619 self._size_reader_helper(new_rev, output, with_markdown):
620 text_vari = cal_size_section_variation(mod, fname,
621 size_entry, 'text')
622 data_vari = cal_size_section_variation(mod, fname,
623 size_entry, 'data')
Yanray Wang9b174e92023-07-17 17:59:53 +0800624
625 if len(text_vari) != 1:
Yanray Wangb664cb72023-07-18 12:28:35 +0800626 # skip the files that haven't changed in code size if we write
627 # comparison result in a markdown table.
628 if with_markdown and text_vari[2] == 0 and data_vari[2] == 0:
629 continue
Yanray Wang386c2f92023-07-20 15:32:15 +0800630 output.write(
631 format_string
632 .format(fname,
633 str(text_vari[0]) + "," + str(data_vari[0]),
634 str(text_vari[1]) + "," + str(data_vari[1]),
635 str(text_vari[2]) + "," + str(data_vari[2]),
636 "{:.2%}".format(text_vari[3]) + ","
637 + "{:.2%}".format(data_vari[3])))
Yanray Wang9b174e92023-07-17 17:59:53 +0800638 else:
Yanray Wang386c2f92023-07-20 15:32:15 +0800639 output.write("{:<30} {:<18}\n"
640 .format(fname,
641 str(text_vari[0]) + "," + str(data_vari[0])))
Yanray Wang16ebc572023-05-30 18:10:20 +0800642
Yanray Wang15c43f32023-07-17 11:17:12 +0800643 def size_generator_write_record(
644 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800645 git_rev: str,
Yanray Wang15c43f32023-07-17 11:17:12 +0800646 code_size_text: typing.Dict,
647 output_file: str
648 ) -> None:
649 """Write size record into a specified file based on Git revision and
650 output from `size` tool."""
Yanray Wang955671b2023-07-21 12:08:27 +0800651 self.logger.debug("Generating code size csv for {}.".format(git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800652
Yanray Wang15c43f32023-07-17 11:17:12 +0800653 for mod, size_text in code_size_text.items():
Yanray Wang955671b2023-07-21 12:08:27 +0800654 self._set_size_record(git_rev, mod, size_text)
Yanray Wang15c43f32023-07-17 11:17:12 +0800655
Yanray Wang15c43f32023-07-17 11:17:12 +0800656 output = open(output_file, "w")
Yanray Wang955671b2023-07-21 12:08:27 +0800657 self._write_size_record(git_rev, output)
Yanray Wang15c43f32023-07-17 11:17:12 +0800658
659 def size_generator_write_comparison(
660 self,
661 old_rev: str,
662 new_rev: str,
Yanray Wang386c2f92023-07-20 15:32:15 +0800663 output_stream: str,
Yanray Wang955671b2023-07-21 12:08:27 +0800664 result_options: CodeSizeResultInfo
Yanray Wang15c43f32023-07-17 11:17:12 +0800665 ) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800666 """Write a comparision result into a stream between two Git revisions.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800667
668 By default, it's written into a file called output_stream.
669 Once result_options.stdout is set, it's written into sys.stdout instead.
670 """
Yanray Wang21127f72023-07-19 12:09:45 +0800671 self.logger.debug("Generating comparison results between {} and {}."
672 .format(old_rev, new_rev))
673
Yanray Wang227576a2023-07-18 14:35:05 +0800674 if result_options.stdout:
675 output = sys.stdout
676 else:
677 output = open(output_stream, "w")
Yanray Wang386c2f92023-07-20 15:32:15 +0800678 self._write_comparison(old_rev, new_rev, output,
679 result_options.with_markdown)
Yanray Wang15c43f32023-07-17 11:17:12 +0800680
Yanray Wang16ebc572023-05-30 18:10:20 +0800681
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800682class CodeSizeComparison:
Xiaofei Bai2400b502021-10-21 12:22:58 +0000683 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000684
Yanray Wang955671b2023-07-21 12:08:27 +0800685 def __init__( #pylint: disable=too-many-arguments
Yanray Wang72b105f2023-05-31 15:20:39 +0800686 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800687 old_size_dist_info: CodeSizeDistinctInfo,
688 new_size_dist_info: CodeSizeDistinctInfo,
689 size_common_info: CodeSizeCommonInfo,
690 result_options: CodeSizeResultInfo,
Yanray Wang21127f72023-07-19 12:09:45 +0800691 logger: logging.Logger,
Yanray Wang72b105f2023-05-31 15:20:39 +0800692 ) -> None:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000693 """
Yanray Wang955671b2023-07-21 12:08:27 +0800694 :param old_size_dist_info: CodeSizeDistinctInfo containing old distinct
695 info to compare code size with.
696 :param new_size_dist_info: CodeSizeDistinctInfo containing new distinct
697 info to take as comparision base.
698 :param size_common_info: CodeSizeCommonInfo containing common info for
699 both old and new size distinct info and
700 measurement tool.
701 :param result_options: CodeSizeResultInfo containing results options for
702 code size record and comparision.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800703 :param logger: logging module
Xiaofei Baibca03e52021-09-09 09:42:37 +0000704 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000705
Yanray Wang21127f72023-07-19 12:09:45 +0800706 self.logger = logger
707
Yanray Wang955671b2023-07-21 12:08:27 +0800708 self.old_size_dist_info = old_size_dist_info
709 self.new_size_dist_info = new_size_dist_info
710 self.size_common_info = size_common_info
Yanray Wang5605c6f2023-07-21 16:09:00 +0800711 # infer pre make command
712 self.old_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
713 self.old_size_dist_info, self.size_common_info.host_arch,
714 self.logger).infer_pre_make_command()
715 self.new_size_dist_info.pre_make_cmd = CodeSizeBuildInfo(
716 self.new_size_dist_info, self.size_common_info.host_arch,
717 self.logger).infer_pre_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800718 # infer make command
Yanray Wang955671b2023-07-21 12:08:27 +0800719 self.old_size_dist_info.make_cmd = CodeSizeBuildInfo(
720 self.old_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800721 self.logger).infer_make_command()
Yanray Wang955671b2023-07-21 12:08:27 +0800722 self.new_size_dist_info.make_cmd = CodeSizeBuildInfo(
723 self.new_size_dist_info, self.size_common_info.host_arch,
Yanray Wang21127f72023-07-19 12:09:45 +0800724 self.logger).infer_make_command()
Yanray Wang386c2f92023-07-20 15:32:15 +0800725 # initialize size parser with corresponding measurement tool
Yanray Wang21127f72023-07-19 12:09:45 +0800726 self.code_size_generator = self.__generate_size_parser()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000727
Yanray Wang955671b2023-07-21 12:08:27 +0800728 self.result_options = result_options
729 self.csv_dir = os.path.abspath(self.result_options.record_dir)
730 os.makedirs(self.csv_dir, exist_ok=True)
731 self.comp_dir = os.path.abspath(self.result_options.comp_dir)
732 os.makedirs(self.comp_dir, exist_ok=True)
733
Yanray Wang21127f72023-07-19 12:09:45 +0800734 def __generate_size_parser(self):
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800735 """Generate a parser for the corresponding measurement tool."""
Yanray Wang955671b2023-07-21 12:08:27 +0800736 if re.match(r'size', self.size_common_info.measure_cmd.strip()):
Yanray Wang21127f72023-07-19 12:09:45 +0800737 return CodeSizeGeneratorWithSize(self.logger)
Yanray Wang802af162023-07-17 14:04:30 +0800738 else:
Yanray Wang21127f72023-07-19 12:09:45 +0800739 self.logger.error("Unsupported measurement tool: `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800740 .format(self.size_common_info.measure_cmd
Yanray Wang21127f72023-07-19 12:09:45 +0800741 .strip().split(' ')[0]))
Yanray Wang802af162023-07-17 14:04:30 +0800742 sys.exit(1)
743
Yanray Wang386c2f92023-07-20 15:32:15 +0800744 def cal_code_size(
745 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800746 size_dist_info: CodeSizeDistinctInfo
Yanray Wang386c2f92023-07-20 15:32:15 +0800747 ) -> typing.Dict[str, str]:
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800748 """Calculate code size of library/*.o in a UTF-8 encoding"""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000749
Yanray Wang955671b2023-07-21 12:08:27 +0800750 return CodeSizeCalculator(size_dist_info.git_rev,
Yanray Wang5605c6f2023-07-21 16:09:00 +0800751 size_dist_info.pre_make_cmd,
Yanray Wang955671b2023-07-21 12:08:27 +0800752 size_dist_info.make_cmd,
753 self.size_common_info.measure_cmd,
Yanray Wang21127f72023-07-19 12:09:45 +0800754 self.logger).cal_libraries_code_size()
Yanray Wang8804db92023-05-30 18:18:18 +0800755
Yanray Wang386c2f92023-07-20 15:32:15 +0800756 def gen_file_name(
757 self,
Yanray Wang955671b2023-07-21 12:08:27 +0800758 old_size_dist_info: CodeSizeDistinctInfo,
759 new_size_dist_info=None
Yanray Wang386c2f92023-07-20 15:32:15 +0800760 ) -> str:
Yanray Wang21127f72023-07-19 12:09:45 +0800761 """Generate a literal string as csv file name."""
Yanray Wang955671b2023-07-21 12:08:27 +0800762 if new_size_dist_info:
Yanray Wang802af162023-07-17 14:04:30 +0800763 return '{}-{}-{}-{}-{}-{}-{}.csv'\
Yanray Wang955671b2023-07-21 12:08:27 +0800764 .format(old_size_dist_info.git_rev, old_size_dist_info.arch,
765 old_size_dist_info.config,
766 new_size_dist_info.git_rev, new_size_dist_info.arch,
767 new_size_dist_info.config,
768 self.size_common_info.measure_cmd.strip()\
Yanray Wang386c2f92023-07-20 15:32:15 +0800769 .split(' ')[0])
Yanray Wang923f9432023-07-17 12:43:00 +0800770 else:
Yanray Wang802af162023-07-17 14:04:30 +0800771 return '{}-{}-{}-{}.csv'\
Yanray Wang955671b2023-07-21 12:08:27 +0800772 .format(old_size_dist_info.git_rev,
773 old_size_dist_info.arch,
774 old_size_dist_info.config,
775 self.size_common_info.measure_cmd.strip()\
Yanray Wang386c2f92023-07-20 15:32:15 +0800776 .split(' ')[0])
Yanray Wang923f9432023-07-17 12:43:00 +0800777
Yanray Wang955671b2023-07-21 12:08:27 +0800778 def gen_code_size_report(self, size_dist_info: CodeSizeDistinctInfo) -> None:
Yanray Wang5e9130a2023-07-17 11:55:54 +0800779 """Generate code size record and write it into a file."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000780
Yanray Wang21127f72023-07-19 12:09:45 +0800781 self.logger.info("Start to generate code size record for {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800782 .format(size_dist_info.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800783 output_file = os.path.join(self.csv_dir,
Yanray Wang955671b2023-07-21 12:08:27 +0800784 self.gen_file_name(size_dist_info))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000785 # Check if the corresponding record exists
Yanray Wang955671b2023-07-21 12:08:27 +0800786 if size_dist_info.git_rev != "current" and \
Yanray Wang21127f72023-07-19 12:09:45 +0800787 os.path.exists(output_file):
788 self.logger.debug("Code size csv file for {} already exists."
Yanray Wang955671b2023-07-21 12:08:27 +0800789 .format(size_dist_info.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800790 self.code_size_generator.read_size_record(
Yanray Wang955671b2023-07-21 12:08:27 +0800791 size_dist_info.git_rev, output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000792 else:
Yanray Wang386c2f92023-07-20 15:32:15 +0800793 self.code_size_generator.size_generator_write_record(
Yanray Wang955671b2023-07-21 12:08:27 +0800794 size_dist_info.git_rev, self.cal_code_size(size_dist_info),
Yanray Wang386c2f92023-07-20 15:32:15 +0800795 output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000796
Yanray Wang386c2f92023-07-20 15:32:15 +0800797 def gen_code_size_comparison(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800798 """Generate results of code size changes between two Git revisions,
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800799 old and new.
800
Yanray Wang955671b2023-07-21 12:08:27 +0800801 - Measured code size result of these two Git revisions must be available.
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800802 - The result is directed into either file / stdout depending on
Yanray Wang955671b2023-07-21 12:08:27 +0800803 the option, size_common_info.result_options.stdout. (Default: file)
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800804 """
Xiaofei Baibca03e52021-09-09 09:42:37 +0000805
Yanray Wang21127f72023-07-19 12:09:45 +0800806 self.logger.info("Start to generate comparision result between "\
807 "{} and {}."
Yanray Wang955671b2023-07-21 12:08:27 +0800808 .format(self.old_size_dist_info.git_rev,
809 self.new_size_dist_info.git_rev))
Yanray Wang21127f72023-07-19 12:09:45 +0800810 output_file = os.path.join(
Yanray Wang955671b2023-07-21 12:08:27 +0800811 self.comp_dir,
812 self.gen_file_name(self.old_size_dist_info, self.new_size_dist_info))
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000813
Yanray Wang21127f72023-07-19 12:09:45 +0800814 self.code_size_generator.size_generator_write_comparison(
Yanray Wang955671b2023-07-21 12:08:27 +0800815 self.old_size_dist_info.git_rev,
816 self.new_size_dist_info.git_rev,
817 output_file, self.result_options)
Yanray Wang21127f72023-07-19 12:09:45 +0800818
Yanray Wang386c2f92023-07-20 15:32:15 +0800819 def get_comparision_results(self) -> None:
Yanray Wang955671b2023-07-21 12:08:27 +0800820 """Compare size of library/*.o between self.old_size_dist_info and
821 self.old_size_dist_info and generate the result file."""
Gilles Peskined9071e72022-09-18 21:17:09 +0200822 build_tree.check_repo_path()
Yanray Wang955671b2023-07-21 12:08:27 +0800823 self.gen_code_size_report(self.old_size_dist_info)
824 self.gen_code_size_report(self.new_size_dist_info)
Yanray Wang386c2f92023-07-20 15:32:15 +0800825 self.gen_code_size_comparison()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000826
Xiaofei Bai2400b502021-10-21 12:22:58 +0000827def main():
Yanray Wang502c54f2023-05-31 11:41:36 +0800828 parser = argparse.ArgumentParser(description=(__doc__))
829 group_required = parser.add_argument_group(
830 'required arguments',
831 'required arguments to parse for running ' + os.path.basename(__file__))
832 group_required.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800833 '-o', '--old-rev', type=str, required=True,
Yanray Wang955671b2023-07-21 12:08:27 +0800834 help='old Git revision for comparison.')
Yanray Wang502c54f2023-05-31 11:41:36 +0800835
836 group_optional = parser.add_argument_group(
837 'optional arguments',
838 'optional arguments to parse for running ' + os.path.basename(__file__))
839 group_optional.add_argument(
Yanray Wang955671b2023-07-21 12:08:27 +0800840 '--record_dir', type=str, default='code_size_records',
841 help='directory where code size record is stored. '
842 '(Default: code_size_records)')
843 group_optional.add_argument(
844 '-r', '--comp-dir', type=str, default='comparison',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800845 help='directory where comparison result is stored. '
846 '(Default: comparison)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800847 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800848 '-n', '--new-rev', type=str, default=None,
Yanray Wang955671b2023-07-21 12:08:27 +0800849 help='new Git revision as comparison base. '
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800850 '(Default is the current work directory, including uncommitted '
851 'changes.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800852 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800853 '-a', '--arch', type=str, default=detect_arch(),
Yanray Wang23bd5322023-05-24 11:03:59 +0800854 choices=list(map(lambda s: s.value, SupportedArch)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800855 help='Specify architecture for code size comparison. '
856 '(Default is the host architecture.)')
Yanray Wang502c54f2023-05-31 11:41:36 +0800857 group_optional.add_argument(
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800858 '-c', '--config', type=str, default=SupportedConfig.DEFAULT.value,
Yanray Wang6a862582023-05-24 12:24:38 +0800859 choices=list(map(lambda s: s.value, SupportedConfig)),
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800860 help='Specify configuration type for code size comparison. '
861 '(Default is the current MbedTLS configuration.)')
Yanray Wangb664cb72023-07-18 12:28:35 +0800862 group_optional.add_argument(
863 '--markdown', action='store_true', dest='markdown',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800864 help='Show comparision of code size in a markdown table. '
865 '(Only show the files that have changed).')
Yanray Wang227576a2023-07-18 14:35:05 +0800866 group_optional.add_argument(
867 '--stdout', action='store_true', dest='stdout',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800868 help='Set this option to direct comparison result into sys.stdout. '
869 '(Default: file)')
Yanray Wang21127f72023-07-19 12:09:45 +0800870 group_optional.add_argument(
871 '--verbose', action='store_true', dest='verbose',
Yanray Wang5b64e4c2023-07-20 15:09:51 +0800872 help='Show logs in detail for code size measurement. '
873 '(Default: False)')
Xiaofei Baibca03e52021-09-09 09:42:37 +0000874 comp_args = parser.parse_args()
875
Yanray Wang21127f72023-07-19 12:09:45 +0800876 logger = logging.getLogger()
877 logging_util.configure_logger(logger)
878 logger.setLevel(logging.DEBUG if comp_args.verbose else logging.INFO)
879
Yanray Wang955671b2023-07-21 12:08:27 +0800880 if os.path.isfile(comp_args.comp_dir):
881 logger.error("{} is not a directory".format(comp_args.comp_dir))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000882 parser.exit()
883
Yanray Wang955671b2023-07-21 12:08:27 +0800884 old_revision = CodeSizeCalculator.validate_git_revision(comp_args.old_rev)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000885 if comp_args.new_rev is not None:
Yanray Wang955671b2023-07-21 12:08:27 +0800886 new_revision = CodeSizeCalculator.validate_git_revision(
887 comp_args.new_rev)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000888 else:
Yanray Wang5605c6f2023-07-21 16:09:00 +0800889 new_revision = 'current'
Xiaofei Bai2400b502021-10-21 12:22:58 +0000890
Yanray Wang5605c6f2023-07-21 16:09:00 +0800891 # version, git_rev, arch, config, compiler, opt_level
Yanray Wang955671b2023-07-21 12:08:27 +0800892 old_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang5605c6f2023-07-21 16:09:00 +0800893 'old', old_revision, comp_args.arch, comp_args.config, 'cc', '-Os')
Yanray Wang955671b2023-07-21 12:08:27 +0800894 new_size_dist_info = CodeSizeDistinctInfo(
Yanray Wang5605c6f2023-07-21 16:09:00 +0800895 'new', new_revision, comp_args.arch, comp_args.config, 'cc', '-Os')
896 # host_arch, measure_cmd
Yanray Wang955671b2023-07-21 12:08:27 +0800897 size_common_info = CodeSizeCommonInfo(
898 detect_arch(), 'size -t')
Yanray Wang5605c6f2023-07-21 16:09:00 +0800899 # record_dir, comp_dir, with_markdown, stdout
Yanray Wang955671b2023-07-21 12:08:27 +0800900 result_options = CodeSizeResultInfo(
901 comp_args.record_dir, comp_args.comp_dir,
902 comp_args.markdown, comp_args.stdout)
Yanray Wang923f9432023-07-17 12:43:00 +0800903
Yanray Wang21127f72023-07-19 12:09:45 +0800904 logger.info("Measure code size between {}:{}-{} and {}:{}-{} by `{}`."
Yanray Wang955671b2023-07-21 12:08:27 +0800905 .format(old_size_dist_info.git_rev, old_size_dist_info.config,
906 old_size_dist_info.arch,
907 new_size_dist_info.git_rev, old_size_dist_info.config,
908 new_size_dist_info.arch,
909 size_common_info.measure_cmd.strip().split(' ')[0]))
910 CodeSizeComparison(old_size_dist_info, new_size_dist_info,
911 size_common_info, result_options,
912 logger).get_comparision_results()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000913
Xiaofei Baibca03e52021-09-09 09:42:37 +0000914if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000915 main()