Squashed 'utils_v2/' content from commit ef9630d
git-subtree-dir: utils_v2 git-subtree-split: ef9630d728847764d4832ce8f3f956571901383a
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
"""
|
||||
|
||||
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())
|
||||
Reference in New Issue
Block a user