diff --git a/deployment/README.md b/deployment/README.md index 2cfcd179..5892eeb7 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -43,3 +43,30 @@ kubectl get all -n devoops # Shows all Kubernetes resources dep helm uninstall meetatmensa -n devoops # Deletes all resources deployed with the Helm chart ``` If you want to access cluster-internal services, you can utilize port-forwarding: `kubectl port-forward svc/meetatmensa- :80 -n devoops`. Do not use ports 8080, 8081 or 8082 - they are reserved as entry points to the application (client service). + +## AWS Deployment + +For AWS deployment, use the `compose.aws.yml` file which is configured for production deployment with SSL certificates and monitoring. + +### Access Points +- **Client Application**: https://client.54.204.29.206.sslip.io +- **API Gateway**: https://api.54.204.29.206.sslip.io + + + +## 📊 Monitoring + +Meet@Mensa includes a comprehensive monitoring stack with Prometheus for metrics collection, Grafana for visualization, and Loki for log aggregation. + +### Local Deployment (Docker Compose) + +When running locally with Docker Compose, monitoring services are available at: + +- **Grafana Dashboard**: http://localhost:3000 + - Default credentials: `admin` / `admin` + - Access dashboards for application metrics, logs, and system health + +- **Prometheus**: http://localhost:9090 + - View metrics, alerts, and targets + - Access alert rules and their current state + diff --git a/deployment/docker/compose.yml b/deployment/docker/compose.yml index ca255f22..e83d53ef 100644 --- a/deployment/docker/compose.yml +++ b/deployment/docker/compose.yml @@ -79,10 +79,75 @@ services: networks: - backend + prometheus: + image: prom/prometheus:v2.52.0 + container_name: prometheus + ports: + - "9090:9090" + volumes: + - ../../prometheus:/etc/prometheus + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/etc/prometheus/console_libraries' + - '--web.console.templates=/etc/prometheus/consoles' + - '--web.enable-lifecycle' + networks: + - backend + restart: unless-stopped + + grafana: + image: grafana/grafana-oss:latest + container_name: grafana + ports: + - "3001:3000" + volumes: + - grafana-storage:/var/lib/grafana + - ../../grafana/provisioning:/etc/grafana/provisioning:ro + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_USERS_ALLOW_SIGN_UP=false + - GF_FEATURE_TOGGLES_ENABLE=logsInExplore + - GF_LOG_CONSOLECOLORS=true + depends_on: + - prometheus + - loki + networks: + - backend + restart: unless-stopped + + promtail: + image: grafana/promtail:latest + volumes: + - /var/lib/docker/containers:/var/lib/docker/containers:ro + - ../../promtail/promtail.yml:/etc/promtail/promtail.yml + command: -config.file=/etc/promtail/promtail.yml + depends_on: + - loki + networks: + - backend + restart: unless-stopped + + loki: + image: grafana/loki:2.9.0 + ports: + - "3100:3100" + volumes: + - ../../loki/loki-config.yaml:/etc/loki/loki-config.yaml:ro + command: -config.file=/etc/loki/loki-config.yaml + networks: + - backend + restart: unless-stopped + networks: backend: driver: bridge volumes: matchdb_data: - userdb_data: \ No newline at end of file + userdb_data: + prometheus_data: + grafana-storage: + loki-data: \ No newline at end of file diff --git a/grafana/provisioning/dashboards/README.md b/grafana/provisioning/dashboards/README.md new file mode 100644 index 00000000..61be9492 --- /dev/null +++ b/grafana/provisioning/dashboards/README.md @@ -0,0 +1,243 @@ +# Meet@Mensa Grafana Dashboards Documentation + +This directory contains Grafana dashboards for monitoring the Meet@Mensa microservices application. Each dashboard is designed to provide specific insights into different aspects of the system. + +**Business Request**: Any HTTP/API request that directly relates to core business functionality (e.g., creating match requests, user registration, GenAI interactions) - distinct from technical requests like health checks. + +## Dashboard Overview + +### 1. **Meet@Mensa Overview** (`meetatmensa-overview.json`) +**Purpose**: High-level system overview for executive and operational monitoring + +**Key Metrics Monitored**: +- **Request Rate (req/sec)**: Total requests per second across all services + - *Why monitor*: Indicates system load and user activity + - *Alert threshold*: >80 req/sec (red), >100 req/sec (yellow) +- **Active Requests**: Number of concurrent requests being processed + - *Why monitor*: System load and capacity utilization + - *Alert threshold*: >100 active requests (warning) +- **Memory Usage**: Memory consumption across services + - *Why monitor*: Resource utilization and potential memory leaks + - *Alert threshold*: >80% (warning), >90% (critical) +- **Active Threads**: Number of active threads per service + - *Why monitor*: Thread pool utilization and performance + +**Alerts Configured**: +- `ServiceDown`: Triggers when any service is down for >1 minute +- `HighErrorRate`: Triggers when error rate >5% for 5 minutes +- `CriticalErrorRate`: Triggers when error rate >10% for 3 minutes + +--- + +### 2. **Meet@Mensa Application** (`meetatmensa-application.json`) +**Purpose**: Business-focused metrics and application performance + +**Key Metrics Monitored**: +- **Business Request Rate**: Requests per second for business operations + - *Why monitor*: Track user engagement and feature usage +- **Active Business Requests**: Number of concurrent business operations + - *Why monitor*: Business activity and system load +- **Database Connections by Service**: Active database connections per service + - *Why monitor*: Database connection pool utilization +- **Database Idle Connections by Service**: Idle database connections per service + - *Why monitor*: Connection pool efficiency and resource management + +**Alerts Configured**: +- `LowRequestRate`: Triggers when request rate <0.1 req/sec for 10 minutes +- `HighActiveRequests`: Triggers when active requests >100 for 5 minutes + +--- + +### 3. **Meet@Mensa Microservices** (`meetatmensa-microservices.json`) +**Purpose**: Detailed service-level monitoring and troubleshooting + +**Key Metrics Monitored**: +- **Request Rate by Service**: Individual service request rates + - *Why monitor*: Identify bottlenecks and service-specific issues +- **JVM Memory Usage (%)**: Java Virtual Machine memory utilization per service + - *Why monitor*: Memory leaks and resource management +- **Active Threads**: Number of active threads per service + - *Why monitor*: Thread pool utilization and performance +- **Database Active Connections**: Active database connections per service + - *Why monitor*: Database performance and connection management + +**Alerts Configured**: +- `HighMemoryUsage`: Triggers when memory usage >80% for 10 minutes +- `CriticalMemoryUsage`: Triggers when memory usage >90% for 5 minutes +- `DatabaseConnectionPoolExhausted`: Triggers when pool usage >80% for 5 minutes +- `DatabaseConnectionPoolFull`: Triggers when pool usage >95% for 2 minutes + +--- + +### 4. **Meet@Mensa Logs** (`meetatmensa-logs.json`) +**Purpose**: Centralized log analysis and error tracking + +**Key Metrics Monitored**: +- **Error and Warning Rate**: Log-based error and warning frequency + - *Why monitor*: Proactive error detection and debugging +- **Error and Warning Logs**: Detailed error and warning log entries + - *Why monitor*: Identify problematic services and error patterns +- **Info Logs**: Information-level log entries + - *Why monitor*: General system activity and debugging + +**Data Source**: Loki (log aggregation) +**Query Examples**: +- `sum(rate({job="meetatmensa-app"} | json | level="ERROR" [5m])) by (container_name)` +- `sum(rate({job="meetatmensa-app"} | json | level="WARN" [5m])) by (container_name)` + +--- + +### 5. **Meet@Mensa Custom Metrics** (`meetatmensa-custom-metrics.json`) +**Purpose**: Business-specific metrics and KPIs + +**Key Metrics Monitored**: +- **Total Users Created**: Cumulative user registrations + - *Why monitor*: Business growth and user acquisition + - *Metric name*: `users_total` +- **Total Match Requests Created**: Cumulative match requests + - *Why monitor*: Core business activity and user engagement + - *Metric name*: `match_requests_total` +- **Total GenAI Requests**: Cumulative AI-powered conversation starter requests + - *Why monitor*: Feature adoption and AI service performance + - *Metric name*: `genai_requests_total` +- **Registration and Request Rates**: User registration and match request rates over time + - *Why monitor*: Business activity trends and user engagement +- **GenAI Request Rate**: AI service requests per time period + - *Why monitor*: AI feature usage patterns and performance + +**Note**: These metrics require custom implementation in the application code. + +--- + +### 6. **Sample Dashboard** (`sample-dashboard.json`) +**Purpose**: Template and example dashboard for reference + +**Key Metrics Monitored**: +- **New panel**: Example panel for testing and learning + - *Why monitor*: Template for creating new dashboard panels + +**Usage**: +- Reference for creating new dashboards +- Testing dashboard configurations +- Learning Grafana dashboard structure + +--- + +## Alert Strategy + +### Alert Severity Levels + +1. **Critical (Red)**: Immediate action required + - Service down + - High error rates (>10%) + - Critical response times (>2s) + - Memory usage >90% + +2. **Warning (Yellow)**: Attention needed + - High error rates (>5%) + - High response times (>1s) + - Memory usage >80% + - Database connection pool >80% + +3. **Info (Blue)**: Monitoring + - Low request rates + - High active requests + +### Alert Response Actions + +**Service Down**: +1. Check container status: `docker ps` +2. Check service logs: `docker logs ` +3. Restart service if needed: `docker-compose restart ` + +**High Error Rate**: +1. Check application logs in Grafana +2. Review recent deployments +3. Check database connectivity +4. Scale service if needed + +**High Response Time**: +1. Check CPU and memory usage +2. Review database performance +3. Check network connectivity +4. Consider service scaling + +**Memory Issues**: +1. Check for memory leaks +2. Review garbage collection logs +3. Increase memory limits if needed +4. Restart service if critical + +--- + +## Data Sources + +### Prometheus +- **Purpose**: Metrics collection and storage +- **URL**: `http://prometheus:9090` +- **Metrics**: System metrics, application metrics, custom business metrics + +### Loki +- **Purpose**: Log aggregation and querying +- **URL**: `http://loki:3100` +- **Data**: Application logs, error logs, access logs + +--- + +## Dashboard Refresh and Updates + +### Automatic Refresh +- Dashboards refresh every 30 seconds by default +- Time range: Last 1 hour (configurable) + +### Manual Updates +To apply dashboard changes: +```bash +# Restart Grafana container +docker-compose -f deployment/docker/compose.yml restart grafana + +# Or restart the entire stack +docker-compose -f deployment/docker/compose.yml down +docker-compose -f deployment/docker/compose.yml up -d +``` + +### Dashboard Provisioning +Dashboards are automatically provisioned from JSON files in this directory. Changes to JSON files require a Grafana restart to take effect. + +--- + +## Troubleshooting + +### No Data in Dashboards +1. **Check Prometheus targets**: Visit `http://localhost:9090/targets` +2. **Verify service endpoints**: Check `/actuator/prometheus` endpoints +3. **Check time range**: Ensure dashboard time range includes data +4. **Verify metric names**: Use Prometheus UI to search for metrics + +### Missing Custom Metrics +1. **Check application code**: Ensure metrics are properly implemented +2. **Verify metric names**: Match exact names in dashboard queries +3. **Check Prometheus configuration**: Ensure services are scraped +4. **Restart services**: Reload metrics after code changes + +### Log Issues +1. **Check Promtail configuration**: Verify log collection setup +2. **Check Loki connectivity**: Ensure Loki is running and accessible +3. **Verify log format**: Ensure logs are in expected JSON format +4. **Check container logs**: Verify logs are being generated + +--- + +## Best Practices + +1. **Monitor Key Business Metrics**: Focus on metrics that impact user experience +2. **Set Appropriate Thresholds**: Base thresholds on historical data and SLAs +3. **Use Meaningful Alert Messages**: Include actionable information in alerts +4. **Regular Dashboard Reviews**: Update dashboards based on changing needs +5. **Document Changes**: Update this README when adding new metrics or alerts + +--- + +## Contact + +For questions about monitoring setup or dashboard configuration, refer to the team documentation or contact the DevOps team. \ No newline at end of file diff --git a/grafana/provisioning/dashboards/dashboards.yml b/grafana/provisioning/dashboards/dashboards.yml new file mode 100644 index 00000000..b2e8a9b4 --- /dev/null +++ b/grafana/provisioning/dashboards/dashboards.yml @@ -0,0 +1,24 @@ +apiVersion: 1 + +providers: + - name: 'default' + orgId: 1 + folder: '' + type: file + disableDeletion: false + updateIntervalSeconds: 10 + allowUiUpdates: true + options: + path: /etc/grafana/provisioning/dashboards + foldersFromFilesStructure: true + + - name: 'meetatmensa-dashboards' + orgId: 1 + folder: 'Meet@Mensa' + type: file + disableDeletion: false + updateIntervalSeconds: 10 + allowUiUpdates: true + options: + path: /etc/grafana/provisioning/dashboards + foldersFromFilesStructure: false \ No newline at end of file diff --git a/grafana/provisioning/dashboards/meetatmensa-application.json b/grafana/provisioning/dashboards/meetatmensa-application.json new file mode 100644 index 00000000..adc6d05e --- /dev/null +++ b/grafana/provisioning/dashboards/meetatmensa-application.json @@ -0,0 +1,438 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Application-specific metrics dashboard for Meet@Mensa - shows business metrics, user activity, and application performance", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(rate(http_server_requests_seconds_count{job=~\".*meetatmensa.*\"}[5m])) by (job)", + "legendFormat": "{{job}}", + "range": true, + "refId": "A" + } + ], + "title": "Business Request Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 100 + }, + { + "color": "red", + "value": 200 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "http_server_requests_active_seconds_max{job=~\".*meetatmensa.*\"}", + "legendFormat": "{{job}}", + "range": true, + "refId": "A" + } + ], + "title": "Active Business Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 20 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "hikaricp_connections_active{job=~\".*meetatmensa.*\"}", + "legendFormat": "{{job}}", + "range": true, + "refId": "A" + } + ], + "title": "Database Connections by Service", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 1000 + }, + { + "color": "red", + "value": 5000 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "hikaricp_connections_idle{job=~\".*meetatmensa.*\"}", + "legendFormat": "{{job}}", + "range": true, + "refId": "A" + } + ], + "title": "Database Idle Connections by Service", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 41, + "style": "dark", + "tags": ["meetatmensa", "application"], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Meet@Mensa - Application Metrics", + "uid": "meetatmensa-application", + "version": 1 +} \ No newline at end of file diff --git a/grafana/provisioning/dashboards/meetatmensa-custom-metrics.json b/grafana/provisioning/dashboards/meetatmensa-custom-metrics.json new file mode 100644 index 00000000..581f5fc7 --- /dev/null +++ b/grafana/provisioning/dashboards/meetatmensa-custom-metrics.json @@ -0,0 +1,532 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Custom business metrics dashboard for Meet@Mensa - shows user registrations, match requests, and GenAI usage", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "users_total", + "legendFormat": "Total Users Created", + "range": true, + "refId": "A" + } + ], + "title": "Total Users Created", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "match_requests_total", + "legendFormat": "Total Match Requests Created", + "range": true, + "refId": "A" + } + ], + "title": "Total Match Requests Created", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "genai_requests_total{job=\"meetatmensa-genai\"}", + "legendFormat": "Total GenAI Requests", + "range": true, + "refId": "A" + } + ], + "title": "Total GenAI Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "rate(users_total{job=\"meetatmensa-user\"}[5m])", + "legendFormat": "User Registration Rate", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "rate(match_requests_total{job=\"meetatmensa-matching\"}[5m])", + "legendFormat": "Match Request Creation Rate", + "range": true, + "refId": "B" + } + ], + "title": "Registration and Request Rates", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 5, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "rate(genai_requests_total{job=\"meetatmensa-genai\"}[5m])", + "legendFormat": "GenAI Request Rate", + "range": true, + "refId": "A" + } + ], + "title": "GenAI Request Rate", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 41, + "style": "dark", + "tags": ["meetatmensa", "custom-metrics", "business"], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Meet@Mensa - Custom Business Metrics", + "uid": "meetatmensa-custom-metrics", + "version": 1 +} \ No newline at end of file diff --git a/grafana/provisioning/dashboards/meetatmensa-logs.json b/grafana/provisioning/dashboards/meetatmensa-logs.json new file mode 100644 index 00000000..edc2c3fd --- /dev/null +++ b/grafana/provisioning/dashboards/meetatmensa-logs.json @@ -0,0 +1,327 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Centralized logs dashboard for Meet@Mensa - view and analyze logs from all microservices", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "loki", + "uid": "P8E80F9AEF21F6940" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "P8E80F9AEF21F6940" + }, + "expr": "sum(rate({job=\"meetatmensa-app\"} | json | level=\"ERROR\" [5m])) by (container_name)", + "legendFormat": "{{container_name}} - Errors", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "loki", + "uid": "P8E80F9AEF21F6940" + }, + "expr": "sum(rate({job=\"meetatmensa-app\"} | json | level=\"WARN\" [5m])) by (container_name)", + "legendFormat": "{{container_name}} - Warnings", + "range": true, + "refId": "B" + } + ], + "title": "Error and Warning Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "loki", + "uid": "P8E80F9AEF21F6940" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "P8E80F9AEF21F6940" + }, + "expr": "{job=\"meetatmensa-app\"} | json | level=~\"ERROR|WARN\"", + "refId": "A" + } + ], + "title": "Error and Warning Logs", + "type": "logs" + }, + { + "datasource": { + "type": "loki", + "uid": "P8E80F9AEF21F6940" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 4, + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "P8E80F9AEF21F6940" + }, + "expr": "{job=\"meetatmensa-app\"} | json | level=\"INFO\"", + "refId": "A" + } + ], + "title": "Info Logs", + "type": "logs" + } + ], + "refresh": "30s", + "schemaVersion": 41, + "style": "dark", + "tags": ["meetatmensa", "logs"], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "loki", + "uid": "P8E80F9AEF21F6940" + }, + "definition": "label_values(container_name)", + "hide": 0, + "includeAll": true, + "label": "Service", + "multi": true, + "name": "service", + "options": [], + "query": { + "query": "label_values(container_name)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "loki", + "uid": "P8E80F9AEF21F6940" + }, + "definition": "label_values(level)", + "hide": 0, + "includeAll": true, + "label": "Log Level", + "multi": true, + "name": "level", + "options": [], + "query": { + "query": "label_values(level)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Meet@Mensa - Logs", + "uid": "meetatmensa-logs", + "version": 1 +} \ No newline at end of file diff --git a/grafana/provisioning/dashboards/meetatmensa-microservices.json b/grafana/provisioning/dashboards/meetatmensa-microservices.json new file mode 100644 index 00000000..be65d303 --- /dev/null +++ b/grafana/provisioning/dashboards/meetatmensa-microservices.json @@ -0,0 +1,466 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Detailed microservices dashboard for Meet@Mensa - shows service-specific metrics and performance indicators", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(rate(http_server_requests_seconds_count{job=~\".*meetatmensa.*\"}[5m])) by (job)", + "legendFormat": "{{job}}", + "range": true, + "refId": "A" + } + ], + "title": "Request Rate by Service", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_memory_used_bytes{job=~\".*meetatmensa.*\"} / jvm_memory_max_bytes{job=~\".*meetatmensa.*\"} * 100", + "legendFormat": "{{job}} - {{area}}", + "range": true, + "refId": "A" + } + ], + "title": "JVM Memory Usage (%)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 70 + }, + { + "color": "red", + "value": 90 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_threads_live_threads{job=~\".*meetatmensa.*\"}", + "legendFormat": "{{job}}", + "range": true, + "refId": "A" + } + ], + "title": "Active Threads", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 20 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "hikaricp_connections_active{job=~\".*meetatmensa.*\"}", + "legendFormat": "{{job}}", + "range": true, + "refId": "A" + } + ], + "title": "Database Active Connections", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 41, + "style": "dark", + "tags": ["meetatmensa", "microservices"], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "label_values(job, job)", + "hide": 0, + "includeAll": true, + "label": "Service", + "multi": true, + "name": "service", + "options": [], + "query": { + "query": "label_values(job, job)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Meet@Mensa - Microservices", + "uid": "meetatmensa-microservices", + "version": 1 +} \ No newline at end of file diff --git a/grafana/provisioning/dashboards/meetatmensa-overview.json b/grafana/provisioning/dashboards/meetatmensa-overview.json new file mode 100644 index 00000000..98deeb11 --- /dev/null +++ b/grafana/provisioning/dashboards/meetatmensa-overview.json @@ -0,0 +1,438 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Overview dashboard for Meet@Mensa microservices - shows high-level metrics across all services", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(rate(http_server_requests_seconds_count{job=~\".*meetatmensa.*\"}[5m])) by (job)", + "legendFormat": "{{job}}", + "range": true, + "refId": "A" + } + ], + "title": "Request Rate (req/sec)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 100 + }, + { + "color": "red", + "value": 200 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "http_server_requests_active_seconds_max{job=~\".*meetatmensa.*\"}", + "legendFormat": "{{job}}", + "range": true, + "refId": "A" + } + ], + "title": "Active Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 80 + }, + { + "color": "red", + "value": 90 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "process_resident_memory_bytes{job=~\".*meetatmensa.*\"}", + "legendFormat": "{{job}}", + "range": true, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 20 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "jvm_threads_live_threads{job=~\".*meetatmensa.*\"}", + "legendFormat": "{{job}}", + "range": true, + "refId": "A" + } + ], + "title": "Active Threads", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 41, + "style": "dark", + "tags": ["meetatmensa", "overview"], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Meet@Mensa - Overview", + "uid": "meetatmensa-overview", + "version": 1 +} \ No newline at end of file diff --git a/grafana/provisioning/datasources/loki.yaml b/grafana/provisioning/datasources/loki.yaml new file mode 100644 index 00000000..f91c77c0 --- /dev/null +++ b/grafana/provisioning/datasources/loki.yaml @@ -0,0 +1,13 @@ +apiVersion: 1 + +datasources: + - name: Loki + type: loki + access: proxy + url: http://loki:3100 + uid: P8E80F9AEF21F6940 + isDefault: false + jsonData: + maxLines: 1000 + httpMethod: POST + version: 1 \ No newline at end of file diff --git a/grafana/provisioning/datasources/prometheus.yaml b/grafana/provisioning/datasources/prometheus.yaml new file mode 100644 index 00000000..86966357 --- /dev/null +++ b/grafana/provisioning/datasources/prometheus.yaml @@ -0,0 +1,13 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + uid: PBFA97CFB590B2093 + isDefault: true + jsonData: + timeInterval: 15s + version: 1 + diff --git a/grafana/provisioning/init_grafana.sh b/grafana/provisioning/init_grafana.sh new file mode 100644 index 00000000..58152936 --- /dev/null +++ b/grafana/provisioning/init_grafana.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +echo "Starting Grafana initialization process!" + +echo "Checking provisioning files." +#Grafana automatically provisions if the path is like this. +DATASOURCE_FILE='/etc/grafana/provisioning/datasource/prometheus.yml' +DASHBOARD_DIR='/etc/grafana/provisioning/dashboards' + +if [ ! -f "$DATASOURCE_FILE" ]; then + log "WARNING: Data source file $DATASOURCE_FILE not found!" +else + log "Data source configuration found: $(ls -l $DATASOURCE_FILE)" +fi + +if [ ! -d "$DASHBOARD_DIR" ]; then + log "WARNING: Dashboard directory $DASHBOARD_DIR not found!" +else + log "Dashboard files found: $(ls -l $DASHBOARD_DIR/*.json 2>/dev/null | wc -l)" +fi + + +echo "Starting Grafana Server." +exec /run.sh \ No newline at end of file diff --git a/grafana/provisioning/notifiers/alerts.yaml b/grafana/provisioning/notifiers/alerts.yaml new file mode 100644 index 00000000..579f3342 --- /dev/null +++ b/grafana/provisioning/notifiers/alerts.yaml @@ -0,0 +1,149 @@ +apiVersion: 1 + +groups: + - name: meetatmensa-availability + rules: + - alert: ServiceDown + expr: up{job=~".*meetatmensa.*"} == 0 + for: 1m + labels: + severity: critical + service: "{{ $labels.job }}" + annotations: + summary: "Service {{ $labels.job }} is down" + description: "Service {{ $labels.job }} has been down for more than 1 minute" + + - alert: HighErrorRate + expr: sum(rate(http_server_requests_seconds_count{job=~".*meetatmensa.*", status=~"5.."}[5m])) by (job) / sum(rate(http_server_requests_seconds_count{job=~".*meetatmensa.*"}[5m])) by (job) * 100 > 5 + for: 5m + labels: + severity: warning + service: "{{ $labels.job }}" + annotations: + summary: "High error rate for {{ $labels.job }}" + description: "Error rate is {{ $value }}% for {{ $labels.job }} (threshold: 5%)" + + - alert: CriticalErrorRate + expr: sum(rate(http_server_requests_seconds_count{job=~".*meetatmensa.*", status=~"5.."}[5m])) by (job) / sum(rate(http_server_requests_seconds_count{job=~".*meetatmensa.*"}[5m])) by (job) * 100 > 10 + for: 3m + labels: + severity: critical + service: "{{ $labels.job }}" + annotations: + summary: "Critical error rate for {{ $labels.job }}" + description: "Error rate is {{ $value }}% for {{ $labels.job }} (threshold: 10%)" + + - name: meetatmensa-performance + rules: + - alert: HighResponseTime + expr: histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket{job=~".*meetatmensa.*"}[5m])) by (job, le)) * 1000 > 1000 + for: 5m + labels: + severity: warning + service: "{{ $labels.job }}" + annotations: + summary: "High response time for {{ $labels.job }}" + description: "95th percentile response time is {{ $value }}ms for {{ $labels.job }} (threshold: 1000ms)" + + - alert: CriticalResponseTime + expr: histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket{job=~".*meetatmensa.*"}[5m])) by (job, le)) * 1000 > 2000 + for: 3m + labels: + severity: critical + service: "{{ $labels.job }}" + annotations: + summary: "Critical response time for {{ $labels.job }}" + description: "95th percentile response time is {{ $value }}ms for {{ $labels.job }} (threshold: 2000ms)" + + - alert: HighMemoryUsage + expr: jvm_memory_used_bytes{job=~".*meetatmensa.*"} / jvm_memory_max_bytes{job=~".*meetatmensa.*"} * 100 > 80 + for: 10m + labels: + severity: warning + service: "{{ $labels.job }}" + annotations: + summary: "High memory usage for {{ $labels.job }}" + description: "Memory usage is {{ $value }}% for {{ $labels.job }} (threshold: 80%)" + + - alert: CriticalMemoryUsage + expr: jvm_memory_used_bytes{job=~".*meetatmensa.*"} / jvm_memory_max_bytes{job=~".*meetatmensa.*"} * 100 > 90 + for: 5m + labels: + severity: critical + service: "{{ $labels.job }}" + annotations: + summary: "Critical memory usage for {{ $labels.job }}" + description: "Memory usage is {{ $value }}% for {{ $labels.job }} (threshold: 90%)" + + - name: meetatmensa-database + rules: + - alert: DatabaseConnectionPoolExhausted + expr: hikaricp_connections_active{job=~".*meetatmensa.*"} / hikaricp_connections_max{job=~".*meetatmensa.*"} * 100 > 80 + for: 5m + labels: + severity: warning + service: "{{ $labels.job }}" + annotations: + summary: "Database connection pool nearly exhausted for {{ $labels.job }}" + description: "Database connection pool usage is {{ $value }}% for {{ $labels.job }} (threshold: 80%)" + + - alert: DatabaseConnectionPoolFull + expr: hikaricp_connections_active{job=~".*meetatmensa.*"} / hikaricp_connections_max{job=~".*meetatmensa.*"} * 100 > 95 + for: 2m + labels: + severity: critical + service: "{{ $labels.job }}" + annotations: + summary: "Database connection pool full for {{ $labels.job }}" + description: "Database connection pool usage is {{ $value }}% for {{ $labels.job }} (threshold: 95%)" + + - name: meetatmensa-business + rules: + - alert: LowRequestRate + expr: sum(rate(http_server_requests_seconds_count{job=~".*meetatmensa.*"}[5m])) by (job) < 0.1 + for: 10m + labels: + severity: warning + service: "{{ $labels.job }}" + annotations: + summary: "Low request rate for {{ $labels.job }}" + description: "Request rate is {{ $value }} req/sec for {{ $labels.job }} (threshold: 0.1 req/sec)" + + - alert: HighActiveRequests + expr: http_server_requests_active_seconds_max{job=~".*meetatmensa.*"} > 100 + for: 5m + labels: + severity: warning + service: "{{ $labels.job }}" + annotations: + summary: "High number of active requests for {{ $labels.job }}" + description: "Active requests: {{ $value }} for {{ $labels.job }} (threshold: 100)" + + - name: meetatmensa-system + rules: + - alert: HighCPUUsage + expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80 + for: 10m + labels: + severity: warning + annotations: + summary: "High CPU usage on {{ $labels.instance }}" + description: "CPU usage is {{ $value }}% for last 10 minutes (threshold: 80%)" + + - alert: HighSystemMemoryUsage + expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100 > 85 + for: 15m + labels: + severity: warning + annotations: + summary: "High memory usage on {{ $labels.instance }}" + description: "Memory usage is {{ $value }}% for last 15 minutes (threshold: 85%)" + + - alert: DiskSpaceLow + expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 10 + for: 10m + labels: + severity: warning + annotations: + summary: "Low disk space on {{ $labels.instance }}" + description: "Disk space usage is {{ $value }}% (threshold: 90%)" diff --git a/loki/loki-config.yaml b/loki/loki-config.yaml new file mode 100644 index 00000000..97cb4573 --- /dev/null +++ b/loki/loki-config.yaml @@ -0,0 +1,26 @@ +auth_enabled: false + +server: + http_listen_port: 3100 + +common: + path_prefix: /loki + storage: + filesystem: + chunks_directory: /loki/chunks + rules_directory: /loki/rules + replication_factor: 1 + ring: + instance_addr: 127.0.0.1 + kvstore: + store: inmemory + +schema_config: + configs: + - from: 2020-10-24 + store: boltdb-shipper + object_store: filesystem + schema: v11 + index: + prefix: index_ + period: 24h \ No newline at end of file diff --git a/prometheus/alerts.yml b/prometheus/alerts.yml new file mode 100644 index 00000000..da89020c --- /dev/null +++ b/prometheus/alerts.yml @@ -0,0 +1,118 @@ +groups: + - name: meetatmensa-availability + rules: + - alert: ServiceDown + expr: up{job=~".*meetatmensa.*"} == 0 + for: 1m + labels: + severity: critical + service: "{{ $labels.job }}" + annotations: + summary: "Service {{ $labels.job }} is down" + description: "Service {{ $labels.job }} has been down for more than 1 minute" + + - alert: HighErrorRate + expr: sum(rate(http_server_requests_seconds_count{job=~".*meetatmensa.*", status=~"5.."}[5m])) by (job) / sum(rate(http_server_requests_seconds_count{job=~".*meetatmensa.*"}[5m])) by (job) * 100 > 5 + for: 5m + labels: + severity: warning + service: "{{ $labels.job }}" + annotations: + summary: "High error rate for {{ $labels.job }}" + description: "Error rate is {{ $value }}% for {{ $labels.job }} (threshold: 5%)" + + - alert: CriticalErrorRate + expr: sum(rate(http_server_requests_seconds_count{job=~".*meetatmensa.*", status=~"5.."}[5m])) by (job) / sum(rate(http_server_requests_seconds_count{job=~".*meetatmensa.*"}[5m])) by (job) * 100 > 10 + for: 3m + labels: + severity: critical + service: "{{ $labels.job }}" + annotations: + summary: "Critical error rate for {{ $labels.job }}" + description: "Error rate is {{ $value }}% for {{ $labels.job }} (threshold: 10%)" + + - name: meetatmensa-performance + rules: + - alert: HighResponseTime + expr: histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket{job=~".*meetatmensa.*"}[5m])) by (job, le)) * 1000 > 1000 + for: 5m + labels: + severity: warning + service: "{{ $labels.job }}" + annotations: + summary: "High response time for {{ $labels.job }}" + description: "95th percentile response time is {{ $value }}ms for {{ $labels.job }} (threshold: 1000ms)" + + - alert: CriticalResponseTime + expr: histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket{job=~".*meetatmensa.*"}[5m])) by (job, le)) * 1000 > 2000 + for: 3m + labels: + severity: critical + service: "{{ $labels.job }}" + annotations: + summary: "Critical response time for {{ $labels.job }}" + description: "95th percentile response time is {{ $value }}ms for {{ $labels.job }} (threshold: 2000ms)" + + - alert: HighMemoryUsage + expr: jvm_memory_used_bytes{job=~".*meetatmensa.*"} / jvm_memory_max_bytes{job=~".*meetatmensa.*"} * 100 > 80 + for: 10m + labels: + severity: warning + service: "{{ $labels.job }}" + annotations: + summary: "High memory usage for {{ $labels.job }}" + description: "Memory usage is {{ $value }}% for {{ $labels.job }} (threshold: 80%)" + + - alert: CriticalMemoryUsage + expr: jvm_memory_used_bytes{job=~".*meetatmensa.*"} / jvm_memory_max_bytes{job=~".*meetatmensa.*"} * 100 > 90 + for: 5m + labels: + severity: critical + service: "{{ $labels.job }}" + annotations: + summary: "Critical memory usage for {{ $labels.job }}" + description: "Memory usage is {{ $value }}% for {{ $labels.job }} (threshold: 90%)" + + - name: meetatmensa-database + rules: + - alert: DatabaseConnectionPoolExhausted + expr: hikaricp_connections_active{job=~".*meetatmensa.*"} / hikaricp_connections_max{job=~".*meetatmensa.*"} * 100 > 80 + for: 5m + labels: + severity: warning + service: "{{ $labels.job }}" + annotations: + summary: "Database connection pool nearly exhausted for {{ $labels.job }}" + description: "Database connection pool usage is {{ $value }}% for {{ $labels.job }} (threshold: 80%)" + + - alert: DatabaseConnectionPoolFull + expr: hikaricp_connections_active{job=~".*meetatmensa.*"} / hikaricp_connections_max{job=~".*meetatmensa.*"} * 100 > 95 + for: 2m + labels: + severity: critical + service: "{{ $labels.job }}" + annotations: + summary: "Database connection pool full for {{ $labels.job }}" + description: "Database connection pool usage is {{ $value }}% for {{ $labels.job }} (threshold: 95%)" + + - name: meetatmensa-business + rules: + - alert: LowRequestRate + expr: sum(rate(http_server_requests_seconds_count{job=~".*meetatmensa.*"}[5m])) by (job) < 0.1 + for: 10m + labels: + severity: warning + service: "{{ $labels.job }}" + annotations: + summary: "Low request rate for {{ $labels.job }}" + description: "Request rate is {{ $value }} req/sec for {{ $labels.job }} (threshold: 0.1 req/sec)" + + - alert: HighActiveRequests + expr: http_server_requests_active_seconds_max{job=~".*meetatmensa.*"} > 100 + for: 5m + labels: + severity: warning + service: "{{ $labels.job }}" + annotations: + summary: "High number of active requests for {{ $labels.job }}" + description: "Active requests: {{ $value }} for {{ $labels.job }} (threshold: 100)" \ No newline at end of file diff --git a/prometheus/prometheus.yml b/prometheus/prometheus.yml new file mode 100644 index 00000000..fe139ba3 --- /dev/null +++ b/prometheus/prometheus.yml @@ -0,0 +1,37 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +rule_files: + - "alerts.yml" + +alerting: + alertmanagers: + - static_configs: + - targets: + # - alertmanager:9093 + +scrape_configs: + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + + - job_name: 'meetatmensa-gateway' + metrics_path: '/actuator/prometheus' + static_configs: + - targets: ['meetatmensa-gateway:8080'] + + - job_name: 'meetatmensa-matching' + metrics_path: '/actuator/prometheus' + static_configs: + - targets: ['meetatmensa-matching:80'] + + - job_name: 'meetatmensa-user' + metrics_path: '/actuator/prometheus' + static_configs: + - targets: ['meetatmensa-user:80'] + + - job_name: 'meetatmensa-genai' + metrics_path: '/metrics' + static_configs: + - targets: ['meetatmensa-genai:80'] \ No newline at end of file diff --git a/promtail/promtail.yml b/promtail/promtail.yml new file mode 100644 index 00000000..20f4a5aa --- /dev/null +++ b/promtail/promtail.yml @@ -0,0 +1,35 @@ +server: + http_listen_port: 9080 + grpc_listen_port: 0 + +positions: + filename: /tmp/positions.yaml + +clients: + - url: http://loki:3100/loki/api/v1/push + +scrape_configs: + - job_name: meetatmensa-logs + static_configs: + - targets: [localhost] + labels: + job: meetatmensa-app + __path__: /var/lib/docker/containers/*/*.log + pipeline_stages: + - docker: {} + - json: + expressions: + timestamp: timestamp + level: level + logger: logger + message: message + exception: exception + - labels: + level: + logger: + - match: + selector: '{container_name=~"meetatmensa-.*"}' + stages: + - labels: + container_name: + service: meetatmensa \ No newline at end of file diff --git a/server/matching/src/main/java/meet_at_mensa/matching/service/MatchRequestService.java b/server/matching/src/main/java/meet_at_mensa/matching/service/MatchRequestService.java index 3eac8eed..63429387 100644 --- a/server/matching/src/main/java/meet_at_mensa/matching/service/MatchRequestService.java +++ b/server/matching/src/main/java/meet_at_mensa/matching/service/MatchRequestService.java @@ -18,6 +18,10 @@ import meet_at_mensa.matching.model.MatchRequestEntity; import meet_at_mensa.matching.repository.MatchRequestRepository; +// Add Micrometer imports for Prometheus metrics +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; + @Service public class MatchRequestService { @@ -27,6 +31,16 @@ public class MatchRequestService { @Autowired private TimeslotService timeslotService; + // Prometheus metrics + private final Counter matchRequestsCreatedCounter; + + public MatchRequestService(MeterRegistry meterRegistry) { + // Initialize custom metrics + this.matchRequestsCreatedCounter = Counter.builder("match_requests_created_total") + .description("Total number of match requests created") + .register(meterRegistry); + } + /** * Fetch a single MatchRequest based on its requestID * @@ -186,6 +200,9 @@ public MatchRequest registerRequest(MatchRequestNew requestNew) { requestNew.getTimeslot() // timeslots ); + // Increment the counter for match request creation + matchRequestsCreatedCounter.increment(); + // return newly generated request return getRequest(requestEntity.getRequestID()); @@ -386,4 +403,13 @@ protected void cleanupExpired() { } } } + + /** + * Gets the current count of match requests created (for testing/debugging purposes) + * + * @return current count of match requests created + */ + public double getMatchRequestsCreatedCount() { + return matchRequestsCreatedCounter.count(); + } } \ No newline at end of file diff --git a/server/matching/src/test/java/meet_at_mensa/matching/service/MatchRequestServiceMetricsTest.java b/server/matching/src/test/java/meet_at_mensa/matching/service/MatchRequestServiceMetricsTest.java new file mode 100644 index 00000000..0290f757 --- /dev/null +++ b/server/matching/src/test/java/meet_at_mensa/matching/service/MatchRequestServiceMetricsTest.java @@ -0,0 +1,132 @@ +package meet_at_mensa.matching.service; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openapitools.model.MatchRequestNew; +import org.openapitools.model.MatchPreferences; +import org.openapitools.model.Location; + +import meet_at_mensa.matching.repository.MatchRequestRepository; +import meet_at_mensa.matching.exception.RequestOverlapException; +import meet_at_mensa.matching.model.MatchRequestEntity; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class MatchRequestServiceMetricsTest { + + @Mock + private MatchRequestRepository requestRepository; + + @Mock + private TimeslotService timeslotService; + + private MeterRegistry meterRegistry; + private MatchRequestService matchRequestService; + + @BeforeEach + void setUp() { + meterRegistry = new SimpleMeterRegistry(); + matchRequestService = new MatchRequestService(meterRegistry); + + // Inject mocked repositories using reflection for testing + try { + java.lang.reflect.Field requestRepoField = MatchRequestService.class.getDeclaredField("requestRepository"); + requestRepoField.setAccessible(true); + requestRepoField.set(matchRequestService, requestRepository); + + java.lang.reflect.Field timeslotServiceField = MatchRequestService.class.getDeclaredField("timeslotService"); + timeslotServiceField.setAccessible(true); + timeslotServiceField.set(matchRequestService, timeslotService); + } catch (Exception e) { + throw new RuntimeException("Failed to inject mocked repositories", e); + } + } + + @Test + void testMatchRequestsCreatedCounterIncrements() { + // Given + MatchRequestNew requestNew = createValidMatchRequestNew(); + MatchRequestEntity mockEntity = createMockMatchRequestEntity(); + when(requestRepository.save(any())).thenReturn(mockEntity); + when(requestRepository.findByDateAndUserID(any(), any())).thenReturn(new ArrayList<>()); + when(requestRepository.findById(any())).thenReturn(java.util.Optional.of(mockEntity)); + + // When + double initialCount = matchRequestService.getMatchRequestsCreatedCount(); + matchRequestService.registerRequest(requestNew); + double finalCount = matchRequestService.getMatchRequestsCreatedCount(); + + // Then + assertEquals(1.0, finalCount - initialCount, "Match request creation counter should increment by 1"); + } + + @Test + void testMultipleMatchRequestsIncrement() { + // Given + MatchRequestNew requestNew1 = createValidMatchRequestNew(); + MatchRequestNew requestNew2 = createValidMatchRequestNew(); + MatchRequestEntity mockEntity1 = createMockMatchRequestEntity(); + MatchRequestEntity mockEntity2 = createMockMatchRequestEntity(); + + when(requestRepository.save(any())).thenReturn(mockEntity1, mockEntity2); + when(requestRepository.findById(any())).thenReturn(java.util.Optional.of(mockEntity1)); + when(requestRepository.findByDateAndUserID(any(), any())).thenReturn(new ArrayList<>()); + + // When + double initialCount = matchRequestService.getMatchRequestsCreatedCount(); + matchRequestService.registerRequest(requestNew1); + matchRequestService.registerRequest(requestNew2); + double finalCount = matchRequestService.getMatchRequestsCreatedCount(); + + // Then + assertEquals(2.0, finalCount - initialCount, "Match request counter should increment by 2"); + } + + @Test + void testMetricsAreRegistered() { + // Verify that the metrics are properly registered in the meter registry + assertNotNull(meterRegistry.find("match_requests_created_total").counter()); + } + + private MatchRequestNew createValidMatchRequestNew() { + MatchRequestNew requestNew = new MatchRequestNew(); + requestNew.setUserID(UUID.randomUUID()); + requestNew.setDate(LocalDate.now().plusDays(1)); + requestNew.setLocation(Location.GARCHING); + requestNew.setTimeslot(Arrays.asList(1, 2, 3)); + + MatchPreferences preferences = new MatchPreferences(); + preferences.setDegreePref(true); + preferences.setAgePref(true); + preferences.setGenderPref(false); + requestNew.setPreferences(preferences); + + return requestNew; + } + + private MatchRequestEntity createMockMatchRequestEntity() { + MatchRequestEntity entity = new MatchRequestEntity(); + // Set required fields using reflection since they might be final + try { + java.lang.reflect.Field idField = MatchRequestEntity.class.getDeclaredField("requestID"); + idField.setAccessible(true); + idField.set(entity, UUID.randomUUID()); + } catch (Exception e) { + throw new RuntimeException("Failed to set request ID", e); + } + return entity; + } +} \ No newline at end of file diff --git a/server/user/src/main/java/meet_at_mensa/user/service/UserService.java b/server/user/src/main/java/meet_at_mensa/user/service/UserService.java index 6b240d4f..872f5c9c 100644 --- a/server/user/src/main/java/meet_at_mensa/user/service/UserService.java +++ b/server/user/src/main/java/meet_at_mensa/user/service/UserService.java @@ -25,6 +25,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +// Add Micrometer imports for Prometheus metrics +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; + @Service public class UserService { @@ -42,6 +46,16 @@ public class UserService { @Autowired IdentityRepository identityRepository; + // Prometheus metrics + private final Counter usersCreatedCounter; + + public UserService(MeterRegistry meterRegistry) { + // Initialize custom metrics + this.usersCreatedCounter = Counter.builder("users_created_total") + .description("Total number of users created") + .register(meterRegistry); + } + /** * Searches the database for a user with id {userID} * @@ -142,6 +156,7 @@ public User registerUser(UserNew userNew) { identityEntity = identityRepository.save(identityEntity); logger.info("User created successfully: {}", userEntity.getUserID()); // throw Exception if duplicate email + usersCreatedCounter.increment(); // Increment the counter for user creation } catch (DataIntegrityViolationException e) { logger.error("User registration failed due to duplicate email: {}", userNew.getEmail()); throw new UserConflictException(e.toString()); @@ -447,4 +462,13 @@ public Boolean isValidEmail(String email) { return email.matches(emailRegex); } + /** + * Gets the current count of users created (for testing/debugging purposes) + * + * @return current count of users created + */ + public double getUsersCreatedCount() { + return usersCreatedCounter.count(); + } + } diff --git a/server/user/src/test/java/meet_at_mensa/user/service/UserServiceMetricsTest.java b/server/user/src/test/java/meet_at_mensa/user/service/UserServiceMetricsTest.java new file mode 100644 index 00000000..157d3b83 --- /dev/null +++ b/server/user/src/test/java/meet_at_mensa/user/service/UserServiceMetricsTest.java @@ -0,0 +1,122 @@ +package meet_at_mensa.user.service; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openapitools.model.UserNew; + +import meet_at_mensa.user.repository.UserRepository; +import meet_at_mensa.user.repository.InterestRepository; +import meet_at_mensa.user.repository.IdentityRepository; +import meet_at_mensa.user.exception.UserMalformedException; + +import java.time.LocalDate; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class UserServiceMetricsTest { + + @Mock + private UserRepository userRepository; + + @Mock + private InterestRepository interestRepository; + + @Mock + private IdentityRepository identityRepository; + + private MeterRegistry meterRegistry; + private UserService userService; + + @BeforeEach + void setUp() { + meterRegistry = new SimpleMeterRegistry(); + userService = new UserService(meterRegistry); + + // Inject mocked repositories using reflection for testing + try { + java.lang.reflect.Field userRepoField = UserService.class.getDeclaredField("userRepository"); + userRepoField.setAccessible(true); + userRepoField.set(userService, userRepository); + + java.lang.reflect.Field interestRepoField = UserService.class.getDeclaredField("interestRepository"); + interestRepoField.setAccessible(true); + interestRepoField.set(userService, interestRepository); + + java.lang.reflect.Field identityRepoField = UserService.class.getDeclaredField("identityRepository"); + identityRepoField.setAccessible(true); + identityRepoField.set(userService, identityRepository); + } catch (Exception e) { + throw new RuntimeException("Failed to inject mocked repositories", e); + } + } + + @Test + void testUsersCreatedCounterIncrements() { + // Given + UserNew userNew = createValidUserNew(); + meet_at_mensa.user.model.UserEntity mockUserEntity = createMockUserEntity(); + when(userRepository.save(any())).thenReturn(mockUserEntity); + when(identityRepository.save(any())).thenReturn(createMockIdentityEntity()); + when(userRepository.findById(mockUserEntity.getUserID())).thenReturn(java.util.Optional.of(mockUserEntity)); + when(interestRepository.findByUserID(mockUserEntity.getUserID())).thenReturn(new ArrayList<>()); + + // When + double initialCount = userService.getUsersCreatedCount(); + userService.registerUser(userNew); + double finalCount = userService.getUsersCreatedCount(); + + // Then + assertEquals(1.0, finalCount - initialCount, "User creation counter should increment by 1"); + } + + + + @Test + void testMetricsAreRegistered() { + // Verify that the metrics are properly registered in the meter registry + assertNotNull(meterRegistry.find("users_created_total").counter()); + } + + private UserNew createValidUserNew() { + UserNew userNew = new UserNew(); + userNew.setEmail("test@example.com"); + userNew.setFirstname("John"); + userNew.setLastname("Doe"); + userNew.setBirthday(LocalDate.of(1990, 1, 1)); + userNew.setGender("MALE"); + userNew.setDegree("Computer Science"); + userNew.setDegreeStart(2020); + userNew.setBio("Test bio"); + userNew.setInterests(Arrays.asList("Programming", "Reading")); + userNew.setAuthID("auth0|123456789"); + return userNew; + } + + private meet_at_mensa.user.model.UserEntity createMockUserEntity() { + meet_at_mensa.user.model.UserEntity entity = new meet_at_mensa.user.model.UserEntity(); + // Set required fields using reflection since they might be final + try { + java.lang.reflect.Field idField = meet_at_mensa.user.model.UserEntity.class.getDeclaredField("userID"); + idField.setAccessible(true); + idField.set(entity, UUID.randomUUID()); + } catch (Exception e) { + throw new RuntimeException("Failed to set user ID", e); + } + return entity; + } + + private meet_at_mensa.user.model.IdentityEntity createMockIdentityEntity() { + return new meet_at_mensa.user.model.IdentityEntity(UUID.randomUUID(), "auth0|123456789"); + } +} \ No newline at end of file