summaryrefslogtreecommitdiff
path: root/grpc/tools/run_tests/performance/loadtest_template.py
diff options
context:
space:
mode:
Diffstat (limited to 'grpc/tools/run_tests/performance/loadtest_template.py')
-rwxr-xr-xgrpc/tools/run_tests/performance/loadtest_template.py216
1 files changed, 216 insertions, 0 deletions
diff --git a/grpc/tools/run_tests/performance/loadtest_template.py b/grpc/tools/run_tests/performance/loadtest_template.py
new file mode 100755
index 00000000..9684bb9a
--- /dev/null
+++ b/grpc/tools/run_tests/performance/loadtest_template.py
@@ -0,0 +1,216 @@
+#!/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 sys
+
+from typing import Any, Dict, Iterable, Mapping, Type
+
+import yaml
+
+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 loadtest_template(
+ input_file_names: Iterable[str],
+ metadata: Mapping[str, Any],
+ inject_client_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."""
+ clients = list()
+ servers = list()
+ spec = dict()
+ client_languages = set()
+ server_languages = set()
+ 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']:
+ if client['language'] in client_languages:
+ continue
+ if inject_client_pool:
+ client['pool'] = '${client_pool}'
+ clients.append(client)
+ client_languages.add(client['language'])
+
+ for server in input_config['spec']['servers']:
+ if server['language'] in server_languages:
+ continue
+ if inject_server_pool:
+ server['pool'] = '${server_pool}'
+ servers.append(server)
+ server_languages.add(server['language'])
+
+ input_spec = input_config['spec']
+ del input_spec['clients']
+ del input_spec['servers']
+ del input_spec['scenariosJSON']
+ spec.update(input_config['spec'])
+
+ clients.sort(key=lambda x: x['language'])
+ servers.sort(key=lambda x: x['language'])
+
+ spec.update({
+ 'clients': clients,
+ 'servers': servers,
+ })
+
+ 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 expect_block_sequence(self):
+ super().expect_block_sequence()
+ self.increase_indent()
+
+ def expect_block_sequence_item(self, first=False):
+ if isinstance(self.event, yaml.SequenceEndEvent):
+ self.indent = self.indents.pop()
+ super().expect_block_sequence_item(first)
+
+ 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_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_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()