Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6daa184
Build the `monitor-queries` ui v3 using python textual with fake data…
tanmay-9 May 15, 2026
f11d537
Finish UI implementation without the data layer
tanmay-9 May 16, 2026
009a398
Merge remote-tracking branch 'origin/main' into monitor-queries-v3
tanmay-9 May 16, 2026
871b150
Add textual and rich to pyproject.toml
tanmay-9 May 16, 2026
6618630
Add `log_reader.py` with pure functions to parse a jsonl line with an…
tanmay-9 May 19, 2026
5d365bf
Add functions to find the file offset for a given timestamp and scan …
tanmay-9 May 19, 2026
15bd402
Add a way to get active and completed queries from the log file
tanmay-9 May 20, 2026
05092c6
First working version of LiveScreen
tanmay-9 May 22, 2026
54abc14
First working historic screen
tanmay-9 May 24, 2026
1a3f434
Fix theme cycling and timeline window bar errors
tanmay-9 May 25, 2026
ff6c8d0
The timeline widget caps shouldn't render when the bar is at the edges
tanmay-9 May 26, 2026
b5e499d
Fix metric row for last 1h oscillating between empty and filled, and …
tanmay-9 May 26, 2026
410aba1
Show really fast queries in live mode at least once to indicate serve…
tanmay-9 May 26, 2026
56fe02f
Add server reachable/unreachable wiring and fix empty log file crashe…
tanmay-9 May 26, 2026
bb3c98e
Add client ip to SparqlPane header
tanmay-9 May 26, 2026
be70a6e
Fix flash query duration and extra linger, plus orphaned query logic
tanmay-9 May 26, 2026
765adfd
Update monitor-queries to use correct metrics-log.jsonl filename
tanmay-9 May 27, 2026
edb7e82
Replace QID column with Started column on live view table, and make c…
tanmay-9 May 28, 2026
74cccfa
Give each worker thread a unique group so that one thread ending does…
tanmay-9 May 28, 2026
9ce2311
Ensure that every query is shown in live view at least once
tanmay-9 May 29, 2026
926fde2
Add --refresh option to monitor-queries to allow the user to control …
tanmay-9 May 30, 2026
6c6ccd4
Fix live table screen flicker beacuse of table rewrite and add upper …
tanmay-9 May 30, 2026
81890c6
Merge branch 'qlever-dev:main' into monitor-queries-v3
tanmay-9 May 31, 2026
098f29d
Make sure qids, client_ip and sparql are only read for the visible ro…
tanmay-9 May 31, 2026
f7ada75
Add a 0.1s debounce to historic view to let the user selection settle…
tanmay-9 May 31, 2026
6418caf
Fix live view metrics for last 1h window and make historic view metri…
tanmay-9 Jun 1, 2026
41562ae
Fix timeline not rendering correctly at log end and change slow to (>…
tanmay-9 Jun 1, 2026
b747a98
Unregister ansi theme because of unreadable footer and complicated co…
tanmay-9 Jun 1, 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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ classifiers = [
"Topic :: Database :: Front-Ends"
]

dependencies = [ "psutil", "termcolor", "argcomplete", "pyyaml", "rdflib", "requests-sse", "tqdm>=4.60.0" ]
dependencies = [ "psutil", "termcolor", "argcomplete", "pyyaml", "rdflib", "requests-sse", "tqdm>=4.60.0", "textual>=8.0", "rich" ]

[project.optional-dependencies]
dev = [ "ruff", "pre-commit" ]
Expand Down
44 changes: 11 additions & 33 deletions src/qlever/commands/benchmark_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,12 @@
from qlever.command import QleverCommand
from qlever.commands.clear_cache import ClearCacheCommand
from qlever.commands.ui import dict_to_yaml
from qlever.containerize import Containerize
from qlever.log import log, mute_log
from qlever.util import run_command, run_curl_command


def pretty_printed_query(
query: str, show_prefixes: bool, system: str = "docker"
) -> str:
"""
Pretty-print a SPARQL query using the sparql-formatter Docker image.
Optionally strips PREFIX declarations from the output.
Argument `system` can either be docker or podman.
"""
if system not in Containerize.supported_systems():
system = "docker"
remove_prefixes_cmd = " | sed '/^PREFIX /Id'" if not show_prefixes else ""
pretty_print_query_cmd = (
f"echo {shlex.quote(query)}"
f" | {system} run -i --rm docker.io/sparqling/sparql-formatter"
f"{remove_prefixes_cmd} | grep -v '^$'"
)
try:
query_pretty_printed = run_command(
pretty_print_query_cmd, return_output=True
)
return query_pretty_printed.rstrip()
except Exception as e:
log.debug(
f"Failed to pretty-print query, returning original query: {e}"
)
return query.rstrip()
from qlever.util import (
pretty_printed_query,
run_command,
run_curl_command,
)


def sparql_query_type(query: str) -> str:
Expand Down Expand Up @@ -1005,7 +980,8 @@ def execute(self, args) -> bool:
colored(
pretty_printed_query(
query, args.show_prefixes, args.system
),
)
or query,
"cyan",
)
)
Expand Down Expand Up @@ -1107,7 +1083,8 @@ def execute(self, args) -> bool:
description=description,
query=pretty_printed_query(
query, args.show_prefixes, args.system
),
)
or query,
client_time=time_seconds,
result=query_results,
result_size=result_length,
Expand Down Expand Up @@ -1166,7 +1143,8 @@ def execute(self, args) -> bool:
colored(
pretty_printed_query(
query, args.show_prefixes, args.system
),
)
or query,
"cyan",
)
)
Expand Down
118 changes: 118 additions & 0 deletions src/qlever/commands/monitor_queries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
from __future__ import annotations

from pathlib import Path

from qlever.command import QleverCommand
from qlever.log import log
from qlever.monitor.app import MonitorQueriesApp

# `LiveLogReader` ingests new log lines every 0.2s, so a faster screen
# refresh would only re-render the ticking duration; 0.2s is the floor.
REFRESH_MIN_S = 0.2
REFRESH_MAX_S = 2.0


class MonitorQueriesCommand(QleverCommand):
"""
Class for executing the `monitor-queries-tui` command.
"""

def __init__(self):
pass

def description(self) -> str:
return (
"Show the currently active and historically all the active queries "
"on the server (interactive TUI)"
)

def should_have_qleverfile(self) -> bool:
return False

def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
return {
"data": ["name"],
"server": ["host_name", "port", "timeout"],
"runtime": ["system"],
}

def additional_arguments(self, subparser) -> None:
subparser.add_argument(
"--sparql-endpoint",
type=str,
help="URL of the SPARQL endpoint (default = {host_name}:{port})",
)
subparser.add_argument(
"--metrics-log",
type=Path,
help=(
"QLever's `metrics-log.jsonl` log file "
"(default = {name}.metrics-log.jsonl)"
),
)
subparser.add_argument(
"--slow-threshold",
type=int,
default=None,
help="Duration in seconds above which a query (active or"
" completed) is counted as slow in the metrics"
" (default = server timeout - 10s)",
)
subparser.add_argument(
"--refresh",
type=float,
default=REFRESH_MIN_S,
help="Live view screen refresh interval in seconds, between"
f" {REFRESH_MIN_S} and {REFRESH_MAX_S}"
f" (default = {REFRESH_MIN_S})",
)

def execute(self, args) -> bool:
if not args.metrics_log:
args.metrics_log = Path.cwd() / f"{args.name}.metrics-log.jsonl"
show_msg = (
f"Reading server logs from {args.metrics_log} to display the "
"currently active queries on the server"
)
self.show(show_msg, only_show=args.show)
if args.show:
return True

if not args.metrics_log.is_file():
log.error(f"Log file not found: {args.metrics_log}")
return False

timeout_s = 30
if args.slow_threshold is None:
try:
timeout_s = int(args.timeout.rstrip("s"))
except ValueError:
log.error(
f"Could not parse server timeout {args.timeout!r};"
" pass --slow-threshold explicitly"
)
return False
args.slow_threshold = max(1, timeout_s - 10)

if args.refresh < REFRESH_MIN_S or args.refresh > REFRESH_MAX_S:
log.error(
f"--refresh must be between {REFRESH_MIN_S} and"
f" {REFRESH_MAX_S} seconds"
)
return False

sparql_endpoint = (
args.sparql_endpoint
if args.sparql_endpoint
else f"{args.host_name}:{args.port}"
)

MonitorQueriesApp(
log_file=args.metrics_log,
sparql_endpoint=sparql_endpoint,
timeout=timeout_s,
slow_threshold=args.slow_threshold,
refresh_interval=args.refresh,
system=args.system,
).run()
return True
Empty file added src/qlever/monitor/__init__.py
Empty file.
Loading
Loading