22import bentoml
33import pandas as pd
44from pydantic import BaseModel , Field , ConfigDict
5+ import time
6+ from prometheus_client import Counter , Histogram , generate_latest , CONTENT_TYPE_LATEST
7+ from starlette .responses import Response
8+ import mlflow
59
6- with bentoml .importing ():
7- import mlflow
10+ # Métriques Prometheus
11+ REQUEST_COUNT = Counter (
12+ "app_requests_total" ,
13+ "Total requests" ,
14+ labelnames = ["method" , "endpoint" , "status" ]
15+ )
816
9- mlflow .set_tracking_uri (os .getenv ("MLFLOW_TRACKING_URI" ))
17+ REQUEST_LATENCY = Histogram (
18+ "app_request_latency_seconds" ,
19+ "Request latency in seconds" ,
20+ buckets = (0.1 , 0.5 , 1.0 , 2.0 , 5.0 )
21+ )
22+
23+ PREDICTIONS_TOTAL = Counter (
24+ "model_predictions_total" ,
25+ "Total predictions made" ,
26+ labelnames = ["model" , "status" ]
27+ )
1028
29+ PREDICTION_LATENCY = Histogram (
30+ "model_prediction_latency_seconds" ,
31+ "Model prediction latency" ,
32+ buckets = (0.01 , 0.05 , 0.1 , 0.5 , 1.0 )
33+ )
34+
35+ mlflow .set_tracking_uri (os .getenv ("MLFLOW_TRACKING_URI" ))
1136
1237class InputModel (BaseModel ):
1338 model_config = ConfigDict (populate_by_name = True )
@@ -51,48 +76,79 @@ def __init__(self):
5176
5277 @bentoml .api (route = "/predict" )
5378 def predict (self , input_data : InputModel ) -> dict :
54- feature_names = [
55- "place" , "catu" , "sexe" , "secu1" , "year_acc" , "victim_age" ,
56- "catv" , "obsm" , "motor" , "catr" , "circ" , "surf" , "situ" , "vma" ,
57- "jour" , "mois" , "lum" , "dep" , "com" , "agg_" , "int" , "atm" ,
58- "col" , "lat" , "long" , "hour" , "nb_victim" , "nb_vehicules" ,
59- ]
60- x = pd .DataFrame (
61- [
79+ """Endpoint de prédiction avec métriques"""
80+ start = time .perf_counter ()
81+
82+ # On incrémente le compteur de requêtes ici :
83+ REQUEST_COUNT .labels (method = "POST" , endpoint = "/predict" , status = "started" ).inc ()
84+
85+ try :
86+ feature_names = [
87+ "place" , "catu" , "sexe" , "secu1" , "year_acc" , "victim_age" ,
88+ "catv" , "obsm" , "motor" , "catr" , "circ" , "surf" , "situ" , "vma" ,
89+ "jour" , "mois" , "lum" , "dep" , "com" , "agg_" , "int" , "atm" ,
90+ "col" , "lat" , "long" , "hour" , "nb_victim" , "nb_vehicules" ,
91+ ]
92+ x = pd .DataFrame (
6293 [
63- input_data .place ,
64- input_data .catu ,
65- input_data .sexe ,
66- input_data .secu1 ,
67- input_data .year_acc ,
68- input_data .victim_age ,
69- input_data .catv ,
70- input_data .obsm ,
71- input_data .motor ,
72- input_data .catr ,
73- input_data .circ ,
74- input_data .surf ,
75- input_data .situ ,
76- input_data .vma ,
77- input_data .jour ,
78- input_data .mois ,
79- input_data .lum ,
80- input_data .dep ,
81- input_data .com ,
82- input_data .agg_ ,
83- input_data .int_ ,
84- input_data .atm ,
85- input_data .col ,
86- input_data .lat ,
87- input_data .long ,
88- input_data .hour ,
89- input_data .nb_victim ,
90- input_data .nb_vehicules ,
91- ]
92- ],
93- columns = feature_names ,
94- dtype = float ,
95- )
94+ [
95+ input_data .place ,
96+ input_data .catu ,
97+ input_data .sexe ,
98+ input_data .secu1 ,
99+ input_data .year_acc ,
100+ input_data .victim_age ,
101+ input_data .catv ,
102+ input_data .obsm ,
103+ input_data .motor ,
104+ input_data .catr ,
105+ input_data .circ ,
106+ input_data .surf ,
107+ input_data .situ ,
108+ input_data .vma ,
109+ input_data .jour ,
110+ input_data .mois ,
111+ input_data .lum ,
112+ input_data .dep ,
113+ input_data .com ,
114+ input_data .agg_ ,
115+ input_data .int_ ,
116+ input_data .atm ,
117+ input_data .col ,
118+ input_data .lat ,
119+ input_data .long ,
120+ input_data .hour ,
121+ input_data .nb_victim ,
122+ input_data .nb_vehicules ,
123+ ]
124+ ],
125+ columns = feature_names ,
126+ dtype = float ,
127+ )
128+
129+ pred = self .model .predict (x )
130+
131+ duration = time .perf_counter () - start
132+
133+ # Mise à jour des métriques
134+ PREDICTIONS_TOTAL .labels (model = "RandomForest" , status = "success" ).inc ()
135+ PREDICTION_LATENCY .observe (duration )
136+ REQUEST_COUNT .labels (method = "POST" , endpoint = "/predict" , status = "success" ).inc ()
137+
138+ return {"prediction" : pred .tolist ()}
139+
140+ except Exception as e :
141+ PREDICTIONS_TOTAL .labels (model = "RandomForest" , status = "error" ).inc ()
142+ raise
143+
144+ @staticmethod
145+ def on_asgi_app (app ):
146+ from starlette .routing import Route
147+ from prometheus_client import generate_latest , CONTENT_TYPE_LATEST
148+ from starlette .responses import Response
96149
97- pred = self .model .predict (x )
98- return {"prediction" : pred .tolist ()}
150+ async def metrics_endpoint (request ):
151+ return Response (content = generate_latest (), media_type = CONTENT_TYPE_LATEST )
152+ # Ajoute la route /metrics à l'application ASGI existante
153+ app .router .routes .append (Route ("/metrics" , metrics_endpoint , methods = ["GET" ]))
154+
0 commit comments