Skip to content

Commit 7809cd2

Browse files
committed
Ajout Prometheus et Grafana
1 parent 95d48ef commit 7809cd2

8 files changed

Lines changed: 170 additions & 147 deletions

File tree

docker-compose.yml

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ services:
2020
ml-api:
2121
build:
2222
context: .
23-
dockerfile: src/bentoml/Dockerfile.local
23+
dockerfile: src/bentoml/Dockerfile
2424
container_name: accident-prediction-api
2525
environment:
2626
- MLFLOW_TRACKING_URI=http://mlflow:5000
@@ -32,7 +32,7 @@ services:
3232
condition: service_started
3333
train:
3434
condition: service_completed_successfully
35-
35+
3636
streamlit:
3737
build:
3838
context: .
@@ -56,6 +56,33 @@ services:
5656
- ml-api
5757
restart: unless-stopped
5858

59+
prometheus:
60+
image: prom/prometheus:latest
61+
volumes:
62+
- ./src/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
63+
- prometheus_data:/prometheus
64+
ports:
65+
- "9090:9090"
66+
command:
67+
- '--config.file=/etc/prometheus/prometheus.yml'
68+
- '--storage.tsdb.path=/prometheus'
69+
depends_on:
70+
- ml-api
71+
72+
grafana:
73+
image: grafana/grafana:latest
74+
environment:
75+
GF_SECURITY_ADMIN_PASSWORD: admin
76+
GF_SECURITY_ADMIN_USER: admin
77+
volumes:
78+
- grafana_data:/var/lib/grafana
79+
- ./grafana/dashboards:/etc/grafana/provisioning/dashboards
80+
- ./grafana/datasources:/etc/grafana/provisioning/datasources
81+
ports:
82+
- "3000:3000"
83+
depends_on:
84+
- prometheus
85+
5986
postgres-airflow:
6087
image: postgres:15
6188
environment:
@@ -119,3 +146,5 @@ services:
119146
volumes:
120147
postgres-airflow-data:
121148
airflow-logs:
149+
prometheus_data:
150+
grafana_data:

src/bentoml/Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM python:3.12-slim
2+
3+
WORKDIR /app
4+
5+
COPY ./src/bentoml/requirements.txt .
6+
7+
RUN pip install --no-cache-dir -r requirements.txt
8+
9+
COPY ./src/bentoml/. .
10+
EXPOSE 3000
11+
12+
# vérifier à quoi "--production"
13+
CMD ["bentoml", "serve", "service:PredictService", "--production", "--host", "0.0.0.0", "--port", "3000"]

src/bentoml/Dockerfile.local

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/bentoml/bentofile.yaml

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/bentoml/requirements.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
numpy==1.26.4
2+
pandas==2.3.3
3+
scikit-learn==1.3.2
4+
bentoml==1.4.39
5+
pydantic==2.11.0
6+
requests==2.32.5
7+
pyjwt==2.10.1
8+
mlflow==2.11.3
9+
setuptools<81

src/bentoml/service.py

Lines changed: 102 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,37 @@
22
import bentoml
33
import pandas as pd
44
from 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

1237
class 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+

src/bentoml/service_local.py

Lines changed: 0 additions & 69 deletions
This file was deleted.

src/prometheus/prometheus.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
global:
2+
scrape_interval: 15s
3+
evaluation_interval: 15s
4+
5+
scrape_configs:
6+
# 1. Prometheus se monitor lui-même
7+
- job_name: 'prometheus'
8+
static_configs:
9+
- targets: ['localhost:9090']
10+
11+
# 2. Notre service BentoML
12+
- job_name: 'bentoml-api'
13+
static_configs:
14+
- targets: ['ml-api:3000']
15+
# metrics_path: '/metrics' <-- Optionnel car c'est la valeur par défaut

0 commit comments

Comments
 (0)