From 92512520c8bccce168215dfb42f3b4822c68a429 Mon Sep 17 00:00:00 2001 From: adarshm11 Date: Sat, 12 Jul 2025 21:56:11 -0700 Subject: [PATCH 1/4] added two small metrics --- backend/routers/v1/inventory.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/backend/routers/v1/inventory.py b/backend/routers/v1/inventory.py index 30f99f1..e2a3fd5 100644 --- a/backend/routers/v1/inventory.py +++ b/backend/routers/v1/inventory.py @@ -29,12 +29,27 @@ update_snack, create_bulk_items ) +import prometheus_client router = APIRouter() +# Prometheus metrics +# track count of different types of snacks +snack_count = prometheus_client.Gauge( + "snack_count", + "Amount of snack types in inventory", +) + +# track count of purchases +purchase_count = prometheus_client.Counter( + "purchase_count", + "Total number of snacks bought from inventory", +) + @router.get("/", response_model=InventoryResponse) async def get_inventory_route(): snacks = get_inventory() + snack_count.set(len(snacks)) # set the counter to the current number of snacks return { "snacks": snacks } @router.get("/snacks/{sku}", response_model=Snack) @@ -44,6 +59,7 @@ async def get_snack_route(sku: str): @router.post("/snacks", response_model=Snack) async def create_snack_route(snack: SnackCreateSchema): + snack_count.inc() return create_snack(snack) @router.put("/snacks/{sku}", response_model=Snack) @@ -55,10 +71,12 @@ async def purchase_snack_route(request:PurchaseRequest): for purchase_request in request.purchase_requests: snack=get_snack(purchase_request.sku) update_snack(purchase_request.sku, SnackUpdateSchema(quantity=max(0,snack.quantity - purchase_request.quantity))) + purchase_count.inc() return PurchaseResponse(success=True,message="Purchase successful",purchase_requests=request.purchase_requests) @router.delete("/snacks/{sku}", response_model=Snack) async def delete_snack_route(sku: str): + snack_count.dec(1) return delete_snack(sku) @router.post("/snacks/bulk", response_model=BulkSnackResponse) From e9cb5a8359181683bd1955b04e97f0b623233475 Mon Sep 17 00:00:00 2001 From: adarshm11 Date: Sat, 12 Jul 2025 22:59:04 -0700 Subject: [PATCH 2/4] changed metrics to be snack-specific --- backend/routers/v1/inventory.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/backend/routers/v1/inventory.py b/backend/routers/v1/inventory.py index e2a3fd5..8f65922 100644 --- a/backend/routers/v1/inventory.py +++ b/backend/routers/v1/inventory.py @@ -35,31 +35,33 @@ # Prometheus metrics # track count of different types of snacks -snack_count = prometheus_client.Gauge( - "snack_count", +snack_gauge = prometheus_client.Gauge( + "snack_gauge", "Amount of snack types in inventory", + ["sku"], ) # track count of purchases purchase_count = prometheus_client.Counter( "purchase_count", "Total number of snacks bought from inventory", + ["sku"], ) @router.get("/", response_model=InventoryResponse) async def get_inventory_route(): snacks = get_inventory() - snack_count.set(len(snacks)) # set the counter to the current number of snacks return { "snacks": snacks } @router.get("/snacks/{sku}", response_model=Snack) async def get_snack_route(sku: str): snack = get_snack(sku) + snack_gauge.labels(sku=snack.sku).set(snack.quantity) return snack @router.post("/snacks", response_model=Snack) async def create_snack_route(snack: SnackCreateSchema): - snack_count.inc() + snack_gauge.labels(sku=snack.sku).set(1) # default value, change later return create_snack(snack) @router.put("/snacks/{sku}", response_model=Snack) @@ -71,12 +73,13 @@ async def purchase_snack_route(request:PurchaseRequest): for purchase_request in request.purchase_requests: snack=get_snack(purchase_request.sku) update_snack(purchase_request.sku, SnackUpdateSchema(quantity=max(0,snack.quantity - purchase_request.quantity))) - purchase_count.inc() + purchase_count.labels(sku=snack.sku).inc(purchase_request.quantity) + snack_gauge.labels(sku=snack.sku).dec(purchase_request.quantity) # now that stuff is bought, decrease inventory count return PurchaseResponse(success=True,message="Purchase successful",purchase_requests=request.purchase_requests) @router.delete("/snacks/{sku}", response_model=Snack) async def delete_snack_route(sku: str): - snack_count.dec(1) + snack_gauge.remove(sku) return delete_snack(sku) @router.post("/snacks/bulk", response_model=BulkSnackResponse) From 6faf3aa1918a7228b7f17773a275d28a9e0ad294 Mon Sep 17 00:00:00 2001 From: adarshm11 Date: Tue, 15 Jul 2025 18:04:20 -0700 Subject: [PATCH 3/4] add metrics endpoint --- backend/routers/v1/inventory.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/routers/v1/inventory.py b/backend/routers/v1/inventory.py index 8f65922..b2b3111 100644 --- a/backend/routers/v1/inventory.py +++ b/backend/routers/v1/inventory.py @@ -61,7 +61,7 @@ async def get_snack_route(sku: str): @router.post("/snacks", response_model=Snack) async def create_snack_route(snack: SnackCreateSchema): - snack_gauge.labels(sku=snack.sku).set(1) # default value, change later + snack_gauge.labels(sku=snack.sku).set(snack.quantity) return create_snack(snack) @router.put("/snacks/{sku}", response_model=Snack) @@ -97,3 +97,10 @@ async def create_bulk_route(request:BulkSnackCreate): items=None, error=f"Error Processing bulk request:{str(e)}" ) + +@router.get("/metrics") +def get_metrics(): + return PlainTextResponse( + media_type='text/plain', + content=prometheus_client.generate_latest() + ) \ No newline at end of file From 926eee2ef41075bb233c5db27b95fefd30a7c174 Mon Sep 17 00:00:00 2001 From: adarshm11 Date: Mon, 21 Jul 2025 20:50:17 -0700 Subject: [PATCH 4/4] moved metrics logic to functions --- backend/routers/v1/inventory.py | 25 +++---------------------- backend/utils/db.py | 28 +++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/backend/routers/v1/inventory.py b/backend/routers/v1/inventory.py index b2b3111..c5d6e3b 100644 --- a/backend/routers/v1/inventory.py +++ b/backend/routers/v1/inventory.py @@ -29,25 +29,11 @@ update_snack, create_bulk_items ) -import prometheus_client +from prometheus_client import generate_latest +from utils.db import snack_gauge, purchase_count router = APIRouter() -# Prometheus metrics -# track count of different types of snacks -snack_gauge = prometheus_client.Gauge( - "snack_gauge", - "Amount of snack types in inventory", - ["sku"], -) - -# track count of purchases -purchase_count = prometheus_client.Counter( - "purchase_count", - "Total number of snacks bought from inventory", - ["sku"], -) - @router.get("/", response_model=InventoryResponse) async def get_inventory_route(): snacks = get_inventory() @@ -56,12 +42,10 @@ async def get_inventory_route(): @router.get("/snacks/{sku}", response_model=Snack) async def get_snack_route(sku: str): snack = get_snack(sku) - snack_gauge.labels(sku=snack.sku).set(snack.quantity) return snack @router.post("/snacks", response_model=Snack) async def create_snack_route(snack: SnackCreateSchema): - snack_gauge.labels(sku=snack.sku).set(snack.quantity) return create_snack(snack) @router.put("/snacks/{sku}", response_model=Snack) @@ -73,13 +57,10 @@ async def purchase_snack_route(request:PurchaseRequest): for purchase_request in request.purchase_requests: snack=get_snack(purchase_request.sku) update_snack(purchase_request.sku, SnackUpdateSchema(quantity=max(0,snack.quantity - purchase_request.quantity))) - purchase_count.labels(sku=snack.sku).inc(purchase_request.quantity) - snack_gauge.labels(sku=snack.sku).dec(purchase_request.quantity) # now that stuff is bought, decrease inventory count return PurchaseResponse(success=True,message="Purchase successful",purchase_requests=request.purchase_requests) @router.delete("/snacks/{sku}", response_model=Snack) async def delete_snack_route(sku: str): - snack_gauge.remove(sku) return delete_snack(sku) @router.post("/snacks/bulk", response_model=BulkSnackResponse) @@ -102,5 +83,5 @@ async def create_bulk_route(request:BulkSnackCreate): def get_metrics(): return PlainTextResponse( media_type='text/plain', - content=prometheus_client.generate_latest() + content=generate_latest() ) \ No newline at end of file diff --git a/backend/utils/db.py b/backend/utils/db.py index 47b5001..7b9d7cb 100644 --- a/backend/utils/db.py +++ b/backend/utils/db.py @@ -13,6 +13,22 @@ import sqlite3 from models.snack import Snack, SnackCreateSchema, SnackUpdateSchema,BulkSnackCreate from typing import List +import prometheus_client + +# Prometheus metrics +# track count of different types of snacks +snack_gauge = prometheus_client.Gauge( + "snack_gauge", + "Amount of snack types in inventory", + ["sku"], +) + +# track count of purchases +purchase_count = prometheus_client.Counter( + "purchase_count", + "Total number of snacks bought from inventory", + ["sku"], +) def get_db_connection(db_file_path:str="data/db.sqlite3"): """Creates and returns a SQLite database connection""" @@ -79,6 +95,7 @@ def delete_snack(sku: str) -> Snack: RETURNING * """, (sku,)) record = cursor.fetchone() + snack_gauge.remove(sku) return Snack(**record) @@ -102,6 +119,8 @@ def create_snack(snack: SnackCreateSchema) -> Snack: snack.photo_url )) record = cursor.fetchone() + snack = Snack(**record) + snack_gauge.labels(sku=snack.sku).set(snack.quantity) return Snack(**record) @@ -124,6 +143,9 @@ def update_snack(sku: str, updates: SnackUpdateSchema) -> Snack: description = updates.description if updates.description is not None else current_snack['description'] category = updates.category if updates.category is not None else current_snack['category'] photo_url = updates.photo_url if updates.photo_url is not None else current_snack['photo_url'] + + purchase_made = quantity < current_snack['quantity'] + quantity_change = current_snack['quantity'] - quantity cursor.execute(""" UPDATE snacks @@ -146,7 +168,11 @@ def update_snack(sku: str, updates: SnackUpdateSchema) -> Snack: sku )) record = cursor.fetchone() - return Snack(**record) + snack = Snack(**record) + if purchase_made: + purchase_count.labels(sku).inc(quantity_change) + snack_gauge.labels(sku).set(quantity) + return snack # Bulk Processing function