1#!/usr/bin/env python3 2# Copyright 2016 Google Inc. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS-IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import random 17import os 18 19import fruit_source_generator 20import boost_di_source_generator 21import no_di_library_source_generator 22from makefile_generator import generate_makefile 23import argparse 24import networkx as nx 25 26 27def generate_injection_graph(num_components_with_no_deps, 28 num_components_with_deps, 29 num_deps): 30 injection_graph = nx.DiGraph() 31 32 num_used_ids = 0 33 is_toplevel = [True for i in range(0, num_components_with_no_deps + num_components_with_deps)] 34 toplevel_components = set() 35 for i in range(0, num_components_with_no_deps): 36 id = num_used_ids 37 num_used_ids += 1 38 toplevel_components.add(id) 39 40 # Then the rest have num_deps deps, chosen (pseudo-)randomly from the previous components. 41 # The last few components depend more components with >1 deps, so that the last component transitively depends on 42 # everything. 43 for i in range(0, num_components_with_deps): 44 deps = set() 45 46 if len(toplevel_components) > (num_components_with_deps - 1 - i) * (num_deps - 1): 47 # We need at least 1 dep with deps, otherwise the last few components will not be enough 48 # to tie together all components. 49 num_deps_with_deps = len(toplevel_components) - (num_components_with_deps - 1 - i) * (num_deps - 1) 50 deps |= set(random.sample(toplevel_components, num_deps_with_deps)) 51 52 # Add other deps to get to the desired num_deps. 53 deps |= set(random.sample(range(0, num_components_with_no_deps + i), num_deps - len(deps))) 54 55 toplevel_components -= deps 56 for dep in deps: 57 is_toplevel[dep] = False 58 59 component_id = num_used_ids 60 toplevel_components |= {component_id} 61 num_used_ids += 1 62 deps_list = list(deps) 63 random.shuffle(deps_list) 64 for dep in deps_list: 65 injection_graph.add_edge(component_id, dep) 66 67 assert len(toplevel_components) == 1, toplevel_components 68 toplevel_component = num_used_ids - 1 69 assert is_toplevel[toplevel_component] 70 71 return injection_graph 72 73def generate_benchmark( 74 di_library, 75 compiler, 76 cxx_std, 77 output_dir, 78 num_components_with_no_deps, 79 num_components_with_deps, 80 num_deps, 81 generate_runtime_bench_code, 82 use_exceptions=True, 83 use_rtti=True, 84 fruit_build_dir=None, 85 fruit_sources_dir=None, 86 boost_di_sources_dir=None, 87 generate_debuginfo=False, 88 use_new_delete=False, 89 use_interfaces=False, 90 use_normalized_component=False): 91 """Generates a sample codebase using the specified DI library, meant for benchmarking. 92 93 :param boost_di_sources_dir: this is only used if di_library=='boost_di', it can be None otherwise. 94 """ 95 96 if num_components_with_no_deps < num_deps: 97 raise Exception( 98 "Too few components with no deps. num_components_with_no_deps=%s but num_deps=%s." % (num_components_with_no_deps, num_deps)) 99 if num_deps < 2: 100 raise Exception("num_deps should be at least 2.") 101 102 # This is a constant so that we always generate the same file (=> benchmark more repeatable). 103 random.seed(42) 104 105 injection_graph = generate_injection_graph(num_components_with_no_deps=num_components_with_no_deps, 106 num_components_with_deps=num_components_with_deps, 107 num_deps=num_deps) 108 109 if di_library == 'fruit': 110 file_content_by_name = fruit_source_generator.generate_files(injection_graph, generate_runtime_bench_code) 111 include_dirs = [fruit_build_dir + '/include', fruit_sources_dir + '/include'] 112 library_dirs = [fruit_build_dir + '/src'] 113 link_libraries = ['fruit'] 114 elif di_library == 'boost_di': 115 file_content_by_name = boost_di_source_generator.generate_files(injection_graph, generate_runtime_bench_code) 116 include_dirs = [boost_di_sources_dir + '/include', boost_di_sources_dir + '/extension/include'] 117 library_dirs = [] 118 link_libraries = [] 119 elif di_library == 'none': 120 file_content_by_name = no_di_library_source_generator.generate_files(injection_graph, use_new_delete, use_interfaces, generate_runtime_bench_code) 121 include_dirs = [] 122 library_dirs = [] 123 link_libraries = [] 124 else: 125 raise Exception('Unrecognized di_library: %s' % di_library) 126 127 include_flags = ' '.join(['-I%s' % include_dir for include_dir in include_dirs]) 128 library_dirs_flags = ' '.join(['-L%s' % library_dir for library_dir in library_dirs]) 129 rpath_flags = ' '.join(['-Wl,-rpath,%s' % library_dir for library_dir in library_dirs]) 130 link_libraries_flags = ' '.join(['-l%s' % library for library in link_libraries]) 131 other_compile_flags = [] 132 if generate_debuginfo: 133 other_compile_flags.append('-g') 134 if not use_exceptions: 135 other_compile_flags.append('-fno-exceptions') 136 if not use_rtti: 137 other_compile_flags.append('-fno-rtti') 138 compile_command = '%s -std=%s -MMD -MP -O2 -W -Wall -Werror -DNDEBUG -ftemplate-depth=10000 %s %s' % (compiler, cxx_std, include_flags, ' '.join(other_compile_flags)) 139 link_command = '%s -std=%s -O2 -W -Wall -Werror %s %s' % (compiler, cxx_std, rpath_flags, library_dirs_flags) 140 # GCC requires passing the -lfruit flag *after* all object files to be linked for some reason. 141 link_command_suffix = link_libraries_flags 142 143 cpp_files = [file_name 144 for file_name in file_content_by_name.keys() 145 if file_name.endswith('.cpp')] 146 147 file_content_by_name['Makefile'] = generate_makefile(cpp_files, 'main', compile_command, link_command, link_command_suffix) 148 149 os.makedirs(output_dir, exist_ok=True) 150 for file_name, file_content in file_content_by_name.items(): 151 with open('%s/%s' % (output_dir, file_name), 'w') as file: 152 file.write(file_content) 153 154 return file_content_by_name.keys() 155 156def main(): 157 parser = argparse.ArgumentParser(description='Generates source files and a build script for benchmarks.') 158 parser.add_argument('--di-library', default='fruit', help='DI library to use. One of {fruit, boost_di, none}. (default: fruit)') 159 parser.add_argument('--compiler', help='Compiler to use') 160 parser.add_argument('--fruit-sources-dir', help='Path to the fruit sources (only used when di_library==\'fruit\')') 161 parser.add_argument('--fruit-build-dir', help='Path to the fruit build dir (only used with --di_library=\'fruit\')') 162 parser.add_argument('--boost-di-sources-dir', help='Path to the Boost.DI sources (only used with --di-library==\'boost_di\')') 163 parser.add_argument('--num-components-with-no-deps', default=10, help='Number of components with no deps that will be generated') 164 parser.add_argument('--num-components-with-deps', default=90, help='Number of components with deps that will be generated') 165 parser.add_argument('--num-deps', default=10, help='Number of deps in each component with deps that will be generated') 166 parser.add_argument('--output-dir', help='Output directory for generated files') 167 parser.add_argument('--cxx-std', default='c++11', 168 help='Version of the C++ standard to use. Typically one of \'c++11\' and \'c++14\'. (default: \'c++11\')') 169 parser.add_argument('--use-new-delete', default='false', help='Set this to \'true\' to use new/delete. Only relevant when --di_library=none.') 170 parser.add_argument('--use-interfaces', default='false', help='Set this to \'true\' to use interfaces. Only relevant when --di_library=none.') 171 parser.add_argument('--use-normalized-component', default='false', help='Set this to \'true\' to create a NormalizedComponent and create the injector from that. Only relevant when --di_library=fruit and --generate-runtime-bench-code=false.') 172 parser.add_argument('--generate-runtime-bench-code', default='true', help='Set this to \'false\' for compile benchmarks.') 173 parser.add_argument('--generate-debuginfo', default='false', help='Set this to \'true\' to generate debugging information (-g).') 174 parser.add_argument('--use-exceptions', default='true', help='Set this to \'false\' to disable exceptions.') 175 parser.add_argument('--use-rtti', default='true', help='Set this to \'false\' to disable RTTI.') 176 177 args = parser.parse_args() 178 179 if args.compiler is None: 180 raise Exception('--compiler is required.') 181 182 if args.di_library == 'fruit': 183 if args.fruit_sources_dir is None: 184 raise Exception('--fruit-sources-dir is required with --di-library=\'fruit\'.') 185 if args.fruit_build_dir is None: 186 raise Exception('--fruit-build-dir is required with --di-library=\'fruit\'.') 187 elif args.di_library == 'boost_di': 188 if args.boost_di_sources_dir is None: 189 raise Exception('--boost-di-sources-dir is required with --di-library=\'boost_di\'.') 190 elif args.di_library == 'none': 191 pass 192 else: 193 raise Exception('Unrecognized --di-library: \'%s\'. Allowed values are %s' % (args.di_library, {'fruit', 'boost_di', 'none'})) 194 195 num_components_with_deps = int(args.num_components_with_deps) 196 num_components_with_no_deps = int(args.num_components_with_no_deps) 197 num_deps = int(args.num_deps) 198 199 if args.output_dir is None: 200 raise Exception("output_dir must be specified.") 201 202 generate_benchmark( 203 di_library=args.di_library, 204 fruit_sources_dir=args.fruit_sources_dir, 205 boost_di_sources_dir=args.boost_di_sources_dir, 206 output_dir=args.output_dir, 207 compiler=args.compiler, 208 cxx_std=args.cxx_std, 209 num_components_with_deps=num_components_with_deps, 210 num_components_with_no_deps=num_components_with_no_deps, 211 fruit_build_dir=args.fruit_build_dir, 212 num_deps=num_deps, 213 generate_debuginfo=(args.generate_debuginfo == 'true'), 214 use_new_delete=(args.use_new_delete == 'true'), 215 use_interfaces=(args.use_interfaces == 'true'), 216 use_normalized_component=(args.use_normalized_component == 'true'), 217 generate_runtime_bench_code=(args.generate_runtime_bench_code == 'true'), 218 use_exceptions=(args.use_exceptions == 'true'), 219 use_rtti=(args.use_rtti == 'true')) 220 221 222if __name__ == "__main__": 223 main() 224