31 from typing
import Any, Dict, Iterable, List, Mapping, Type
35 sys.path.append(os.path.dirname(os.path.abspath(__file__)))
36 import loadtest_config
38 TEMPLATE_FILE_HEADER_COMMENT =
"""
39 # Template generated from load test configurations by loadtest_template.py.
41 # Configuration templates contain client and server configurations for multiple
42 # languages, and may contain template substitution keys. These templates are
43 # used to generate load test configurations by selecting clients and servers for
44 # the required languages. The source files for template generation may be load
45 # test configurations or load test configuration templates. Load test
46 # configuration generation is performed by loadtest_config.py. See documentation
48 # https://github.com/grpc/grpc/blob/master/tools/run_tests/performance/README.md
54 """Inserts client or server into a list, without inserting duplicates."""
57 return yaml.dump(w, Dumper=yaml.SafeDumper, default_flow_style=
False)
59 worker_str = dump(worker)
60 if any((worker_str == dump(w)
for w
in workers)):
62 workers.append(worker)
66 """Name workers if there is more than one for the same map key."""
67 for workers
in list(workermap.values()):
70 for i, worker
in enumerate(workers):
71 worker[
'name'] =
str(i)
75 input_file_names: Iterable[str],
76 metadata: Mapping[str, Any],
77 inject_client_pool: bool,
78 inject_driver_image: bool,
79 inject_driver_pool: bool,
80 inject_server_pool: bool,
81 inject_big_query_table: bool,
82 inject_timeout_seconds: bool,
83 inject_ttl_seconds: bool) -> Dict[str, Any]:
84 """Generates the load test template."""
89 'apiVersion':
'e2etest.grpc.io/v1',
93 for input_file_name
in input_file_names:
94 with open(input_file_name)
as f:
95 input_config = yaml.safe_load(f.read())
97 if input_config.get(
'apiVersion') != template[
'apiVersion']:
98 raise ValueError(
'Unexpected api version in file {}: {}'.
format(
99 input_file_name, input_config.get(
'apiVersion')))
100 if input_config.get(
'kind') != template[
'kind']:
101 raise ValueError(
'Unexpected kind in file {}: {}'.
format(
102 input_file_name, input_config.get(
'kind')))
104 for client
in input_config[
'spec'][
'clients']:
106 if inject_client_pool:
107 client[
'pool'] =
'${client_pool}'
108 if client[
'language']
not in clientmap:
109 clientmap[client[
'language']] = []
112 for server
in input_config[
'spec'][
'servers']:
114 if inject_server_pool:
115 server[
'pool'] =
'${server_pool}'
116 if server[
'language']
not in servermap:
117 servermap[server[
'language']] = []
120 input_spec = input_config[
'spec']
121 del input_spec[
'clients']
122 del input_spec[
'servers']
123 del input_spec[
'scenariosJSON']
124 spec.update(input_config[
'spec'])
131 sum((clientmap[language]
for language
in sorted(clientmap)),
134 sum((servermap[language]
for language
in sorted(servermap)),
138 if 'driver' not in spec:
139 spec[
'driver'] = {
'language':
'cxx'}
141 driver = spec[
'driver']
144 if inject_driver_image:
145 if 'run' not in driver:
146 driver[
'run'] = [{
'name':
'main'}]
147 driver[
'run'][0][
'image'] =
'${driver_image}'
148 if inject_driver_pool:
149 driver[
'pool'] =
'${driver_pool}'
151 if 'run' not in driver:
152 if inject_driver_pool:
153 raise ValueError(
'Cannot inject driver.pool: missing driver.run.')
156 if inject_big_query_table:
157 if 'results' not in spec:
158 spec[
'results'] = dict()
159 spec[
'results'][
'bigQueryTable'] =
'${big_query_table}'
160 if inject_timeout_seconds:
161 spec[
'timeoutSeconds'] =
'${timeout_seconds}'
162 if inject_ttl_seconds:
163 spec[
'ttlSeconds'] =
'${ttl_seconds}'
165 template[
'spec'] = spec
171 """Returns a custom dumper to dump templates in the expected format."""
173 class TemplateDumper(yaml.SafeDumper):
175 def expect_stream_start(self):
176 super().expect_stream_start()
177 if isinstance(self.event, yaml.StreamStartEvent):
179 self.write_indicator(header_comment, need_whitespace=
False)
181 def str_presenter(dumper, data):
183 return dumper.represent_scalar(
'tag:yaml.org,2002:str',
186 return dumper.represent_scalar(
'tag:yaml.org,2002:str', data)
188 TemplateDumper.add_representer(str, str_presenter)
190 return TemplateDumper
194 argp = argparse.ArgumentParser(
195 description=
'Creates a load test config generator template.',
196 fromfile_prefix_chars=
'@')
197 argp.add_argument(
'-i',
203 argp.add_argument(
'-o',
206 help=
'Output file. Outputs to stdout if not set.')
208 '--inject_client_pool',
210 help=
'Set spec.client(s).pool values to \'${client_pool}\'.')
212 '--inject_driver_image',
214 help=
'Set spec.driver(s).image values to \'${driver_image}\'.')
216 '--inject_driver_pool',
218 help=
'Set spec.driver(s).pool values to \'${driver_pool}\'.')
220 '--inject_server_pool',
222 help=
'Set spec.server(s).pool values to \'${server_pool}\'.')
224 '--inject_big_query_table',
226 help=
'Set spec.results.bigQueryTable to \'${big_query_table}\'.')
227 argp.add_argument(
'--inject_timeout_seconds',
229 help=
'Set spec.timeoutSeconds to \'${timeout_seconds}\'.')
230 argp.add_argument(
'--inject_ttl_seconds',
233 argp.add_argument(
'-n',
237 help=
'metadata.name.')
238 argp.add_argument(
'-a',
242 help=
'metadata.annotation(s), in the form key=value.',
244 args = argp.parse_args()
246 annotations = loadtest_config.parse_key_value_args(args.annotations)
248 metadata = {
'name': args.name}
250 metadata[
'annotations'] = annotations
253 input_file_names=args.inputs,
255 inject_client_pool=args.inject_client_pool,
256 inject_driver_image=args.inject_driver_image,
257 inject_driver_pool=args.inject_driver_pool,
258 inject_server_pool=args.inject_server_pool,
259 inject_big_query_table=args.inject_big_query_table,
260 inject_timeout_seconds=args.inject_timeout_seconds,
261 inject_ttl_seconds=args.inject_ttl_seconds)
263 with open(args.output,
'w')
if args.output
else sys.stdout
as f:
267 default_flow_style=
False)
270 if __name__ ==
'__main__':