123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- #!/usr/bin/env python3
- # Copyright 2020 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.
- # Library to extract scenario definitions from scenario_config.py.
- #
- # Contains functions to filter, analyze and dump scenario definitions.
- #
- # This library is used in loadtest_config.py to generate the "scenariosJSON"
- # field in the format accepted by the OSS benchmarks framework.
- # See https://github.com/grpc/test-infra/blob/master/config/samples/cxx_example_loadtest.yaml
- #
- # It can also be used to dump scenarios to files, to count scenarios by
- # language, and to export scenario languages in a format that can be used for
- # automation.
- #
- # Example usage:
- #
- # scenario_config.py --export_scenarios -l cxx -f cxx_scenario_ -r '.*' \
- # --category=scalable
- #
- # scenario_config.py --count_scenarios
- #
- # scenario_config.py --count_scenarios --category=scalable
- #
- # For usage of the language config output, see loadtest_config.py.
- import argparse
- import collections
- import json
- import re
- import sys
- from typing import Any, Callable, Dict, Iterable, NamedTuple
- import scenario_config
- # Language parameters for load test config generation.
- LanguageConfig = NamedTuple('LanguageConfig', [('category', str),
- ('language', str),
- ('client_language', str),
- ('server_language', str)])
- def category_string(categories: Iterable[str], category: str) -> str:
- """Converts a list of categories into a single string for counting."""
- if category != 'all':
- return category if category in categories else ''
- main_categories = ('scalable', 'smoketest')
- s = set(categories)
- c = [m for m in main_categories if m in s]
- s.difference_update(main_categories)
- c.extend(s)
- return ' '.join(c)
- def gen_scenario_languages(category: str) -> Iterable[LanguageConfig]:
- """Generates tuples containing the languages specified in each scenario."""
- for language in scenario_config.LANGUAGES:
- for scenario in scenario_config.LANGUAGES[language].scenarios():
- client_language = scenario.get('CLIENT_LANGUAGE', '')
- server_language = scenario.get('SERVER_LANGUAGE', '')
- categories = scenario.get('CATEGORIES', [])
- if category != 'all' and category not in categories:
- continue
- cat = category_string(categories, category)
- yield LanguageConfig(category=cat,
- language=language,
- client_language=client_language,
- server_language=server_language)
- def scenario_filter(
- scenario_name_regex: str = '.*',
- category: str = 'all',
- client_language: str = '',
- server_language: str = '',
- ) -> Callable[[Dict[str, Any]], bool]:
- """Returns a function to filter scenarios to process."""
- def filter_scenario(scenario: Dict[str, Any]) -> bool:
- """Filters scenarios that match specified criteria."""
- if not re.search(scenario_name_regex, scenario["name"]):
- return False
- # if the 'CATEGORIES' key is missing, treat scenario as part of
- # 'scalable' and 'smoketest'. This matches the behavior of
- # run_performance_tests.py.
- scenario_categories = scenario.get('CATEGORIES',
- ['scalable', 'smoketest'])
- if category not in scenario_categories and category != 'all':
- return False
- scenario_client_language = scenario.get('CLIENT_LANGUAGE', '')
- if client_language != scenario_client_language:
- return False
- scenario_server_language = scenario.get('SERVER_LANGUAGE', '')
- if server_language != scenario_server_language:
- return False
- return True
- return filter_scenario
- def gen_scenarios(
- language_name: str, scenario_filter_function: Callable[[Dict[str, Any]],
- bool]
- ) -> Iterable[Dict[str, Any]]:
- """Generates scenarios that match a given filter function."""
- return map(
- scenario_config.remove_nonproto_fields,
- filter(scenario_filter_function,
- scenario_config.LANGUAGES[language_name].scenarios()))
- def dump_to_json_files(scenarios: Iterable[Dict[str, Any]],
- filename_prefix: str) -> None:
- """Dumps a list of scenarios to JSON files"""
- count = 0
- for scenario in scenarios:
- filename = '{}{}.json'.format(filename_prefix, scenario['name'])
- print('Writing file {}'.format(filename), file=sys.stderr)
- with open(filename, 'w') as outfile:
- # The dump file should have {"scenarios" : []} as the top level
- # element, when embedded in a LoadTest configuration YAML file.
- json.dump({'scenarios': [scenario]}, outfile, indent=2)
- count += 1
- print('Wrote {} scenarios'.format(count), file=sys.stderr)
- def main() -> None:
- language_choices = sorted(scenario_config.LANGUAGES.keys())
- argp = argparse.ArgumentParser(description='Exports scenarios to files.')
- argp.add_argument('--export_scenarios',
- action='store_true',
- help='Export scenarios to JSON files.')
- argp.add_argument('--count_scenarios',
- action='store_true',
- help='Count scenarios for all test languages.')
- argp.add_argument('-l',
- '--language',
- choices=language_choices,
- help='Language to export.')
- argp.add_argument('-f',
- '--filename_prefix',
- default='scenario_dump_',
- type=str,
- help='Prefix for exported JSON file names.')
- argp.add_argument('-r',
- '--regex',
- default='.*',
- type=str,
- help='Regex to select scenarios to run.')
- argp.add_argument(
- '--category',
- default='all',
- choices=['all', 'inproc', 'scalable', 'smoketest', 'sweep'],
- help='Select scenarios for a category of tests.')
- argp.add_argument(
- '--client_language',
- default='',
- choices=language_choices,
- help='Select only scenarios with a specified client language.')
- argp.add_argument(
- '--server_language',
- default='',
- choices=language_choices,
- help='Select only scenarios with a specified server language.')
- args = argp.parse_args()
- if args.export_scenarios and not args.language:
- print('Dumping scenarios requires a specified language.',
- file=sys.stderr)
- argp.print_usage(file=sys.stderr)
- return
- if args.export_scenarios:
- s_filter = scenario_filter(scenario_name_regex=args.regex,
- category=args.category,
- client_language=args.client_language,
- server_language=args.server_language)
- scenarios = gen_scenarios(args.language, s_filter)
- dump_to_json_files(scenarios, args.filename_prefix)
- if args.count_scenarios:
- print('Scenario count for all languages (category: {}):'.format(
- args.category))
- print('{:>5} {:16} {:8} {:8} {}'.format('Count', 'Language', 'Client',
- 'Server', 'Categories'))
- c = collections.Counter(gen_scenario_languages(args.category))
- total = 0
- for ((cat, l, cl, sl), count) in c.most_common():
- print('{count:5} {l:16} {cl:8} {sl:8} {cat}'.format(l=l,
- cl=cl,
- sl=sl,
- count=count,
- cat=cat))
- total += count
- print('\n{:>5} total scenarios (category: {})'.format(
- total, args.category))
- if __name__ == "__main__":
- main()
|