blob: 19a6c43d012c9bdc8ac6ddb497e370772bee2d61 [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:
34 """compare code size between two Git revisions"""
35
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
61 def _create_git_worktree(self, revision):
62 """Make a separate worktree for revision.
63 Do not modify the current worktree."""
64
65 if revision == "head":
66 print("Using current work directory.")
67 git_worktree_path = self.repo_path
68 else:
69 print("Creating git worktree for", revision)
70 git_worktree_path = os.path.join(self.repo_path, "temp-" + revision)
71 subprocess.check_output(
72 [self.git_command, "worktree", "add", "--detach",
73 git_worktree_path, revision], cwd=self.repo_path,
74 stderr=subprocess.STDOUT
75 )
76 return git_worktree_path
77
78 def _build_libraries(self, git_worktree_path):
79 """Build libraries in the specified worktree."""
80
81 my_environment = os.environ.copy()
82 subprocess.check_output(
83 [self.make_command, "-j", "lib"], env=my_environment,
84 cwd=git_worktree_path, stderr=subprocess.STDOUT,
85 )
86
87 def _gen_code_size_csv(self, revision, git_worktree_path):
88 """Generate code size csv file."""
89
90 csv_fname = revision + ".csv"
91 print("Measuring code size for", revision)
92 result = subprocess.check_output(
93 ["size library/*.o"], cwd=git_worktree_path, shell=True
94 )
95 size_text = result.decode()
96 csv_file = open(os.path.join(self.csv_dir, csv_fname), "w")
97 for line in size_text.splitlines()[1:]:
98 data = line.split()
99 csv_file.write("{}, {}\n".format(data[5], data[3]))
100
101 def _remove_worktree(self, git_worktree_path):
102 """Remove temporary worktree."""
103 if git_worktree_path != self.repo_path:
104 print("Removing temporary worktree", git_worktree_path)
105 subprocess.check_output(
106 [self.git_command, "worktree", "remove", "--force",
107 git_worktree_path], cwd=self.repo_path,
108 stderr=subprocess.STDOUT
109 )
110
111 def _get_code_size_for_rev(self, revision):
112 """Generate code size csv file for the specified git revision."""
113
114 # Check if the corresponding record exists
115 csv_fname = revision + ".csv"
116 if (revision != "head") and \
117 os.path.exists(os.path.join(self.csv_dir, csv_fname)):
118 print("Code size csv file for", revision, "already exists.")
119 else:
120 git_worktree_path = self._create_git_worktree(revision)
121 self._build_libraries(git_worktree_path)
122 self._gen_code_size_csv(revision, git_worktree_path)
123 self._remove_worktree(git_worktree_path)
124
125 def compare_code_size(self):
126 """Generate results of the size changes between two revisions,
127 old and new. Measured code size results of these two revisions
128 must be available"""
129
130 old_file = open(os.path.join(self.csv_dir, self.old_rev + ".csv"), "r")
131 new_file = open(os.path.join(self.csv_dir, self.new_rev + ".csv"), "r")
132 res_file = open(os.path.join(self.result_dir, "compare-" + self.old_rev
133 + "-" + self.new_rev + ".csv"), "w")
134 res_file.write("file_name, this_size, old_size, change, change %\n")
135 print("Generate comparision results.")
136
137 old_ds = {}
138 for line in old_file.readlines()[1:]:
139 cols = line.split(", ")
140 fname = cols[0]
141 size = int(cols[1])
142 if size != 0:
143 old_ds[fname] = size
144
145 new_ds = {}
146 for line in new_file.readlines()[1:]:
147 cols = line.split(", ")
148 fname = cols[0]
149 size = int(cols[1])
150 new_ds[fname] = size
151
152 for fname in new_ds:
153 this_size = new_ds[fname]
154 if fname in old_ds:
155 old_size = old_ds[fname]
156 change = this_size - old_size
157 change_pct = change / old_size
158 res_file.write("{}, {}, {}, {}, {:.2%}\n".format(fname, \
159 this_size, old_size, change, float(change_pct)))
160 else:
161 res_file.write("{}, {}\n".format(fname, this_size))
162 return 1
163
164 def get_comparision_results(self):
165 """Compare size of library/*.o between self.old_rev and self.new_rev,
166 and generate the result file."""
167 self.check_repo_path()
168 self._get_code_size_for_rev(self.old_rev)
169 self._get_code_size_for_rev(self.new_rev)
170 return self.compare_code_size()
171
172def run_main():
173 parser = argparse.ArgumentParser(
174 description=(
175 """This script is for comparing the size of the library files
176 from two different Git revisions within an Mbed TLS repository.
177 The results of the comparison is formatted as csv, and stored at
178 a configurable location.
179 Note: must be run from Mbed TLS root."""
180 )
181 )
182 parser.add_argument(
183 "-r", "--result-dir", type=str, default="comparison",
184 help="directory where comparison result is stored, \
185 default is comparison",
186 )
187 parser.add_argument(
188 "-o", "--old-rev", type=str, help="old revision for comparison",
189 required=True,
190 )
191 parser.add_argument(
192 "-n", "--new-rev", type=str, default="head",
193 help="new revision for comparison, default is current work directory."
194 )
195 comp_args = parser.parse_args()
196
197 if os.path.isfile(comp_args.result_dir):
198 print("Error: {} is not a directory".format(comp_args.result_dir))
199 parser.exit()
200
201 old_revision = comp_args.old_rev
202 new_revision = comp_args.new_rev
203 result_dir = comp_args.result_dir
204 size_compare = CodeSizeComparison(old_revision, new_revision, result_dir)
205 return_code = size_compare.get_comparision_results()
206 sys.exit(return_code)
207
208
209if __name__ == "__main__":
210 run_main()