mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-12-15 16:43:38 +00:00
Add prometheus metrics endpoint. Closes #67
This commit is contained in:
40
core/prometheus_metrics_handler.py
Normal file
40
core/prometheus_metrics_handler.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from bottle import response
|
||||||
|
from prometheus_client import CollectorRegistry, generate_latest, CONTENT_TYPE_LATEST, Counter, disable_created_metrics, \
|
||||||
|
Gauge
|
||||||
|
|
||||||
|
disable_created_metrics()
|
||||||
|
# Prometheus metrics registry
|
||||||
|
registry = CollectorRegistry()
|
||||||
|
|
||||||
|
page_requests_counter = Counter(
|
||||||
|
"page_requests",
|
||||||
|
"Total number of page requests received",
|
||||||
|
registry=registry,
|
||||||
|
)
|
||||||
|
api_requests_counter = Counter(
|
||||||
|
"api_requests",
|
||||||
|
"Total number of API requests received",
|
||||||
|
registry=registry
|
||||||
|
)
|
||||||
|
spots_gauge = Gauge(
|
||||||
|
"spots",
|
||||||
|
"Number of spots currently in the software",
|
||||||
|
registry=registry
|
||||||
|
)
|
||||||
|
alerts_gauge = Gauge(
|
||||||
|
"alerts",
|
||||||
|
"Number of alerts currently in the software",
|
||||||
|
registry=registry
|
||||||
|
)
|
||||||
|
memory_use_gauge = Gauge(
|
||||||
|
"memory_usage_bytes",
|
||||||
|
"Current memory usage of the software in bytes",
|
||||||
|
registry=registry
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Get a Prometheus metrics response for Bottle
|
||||||
|
def get_metrics():
|
||||||
|
response.content_type = CONTENT_TYPE_LATEST
|
||||||
|
response.status = 200
|
||||||
|
return generate_latest(registry)
|
||||||
@@ -7,6 +7,7 @@ import pytz
|
|||||||
|
|
||||||
from core.config import SERVER_OWNER_CALLSIGN
|
from core.config import SERVER_OWNER_CALLSIGN
|
||||||
from core.constants import SOFTWARE_VERSION
|
from core.constants import SOFTWARE_VERSION
|
||||||
|
from core.prometheus_metrics_handler import memory_use_gauge, spots_gauge, alerts_gauge
|
||||||
|
|
||||||
|
|
||||||
# Provides a timed update of the application's status data.
|
# Provides a timed update of the application's status data.
|
||||||
@@ -60,8 +61,15 @@ class StatusReporter:
|
|||||||
self.status_data["webserver"] = {"status": self.web_server.status,
|
self.status_data["webserver"] = {"status": self.web_server.status,
|
||||||
"last_api_access": self.web_server.last_api_access_time.replace(
|
"last_api_access": self.web_server.last_api_access_time.replace(
|
||||||
tzinfo=pytz.UTC).timestamp() if self.web_server.last_api_access_time else 0,
|
tzinfo=pytz.UTC).timestamp() if self.web_server.last_api_access_time else 0,
|
||||||
|
"api_access_count": self.web_server.api_access_counter,
|
||||||
"last_page_access": self.web_server.last_page_access_time.replace(
|
"last_page_access": self.web_server.last_page_access_time.replace(
|
||||||
tzinfo=pytz.UTC).timestamp() if self.web_server.last_page_access_time else 0}
|
tzinfo=pytz.UTC).timestamp() if self.web_server.last_page_access_time else 0,
|
||||||
|
"page_access_count": self.web_server.page_access_counter}
|
||||||
|
|
||||||
|
# Update Prometheus metrics
|
||||||
|
memory_use_gauge.set(psutil.Process(os.getpid()).memory_info().rss * 1024)
|
||||||
|
spots_gauge.set(len(self.spots))
|
||||||
|
alerts_gauge.set(len(self.alerts))
|
||||||
|
|
||||||
self.run_timer = Timer(self.run_interval, self.run)
|
self.run_timer = Timer(self.run_interval, self.run)
|
||||||
self.run_timer.start()
|
self.run_timer.start()
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ psutil~=7.1.0
|
|||||||
requests-sse~=0.5.2
|
requests-sse~=0.5.2
|
||||||
rss-parser~=2.1.1
|
rss-parser~=2.1.1
|
||||||
pyproj~=3.7.2
|
pyproj~=3.7.2
|
||||||
|
prometheus_client~=0.23.1
|
||||||
@@ -6,9 +6,11 @@ from threading import Thread
|
|||||||
import bottle
|
import bottle
|
||||||
import pytz
|
import pytz
|
||||||
from bottle import run, request, response, template
|
from bottle import run, request, response, template
|
||||||
|
from prometheus_client import CONTENT_TYPE_LATEST, generate_latest
|
||||||
|
|
||||||
from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING
|
from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING
|
||||||
from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, SOFTWARE_VERSION
|
from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, SOFTWARE_VERSION
|
||||||
|
from core.prometheus_metrics_handler import page_requests_counter, registry, get_metrics, api_requests_counter
|
||||||
from data.spot import Spot
|
from data.spot import Spot
|
||||||
|
|
||||||
|
|
||||||
@@ -19,6 +21,8 @@ class WebServer:
|
|||||||
def __init__(self, spots, alerts, status_data, port):
|
def __init__(self, spots, alerts, status_data, port):
|
||||||
self.last_page_access_time = None
|
self.last_page_access_time = None
|
||||||
self.last_api_access_time = None
|
self.last_api_access_time = None
|
||||||
|
self.page_access_counter = 0
|
||||||
|
self.api_access_counter = 0
|
||||||
self.spots = spots
|
self.spots = spots
|
||||||
self.alerts = alerts
|
self.alerts = alerts
|
||||||
self.status_data = status_data
|
self.status_data = status_data
|
||||||
@@ -44,6 +48,8 @@ class WebServer:
|
|||||||
bottle.get("/status")(lambda: self.serve_template('webpage_status'))
|
bottle.get("/status")(lambda: self.serve_template('webpage_status'))
|
||||||
bottle.get("/about")(lambda: self.serve_template('webpage_about'))
|
bottle.get("/about")(lambda: self.serve_template('webpage_about'))
|
||||||
bottle.get("/apidocs")(lambda: self.serve_template('webpage_apidocs'))
|
bottle.get("/apidocs")(lambda: self.serve_template('webpage_apidocs'))
|
||||||
|
# Route for Prometheus metrics
|
||||||
|
bottle.get("/metrics")(lambda: self.serve_prometheus_metrics())
|
||||||
# Default route to serve from "webassets"
|
# Default route to serve from "webassets"
|
||||||
bottle.get("/<filepath:path>")(self.serve_static_file)
|
bottle.get("/<filepath:path>")(self.serve_static_file)
|
||||||
|
|
||||||
@@ -92,6 +98,8 @@ class WebServer:
|
|||||||
# Serve a JSON API endpoint
|
# Serve a JSON API endpoint
|
||||||
def serve_api(self, data):
|
def serve_api(self, data):
|
||||||
self.last_api_access_time = datetime.now(pytz.UTC)
|
self.last_api_access_time = datetime.now(pytz.UTC)
|
||||||
|
self.api_access_counter += 1
|
||||||
|
api_requests_counter.inc()
|
||||||
self.status = "OK"
|
self.status = "OK"
|
||||||
response.content_type = 'application/json'
|
response.content_type = 'application/json'
|
||||||
response.set_header('Cache-Control', 'no-store')
|
response.set_header('Cache-Control', 'no-store')
|
||||||
@@ -100,6 +108,8 @@ class WebServer:
|
|||||||
# Accept a spot
|
# Accept a spot
|
||||||
def accept_spot(self):
|
def accept_spot(self):
|
||||||
self.last_api_access_time = datetime.now(pytz.UTC)
|
self.last_api_access_time = datetime.now(pytz.UTC)
|
||||||
|
self.api_access_counter += 1
|
||||||
|
api_requests_counter.inc()
|
||||||
self.status = "OK"
|
self.status = "OK"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -153,6 +163,8 @@ class WebServer:
|
|||||||
# Serve a templated page
|
# Serve a templated page
|
||||||
def serve_template(self, template_name):
|
def serve_template(self, template_name):
|
||||||
self.last_page_access_time = datetime.now(pytz.UTC)
|
self.last_page_access_time = datetime.now(pytz.UTC)
|
||||||
|
self.page_access_counter += 1
|
||||||
|
page_requests_counter.inc()
|
||||||
self.status = "OK"
|
self.status = "OK"
|
||||||
return template(template_name)
|
return template(template_name)
|
||||||
|
|
||||||
@@ -160,6 +172,10 @@ class WebServer:
|
|||||||
def serve_static_file(self, filepath):
|
def serve_static_file(self, filepath):
|
||||||
return bottle.static_file(filepath, root="webassets")
|
return bottle.static_file(filepath, root="webassets")
|
||||||
|
|
||||||
|
# Serve Prometheus metrics
|
||||||
|
def serve_prometheus_metrics(self):
|
||||||
|
return get_metrics()
|
||||||
|
|
||||||
# Utility method to apply filters to the overall spot list and return only a subset. Enables query parameters in
|
# Utility method to apply filters to the overall spot list and return only a subset. Enables query parameters in
|
||||||
# the main "spots" GET call.
|
# the main "spots" GET call.
|
||||||
def get_spot_list_with_filters(self):
|
def get_spot_list_with_filters(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user