-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathupdate_report.py
More file actions
154 lines (121 loc) · 4.62 KB
/
Copy pathupdate_report.py
File metadata and controls
154 lines (121 loc) · 4.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#!/usr/bin/env python3
"""
update_report.py — Automatically update index.html with the latest benchmark results.
Reads all results/results_*.json files and rewrites the DATA block in index.html.
Usage:
python update_report.py
"""
import json
import re
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent
RESULTS_DIR = SCRIPT_DIR / "results"
REPORT_PATH = SCRIPT_DIR / "index.html"
# Map JSON operation names → DATA keys
OP_MAP = {
"read_turtle": "read_turtle",
"write_turtle": "write_turtle",
"read_ntriples": "read_ntriples",
"write_ntriples": "write_ntriples",
"query_q1_count": "q1",
"query_q2_customer_orders": "q2",
"query_q3_join_3_entities": "q3",
"query_q4_optional_aggregation": "q4",
"query_q5_construct": "q5",
"query_q6_delete_insert": "q6",
"query_q1_count_cold": "q1_cold",
"query_q2_customer_orders_cold": "q2_cold",
"query_q3_join_3_entities_cold": "q3_cold",
"query_q4_optional_aggregation_cold": "q4_cold",
"query_q5_construct_cold": "q5_cold",
"query_q6_delete_insert_cold": "q6_cold",
}
# Frameworks to include (skip kolibrie)
FRAMEWORKS = [
"maplib", "oxigraph", "rdflib", "jena", "rdf4j",
"qlever", "virtuoso", "maplib_disk", "graphdb",
"dotnetrdf", "neo4j",
]
SCALES = ["medium", "large", "xlarge"]
OPS = ["read_turtle", "write_turtle", "read_ntriples", "write_ntriples", "q1", "q2", "q3", "q4", "q5", "q6", "q1_cold", "q2_cold", "q3_cold", "q4_cold", "q5_cold", "q6_cold"]
def load_results():
"""Load all result JSON files into a nested dict: {framework: {scale: {op: value}}}."""
data = {}
for path in sorted(RESULTS_DIR.glob("results_*.json")):
with open(path) as f:
entries = json.load(f)
for entry in entries:
fw = entry["framework"]
if fw not in FRAMEWORKS:
continue
scale = entry["scale"]
op = OP_MAP.get(entry["operation"])
if op is None:
continue
val = entry["seconds"]
if val in ("N/A", "TIMEOUT", "ERROR") or val is None:
val = None
else:
try:
val = round(float(val), 5)
except (ValueError, TypeError):
val = None
data.setdefault(fw, {}).setdefault(scale, {})[op] = val
return data
def format_value(v):
"""Format a single value for JS."""
if v is None:
return "null"
return str(v)
def build_data_block(data):
"""Build the JS DATA object string."""
lines = ["const DATA = {"]
for fw in FRAMEWORKS:
if fw not in data:
continue
lines.append(f" {fw}: {{")
for scale in SCALES:
if scale not in data.get(fw, {}):
# Write all nulls if scale is missing
vals = {op: None for op in OPS}
else:
vals = data[fw][scale]
io_parts = [f"{op}: {format_value(vals.get(op))}" for op in OPS[:4]]
q_parts = [f"{op}: {format_value(vals.get(op))}" for op in OPS[4:10]]
cold_parts = [f"{op}: {format_value(vals.get(op))}" for op in OPS[10:]]
lines.append(f" {scale}: {{")
lines.append(f" {', '.join(io_parts)},")
lines.append(f" {', '.join(q_parts)},")
lines.append(f" {', '.join(cold_parts)}")
lines.append(f" }},")
lines.append(f" }},")
lines.append("};")
return "\n".join(lines)
def update_report(data):
"""Replace the DATA block in index.html."""
html = REPORT_PATH.read_text()
# Match from 'const DATA = {' to the closing '};'
pattern = r"const DATA = \{.*?\};\s*\n"
match = re.search(pattern, html, re.DOTALL)
if not match:
raise ValueError("Could not find 'const DATA = { ... };' in index.html")
new_block = build_data_block(data) + "\n"
html = html[:match.start()] + new_block + html[match.end():]
REPORT_PATH.write_text(html)
return match.start(), match.end()
def main():
result_files = list(RESULTS_DIR.glob("results_*.json"))
print(f"Found {len(result_files)} result files in {RESULTS_DIR}/")
for f in sorted(result_files):
print(f" {f.name}")
data = load_results()
print(f"\nLoaded data for {len(data)} frameworks: {', '.join(data.keys())}")
for fw in FRAMEWORKS:
if fw in data:
scales = list(data[fw].keys())
print(f" {fw}: {', '.join(scales)}")
start, end = update_report(data)
print(f"\nUpdated DATA block in {REPORT_PATH.name} (chars {start}-{end})")
print("Done!")
if __name__ == "__main__":
main()