aboutsummaryrefslogtreecommitdiff
path: root/scripts/code_size_compare.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/code_size_compare.py')
-rwxr-xr-xscripts/code_size_compare.py224
1 files changed, 224 insertions, 0 deletions
diff --git a/scripts/code_size_compare.py b/scripts/code_size_compare.py
new file mode 100755
index 000000000..af6ddd4fc
--- /dev/null
+++ b/scripts/code_size_compare.py
@@ -0,0 +1,224 @@
+#!/usr/bin/env python3
+
+"""
+Purpose
+
+This script is for comparing the size of the library files from two
+different Git revisions within an Mbed TLS repository.
+The results of the comparison is formatted as csv and stored at a
+configurable location.
+Note: must be run from Mbed TLS root.
+"""
+
+# Copyright The Mbed TLS Contributors
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import os
+import subprocess
+import sys
+
+from mbedtls_dev import build_tree
+
+
+class CodeSizeComparison:
+ """Compare code size between two Git revisions."""
+
+ def __init__(self, old_revision, new_revision, result_dir):
+ """
+ old_revision: revision to compare against
+ new_revision:
+ result_dir: directory for comparison result
+ """
+ self.repo_path = "."
+ self.result_dir = os.path.abspath(result_dir)
+ os.makedirs(self.result_dir, exist_ok=True)
+
+ self.csv_dir = os.path.abspath("code_size_records/")
+ os.makedirs(self.csv_dir, exist_ok=True)
+
+ self.old_rev = old_revision
+ self.new_rev = new_revision
+ self.git_command = "git"
+ self.make_command = "make"
+
+ @staticmethod
+ def validate_revision(revision):
+ result = subprocess.check_output(["git", "rev-parse", "--verify",
+ revision + "^{commit}"], shell=False)
+ return result
+
+ def _create_git_worktree(self, revision):
+ """Make a separate worktree for revision.
+ Do not modify the current worktree."""
+
+ if revision == "current":
+ print("Using current work directory.")
+ git_worktree_path = self.repo_path
+ else:
+ print("Creating git worktree for", revision)
+ git_worktree_path = os.path.join(self.repo_path, "temp-" + revision)
+ subprocess.check_output(
+ [self.git_command, "worktree", "add", "--detach",
+ git_worktree_path, revision], cwd=self.repo_path,
+ stderr=subprocess.STDOUT
+ )
+ return git_worktree_path
+
+ def _build_libraries(self, git_worktree_path):
+ """Build libraries in the specified worktree."""
+
+ my_environment = os.environ.copy()
+ subprocess.check_output(
+ [self.make_command, "-j", "lib"], env=my_environment,
+ cwd=git_worktree_path, stderr=subprocess.STDOUT,
+ )
+
+ def _gen_code_size_csv(self, revision, git_worktree_path):
+ """Generate code size csv file."""
+
+ csv_fname = revision + ".csv"
+ if revision == "current":
+ print("Measuring code size in current work directory.")
+ else:
+ print("Measuring code size for", revision)
+ result = subprocess.check_output(
+ ["size library/*.o"], cwd=git_worktree_path, shell=True
+ )
+ size_text = result.decode()
+ csv_file = open(os.path.join(self.csv_dir, csv_fname), "w")
+ for line in size_text.splitlines()[1:]:
+ data = line.split()
+ csv_file.write("{}, {}\n".format(data[5], data[3]))
+
+ def _remove_worktree(self, git_worktree_path):
+ """Remove temporary worktree."""
+ if git_worktree_path != self.repo_path:
+ print("Removing temporary worktree", git_worktree_path)
+ subprocess.check_output(
+ [self.git_command, "worktree", "remove", "--force",
+ git_worktree_path], cwd=self.repo_path,
+ stderr=subprocess.STDOUT
+ )
+
+ def _get_code_size_for_rev(self, revision):
+ """Generate code size csv file for the specified git revision."""
+
+ # Check if the corresponding record exists
+ csv_fname = revision + ".csv"
+ if (revision != "current") and \
+ os.path.exists(os.path.join(self.csv_dir, csv_fname)):
+ print("Code size csv file for", revision, "already exists.")
+ else:
+ git_worktree_path = self._create_git_worktree(revision)
+ self._build_libraries(git_worktree_path)
+ self._gen_code_size_csv(revision, git_worktree_path)
+ self._remove_worktree(git_worktree_path)
+
+ def compare_code_size(self):
+ """Generate results of the size changes between two revisions,
+ old and new. Measured code size results of these two revisions
+ must be available."""
+
+ old_file = open(os.path.join(self.csv_dir, self.old_rev + ".csv"), "r")
+ new_file = open(os.path.join(self.csv_dir, self.new_rev + ".csv"), "r")
+ res_file = open(os.path.join(self.result_dir, "compare-" + self.old_rev
+ + "-" + self.new_rev + ".csv"), "w")
+
+ res_file.write("file_name, this_size, old_size, change, change %\n")
+ print("Generating comparison results.")
+
+ old_ds = {}
+ for line in old_file.readlines()[1:]:
+ cols = line.split(", ")
+ fname = cols[0]
+ size = int(cols[1])
+ if size != 0:
+ old_ds[fname] = size
+
+ new_ds = {}
+ for line in new_file.readlines()[1:]:
+ cols = line.split(", ")
+ fname = cols[0]
+ size = int(cols[1])
+ new_ds[fname] = size
+
+ for fname in new_ds:
+ this_size = new_ds[fname]
+ if fname in old_ds:
+ old_size = old_ds[fname]
+ change = this_size - old_size
+ change_pct = change / old_size
+ res_file.write("{}, {}, {}, {}, {:.2%}\n".format(fname, \
+ this_size, old_size, change, float(change_pct)))
+ else:
+ res_file.write("{}, {}\n".format(fname, this_size))
+ return 0
+
+ def get_comparision_results(self):
+ """Compare size of library/*.o between self.old_rev and self.new_rev,
+ and generate the result file."""
+ build_tree.check_repo_path()
+ self._get_code_size_for_rev(self.old_rev)
+ self._get_code_size_for_rev(self.new_rev)
+ return self.compare_code_size()
+
+def main():
+ parser = argparse.ArgumentParser(
+ description=(
+ """This script is for comparing the size of the library files
+ from two different Git revisions within an Mbed TLS repository.
+ The results of the comparison is formatted as csv, and stored at
+ a configurable location.
+ Note: must be run from Mbed TLS root."""
+ )
+ )
+ parser.add_argument(
+ "-r", "--result-dir", type=str, default="comparison",
+ help="directory where comparison result is stored, \
+ default is comparison",
+ )
+ parser.add_argument(
+ "-o", "--old-rev", type=str, help="old revision for comparison.",
+ required=True,
+ )
+ parser.add_argument(
+ "-n", "--new-rev", type=str, default=None,
+ help="new revision for comparison, default is the current work \
+ directory, including uncommitted changes."
+ )
+ comp_args = parser.parse_args()
+
+ if os.path.isfile(comp_args.result_dir):
+ print("Error: {} is not a directory".format(comp_args.result_dir))
+ parser.exit()
+
+ validate_res = CodeSizeComparison.validate_revision(comp_args.old_rev)
+ old_revision = validate_res.decode().replace("\n", "")
+
+ if comp_args.new_rev is not None:
+ validate_res = CodeSizeComparison.validate_revision(comp_args.new_rev)
+ new_revision = validate_res.decode().replace("\n", "")
+ else:
+ new_revision = "current"
+
+ result_dir = comp_args.result_dir
+ size_compare = CodeSizeComparison(old_revision, new_revision, result_dir)
+ return_code = size_compare.get_comparision_results()
+ sys.exit(return_code)
+
+
+if __name__ == "__main__":
+ main()