199 lines
7.2 KiB
Python
199 lines
7.2 KiB
Python
"""
|
|
|
|
AUTHOR:
|
|
|
|
Khushal P Soonderji
|
|
Sharvil J Daiya
|
|
|
|
DATE:
|
|
|
|
Saturday, 28th Sept., 2024
|
|
|
|
OBJECTIVE:
|
|
|
|
To have one place from where several metrics are measured using easy to use context managers.
|
|
|
|
REFERENCES:
|
|
|
|
N/A
|
|
|
|
DOWNLOADS:
|
|
|
|
N/A
|
|
|
|
"""
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** IMPORT ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
# To make sibling directories accessible for imports:
|
|
import sys
|
|
sys.path.append(".")
|
|
sys.path.append("..")
|
|
|
|
# To measure time:
|
|
import time
|
|
|
|
# To capture metrics for Prometheus:
|
|
from prometheus_client import Counter, Summary, Gauge
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** MACROS / ONE-TIME INIT ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
# Define the Prometheus metrics.
|
|
# FOR THE MICROSERVICE AS A WHOLE:
|
|
WORKER_COUNT = Counter(
|
|
name = "ms_workers_active_total",
|
|
documentation = "The number of threads for the microservice being monitored.",
|
|
labelnames = ["project_name", "service_name", "host_name"]
|
|
)
|
|
|
|
# Define the Prometheus metrics.
|
|
# FOR INDIVIDUAL API ENDPOINTS:
|
|
REQUEST_LATENCY = Summary(
|
|
name = "http_request_latency_seconds",
|
|
documentation = "Latency of HTTP requests in seconds.",
|
|
labelnames = ["method", "endpoint", "http_status"]
|
|
)
|
|
TOTAL_REQUEST_COUNT = Counter(
|
|
name = "http_requests_total",
|
|
documentation = "Total HTTP requests.",
|
|
labelnames = ["method", "endpoint", "http_status"]
|
|
)
|
|
LIVE_REQUEST_COUNT = Gauge(
|
|
name = "http_requests_live_total",
|
|
documentation = "To check if an API endpoint is being served right now.",
|
|
labelnames = ["endpoint"]
|
|
)
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** VARIABLES ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
# --- Nothing Yet
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** CLASSES ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
class MetricsAPI:
|
|
|
|
"""
|
|
Use this class through its context manager to automatically measure all the metrics in one place.
|
|
This was originally created to measure the performance of API endpoints made in Quart, but it should work with
|
|
other frameworks as well.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
method = None,
|
|
endpoint = None,
|
|
raise_exception = False
|
|
):
|
|
|
|
# Make provisions for things to note.
|
|
# NOTE: THESE MUST BE SET FROM OUTSIDE:
|
|
self.method = method
|
|
self.endpoint = endpoint
|
|
self.http_code = None
|
|
self.__raise_exception = raise_exception
|
|
|
|
async def __aenter__(self):
|
|
|
|
# Note down the start time immediately:
|
|
self.__start_ts = time.perf_counter()
|
|
self.__cpu_start_ts = time.process_time()
|
|
|
|
# Note down the metrics:
|
|
LIVE_REQUEST_COUNT.labels(self.endpoint).inc(1)
|
|
|
|
# Setup done:
|
|
return self
|
|
|
|
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
|
|
# Note down the metrics:
|
|
LIVE_REQUEST_COUNT.labels(self.endpoint).dec(1)
|
|
|
|
TOTAL_REQUEST_COUNT.labels(
|
|
self.method,
|
|
self.endpoint,
|
|
self.http_code
|
|
).inc()
|
|
|
|
REQUEST_LATENCY.labels(
|
|
self.method,
|
|
self.endpoint,
|
|
self.http_code
|
|
).observe(time.perf_counter() - self.__start_ts)
|
|
|
|
# Handle the exception as per the user's preference:
|
|
return False if self.__raise_exception else True
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** FUNCTIONS ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
# --- Nothing Yet
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** MAIN PROGRAM ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
import asyncio
|
|
import random
|
|
from prometheus_client import generate_latest
|
|
|
|
async def simulate_endpoint():
|
|
|
|
async with MetricsAPI(
|
|
method = random.choice(["GET", "POST"]),
|
|
endpoint = f"https://my.domain.com/api/{random.choice([0, 1, 2, 3])}"
|
|
) as metrics:
|
|
|
|
# Simulate some action on some endpoint:
|
|
await asyncio.sleep(1.0)
|
|
|
|
# Note down the values:
|
|
metrics.http_code = 200
|
|
|
|
async def main():
|
|
|
|
print("Simulating endpoints...")
|
|
tasks = [simulate_endpoint() for _ in range(250)]
|
|
await asyncio.gather(*tasks)
|
|
print("Done!")
|
|
|
|
print("METRICS:")
|
|
print(generate_latest().decode())
|
|
|
|
asyncio.run(main())
|