• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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