mirror of https://github.com/istio/tools.git
197 lines
6.4 KiB
Python
Executable File
197 lines
6.4 KiB
Python
Executable File
# Copyright Istio 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.
|
|
|
|
import sys
|
|
import argparse
|
|
import pandas as pd
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
metric_dict = {"cpu-client": "cpu_mili_avg_istio_proxy_fortioclient",
|
|
"cpu-server": "cpu_mili_avg_istio_proxy_fortioserver",
|
|
"mem-client": "mem_Mi_avg_istio_proxy_fortioclient",
|
|
"mem-server": "mem_Mi_avg_istio_proxy_fortioserver"}
|
|
|
|
|
|
def plotter(args):
|
|
check_if_args_provided(args)
|
|
|
|
df = pd.read_csv(args.csv_filepath)
|
|
telemetry_modes_y_data = {}
|
|
metric_name = get_metric_name(args)
|
|
constructed_query_str = get_constructed_query_str(args)
|
|
|
|
for telemetry_mode in args.telemetry_modes:
|
|
telemetry_modes_y_data[telemetry_mode] = get_data_helper(df, args.query_list, constructed_query_str,
|
|
telemetry_mode, metric_name)
|
|
|
|
dpi = 100
|
|
plt.figure(figsize=(1138 / dpi, 871 / dpi), dpi=dpi)
|
|
fig = plt.figure(figsize=(1138 / dpi, 871 / dpi), dpi=dpi)
|
|
ax = fig.add_subplot(111)
|
|
ax.set_ylim(0, 1.0)
|
|
for key, val in telemetry_modes_y_data.items():
|
|
plot_key = key
|
|
match key:
|
|
case "no_istio_mtls_no_istio":
|
|
plot_key = "no_istio"
|
|
case "istio_with_stats_both":
|
|
plot_key = "istio_with_stats"
|
|
plt.plot(args.query_list, val, marker='o', label=plot_key)
|
|
for i, j in zip(args.query_list, val):
|
|
ax.annotate(str(j), xy=(i, j))
|
|
print("i=%x,j=%x,args.querylist=%x,val=%x,key=%x", i, j, args.query_list, val, key)
|
|
|
|
plt.xlabel(get_x_label(args))
|
|
plt.ylabel(get_y_label(args))
|
|
plt.legend()
|
|
plt.grid()
|
|
plt.savefig(args.graph_title, dpi=dpi)
|
|
plt.show()
|
|
|
|
|
|
# Helpers
|
|
def check_if_args_provided(args):
|
|
args_all_provided = True
|
|
# print(vars(args))
|
|
for _, val in vars(args).items():
|
|
if val == "":
|
|
print("Warning: There is at least one argument that you did not specify with a value.\n")
|
|
args_all_provided = False
|
|
if not check_args_consistency(args):
|
|
args_all_provided = False
|
|
if not args_all_provided:
|
|
sys.exit(-1)
|
|
|
|
|
|
def check_args_consistency(args):
|
|
if args.x_axis == "conn" and not args.query_str.startswith("ActualQPS=="):
|
|
print("Warning: your specified query_str does not match with the x_axis definition.")
|
|
return False
|
|
if args.x_axis == "qps" and not args.query_str.startswith("NumThreads=="):
|
|
print("Warning: your specified query_str does not match with the x_axis definition.")
|
|
return False
|
|
return True
|
|
|
|
|
|
def get_constructed_query_str(args):
|
|
if args.x_axis == "qps":
|
|
return 'ActualQPS==@ql and ' + args.query_str + ' and Labels.str.endswith(@telemetry_mode)'
|
|
elif args.x_axis == "conn":
|
|
return 'NumThreads==@ql and Labels.str.endswith(@telemetry_mode)'
|
|
return ""
|
|
|
|
|
|
def get_metric_name(args):
|
|
if args.graph_type.startswith("latency"):
|
|
return args.graph_type.split("-")[1]
|
|
return metric_dict[args.graph_type]
|
|
|
|
|
|
def get_data_helper(df, query_list, query_str, telemetry_mode, metric_name):
|
|
y_series_data = []
|
|
|
|
for ql in query_list:
|
|
data = df.query(query_str)
|
|
try:
|
|
data[metric_name].head().empty
|
|
except KeyError as e:
|
|
y_series_data.append(None)
|
|
else:
|
|
if not data[metric_name].head().empty:
|
|
if metric_name.startswith('cpu') or metric_name.startswith('mem'):
|
|
y_series_data.append(data[metric_name].head(1).values[0])
|
|
else:
|
|
y_series_data.append(data[metric_name].head(1).values[0] / data["ActualQPS"].head(1).values[0])
|
|
else:
|
|
y_series_data.append(None)
|
|
|
|
return y_series_data
|
|
|
|
|
|
def get_x_label(args):
|
|
if args.x_axis == "qps":
|
|
return "QPS"
|
|
if args.x_axis == "conn":
|
|
return "Connections"
|
|
return ""
|
|
|
|
|
|
def get_y_label(args):
|
|
if args.graph_type.startswith("latency"):
|
|
return 'Latency in milliseconds'
|
|
if args.graph_type.startswith("cpu"):
|
|
return 'istio-proxy average CPUs (milliseconds)'
|
|
if args.graph_type.startswith("mem"):
|
|
return "istio-proxy average Memory (Mi)"
|
|
return ""
|
|
|
|
|
|
def int_list(lst):
|
|
return [int(i) for i in lst.split(",")]
|
|
|
|
|
|
def string_list(lst):
|
|
return [str(i) for i in lst.split(",")]
|
|
|
|
|
|
def get_parser():
|
|
parser = argparse.ArgumentParser(
|
|
"Istio performance benchmark CSV file graph plotter.")
|
|
parser.add_argument(
|
|
"--graph_type",
|
|
help="Choose from one of them: [latency-p50, latency-p90, latency-p99, latency-p999, "
|
|
"cpu-client, cpu-server, mem-client, mem-server]."
|
|
)
|
|
parser.add_argument(
|
|
"--x_axis",
|
|
help="Either qps or conn.",
|
|
)
|
|
parser.add_argument(
|
|
"--telemetry_modes",
|
|
help="This is a list of perf test labels, currently it can be any combinations from the follow supported modes:"
|
|
"[no_istio, none_mtls_both, v2-sd-full-nullvm_both, istio_with_stats, "
|
|
"v2-stats-wasm_both, v2-sd-nologging-nullvm_both].",
|
|
type=string_list
|
|
)
|
|
parser.add_argument(
|
|
"--query_list",
|
|
help="Specify the qps or conn range you want to plot based on the CSV file."
|
|
"For example, conn_query_list=[2, 4, 8, 16, 32, 64], qps_query_list=[10, 100, 200, 400, 800, 1000].",
|
|
type=int_list
|
|
)
|
|
parser.add_argument(
|
|
"--query_str",
|
|
help="Specify the qps or conn query_str that will be used to query your y-axis data based on the CSV file."
|
|
"For example: conn_query_str=ActualQPS==1000, qps_query_str=NumThreads==16."
|
|
)
|
|
parser.add_argument(
|
|
"--csv_filepath",
|
|
help="The path of the CSV file."
|
|
)
|
|
parser.add_argument(
|
|
"--graph_title",
|
|
help="The graph title."
|
|
)
|
|
return parser
|
|
|
|
|
|
def main(argv):
|
|
args = get_parser().parse_args(argv)
|
|
return plotter(args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv[1:]))
|