blob: b9b9aca2392c25ad05ca026f59fd745a8af31ed1 [file] [log] [blame]
#!/usr/bin/env python
# cmpcodesize/main.py - Command-line entry point for cmpcodesize -*- python -*-
#
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See https://swift.org/LICENSE.txt for license information
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
from __future__ import print_function
import argparse
import collections
import csv
import glob
import os
import sys
from cmpcodesize.compare import \
compare_function_sizes, compare_sizes_of_file, list_function_sizes,\
read_sizes
SHORTCUTS = {
"O": "bin/Benchmark_O",
"Osize": "bin/Benchmark_Osize",
"Onone": "bin/Benchmark_Onone",
"dylib": "lib/swift/macosx/x86_64/libswiftCore.dylib",
}
def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="""
Compares code sizes of "new" files, taking "old" files as a reference.
Environment variables:
SWIFT_NEW_BUILDDIR The new build-dir
E.g. .../swiftnew/build/Ninja-ReleaseAssert+stdlib-Release/swift-macosx-x86_64
SWIFT_OLD_BUILDDIR The old build-dir
E.g. .../swiftold/build/Ninja-ReleaseAssert+stdlib-Release/swift-macosx-x86_64
How to specify files:
1) No files:
Compares codesize of the Benchmark_* executables and the swiftCore dylib in
the new and old build-dirs.
Example:
cmpcodesize
2) One or more paths relative to the build-dirs (can be a pattern):
Compares the files in the new and old build-dirs.
Aliases:
O => bin/Benchmark_O
Osize => bin/Benchmark_Osize
Onone => bin/Benchmark_Onone
dylib => lib/swift/macosx/x86_64/libswiftCore.dylib
Examples:
cmpcodesize Onone
cmpcodesize benchmark/PerfTestSuite/O/*.o
3) Two files:
Compares these two files (the first is the old file).
Example:
cmpcodesize test.o newversion.o
4) Two lists of files, separated by '--':
Compares a set of files.
Example:
cmpcodesize olddir/*.o -- newdir/*.o
5) One file (only available with the -l option):
Lists function sizes for that file
Example:
cmpcodesize -l test.o""")
# Optional arguments.
parser.add_argument('-a', '--additional-sections',
help='Show sizes of additional sections.',
action='store_true',
dest='all_sections',
default=False)
parser.add_argument('-c', '--category',
help='Show functions by category.',
action='store_true',
dest='list_categories',
default=False)
parser.add_argument('-l', '--list',
help='List all functions (can be a very long list). ' +
'Cannot be used in conjunction with ' +
'--additional-sections or --category. ' +
'You must specify between one and two files ' +
'when using this option.',
action='store_true',
dest='list_functions',
default=False)
parser.add_argument('-s', '--summarize',
help='Summarize the sizes of multiple files instead ' +
'of listing each file separately.',
action='store_true',
dest='sum_sizes',
default=False)
parser.add_argument('-p', '--parseable',
help='Generate output as CSV that can be parsed by ' +
'other programs.',
action='store_true',
default=False)
# Positional arguments.
# These can be specified in means beyond what argparse supports,
# so we gather them in a list and parse them manually.
parser.add_argument('files', nargs='*',
help='A list of old and new files.')
# argparse can't handle an '--' argument, so we replace it with
# a custom identifier.
separator_token = '*-*-*'
parsed_arguments = parser.parse_args(
[separator_token if arg == '--' else arg for arg in sys.argv[1:]])
if parsed_arguments.list_functions:
# --list is mutually exclusive with both --additional-sections
# and --category. argparse is only capable of expressing mutual
# exclusivity among options, not among groups of options, so
# we detect this case manually.
assert (not parsed_arguments.all_sections and
not parsed_arguments.list_categories), \
'Incorrect usage: --list cannot be specified in conjunction ' + \
'with --additional-sections or --category.'
# A file must be specified when using --list.
assert parsed_arguments.files, \
'Incorrect usage: Must specify between one and two files when ' + \
'using --list, but you specified no files.'
csv_out = None
if parsed_arguments.parseable:
csv_out = csv.writer(sys.stdout)
if separator_token in parsed_arguments.files:
separator_index = parsed_arguments.files.index(separator_token)
old_files = parsed_arguments.files[:separator_index]
new_files = parsed_arguments.files[separator_index + 1:]
else:
old_file_args = parsed_arguments.files
old_build_dir = os.environ.get("SWIFT_OLD_BUILDDIR")
new_build_dir = os.environ.get("SWIFT_NEW_BUILDDIR")
if not parsed_arguments.files:
assert old_build_dir and new_build_dir, \
'Incorrect usage: You must specify either a list of ' + \
'files, or have both $SWIFT_OLD_BUILDDIR and ' + \
'$SWIFT_NEW_BUILDDIR environment variables set.\n' + \
'$SWIFT_OLD_BUILDDIR = {0}\n$SWIFT_NEW_BUILDDIR = {1}'.format(
old_build_dir, new_build_dir)
old_file_args = list(SHORTCUTS.keys())
old_files = []
new_files = []
num_expanded = 0
for file in old_file_args:
if file in SHORTCUTS:
file = SHORTCUTS[file]
if not file.startswith("./") and old_build_dir and new_build_dir:
old_expanded = glob.glob(os.path.join(old_build_dir, file))
new_expanded = glob.glob(os.path.join(new_build_dir, file))
if old_expanded and new_expanded:
old_files.extend(old_expanded)
new_files.extend(new_expanded)
num_expanded += 1
if num_expanded != 0 and num_expanded != len(old_file_args):
sys.exit("mix of expanded/not-expanded arguments")
if num_expanded == 0:
if len(old_file_args) > 2:
sys.exit("too many arguments")
old_files = old_file_args[0:1]
new_files = old_file_args[1:2]
for file in (old_files + new_files):
if not os.path.isfile(file):
sys.exit("file " + file + " not found")
if parsed_arguments.list_functions:
if not new_files:
sizes = collections.defaultdict(int)
for file in old_files:
read_sizes(sizes, file, True, False)
print(os.linesep.join(list_function_sizes(sizes.items())))
else:
compare_function_sizes(old_files, new_files, csv=csv_out)
else:
if csv_out:
csv_out.writerow(["Title", "Section", "Old", "Old Relative",
"New", "New Relative", "Percentage Change"])
else:
print("%-26s%16s %14s %14s %s" %
("Title", "Section", "Old", "New", "Percent"))
if parsed_arguments.sum_sizes:
compare_sizes_of_file(old_files, new_files,
parsed_arguments.all_sections,
parsed_arguments.list_categories,
csv=csv_out)
else:
if len(old_files) != len(new_files):
sys.exit("number of new files must be the same of old files")
old_files.sort()
new_files.sort()
for old_file, new_file in zip(old_files, new_files):
compare_sizes_of_file([old_file], [new_file],
parsed_arguments.all_sections,
parsed_arguments.list_categories,
csv=csv_out)
if __name__ == '__main__':
main()