Extract webserver metrics into a separate class to avoid passing it into every API call. Change the display to requests per hour rather than just last request time. Add SSE and telnet client connected count.

This commit is contained in:
Ian Renton
2026-09-11 22:32:04 +01:00
parent 4a09e46fe0
commit 7c458a8c5b
25 changed files with 161 additions and 230 deletions
+1 -12
View File
@@ -1,10 +1,8 @@
import logging
import re
import threading
from datetime import datetime
from typing import Any
import pytz
import requests
import tornado
from tornado import httputil
@@ -12,7 +10,6 @@ from tornado.web import Application
from core.config import ALLOW_SPOTTING, ALLOW_UPSTREAM_SPOTTING, RECAPTCHA_SECRET_KEY
from core.constants import UNKNOWN_BAND
from core.prometheus_metrics_handler import api_requests_counter
from core.sig_utils import get_ref_regex_for_sig
from core.utils import infer_band_from_freq, safe_json_dumps
from data.spot import Spot
@@ -33,23 +30,15 @@ class APISpotHandler(tornado.web.RequestHandler):
**kwargs: Any,
):
self._spots = None
self._web_server_metrics = None
self._spot_providers = None
super().__init__(application, request, **kwargs)
def initialize(self, spots, web_server_metrics, spot_providers=None):
def initialize(self, spots, spot_providers=None):
self._spots = spots
self._web_server_metrics = web_server_metrics
self._spot_providers = spot_providers or []
def post(self):
try:
# Metrics
self._web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
self._web_server_metrics["api_access_counter"] += 1
self._web_server_metrics["status"] = "OK"
api_requests_counter.inc()
# Reject if not allowed
if not ALLOW_SPOTTING:
self.set_status(401)
+2 -19
View File
@@ -10,7 +10,6 @@ from tornado import httputil
from tornado.web import Application
from core.enums import AlertType
from core.prometheus_metrics_handler import api_requests_counter
from core.utils import safe_json_dumps
from data.lookup_credentials import extract_credentials
@@ -27,12 +26,10 @@ class APIAlertsHandler(tornado.web.RequestHandler):
**kwargs: Any,
):
self._alerts = None
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
def initialize(self, alerts, web_server_metrics):
def initialize(self, alerts):
self._alerts = alerts
self._web_server_metrics = web_server_metrics
@staticmethod
def _enrich(alerts, credentials):
@@ -45,12 +42,6 @@ class APIAlertsHandler(tornado.web.RequestHandler):
def get(self):
try:
# Metrics
self._web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
self._web_server_metrics["api_access_counter"] += 1
self._web_server_metrics["status"] = "OK"
api_requests_counter.inc()
# request.arguments contains lists for each param key because technically the client can supply multiple,
# reduce that to just the first entry, and convert bytes to string
query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
@@ -82,15 +73,13 @@ class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
def __init__(self, application, request, **kwargs: Any):
self._sse_alert_broadcaster = None
self._web_server_metrics = None
self._query_params = None
self._credentials = None
self._fields = None
super().__init__(application, request, **kwargs)
def initialize(self, sse_alert_broadcaster, web_server_metrics):
def initialize(self, sse_alert_broadcaster):
self._sse_alert_broadcaster = sse_alert_broadcaster
self._web_server_metrics = web_server_metrics
def custom_headers(self):
"""Custom headers to avoid e.g. nginx reverse proxy from buffering SSE data"""
@@ -99,12 +88,6 @@ class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
def open(self):
try:
# Metrics
self._web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
self._web_server_metrics["api_access_counter"] += 1
self._web_server_metrics["status"] = "OK"
api_requests_counter.inc()
# request.arguments contains lists for each param key because technically the client can supply multiple,
# reduce that to just the first entry, and convert bytes to string
self._query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
+1 -9
View File
@@ -11,7 +11,6 @@ from tornado.web import Application
from core.constants import BANDS
from core.enums import Continent
from core.prometheus_metrics_handler import api_requests_counter
from core.utils import safe_json_dumps
logger = logging.getLogger(__name__)
@@ -30,20 +29,13 @@ class APIDxStatsHandler(tornado.web.RequestHandler):
**kwargs: Any,
):
self._spots = None
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
def initialize(self, spots, web_server_metrics):
def initialize(self, spots):
self._spots = spots
self._web_server_metrics = web_server_metrics
def get(self):
try:
self._web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
self._web_server_metrics["api_access_counter"] += 1
self._web_server_metrics["status"] = "OK"
api_requests_counter.inc()
one_hour_ago = (datetime.now(pytz.UTC) - timedelta(hours=1)).timestamp()
counts = Counter()
-33
View File
@@ -1,9 +1,7 @@
import logging
import re
from datetime import datetime
from typing import Any
import pytz
import tornado
from tornado import httputil
from tornado.web import Application
@@ -15,7 +13,6 @@ from core.geo_utils import (
lat_lon_to_cq_zone,
lat_lon_to_itu_zone,
)
from core.prometheus_metrics_handler import api_requests_counter
from core.sig_lookup_helper import populate_missing_sig_ref_info
from core.sig_utils import get_ref_regex_for_sig
from core.utils import safe_json_dumps
@@ -34,20 +31,10 @@ class APILookupCallHandler(tornado.web.RequestHandler):
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
def initialize(self, web_server_metrics):
self._web_server_metrics = web_server_metrics
def get(self):
try:
# Metrics
self._web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
self._web_server_metrics["api_access_counter"] += 1
self._web_server_metrics["status"] = "OK"
api_requests_counter.inc()
# request.arguments contains lists for each param key because technically the client can supply multiple,
# reduce that to just the first entry, and convert bytes to string
query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
@@ -85,20 +72,10 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
def initialize(self, web_server_metrics):
self._web_server_metrics = web_server_metrics
def get(self):
try:
# Metrics
self._web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
self._web_server_metrics["api_access_counter"] += 1
self._web_server_metrics["status"] = "OK"
api_requests_counter.inc()
# request.arguments contains lists for each param key because technically the client can supply multiple,
# reduce that to just the first entry, and convert bytes to string
query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
@@ -143,20 +120,10 @@ class APILookupGridHandler(tornado.web.RequestHandler):
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
def initialize(self, web_server_metrics):
self._web_server_metrics = web_server_metrics
def get(self):
try:
# Metrics
self._web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
self._web_server_metrics["api_access_counter"] += 1
self._web_server_metrics["status"] = "OK"
api_requests_counter.inc()
# request.arguments contains lists for each param key because technically the client can supply multiple,
# reduce that to just the first entry, and convert bytes to string
query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
+1 -12
View File
@@ -1,8 +1,6 @@
import logging
from datetime import datetime
from typing import Any
import pytz
import tornado
from tornado import httputil
from tornado.web import Application
@@ -10,7 +8,6 @@ from tornado.web import Application
from core.config import ALLOW_SPOTTING, MAX_SPOT_AGE
from core.constants import BANDS, PROPAGATION_MODES, SIGS
from core.enums import Continent, Mode, ModeType
from core.prometheus_metrics_handler import api_requests_counter
from core.utils import safe_json_dumps
logger = logging.getLogger(__name__)
@@ -26,23 +23,15 @@ class APIOptionsHandler(tornado.web.RequestHandler):
**kwargs: Any,
):
self._status_data = None
self._web_server_metrics = None
self._spot_providers = None
super().__init__(application, request, **kwargs)
def initialize(self, status_data, web_server_metrics, spot_providers=None):
def initialize(self, status_data, spot_providers=None):
self._status_data = status_data
self._web_server_metrics = web_server_metrics
self._spot_providers = spot_providers or []
def get(self):
try:
# Metrics
self._web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
self._web_server_metrics["api_access_counter"] += 1
self._web_server_metrics["status"] = "OK"
api_requests_counter.inc()
# Build a map of SIG name -> list of provider names that can submit spots for that SIG
spot_submit_providers = {}
+1 -12
View File
@@ -1,13 +1,10 @@
import logging
from datetime import datetime
from typing import Any
import pytz
import tornado
from tornado import httputil
from tornado.web import Application
from core.prometheus_metrics_handler import api_requests_counter
from core.utils import safe_json_dumps
logger = logging.getLogger(__name__)
@@ -23,21 +20,13 @@ class APISolarConditionsHandler(tornado.web.RequestHandler):
**kwargs: Any,
):
self._solar_conditions = None
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
def initialize(self, solar_conditions, web_server_metrics):
def initialize(self, solar_conditions):
self._solar_conditions = solar_conditions
self._web_server_metrics = web_server_metrics
def get(self):
try:
# Metrics
self._web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
self._web_server_metrics["api_access_counter"] += 1
self._web_server_metrics["status"] = "OK"
api_requests_counter.inc()
self.write(self._solar_conditions.to_json())
self.set_status(200)
self.set_header("Cache-Control", "no-store")
+2 -19
View File
@@ -9,7 +9,6 @@ import tornado_eventsource.handler
from tornado import httputil
from tornado.web import Application
from core.prometheus_metrics_handler import api_requests_counter
from core.utils import safe_json_dumps
from data.lookup_credentials import extract_credentials
@@ -26,12 +25,10 @@ class APISpotsHandler(tornado.web.RequestHandler):
**kwargs: Any,
):
self._spots = None
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
def initialize(self, spots, web_server_metrics):
def initialize(self, spots):
self._spots = spots
self._web_server_metrics = web_server_metrics
@staticmethod
def _enrich(spots, credentials):
@@ -44,12 +41,6 @@ class APISpotsHandler(tornado.web.RequestHandler):
def get(self):
try:
# Metrics
self._web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
self._web_server_metrics["api_access_counter"] += 1
self._web_server_metrics["status"] = "OK"
api_requests_counter.inc()
# request.arguments contains lists for each param key because technically the client can supply multiple,
# reduce that to just the first entry, and convert bytes to string
query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
@@ -81,15 +72,13 @@ class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
def __init__(self, application, request, **kwargs: Any):
self._sse_spot_broadcaster = None
self._web_server_metrics = None
self._query_params = None
self._credentials = None
self._fields = None
super().__init__(application, request, **kwargs)
def initialize(self, sse_spot_broadcaster, web_server_metrics):
def initialize(self, sse_spot_broadcaster):
self._sse_spot_broadcaster = sse_spot_broadcaster
self._web_server_metrics = web_server_metrics
def custom_headers(self):
"""Custom headers to avoid e.g. nginx reverse proxy from buffering SSE data"""
@@ -100,12 +89,6 @@ class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
"""Called once on the client opening a connection, set things up"""
try:
# Metrics
self._web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
self._web_server_metrics["api_access_counter"] += 1
self._web_server_metrics["status"] = "OK"
api_requests_counter.inc()
# request.arguments contains lists for each param key because technically the client can supply multiple,
# reduce that to just the first entry, and convert bytes to string
self._query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
+1 -12
View File
@@ -1,13 +1,10 @@
import logging
from datetime import datetime
from typing import Any
import pytz
import tornado
from tornado import httputil
from tornado.web import Application
from core.prometheus_metrics_handler import api_requests_counter
from core.utils import safe_json_dumps
logger = logging.getLogger(__name__)
@@ -23,21 +20,13 @@ class APIStatusHandler(tornado.web.RequestHandler):
**kwargs: Any,
):
self._status_data = None
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
def initialize(self, status_data, web_server_metrics):
def initialize(self, status_data):
self._status_data = status_data
self._web_server_metrics = web_server_metrics
def get(self):
try:
# Metrics
self._web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
self._web_server_metrics["api_access_counter"] += 1
self._web_server_metrics["status"] = "OK"
api_requests_counter.inc()
self.write(safe_json_dumps(self._status_data))
self.set_status(200)
self.set_header("Cache-Control", "no-store")
+1 -12
View File
@@ -1,16 +1,13 @@
import logging
import re
from datetime import datetime
from typing import Any
import pytz
import tornado
from tornado import httputil
from tornado.web import Application
from core.config import ALLOW_SPOTTING
from core.constants import UNKNOWN_BAND
from core.prometheus_metrics_handler import api_requests_counter
from core.sig_utils import get_ref_regex_for_sig
from core.utils import infer_band_from_freq, safe_json_dumps
from data.spot import Spot
@@ -28,21 +25,13 @@ class V1APISpotHandler(tornado.web.RequestHandler):
**kwargs: Any,
):
self._spots = None
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
def initialize(self, spots, web_server_metrics):
def initialize(self, spots):
self._spots = spots
self._web_server_metrics = web_server_metrics
def post(self):
try:
# Metrics
self._web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
self._web_server_metrics["api_access_counter"] += 1
self._web_server_metrics["status"] = "OK"
api_requests_counter.inc()
# Reject if not allowed
if not ALLOW_SPOTTING:
self.set_status(401)