blob: 0bd914396148b282586212a3e903bc87695dcc9c [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,
Yanray Wangb664cb72023-07-18 12:28:35 +0800277 output_stream,
Yanray Wang227576a2023-07-18 14:35:05 +0800278 result_options: SimpleNamespace
Yanray Wang15c43f32023-07-17 11:17:12 +0800279 ) -> None:
280 """Write a comparision result into a stream between two revisions.
281
282 old_rev: old git revision to compared with.
283 new_rev: new git revision to compared with.
284 output_stream: stream which the code size record is written to.
285 (E.g: file / sys.stdout)
Yanray Wang227576a2023-07-18 14:35:05 +0800286 result_options: SimpleNamespace containing options for comparison result.
287 with_markdown: write comparision result in a markdown table. (Default: False)
288 stdout: direct comparison result into sys.stdout. (Default: False)
Yanray Wang15c43f32023-07-17 11:17:12 +0800289 """
290 raise NotImplementedError
291
292
293class CodeSizeGeneratorWithSize(CodeSizeGenerator):
Yanray Wang16ebc572023-05-30 18:10:20 +0800294 """Code Size Base Class for size record saving and writing."""
295
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800296 class SizeEntry: # pylint: disable=too-few-public-methods
297 """Data Structure to only store information of code size."""
298 def __init__(self, text, data, bss, dec):
299 self.text = text
300 self.data = data
301 self.bss = bss
302 self.total = dec # total <=> dec
303
Yanray Wang16ebc572023-05-30 18:10:20 +0800304 def __init__(self) -> None:
305 """ Variable code_size is used to store size info for any revisions.
306 code_size: (data format)
Yanray Wang9b174e92023-07-17 17:59:53 +0800307 {revision: {module: {file_name: [text, data, bss, dec],
Yanray Wang16ebc572023-05-30 18:10:20 +0800308 etc ...
309 },
310 etc ...
311 },
312 etc ...
313 }
314 """
315 self.code_size = {} #type: typing.Dict[str, typing.Dict]
316
317 def set_size_record(self, revision: str, mod: str, size_text: str) -> None:
318 """Store size information for target revision and high-level module.
319
320 size_text Format: text data bss dec hex filename
321 """
322 size_record = {}
323 for line in size_text.splitlines()[1:]:
324 data = line.split()
Yanray Wang9b174e92023-07-17 17:59:53 +0800325 # file_name: SizeEntry(text, data, bss, dec)
326 size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry(
327 data[0], data[1], data[2], data[3])
Yanray Wang16ebc572023-05-30 18:10:20 +0800328 if revision in self.code_size:
329 self.code_size[revision].update({mod: size_record})
330 else:
331 self.code_size[revision] = {mod: size_record}
332
333 def read_size_record(self, revision: str, fname: str) -> None:
334 """Read size information from csv file and write it into code_size.
335
336 fname Format: filename text data bss dec
337 """
338 mod = ""
339 size_record = {}
340 with open(fname, 'r') as csv_file:
341 for line in csv_file:
342 data = line.strip().split()
343 # check if we find the beginning of a module
344 if data and data[0] in MBEDTLS_STATIC_LIB:
345 mod = data[0]
346 continue
347
348 if mod:
Yanray Wang9b174e92023-07-17 17:59:53 +0800349 # file_name: SizeEntry(text, data, bss, dec)
350 size_record[data[0]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800351 data[1], data[2], data[3], data[4])
Yanray Wang16ebc572023-05-30 18:10:20 +0800352
353 # check if we hit record for the end of a module
354 m = re.match(r'.?TOTALS', line)
355 if m:
356 if revision in self.code_size:
357 self.code_size[revision].update({mod: size_record})
358 else:
359 self.code_size[revision] = {mod: size_record}
360 mod = ""
361 size_record = {}
362
363 def _size_reader_helper(
364 self,
365 revision: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800366 output: typing_util.Writable,
367 with_markdown=False
Yanray Wang16ebc572023-05-30 18:10:20 +0800368 ) -> typing.Iterator[tuple]:
369 """A helper function to peel code_size based on revision."""
370 for mod, file_size in self.code_size[revision].items():
Yanray Wangb664cb72023-07-18 12:28:35 +0800371 if not with_markdown:
372 output.write("\n" + mod + "\n")
Yanray Wang16ebc572023-05-30 18:10:20 +0800373 for fname, size_entry in file_size.items():
374 yield mod, fname, size_entry
375
376 def write_size_record(
377 self,
378 revision: str,
379 output: typing_util.Writable
380 ) -> None:
381 """Write size information to a file.
382
383 Writing Format: file_name text data bss total(dec)
384 """
Yanray Wangb664cb72023-07-18 12:28:35 +0800385 format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n"
386 output.write(format_string.format("filename",
387 "text", "data", "bss", "total"))
Yanray Wang16ebc572023-05-30 18:10:20 +0800388 for _, fname, size_entry in self._size_reader_helper(revision, output):
Yanray Wangb664cb72023-07-18 12:28:35 +0800389 output.write(format_string.format(fname,
390 size_entry.text, size_entry.data,
391 size_entry.bss, size_entry.total))
Yanray Wang16ebc572023-05-30 18:10:20 +0800392
393 def write_comparison(
394 self,
395 old_rev: str,
396 new_rev: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800397 output: typing_util.Writable,
398 with_markdown: bool
Yanray Wang16ebc572023-05-30 18:10:20 +0800399 ) -> None:
400 """Write comparison result into a file.
401
Yanray Wang9b174e92023-07-17 17:59:53 +0800402 Writing Format: file_name current(text,data) old(text,data)\
403 change(text,data) change_pct%(text,data)
Yanray Wang16ebc572023-05-30 18:10:20 +0800404 """
Yanray Wang9b174e92023-07-17 17:59:53 +0800405
406 def cal_size_section_variation(mod, fname, size_entry, attr):
407 new_size = int(size_entry.__dict__[attr])
Yanray Wang16ebc572023-05-30 18:10:20 +0800408 # check if we have the file in old revision
409 if fname in self.code_size[old_rev][mod]:
Yanray Wang9b174e92023-07-17 17:59:53 +0800410 old_size = int(self.code_size[old_rev][mod][fname].__dict__[attr])
Yanray Wang16ebc572023-05-30 18:10:20 +0800411 change = new_size - old_size
412 if old_size != 0:
413 change_pct = change / old_size
414 else:
415 change_pct = 0
Yanray Wang9b174e92023-07-17 17:59:53 +0800416 return [new_size, old_size, change, change_pct]
Yanray Wang16ebc572023-05-30 18:10:20 +0800417 else:
Yanray Wang9b174e92023-07-17 17:59:53 +0800418 return [new_size]
419
Yanray Wangb664cb72023-07-18 12:28:35 +0800420 if with_markdown:
421 format_string = "| {:<30} | {:<18} | {:<14} | {:<17} | {:<18} |\n"
422 else:
423 format_string = "{:<30} {:<18} {:<14} {:<17} {:<18}\n"
424
425 output.write(format_string.format("filename", "current(text,data)",\
426 "old(text,data)", "change(text,data)", "change%(text,data)"))
427 if with_markdown:
428 output.write(format_string
429 .format("----:", "----:", "----:", "----:", "----:"))
430
431 for mod, fname, size_entry in\
432 self._size_reader_helper(new_rev, output, with_markdown):
433 text_vari = cal_size_section_variation(mod, fname,
434 size_entry, 'text')
435 data_vari = cal_size_section_variation(mod, fname,
436 size_entry, 'data')
Yanray Wang9b174e92023-07-17 17:59:53 +0800437
438 if len(text_vari) != 1:
Yanray Wangb664cb72023-07-18 12:28:35 +0800439 # skip the files that haven't changed in code size if we write
440 # comparison result in a markdown table.
441 if with_markdown and text_vari[2] == 0 and data_vari[2] == 0:
442 continue
443 output.write(format_string.format(fname,\
444 str(text_vari[0]) + "," + str(data_vari[0]),\
445 str(text_vari[1]) + "," + str(data_vari[1]),\
446 str(text_vari[2]) + "," + str(data_vari[2]),\
447 "{:.2%}".format(text_vari[3]) + "," +\
448 "{:.2%}".format(data_vari[3])))
Yanray Wang9b174e92023-07-17 17:59:53 +0800449 else:
Yanray Wangb664cb72023-07-18 12:28:35 +0800450 output.write("{:<30} {:<18}\n".format(fname,\
451 str(text_vari[0]) + "," + str(data_vari[0])))
Yanray Wang16ebc572023-05-30 18:10:20 +0800452
Yanray Wang15c43f32023-07-17 11:17:12 +0800453 def size_generator_write_record(
454 self,
455 revision: str,
456 code_size_text: typing.Dict,
457 output_file: str
458 ) -> None:
459 """Write size record into a specified file based on Git revision and
460 output from `size` tool."""
461 for mod, size_text in code_size_text.items():
462 self.set_size_record(revision, mod, size_text)
463
464 print("Generating code size csv for", revision)
465 output = open(output_file, "w")
466 self.write_size_record(revision, output)
467
468 def size_generator_write_comparison(
469 self,
470 old_rev: str,
471 new_rev: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800472 output_stream,
Yanray Wang227576a2023-07-18 14:35:05 +0800473 result_options: SimpleNamespace
Yanray Wang15c43f32023-07-17 11:17:12 +0800474 ) -> None:
475 """Write a comparision result into a stream between two revisions."""
Yanray Wang227576a2023-07-18 14:35:05 +0800476 if result_options.stdout:
477 output = sys.stdout
478 else:
479 output = open(output_stream, "w")
480 self.write_comparison(old_rev, new_rev, output, result_options.with_markdown)
Yanray Wang15c43f32023-07-17 11:17:12 +0800481
Yanray Wang16ebc572023-05-30 18:10:20 +0800482
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800483class CodeSizeComparison:
Xiaofei Bai2400b502021-10-21 12:22:58 +0000484 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000485
Yanray Wang72b105f2023-05-31 15:20:39 +0800486 def __init__(
487 self,
Yanray Wang923f9432023-07-17 12:43:00 +0800488 old_size_version: SimpleNamespace,
489 new_size_version: SimpleNamespace,
Yanray Wang802af162023-07-17 14:04:30 +0800490 code_size_common: SimpleNamespace,
Yanray Wang72b105f2023-05-31 15:20:39 +0800491 ) -> None:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000492 """
Yanray Wang6a862582023-05-24 12:24:38 +0800493 old_revision: revision to compare against.
Xiaofei Baibca03e52021-09-09 09:42:37 +0000494 new_revision:
Yanray Wang6a862582023-05-24 12:24:38 +0800495 result_dir: directory for comparison result.
Xiaofei Baibca03e52021-09-09 09:42:37 +0000496 """
497 self.repo_path = "."
Yanray Wang227576a2023-07-18 14:35:05 +0800498 self.result_dir = os.path.abspath(code_size_common.result_options.result_dir)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000499 os.makedirs(self.result_dir, exist_ok=True)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000500
501 self.csv_dir = os.path.abspath("code_size_records/")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000502 os.makedirs(self.csv_dir, exist_ok=True)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000503
Yanray Wang923f9432023-07-17 12:43:00 +0800504 self.old_size_version = old_size_version
505 self.new_size_version = new_size_version
Yanray Wang802af162023-07-17 14:04:30 +0800506 self.code_size_common = code_size_common
Yanray Wang923f9432023-07-17 12:43:00 +0800507 self.old_size_version.make_cmd = \
Yanray Wang802af162023-07-17 14:04:30 +0800508 CodeSizeBuildInfo(self.old_size_version,\
509 self.code_size_common.host_arch).infer_make_command()
Yanray Wang923f9432023-07-17 12:43:00 +0800510 self.new_size_version.make_cmd = \
Yanray Wang802af162023-07-17 14:04:30 +0800511 CodeSizeBuildInfo(self.new_size_version,\
512 self.code_size_common.host_arch).infer_make_command()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000513 self.git_command = "git"
Yanray Wang4c26db02023-07-04 16:49:04 +0800514 self.make_clean = 'make clean'
Yanray Wang802af162023-07-17 14:04:30 +0800515 self.code_size_generator = self.__init_code_size_generator__(\
516 self.code_size_common.measure_cmd)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000517
Yanray Wang923f9432023-07-17 12:43:00 +0800518 @staticmethod
Yanray Wang802af162023-07-17 14:04:30 +0800519 def __init_code_size_generator__(measure_cmd):
520 if re.match(r'size', measure_cmd.strip()):
521 return CodeSizeGeneratorWithSize()
522 else:
523 print("Error: unsupported tool:", measure_cmd.strip().split(' ')[0])
524 sys.exit(1)
525
526
527 def cal_code_size(self, size_version: SimpleNamespace):
Yanray Wang5e9130a2023-07-17 11:55:54 +0800528 """Calculate code size of library objects in a UTF-8 encoding"""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000529
Yanray Wang802af162023-07-17 14:04:30 +0800530 return CodeSizeCalculator(size_version.revision, size_version.make_cmd,\
531 self.code_size_common.measure_cmd).cal_libraries_code_size()
Yanray Wang8804db92023-05-30 18:18:18 +0800532
Yanray Wang802af162023-07-17 14:04:30 +0800533 def gen_file_name(self, old_size_version, new_size_version=None):
Yanray Wang923f9432023-07-17 12:43:00 +0800534 if new_size_version:
Yanray Wang802af162023-07-17 14:04:30 +0800535 return '{}-{}-{}-{}-{}-{}-{}.csv'\
Yanray Wang923f9432023-07-17 12:43:00 +0800536 .format(old_size_version.revision[:7],
537 old_size_version.arch, old_size_version.config,
538 new_size_version.revision[:7],
Yanray Wang802af162023-07-17 14:04:30 +0800539 new_size_version.arch, new_size_version.config,
540 self.code_size_common.measure_cmd.strip().split(' ')[0])
Yanray Wang923f9432023-07-17 12:43:00 +0800541 else:
Yanray Wang802af162023-07-17 14:04:30 +0800542 return '{}-{}-{}-{}.csv'\
Yanray Wang923f9432023-07-17 12:43:00 +0800543 .format(old_size_version.revision[:7],
Yanray Wang802af162023-07-17 14:04:30 +0800544 old_size_version.arch, old_size_version.config,
545 self.code_size_common.measure_cmd.strip().split(' ')[0])
Yanray Wang923f9432023-07-17 12:43:00 +0800546
547 def gen_code_size_report(self, size_version: SimpleNamespace):
Yanray Wang5e9130a2023-07-17 11:55:54 +0800548 """Generate code size record and write it into a file."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000549
Yanray Wang923f9432023-07-17 12:43:00 +0800550 output_file = os.path.join(self.csv_dir, self.gen_file_name(size_version))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000551 # Check if the corresponding record exists
Yanray Wang923f9432023-07-17 12:43:00 +0800552 if (size_version.revision != "current") and os.path.exists(output_file):
553 print("Code size csv file for", size_version.revision, "already exists.")
554 self.code_size_generator.read_size_record(size_version.revision, output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000555 else:
Yanray Wang923f9432023-07-17 12:43:00 +0800556 self.code_size_generator.size_generator_write_record(\
557 size_version.revision, self.cal_code_size(size_version),
558 output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000559
Yanray Wang5e9130a2023-07-17 11:55:54 +0800560 def gen_code_size_comparison(self) -> int:
561 """Generate results of code size changes between two revisions,
Xiaofei Baibca03e52021-09-09 09:42:37 +0000562 old and new. Measured code size results of these two revisions
Xiaofei Bai2400b502021-10-21 12:22:58 +0000563 must be available."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000564
Yanray Wang923f9432023-07-17 12:43:00 +0800565 output_file = os.path.join(self.result_dir,\
566 self.gen_file_name(self.old_size_version, self.new_size_version))
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000567
Yanray Wangc7a2a6d2023-05-31 15:47:25 +0800568 print("\nGenerating comparison results between",\
Yanray Wang923f9432023-07-17 12:43:00 +0800569 self.old_size_version.revision, "and", self.new_size_version.revision)
Yanray Wange0e27602023-07-14 17:37:45 +0800570 self.code_size_generator.size_generator_write_comparison(\
Yanray Wang923f9432023-07-17 12:43:00 +0800571 self.old_size_version.revision, self.new_size_version.revision,\
Yanray Wang227576a2023-07-18 14:35:05 +0800572 output_file, self.code_size_common.result_options)
Xiaofei Bai2400b502021-10-21 12:22:58 +0000573 return 0
Xiaofei Baibca03e52021-09-09 09:42:37 +0000574
Yanray Wang72b105f2023-05-31 15:20:39 +0800575 def get_comparision_results(self) -> int:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000576 """Compare size of library/*.o between self.old_rev and self.new_rev,
577 and generate the result file."""
Gilles Peskined9071e72022-09-18 21:17:09 +0200578 build_tree.check_repo_path()
Yanray Wang923f9432023-07-17 12:43:00 +0800579 self.gen_code_size_report(self.old_size_version)
580 self.gen_code_size_report(self.new_size_version)
Yanray Wang5e9130a2023-07-17 11:55:54 +0800581 return self.gen_code_size_comparison()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000582
Yanray Wang923f9432023-07-17 12:43:00 +0800583
Xiaofei Bai2400b502021-10-21 12:22:58 +0000584def main():
Yanray Wang502c54f2023-05-31 11:41:36 +0800585 parser = argparse.ArgumentParser(description=(__doc__))
586 group_required = parser.add_argument_group(
587 'required arguments',
588 'required arguments to parse for running ' + os.path.basename(__file__))
589 group_required.add_argument(
590 "-o", "--old-rev", type=str, required=True,
591 help="old revision for comparison.")
592
593 group_optional = parser.add_argument_group(
594 'optional arguments',
595 'optional arguments to parse for running ' + os.path.basename(__file__))
596 group_optional.add_argument(
Xiaofei Baibca03e52021-09-09 09:42:37 +0000597 "-r", "--result-dir", type=str, default="comparison",
598 help="directory where comparison result is stored, \
Yanray Wang502c54f2023-05-31 11:41:36 +0800599 default is comparison")
600 group_optional.add_argument(
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000601 "-n", "--new-rev", type=str, default=None,
602 help="new revision for comparison, default is the current work \
Yanray Wang502c54f2023-05-31 11:41:36 +0800603 directory, including uncommitted changes.")
604 group_optional.add_argument(
Yanray Wang23bd5322023-05-24 11:03:59 +0800605 "-a", "--arch", type=str, default=detect_arch(),
606 choices=list(map(lambda s: s.value, SupportedArch)),
607 help="specify architecture for code size comparison, default is the\
Yanray Wang502c54f2023-05-31 11:41:36 +0800608 host architecture.")
609 group_optional.add_argument(
Yanray Wang6a862582023-05-24 12:24:38 +0800610 "-c", "--config", type=str, default=SupportedConfig.DEFAULT.value,
611 choices=list(map(lambda s: s.value, SupportedConfig)),
612 help="specify configuration type for code size comparison,\
Yanray Wang502c54f2023-05-31 11:41:36 +0800613 default is the current MbedTLS configuration.")
Yanray Wangb664cb72023-07-18 12:28:35 +0800614 group_optional.add_argument(
615 '--markdown', action='store_true', dest='markdown',
616 help="Show comparision of code size in a markdown table\
617 (only show the files that have changed).")
Yanray Wang227576a2023-07-18 14:35:05 +0800618 group_optional.add_argument(
619 '--stdout', action='store_true', dest='stdout',
620 help="Set this option to direct comparison result into sys.stdout.\
621 (Default: file)")
Xiaofei Baibca03e52021-09-09 09:42:37 +0000622 comp_args = parser.parse_args()
623
624 if os.path.isfile(comp_args.result_dir):
625 print("Error: {} is not a directory".format(comp_args.result_dir))
626 parser.exit()
627
Yanray Wange0e27602023-07-14 17:37:45 +0800628 validate_res = CodeSizeCalculator.validate_revision(comp_args.old_rev)
Xiaofei Baiccd738b2021-11-03 07:12:31 +0000629 old_revision = validate_res.decode().replace("\n", "")
Xiaofei Bai2400b502021-10-21 12:22:58 +0000630
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000631 if comp_args.new_rev is not None:
Yanray Wange0e27602023-07-14 17:37:45 +0800632 validate_res = CodeSizeCalculator.validate_revision(comp_args.new_rev)
Xiaofei Baiccd738b2021-11-03 07:12:31 +0000633 new_revision = validate_res.decode().replace("\n", "")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000634 else:
635 new_revision = "current"
Xiaofei Bai2400b502021-10-21 12:22:58 +0000636
Yanray Wang923f9432023-07-17 12:43:00 +0800637 old_size_version = SimpleNamespace(
638 version="old",
639 revision=old_revision,
640 config=comp_args.config,
641 arch=comp_args.arch,
Yanray Wang923f9432023-07-17 12:43:00 +0800642 make_cmd='',
643 )
644 new_size_version = SimpleNamespace(
645 version="new",
646 revision=new_revision,
647 config=comp_args.config,
648 arch=comp_args.arch,
Yanray Wang923f9432023-07-17 12:43:00 +0800649 make_cmd='',
650 )
Yanray Wang802af162023-07-17 14:04:30 +0800651 code_size_common = SimpleNamespace(
Yanray Wang227576a2023-07-18 14:35:05 +0800652 result_options=SimpleNamespace(
653 result_dir=comp_args.result_dir,
654 with_markdown=comp_args.markdown,
655 stdout=comp_args.stdout,
656 ),
Yanray Wang802af162023-07-17 14:04:30 +0800657 host_arch=detect_arch(),
658 measure_cmd='size -t',
659 )
Yanray Wang923f9432023-07-17 12:43:00 +0800660
661 size_compare = CodeSizeComparison(old_size_version, new_size_version,\
Yanray Wang227576a2023-07-18 14:35:05 +0800662 code_size_common)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000663 return_code = size_compare.get_comparision_results()
664 sys.exit(return_code)
665
666
667if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000668 main()