code_size_compare: add CodeSizeCalculator to calculate code size
CodeSizeCalculator is aimed to calculate code size based on a Git revision and code size measurement tool. The output of code size is in utf-8 encoding. Signed-off-by: Yanray Wang <yanray.wang@arm.com>
This commit is contained in:
parent
15c43f3407
commit
e0e276046b
1 changed files with 132 additions and 91 deletions
|
@ -126,6 +126,123 @@ class CodeSizeInfo: # pylint: disable=too-few-public-methods
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
class CodeSizeCalculator:
|
||||||
|
""" A calculator to calculate code size of library objects based on
|
||||||
|
Git revision and code size measurement tool.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
revision: str,
|
||||||
|
make_cmd: str,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
revision: Git revision.(E.g: commit)
|
||||||
|
make_cmd: command to build library objects.
|
||||||
|
"""
|
||||||
|
self.repo_path = "."
|
||||||
|
self.git_command = "git"
|
||||||
|
self.make_clean = 'make clean'
|
||||||
|
|
||||||
|
self.revision = revision
|
||||||
|
self.make_cmd = make_cmd
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate_revision(revision: str) -> bytes:
|
||||||
|
result = subprocess.check_output(["git", "rev-parse", "--verify",
|
||||||
|
revision + "^{commit}"], shell=False)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _create_git_worktree(self, revision: str) -> str:
|
||||||
|
"""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: str) -> None:
|
||||||
|
"""Build libraries in the specified worktree."""
|
||||||
|
|
||||||
|
my_environment = os.environ.copy()
|
||||||
|
try:
|
||||||
|
subprocess.check_output(
|
||||||
|
self.make_clean, env=my_environment, shell=True,
|
||||||
|
cwd=git_worktree_path, stderr=subprocess.STDOUT,
|
||||||
|
)
|
||||||
|
subprocess.check_output(
|
||||||
|
self.make_cmd, env=my_environment, shell=True,
|
||||||
|
cwd=git_worktree_path, stderr=subprocess.STDOUT,
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
self._handle_called_process_error(e, git_worktree_path)
|
||||||
|
|
||||||
|
def _gen_raw_code_size(self, revision, git_worktree_path):
|
||||||
|
"""Calculate code size with measurement tool in UTF-8 encoding."""
|
||||||
|
if revision == "current":
|
||||||
|
print("Measuring code size in current work directory")
|
||||||
|
else:
|
||||||
|
print("Measuring code size for", revision)
|
||||||
|
|
||||||
|
res = {}
|
||||||
|
for mod, st_lib in MBEDTLS_STATIC_LIB.items():
|
||||||
|
try:
|
||||||
|
result = subprocess.check_output(
|
||||||
|
["size", st_lib, "-t"], cwd=git_worktree_path,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
res[mod] = result
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
self._handle_called_process_error(e, git_worktree_path)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _remove_worktree(self, git_worktree_path: str) -> None:
|
||||||
|
"""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 _handle_called_process_error(self, e: subprocess.CalledProcessError,
|
||||||
|
git_worktree_path: str) -> None:
|
||||||
|
"""Handle a CalledProcessError and quit the program gracefully.
|
||||||
|
Remove any extra worktrees so that the script may be called again."""
|
||||||
|
|
||||||
|
# Tell the user what went wrong
|
||||||
|
print("The following command: {} failed and exited with code {}"
|
||||||
|
.format(e.cmd, e.returncode))
|
||||||
|
print("Process output:\n {}".format(str(e.output, "utf-8")))
|
||||||
|
|
||||||
|
# Quit gracefully by removing the existing worktree
|
||||||
|
self._remove_worktree(git_worktree_path)
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
def cal_libraries_code_size(self) -> typing.Dict:
|
||||||
|
"""Calculate code size of libraries by measurement tool."""
|
||||||
|
|
||||||
|
revision = self.revision
|
||||||
|
git_worktree_path = self._create_git_worktree(revision)
|
||||||
|
self._build_libraries(git_worktree_path)
|
||||||
|
res = self._gen_raw_code_size(revision, git_worktree_path)
|
||||||
|
self._remove_worktree(git_worktree_path)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
class CodeSizeGenerator:
|
class CodeSizeGenerator:
|
||||||
""" A generator based on size measurement tool for library objects.
|
""" A generator based on size measurement tool for library objects.
|
||||||
|
|
||||||
|
@ -328,7 +445,6 @@ class CodeSizeComparison:
|
||||||
result_dir: directory for comparison result.
|
result_dir: directory for comparison result.
|
||||||
code_size_info: an object containing information to build library.
|
code_size_info: an object containing information to build library.
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
|
||||||
self.repo_path = "."
|
self.repo_path = "."
|
||||||
self.result_dir = os.path.abspath(result_dir)
|
self.result_dir = os.path.abspath(result_dir)
|
||||||
os.makedirs(self.result_dir, exist_ok=True)
|
os.makedirs(self.result_dir, exist_ok=True)
|
||||||
|
@ -345,47 +461,7 @@ class CodeSizeComparison:
|
||||||
code_size_info.config
|
code_size_info.config
|
||||||
self.code_size_generator = CodeSizeGeneratorWithSize()
|
self.code_size_generator = CodeSizeGeneratorWithSize()
|
||||||
|
|
||||||
@staticmethod
|
def _gen_code_size_csv(self, revision: str) -> None:
|
||||||
def validate_revision(revision: str) -> bytes:
|
|
||||||
result = subprocess.check_output(["git", "rev-parse", "--verify",
|
|
||||||
revision + "^{commit}"], shell=False)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _create_git_worktree(self, revision: str) -> str:
|
|
||||||
"""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: str) -> None:
|
|
||||||
"""Build libraries in the specified worktree."""
|
|
||||||
|
|
||||||
my_environment = os.environ.copy()
|
|
||||||
try:
|
|
||||||
subprocess.check_output(
|
|
||||||
self.make_clean, env=my_environment, shell=True,
|
|
||||||
cwd=git_worktree_path, stderr=subprocess.STDOUT,
|
|
||||||
)
|
|
||||||
subprocess.check_output(
|
|
||||||
self.make_command, env=my_environment, shell=True,
|
|
||||||
cwd=git_worktree_path, stderr=subprocess.STDOUT,
|
|
||||||
)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
self._handle_called_process_error(e, git_worktree_path)
|
|
||||||
|
|
||||||
def _gen_code_size_csv(self, revision: str, git_worktree_path: str) -> None:
|
|
||||||
"""Generate code size csv file."""
|
"""Generate code size csv file."""
|
||||||
|
|
||||||
if revision == "current":
|
if revision == "current":
|
||||||
|
@ -393,31 +469,13 @@ class CodeSizeComparison:
|
||||||
else:
|
else:
|
||||||
print("Measuring code size for", revision)
|
print("Measuring code size for", revision)
|
||||||
|
|
||||||
for mod, st_lib in MBEDTLS_STATIC_LIB.items():
|
code_size_text = CodeSizeCalculator(revision, self.make_command).\
|
||||||
try:
|
cal_libraries_code_size()
|
||||||
result = subprocess.check_output(
|
|
||||||
["size", st_lib, "-t"], cwd=git_worktree_path
|
|
||||||
)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
self._handle_called_process_error(e, git_worktree_path)
|
|
||||||
size_text = result.decode("utf-8")
|
|
||||||
|
|
||||||
self.code_size_generator.set_size_record(revision, mod, size_text)
|
csv_file = os.path.join(self.csv_dir, revision +
|
||||||
|
self.fname_suffix + ".csv")
|
||||||
print("Generating code size csv for", revision)
|
self.code_size_generator.size_generator_write_record(revision,\
|
||||||
csv_file = open(os.path.join(self.csv_dir, revision +
|
code_size_text, csv_file)
|
||||||
self.fname_suffix + ".csv"), "w")
|
|
||||||
self.code_size_generator.write_size_record(revision, csv_file)
|
|
||||||
|
|
||||||
def _remove_worktree(self, git_worktree_path: str) -> None:
|
|
||||||
"""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: str) -> None:
|
def _get_code_size_for_rev(self, revision: str) -> None:
|
||||||
"""Generate code size csv file for the specified git revision."""
|
"""Generate code size csv file for the specified git revision."""
|
||||||
|
@ -430,24 +488,21 @@ class CodeSizeComparison:
|
||||||
self.code_size_generator.read_size_record(revision,\
|
self.code_size_generator.read_size_record(revision,\
|
||||||
os.path.join(self.csv_dir, csv_fname))
|
os.path.join(self.csv_dir, csv_fname))
|
||||||
else:
|
else:
|
||||||
git_worktree_path = self._create_git_worktree(revision)
|
self._gen_code_size_csv(revision)
|
||||||
self._build_libraries(git_worktree_path)
|
|
||||||
self._gen_code_size_csv(revision, git_worktree_path)
|
|
||||||
self._remove_worktree(git_worktree_path)
|
|
||||||
|
|
||||||
def _gen_code_size_comparison(self) -> int:
|
def _gen_code_size_comparison(self) -> int:
|
||||||
"""Generate results of the size changes between two revisions,
|
"""Generate results of the size changes between two revisions,
|
||||||
old and new. Measured code size results of these two revisions
|
old and new. Measured code size results of these two revisions
|
||||||
must be available."""
|
must be available."""
|
||||||
|
|
||||||
res_file = open(os.path.join(self.result_dir, "compare-" +
|
res_file = os.path.join(self.result_dir, "compare-" +
|
||||||
self.old_rev + "-" + self.new_rev +
|
self.old_rev + "-" + self.new_rev +
|
||||||
self.fname_suffix +
|
self.fname_suffix + ".csv")
|
||||||
".csv"), "w")
|
|
||||||
|
|
||||||
print("\nGenerating comparison results between",\
|
print("\nGenerating comparison results between",\
|
||||||
self.old_rev, "and", self.new_rev)
|
self.old_rev, "and", self.new_rev)
|
||||||
self.code_size_generator.write_comparison(self.old_rev, self.new_rev, res_file)
|
self.code_size_generator.size_generator_write_comparison(\
|
||||||
|
self.old_rev, self.new_rev, res_file)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -459,20 +514,6 @@ class CodeSizeComparison:
|
||||||
self._get_code_size_for_rev(self.new_rev)
|
self._get_code_size_for_rev(self.new_rev)
|
||||||
return self._gen_code_size_comparison()
|
return self._gen_code_size_comparison()
|
||||||
|
|
||||||
def _handle_called_process_error(self, e: subprocess.CalledProcessError,
|
|
||||||
git_worktree_path: str) -> None:
|
|
||||||
"""Handle a CalledProcessError and quit the program gracefully.
|
|
||||||
Remove any extra worktrees so that the script may be called again."""
|
|
||||||
|
|
||||||
# Tell the user what went wrong
|
|
||||||
print("The following command: {} failed and exited with code {}"
|
|
||||||
.format(e.cmd, e.returncode))
|
|
||||||
print("Process output:\n {}".format(str(e.output, "utf-8")))
|
|
||||||
|
|
||||||
# Quit gracefully by removing the existing worktree
|
|
||||||
self._remove_worktree(git_worktree_path)
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description=(__doc__))
|
parser = argparse.ArgumentParser(description=(__doc__))
|
||||||
group_required = parser.add_argument_group(
|
group_required = parser.add_argument_group(
|
||||||
|
@ -509,11 +550,11 @@ def main():
|
||||||
print("Error: {} is not a directory".format(comp_args.result_dir))
|
print("Error: {} is not a directory".format(comp_args.result_dir))
|
||||||
parser.exit()
|
parser.exit()
|
||||||
|
|
||||||
validate_res = CodeSizeComparison.validate_revision(comp_args.old_rev)
|
validate_res = CodeSizeCalculator.validate_revision(comp_args.old_rev)
|
||||||
old_revision = validate_res.decode().replace("\n", "")
|
old_revision = validate_res.decode().replace("\n", "")
|
||||||
|
|
||||||
if comp_args.new_rev is not None:
|
if comp_args.new_rev is not None:
|
||||||
validate_res = CodeSizeComparison.validate_revision(comp_args.new_rev)
|
validate_res = CodeSizeCalculator.validate_revision(comp_args.new_rev)
|
||||||
new_revision = validate_res.decode().replace("\n", "")
|
new_revision = validate_res.decode().replace("\n", "")
|
||||||
else:
|
else:
|
||||||
new_revision = "current"
|
new_revision = "current"
|
||||||
|
|
Loading…
Reference in a new issue