Skip to content

Commit

Permalink
Merge pull request #13 from pblottiere/qsa_cli_stats
Browse files Browse the repository at this point in the history
Add qsa-cli stats command
  • Loading branch information
pblottiere authored Apr 16, 2024
2 parents 95b0d20 + 170ee96 commit 61fc73f
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 5 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

Components:

* [QSA REST API](https://pblottiere.github.io/QSA/qsa-api/): Flask web server with a REST API for administrating QGIS Server
* [QSA plugin](https://pblottiere.github.io/QSA/qsa-plugin/): QGIS Server plugin for introspection
* [QSA cli](https://pblottiere.github.io/QSA/qsa-cli/): Command line tool
* `qsa-api`: Flask web server with a REST API for administrating QGIS Server
* `qsa-plugin`: QGIS Server plugin for introspection
* `qsa-cli`: Command line tool

Main features:
* Create and manage QGIS projects stored on filesystem
Expand Down
1 change: 1 addition & 0 deletions docs/src/qsa-cli/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Commands:
inspect Returns metadata about a specific QGIS Server instance
logs Returns logs of a specific QGIS Server instance
ps List QGIS Server instances
stats Returns stats of QGIS Server instances
````

Examples:
Expand Down
13 changes: 13 additions & 0 deletions qsa-api/qsa_api/api/instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,16 @@ def instances_logs(instance):
return {"error": "QGIS Server instance is not available"}, 415

return monitor.conns[instance].logs


@instances.get("/<instance>/stats")
def instances_stats(instance):
monitor = current_app.config["MONITOR"]

if not monitor:
return {"error": "QGIS Server monitoring is not activated"}, 415

if instance not in monitor.conns:
return {"error": "QGIS Server instance is not available"}, 415

return monitor.conns[instance].stats
10 changes: 10 additions & 0 deletions qsa-api/qsa_api/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ def logs(self) -> dict:
print(e, file=sys.stderr)
return {}

@property
def stats(self) -> dict:
self.response = None
try:
self.con.send(b"stats")
return self._wait_recv()
except Exception as e:
print(e, file=sys.stderr)
return {}

def _wait_recv(self):
it = 0

Expand Down
64 changes: 64 additions & 0 deletions qsa-cli/qsa/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import os
import json
import time
import click
import requests
from pathlib import Path
from tabulate import tabulate

QSA_URL = os.environ.get("QSA_SERVER_URL", "http://localhost:5000/")
Expand Down Expand Up @@ -61,3 +63,65 @@ def logs(id):
data = requests.get(url)

print(data.json()["logs"])


@cli.command()
@click.argument("id", required=False)
def stats(id):
"""
Returns stats of QGIS Server instances
"""

ids = []
if id:
ids.append(id)
else:
url = f"{QSA_URL}/api/instances"
data = requests.get(url)
for s in data.json()["servers"]:
ids.append(s["id"])

headers = [
"INSTANCE ID",
"COUNT",
"TIME ",
"SERVICE",
"REQUEST",
"PROJECT",
]

try:
while 1:
table = []
for i in ids:
url = f"{QSA_URL}/api/instances/{i}/stats"
task = requests.get(url).json()

if "error" in task:
continue

t = []
t.append(i)
t.append(task["count"])

if "service" in task:
t.append(f"{task['duration']} ms")
t.append(task["service"])
t.append(task["request"])
p = Path(task["project"]).name
t.append(p)
else:
t.append("")
t.append("")
t.append("")
t.append("")

table.append(t)

s = tabulate(table, headers=headers)
os.system("cls" if os.name == "nt" else "clear")
print(s)

time.sleep(0.25)
except:
pass
52 changes: 50 additions & 2 deletions qsa-plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,59 @@
from datetime import datetime

from qgis import PyQt
from qgis.server import QgsConfigCache
from qgis.utils import server_active_plugins
from qgis.server import QgsConfigCache, QgsServerFilter
from qgis.core import Qgis, QgsProviderRegistry, QgsApplication

LOG_MESSAGES = []


class ProbeFilter(QgsServerFilter):
def __init__(self, iface, task):
super().__init__(iface)
self.task = task

def onRequestReady(self) -> bool:
request = self.serverInterface().requestHandler()
params = request.parameterMap()

self.task["project"] = params.get("MAP", "")
self.task["service"] = params.get("SERVICE", "")
self.task["request"] = params.get("REQUEST", "")
self.task["start"] = datetime.now()
self.task["count"] += 1

return True

def onResponseComplete(self) -> bool:
self._clear_task()
return True

def onSendResponse(self) -> bool:
self._clear_task()
return True

def _clear_task(self):
count = self.task["count"]
self.task.clear()
self.task["count"] = count


def log_messages():
m = {}
m["logs"] = "\n".join(LOG_MESSAGES)
return m


def stats(task):
s = task
if "start" in s:
s["duration"] = int(
(datetime.now() - s["start"]).total_seconds() * 1000
)
return s


def metadata(iface) -> dict:
m = {}
m["plugins"] = server_active_plugins
Expand Down Expand Up @@ -61,7 +101,7 @@ def auto_connect(s: socket.socket, host: str, port: int) -> socket.socket:
return s


def f(iface, host: str, port: int) -> None:
def f(iface, host: str, port: int, task: dict) -> None:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s = auto_connect(s, host, port)

Expand All @@ -74,6 +114,8 @@ def f(iface, host: str, port: int) -> None:
payload = metadata(iface)
elif b"logs" in data:
payload = log_messages()
elif b"stats" in data:
payload = stats(task)

ser = pickle.dumps(payload)
s.sendall(struct.pack(">I", len(ser)))
Expand All @@ -95,12 +137,18 @@ def serverClassFactory(iface):
host = str(os.environ.get("QSA_HOST", "localhost"))
port = int(os.environ.get("QSA_PORT", 9999))

task = {}
task["count"] = 0

t = Thread(
target=f,
args=(
iface,
host.replace('"', ""),
port,
task,
),
)
t.start()

iface.registerFilter(ProbeFilter(iface, task), 100)
2 changes: 2 additions & 0 deletions sandbox/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ services:
- QGIS_SERVER_LOG_LEVEL=0
- QSA_HOST="qsa"
- QSA_PORT=9999
ports:
- "8080:80"
qsa:
image: pblottiere/qsa
entrypoint: "qsa"
Expand Down

0 comments on commit 61fc73f

Please sign in to comment.