import json import logging from collections import Counter from datetime import datetime, timedelta 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 CONTINENTS = ["EU", "NA", "SA", "AS", "AF", "OC", "AN"] BANDS = ["160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m", "6m"] CONTINENTS_SET = frozenset(CONTINENTS) BANDS_SET = frozenset(BANDS) class APIDxStatsHandler(tornado.web.RequestHandler): """API request handler for /api/v1/dxstats""" def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any): self._spots = None self._web_server_metrics = None super().__init__(application, request, **kwargs) def initialize(self, spots, web_server_metrics): 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() for key in self._spots.keys(): spot = self._spots.get(key) if spot is None: continue if not spot.time or spot.time < one_hour_ago: continue if spot.de_continent in CONTINENTS_SET and spot.dx_continent in CONTINENTS_SET and spot.band in BANDS_SET: counts[spot.de_continent, spot.dx_continent, spot.band] += 1 result = { de: {dx: {band: counts[de, dx, band] for band in BANDS} for dx in CONTINENTS} for de in CONTINENTS } self.write(json.dumps(result)) self.set_status(200) self.set_header("Cache-Control", "no-store") self.set_header("Content-Type", "application/json") except Exception as e: logging.error("Exception when handling client request to dx stats API: %s", e, exc_info=True) self.write(safe_json_dumps("Error - an internal server error occurred.")) self.set_status(500)