Files
galaxy/scripts/tests_markdown.py
T
Nicola Soranzo 90a729e431 New pyupgrade fixes
Found by running `ack --type=python -f | grep -v '^tools/' | xargs pyupgrade --py36-plus`
2021-10-24 12:10:34 +01:00

307 lines
10 KiB
Python

#!/usr/bin/env python
import argparse
import json
import os
import statistics
import sys
import jinja2
from mir import html_report
DESCRIPTION = "Script to generate (potentially merged) HTML summary of Galaxy Test Performance"
templateLoader = jinja2.FileSystemLoader(searchpath="./scripts")
template_env = jinja2.Environment(loader=templateLoader)
TEMPLATE_FILE = "tests_markdown.tpl"
TEMPLATE_COMPARE_FILE = "tests_markdown_compare.tpl"
LINKS = [
{"href": "https://github.com/galaxyproject/galaxy", "title": "Galaxy"}
]
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
args = _parser().parse_args(argv)
common_env = {
"include_raw_metrics": args.include_raw_metrics,
}
if len(args.input_path) == 1:
markdown_output = _summarize_one(args.input_path[0], common_env)
else:
markdown_output = _summarize_many(args.input_path, common_env)
html_output = html_report(markdown_output, args.title, links=LINKS)
with open(args.output_path, "w") as f:
f.write(html_output)
def _summarize_one(input_path, common_env):
environment = _prepare_raw_data(input_path)
environment.update(common_env)
template = template_env.get_template(TEMPLATE_FILE)
output = template.render(environment)
return output
def _summarize_many(input_paths, common_env):
raw_data_dicts = []
for arg in input_paths:
raw_data_dicts.append(_prepare_raw_data(arg))
environment = _merge_summarizes(raw_data_dicts)
environment.update(common_env)
template = template_env.get_template(TEMPLATE_COMPARE_FILE)
output = template.render(environment)
return output
def _empty_statistics():
return {"count": 0, "sum": "n/a", "median": "n/a", "stdev": "n/a", "mean": "n/a"}
def _merge_summarizes(raw_data_dicts):
tests = {}
api_endpoints = {}
internals = {}
all_tests = set()
all_api_endpoints = set()
all_internal_metrics = set()
all_labels = set()
for raw_data_dict in raw_data_dicts:
these_api_endpoints = raw_data_dict['raw_data']['api_endpoint_metrics']
for api_endpoint in these_api_endpoints.keys():
all_api_endpoints.add(api_endpoint)
these_internal_endpoints = raw_data_dict['raw_data']['internals_metrics']
for internal_endpoint in these_internal_endpoints.keys():
all_internal_metrics.add(internal_endpoint)
these_tests = raw_data_dict['raw_data']['tests']
for test in these_tests:
all_tests.add(test['nodeid'])
all_labels.add(raw_data_dict["label"])
for label in all_labels:
for api_endpoint in all_api_endpoints:
_ensure_has_dict_at_key(api_endpoints, api_endpoint,
total_time={},
sql_time={},
sql_queries={},
)
api_endpoints[api_endpoint]["total_time"][label] = _empty_statistics()
api_endpoints[api_endpoint]["sql_time"][label] = _empty_statistics()
for internal_endpoint in all_internal_metrics:
_ensure_has_dict_at_key(internals, internal_endpoint,
total_time={},
)
internals[internal_endpoint]["total_time"][label] = _empty_statistics()
for test in all_tests:
if test not in tests:
tests[test] = {}
tests[test][label] = {"outcome": "absent"}
for raw_data_dict in raw_data_dicts:
ab_label = raw_data_dict['label']
these_api_endpoints = raw_data_dict['raw_data']['api_endpoint_metrics']
these_internals = raw_data_dict['raw_data']['internals_metrics']
these_tests = raw_data_dict['raw_data']['tests']
for api_endpoint, endpoint_metrics in these_api_endpoints.items():
api_endpoints[api_endpoint]["label"] = endpoint_metrics["label"]
api_endpoints[api_endpoint]["total_time"][ab_label].update(endpoint_metrics["total_time"])
api_endpoints[api_endpoint]["sql_time"][ab_label].update(endpoint_metrics["sql_time"])
for endpoint, endpoint_metrics in these_internals.items():
internals[endpoint]["label"] = endpoint_metrics["label"]
internals[endpoint]['total_time'][ab_label].update(endpoint_metrics["total_time"])
for test in these_tests:
tests[test["nodeid"]][ab_label]["outcome"] = test.get('outcome')
return {"raw_data": {
"api_endpoint_metrics": api_endpoints,
"internals_metrics": internals,
"tests": tests,
}}
def _prepare_raw_data(path):
with open(path) as f:
inp_dict = json.load(f)
environment = dict(raw_data=inp_dict, label=os.path.basename(path))
__inject_summary(environment)
__inject_api_timing_summary_environment(environment)
__inject_api_timing_summary_across_tests(environment)
__inject_raw_timings(environment)
return environment
def __inject_api_timing_summary_environment(environment):
for test in environment['raw_data']['tests']:
if 'metadata' not in test:
continue
if 'local_metrics' in test['metadata']:
__inject_api_timing_summary_test(test)
def __inject_api_timing_summary_across_tests(environment):
api_endpoints = {}
internals = {}
for test in environment['raw_data']['tests']:
if 'metadata' not in test:
continue
test_endpoints = test['api_endpoint_metrics']
for api_endpoint, endpoint_metrics in test_endpoints.items():
_ensure_has_dict_at_key(api_endpoints, api_endpoint,
total_time={'raw': []},
sql_time={'raw': []},
sql_queries={'raw': []},
)
api_endpoints[api_endpoint]['label'] = endpoint_metrics['label']
api_endpoints[api_endpoint]['total_time']['raw'].extend(endpoint_metrics['total_time']['raw'])
api_endpoints[api_endpoint]['sql_time']['raw'].extend(endpoint_metrics['sql_time']['raw'])
api_endpoints[api_endpoint]['sql_queries']['raw'].extend(endpoint_metrics['sql_queries']['raw'])
test_endpoints = test['internals_metrics']
for api_endpoint, endpoint_metrics in test_endpoints.items():
_ensure_has_dict_at_key(internals, api_endpoint,
total_time={'raw': []},
)
internals[api_endpoint]['label'] = endpoint_metrics['label']
internals[api_endpoint]['total_time']['raw'].extend(endpoint_metrics['total_time']['raw'])
for endpoint_metrics in api_endpoints.values():
__inject_statistics(endpoint_metrics['total_time'])
__inject_statistics(endpoint_metrics['sql_time'])
__inject_statistics(endpoint_metrics['sql_queries'])
for endpoint_metrics in internals.values():
__inject_statistics(endpoint_metrics['total_time'])
environment['raw_data']['api_endpoint_metrics'] = api_endpoints
environment['raw_data']['internals_metrics'] = internals
def __inject_raw_timings(environment):
all_timings = []
for test in environment['raw_data']['tests']:
if 'metadata' not in test or 'local_metrics' not in test['metadata']:
continue
metrics = test['metadata']['local_metrics']
timing = metrics['timing']
for endpoint, timings in timing.items():
recording = timings[0].copy()
recording['endpoint'] = endpoint
all_timings.append(recording)
environment['raw_data']['all_timings'] = all_timings
def _ensure_has_dict_at_key(the_dict, key, **kwd):
if key not in the_dict:
the_dict[key] = dict(**kwd)
def __inject_api_timing_summary_test(test):
metrics = test['metadata']['local_metrics']
timing = metrics['timing']
counter = metrics['counter']
api_endpoints = {}
internal_timings = {}
def summarize_times(timings):
times = list(map(lambda t: t['time'], timings))
return __inject_statistics({
'raw': times,
})
def summarize_counter(c):
counters = list(map(lambda t: t['n'], c))
return __inject_statistics({
'raw': counters,
})
for endpoint, timings in timing.items():
if not endpoint.startswith("api"):
continue
endpoint_summary = {
'total_time': summarize_times(timings),
'label': endpoint[len("api."):]
}
sql_times = "sql.%s" % endpoint
if sql_times in timing:
endpoint_summary['sql_time'] = summarize_times(timing[sql_times])
sql_queries = "sqlqueries.%s" % endpoint
if sql_queries in counter:
endpoint_summary['sql_queries'] = summarize_counter(counter[sql_queries])
api_endpoints[endpoint] = endpoint_summary
for endpoint, timings in timing.items():
if not endpoint.startswith("internals"):
continue
internal_summary = {
'total_time': summarize_times(timings),
'label': endpoint[len("internals."):]
}
internal_timings[endpoint] = internal_summary
test['api_endpoint_metrics'] = api_endpoints
test['internals_metrics'] = internal_timings
def __inject_statistics(from_dict):
raw_values = from_dict['raw']
from_dict['sum'] = sum(raw_values)
from_dict['median'] = f"{statistics.median(raw_values):.2f}"
if len(raw_values) > 1:
from_dict['stdev'] = f"{statistics.stdev(raw_values):.4f}"
else:
from_dict['stdev'] = "n/a"
from_dict['mean'] = f"{statistics.mean(raw_values):.2f}"
from_dict['count'] = len(raw_values)
return from_dict
def __inject_summary(environment):
total = 0
failures = 0
skips = 0
for test in environment['raw_data']['tests']:
total += 1
status = test.get('outcome')
if status == 'failed':
failures += 1
elif status == 'skipped':
skips += 1
environment['raw_data']['results'] = {
'total': total,
'failures': failures,
'skips': skips,
}
def _parser():
parser = argparse.ArgumentParser(description=DESCRIPTION)
parser.add_argument('input_path', metavar='INPUT', type=str, nargs="+",
help='structured input path (.json)')
parser.add_argument('--output_path', type=str, default="test.html",
help='output path (.html)')
parser.add_argument('--title', type=str, default="Test Performance Summary",
help='Performance Test Results')
parser.add_argument('--include_raw_metrics', action="store_true", default=False)
return parser
if __name__ == "__main__":
main()