blob: 96ebf3d54b6908cf6fa5891d3f420585ac42f47f [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
32
33class CodeSizeComparison:
Xiaofei Bai2400b502021-10-21 12:22:58 +000034 """Compare code size between two Git revisions."""
Xiaofei Baibca03e52021-09-09 09:42:37 +000035
36 def __init__(self, old_revision, new_revision, result_dir):
37 """
38 old_revision: revision to compare against
39 new_revision:
40 result_dir: directory for comparision result
41 """
42 self.repo_path = "."
43 self.result_dir = os.path.abspath(result_dir)
44 if os.path.exists(self.result_dir) is False:
45 os.makedirs(self.result_dir)
46
47 self.csv_dir = os.path.abspath("code_size_records/")
48 if os.path.exists(self.csv_dir) is False:
49 os.makedirs(self.csv_dir)
50
51 self.old_rev = old_revision
52 self.new_rev = new_revision
53 self.git_command = "git"
54 self.make_command = "make"
55
56 @staticmethod
57 def check_repo_path():
58 if not all(os.path.isdir(d) for d in ["include", "library", "tests"]):
59 raise Exception("Must be run from Mbed TLS root")
60
Xiaofei Bai2400b502021-10-21 12:22:58 +000061 @staticmethod
62 def validate_revision(revision):
63 result = subprocess.run(["git", "cat-file", "-e", revision], check=False)
64 return result.returncode
65
Xiaofei Baibca03e52021-09-09 09:42:37 +000066 def _create_git_worktree(self, revision):
67 """Make a separate worktree for revision.
68 Do not modify the current worktree."""
69
Xiaofei Bai2400b502021-10-21 12:22:58 +000070 if revision == "HEAD":
Xiaofei Baibca03e52021-09-09 09:42:37 +000071 print("Using current work directory.")
72 git_worktree_path = self.repo_path
73 else:
74 print("Creating git worktree for", revision)
Xiaofei Bai2400b502021-10-21 12:22:58 +000075 rev_dirname = revision.replace("/", "_")
76 git_worktree_path = os.path.join(self.repo_path, "temp-" + rev_dirname)
Xiaofei Baibca03e52021-09-09 09:42:37 +000077 subprocess.check_output(
78 [self.git_command, "worktree", "add", "--detach",
79 git_worktree_path, revision], cwd=self.repo_path,
80 stderr=subprocess.STDOUT
81 )
82 return git_worktree_path
83
84 def _build_libraries(self, git_worktree_path):
85 """Build libraries in the specified worktree."""
86
87 my_environment = os.environ.copy()
88 subprocess.check_output(
89 [self.make_command, "-j", "lib"], env=my_environment,
90 cwd=git_worktree_path, stderr=subprocess.STDOUT,
91 )
92
93 def _gen_code_size_csv(self, revision, git_worktree_path):
94 """Generate code size csv file."""
95
Xiaofei Bai2400b502021-10-21 12:22:58 +000096 csv_fname = revision.replace("/", "_") + ".csv"
Xiaofei Baibca03e52021-09-09 09:42:37 +000097 print("Measuring code size for", revision)
98 result = subprocess.check_output(
99 ["size library/*.o"], cwd=git_worktree_path, shell=True
100 )
101 size_text = result.decode()
102 csv_file = open(os.path.join(self.csv_dir, csv_fname), "w")
103 for line in size_text.splitlines()[1:]:
104 data = line.split()
105 csv_file.write("{}, {}\n".format(data[5], data[3]))
106
107 def _remove_worktree(self, git_worktree_path):
108 """Remove temporary worktree."""
109 if git_worktree_path != self.repo_path:
110 print("Removing temporary worktree", git_worktree_path)
111 subprocess.check_output(
112 [self.git_command, "worktree", "remove", "--force",
113 git_worktree_path], cwd=self.repo_path,
114 stderr=subprocess.STDOUT
115 )
116
117 def _get_code_size_for_rev(self, revision):
118 """Generate code size csv file for the specified git revision."""
119
120 # Check if the corresponding record exists
Xiaofei Bai2400b502021-10-21 12:22:58 +0000121 csv_fname = revision.replace("/", "_") + ".csv"
122 if (revision != "HEAD") and \
Xiaofei Baibca03e52021-09-09 09:42:37 +0000123 os.path.exists(os.path.join(self.csv_dir, csv_fname)):
124 print("Code size csv file for", revision, "already exists.")
125 else:
126 git_worktree_path = self._create_git_worktree(revision)
127 self._build_libraries(git_worktree_path)
128 self._gen_code_size_csv(revision, git_worktree_path)
129 self._remove_worktree(git_worktree_path)
130
131 def compare_code_size(self):
132 """Generate results of the size changes between two revisions,
133 old and new. Measured code size results of these two revisions
Xiaofei Bai2400b502021-10-21 12:22:58 +0000134 must be available."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000135
Xiaofei Bai2400b502021-10-21 12:22:58 +0000136 old_file = open(os.path.join(self.csv_dir, \
137 self.old_rev.replace("/", "_") + ".csv"), "r")
138 new_file = open(os.path.join(self.csv_dir, \
139 self.new_rev.replace("/", "_") + ".csv"), "r")
140 res_file = open(os.path.join(self.result_dir, \
141 "compare-" + self.old_rev.replace("/", "_") + "-" \
142 + self.new_rev.replace("/", "_") + ".csv"), "w")
Xiaofei Baibca03e52021-09-09 09:42:37 +0000143 res_file.write("file_name, this_size, old_size, change, change %\n")
Xiaofei Bai2400b502021-10-21 12:22:58 +0000144 print("Generating comparision results.")
Xiaofei Baibca03e52021-09-09 09:42:37 +0000145
146 old_ds = {}
147 for line in old_file.readlines()[1:]:
148 cols = line.split(", ")
149 fname = cols[0]
150 size = int(cols[1])
151 if size != 0:
152 old_ds[fname] = size
153
154 new_ds = {}
155 for line in new_file.readlines()[1:]:
156 cols = line.split(", ")
157 fname = cols[0]
158 size = int(cols[1])
159 new_ds[fname] = size
160
161 for fname in new_ds:
162 this_size = new_ds[fname]
163 if fname in old_ds:
164 old_size = old_ds[fname]
165 change = this_size - old_size
166 change_pct = change / old_size
167 res_file.write("{}, {}, {}, {}, {:.2%}\n".format(fname, \
168 this_size, old_size, change, float(change_pct)))
169 else:
170 res_file.write("{}, {}\n".format(fname, this_size))
Xiaofei Bai2400b502021-10-21 12:22:58 +0000171 return 0
Xiaofei Baibca03e52021-09-09 09:42:37 +0000172
173 def get_comparision_results(self):
174 """Compare size of library/*.o between self.old_rev and self.new_rev,
175 and generate the result file."""
176 self.check_repo_path()
177 self._get_code_size_for_rev(self.old_rev)
178 self._get_code_size_for_rev(self.new_rev)
179 return self.compare_code_size()
180
Xiaofei Bai2400b502021-10-21 12:22:58 +0000181def main():
Xiaofei Baibca03e52021-09-09 09:42:37 +0000182 parser = argparse.ArgumentParser(
183 description=(
184 """This script is for comparing the size of the library files
185 from two different Git revisions within an Mbed TLS repository.
186 The results of the comparison is formatted as csv, and stored at
187 a configurable location.
188 Note: must be run from Mbed TLS root."""
189 )
190 )
191 parser.add_argument(
192 "-r", "--result-dir", type=str, default="comparison",
193 help="directory where comparison result is stored, \
194 default is comparison",
195 )
196 parser.add_argument(
Xiaofei Bai2400b502021-10-21 12:22:58 +0000197 "-o", "--old-rev", type=str, help="old revision for comparison.(prefer commit ID)",
Xiaofei Baibca03e52021-09-09 09:42:37 +0000198 required=True,
199 )
200 parser.add_argument(
Xiaofei Bai2400b502021-10-21 12:22:58 +0000201 "-n", "--new-rev", type=str, default="HEAD",
Xiaofei Baibca03e52021-09-09 09:42:37 +0000202 help="new revision for comparison, default is current work directory."
203 )
204 comp_args = parser.parse_args()
205
206 if os.path.isfile(comp_args.result_dir):
207 print("Error: {} is not a directory".format(comp_args.result_dir))
208 parser.exit()
209
Xiaofei Bai2400b502021-10-21 12:22:58 +0000210 validate_result = CodeSizeComparison.validate_revision(comp_args.old_rev)
211 if validate_result != 0:
212 sys.exit(validate_result)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000213 old_revision = comp_args.old_rev
Xiaofei Bai2400b502021-10-21 12:22:58 +0000214
215 validate_result = CodeSizeComparison.validate_revision(comp_args.new_rev)
216 if validate_result != 0:
217 sys.exit(validate_result)
Xiaofei Baibca03e52021-09-09 09:42:37 +0000218 new_revision = comp_args.new_rev
Xiaofei Bai2400b502021-10-21 12:22:58 +0000219
Xiaofei Baibca03e52021-09-09 09:42:37 +0000220 result_dir = comp_args.result_dir
221 size_compare = CodeSizeComparison(old_revision, new_revision, result_dir)
222 return_code = size_compare.get_comparision_results()
223 sys.exit(return_code)
224
225
226if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000227 main()