blob: 898aaf9f38334210075ce298c17140627a53c3ad [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)
Xiaofei Bai184e8b62021-10-26 09:23:42 +000044 os.makedirs(self.result_dir, exist_ok=True)
Xiaofei Baibca03e52021-09-09 09:42:37 +000045
46 self.csv_dir = os.path.abspath("code_size_records/")
Xiaofei Bai184e8b62021-10-26 09:23:42 +000047 os.makedirs(self.csv_dir, exist_ok=True)
Xiaofei Baibca03e52021-09-09 09:42:37 +000048
49 self.old_rev = old_revision
50 self.new_rev = new_revision
51 self.git_command = "git"
52 self.make_command = "make"
53
54 @staticmethod
55 def check_repo_path():
56 if not all(os.path.isdir(d) for d in ["include", "library", "tests"]):
57 raise Exception("Must be run from Mbed TLS root")
58
Xiaofei Bai2400b502021-10-21 12:22:58 +000059 @staticmethod
60 def validate_revision(revision):
Xiaofei Bai184e8b62021-10-26 09:23:42 +000061 result = subprocess.run(["git", "rev-parse", "--verify", revision],
62 check=False, stdout=subprocess.PIPE)
63 return result
Xiaofei Bai2400b502021-10-21 12:22:58 +000064
Xiaofei Baibca03e52021-09-09 09:42:37 +000065 def _create_git_worktree(self, revision):
66 """Make a separate worktree for revision.
67 Do not modify the current worktree."""
68
Xiaofei Bai184e8b62021-10-26 09:23:42 +000069 if revision == "current":
Xiaofei Baibca03e52021-09-09 09:42:37 +000070 print("Using current work directory.")
71 git_worktree_path = self.repo_path
72 else:
73 print("Creating git worktree for", revision)
Xiaofei Bai184e8b62021-10-26 09:23:42 +000074 git_worktree_path = os.path.join(self.repo_path, "temp-" + revision)
Xiaofei Baibca03e52021-09-09 09:42:37 +000075 subprocess.check_output(
76 [self.git_command, "worktree", "add", "--detach",
77 git_worktree_path, revision], cwd=self.repo_path,
78 stderr=subprocess.STDOUT
79 )
80 return git_worktree_path
81
82 def _build_libraries(self, git_worktree_path):
83 """Build libraries in the specified worktree."""
84
85 my_environment = os.environ.copy()
86 subprocess.check_output(
87 [self.make_command, "-j", "lib"], env=my_environment,
88 cwd=git_worktree_path, stderr=subprocess.STDOUT,
89 )
90
91 def _gen_code_size_csv(self, revision, git_worktree_path):
92 """Generate code size csv file."""
93
Xiaofei Bai184e8b62021-10-26 09:23:42 +000094 csv_fname = revision + ".csv"
95 if revision == "current":
96 print("Measuring code size in current work directory.")
97 else:
98 print("Measuring code size for", revision)
Xiaofei Baibca03e52021-09-09 09:42:37 +000099 result = subprocess.check_output(
100 ["size library/*.o"], cwd=git_worktree_path, shell=True
101 )
102 size_text = result.decode()
103 csv_file = open(os.path.join(self.csv_dir, csv_fname), "w")
104 for line in size_text.splitlines()[1:]:
105 data = line.split()
106 csv_file.write("{}, {}\n".format(data[5], data[3]))
107
108 def _remove_worktree(self, git_worktree_path):
109 """Remove temporary worktree."""
110 if git_worktree_path != self.repo_path:
111 print("Removing temporary worktree", git_worktree_path)
112 subprocess.check_output(
113 [self.git_command, "worktree", "remove", "--force",
114 git_worktree_path], cwd=self.repo_path,
115 stderr=subprocess.STDOUT
116 )
117
118 def _get_code_size_for_rev(self, revision):
119 """Generate code size csv file for the specified git revision."""
120
121 # Check if the corresponding record exists
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000122 csv_fname = revision + ".csv"
123 if (revision != "current") and \
Xiaofei Baibca03e52021-09-09 09:42:37 +0000124 os.path.exists(os.path.join(self.csv_dir, csv_fname)):
125 print("Code size csv file for", revision, "already exists.")
126 else:
127 git_worktree_path = self._create_git_worktree(revision)
128 self._build_libraries(git_worktree_path)
129 self._gen_code_size_csv(revision, git_worktree_path)
130 self._remove_worktree(git_worktree_path)
131
132 def compare_code_size(self):
133 """Generate results of the size changes between two revisions,
134 old and new. Measured code size results of these two revisions
Xiaofei Bai2400b502021-10-21 12:22:58 +0000135 must be available."""
Xiaofei Baibca03e52021-09-09 09:42:37 +0000136
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000137 old_file = open(os.path.join(self.csv_dir, self.old_rev + ".csv"), "r")
138 new_file = open(os.path.join(self.csv_dir, self.new_rev + ".csv"), "r")
139 res_file = open(os.path.join(self.result_dir, "compare-" + self.old_rev
140 + "-" + self.new_rev + ".csv"), "w")
141
Xiaofei Baibca03e52021-09-09 09:42:37 +0000142 res_file.write("file_name, this_size, old_size, change, change %\n")
Xiaofei Bai2400b502021-10-21 12:22:58 +0000143 print("Generating comparision results.")
Xiaofei Baibca03e52021-09-09 09:42:37 +0000144
145 old_ds = {}
146 for line in old_file.readlines()[1:]:
147 cols = line.split(", ")
148 fname = cols[0]
149 size = int(cols[1])
150 if size != 0:
151 old_ds[fname] = size
152
153 new_ds = {}
154 for line in new_file.readlines()[1:]:
155 cols = line.split(", ")
156 fname = cols[0]
157 size = int(cols[1])
158 new_ds[fname] = size
159
160 for fname in new_ds:
161 this_size = new_ds[fname]
162 if fname in old_ds:
163 old_size = old_ds[fname]
164 change = this_size - old_size
165 change_pct = change / old_size
166 res_file.write("{}, {}, {}, {}, {:.2%}\n".format(fname, \
167 this_size, old_size, change, float(change_pct)))
168 else:
169 res_file.write("{}, {}\n".format(fname, this_size))
Xiaofei Bai2400b502021-10-21 12:22:58 +0000170 return 0
Xiaofei Baibca03e52021-09-09 09:42:37 +0000171
172 def get_comparision_results(self):
173 """Compare size of library/*.o between self.old_rev and self.new_rev,
174 and generate the result file."""
175 self.check_repo_path()
176 self._get_code_size_for_rev(self.old_rev)
177 self._get_code_size_for_rev(self.new_rev)
178 return self.compare_code_size()
179
Xiaofei Bai2400b502021-10-21 12:22:58 +0000180def main():
Xiaofei Baibca03e52021-09-09 09:42:37 +0000181 parser = argparse.ArgumentParser(
182 description=(
183 """This script is for comparing the size of the library files
184 from two different Git revisions within an Mbed TLS repository.
185 The results of the comparison is formatted as csv, and stored at
186 a configurable location.
187 Note: must be run from Mbed TLS root."""
188 )
189 )
190 parser.add_argument(
191 "-r", "--result-dir", type=str, default="comparison",
192 help="directory where comparison result is stored, \
193 default is comparison",
194 )
195 parser.add_argument(
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000196 "-o", "--old-rev", type=str, help="old revision for comparison.",
Xiaofei Baibca03e52021-09-09 09:42:37 +0000197 required=True,
198 )
199 parser.add_argument(
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000200 "-n", "--new-rev", type=str, default=None,
201 help="new revision for comparison, default is the current work \
202 directory, including uncommited changes."
Xiaofei Baibca03e52021-09-09 09:42:37 +0000203 )
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 Bai184e8b62021-10-26 09:23:42 +0000210 validate_res = CodeSizeComparison.validate_revision(comp_args.old_rev)
211 if validate_res.returncode != 0:
212 sys.exit(validate_res.returncode)
213 old_revision = validate_res.stdout.decode().replace("\n", "")
Xiaofei Bai2400b502021-10-21 12:22:58 +0000214
Xiaofei Bai184e8b62021-10-26 09:23:42 +0000215 if comp_args.new_rev is not None:
216 validate_res = CodeSizeComparison.validate_revision(comp_args.new_rev)
217 if validate_res.returncode != 0:
218 sys.exit(validate_res.returncode)
219 new_revision = validate_res.stdout.decode().replace("\n", "")
220 else:
221 new_revision = "current"
Xiaofei Bai2400b502021-10-21 12:22:58 +0000222
Xiaofei Baibca03e52021-09-09 09:42:37 +0000223 result_dir = comp_args.result_dir
224 size_compare = CodeSizeComparison(old_revision, new_revision, result_dir)
225 return_code = size_compare.get_comparision_results()
226 sys.exit(return_code)
227
228
229if __name__ == "__main__":
Xiaofei Bai2400b502021-10-21 12:22:58 +0000230 main()