constellation/.github/actions/e2e_kbench/evaluate/graph.py
Christoph Meyer d612ed2cae AB#2530 CI benchmarks compare to previous and generate graphs
- Get the previous benchmark results from artifact store S3 bucket
- Compare the current benchmark to the previous results
- Attach markdown table comparing results to the workflow output
- Update benchmarks in bucket if running on main
- Generate graphs from comparison
- Document continous benchmarking
2022-11-11 18:37:35 +01:00

195 lines
6.2 KiB
Python

"""Generate graphs comparing K-Bench benchmarks across cloud providers and Constellation."""
import json
import os
from collections import defaultdict
import numpy as np
from matplotlib import pyplot as plt
SUBJECTS = [
'constellation-azure',
'AKS',
'constellation-gcp',
'GKE',
]
LEGEND_NAMES = [
'Constellation on Azure',
'AKS',
'Constellation on GCP',
'GKE',
]
BAR_COLORS = ['#90FF99', '#929292', '#8B04DD', '#000000']
# Rotate bar labels by X degrees
LABEL_ROTATE_BY = 30
LABEL_FONTSIZE = 9
# Some lookup dictionaries for x axis
api_suffix = 'ms'
pod_key2header = {
'pod_create': 'Pod Create',
'pod_list': 'Pod List',
'pod_get': 'Pod Get',
'pod_update': 'Pod Update',
'pod_delete': 'Pod Delete',
}
svc_key2header = {
'svc_create': 'Service Create',
'svc_list': 'Service List',
'svc_update': 'Service Update',
'svc_delete': 'Service Delete',
'svc_get': 'Service Get',
}
depl_key2header = {
'depl_create': 'Deployment Create',
'depl_list': 'Deployment List',
'depl_update': 'Deployment Update',
'depl_scale': 'Deployment Scale',
'depl_delete': 'Deployment Delete',
}
fio_suffix = 'MiB/s'
fio_key2header = {
'fio_root_async_R70W30_R': 'async_R70W30 mix,\n seq. reads',
'fio_root_async_R70W30_W': 'async_R70W30 mix,\n seq. writes',
'fio_root_async_R100W0_R': 'async_R100W0 mix,\n seq. reads',
'fio_root_async_R0W100_W': 'async_R0W100 mix,\n seq. writes',
}
net_suffix = 'Mbit/s'
net_key2header = {
'net_internode_snd': 'iperf internode \n send ({net_suffix})'.format(net_suffix=net_suffix),
'net_intranode_snd': 'iperf intranode \n send ({net_suffix})'.format(net_suffix=net_suffix),
}
def configure() -> str:
"""Read the benchmark data paths.
Expects ENV vars (required):
- BDIR=benchmarks
Raises TypeError if at least one of them is missing.
Returns: out_dir
"""
out_dir = os.environ.get('BDIR', None)
if not out_dir:
raise TypeError(
'ENV variables BDIR is required.')
return out_dir
def bar_chart(data, headers, title='', suffix='', val_label=True, y_log=False):
"""Draws a bar chart with multiple bars per data point.
Args:
data (dict[str, list]): Benchmark data dictionary: subject -> lists of value points
headers (list): List of headers (x-axis).
title (str, optional): The title for the chart. Defaults to "".
suffix (str, optional): The suffix for values e.g. "MiB/s". Defaults to "".
val_label (bool, optional): Put a label of the value over the bar chart. Defaults to True.
y_log (bool, optional): Set the y-axis to a logarithmic scale. Defaults to False.
Returns:
fig (matplotlib.pyplot.figure): The pyplot figure
"""
fig, ax = plt.subplots(figsize=(10, 5))
fig.patch.set_facecolor('white')
# Number of bars per group
n_bars = len(data)
# The width of a single bar
bar_width = 0.8 / n_bars
# List containing handles for the drawn bars, used for the legend
bars = []
# Iterate over all data
for i, values in enumerate(data.values()):
# The offset in x direction of that bar
x_offset = (i - n_bars / 2) * bar_width + bar_width / 2
# Draw a bar for every value of that type
for x, y in enumerate(values):
bar = ax.bar(x + x_offset, y, width=bar_width * 0.9,
color=BAR_COLORS[i % len(BAR_COLORS)], edgecolor='black')
if val_label:
ax.bar_label(bar, padding=1,
fmt='%g {suffix}'.format(suffix=suffix))
# Add a handle to the last drawn bar, which we'll need for the legend
bars.append(bar[0])
# Draw legend
ax.legend(bars, LEGEND_NAMES)
if y_log:
ax.set_yscale('log')
ax.set_xticks(np.arange(len(headers)))
ax.set_xticklabels(headers)
plt.setp(ax.get_xticklabels(), fontsize=LABEL_FONTSIZE,
rotation=LABEL_ROTATE_BY)
plt.title('{title} ({suffix})'.format(title=title, suffix=suffix))
plt.tight_layout()
return fig
def main():
"""Read the files and create diagrams."""
out_dir = configure()
combined_results = defaultdict(dict)
for test in SUBJECTS:
# Read the previous results
read_path = os.path.join(
out_dir, '{subject}.json'.format(subject=test))
try:
with open(read_path, 'r') as res_file:
combined_results[test].update(json.load(res_file))
except OSError as e:
raise ValueError(
'Failed reading {subject} benchmark records: {e}'.format(subject=test, e=e))
# Combine the evaluation of the Kubernetes API benchmarks
for i, api in enumerate([pod_key2header, svc_key2header, depl_key2header]):
api_data = {}
for s in SUBJECTS:
points = combined_results[s]['kbench']
subject_data = [points[h] for h in api]
api_data[s] = subject_data
hdrs = list(api.values())
bar_chart(data=api_data, headers=hdrs,
title='API Latency', suffix=api_suffix, y_log=True)
save_name = os.path.join(out_dir, 'api_{i}_perf.png'.format(i=i))
plt.savefig(save_name, bbox_inches='tight')
# Network chart
net_data = {}
for s in SUBJECTS:
points = combined_results[s]['kbench']
subject_data = [points[h] for h in net_key2header]
net_data[s] = subject_data
hdrs = list(net_key2header.values())
bar_chart(data=net_data, headers=hdrs,
title='Network Throughput', suffix=net_suffix, y_log=True)
save_name = os.path.join(out_dir, 'net_perf.png')
plt.savefig(save_name, bbox_inches='tight')
# fio chart
fio_data = {}
for s in SUBJECTS:
points = combined_results[s]['kbench']
subject_data = [points[h] for h in fio_key2header]
fio_data[s] = subject_data
hdrs = list(fio_key2header.values())
bar_chart(data=fio_data, headers=hdrs,
title='Storage Throughput', suffix=fio_suffix, y_log=True)
save_name = os.path.join(out_dir, 'storage_perf.png')
plt.savefig(save_name, bbox_inches='tight')
if __name__ == '__main__':
main()