blob: fbc0dc171bba770a1517d89f8df60477b08ac106 [file] [log] [blame]
Xiaofei Baibca03e52021-09-09 09:42:37 +00001#!/usr/bin/env python3
2
3"""
4Purpose
5
6This script is for comparing the size of the library files from two
7different Git revisions within an Mbed TLS repository.
8The results of the comparison is formatted as csv and stored at a
9configurable location.
10Note: must be run from Mbed TLS root.
11"""
12
13# Copyright The Mbed TLS Contributors
14# SPDX-License-Identifier: Apache-2.0
15#
16# Licensed under the Apache License, Version 2.0 (the "License"); you may
17# not use this file except in compliance with the License.
18# You may obtain a copy of the License at
19#
20# http://www.apache.org/licenses/LICENSE-2.0
21#
22# Unless required by applicable law or agreed to in writing, software
23# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
24# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25# See the License for the specific language governing permissions and
26# limitations under the License.
27
28import argparse
29import os
30import subprocess
31import sys
Yanray Wang23bd5322023-05-24 11:03:59 +080032from enum import Enum
Xiaofei Baibca03e52021-09-09 09:42:37 +000033
Gilles Peskined9071e72022-09-18 21:17:09 +020034from mbedtls_dev import build_tree
35
Yanray Wang23bd5322023-05-24 11:03:59 +080036class SupportedArch(Enum):
37 """Supported architecture for code size measurement."""
38 AARCH64 = 'aarch64'
39 AARCH32 = 'aarch32'
40 X86_64 = 'x86_64'
41 X86 = 'x86'
42
43DETECT_ARCH_CMD = "cc -dM -E - < /dev/null"
44def detect_arch() -> str:
45 """Auto-detect host architecture."""
46 cc_output = subprocess.check_output(DETECT_ARCH_CMD, shell=True).decode()
47 if "__aarch64__" in cc_output:
48 return SupportedArch.AARCH64.value
49 if "__arm__" in cc_output:
50 return SupportedArch.AARCH32.value
51 if "__x86_64__" in cc_output:
52 return SupportedArch.X86_64.value
53 if "__x86__" in cc_output:
54 return SupportedArch.X86.value
55 else:
56 print("Unknown host architecture, cannot auto-detect arch.")
57 sys.exit(1)
Gilles Peskined9071e72022-09-18 21:17:09 +020058
Xiaofei Baibca03e52021-09-09 09:42:37 +000059class CodeSizeComparison:
Xiaofei Bai2400b502021-10-21 12:22:58 +000060 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +000061
62 def __init__(self, old_revision, new_revision, result_dir):
63 """
64 old_revision: revision to compare against
65 new_revision:
Shaun Case8b0ecbc2021-12-20 21:14:10 -080066 result_dir: directory for comparison result
Xiaofei Baibca03e52021-09-09 09:42:37 +000067 """
68 self.repo_path = "."
69 self.result_dir = os.path.abspath(result_dir)
Xiaofei Bai184e8b62021-10-26 09:23:42 +000070 os.makedirs(self.result_dir, exist_ok=True)
Xiaofei Baibca03e52021-09-09 09:42:37 +000071
72 self.csv_dir = os.path.abspath("code_size_records/")
Xiaofei Bai184e8b62021-10-26 09:23:42 +000073 os.makedirs(self.csv_dir, exist_ok=True)
Xiaofei Baibca03e52021-09-09 09:42:37 +000074
75 self.old_rev = old_revision
76 self.new_rev = new_revision
77 self.git_command = "git"
78 self.make_command = "make"
79
80 @staticmethod
Xiaofei Bai2400b502021-10-21 12:22:58 +000081 def validate_revision(revision):
Xiaofei Baiccd738b2021-11-03 07:12:31 +000082 result = subprocess.check_output(["git", "rev-parse", "--verify",
83 revision + "^{commit}"], shell=False)
Xiaofei Bai184e8b62021-10-26 09:23:42 +000084 return result
Xiaofei Bai2400b502021-10-21 12:22:58 +000085
Xiaofei Baibca03e52021-09-09 09:42:37 +000086 def _create_git_worktree(self, revision):
87 """Make a separate worktree for revision.
88 Do not modify the current worktree."""
89
Xiaofei Bai184e8b62021-10-26 09:23:42 +000090 if revision == "current":
Xiaofei Baibca03e52021-09-09 09:42:37 +000091 print("Using current work directory.")
92 git_worktree_path = self.repo_path
93 else:
94 print("Creating git worktree for", revision)
Xiaofei Bai184e8b62021-10-26 09:23:42 +000095 git_worktree_path = os.path.join(self.repo_path, "temp-" + revision)
Xiaofei Baibca03e52021-09-09 09:42:37 +000096 subprocess.check_output(
97 [self.git_command, "worktree", "add", "--detach",
98 git_worktree_path, revision], cwd=self.repo_path,
99 stderr=subprocess.STDOUT
100 )
101 return git_worktree_path
102
103 def _build_libraries(self, git_worktree_path):
104 """Build libraries in the specified worktree."""
105
106 my_environment = os.environ.copy()
107 subprocess.check_output(
108 [self.make_command, "-j", "lib"], env=my_environment,
109 cwd=git_worktree_path, stderr=subprocess.STDOUT,
110 )
111
112 def _gen_code_size_csv(self, revision, git_worktree_path):
113 """Generate code size csv file."""
114
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000115 csv_fname = revision + ".csv"
116 if revision == "current":
117 print("Measuring code size in current work directory.")
118 else:
119 print("Measuring code size for", revision)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000120 result = subprocess.check_output(
121 ["size library/*.o"], cwd=git_worktree_path, shell=True
122 )
123 size_text = result.decode()
124 csv_file = open(os.path.join(self.csv_dir, csv_fname), "w")
125 for line in size_text.splitlines()[1:]:
126 data = line.split()
127 csv_file.write("{}, {}\n".format(data[5], data[3]))
128
129 def _remove_worktree(self, git_worktree_path):
130 """Remove temporary worktree."""
131 if git_worktree_path != self.repo_path:
132 print("Removing temporary worktree", git_worktree_path)
133 subprocess.check_output(
134 [self.git_command, "worktree", "remove", "--force",
135 git_worktree_path], cwd=self.repo_path,
136 stderr=subprocess.STDOUT
137 )
138
139 def _get_code_size_for_rev(self, revision):
140 """Generate code size csv file for the specified git revision."""
141
142 # Check if the corresponding record exists
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000143 csv_fname = revision + ".csv"
144 if (revision != "current") and \
Xiaofei Baibca03e52021-09-09 09:42:37 +0000145 os.path.exists(os.path.join(self.csv_dir, csv_fname)):
146 print("Code size csv file for", revision, "already exists.")
147 else:
148 git_worktree_path = self._create_git_worktree(revision)
149 self._build_libraries(git_worktree_path)
150 self._gen_code_size_csv(revision, git_worktree_path)
151 self._remove_worktree(git_worktree_path)
152
153 def compare_code_size(self):
154 """Generate results of the size changes between two revisions,
155 old and new. Measured code size results of these two revisions
Xiaofei Bai2400b502021-10-21 12:22:58 +0000156 must be available."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000157
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000158 old_file = open(os.path.join(self.csv_dir, self.old_rev + ".csv"), "r")
159 new_file = open(os.path.join(self.csv_dir, self.new_rev + ".csv"), "r")
160 res_file = open(os.path.join(self.result_dir, "compare-" + self.old_rev
161 + "-" + self.new_rev + ".csv"), "w")
162
Xiaofei Baibca03e52021-09-09 09:42:37 +0000163 res_file.write("file_name, this_size, old_size, change, change %\n")
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800164 print("Generating comparison results.")
Xiaofei Baibca03e52021-09-09 09:42:37 +0000165
166 old_ds = {}
167 for line in old_file.readlines()[1:]:
168 cols = line.split(", ")
169 fname = cols[0]
170 size = int(cols[1])
171 if size != 0:
172 old_ds[fname] = size
173
174 new_ds = {}
175 for line in new_file.readlines()[1:]:
176 cols = line.split(", ")
177 fname = cols[0]
178 size = int(cols[1])
179 new_ds[fname] = size
180
181 for fname in new_ds:
182 this_size = new_ds[fname]
183 if fname in old_ds:
184 old_size = old_ds[fname]
185 change = this_size - old_size
186 change_pct = change / old_size
187 res_file.write("{}, {}, {}, {}, {:.2%}\n".format(fname, \
188 this_size, old_size, change, float(change_pct)))
189 else:
190 res_file.write("{}, {}\n".format(fname, this_size))
Xiaofei Bai2400b502021-10-21 12:22:58 +0000191 return 0
Xiaofei Baibca03e52021-09-09 09:42:37 +0000192
193 def get_comparision_results(self):
194 """Compare size of library/*.o between self.old_rev and self.new_rev,
195 and generate the result file."""
Gilles Peskined9071e72022-09-18 21:17:09 +0200196 build_tree.check_repo_path()
Xiaofei Baibca03e52021-09-09 09:42:37 +0000197 self._get_code_size_for_rev(self.old_rev)
198 self._get_code_size_for_rev(self.new_rev)
199 return self.compare_code_size()
200
Xiaofei Bai2400b502021-10-21 12:22:58 +0000201def main():
Xiaofei Baibca03e52021-09-09 09:42:37 +0000202 parser = argparse.ArgumentParser(
203 description=(
204 """This script is for comparing the size of the library files
205 from two different Git revisions within an Mbed TLS repository.
206 The results of the comparison is formatted as csv, and stored at
207 a configurable location.
208 Note: must be run from Mbed TLS root."""
209 )
210 )
211 parser.add_argument(
212 "-r", "--result-dir", type=str, default="comparison",
213 help="directory where comparison result is stored, \
214 default is comparison",
215 )
216 parser.add_argument(
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000217 "-o", "--old-rev", type=str, help="old revision for comparison.",
Xiaofei Baibca03e52021-09-09 09:42:37 +0000218 required=True,
219 )
220 parser.add_argument(
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000221 "-n", "--new-rev", type=str, default=None,
222 help="new revision for comparison, default is the current work \
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800223 directory, including uncommitted changes."
Xiaofei Baibca03e52021-09-09 09:42:37 +0000224 )
Yanray Wang23bd5322023-05-24 11:03:59 +0800225 parser.add_argument(
226 "-a", "--arch", type=str, default=detect_arch(),
227 choices=list(map(lambda s: s.value, SupportedArch)),
228 help="specify architecture for code size comparison, default is the\
229 host architecture."
230 )
Xiaofei Baibca03e52021-09-09 09:42:37 +0000231 comp_args = parser.parse_args()
232
233 if os.path.isfile(comp_args.result_dir):
234 print("Error: {} is not a directory".format(comp_args.result_dir))
235 parser.exit()
236
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000237 validate_res = CodeSizeComparison.validate_revision(comp_args.old_rev)
Xiaofei Baiccd738b2021-11-03 07:12:31 +0000238 old_revision = validate_res.decode().replace("\n", "")
Xiaofei Bai2400b502021-10-21 12:22:58 +0000239
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000240 if comp_args.new_rev is not None:
241 validate_res = CodeSizeComparison.validate_revision(comp_args.new_rev)
Xiaofei Baiccd738b2021-11-03 07:12:31 +0000242 new_revision = validate_res.decode().replace("\n", "")
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000243 else:
244 new_revision = "current"
Xiaofei Bai2400b502021-10-21 12:22:58 +0000245
Yanray Wang23bd5322023-05-24 11:03:59 +0800246 print("Measure code size for architecture: {}".format(comp_args.arch))
Xiaofei Baibca03e52021-09-09 09:42:37 +0000247 result_dir = comp_args.result_dir
248 size_compare = CodeSizeComparison(old_revision, new_revision, result_dir)
249 return_code = size_compare.get_comparision_results()
250 sys.exit(return_code)
251
252
253if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000254 main()