Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
2292c8f
`example-queries` from add-evaluation-web-app branch
tanmay-9 Jun 5, 2025
2f929c9
Half-done main page with ag-grid and `serve-evaluation-app` command
tanmay-9 Jun 5, 2025
3379a88
Pull updated `benchmark-queries` code from add-evaluation-web-app pr
tanmay-9 Jun 6, 2025
42762b6
First completed version of new eval web app
tanmay-9 Jun 8, 2025
e07a81a
Second completed version with comapre-exec-trees page and site-wide e…
tanmay-9 Jun 10, 2025
02d02ea
First fully-completed version with all the functionalities
tanmay-9 Jun 10, 2025
8f81d5b
Added CompareExecTreesBtn to details page and moved some things around
tanmay-9 Jun 12, 2025
3bd7ec0
Add updates and tests from add-evaluation-branch
tanmay-9 Jun 13, 2025
0a5ef5b
Add new pr changes to `benchmark-queries` from add-evaluation-web-app…
tanmay-9 Jul 4, 2025
803e6d9
Maintain column order in tables when data changes and suppress column…
tanmay-9 Jul 9, 2025
01b1ed9
Merge remote-tracking branch 'origin/main' into new-eval-app
tanmay-9 Jul 10, 2025
de569cf
Added penalized failed queries for aggregate metrics in web app
tanmay-9 Aug 6, 2025
cf14ca0
Have some metrics shown when all queries fail
tanmay-9 Aug 7, 2025
037a7c5
Use stdlib statistics for mean, median and geometric mean
tanmay-9 Aug 8, 2025
702a47f
Split query text into short and long query and add index description …
tanmay-9 Aug 13, 2025
58bf62a
Serve additional_data (penalty and per kb index description) to web app
tanmay-9 Aug 13, 2025
abe3e9e
Add index description info pill to main page
tanmay-9 Aug 13, 2025
71f94c2
Add sparql formatter for sparql queries
tanmay-9 Aug 13, 2025
4ec2587
Move page headers to nav-bar with resources
tanmay-9 Aug 13, 2025
389c32b
Split query into short and long query in details and comparison grid …
tanmay-9 Aug 13, 2025
28f3958
Add show metrics and order columns by metric options to comparison pa…
tanmay-9 Aug 13, 2025
3140ddb
Some leftover changes related to order columns and index description
tanmay-9 Aug 13, 2025
9b46cbf
Merge branch 'ad-freiburg:main' into new-eval-app
tanmay-9 Aug 13, 2025
2c98148
Merge branch 'ad-freiburg:main' into new-eval-app
tanmay-9 Aug 25, 2025
e7557a5
Change benchmark-queries command for the new --queries-yml format
tanmay-9 Aug 26, 2025
19cd871
Update serve-evaluation-app command for the new output yml format
tanmay-9 Aug 26, 2025
fe6a9f3
Update eval app for the new output yml format
tanmay-9 Aug 26, 2025
ac4f12a
Fix title bug when user visits the same page again
tanmay-9 Aug 28, 2025
f6e0cd9
Add help accordions to main and comparison pages
tanmay-9 Sep 2, 2025
12dc9b8
Fix SPARQL query missing some parts in tooltip bug
tanmay-9 Sep 6, 2025
6bbfe19
Maintain the user-defined column sizing on comparison table when vari…
tanmay-9 Sep 6, 2025
05b6fc5
Add Download as TSV buttons to main and comparison page tables
tanmay-9 Sep 6, 2025
8c7e87d
Fix copy button in secure context only bug
tanmay-9 Sep 6, 2025
78296dc
Add csvToLatex function for future
tanmay-9 Sep 6, 2025
2bf3844
Replace --error-penalty with a fixed gmeanTime2 and gmeanTime10 metrics
tanmay-9 Sep 9, 2025
5472968
Show failed and timeout queries explicitly on comparison page, improv…
tanmay-9 Sep 9, 2025
7391a57
FIx bugs introduced by introducing gmean2 and gmean10
tanmay-9 Sep 9, 2025
bbb12af
Add benchmark name and benchmark scale to present tables neatly on th…
tanmay-9 Sep 9, 2025
c3e45df
Make the sortedKbNames function more robust
tanmay-9 Sep 9, 2025
1f5760f
Update all tables to use domLayout normal
tanmay-9 Sep 12, 2025
95ef7d5
Add dark mode support
tanmay-9 Sep 22, 2025
55ecde4
Merge remote-tracking branch 'origin/main' into new-eval-app
tanmay-9 Sep 22, 2025
74e8309
Merge remote-tracking branch 'origin/main' into new-eval-app
Oct 9, 2025
4e186f4
Make the eval-app more responsive and look good on smartphones
tanmay-9 Oct 17, 2025
460cf0c
SPARQL Engine -> RDF Graph Database and Engine -> System
Oct 23, 2025
2b1ab9d
Merge branch 'new-eval-app' of github.com:tanmay-9/qlever-control int…
Oct 23, 2025
e976f56
Merge remote-tracking branch 'origin/main' into new-eval-app
Oct 23, 2025
3e4f0b4
More conflicts resolved
Oct 23, 2025
922a77d
Rename "Compare Results" to "Detailed Results per Query"
Oct 23, 2025
2d3e4ff
Fix column order on overview page and improve styling of compareExecT…
tanmay-9 Oct 23, 2025
8ef6314
Fix compareExecTrees zoom buttons not working because of icon change
tanmay-9 Oct 23, 2025
70ddd06
Update styling of overview page table cards and change compare exec t…
tanmay-9 Oct 23, 2025
3e9ef0d
Further UI enhancements
tanmay-9 Oct 23, 2025
4523257
Improve theme toggling code
tanmay-9 Oct 30, 2025
7b5e402
Merge branch 'qlever-dev:main' into new-eval-app
tanmay-9 Oct 30, 2025
7db6872
Merge branch 'new-eval-app' of https://github.com/tanmay-9/qlever-con…
tanmay-9 Oct 30, 2025
94cb777
Merge branch 'qlever-dev:main' into new-eval-app
tanmay-9 Nov 17, 2025
7ecee39
Fix overview page kg-header padding on smaller screens
tanmay-9 Nov 17, 2025
c067636
Remove old benchmark title and use benchmark name for benchmark-queri…
tanmay-9 Nov 17, 2025
36f101a
Update the Details page header to have the full benchmark name
tanmay-9 Nov 17, 2025
653e1d0
Modify index-stats command to return the computed index times and siz…
tanmay-9 Nov 19, 2025
f02b74e
Compute index stats inside benchmark-queries command and output to YA…
tanmay-9 Nov 19, 2025
4e80547
Send index stats to the web app
tanmay-9 Nov 23, 2025
497ed5d
Add index stats to main screen tables and have a space before the uni…
tanmay-9 Nov 23, 2025
b0d423b
Dummy dropdown to show/hide columns of main screen tables
tanmay-9 Nov 23, 2025
c08dd20
Merge branch 'qlever-dev:main' into new-eval-app
tanmay-9 Nov 26, 2025
82150e6
Add functionality to show/hide metrics on main screen tables
tanmay-9 Nov 26, 2025
2f9ae66
Make qlever index-stats more extensible for other engines
tanmay-9 Nov 29, 2025
4b69ef5
Add index stats to comparison page
tanmay-9 Dec 3, 2025
9973248
Merge remote-tracking branch 'origin/main' into new-eval-app
tanmay-9 Jan 29, 2026
6b7ef04
Merge branch 'qlever-dev:main' into new-eval-app
tanmay-9 Feb 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 159 additions & 57 deletions src/qlever/commands/benchmark_queries.py

Large diffs are not rendered by default.

232 changes: 151 additions & 81 deletions src/qlever/commands/index_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@ def additional_arguments(self, subparser) -> None:
help="The size unit",
)

def execute_time(self, args, log_file_name) -> bool:
def execute_time(
self, args, log_file_name: str
) -> dict[str, tuple[float | None, str]]:
"""
Part of `execute` that shows the time used.
Part of `execute` that returns the time used for each part of indexing
along with the unit.
"""

# Read the content of `log_file_name` into a list of lines.
Expand All @@ -69,19 +72,20 @@ def execute_time(self, args, log_file_name) -> bool:
lines = log_file.readlines()
except Exception as e:
log.error(f"Problem reading index log file {log_file_name}: {e}")
return False
return {}
# If there is a separate `add-text-index-log.txt` file, append those
# lines.
text_log_file_name = f"{args.name}.text-index-log.txt"
try:
text_log_file_name = f"{args.name}.text-index-log.txt"
if Path(text_log_file_name).exists():
with open(text_log_file_name, "r") as text_log_file:
lines.extend(text_log_file.readlines())
except Exception as e:
log.error(
f"Problem reading text index log file " f"{text_log_file_name}: {e}"
f"Problem reading text index log file "
f"{text_log_file_name}: {e}"
)
return False
return {}

# Helper function that finds the next line matching the given `regex`,
# starting from `current_line`, and extracts the time. Returns a tuple
Expand All @@ -95,7 +99,7 @@ def execute_time(self, args, log_file_name) -> bool:
# line if no match is found.
current_line = 0

def find_next_line(regex, update_current_line=True):
def find_next_line(regex: str, update_current_line: bool = True):
nonlocal lines
nonlocal current_line
current_line_backup = current_line
Expand All @@ -109,7 +113,8 @@ def find_next_line(regex, update_current_line=True):
if regex_match:
try:
return datetime.strptime(
re.match(timestamp_regex, line).group(), timestamp_format
re.match(timestamp_regex, line).group(),
timestamp_format,
), regex_match
except Exception as e:
log.error(
Expand Down Expand Up @@ -167,18 +172,20 @@ def find_next_line(regex, update_current_line=True):
# Check whether at least the first phase is done.
if overall_begin is None:
log.error("Missing line that index build has started")
return False
return {}
if overall_begin and not merge_begin:
log.error(
"According to the log file, the index build "
"has started, but is still in its first "
"phase (parsing the input)"
)
return False
return {}

# Helper function that shows the duration for a phase (if the start and
# Helper function that computes the duration for a phase (if the start and
# end timestamps are available).
def show_duration(heading, start_end_pairs):
def duration(
start_end_pairs: list[tuple[datetime | None, datetime | None]],
) -> float | None:
nonlocal time_unit
num_start_end_pairs = 0
diff_seconds = 0
Expand All @@ -187,28 +194,32 @@ def show_duration(heading, start_end_pairs):
diff_seconds += (end - start).total_seconds()
num_start_end_pairs += 1
if num_start_end_pairs > 0:
if time_unit == "h":
diff = diff_seconds / 3600
elif time_unit == "min":
diff = diff_seconds / 60
else:
diff = diff_seconds
log.info(f"{heading:<21} : {diff:>6.1f} {time_unit}")
diff = diff_seconds / self.get_time_unit_factor(time_unit)
return diff
return None
# log.info(f"{heading:<21} : {diff:>6.1f} {time_unit}")

# Get the times of the various phases (hours or minutes, depending on
# how long the first phase took).
time_unit = args.time_unit
if time_unit == "auto":
time_unit = "h"
if merge_begin and overall_begin:
parse_duration = (merge_begin - overall_begin).total_seconds()
if parse_duration < 200:
time_unit = "s"
elif parse_duration < 3600:
time_unit = "min"
show_duration("Parse input", [(overall_begin, merge_begin)])
show_duration("Build vocabularies", [(merge_begin, convert_begin)])
show_duration("Convert to global IDs", [(convert_begin, convert_end)])
parse_duration = None
if merge_begin and overall_begin:
parse_duration = (merge_begin - overall_begin).total_seconds()
time_unit = self.get_time_unit(args.time_unit, parse_duration)

durations = {}

durations["Parse input"] = (
duration([(overall_begin, merge_begin)]),
time_unit,
)
durations["Build vocabularies"] = (
duration([(merge_begin, convert_begin)]),
time_unit,
)
durations["Convert to global IDs"] = (
duration([(convert_begin, convert_end)]),
time_unit,
)
for i in range(len(perm_begin_and_info)):
perm_begin, perm_info = perm_begin_and_info[i]
perm_end = (
Expand All @@ -217,65 +228,108 @@ def show_duration(heading, start_end_pairs):
else normal_end
)
perm_info_text = (
perm_info.group(1).replace(" and ", " & ") if perm_info else f"#{i + 1}"
perm_info.group(1).replace(" and ", " & ")
if perm_info
else f"#{i + 1}"
)
durations[f"Permutation {perm_info_text}"] = (
duration([(perm_begin, perm_end)]),
time_unit,
)
show_duration(f"Permutation {perm_info_text}", [(perm_begin, perm_end)])
show_duration("Text index", [(text_begin, text_end)])
durations["Text index"] = (
duration([(text_begin, text_end)]),
time_unit,
)
if text_begin and text_end:
log.info("")
show_duration(
"TOTAL time", [(overall_begin, normal_end), (text_begin, text_end)]
durations["TOTAL time"] = (
duration(
[(overall_begin, normal_end), (text_begin, text_end)]
),
time_unit,
)
elif normal_end:
log.info("")
show_duration("TOTAL time", [(overall_begin, normal_end)])
return True
durations["TOTAL time"] = (
duration([(overall_begin, normal_end)]),
time_unit,
)
return durations

@staticmethod
def get_time_unit(time_unit: str, parse_duration: float | None) -> str:
if time_unit != "auto":
return time_unit
time_unit = "h"
if parse_duration is not None:
if parse_duration < 200:
time_unit = "s"
elif parse_duration < 3600:
time_unit = "min"
return time_unit

@staticmethod
def get_time_unit_factor(time_unit: str) -> int:
unit_factor = {
"s": 1,
"min": 60,
"h": 3600,
}[time_unit]

return unit_factor

def execute_space(self, args) -> bool:
def execute_space(self, args) -> dict[str, tuple[float, str]]:
"""
Part of `execute` that shows the space used.
Part of `execute` that returns the space used by different types of
index along with the unit.
"""

# Get the sizes for the various groups of index files.
index_size = get_total_file_size([f"{args.name}.index.*"])
vocab_size = get_total_file_size([f"{args.name}.vocabulary.*"])
text_size = get_total_file_size([f"{args.name}.text.*"])
sizes = {}
for size_type in ["index", "vocabulary", "text"]:
sizes[size_type] = get_total_file_size(
[f"{args.name}.{size_type}.*"]
)
if args.ignore_text_index:
text_size = 0
total_size = index_size + vocab_size + text_size

# Determing the proper unit for the size.
size_unit = args.size_unit
if size_unit == "auto":
size_unit = "TB"
if total_size < 1e6:
size_unit = "B"
elif total_size < 1e9:
size_unit = "MB"
elif total_size < 1e12:
size_unit = "GB"

# Helper function for showing the size in a uniform way.
def show_size(heading, size):
nonlocal size_unit
if size_unit == "GB":
size /= 1e9
elif size_unit == "MB":
size /= 1e6
elif size_unit == "TB":
size /= 1e12
if size_unit == "B":
log.info(f"{heading:<21} : {size:,} {size_unit}")
else:
log.info(f"{heading:<21} : {size:>6.1f} {size_unit}")
sizes["text"] = 0
sizes["total"] = sum(sizes.values())

size_unit = self.get_size_unit(args.size_unit, sizes["total"])
unit_factor = self.get_size_unit_factor(size_unit)

for size_type in sizes:
sizes[size_type] /= unit_factor

show_size("Files index.*", index_size)
show_size("Files vocabulary.*", vocab_size)
if text_size > 0:
show_size("Files text.*", text_size)
log.info("")
show_size("TOTAL size", total_size)
return True
sizes_to_show = {}

sizes_to_show["Files index.*"] = (sizes["index"], size_unit)
sizes_to_show["Files vocabulary.*"] = (sizes["vocabulary"], size_unit)
if sizes["text"] > 0:
sizes_to_show["Files text.*"] = (sizes["text"], size_unit)
sizes_to_show["TOTAL size"] = (sizes["total"], size_unit)
return sizes_to_show

@staticmethod
def get_size_unit(size_unit: str, total_size: int) -> str:
if size_unit != "auto":
return size_unit
size_unit = "TB"
if total_size < 1e6:
size_unit = "B"
elif total_size < 1e9:
size_unit = "MB"
elif total_size < 1e12:
size_unit = "GB"
return size_unit

@staticmethod
def get_size_unit_factor(size_unit: str) -> int:
unit_factor = {
"B": 1,
"MB": 1e6,
"GB": 1e9,
"TB": 1e12,
}[size_unit]

return unit_factor

def execute(self, args) -> bool:
return_value = True
Expand All @@ -290,7 +344,15 @@ def execute(self, args) -> bool:
only_show=args.show,
)
if not args.show:
return_value &= self.execute_time(args, log_file_name)
durations = self.execute_time(args, log_file_name)
for heading, (duration, time_unit) in durations.items():
if duration is not None:
if heading == "TOTAL time":
log.info("")
log.info(
f"{heading:<21} : {duration:>6.1f} {time_unit}"
)
return_value &= len(durations) != 0
if not args.only_time:
log.info("")

Expand All @@ -301,6 +363,14 @@ def execute(self, args) -> bool:
only_show=args.show,
)
if not args.show:
return_value &= self.execute_space(args)
sizes = self.execute_space(args)
for heading, (size, size_unit) in sizes.items():
if heading == "TOTAL size":
log.info("")
if size_unit == "B":
log.info(f"{heading:<21} : {size:,} {size_unit}")
else:
log.info(f"{heading:<21} : {size:>6.1f} {size_unit}")
return_value &= len(sizes) != 0

return return_value
Loading