blob: a2b007c4aa6d618c85887a4440c33352aeff8381 [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 Wang16ebc572023-05-30 18:10:20 +080034from mbedtls_dev import typing_util
Gilles Peskined9071e72022-09-18 21:17:09 +020035from mbedtls_dev import build_tree
36
Yanray Wang23bd5322023-05-24 11:03:59 +080037class SupportedArch(Enum):
38 """Supported architecture for code size measurement."""
39 AARCH64 = 'aarch64'
40 AARCH32 = 'aarch32'
Yanray Wangaba71582023-05-29 16:45:56 +080041 ARMV8_M = 'armv8-m'
Yanray Wang23bd5322023-05-24 11:03:59 +080042 X86_64 = 'x86_64'
43 X86 = 'x86'
44
Yanray Wang6a862582023-05-24 12:24:38 +080045CONFIG_TFM_MEDIUM_MBEDCRYPTO_H = "../configs/tfm_mbedcrypto_config_profile_medium.h"
46CONFIG_TFM_MEDIUM_PSA_CRYPTO_H = "../configs/crypto_config_profile_medium.h"
47class SupportedConfig(Enum):
48 """Supported configuration for code size measurement."""
49 DEFAULT = 'default'
50 TFM_MEDIUM = 'tfm-medium'
51
Yanray Wang16ebc572023-05-30 18:10:20 +080052# Static library
53MBEDTLS_STATIC_LIB = {
54 'CRYPTO': 'library/libmbedcrypto.a',
55 'X509': 'library/libmbedx509.a',
56 'TLS': 'library/libmbedtls.a',
57}
58
Yanray Wang23bd5322023-05-24 11:03:59 +080059DETECT_ARCH_CMD = "cc -dM -E - < /dev/null"
60def detect_arch() -> str:
61 """Auto-detect host architecture."""
62 cc_output = subprocess.check_output(DETECT_ARCH_CMD, shell=True).decode()
63 if "__aarch64__" in cc_output:
64 return SupportedArch.AARCH64.value
65 if "__arm__" in cc_output:
66 return SupportedArch.AARCH32.value
67 if "__x86_64__" in cc_output:
68 return SupportedArch.X86_64.value
69 if "__x86__" in cc_output:
70 return SupportedArch.X86.value
71 else:
72 print("Unknown host architecture, cannot auto-detect arch.")
73 sys.exit(1)
Gilles Peskined9071e72022-09-18 21:17:09 +020074
Yanray Wang6a862582023-05-24 12:24:38 +080075class CodeSizeInfo: # pylint: disable=too-few-public-methods
76 """Gather information used to measure code size.
77
78 It collects information about architecture, configuration in order to
79 infer build command for code size measurement.
80 """
81
Yanray Wangc18cd892023-05-31 11:08:04 +080082 SupportedArchConfig = [
83 "-a " + SupportedArch.AARCH64.value + " -c " + SupportedConfig.DEFAULT.value,
84 "-a " + SupportedArch.AARCH32.value + " -c " + SupportedConfig.DEFAULT.value,
85 "-a " + SupportedArch.X86_64.value + " -c " + SupportedConfig.DEFAULT.value,
86 "-a " + SupportedArch.X86.value + " -c " + SupportedConfig.DEFAULT.value,
87 "-a " + SupportedArch.ARMV8_M.value + " -c " + SupportedConfig.TFM_MEDIUM.value,
88 ]
89
Yanray Wang21f17442023-06-01 11:29:06 +080090 def __init__(self, arch: str, config: str, sys_arch: str) -> None:
Yanray Wang6a862582023-05-24 12:24:38 +080091 """
92 arch: architecture to measure code size on.
93 config: configuration type to measure code size with.
94 make_command: command to build library (Inferred from arch and config).
95 """
96 self.arch = arch
97 self.config = config
Yanray Wang21f17442023-06-01 11:29:06 +080098 self.sys_arch = sys_arch
Yanray Wang6a862582023-05-24 12:24:38 +080099 self.make_command = self.set_make_command()
100
101 def set_make_command(self) -> str:
102 """Infer build command based on architecture and configuration."""
103
Yanray Wang21f17442023-06-01 11:29:06 +0800104 if self.config == SupportedConfig.DEFAULT.value and \
105 self.arch == self.sys_arch:
Yanray Wang6a862582023-05-24 12:24:38 +0800106 return 'make -j lib CFLAGS=\'-Os \' '
Yanray Wangaba71582023-05-29 16:45:56 +0800107 elif self.arch == SupportedArch.ARMV8_M.value and \
Yanray Wang6a862582023-05-24 12:24:38 +0800108 self.config == SupportedConfig.TFM_MEDIUM.value:
109 return \
Yanray Wang60430bd2023-05-29 14:48:18 +0800110 'make -j lib CC=armclang \
Yanray Wang6a862582023-05-24 12:24:38 +0800111 CFLAGS=\'--target=arm-arm-none-eabi -mcpu=cortex-m33 -Os \
112 -DMBEDTLS_CONFIG_FILE=\\\"' + CONFIG_TFM_MEDIUM_MBEDCRYPTO_H + '\\\" \
113 -DMBEDTLS_PSA_CRYPTO_CONFIG_FILE=\\\"' + CONFIG_TFM_MEDIUM_PSA_CRYPTO_H + '\\\" \''
114 else:
Yanray Wang21f17442023-06-01 11:29:06 +0800115 print("Unsupported combination of architecture: {} and configuration: {}"
Yanray Wang6a862582023-05-24 12:24:38 +0800116 .format(self.arch, self.config))
Yanray Wangc18cd892023-05-31 11:08:04 +0800117 print("\nPlease use supported combination of architecture and configuration:")
118 for comb in CodeSizeInfo.SupportedArchConfig:
119 print(comb)
Yanray Wang21f17442023-06-01 11:29:06 +0800120 print("\nFor your system, please use:")
121 for comb in CodeSizeInfo.SupportedArchConfig:
122 if "default" in comb and self.sys_arch not in comb:
123 continue
124 print(comb)
Yanray Wang6a862582023-05-24 12:24:38 +0800125 sys.exit(1)
126
Yanray Wang16ebc572023-05-30 18:10:20 +0800127class SizeEntry: # pylint: disable=too-few-public-methods
128 """Data Structure to only store information of code size."""
129 def __init__(self, text, data, bss, dec):
130 self.text = text
131 self.data = data
132 self.bss = bss
133 self.total = dec # total <=> dec
Yanray Wang6a862582023-05-24 12:24:38 +0800134
Yanray Wang16ebc572023-05-30 18:10:20 +0800135class CodeSizeBase:
136 """Code Size Base Class for size record saving and writing."""
137
138 def __init__(self) -> None:
139 """ Variable code_size is used to store size info for any revisions.
140 code_size: (data format)
141 {revision: {module: {file_name: SizeEntry,
142 etc ...
143 },
144 etc ...
145 },
146 etc ...
147 }
148 """
149 self.code_size = {} #type: typing.Dict[str, typing.Dict]
150
151 def set_size_record(self, revision: str, mod: str, size_text: str) -> None:
152 """Store size information for target revision and high-level module.
153
154 size_text Format: text data bss dec hex filename
155 """
156 size_record = {}
157 for line in size_text.splitlines()[1:]:
158 data = line.split()
159 size_record[data[5]] = SizeEntry(data[0], data[1], data[2], data[3])
160 if revision in self.code_size:
161 self.code_size[revision].update({mod: size_record})
162 else:
163 self.code_size[revision] = {mod: size_record}
164
165 def read_size_record(self, revision: str, fname: str) -> None:
166 """Read size information from csv file and write it into code_size.
167
168 fname Format: filename text data bss dec
169 """
170 mod = ""
171 size_record = {}
172 with open(fname, 'r') as csv_file:
173 for line in csv_file:
174 data = line.strip().split()
175 # check if we find the beginning of a module
176 if data and data[0] in MBEDTLS_STATIC_LIB:
177 mod = data[0]
178 continue
179
180 if mod:
181 size_record[data[0]] = \
182 SizeEntry(data[1], data[2], data[3], data[4])
183
184 # check if we hit record for the end of a module
185 m = re.match(r'.?TOTALS', line)
186 if m:
187 if revision in self.code_size:
188 self.code_size[revision].update({mod: size_record})
189 else:
190 self.code_size[revision] = {mod: size_record}
191 mod = ""
192 size_record = {}
193
194 def _size_reader_helper(
195 self,
196 revision: str,
197 output: typing_util.Writable
198 ) -> typing.Iterator[tuple]:
199 """A helper function to peel code_size based on revision."""
200 for mod, file_size in self.code_size[revision].items():
201 output.write("\n" + mod + "\n")
202 for fname, size_entry in file_size.items():
203 yield mod, fname, size_entry
204
205 def write_size_record(
206 self,
207 revision: str,
208 output: typing_util.Writable
209 ) -> None:
210 """Write size information to a file.
211
212 Writing Format: file_name text data bss total(dec)
213 """
214 output.write("{:<30} {:>7} {:>7} {:>7} {:>7}\n"
215 .format("filename", "text", "data", "bss", "total"))
216 for _, fname, size_entry in self._size_reader_helper(revision, output):
217 output.write("{:<30} {:>7} {:>7} {:>7} {:>7}\n"
218 .format(fname, size_entry.text, size_entry.data,\
219 size_entry.bss, size_entry.total))
220
221 def write_comparison(
222 self,
223 old_rev: str,
224 new_rev: str,
225 output: typing_util.Writable
226 ) -> None:
227 """Write comparison result into a file.
228
229 Writing Format: file_name current(total) old(total) change(Byte) change_pct(%)
230 """
231 output.write("{:<30} {:>7} {:>7} {:>7} {:>7}\n"
232 .format("filename", "current", "old", "change", "change%"))
233 for mod, fname, size_entry in self._size_reader_helper(new_rev, output):
234 new_size = int(size_entry.total)
235 # check if we have the file in old revision
236 if fname in self.code_size[old_rev][mod]:
237 old_size = int(self.code_size[old_rev][mod][fname].total)
238 change = new_size - old_size
239 if old_size != 0:
240 change_pct = change / old_size
241 else:
242 change_pct = 0
243 output.write("{:<30} {:>7} {:>7} {:>7} {:>7.2%}\n"
244 .format(fname, new_size, old_size, change, change_pct))
245 else:
246 output.write("{} {}\n".format(fname, new_size))
247
248
249class CodeSizeComparison(CodeSizeBase):
Xiaofei Bai2400b502021-10-21 12:22:58 +0000250 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000251
Yanray Wang6a862582023-05-24 12:24:38 +0800252 def __init__(self, old_revision, new_revision, result_dir, code_size_info):
Xiaofei Baibca03e52021-09-09 09:42:37 +0000253 """
Yanray Wang6a862582023-05-24 12:24:38 +0800254 old_revision: revision to compare against.
Xiaofei Baibca03e52021-09-09 09:42:37 +0000255 new_revision:
Yanray Wang6a862582023-05-24 12:24:38 +0800256 result_dir: directory for comparison result.
257 code_size_info: an object containing information to build library.
Xiaofei Baibca03e52021-09-09 09:42:37 +0000258 """
259 self.repo_path = "."
260 self.result_dir = os.path.abspath(result_dir)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000261 os.makedirs(self.result_dir, exist_ok=True)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000262
263 self.csv_dir = os.path.abspath("code_size_records/")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000264 os.makedirs(self.csv_dir, exist_ok=True)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000265
266 self.old_rev = old_revision
267 self.new_rev = new_revision
268 self.git_command = "git"
Yanray Wang6a862582023-05-24 12:24:38 +0800269 self.make_command = code_size_info.make_command
Yanray Wang369cd962023-05-24 17:13:29 +0800270 self.fname_suffix = "-" + code_size_info.arch + "-" +\
271 code_size_info.config
Xiaofei Baibca03e52021-09-09 09:42:37 +0000272
273 @staticmethod
Xiaofei Bai2400b502021-10-21 12:22:58 +0000274 def validate_revision(revision):
Xiaofei Baiccd738b2021-11-03 07:12:31 +0000275 result = subprocess.check_output(["git", "rev-parse", "--verify",
276 revision + "^{commit}"], shell=False)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000277 return result
Xiaofei Bai2400b502021-10-21 12:22:58 +0000278
Xiaofei Baibca03e52021-09-09 09:42:37 +0000279 def _create_git_worktree(self, revision):
280 """Make a separate worktree for revision.
281 Do not modify the current worktree."""
282
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000283 if revision == "current":
Xiaofei Baibca03e52021-09-09 09:42:37 +0000284 print("Using current work directory.")
285 git_worktree_path = self.repo_path
286 else:
287 print("Creating git worktree for", revision)
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000288 git_worktree_path = os.path.join(self.repo_path, "temp-" + revision)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000289 subprocess.check_output(
290 [self.git_command, "worktree", "add", "--detach",
291 git_worktree_path, revision], cwd=self.repo_path,
292 stderr=subprocess.STDOUT
293 )
Aditya Deshpande41a0aad2023-04-13 16:32:21 +0100294
Xiaofei Baibca03e52021-09-09 09:42:37 +0000295 return git_worktree_path
296
297 def _build_libraries(self, git_worktree_path):
298 """Build libraries in the specified worktree."""
299
300 my_environment = os.environ.copy()
Aditya Deshpande41a0aad2023-04-13 16:32:21 +0100301 try:
302 subprocess.check_output(
303 self.make_command, env=my_environment, shell=True,
304 cwd=git_worktree_path, stderr=subprocess.STDOUT,
305 )
306 except subprocess.CalledProcessError as e:
307 self._handle_called_process_error(e, git_worktree_path)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000308
309 def _gen_code_size_csv(self, revision, git_worktree_path):
310 """Generate code size csv file."""
311
Yanray Wang369cd962023-05-24 17:13:29 +0800312 csv_fname = revision + self.fname_suffix + ".csv"
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000313 if revision == "current":
314 print("Measuring code size in current work directory.")
315 else:
316 print("Measuring code size for", revision)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000317 result = subprocess.check_output(
318 ["size library/*.o"], cwd=git_worktree_path, shell=True
319 )
320 size_text = result.decode()
321 csv_file = open(os.path.join(self.csv_dir, csv_fname), "w")
322 for line in size_text.splitlines()[1:]:
323 data = line.split()
324 csv_file.write("{}, {}\n".format(data[5], data[3]))
325
326 def _remove_worktree(self, git_worktree_path):
327 """Remove temporary worktree."""
328 if git_worktree_path != self.repo_path:
329 print("Removing temporary worktree", git_worktree_path)
330 subprocess.check_output(
331 [self.git_command, "worktree", "remove", "--force",
332 git_worktree_path], cwd=self.repo_path,
333 stderr=subprocess.STDOUT
334 )
335
336 def _get_code_size_for_rev(self, revision):
337 """Generate code size csv file for the specified git revision."""
338
339 # Check if the corresponding record exists
Yanray Wang369cd962023-05-24 17:13:29 +0800340 csv_fname = revision + self.fname_suffix + ".csv"
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000341 if (revision != "current") and \
Xiaofei Baibca03e52021-09-09 09:42:37 +0000342 os.path.exists(os.path.join(self.csv_dir, csv_fname)):
343 print("Code size csv file for", revision, "already exists.")
344 else:
345 git_worktree_path = self._create_git_worktree(revision)
346 self._build_libraries(git_worktree_path)
347 self._gen_code_size_csv(revision, git_worktree_path)
348 self._remove_worktree(git_worktree_path)
349
350 def compare_code_size(self):
351 """Generate results of the size changes between two revisions,
352 old and new. Measured code size results of these two revisions
Xiaofei Bai2400b502021-10-21 12:22:58 +0000353 must be available."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000354
Yanray Wang369cd962023-05-24 17:13:29 +0800355 old_file = open(os.path.join(self.csv_dir, self.old_rev +
356 self.fname_suffix + ".csv"), "r")
357 new_file = open(os.path.join(self.csv_dir, self.new_rev +
358 self.fname_suffix + ".csv"), "r")
359 res_file = open(os.path.join(self.result_dir, "compare-" +
360 self.old_rev + "-" + self.new_rev +
361 self.fname_suffix +
362 ".csv"), "w")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000363
Xiaofei Baibca03e52021-09-09 09:42:37 +0000364 res_file.write("file_name, this_size, old_size, change, change %\n")
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800365 print("Generating comparison results.")
Xiaofei Baibca03e52021-09-09 09:42:37 +0000366
367 old_ds = {}
Yanray Wanga3841ab2023-05-24 18:33:08 +0800368 for line in old_file.readlines():
Xiaofei Baibca03e52021-09-09 09:42:37 +0000369 cols = line.split(", ")
370 fname = cols[0]
371 size = int(cols[1])
372 if size != 0:
373 old_ds[fname] = size
374
375 new_ds = {}
Yanray Wanga3841ab2023-05-24 18:33:08 +0800376 for line in new_file.readlines():
Xiaofei Baibca03e52021-09-09 09:42:37 +0000377 cols = line.split(", ")
378 fname = cols[0]
379 size = int(cols[1])
380 new_ds[fname] = size
381
382 for fname in new_ds:
383 this_size = new_ds[fname]
384 if fname in old_ds:
385 old_size = old_ds[fname]
386 change = this_size - old_size
387 change_pct = change / old_size
388 res_file.write("{}, {}, {}, {}, {:.2%}\n".format(fname, \
389 this_size, old_size, change, float(change_pct)))
390 else:
391 res_file.write("{}, {}\n".format(fname, this_size))
Xiaofei Bai2400b502021-10-21 12:22:58 +0000392 return 0
Xiaofei Baibca03e52021-09-09 09:42:37 +0000393
394 def get_comparision_results(self):
395 """Compare size of library/*.o between self.old_rev and self.new_rev,
396 and generate the result file."""
Gilles Peskined9071e72022-09-18 21:17:09 +0200397 build_tree.check_repo_path()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000398 self._get_code_size_for_rev(self.old_rev)
399 self._get_code_size_for_rev(self.new_rev)
400 return self.compare_code_size()
401
Aditya Deshpande41a0aad2023-04-13 16:32:21 +0100402 def _handle_called_process_error(self, e: subprocess.CalledProcessError,
403 git_worktree_path):
404 """Handle a CalledProcessError and quit the program gracefully.
405 Remove any extra worktrees so that the script may be called again."""
406
407 # Tell the user what went wrong
408 print("The following command: {} failed and exited with code {}"
409 .format(e.cmd, e.returncode))
410 print("Process output:\n {}".format(str(e.output, "utf-8")))
411
412 # Quit gracefully by removing the existing worktree
413 self._remove_worktree(git_worktree_path)
414 sys.exit(-1)
415
Xiaofei Bai2400b502021-10-21 12:22:58 +0000416def main():
Yanray Wang502c54f2023-05-31 11:41:36 +0800417 parser = argparse.ArgumentParser(description=(__doc__))
418 group_required = parser.add_argument_group(
419 'required arguments',
420 'required arguments to parse for running ' + os.path.basename(__file__))
421 group_required.add_argument(
422 "-o", "--old-rev", type=str, required=True,
423 help="old revision for comparison.")
424
425 group_optional = parser.add_argument_group(
426 'optional arguments',
427 'optional arguments to parse for running ' + os.path.basename(__file__))
428 group_optional.add_argument(
Xiaofei Baibca03e52021-09-09 09:42:37 +0000429 "-r", "--result-dir", type=str, default="comparison",
430 help="directory where comparison result is stored, \
Yanray Wang502c54f2023-05-31 11:41:36 +0800431 default is comparison")
432 group_optional.add_argument(
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000433 "-n", "--new-rev", type=str, default=None,
434 help="new revision for comparison, default is the current work \
Yanray Wang502c54f2023-05-31 11:41:36 +0800435 directory, including uncommitted changes.")
436 group_optional.add_argument(
Yanray Wang23bd5322023-05-24 11:03:59 +0800437 "-a", "--arch", type=str, default=detect_arch(),
438 choices=list(map(lambda s: s.value, SupportedArch)),
439 help="specify architecture for code size comparison, default is the\
Yanray Wang502c54f2023-05-31 11:41:36 +0800440 host architecture.")
441 group_optional.add_argument(
Yanray Wang6a862582023-05-24 12:24:38 +0800442 "-c", "--config", type=str, default=SupportedConfig.DEFAULT.value,
443 choices=list(map(lambda s: s.value, SupportedConfig)),
444 help="specify configuration type for code size comparison,\
Yanray Wang502c54f2023-05-31 11:41:36 +0800445 default is the current MbedTLS configuration.")
Xiaofei Baibca03e52021-09-09 09:42:37 +0000446 comp_args = parser.parse_args()
447
448 if os.path.isfile(comp_args.result_dir):
449 print("Error: {} is not a directory".format(comp_args.result_dir))
450 parser.exit()
451
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000452 validate_res = CodeSizeComparison.validate_revision(comp_args.old_rev)
Xiaofei Baiccd738b2021-11-03 07:12:31 +0000453 old_revision = validate_res.decode().replace("\n", "")
Xiaofei Bai2400b502021-10-21 12:22:58 +0000454
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000455 if comp_args.new_rev is not None:
456 validate_res = CodeSizeComparison.validate_revision(comp_args.new_rev)
Xiaofei Baiccd738b2021-11-03 07:12:31 +0000457 new_revision = validate_res.decode().replace("\n", "")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000458 else:
459 new_revision = "current"
Xiaofei Bai2400b502021-10-21 12:22:58 +0000460
Yanray Wang21f17442023-06-01 11:29:06 +0800461 code_size_info = CodeSizeInfo(comp_args.arch, comp_args.config,
462 detect_arch())
Yanray Wangaba71582023-05-29 16:45:56 +0800463 print("Measure code size for architecture: {}, configuration: {}"
464 .format(code_size_info.arch, code_size_info.config))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000465 result_dir = comp_args.result_dir
Yanray Wang6a862582023-05-24 12:24:38 +0800466 size_compare = CodeSizeComparison(old_revision, new_revision, result_dir,
467 code_size_info)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000468 return_code = size_compare.get_comparision_results()
469 sys.exit(return_code)
470
471
472if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000473 main()