blob: e679af0a5fa6b89c6ef5ac94185d3804c4bf9f4d [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
27import os
Yanray Wang16ebc572023-05-30 18:10:20 +080028import re
Xiaofei Baibca03e52021-09-09 09:42:37 +000029import subprocess
30import sys
Yanray Wang16ebc572023-05-30 18:10:20 +080031import typing
Yanray Wang23bd5322023-05-24 11:03:59 +080032from enum import Enum
Xiaofei Baibca03e52021-09-09 09:42:37 +000033
Yanray Wang923f9432023-07-17 12:43:00 +080034from types import SimpleNamespace
Yanray Wang16ebc572023-05-30 18:10:20 +080035from mbedtls_dev import typing_util
Gilles Peskined9071e72022-09-18 21:17:09 +020036from mbedtls_dev import build_tree
37
Yanray Wang23bd5322023-05-24 11:03:59 +080038class SupportedArch(Enum):
39 """Supported architecture for code size measurement."""
40 AARCH64 = 'aarch64'
41 AARCH32 = 'aarch32'
Yanray Wangaba71582023-05-29 16:45:56 +080042 ARMV8_M = 'armv8-m'
Yanray Wang23bd5322023-05-24 11:03:59 +080043 X86_64 = 'x86_64'
44 X86 = 'x86'
45
Yanray Wang6a862582023-05-24 12:24:38 +080046CONFIG_TFM_MEDIUM_MBEDCRYPTO_H = "../configs/tfm_mbedcrypto_config_profile_medium.h"
47CONFIG_TFM_MEDIUM_PSA_CRYPTO_H = "../configs/crypto_config_profile_medium.h"
48class SupportedConfig(Enum):
49 """Supported configuration for code size measurement."""
50 DEFAULT = 'default'
51 TFM_MEDIUM = 'tfm-medium'
52
Yanray Wang16ebc572023-05-30 18:10:20 +080053# Static library
54MBEDTLS_STATIC_LIB = {
55 'CRYPTO': 'library/libmbedcrypto.a',
56 'X509': 'library/libmbedx509.a',
57 'TLS': 'library/libmbedtls.a',
58}
59
Yanray Wang23bd5322023-05-24 11:03:59 +080060DETECT_ARCH_CMD = "cc -dM -E - < /dev/null"
61def detect_arch() -> str:
62 """Auto-detect host architecture."""
63 cc_output = subprocess.check_output(DETECT_ARCH_CMD, shell=True).decode()
64 if "__aarch64__" in cc_output:
65 return SupportedArch.AARCH64.value
66 if "__arm__" in cc_output:
67 return SupportedArch.AARCH32.value
68 if "__x86_64__" in cc_output:
69 return SupportedArch.X86_64.value
70 if "__x86__" in cc_output:
71 return SupportedArch.X86.value
72 else:
73 print("Unknown host architecture, cannot auto-detect arch.")
74 sys.exit(1)
Gilles Peskined9071e72022-09-18 21:17:09 +020075
Yanray Wang923f9432023-07-17 12:43:00 +080076class CodeSizeBuildInfo: # pylint: disable=too-few-public-methods
Yanray Wang6a862582023-05-24 12:24:38 +080077 """Gather information used to measure code size.
78
79 It collects information about architecture, configuration in order to
80 infer build command for code size measurement.
81 """
82
Yanray Wangc18cd892023-05-31 11:08:04 +080083 SupportedArchConfig = [
84 "-a " + SupportedArch.AARCH64.value + " -c " + SupportedConfig.DEFAULT.value,
85 "-a " + SupportedArch.AARCH32.value + " -c " + SupportedConfig.DEFAULT.value,
86 "-a " + SupportedArch.X86_64.value + " -c " + SupportedConfig.DEFAULT.value,
87 "-a " + SupportedArch.X86.value + " -c " + SupportedConfig.DEFAULT.value,
88 "-a " + SupportedArch.ARMV8_M.value + " -c " + SupportedConfig.TFM_MEDIUM.value,
89 ]
90
Yanray Wang802af162023-07-17 14:04:30 +080091 def __init__(
92 self,
93 size_version: SimpleNamespace,
94 host_arch: str
95 ) -> None:
Yanray Wang6a862582023-05-24 12:24:38 +080096 """
Yanray Wang923f9432023-07-17 12:43:00 +080097 size_version: SimpleNamespace containing info for code size measurement.
98 size_version.arch: architecture to measure code size on.
99 size_version.config: configuration type to measure code size with.
Yanray Wang802af162023-07-17 14:04:30 +0800100 host_arch: host architecture.
Yanray Wang6a862582023-05-24 12:24:38 +0800101 """
Yanray Wang923f9432023-07-17 12:43:00 +0800102 self.size_version = size_version
Yanray Wang802af162023-07-17 14:04:30 +0800103 self.host_arch = host_arch
Yanray Wang6a862582023-05-24 12:24:38 +0800104
Yanray Wang923f9432023-07-17 12:43:00 +0800105 def infer_make_command(self) -> str:
Yanray Wang6a862582023-05-24 12:24:38 +0800106 """Infer build command based on architecture and configuration."""
107
Yanray Wang923f9432023-07-17 12:43:00 +0800108 if self.size_version.config == SupportedConfig.DEFAULT.value and \
Yanray Wang802af162023-07-17 14:04:30 +0800109 self.size_version.arch == self.host_arch:
Yanray Wang6a862582023-05-24 12:24:38 +0800110 return 'make -j lib CFLAGS=\'-Os \' '
Yanray Wang923f9432023-07-17 12:43:00 +0800111 elif self.size_version.arch == SupportedArch.ARMV8_M.value and \
112 self.size_version.config == SupportedConfig.TFM_MEDIUM.value:
Yanray Wang6a862582023-05-24 12:24:38 +0800113 return \
Yanray Wang60430bd2023-05-29 14:48:18 +0800114 'make -j lib CC=armclang \
Yanray Wang6a862582023-05-24 12:24:38 +0800115 CFLAGS=\'--target=arm-arm-none-eabi -mcpu=cortex-m33 -Os \
116 -DMBEDTLS_CONFIG_FILE=\\\"' + CONFIG_TFM_MEDIUM_MBEDCRYPTO_H + '\\\" \
117 -DMBEDTLS_PSA_CRYPTO_CONFIG_FILE=\\\"' + CONFIG_TFM_MEDIUM_PSA_CRYPTO_H + '\\\" \''
118 else:
Yanray Wang21f17442023-06-01 11:29:06 +0800119 print("Unsupported combination of architecture: {} and configuration: {}"
Yanray Wang923f9432023-07-17 12:43:00 +0800120 .format(self.size_version.arch, self.size_version.config))
Yanray Wangc18cd892023-05-31 11:08:04 +0800121 print("\nPlease use supported combination of architecture and configuration:")
Yanray Wang923f9432023-07-17 12:43:00 +0800122 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wangc18cd892023-05-31 11:08:04 +0800123 print(comb)
Yanray Wang21f17442023-06-01 11:29:06 +0800124 print("\nFor your system, please use:")
Yanray Wang923f9432023-07-17 12:43:00 +0800125 for comb in CodeSizeBuildInfo.SupportedArchConfig:
Yanray Wang802af162023-07-17 14:04:30 +0800126 if "default" in comb and self.host_arch not in comb:
Yanray Wang21f17442023-06-01 11:29:06 +0800127 continue
128 print(comb)
Yanray Wang6a862582023-05-24 12:24:38 +0800129 sys.exit(1)
130
131
Yanray Wange0e27602023-07-14 17:37:45 +0800132class CodeSizeCalculator:
133 """ A calculator to calculate code size of library objects based on
134 Git revision and code size measurement tool.
135 """
136
137 def __init__(
138 self,
139 revision: str,
140 make_cmd: str,
Yanray Wang802af162023-07-17 14:04:30 +0800141 measure_cmd: str
Yanray Wange0e27602023-07-14 17:37:45 +0800142 ) -> None:
143 """
144 revision: Git revision.(E.g: commit)
Yanray Wang802af162023-07-17 14:04:30 +0800145 make_cmd: command to build objects in library.
146 measure_cmd: command to measure code size for objects in library.
Yanray Wange0e27602023-07-14 17:37:45 +0800147 """
148 self.repo_path = "."
149 self.git_command = "git"
150 self.make_clean = 'make clean'
151
152 self.revision = revision
153 self.make_cmd = make_cmd
Yanray Wang802af162023-07-17 14:04:30 +0800154 self.measure_cmd = measure_cmd
Yanray Wange0e27602023-07-14 17:37:45 +0800155
156 @staticmethod
157 def validate_revision(revision: str) -> bytes:
158 result = subprocess.check_output(["git", "rev-parse", "--verify",
159 revision + "^{commit}"], shell=False)
160 return result
161
162 def _create_git_worktree(self, revision: str) -> str:
163 """Make a separate worktree for revision.
164 Do not modify the current worktree."""
165
166 if revision == "current":
167 print("Using current work directory")
168 git_worktree_path = self.repo_path
169 else:
170 print("Creating git worktree for", revision)
171 git_worktree_path = os.path.join(self.repo_path, "temp-" + revision)
172 subprocess.check_output(
173 [self.git_command, "worktree", "add", "--detach",
174 git_worktree_path, revision], cwd=self.repo_path,
175 stderr=subprocess.STDOUT
176 )
177
178 return git_worktree_path
179
180 def _build_libraries(self, git_worktree_path: str) -> None:
181 """Build libraries in the specified worktree."""
182
183 my_environment = os.environ.copy()
184 try:
185 subprocess.check_output(
186 self.make_clean, env=my_environment, shell=True,
187 cwd=git_worktree_path, stderr=subprocess.STDOUT,
188 )
189 subprocess.check_output(
190 self.make_cmd, env=my_environment, shell=True,
191 cwd=git_worktree_path, stderr=subprocess.STDOUT,
192 )
193 except subprocess.CalledProcessError as e:
194 self._handle_called_process_error(e, git_worktree_path)
195
196 def _gen_raw_code_size(self, revision, git_worktree_path):
197 """Calculate code size with measurement tool in UTF-8 encoding."""
198 if revision == "current":
199 print("Measuring code size in current work directory")
200 else:
201 print("Measuring code size for", revision)
202
203 res = {}
204 for mod, st_lib in MBEDTLS_STATIC_LIB.items():
205 try:
206 result = subprocess.check_output(
Yanray Wang802af162023-07-17 14:04:30 +0800207 [self.measure_cmd + ' ' + st_lib], cwd=git_worktree_path,
208 shell=True, universal_newlines=True
Yanray Wange0e27602023-07-14 17:37:45 +0800209 )
210 res[mod] = result
211 except subprocess.CalledProcessError as e:
212 self._handle_called_process_error(e, git_worktree_path)
213
214 return res
215
216 def _remove_worktree(self, git_worktree_path: str) -> None:
217 """Remove temporary worktree."""
218 if git_worktree_path != self.repo_path:
219 print("Removing temporary worktree", git_worktree_path)
220 subprocess.check_output(
221 [self.git_command, "worktree", "remove", "--force",
222 git_worktree_path], cwd=self.repo_path,
223 stderr=subprocess.STDOUT
224 )
225
226 def _handle_called_process_error(self, e: subprocess.CalledProcessError,
227 git_worktree_path: str) -> None:
228 """Handle a CalledProcessError and quit the program gracefully.
229 Remove any extra worktrees so that the script may be called again."""
230
231 # Tell the user what went wrong
232 print("The following command: {} failed and exited with code {}"
233 .format(e.cmd, e.returncode))
234 print("Process output:\n {}".format(str(e.output, "utf-8")))
235
236 # Quit gracefully by removing the existing worktree
237 self._remove_worktree(git_worktree_path)
238 sys.exit(-1)
239
240 def cal_libraries_code_size(self) -> typing.Dict:
241 """Calculate code size of libraries by measurement tool."""
242
243 revision = self.revision
244 git_worktree_path = self._create_git_worktree(revision)
245 self._build_libraries(git_worktree_path)
246 res = self._gen_raw_code_size(revision, git_worktree_path)
247 self._remove_worktree(git_worktree_path)
248
249 return res
250
251
Yanray Wang15c43f32023-07-17 11:17:12 +0800252class CodeSizeGenerator:
253 """ A generator based on size measurement tool for library objects.
254
255 This is an abstract class. To use it, derive a class that implements
256 size_generator_write_record and size_generator_write_comparison methods,
257 then call both of them with proper arguments.
258 """
259 def size_generator_write_record(
260 self,
261 revision: str,
262 code_size_text: typing.Dict,
263 output_file: str
264 ) -> None:
265 """Write size record into a file.
266
267 revision: Git revision.(E.g: commit)
268 code_size_text: text output (utf-8) from code size measurement tool.
269 output_file: file which the code size record is written to.
270 """
271 raise NotImplementedError
272
273 def size_generator_write_comparison(
274 self,
275 old_rev: str,
276 new_rev: str,
277 output_stream
278 ) -> None:
279 """Write a comparision result into a stream between two revisions.
280
281 old_rev: old git revision to compared with.
282 new_rev: new git revision to compared with.
283 output_stream: stream which the code size record is written to.
284 (E.g: file / sys.stdout)
285 """
286 raise NotImplementedError
287
288
289class CodeSizeGeneratorWithSize(CodeSizeGenerator):
Yanray Wang16ebc572023-05-30 18:10:20 +0800290 """Code Size Base Class for size record saving and writing."""
291
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800292 class SizeEntry: # pylint: disable=too-few-public-methods
293 """Data Structure to only store information of code size."""
294 def __init__(self, text, data, bss, dec):
295 self.text = text
296 self.data = data
297 self.bss = bss
298 self.total = dec # total <=> dec
299
Yanray Wang16ebc572023-05-30 18:10:20 +0800300 def __init__(self) -> None:
301 """ Variable code_size is used to store size info for any revisions.
302 code_size: (data format)
Yanray Wang9b174e92023-07-17 17:59:53 +0800303 {revision: {module: {file_name: [text, data, bss, dec],
Yanray Wang16ebc572023-05-30 18:10:20 +0800304 etc ...
305 },
306 etc ...
307 },
308 etc ...
309 }
310 """
311 self.code_size = {} #type: typing.Dict[str, typing.Dict]
312
313 def set_size_record(self, revision: str, mod: str, size_text: str) -> None:
314 """Store size information for target revision and high-level module.
315
316 size_text Format: text data bss dec hex filename
317 """
318 size_record = {}
319 for line in size_text.splitlines()[1:]:
320 data = line.split()
Yanray Wang9b174e92023-07-17 17:59:53 +0800321 # file_name: SizeEntry(text, data, bss, dec)
322 size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry(
323 data[0], data[1], data[2], data[3])
Yanray Wang16ebc572023-05-30 18:10:20 +0800324 if revision in self.code_size:
325 self.code_size[revision].update({mod: size_record})
326 else:
327 self.code_size[revision] = {mod: size_record}
328
329 def read_size_record(self, revision: str, fname: str) -> None:
330 """Read size information from csv file and write it into code_size.
331
332 fname Format: filename text data bss dec
333 """
334 mod = ""
335 size_record = {}
336 with open(fname, 'r') as csv_file:
337 for line in csv_file:
338 data = line.strip().split()
339 # check if we find the beginning of a module
340 if data and data[0] in MBEDTLS_STATIC_LIB:
341 mod = data[0]
342 continue
343
344 if mod:
Yanray Wang9b174e92023-07-17 17:59:53 +0800345 # file_name: SizeEntry(text, data, bss, dec)
346 size_record[data[0]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800347 data[1], data[2], data[3], data[4])
Yanray Wang16ebc572023-05-30 18:10:20 +0800348
349 # check if we hit record for the end of a module
350 m = re.match(r'.?TOTALS', line)
351 if m:
352 if revision in self.code_size:
353 self.code_size[revision].update({mod: size_record})
354 else:
355 self.code_size[revision] = {mod: size_record}
356 mod = ""
357 size_record = {}
358
359 def _size_reader_helper(
360 self,
361 revision: str,
362 output: typing_util.Writable
363 ) -> typing.Iterator[tuple]:
364 """A helper function to peel code_size based on revision."""
365 for mod, file_size in self.code_size[revision].items():
366 output.write("\n" + mod + "\n")
367 for fname, size_entry in file_size.items():
368 yield mod, fname, size_entry
369
370 def write_size_record(
371 self,
372 revision: str,
373 output: typing_util.Writable
374 ) -> None:
375 """Write size information to a file.
376
377 Writing Format: file_name text data bss total(dec)
378 """
379 output.write("{:<30} {:>7} {:>7} {:>7} {:>7}\n"
380 .format("filename", "text", "data", "bss", "total"))
381 for _, fname, size_entry in self._size_reader_helper(revision, output):
382 output.write("{:<30} {:>7} {:>7} {:>7} {:>7}\n"
383 .format(fname, size_entry.text, size_entry.data,\
384 size_entry.bss, size_entry.total))
385
386 def write_comparison(
387 self,
388 old_rev: str,
389 new_rev: str,
390 output: typing_util.Writable
391 ) -> None:
392 """Write comparison result into a file.
393
Yanray Wang9b174e92023-07-17 17:59:53 +0800394 Writing Format: file_name current(text,data) old(text,data)\
395 change(text,data) change_pct%(text,data)
Yanray Wang16ebc572023-05-30 18:10:20 +0800396 """
Yanray Wang9b174e92023-07-17 17:59:53 +0800397
398 def cal_size_section_variation(mod, fname, size_entry, attr):
399 new_size = int(size_entry.__dict__[attr])
Yanray Wang16ebc572023-05-30 18:10:20 +0800400 # check if we have the file in old revision
401 if fname in self.code_size[old_rev][mod]:
Yanray Wang9b174e92023-07-17 17:59:53 +0800402 old_size = int(self.code_size[old_rev][mod][fname].__dict__[attr])
Yanray Wang16ebc572023-05-30 18:10:20 +0800403 change = new_size - old_size
404 if old_size != 0:
405 change_pct = change / old_size
406 else:
407 change_pct = 0
Yanray Wang9b174e92023-07-17 17:59:53 +0800408 return [new_size, old_size, change, change_pct]
Yanray Wang16ebc572023-05-30 18:10:20 +0800409 else:
Yanray Wang9b174e92023-07-17 17:59:53 +0800410 return [new_size]
411
412 output.write("{:<30} {:<18} {:<14} {:<17} {:<18}\n"
413 .format("filename", "current(text,data)", "old(text,data)",\
414 "change(text,data)", "change%(text,data)"))
415 for mod, fname, size_entry in self._size_reader_helper(new_rev, output):
416 text_vari = cal_size_section_variation(mod, fname, size_entry, 'text')
417 data_vari = cal_size_section_variation(mod, fname, size_entry, 'data')
418
419 if len(text_vari) != 1:
420 output.write("{:<30} {:<18} {:<14} {:<17} {:<18}\n"
421 .format(fname,\
422 str(text_vari[0]) + "," + str(data_vari[0]),\
423 str(text_vari[1]) + "," + str(data_vari[1]),\
424 str(text_vari[2]) + "," + str(data_vari[2]),\
425 "{:.2%}".format(text_vari[3]) + "," +\
426 "{:.2%}".format(data_vari[3])))
427 else:
428 output.write("{:<30} {:<18}\n"
429 .format(fname,\
430 str(text_vari[0]) + "," + str(data_vari[0])))
Yanray Wang16ebc572023-05-30 18:10:20 +0800431
Yanray Wang15c43f32023-07-17 11:17:12 +0800432 def size_generator_write_record(
433 self,
434 revision: str,
435 code_size_text: typing.Dict,
436 output_file: str
437 ) -> None:
438 """Write size record into a specified file based on Git revision and
439 output from `size` tool."""
440 for mod, size_text in code_size_text.items():
441 self.set_size_record(revision, mod, size_text)
442
443 print("Generating code size csv for", revision)
444 output = open(output_file, "w")
445 self.write_size_record(revision, output)
446
447 def size_generator_write_comparison(
448 self,
449 old_rev: str,
450 new_rev: str,
451 output_stream
452 ) -> None:
453 """Write a comparision result into a stream between two revisions."""
454 output = open(output_stream, "w")
455 self.write_comparison(old_rev, new_rev, output)
456
Yanray Wang16ebc572023-05-30 18:10:20 +0800457
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800458class CodeSizeComparison:
Xiaofei Bai2400b502021-10-21 12:22:58 +0000459 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000460
Yanray Wang72b105f2023-05-31 15:20:39 +0800461 def __init__(
462 self,
Yanray Wang923f9432023-07-17 12:43:00 +0800463 old_size_version: SimpleNamespace,
464 new_size_version: SimpleNamespace,
Yanray Wang802af162023-07-17 14:04:30 +0800465 code_size_common: SimpleNamespace,
Yanray Wang72b105f2023-05-31 15:20:39 +0800466 result_dir: str,
Yanray Wang72b105f2023-05-31 15:20:39 +0800467 ) -> None:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000468 """
Yanray Wang6a862582023-05-24 12:24:38 +0800469 old_revision: revision to compare against.
Xiaofei Baibca03e52021-09-09 09:42:37 +0000470 new_revision:
Yanray Wang6a862582023-05-24 12:24:38 +0800471 result_dir: directory for comparison result.
Xiaofei Baibca03e52021-09-09 09:42:37 +0000472 """
473 self.repo_path = "."
474 self.result_dir = os.path.abspath(result_dir)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000475 os.makedirs(self.result_dir, exist_ok=True)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000476
477 self.csv_dir = os.path.abspath("code_size_records/")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000478 os.makedirs(self.csv_dir, exist_ok=True)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000479
Yanray Wang923f9432023-07-17 12:43:00 +0800480 self.old_size_version = old_size_version
481 self.new_size_version = new_size_version
Yanray Wang802af162023-07-17 14:04:30 +0800482 self.code_size_common = code_size_common
Yanray Wang923f9432023-07-17 12:43:00 +0800483 self.old_size_version.make_cmd = \
Yanray Wang802af162023-07-17 14:04:30 +0800484 CodeSizeBuildInfo(self.old_size_version,\
485 self.code_size_common.host_arch).infer_make_command()
Yanray Wang923f9432023-07-17 12:43:00 +0800486 self.new_size_version.make_cmd = \
Yanray Wang802af162023-07-17 14:04:30 +0800487 CodeSizeBuildInfo(self.new_size_version,\
488 self.code_size_common.host_arch).infer_make_command()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000489 self.git_command = "git"
Yanray Wang4c26db02023-07-04 16:49:04 +0800490 self.make_clean = 'make clean'
Yanray Wang802af162023-07-17 14:04:30 +0800491 self.code_size_generator = self.__init_code_size_generator__(\
492 self.code_size_common.measure_cmd)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000493
Yanray Wang923f9432023-07-17 12:43:00 +0800494 @staticmethod
Yanray Wang802af162023-07-17 14:04:30 +0800495 def __init_code_size_generator__(measure_cmd):
496 if re.match(r'size', measure_cmd.strip()):
497 return CodeSizeGeneratorWithSize()
498 else:
499 print("Error: unsupported tool:", measure_cmd.strip().split(' ')[0])
500 sys.exit(1)
501
502
503 def cal_code_size(self, size_version: SimpleNamespace):
Yanray Wang5e9130a2023-07-17 11:55:54 +0800504 """Calculate code size of library objects in a UTF-8 encoding"""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000505
Yanray Wang802af162023-07-17 14:04:30 +0800506 return CodeSizeCalculator(size_version.revision, size_version.make_cmd,\
507 self.code_size_common.measure_cmd).cal_libraries_code_size()
Yanray Wang8804db92023-05-30 18:18:18 +0800508
Yanray Wang802af162023-07-17 14:04:30 +0800509 def gen_file_name(self, old_size_version, new_size_version=None):
Yanray Wang923f9432023-07-17 12:43:00 +0800510 if new_size_version:
Yanray Wang802af162023-07-17 14:04:30 +0800511 return '{}-{}-{}-{}-{}-{}-{}.csv'\
Yanray Wang923f9432023-07-17 12:43:00 +0800512 .format(old_size_version.revision[:7],
513 old_size_version.arch, old_size_version.config,
514 new_size_version.revision[:7],
Yanray Wang802af162023-07-17 14:04:30 +0800515 new_size_version.arch, new_size_version.config,
516 self.code_size_common.measure_cmd.strip().split(' ')[0])
Yanray Wang923f9432023-07-17 12:43:00 +0800517 else:
Yanray Wang802af162023-07-17 14:04:30 +0800518 return '{}-{}-{}-{}.csv'\
Yanray Wang923f9432023-07-17 12:43:00 +0800519 .format(old_size_version.revision[:7],
Yanray Wang802af162023-07-17 14:04:30 +0800520 old_size_version.arch, old_size_version.config,
521 self.code_size_common.measure_cmd.strip().split(' ')[0])
Yanray Wang923f9432023-07-17 12:43:00 +0800522
523 def gen_code_size_report(self, size_version: SimpleNamespace):
Yanray Wang5e9130a2023-07-17 11:55:54 +0800524 """Generate code size record and write it into a file."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000525
Yanray Wang923f9432023-07-17 12:43:00 +0800526 output_file = os.path.join(self.csv_dir, self.gen_file_name(size_version))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000527 # Check if the corresponding record exists
Yanray Wang923f9432023-07-17 12:43:00 +0800528 if (size_version.revision != "current") and os.path.exists(output_file):
529 print("Code size csv file for", size_version.revision, "already exists.")
530 self.code_size_generator.read_size_record(size_version.revision, output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000531 else:
Yanray Wang923f9432023-07-17 12:43:00 +0800532 self.code_size_generator.size_generator_write_record(\
533 size_version.revision, self.cal_code_size(size_version),
534 output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000535
Yanray Wang5e9130a2023-07-17 11:55:54 +0800536 def gen_code_size_comparison(self) -> int:
537 """Generate results of code size changes between two revisions,
Xiaofei Baibca03e52021-09-09 09:42:37 +0000538 old and new. Measured code size results of these two revisions
Xiaofei Bai2400b502021-10-21 12:22:58 +0000539 must be available."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000540
Yanray Wang923f9432023-07-17 12:43:00 +0800541 output_file = os.path.join(self.result_dir,\
542 self.gen_file_name(self.old_size_version, self.new_size_version))
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000543
Yanray Wangc7a2a6d2023-05-31 15:47:25 +0800544 print("\nGenerating comparison results between",\
Yanray Wang923f9432023-07-17 12:43:00 +0800545 self.old_size_version.revision, "and", self.new_size_version.revision)
Yanray Wange0e27602023-07-14 17:37:45 +0800546 self.code_size_generator.size_generator_write_comparison(\
Yanray Wang923f9432023-07-17 12:43:00 +0800547 self.old_size_version.revision, self.new_size_version.revision,\
548 output_file)
Xiaofei Bai2400b502021-10-21 12:22:58 +0000549 return 0
Xiaofei Baibca03e52021-09-09 09:42:37 +0000550
Yanray Wang72b105f2023-05-31 15:20:39 +0800551 def get_comparision_results(self) -> int:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000552 """Compare size of library/*.o between self.old_rev and self.new_rev,
553 and generate the result file."""
Gilles Peskined9071e72022-09-18 21:17:09 +0200554 build_tree.check_repo_path()
Yanray Wang923f9432023-07-17 12:43:00 +0800555 self.gen_code_size_report(self.old_size_version)
556 self.gen_code_size_report(self.new_size_version)
Yanray Wang5e9130a2023-07-17 11:55:54 +0800557 return self.gen_code_size_comparison()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000558
Yanray Wang923f9432023-07-17 12:43:00 +0800559
Xiaofei Bai2400b502021-10-21 12:22:58 +0000560def main():
Yanray Wang502c54f2023-05-31 11:41:36 +0800561 parser = argparse.ArgumentParser(description=(__doc__))
562 group_required = parser.add_argument_group(
563 'required arguments',
564 'required arguments to parse for running ' + os.path.basename(__file__))
565 group_required.add_argument(
566 "-o", "--old-rev", type=str, required=True,
567 help="old revision for comparison.")
568
569 group_optional = parser.add_argument_group(
570 'optional arguments',
571 'optional arguments to parse for running ' + os.path.basename(__file__))
572 group_optional.add_argument(
Xiaofei Baibca03e52021-09-09 09:42:37 +0000573 "-r", "--result-dir", type=str, default="comparison",
574 help="directory where comparison result is stored, \
Yanray Wang502c54f2023-05-31 11:41:36 +0800575 default is comparison")
576 group_optional.add_argument(
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000577 "-n", "--new-rev", type=str, default=None,
578 help="new revision for comparison, default is the current work \
Yanray Wang502c54f2023-05-31 11:41:36 +0800579 directory, including uncommitted changes.")
580 group_optional.add_argument(
Yanray Wang23bd5322023-05-24 11:03:59 +0800581 "-a", "--arch", type=str, default=detect_arch(),
582 choices=list(map(lambda s: s.value, SupportedArch)),
583 help="specify architecture for code size comparison, default is the\
Yanray Wang502c54f2023-05-31 11:41:36 +0800584 host architecture.")
585 group_optional.add_argument(
Yanray Wang6a862582023-05-24 12:24:38 +0800586 "-c", "--config", type=str, default=SupportedConfig.DEFAULT.value,
587 choices=list(map(lambda s: s.value, SupportedConfig)),
588 help="specify configuration type for code size comparison,\
Yanray Wang502c54f2023-05-31 11:41:36 +0800589 default is the current MbedTLS configuration.")
Xiaofei Baibca03e52021-09-09 09:42:37 +0000590 comp_args = parser.parse_args()
591
592 if os.path.isfile(comp_args.result_dir):
593 print("Error: {} is not a directory".format(comp_args.result_dir))
594 parser.exit()
595
Yanray Wange0e27602023-07-14 17:37:45 +0800596 validate_res = CodeSizeCalculator.validate_revision(comp_args.old_rev)
Xiaofei Baiccd738b2021-11-03 07:12:31 +0000597 old_revision = validate_res.decode().replace("\n", "")
Xiaofei Bai2400b502021-10-21 12:22:58 +0000598
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000599 if comp_args.new_rev is not None:
Yanray Wange0e27602023-07-14 17:37:45 +0800600 validate_res = CodeSizeCalculator.validate_revision(comp_args.new_rev)
Xiaofei Baiccd738b2021-11-03 07:12:31 +0000601 new_revision = validate_res.decode().replace("\n", "")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000602 else:
603 new_revision = "current"
Xiaofei Bai2400b502021-10-21 12:22:58 +0000604
Yanray Wang923f9432023-07-17 12:43:00 +0800605 old_size_version = SimpleNamespace(
606 version="old",
607 revision=old_revision,
608 config=comp_args.config,
609 arch=comp_args.arch,
Yanray Wang923f9432023-07-17 12:43:00 +0800610 make_cmd='',
611 )
612 new_size_version = SimpleNamespace(
613 version="new",
614 revision=new_revision,
615 config=comp_args.config,
616 arch=comp_args.arch,
Yanray Wang923f9432023-07-17 12:43:00 +0800617 make_cmd='',
618 )
Yanray Wang802af162023-07-17 14:04:30 +0800619 code_size_common = SimpleNamespace(
620 host_arch=detect_arch(),
621 measure_cmd='size -t',
622 )
Yanray Wang923f9432023-07-17 12:43:00 +0800623
624 size_compare = CodeSizeComparison(old_size_version, new_size_version,\
Yanray Wang802af162023-07-17 14:04:30 +0800625 code_size_common, comp_args.result_dir)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000626 return_code = size_compare.get_comparision_results()
627 sys.exit(return_code)
628
629
630if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000631 main()