123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- #!/usr/bin/env python3
- # Copyright 2021 The gRPC Authors
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- # This script generates a load test configuration template from a collection of
- # load test configurations.
- #
- # Configuration templates contain client and server configurations for multiple
- # languages, and may contain template substitution keys. These templates are
- # used to generate load test configurations by selecting clients and servers for
- # the required languages. The source files for template generation may be load
- # test configurations or load test configuration templates. Load test
- # configuration generation is performed by loadtest_config.py. See documentation
- # below:
- # https://github.com/grpc/grpc/blob/master/tools/run_tests/performance/README.md
- import argparse
- import os
- import sys
- from typing import Any, Dict, Iterable, List, Mapping, Type
- import yaml
- sys.path.append(os.path.dirname(os.path.abspath(__file__)))
- import loadtest_config
- TEMPLATE_FILE_HEADER_COMMENT = """
- # Template generated from load test configurations by loadtest_template.py.
- #
- # Configuration templates contain client and server configurations for multiple
- # languages, and may contain template substitution keys. These templates are
- # used to generate load test configurations by selecting clients and servers for
- # the required languages. The source files for template generation may be load
- # test configurations or load test configuration templates. Load test
- # configuration generation is performed by loadtest_config.py. See documentation
- # below:
- # https://github.com/grpc/grpc/blob/master/tools/run_tests/performance/README.md
- """
- def insert_worker(worker: Dict[str, Any], workers: List[Dict[str,
- Any]]) -> None:
- """Inserts client or server into a list, without inserting duplicates."""
- def dump(w):
- return yaml.dump(w, Dumper=yaml.SafeDumper, default_flow_style=False)
- worker_str = dump(worker)
- if any((worker_str == dump(w) for w in workers)):
- return
- workers.append(worker)
- def uniquify_workers(workermap: Dict[str, List[Dict[str, Any]]]) -> None:
- """Name workers if there is more than one for the same map key."""
- for workers in list(workermap.values()):
- if len(workers) <= 1:
- continue
- for i, worker in enumerate(workers):
- worker['name'] = str(i)
- def loadtest_template(
- input_file_names: Iterable[str],
- metadata: Mapping[str, Any],
- inject_client_pool: bool,
- inject_driver_image: bool,
- inject_driver_pool: bool,
- inject_server_pool: bool,
- inject_big_query_table: bool,
- inject_timeout_seconds: bool,
- inject_ttl_seconds: bool) -> Dict[str, Any]: # yapf: disable
- """Generates the load test template."""
- spec = dict() # type: Dict[str, Any]
- clientmap = dict() # Dict[str, List[Dict[str, Any]]]
- servermap = dict() # Dict[Str, List[Dict[str, Any]]]
- template = {
- 'apiVersion': 'e2etest.grpc.io/v1',
- 'kind': 'LoadTest',
- 'metadata': metadata,
- }
- for input_file_name in input_file_names:
- with open(input_file_name) as f:
- input_config = yaml.safe_load(f.read())
- if input_config.get('apiVersion') != template['apiVersion']:
- raise ValueError('Unexpected api version in file {}: {}'.format(
- input_file_name, input_config.get('apiVersion')))
- if input_config.get('kind') != template['kind']:
- raise ValueError('Unexpected kind in file {}: {}'.format(
- input_file_name, input_config.get('kind')))
- for client in input_config['spec']['clients']:
- del client['name']
- if inject_client_pool:
- client['pool'] = '${client_pool}'
- if client['language'] not in clientmap:
- clientmap[client['language']] = []
- insert_worker(client, clientmap[client['language']])
- for server in input_config['spec']['servers']:
- del server['name']
- if inject_server_pool:
- server['pool'] = '${server_pool}'
- if server['language'] not in servermap:
- servermap[server['language']] = []
- insert_worker(server, servermap[server['language']])
- input_spec = input_config['spec']
- del input_spec['clients']
- del input_spec['servers']
- del input_spec['scenariosJSON']
- spec.update(input_config['spec'])
- uniquify_workers(clientmap)
- uniquify_workers(servermap)
- spec.update({
- 'clients':
- sum((clientmap[language] for language in sorted(clientmap)),
- start=[]),
- 'servers':
- sum((servermap[language] for language in sorted(servermap)),
- start=[]),
- })
- if 'driver' not in spec:
- spec['driver'] = {'language': 'cxx'}
- driver = spec['driver']
- if 'name' in driver:
- del driver['name']
- if inject_driver_image:
- if 'run' not in driver:
- driver['run'] = [{'name': 'main'}]
- driver['run'][0]['image'] = '${driver_image}'
- if inject_driver_pool:
- driver['pool'] = '${driver_pool}'
- if 'run' not in driver:
- if inject_driver_pool:
- raise ValueError('Cannot inject driver.pool: missing driver.run.')
- del spec['driver']
- if inject_big_query_table:
- if 'results' not in spec:
- spec['results'] = dict()
- spec['results']['bigQueryTable'] = '${big_query_table}'
- if inject_timeout_seconds:
- spec['timeoutSeconds'] = '${timeout_seconds}'
- if inject_ttl_seconds:
- spec['ttlSeconds'] = '${ttl_seconds}'
- template['spec'] = spec
- return template
- def template_dumper(header_comment: str) -> Type[yaml.SafeDumper]:
- """Returns a custom dumper to dump templates in the expected format."""
- class TemplateDumper(yaml.SafeDumper):
- def expect_stream_start(self):
- super().expect_stream_start()
- if isinstance(self.event, yaml.StreamStartEvent):
- self.write_indent()
- self.write_indicator(header_comment, need_whitespace=False)
- def str_presenter(dumper, data):
- if '\n' in data:
- return dumper.represent_scalar('tag:yaml.org,2002:str',
- data,
- style='|')
- return dumper.represent_scalar('tag:yaml.org,2002:str', data)
- TemplateDumper.add_representer(str, str_presenter)
- return TemplateDumper
- def main() -> None:
- argp = argparse.ArgumentParser(
- description='Creates a load test config generator template.',
- fromfile_prefix_chars='@')
- argp.add_argument('-i',
- '--inputs',
- action='extend',
- nargs='+',
- type=str,
- help='Input files.')
- argp.add_argument('-o',
- '--output',
- type=str,
- help='Output file. Outputs to stdout if not set.')
- argp.add_argument(
- '--inject_client_pool',
- action='store_true',
- help='Set spec.client(s).pool values to \'${client_pool}\'.')
- argp.add_argument(
- '--inject_driver_image',
- action='store_true',
- help='Set spec.driver(s).image values to \'${driver_image}\'.')
- argp.add_argument(
- '--inject_driver_pool',
- action='store_true',
- help='Set spec.driver(s).pool values to \'${driver_pool}\'.')
- argp.add_argument(
- '--inject_server_pool',
- action='store_true',
- help='Set spec.server(s).pool values to \'${server_pool}\'.')
- argp.add_argument(
- '--inject_big_query_table',
- action='store_true',
- help='Set spec.results.bigQueryTable to \'${big_query_table}\'.')
- argp.add_argument('--inject_timeout_seconds',
- action='store_true',
- help='Set spec.timeoutSeconds to \'${timeout_seconds}\'.')
- argp.add_argument('--inject_ttl_seconds',
- action='store_true',
- help='Set timeout ')
- argp.add_argument('-n',
- '--name',
- default='',
- type=str,
- help='metadata.name.')
- argp.add_argument('-a',
- '--annotation',
- action='append',
- type=str,
- help='metadata.annotation(s), in the form key=value.',
- dest='annotations')
- args = argp.parse_args()
- annotations = loadtest_config.parse_key_value_args(args.annotations)
- metadata = {'name': args.name}
- if annotations:
- metadata['annotations'] = annotations
- template = loadtest_template(
- input_file_names=args.inputs,
- metadata=metadata,
- inject_client_pool=args.inject_client_pool,
- inject_driver_image=args.inject_driver_image,
- inject_driver_pool=args.inject_driver_pool,
- inject_server_pool=args.inject_server_pool,
- inject_big_query_table=args.inject_big_query_table,
- inject_timeout_seconds=args.inject_timeout_seconds,
- inject_ttl_seconds=args.inject_ttl_seconds)
- with open(args.output, 'w') if args.output else sys.stdout as f:
- yaml.dump(template,
- stream=f,
- Dumper=template_dumper(TEMPLATE_FILE_HEADER_COMMENT.strip()),
- default_flow_style=False)
- if __name__ == '__main__':
- main()
|