blob: e42a6603bfc398b713d4463567fb44658bb1b40d [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,
278 with_markdown=False
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 Wangb664cb72023-07-18 12:28:35 +0800286 with_markdown: write comparision result in a markdown table.
287 (Default: False)
Yanray Wang15c43f32023-07-17 11:17:12 +0800288 """
289 raise NotImplementedError
290
291
292class CodeSizeGeneratorWithSize(CodeSizeGenerator):
Yanray Wang16ebc572023-05-30 18:10:20 +0800293 """Code Size Base Class for size record saving and writing."""
294
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800295 class SizeEntry: # pylint: disable=too-few-public-methods
296 """Data Structure to only store information of code size."""
297 def __init__(self, text, data, bss, dec):
298 self.text = text
299 self.data = data
300 self.bss = bss
301 self.total = dec # total <=> dec
302
Yanray Wang16ebc572023-05-30 18:10:20 +0800303 def __init__(self) -> None:
304 """ Variable code_size is used to store size info for any revisions.
305 code_size: (data format)
Yanray Wang9b174e92023-07-17 17:59:53 +0800306 {revision: {module: {file_name: [text, data, bss, dec],
Yanray Wang16ebc572023-05-30 18:10:20 +0800307 etc ...
308 },
309 etc ...
310 },
311 etc ...
312 }
313 """
314 self.code_size = {} #type: typing.Dict[str, typing.Dict]
315
316 def set_size_record(self, revision: str, mod: str, size_text: str) -> None:
317 """Store size information for target revision and high-level module.
318
319 size_text Format: text data bss dec hex filename
320 """
321 size_record = {}
322 for line in size_text.splitlines()[1:]:
323 data = line.split()
Yanray Wang9b174e92023-07-17 17:59:53 +0800324 # file_name: SizeEntry(text, data, bss, dec)
325 size_record[data[5]] = CodeSizeGeneratorWithSize.SizeEntry(
326 data[0], data[1], data[2], data[3])
Yanray Wang16ebc572023-05-30 18:10:20 +0800327 if revision in self.code_size:
328 self.code_size[revision].update({mod: size_record})
329 else:
330 self.code_size[revision] = {mod: size_record}
331
332 def read_size_record(self, revision: str, fname: str) -> None:
333 """Read size information from csv file and write it into code_size.
334
335 fname Format: filename text data bss dec
336 """
337 mod = ""
338 size_record = {}
339 with open(fname, 'r') as csv_file:
340 for line in csv_file:
341 data = line.strip().split()
342 # check if we find the beginning of a module
343 if data and data[0] in MBEDTLS_STATIC_LIB:
344 mod = data[0]
345 continue
346
347 if mod:
Yanray Wang9b174e92023-07-17 17:59:53 +0800348 # file_name: SizeEntry(text, data, bss, dec)
349 size_record[data[0]] = CodeSizeGeneratorWithSize.SizeEntry(
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800350 data[1], data[2], data[3], data[4])
Yanray Wang16ebc572023-05-30 18:10:20 +0800351
352 # check if we hit record for the end of a module
353 m = re.match(r'.?TOTALS', line)
354 if m:
355 if revision in self.code_size:
356 self.code_size[revision].update({mod: size_record})
357 else:
358 self.code_size[revision] = {mod: size_record}
359 mod = ""
360 size_record = {}
361
362 def _size_reader_helper(
363 self,
364 revision: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800365 output: typing_util.Writable,
366 with_markdown=False
Yanray Wang16ebc572023-05-30 18:10:20 +0800367 ) -> typing.Iterator[tuple]:
368 """A helper function to peel code_size based on revision."""
369 for mod, file_size in self.code_size[revision].items():
Yanray Wangb664cb72023-07-18 12:28:35 +0800370 if not with_markdown:
371 output.write("\n" + mod + "\n")
Yanray Wang16ebc572023-05-30 18:10:20 +0800372 for fname, size_entry in file_size.items():
373 yield mod, fname, size_entry
374
375 def write_size_record(
376 self,
377 revision: str,
378 output: typing_util.Writable
379 ) -> None:
380 """Write size information to a file.
381
382 Writing Format: file_name text data bss total(dec)
383 """
Yanray Wangb664cb72023-07-18 12:28:35 +0800384 format_string = "{:<30} {:>7} {:>7} {:>7} {:>7}\n"
385 output.write(format_string.format("filename",
386 "text", "data", "bss", "total"))
Yanray Wang16ebc572023-05-30 18:10:20 +0800387 for _, fname, size_entry in self._size_reader_helper(revision, output):
Yanray Wangb664cb72023-07-18 12:28:35 +0800388 output.write(format_string.format(fname,
389 size_entry.text, size_entry.data,
390 size_entry.bss, size_entry.total))
Yanray Wang16ebc572023-05-30 18:10:20 +0800391
392 def write_comparison(
393 self,
394 old_rev: str,
395 new_rev: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800396 output: typing_util.Writable,
397 with_markdown: bool
Yanray Wang16ebc572023-05-30 18:10:20 +0800398 ) -> None:
399 """Write comparison result into a file.
400
Yanray Wang9b174e92023-07-17 17:59:53 +0800401 Writing Format: file_name current(text,data) old(text,data)\
402 change(text,data) change_pct%(text,data)
Yanray Wang16ebc572023-05-30 18:10:20 +0800403 """
Yanray Wang9b174e92023-07-17 17:59:53 +0800404
405 def cal_size_section_variation(mod, fname, size_entry, attr):
406 new_size = int(size_entry.__dict__[attr])
Yanray Wang16ebc572023-05-30 18:10:20 +0800407 # check if we have the file in old revision
408 if fname in self.code_size[old_rev][mod]:
Yanray Wang9b174e92023-07-17 17:59:53 +0800409 old_size = int(self.code_size[old_rev][mod][fname].__dict__[attr])
Yanray Wang16ebc572023-05-30 18:10:20 +0800410 change = new_size - old_size
411 if old_size != 0:
412 change_pct = change / old_size
413 else:
414 change_pct = 0
Yanray Wang9b174e92023-07-17 17:59:53 +0800415 return [new_size, old_size, change, change_pct]
Yanray Wang16ebc572023-05-30 18:10:20 +0800416 else:
Yanray Wang9b174e92023-07-17 17:59:53 +0800417 return [new_size]
418
Yanray Wangb664cb72023-07-18 12:28:35 +0800419 if with_markdown:
420 format_string = "| {:<30} | {:<18} | {:<14} | {:<17} | {:<18} |\n"
421 else:
422 format_string = "{:<30} {:<18} {:<14} {:<17} {:<18}\n"
423
424 output.write(format_string.format("filename", "current(text,data)",\
425 "old(text,data)", "change(text,data)", "change%(text,data)"))
426 if with_markdown:
427 output.write(format_string
428 .format("----:", "----:", "----:", "----:", "----:"))
429
430 for mod, fname, size_entry in\
431 self._size_reader_helper(new_rev, output, with_markdown):
432 text_vari = cal_size_section_variation(mod, fname,
433 size_entry, 'text')
434 data_vari = cal_size_section_variation(mod, fname,
435 size_entry, 'data')
Yanray Wang9b174e92023-07-17 17:59:53 +0800436
437 if len(text_vari) != 1:
Yanray Wangb664cb72023-07-18 12:28:35 +0800438 # skip the files that haven't changed in code size if we write
439 # comparison result in a markdown table.
440 if with_markdown and text_vari[2] == 0 and data_vari[2] == 0:
441 continue
442 output.write(format_string.format(fname,\
443 str(text_vari[0]) + "," + str(data_vari[0]),\
444 str(text_vari[1]) + "," + str(data_vari[1]),\
445 str(text_vari[2]) + "," + str(data_vari[2]),\
446 "{:.2%}".format(text_vari[3]) + "," +\
447 "{:.2%}".format(data_vari[3])))
Yanray Wang9b174e92023-07-17 17:59:53 +0800448 else:
Yanray Wangb664cb72023-07-18 12:28:35 +0800449 output.write("{:<30} {:<18}\n".format(fname,\
450 str(text_vari[0]) + "," + str(data_vari[0])))
Yanray Wang16ebc572023-05-30 18:10:20 +0800451
Yanray Wang15c43f32023-07-17 11:17:12 +0800452 def size_generator_write_record(
453 self,
454 revision: str,
455 code_size_text: typing.Dict,
456 output_file: str
457 ) -> None:
458 """Write size record into a specified file based on Git revision and
459 output from `size` tool."""
460 for mod, size_text in code_size_text.items():
461 self.set_size_record(revision, mod, size_text)
462
463 print("Generating code size csv for", revision)
464 output = open(output_file, "w")
465 self.write_size_record(revision, output)
466
467 def size_generator_write_comparison(
468 self,
469 old_rev: str,
470 new_rev: str,
Yanray Wangb664cb72023-07-18 12:28:35 +0800471 output_stream,
472 with_markdown=False
Yanray Wang15c43f32023-07-17 11:17:12 +0800473 ) -> None:
474 """Write a comparision result into a stream between two revisions."""
475 output = open(output_stream, "w")
Yanray Wangb664cb72023-07-18 12:28:35 +0800476 self.write_comparison(old_rev, new_rev, output, with_markdown)
Yanray Wang15c43f32023-07-17 11:17:12 +0800477
Yanray Wang16ebc572023-05-30 18:10:20 +0800478
Yanray Wangfc6ed4d2023-07-14 17:33:09 +0800479class CodeSizeComparison:
Xiaofei Bai2400b502021-10-21 12:22:58 +0000480 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000481
Yanray Wang72b105f2023-05-31 15:20:39 +0800482 def __init__(
483 self,
Yanray Wang923f9432023-07-17 12:43:00 +0800484 old_size_version: SimpleNamespace,
485 new_size_version: SimpleNamespace,
Yanray Wang802af162023-07-17 14:04:30 +0800486 code_size_common: SimpleNamespace,
Yanray Wang72b105f2023-05-31 15:20:39 +0800487 result_dir: str,
Yanray Wang72b105f2023-05-31 15:20:39 +0800488 ) -> None:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000489 """
Yanray Wang6a862582023-05-24 12:24:38 +0800490 old_revision: revision to compare against.
Xiaofei Baibca03e52021-09-09 09:42:37 +0000491 new_revision:
Yanray Wang6a862582023-05-24 12:24:38 +0800492 result_dir: directory for comparison result.
Xiaofei Baibca03e52021-09-09 09:42:37 +0000493 """
494 self.repo_path = "."
495 self.result_dir = os.path.abspath(result_dir)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000496 os.makedirs(self.result_dir, exist_ok=True)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000497
498 self.csv_dir = os.path.abspath("code_size_records/")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000499 os.makedirs(self.csv_dir, exist_ok=True)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000500
Yanray Wang923f9432023-07-17 12:43:00 +0800501 self.old_size_version = old_size_version
502 self.new_size_version = new_size_version
Yanray Wang802af162023-07-17 14:04:30 +0800503 self.code_size_common = code_size_common
Yanray Wang923f9432023-07-17 12:43:00 +0800504 self.old_size_version.make_cmd = \
Yanray Wang802af162023-07-17 14:04:30 +0800505 CodeSizeBuildInfo(self.old_size_version,\
506 self.code_size_common.host_arch).infer_make_command()
Yanray Wang923f9432023-07-17 12:43:00 +0800507 self.new_size_version.make_cmd = \
Yanray Wang802af162023-07-17 14:04:30 +0800508 CodeSizeBuildInfo(self.new_size_version,\
509 self.code_size_common.host_arch).infer_make_command()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000510 self.git_command = "git"
Yanray Wang4c26db02023-07-04 16:49:04 +0800511 self.make_clean = 'make clean'
Yanray Wang802af162023-07-17 14:04:30 +0800512 self.code_size_generator = self.__init_code_size_generator__(\
513 self.code_size_common.measure_cmd)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000514
Yanray Wang923f9432023-07-17 12:43:00 +0800515 @staticmethod
Yanray Wang802af162023-07-17 14:04:30 +0800516 def __init_code_size_generator__(measure_cmd):
517 if re.match(r'size', measure_cmd.strip()):
518 return CodeSizeGeneratorWithSize()
519 else:
520 print("Error: unsupported tool:", measure_cmd.strip().split(' ')[0])
521 sys.exit(1)
522
523
524 def cal_code_size(self, size_version: SimpleNamespace):
Yanray Wang5e9130a2023-07-17 11:55:54 +0800525 """Calculate code size of library objects in a UTF-8 encoding"""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000526
Yanray Wang802af162023-07-17 14:04:30 +0800527 return CodeSizeCalculator(size_version.revision, size_version.make_cmd,\
528 self.code_size_common.measure_cmd).cal_libraries_code_size()
Yanray Wang8804db92023-05-30 18:18:18 +0800529
Yanray Wang802af162023-07-17 14:04:30 +0800530 def gen_file_name(self, old_size_version, new_size_version=None):
Yanray Wang923f9432023-07-17 12:43:00 +0800531 if new_size_version:
Yanray Wang802af162023-07-17 14:04:30 +0800532 return '{}-{}-{}-{}-{}-{}-{}.csv'\
Yanray Wang923f9432023-07-17 12:43:00 +0800533 .format(old_size_version.revision[:7],
534 old_size_version.arch, old_size_version.config,
535 new_size_version.revision[:7],
Yanray Wang802af162023-07-17 14:04:30 +0800536 new_size_version.arch, new_size_version.config,
537 self.code_size_common.measure_cmd.strip().split(' ')[0])
Yanray Wang923f9432023-07-17 12:43:00 +0800538 else:
Yanray Wang802af162023-07-17 14:04:30 +0800539 return '{}-{}-{}-{}.csv'\
Yanray Wang923f9432023-07-17 12:43:00 +0800540 .format(old_size_version.revision[:7],
Yanray Wang802af162023-07-17 14:04:30 +0800541 old_size_version.arch, old_size_version.config,
542 self.code_size_common.measure_cmd.strip().split(' ')[0])
Yanray Wang923f9432023-07-17 12:43:00 +0800543
544 def gen_code_size_report(self, size_version: SimpleNamespace):
Yanray Wang5e9130a2023-07-17 11:55:54 +0800545 """Generate code size record and write it into a file."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000546
Yanray Wang923f9432023-07-17 12:43:00 +0800547 output_file = os.path.join(self.csv_dir, self.gen_file_name(size_version))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000548 # Check if the corresponding record exists
Yanray Wang923f9432023-07-17 12:43:00 +0800549 if (size_version.revision != "current") and os.path.exists(output_file):
550 print("Code size csv file for", size_version.revision, "already exists.")
551 self.code_size_generator.read_size_record(size_version.revision, output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000552 else:
Yanray Wang923f9432023-07-17 12:43:00 +0800553 self.code_size_generator.size_generator_write_record(\
554 size_version.revision, self.cal_code_size(size_version),
555 output_file)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000556
Yanray Wang5e9130a2023-07-17 11:55:54 +0800557 def gen_code_size_comparison(self) -> int:
558 """Generate results of code size changes between two revisions,
Xiaofei Baibca03e52021-09-09 09:42:37 +0000559 old and new. Measured code size results of these two revisions
Xiaofei Bai2400b502021-10-21 12:22:58 +0000560 must be available."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000561
Yanray Wang923f9432023-07-17 12:43:00 +0800562 output_file = os.path.join(self.result_dir,\
563 self.gen_file_name(self.old_size_version, self.new_size_version))
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000564
Yanray Wangc7a2a6d2023-05-31 15:47:25 +0800565 print("\nGenerating comparison results between",\
Yanray Wang923f9432023-07-17 12:43:00 +0800566 self.old_size_version.revision, "and", self.new_size_version.revision)
Yanray Wange0e27602023-07-14 17:37:45 +0800567 self.code_size_generator.size_generator_write_comparison(\
Yanray Wang923f9432023-07-17 12:43:00 +0800568 self.old_size_version.revision, self.new_size_version.revision,\
Yanray Wangb664cb72023-07-18 12:28:35 +0800569 output_file, self.code_size_common.with_markdown)
Xiaofei Bai2400b502021-10-21 12:22:58 +0000570 return 0
Xiaofei Baibca03e52021-09-09 09:42:37 +0000571
Yanray Wang72b105f2023-05-31 15:20:39 +0800572 def get_comparision_results(self) -> int:
Xiaofei Baibca03e52021-09-09 09:42:37 +0000573 """Compare size of library/*.o between self.old_rev and self.new_rev,
574 and generate the result file."""
Gilles Peskined9071e72022-09-18 21:17:09 +0200575 build_tree.check_repo_path()
Yanray Wang923f9432023-07-17 12:43:00 +0800576 self.gen_code_size_report(self.old_size_version)
577 self.gen_code_size_report(self.new_size_version)
Yanray Wang5e9130a2023-07-17 11:55:54 +0800578 return self.gen_code_size_comparison()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000579
Yanray Wang923f9432023-07-17 12:43:00 +0800580
Xiaofei Bai2400b502021-10-21 12:22:58 +0000581def main():
Yanray Wang502c54f2023-05-31 11:41:36 +0800582 parser = argparse.ArgumentParser(description=(__doc__))
583 group_required = parser.add_argument_group(
584 'required arguments',
585 'required arguments to parse for running ' + os.path.basename(__file__))
586 group_required.add_argument(
587 "-o", "--old-rev", type=str, required=True,
588 help="old revision for comparison.")
589
590 group_optional = parser.add_argument_group(
591 'optional arguments',
592 'optional arguments to parse for running ' + os.path.basename(__file__))
593 group_optional.add_argument(
Xiaofei Baibca03e52021-09-09 09:42:37 +0000594 "-r", "--result-dir", type=str, default="comparison",
595 help="directory where comparison result is stored, \
Yanray Wang502c54f2023-05-31 11:41:36 +0800596 default is comparison")
597 group_optional.add_argument(
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000598 "-n", "--new-rev", type=str, default=None,
599 help="new revision for comparison, default is the current work \
Yanray Wang502c54f2023-05-31 11:41:36 +0800600 directory, including uncommitted changes.")
601 group_optional.add_argument(
Yanray Wang23bd5322023-05-24 11:03:59 +0800602 "-a", "--arch", type=str, default=detect_arch(),
603 choices=list(map(lambda s: s.value, SupportedArch)),
604 help="specify architecture for code size comparison, default is the\
Yanray Wang502c54f2023-05-31 11:41:36 +0800605 host architecture.")
606 group_optional.add_argument(
Yanray Wang6a862582023-05-24 12:24:38 +0800607 "-c", "--config", type=str, default=SupportedConfig.DEFAULT.value,
608 choices=list(map(lambda s: s.value, SupportedConfig)),
609 help="specify configuration type for code size comparison,\
Yanray Wang502c54f2023-05-31 11:41:36 +0800610 default is the current MbedTLS configuration.")
Yanray Wangb664cb72023-07-18 12:28:35 +0800611 group_optional.add_argument(
612 '--markdown', action='store_true', dest='markdown',
613 help="Show comparision of code size in a markdown table\
614 (only show the files that have changed).")
Xiaofei Baibca03e52021-09-09 09:42:37 +0000615 comp_args = parser.parse_args()
616
617 if os.path.isfile(comp_args.result_dir):
618 print("Error: {} is not a directory".format(comp_args.result_dir))
619 parser.exit()
620
Yanray Wange0e27602023-07-14 17:37:45 +0800621 validate_res = CodeSizeCalculator.validate_revision(comp_args.old_rev)
Xiaofei Baiccd738b2021-11-03 07:12:31 +0000622 old_revision = validate_res.decode().replace("\n", "")
Xiaofei Bai2400b502021-10-21 12:22:58 +0000623
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000624 if comp_args.new_rev is not None:
Yanray Wange0e27602023-07-14 17:37:45 +0800625 validate_res = CodeSizeCalculator.validate_revision(comp_args.new_rev)
Xiaofei Baiccd738b2021-11-03 07:12:31 +0000626 new_revision = validate_res.decode().replace("\n", "")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000627 else:
628 new_revision = "current"
Xiaofei Bai2400b502021-10-21 12:22:58 +0000629
Yanray Wang923f9432023-07-17 12:43:00 +0800630 old_size_version = SimpleNamespace(
631 version="old",
632 revision=old_revision,
633 config=comp_args.config,
634 arch=comp_args.arch,
Yanray Wang923f9432023-07-17 12:43:00 +0800635 make_cmd='',
636 )
637 new_size_version = SimpleNamespace(
638 version="new",
639 revision=new_revision,
640 config=comp_args.config,
641 arch=comp_args.arch,
Yanray Wang923f9432023-07-17 12:43:00 +0800642 make_cmd='',
643 )
Yanray Wang802af162023-07-17 14:04:30 +0800644 code_size_common = SimpleNamespace(
645 host_arch=detect_arch(),
646 measure_cmd='size -t',
Yanray Wangb664cb72023-07-18 12:28:35 +0800647 with_markdown=comp_args.markdown
Yanray Wang802af162023-07-17 14:04:30 +0800648 )
Yanray Wang923f9432023-07-17 12:43:00 +0800649
650 size_compare = CodeSizeComparison(old_size_version, new_size_version,\
Yanray Wang802af162023-07-17 14:04:30 +0800651 code_size_common, comp_args.result_dir)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000652 return_code = size_compare.get_comparision_results()
653 sys.exit(return_code)
654
655
656if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000657 main()