Mari0304 commited on
Commit
b413138
·
1 Parent(s): b46e1e7

feat: Add FastAPI skill classification service with Prometheus monitoring and MLflow integration, updating dependencies and Docker Compose.

Browse files
docker-compose.yml CHANGED
@@ -47,6 +47,17 @@ services:
47
  condition: service_healthy
48
  restart: unless-stopped
49
 
 
 
 
 
 
 
 
 
 
 
 
50
  networks:
51
  hopcroft-net:
52
  driver: bridge
 
47
  condition: service_healthy
48
  restart: unless-stopped
49
 
50
+ prometheus:
51
+ image: prom/prometheus:latest
52
+ container_name: prometheus
53
+ volumes:
54
+ - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
55
+ ports:
56
+ - "9090:9090"
57
+ networks:
58
+ - hopcroft-net
59
+ restart: unless-stopped
60
+
61
  networks:
62
  hopcroft-net:
63
  driver: bridge
hopcroft_skill_classification_tool_competition/main.py CHANGED
@@ -22,9 +22,17 @@ import os
22
  import time
23
  from typing import List
24
 
25
- from fastapi import FastAPI, HTTPException, status
26
  from fastapi.responses import JSONResponse, RedirectResponse
27
  import mlflow
 
 
 
 
 
 
 
 
28
  from pydantic import ValidationError
29
 
30
  from hopcroft_skill_classification_tool_competition.api_models import (
@@ -40,6 +48,34 @@ from hopcroft_skill_classification_tool_competition.api_models import (
40
  from hopcroft_skill_classification_tool_competition.config import MLFLOW_CONFIG
41
  from hopcroft_skill_classification_tool_competition.modeling.predict import SkillPredictor
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  predictor = None
44
  model_version = "1.0.0"
45
 
@@ -85,6 +121,43 @@ app = FastAPI(
85
  )
86
 
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  @app.get("/", tags=["Root"])
89
  async def root():
90
  """Return basic API information."""
@@ -143,9 +216,11 @@ async def predict_skills(issue: IssueInput) -> PredictionRecord:
143
 
144
  # Combine text fields if needed, or just use issue_text
145
  # The predictor expects a single string
 
146
  full_text = f"{issue.issue_text} {issue.issue_description or ''} {issue.repo_name or ''}"
147
 
148
- predictions_data = predictor.predict(full_text)
 
149
 
150
  # Convert to Pydantic models
151
  predictions = [
 
22
  import time
23
  from typing import List
24
 
25
+ from fastapi import FastAPI, HTTPException, status, Request, Response
26
  from fastapi.responses import JSONResponse, RedirectResponse
27
  import mlflow
28
+ from prometheus_client import (
29
+ CONTENT_TYPE_LATEST,
30
+ Counter,
31
+ Gauge,
32
+ Histogram,
33
+ Summary,
34
+ generate_latest,
35
+ )
36
  from pydantic import ValidationError
37
 
38
  from hopcroft_skill_classification_tool_competition.api_models import (
 
48
  from hopcroft_skill_classification_tool_competition.config import MLFLOW_CONFIG
49
  from hopcroft_skill_classification_tool_competition.modeling.predict import SkillPredictor
50
 
51
+ # Define Prometheus Metrics
52
+ # Counter: Total number of requests
53
+ REQUESTS_TOTAL = Counter(
54
+ "hopcroft_requests_total",
55
+ "Total number of requests",
56
+ ["method", "endpoint", "http_status"],
57
+ )
58
+
59
+ # Histogram: Request duration
60
+ REQUEST_DURATION_SECONDS = Histogram(
61
+ "hopcroft_request_duration_seconds",
62
+ "Request duration in seconds",
63
+ ["method", "endpoint"],
64
+ )
65
+
66
+ # Gauge: In-progress requests
67
+ IN_PROGRESS_REQUESTS = Gauge(
68
+ "hopcroft_in_progress_requests",
69
+ "Number of requests currently in progress",
70
+ ["method", "endpoint"],
71
+ )
72
+
73
+ # Summary: Model prediction time
74
+ MODEL_PREDICTION_SECONDS = Summary(
75
+ "hopcroft_prediction_processing_seconds",
76
+ "Time spent processing model predictions",
77
+ )
78
+
79
  predictor = None
80
  model_version = "1.0.0"
81
 
 
121
  )
122
 
123
 
124
+ @app.middleware("http")
125
+ async def monitor_requests(request: Request, call_next):
126
+ """Middleware to collect Prometheus metrics for each request."""
127
+ method = request.method
128
+ # Use a simplified path or template if possible to avoid high cardinality
129
+ # For now, using request.url.path is acceptable for this scale
130
+ endpoint = request.url.path
131
+
132
+ IN_PROGRESS_REQUESTS.labels(method=method, endpoint=endpoint).inc()
133
+ start_time = time.time()
134
+
135
+ try:
136
+ response = await call_next(request)
137
+ status_code = response.status_code
138
+ REQUESTS_TOTAL.labels(
139
+ method=method, endpoint=endpoint, http_status=status_code
140
+ ).inc()
141
+ return response
142
+ except Exception as e:
143
+ REQUESTS_TOTAL.labels(
144
+ method=method, endpoint=endpoint, http_status=500
145
+ ).inc()
146
+ raise e
147
+ finally:
148
+ duration = time.time() - start_time
149
+ REQUEST_DURATION_SECONDS.labels(method=method, endpoint=endpoint).observe(
150
+ duration
151
+ )
152
+ IN_PROGRESS_REQUESTS.labels(method=method, endpoint=endpoint).dec()
153
+
154
+
155
+ @app.get("/metrics", tags=["Observability"])
156
+ async def metrics():
157
+ """Expose Prometheus metrics."""
158
+ return Response(content=generate_latest(), media_type=CONTENT_TYPE_LATEST)
159
+
160
+
161
  @app.get("/", tags=["Root"])
162
  async def root():
163
  """Return basic API information."""
 
216
 
217
  # Combine text fields if needed, or just use issue_text
218
  # The predictor expects a single string
219
+ # The predictor expects a single string
220
  full_text = f"{issue.issue_text} {issue.issue_description or ''} {issue.repo_name or ''}"
221
 
222
+ with MODEL_PREDICTION_SECONDS.time():
223
+ predictions_data = predictor.predict(full_text)
224
 
225
  # Convert to Pydantic models
226
  predictions = [
monitoring/README.md ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Metrics Collection & Verification
2
+
3
+ This directory contains the configuration for Prometheus monitoring.
4
+
5
+ ## Configuration
6
+ - **Prometheus Config**: `prometheus/prometheus.yml`
7
+ - **Scrape Target**: `hopcroft-api:8080`
8
+ - **Metrics Endpoint**: `http://localhost:8080/metrics`
9
+
10
+ ## Verification Queries (PromQL)
11
+
12
+ You can run these queries in the Prometheus Expression Browser (`http://localhost:9090/graph`):
13
+
14
+ ### 1. Request Rate (Counter)
15
+ Shows the rate of requests per second over the last minute.
16
+ ```promql
17
+ rate(hopcroft_requests_total[1m])
18
+ ```
19
+
20
+ ### 2. Average Request Duration (Histogram)
21
+ Calculates average latency.
22
+ ```promql
23
+ rate(hopcroft_request_duration_seconds_sum[5m]) / rate(hopcroft_request_duration_seconds_count[5m])
24
+ ```
25
+
26
+ ### 3. Current In-Progress Requests (Gauge)
27
+ Shows how many requests are currently being processed.
28
+ ```promql
29
+ hopcroft_in_progress_requests
30
+ ```
31
+
32
+ ### 4. Model Prediction Time (Summary)
33
+ Shows the 90th percentile of model prediction time.
34
+ ```promql
35
+ hopcroft_prediction_processing_seconds{quantile="0.9"}
36
+ ```
monitoring/prometheus/prometheus.yml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ global:
2
+ scrape_interval: 15s
3
+
4
+ scrape_configs:
5
+ - job_name: 'hopcroft-api'
6
+ static_configs:
7
+ - targets: ['hopcroft-api:8080']
requirements.txt CHANGED
@@ -23,6 +23,7 @@ sentence-transformers
23
 
24
  # API Framework
25
  fastapi[standard]>=0.115.0
 
26
  pydantic>=2.0.0
27
  uvicorn>=0.30.0
28
  httpx>=0.27.0
 
23
 
24
  # API Framework
25
  fastapi[standard]>=0.115.0
26
+ prometheus-client>=0.17.0
27
  pydantic>=2.0.0
28
  uvicorn>=0.30.0
29
  httpx>=0.27.0