3 # Copyright 2015 Intel Corporation.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """VSPERF main script.
28 sys.dont_write_bytecode = True
30 from conf import settings
31 from core.loader import Loader
32 from testcases import TestCase
33 from tools import tasks
34 from tools.collectors import collector
35 from tools.pkt_gen import trafficgen
38 'debug': logging.DEBUG,
40 'warning': logging.WARNING,
41 'error': logging.ERROR,
42 'critical': logging.CRITICAL
46 def parse_arguments():
48 Parse command line arguments.
50 class _SplitTestParamsAction(argparse.Action):
52 Parse and split the '--test-params' argument.
54 This expects either 'x=y' or 'x' (implicit true) values.
56 def __call__(self, parser, namespace, values, option_string=None):
59 for value in values.split(';'):
60 result = [key.strip() for key in value.split('=')]
62 results[result[0]] = True
63 elif len(result) == 2:
64 results[result[0]] = result[1]
66 raise argparse.ArgumentTypeError(
67 'expected \'%s\' to be of format \'key=val\' or'
70 setattr(namespace, self.dest, results)
72 class _ValidateFileAction(argparse.Action):
73 """Validate a file can be read from before using it.
75 def __call__(self, parser, namespace, values, option_string=None):
76 if not os.path.isfile(values):
77 raise argparse.ArgumentTypeError(
78 'the path \'%s\' is not a valid path' % values)
79 elif not os.access(values, os.R_OK):
80 raise argparse.ArgumentTypeError(
81 'the path \'%s\' is not accessible' % values)
83 setattr(namespace, self.dest, values)
85 class _ValidateDirAction(argparse.Action):
86 """Validate a directory can be written to before using it.
88 def __call__(self, parser, namespace, values, option_string=None):
89 if not os.path.isdir(values):
90 raise argparse.ArgumentTypeError(
91 'the path \'%s\' is not a valid path' % values)
92 elif not os.access(values, os.W_OK):
93 raise argparse.ArgumentTypeError(
94 'the path \'%s\' is not accessible' % values)
96 setattr(namespace, self.dest, values)
98 def list_logging_levels():
99 """Give a summary of all available logging levels.
101 :return: List of verbosity level names in decreasing order of
104 return sorted(VERBOSITY_LEVELS.keys(),
105 key=lambda x: VERBOSITY_LEVELS[x])
107 parser = argparse.ArgumentParser(prog=__file__, formatter_class=
108 argparse.ArgumentDefaultsHelpFormatter)
109 parser.add_argument('--version', action='version', version='%(prog)s 0.2')
110 parser.add_argument('--list', '--list-tests', action='store_true',
111 help='list all tests and exit')
112 parser.add_argument('--list-trafficgens', action='store_true',
113 help='list all traffic generators and exit')
114 parser.add_argument('--list-collectors', action='store_true',
115 help='list all system metrics loggers and exit')
116 parser.add_argument('--list-vswitches', action='store_true',
117 help='list all system vswitches and exit')
118 parser.add_argument('--list-settings', action='store_true',
119 help='list effective settings configuration and exit')
120 parser.add_argument('test', nargs='*', help='test specification(s)')
122 group = parser.add_argument_group('test selection options')
123 group.add_argument('-f', '--test-spec', help='test specification file')
124 group.add_argument('-d', '--test-dir', help='directory containing tests')
125 group.add_argument('-t', '--tests', help='Comma-separated list of terms \
126 indicating tests to run. e.g. "RFC2544,!p2p" - run all tests whose\
127 name contains RFC2544 less those containing "p2p"')
128 group.add_argument('--verbosity', choices=list_logging_levels(),
130 group.add_argument('--trafficgen', help='traffic generator to use')
131 group.add_argument('--sysmetrics', help='system metrics logger to use')
132 group = parser.add_argument_group('test behavior options')
133 group.add_argument('--load-env', action='store_true',
134 help='enable loading of settings from the environment')
135 group.add_argument('--conf-file', action=_ValidateFileAction,
136 help='settings file')
137 group.add_argument('--test-params', action=_SplitTestParamsAction,
138 help='csv list of test parameters: key=val;...')
140 args = vars(parser.parse_args())
145 def configure_logging(level):
146 """Configure logging.
148 log_file_default = os.path.join(
149 settings.getValue('LOG_DIR'), settings.getValue('LOG_FILE_DEFAULT'))
150 log_file_host_cmds = os.path.join(
151 settings.getValue('LOG_DIR'), settings.getValue('LOG_FILE_HOST_CMDS'))
152 log_file_traffic_gen = os.path.join(
153 settings.getValue('LOG_DIR'),
154 settings.getValue('LOG_FILE_TRAFFIC_GEN'))
155 log_file_sys_metrics = os.path.join(
156 settings.getValue('LOG_DIR'),
157 settings.getValue('LOG_FILE_SYS_METRICS'))
159 logger = logging.getLogger()
160 logger.setLevel(logging.DEBUG)
162 stream_logger = logging.StreamHandler(sys.stdout)
163 stream_logger.setLevel(VERBOSITY_LEVELS[level])
164 stream_logger.setFormatter(logging.Formatter(
165 '[%(levelname)s] %(asctime)s : (%(name)s) - %(message)s'))
166 logger.addHandler(stream_logger)
168 file_logger = logging.FileHandler(filename=log_file_default)
169 file_logger.setLevel(logging.DEBUG)
170 logger.addHandler(file_logger)
172 class CommandFilter(logging.Filter):
173 """Filter out strings beginning with 'cmd :'"""
174 def filter(self, record):
175 return record.getMessage().startswith(tasks.CMD_PREFIX)
177 class TrafficGenCommandFilter(logging.Filter):
178 """Filter out strings beginning with 'gencmd :'"""
179 def filter(self, record):
180 return record.getMessage().startswith(trafficgen.CMD_PREFIX)
182 class SystemMetricsCommandFilter(logging.Filter):
183 """Filter out strings beginning with 'gencmd :'"""
184 def filter(self, record):
185 return record.getMessage().startswith(collector.CMD_PREFIX)
187 cmd_logger = logging.FileHandler(filename=log_file_host_cmds)
188 cmd_logger.setLevel(logging.DEBUG)
189 cmd_logger.addFilter(CommandFilter())
190 logger.addHandler(cmd_logger)
192 gen_logger = logging.FileHandler(filename=log_file_traffic_gen)
193 gen_logger.setLevel(logging.DEBUG)
194 gen_logger.addFilter(TrafficGenCommandFilter())
195 logger.addHandler(gen_logger)
197 metrics_logger = logging.FileHandler(filename=log_file_sys_metrics)
198 metrics_logger.setLevel(logging.DEBUG)
199 metrics_logger.addFilter(SystemMetricsCommandFilter())
200 logger.addHandler(metrics_logger)
203 def apply_filter(tests, tc_filter):
204 """Allow a subset of tests to be conveniently selected
206 :param tests: The list of Tests from which to select.
207 :param tc_filter: A case-insensitive string of comma-separated terms
208 indicating the Tests to select.
209 e.g. 'RFC' - select all tests whose name contains 'RFC'
210 e.g. 'RFC,burst' - select all tests whose name contains 'RFC' or
212 e.g. 'RFC,burst,!p2p' - select all tests whose name contains 'RFC'
213 or 'burst' and from these remove any containing 'p2p'.
214 e.g. '' - empty string selects all tests.
215 :return: A list of the selected Tests.
218 if tc_filter is None:
221 for term in [x.strip() for x in tc_filter.lower().split(",")]:
222 if not term or term[0] != '!':
223 # Add matching tests from 'tests' into results
224 result.extend([test for test in tests \
225 if test.name.lower().find(term) >= 0])
227 # Term begins with '!' so we remove matching tests
228 result = [test for test in result \
229 if test.name.lower().find(term[1:]) < 0]
237 args = parse_arguments()
241 settings.load_from_dir('conf')
243 # load command line parameters first in case there are settings files
245 settings.load_from_dict(args)
247 if args['conf_file']:
248 settings.load_from_file(args['conf_file'])
251 settings.load_from_env()
253 # reload command line parameters since these should take higher priority
254 # than both a settings file and environment variables
255 settings.load_from_dict(args)
257 configure_logging(settings.getValue('VERBOSITY'))
258 logger = logging.getLogger()
260 # configure trafficgens
262 if args['trafficgen']:
263 trafficgens = Loader().get_trafficgens()
264 if args['trafficgen'] not in trafficgens:
265 logging.error('There are no trafficgens matching \'%s\' found in'
266 ' \'%s\'. Exiting...', args['trafficgen'],
267 settings.getValue('TRAFFICGEN_DIR'))
271 # generate results directory name
272 date = datetime.datetime.fromtimestamp(time.time())
273 results_dir = "results_" + date.strftime('%Y-%m-%d_%H-%M-%S')
274 results_path = os.path.join(settings.getValue('LOG_DIR'), results_dir)
277 testcases = settings.getValue('PERFORMANCE_TESTS')
279 for cfg in testcases:
281 all_tests.append(TestCase(cfg, results_path))
282 except (Exception) as _:
283 logger.exception("Failed to create test: %s",
284 cfg.get('Name', '<Name not set>'))
287 # TODO(BOM) Apply filter to select requested tests
288 all_tests = apply_filter(all_tests, args['tests'])
290 # if required, handle list-* operations
293 print("Available Tests:")
295 for test in all_tests:
296 print('* %-18s%s' % ('%s:' % test.name, test.desc))
299 if args['list_trafficgens']:
300 print(Loader().get_trafficgens_printable())
303 if args['list_collectors']:
304 print(Loader().get_collectors_printable())
307 if args['list_vswitches']:
308 print(Loader().get_vswitches_printable())
311 if args['list_settings']:
315 # create results directory
316 if not os.path.exists(results_dir):
317 logger.info("Creating result directory: " + results_path)
318 os.makedirs(results_path)
321 for test in all_tests:
324 #pylint: disable=broad-except
325 except (Exception) as _:
326 logger.exception("Failed to run test: %s", test.name)
327 logger.info("Continuing with next test...")
329 #remove directory if no result files were created.
330 if os.path.exists(results_path):
331 if os.listdir(results_path) == []:
332 shutil.rmtree(results_path)
334 if __name__ == "__main__":