Skip to content

Commit f6fdb39

Browse files
authored
add new scripts to analyse results (#36)
* add new scripts to analyze results * restore visualize.py * add script to plot QPS vs Recall * Fix bug
1 parent 0cbb0fd commit f6fdb39

File tree

3 files changed

+185
-14
lines changed

3 files changed

+185
-14
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ results
66
output.png
77
.venv
88
.env
9+
.ipynb_checkpoints
10+
*.mdx
11+
*.png

benchmarker/scripts/python/collate-results.py

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,97 @@
44
import os
55
import glob
66
import json
7+
from typing import List, Dict, Any
78

8-
def collate_results(dataset, results_directory):
9-
"""Collate results from the results directory into a markdown table."""
10-
json_files_pattern = os.path.join(results_directory, "*.json")
119

10+
INSTANCE_TYPE = 'n4-highmem-16'
11+
RUN = "hnsw"
12+
IGNORE_FIRST_TEST = True
13+
EF_VALS = [64]
14+
15+
def get_all_data_as_dict(results_directory: str) -> List[Dict[str,Any]]:
1216
data = []
1317

14-
for file_path in glob.glob(json_files_pattern):
18+
for file_path in glob.glob(results_directory + "/*.json"):
1519
with open(file_path, 'r') as file:
16-
file_data = json.load(file)
20+
file_data = json.load(file)[(IGNORE_FIRST_TEST == True):]
1721
data.extend(file_data)
1822

19-
filtered_data = [entry for entry in data if entry['dataset_file'] == dataset]
20-
sorted_data = sorted(filtered_data, key=lambda x: x['qps'], reverse=True)
23+
return data
24+
25+
26+
def filter_data(data: List[Dict[str,Any]], dataset: str, limit: int, sorted_by: str="qps") -> List[Dict[str,Any]]:
27+
def _filter_data(item: Dict) -> bool:
28+
if (
29+
item['dataset_file'] == dataset
30+
and item['limit'] == limit
31+
and item['instance_type'] == INSTANCE_TYPE
32+
and item['run'] == RUN
33+
and item['ef'] in EF_VALS
34+
):
35+
return True
36+
return False
37+
38+
return sorted(
39+
[entry for entry in data if _filter_data(entry)],
40+
key=lambda x: x[sorted_by],
41+
reverse=True,
42+
)
43+
44+
45+
def collate_results(data: List[Dict[str,Any]], out=None):
46+
"""Collate results from the results directory into a markdown table."""
2147

22-
print("""| efConstruction | maxConnections | ef | **Recall** | **QPS** | Mean Latency | p99 Latency | Import time |
23-
| ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |""")
24-
for entry in sorted_data:
48+
print("| efConstruction | maxConnections | ef | **Recall** | **QPS** | Mean Latency | p99 Latency | Import time |", file=out)
49+
print("| ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |", file=out)
50+
for entry in data:
2551
latencyms = "{:.2f}ms".format(entry['meanLatency'] * 1000)
2652
p99ms = "{:.2f}ms".format(entry['p99Latency'] * 1000)
2753
recallfmt = "{:.2f}%".format(entry['recall'] * 100)
28-
importtimefmt = "{:.2f}s".format(entry['importTime'])
54+
importtimefmt = "{:.0f}s".format(entry['importTime'])
2955
qpsfmt = "{:.0f}".format(entry['qps'])
30-
print(f"| {entry['efConstruction']} | {entry['maxConnections']} | {entry['ef']} | **{recallfmt}** | **{qpsfmt}** | {latencyms} | {p99ms} | {importtimefmt} |")
56+
print(f"| {entry['efConstruction']} | {entry['maxConnections']} | {entry['ef']} | **{recallfmt}** | **{qpsfmt}** | {latencyms} | {p99ms} | {importtimefmt} |", file=out)
57+
58+
59+
def weaviate_io_results(results_directory: str):
60+
61+
data = get_all_data_as_dict(results_directory)
62+
datasets = set()
63+
limits = set()
64+
65+
for item in data:
66+
datasets.add(item["dataset_file"])
67+
limits.add(item["limit"])
68+
69+
for dataset in datasets:
70+
with open(f"ann-{dataset.replace(".hdf5", ".mdx")}", mode="w") as file:
71+
print("import Tabs from '@theme/Tabs';", file=file)
72+
print("import TabItem from '@theme/TabItem';\n", file=file)
73+
print('<Tabs groupId="limits">', file=file)
74+
for limit in limits:
75+
print(f'<TabItem value="{limit}" label="Limit {limit}">\n', file=file)
76+
collate_results(
77+
data=filter_data(data, dataset, limit),
78+
out=file,
79+
)
80+
print('\n</TabItem>', file=file)
81+
print('</Tabs>', file=file)
82+
3183

3284
if __name__ == "__main__":
3385
parser = argparse.ArgumentParser(description="Collate ann results into markdown tables.")
34-
parser.add_argument('-d', '--dataset', required=True, help="The dataset file to filter by.")
86+
parser.add_argument('-d', '--dataset', default="all", type=str, help="The dataset file to filter by. If the value is 'all' or not specified, it will be computed for all datasets.")
3587
parser.add_argument('-r', '--results', default="./results", help="The directory containing benchmark results")
88+
parser.add_argument('-l', '--limit', default=10, type=int, help="The number of results returned by the ANN to fiter by")
3689
args = parser.parse_args()
3790

38-
collate_results(args.dataset, os.path.expanduser(args.results))
91+
if args.dataset != "all":
92+
filtered_data = filter_data(
93+
data=get_all_data_as_dict(args.results),
94+
dataset=args.dataset,
95+
limit=args.limit,
96+
)
97+
98+
collate_results(filtered_data)
99+
else:
100+
weaviate_io_results(args.results)
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import glob
5+
import json
6+
import argparse
7+
import seaborn as sns
8+
import matplotlib.ticker as tkr
9+
import matplotlib.pyplot as plt
10+
import pandas as pd
11+
12+
EF_CONSTRUCTION = 256
13+
MAX_CONNECTIONS = 32
14+
RUNS = {
15+
"hnsw",
16+
}
17+
18+
19+
def custom_filter(row):
20+
# All ef values that are lowern tha the `limit` are set to the `limit`
21+
if row['limit'] == 100 and row['ef'] < 90:
22+
return False
23+
return True
24+
25+
26+
def get_datapoints(dataset:str, path: str):
27+
datapoints = []
28+
for filename in glob.glob(os.path.join(path, "*.json")):
29+
with open(os.path.join(os.getcwd(), filename), "r") as f:
30+
parsed = json.loads(f.read())
31+
datapoints += parsed[1:]
32+
df = pd.DataFrame(datapoints)
33+
return df[
34+
(df["dataset_file"] == dataset) # filter for a specific dataset
35+
& (df['run'].isin(RUNS)) # remove PQ/BQ/SQ results
36+
& (df["maxConnections"] == MAX_CONNECTIONS)
37+
& (df["efConstruction"] == EF_CONSTRUCTION)
38+
& (df.apply(custom_filter, axis=1))
39+
]
40+
41+
42+
def create_plot(results_df: pd.DataFrame):
43+
44+
dataset = results_df["dataset_file"].iloc[0]
45+
46+
sns.set_theme(
47+
style='whitegrid',
48+
font_scale=1.2,
49+
rc={
50+
# 'axes.grid': True,
51+
# 'savefig.transparent': True,
52+
# 'axes.facecolor': color,
53+
# 'figure.facecolor': color,
54+
# 'axes.edgecolor': color,
55+
# 'grid.color': color,
56+
# 'ytick.labelcolor': color,
57+
# 'xtick.labelcolor': color,
58+
}
59+
)
60+
plot = sns.relplot(
61+
linewidth=3,
62+
height=7,
63+
aspect=1.5,
64+
marker="o",
65+
dashes=False,
66+
data=results_df,
67+
kind="line",
68+
x="recall",
69+
y="qps",
70+
hue="limit",
71+
style="limit",
72+
palette=["b", "g"],
73+
)
74+
plot.set_axis_labels(
75+
x_var="Recall, [%]",
76+
y_var="QPS",
77+
)
78+
plot.figure.subplots_adjust(top=0.85)
79+
plot.figure.suptitle(
80+
f"Query Performance, {dataset}",
81+
weight="bold",
82+
83+
)
84+
sns.move_legend(
85+
plot,
86+
"lower center",
87+
bbox_to_anchor=(.5, .84),
88+
ncol=3,
89+
title="Limit: ",
90+
frameon=False,
91+
)
92+
93+
94+
plot.axes[0][0].get_xaxis().set_major_formatter(tkr.FuncFormatter(lambda x, _: f'{x*100:.0f}'))
95+
plot.axes[0][0].get_yaxis().set_major_formatter(tkr.StrMethodFormatter('{x:,.0f}'))
96+
plt.savefig(f"{dataset.split('.')[0]}.png", bbox_inches='tight')
97+
98+
if __name__ == "__main__":
99+
parser = argparse.ArgumentParser(description="Collate ann results into markdown tables.")
100+
parser.add_argument('-d', '--dataset', required=True, help="The dataset file to filter by")
101+
parser.add_argument('-r', '--results', default="./results", help="The directory containing benchmark results")
102+
args = parser.parse_args()
103+
104+
create_plot(
105+
get_datapoints(args.dataset, os.path.expanduser(args.results)),
106+
)

0 commit comments

Comments
 (0)