123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- #!/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
- 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 comparision 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 check_repo_path():
- if not all(os.path.isdir(d) for d in ["include", "library", "tests"]):
- raise Exception("Must be run from Mbed TLS root")
- @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 comparision 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."""
- self.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 uncommited 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()
|