diff --git a/k8s/app/deployment.yaml b/k8s/app/deployment.yaml index 8d51ed2..16ef0dc 100644 --- a/k8s/app/deployment.yaml +++ b/k8s/app/deployment.yaml @@ -18,7 +18,7 @@ spec: spec: containers: - name: camera-onboarding - image: mrsert/camera-onboarding:monitoring + image: mrsert/camera-onboarding:metrics-v3 resources: requests: cpu: "275m" @@ -31,11 +31,12 @@ spec: envFrom: - secretRef: name: camera-onboarding-secrets + livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 - initialDelaySeconds: 30 + initialDelaySeconds: 180 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 6 @@ -43,7 +44,7 @@ spec: httpGet: path: /actuator/health/readiness port: 8080 - initialDelaySeconds: 15 + initialDelaySeconds: 180 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 6 diff --git a/k8s/logging/grafana/custom-app-dashboard.yaml b/k8s/logging/grafana/custom-app-dashboard.yaml new file mode 100644 index 0000000..ec66ced --- /dev/null +++ b/k8s/logging/grafana/custom-app-dashboard.yaml @@ -0,0 +1,1343 @@ +# k8s/logging/grafana/custom-app-dashboard.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-custom-app-dashboard + namespace: logging + labels: + grafana_dashboard: "1" +data: + custom-app-dashboard.json: | + { + "id": null, + "title": "Custom Application Metrics", + "tags": [ + "spring-boot", + "app-metrics", + "camera-onboarding" + ], + "style": "dark", + "timezone": "browser", + "panels": [ + { + "id": 1, + "title": "Request Latency Percentiles", + "type": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket{kubernetes_namespace=\"app\"}[2m])) by (le))", + "legendFormat": "P95 Latency", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.90, sum(rate(http_server_requests_seconds_bucket{kubernetes_namespace=\"app\"}[2m])) by (le))", + "legendFormat": "P90 Latency", + "refId": "B" + }, + { + "expr": "histogram_quantile(0.50, sum(rate(http_server_requests_seconds_bucket{kubernetes_namespace=\"app\"}[2m])) by (le))", + "legendFormat": "P50 Latency", + "refId": "C" + } + ], + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "fieldConfig": { + "defaults": { + "unit": "s", + "min": 0, + "color": { + "mode": "palette-classic" + } + } + }, + "options": { + "legend": { + "displayMode": "table", + "values": [ + "last", + "max" + ] + } + } + }, + { + "id": 2, + "title": "Request Rate (req/sec)", + "type": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sum(rate(http_server_requests_seconds_count{kubernetes_namespace=\"app\"}[1m]))", + "legendFormat": "Total RPS", + "refId": "A" + } + ], + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 8 + }, + "fieldConfig": { + "defaults": { + "unit": "reqps", + "color": { + "mode": "palette-classic" + } + } + } + }, + { + "id": 3, + "title": "Successful Requests (5m)", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sum(increase(http_server_requests_seconds_count{kubernetes_namespace=\"app\", status=~\"2..\"}[5m]))", + "legendFormat": "Successful" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 14 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": null + } + ] + } + } + } + }, + { + "id": 4, + "title": "Failed Requests (5m)", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sum(increase(http_server_requests_seconds_count{kubernetes_namespace=\"app\", status=~\"[45]..\"}[5m]))", + "legendFormat": "Failed" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 14 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 5, + "title": "Success Rate (%)", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "(sum(rate(http_server_requests_seconds_count{kubernetes_namespace=\"app\", status=~\"2..\"}[5m])) / sum(rate(http_server_requests_seconds_count{kubernetes_namespace=\"app\"}[5m]))) * 100", + "legendFormat": "Success Rate" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 14 + }, + "fieldConfig": { + "defaults": { + "unit": "percent", + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 90 + }, + { + "color": "green", + "value": 95 + } + ] + } + } + } + }, + { + "id": 6, + "title": "Request Status Distribution", + "type": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sum by (status) (rate(http_server_requests_seconds_count{kubernetes_namespace=\"app\"}[1m]))", + "legendFormat": "{{status}}" + } + ], + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 20 + }, + "fieldConfig": { + "defaults": { + "unit": "reqps", + "color": { + "mode": "palette-classic" + } + } + } + }, + { + "id": 7, + "title": "DEBUG PANEL: Available HTTP Metrics", + "type": "table", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "group by (__name__) ({__name__=~\".*http.*request.*\", kubernetes_namespace=\"app\"})", + "format": "table" + } + ], + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 20 + } + }, + { + "id": 8, + "title": "Camera Onboarding Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "camera_onboarding_success_total", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 28 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 9, + "title": "Camera Onboarding Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "camera_onboarding_failure_total", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 28 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 10, + "title": "Camera Initialization Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "camera_initialization_success_total", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 28 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 11, + "title": "Camera Initialization Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "camera_initialization_failure_total", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 34 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 12, + "title": "Image Upload Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "image_upload_success_total", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 34 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 13, + "title": "Image Upload Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "image_upload_failure_total", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 34 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 14, + "title": "Image Download Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "image_download_success_total", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 40 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 15, + "title": "Image Download Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "image_download_failure_total", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 40 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 16, + "title": "Location Add Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "location_add_success_total", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 40 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 17, + "title": "Location Add Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "location_add_failure_total", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 46 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 18, + "title": "Light Sensor Create Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_create_success_total{sensor_type=\"LIGHT\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 46 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 19, + "title": "Light Sensor Create Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_create_failure_total{sensor_type=\"LIGHT\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 46 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 20, + "title": "Motion Sensor Create Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_create_success_total{sensor_type=\"MOTION\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 52 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 21, + "title": "Motion Sensor Create Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_create_failure_total{sensor_type=\"MOTION\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 52 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 22, + "title": "Temperature Sensor Create Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_create_success_total{sensor_type=\"TEMPERATURE\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 52 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 23, + "title": "Temperature Sensor Create Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_create_failure_total{sensor_type=\"TEMPERATURE\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 58 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 24, + "title": "Light Sensor Update Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_update_success_total{sensor_type=\"LIGHT\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 58 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 25, + "title": "Light Sensor Update Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_update_failure_total{sensor_type=\"LIGHT\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 58 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 26, + "title": "Motion Sensor Update Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_update_success_total{sensor_type=\"MOTION\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 64 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 27, + "title": "Motion Sensor Update Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_update_failure_total{sensor_type=\"MOTION\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 64 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 28, + "title": "Temperature Sensor Update Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_update_success_total{sensor_type=\"TEMPERATURE\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 64 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 29, + "title": "Temperature Sensor Update Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_update_failure_total{sensor_type=\"TEMPERATURE\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 70 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 30, + "title": "Light Sensor Delete Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_delete_success_total{sensor_type=\"LIGHT\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 70 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 31, + "title": "Light Sensor Delete Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_delete_failure_total{sensor_type=\"LIGHT\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 70 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 32, + "title": "Motion Sensor Delete Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_delete_success_total{sensor_type=\"MOTION\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 76 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 33, + "title": "Motion Sensor Delete Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_delete_failure_total{sensor_type=\"MOTION\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 76 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 34, + "title": "Temperature Sensor Delete Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_delete_success_total{sensor_type=\"TEMPERATURE\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 76 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 35, + "title": "Temperature Sensor Delete Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_delete_failure_total{sensor_type=\"TEMPERATURE\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 82 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + } + ], + "time": { + "from": "now-15m", + "to": "now" + }, + "refresh": "5s" + } \ No newline at end of file diff --git a/k8s/logging/grafana/dashboard.yaml b/k8s/logging/grafana/dashboard.yaml index e190422..8a3a48d 100644 --- a/k8s/logging/grafana/dashboard.yaml +++ b/k8s/logging/grafana/dashboard.yaml @@ -168,4 +168,1338 @@ data: "from": "now-1h", "to": "now"}, "refresh": "5s" - } \ No newline at end of file + } + custom-app-dashboard.json: | + { + "id": null, + "title": "Custom Application Metrics", + "tags": [ + "spring-boot", + "app-metrics", + "camera-onboarding" + ], + "style": "dark", + "timezone": "browser", + "panels": [ + { + "id": 1, + "title": "Request Latency Percentiles", + "type": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket{kubernetes_namespace=\"app\"}[2m])) by (le))", + "legendFormat": "P95 Latency", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.90, sum(rate(http_server_requests_seconds_bucket{kubernetes_namespace=\"app\"}[2m])) by (le))", + "legendFormat": "P90 Latency", + "refId": "B" + }, + { + "expr": "histogram_quantile(0.50, sum(rate(http_server_requests_seconds_bucket{kubernetes_namespace=\"app\"}[2m])) by (le))", + "legendFormat": "P50 Latency", + "refId": "C" + } + ], + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "fieldConfig": { + "defaults": { + "unit": "s", + "min": 0, + "color": { + "mode": "palette-classic" + } + } + }, + "options": { + "legend": { + "displayMode": "table", + "values": [ + "last", + "max" + ] + } + } + }, + { + "id": 2, + "title": "Request Rate (req/sec)", + "type": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sum(rate(http_server_requests_seconds_count{kubernetes_namespace=\"app\"}[1m]))", + "legendFormat": "Total RPS", + "refId": "A" + } + ], + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 8 + }, + "fieldConfig": { + "defaults": { + "unit": "reqps", + "color": { + "mode": "palette-classic" + } + } + } + }, + { + "id": 3, + "title": "Successful Requests (5m)", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sum(increase(http_server_requests_seconds_count{kubernetes_namespace=\"app\", status=~\"2..\"}[5m]))", + "legendFormat": "Successful" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 14 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": null + } + ] + } + } + } + }, + { + "id": 4, + "title": "Failed Requests (5m)", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sum(increase(http_server_requests_seconds_count{kubernetes_namespace=\"app\", status=~\"[45]..\"}[5m]))", + "legendFormat": "Failed" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 14 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 5, + "title": "Success Rate (%)", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "(sum(rate(http_server_requests_seconds_count{kubernetes_namespace=\"app\", status=~\"2..\"}[5m])) / sum(rate(http_server_requests_seconds_count{kubernetes_namespace=\"app\"}[5m]))) * 100", + "legendFormat": "Success Rate" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 14 + }, + "fieldConfig": { + "defaults": { + "unit": "percent", + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 90 + }, + { + "color": "green", + "value": 95 + } + ] + } + } + } + }, + { + "id": 6, + "title": "Request Status Distribution", + "type": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sum by (status) (rate(http_server_requests_seconds_count{kubernetes_namespace=\"app\"}[1m]))", + "legendFormat": "{{status}}" + } + ], + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 20 + }, + "fieldConfig": { + "defaults": { + "unit": "reqps", + "color": { + "mode": "palette-classic" + } + } + } + }, + { + "id": 7, + "title": "DEBUG PANEL: Available HTTP Metrics", + "type": "table", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "group by (__name__) ({__name__=~\".*http.*request.*\", kubernetes_namespace=\"app\"})", + "format": "table" + } + ], + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 20 + } + }, + { + "id": 8, + "title": "Camera Onboarding Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "camera_onboarding_success_total", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 28 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 9, + "title": "Camera Onboarding Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "camera_onboarding_failure_total", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 28 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 10, + "title": "Camera Initialization Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "camera_initialization_success_total", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 28 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 11, + "title": "Camera Initialization Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "camera_initialization_failure_total", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 34 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 12, + "title": "Image Upload Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "image_upload_success_total", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 34 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 13, + "title": "Image Upload Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "image_upload_failure_total", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 34 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 14, + "title": "Image Download Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "image_download_success_total", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 40 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 15, + "title": "Image Download Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "image_download_failure_total", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 40 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 16, + "title": "Location Add Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "location_add_success_total", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 40 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 17, + "title": "Location Add Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "location_add_failure_total", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 46 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 18, + "title": "Light Sensor Create Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_create_success_total{sensor_type=\"LIGHT\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 46 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 19, + "title": "Light Sensor Create Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_create_failure_total{sensor_type=\"LIGHT\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 46 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 20, + "title": "Motion Sensor Create Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_create_success_total{sensor_type=\"MOTION\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 52 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 21, + "title": "Motion Sensor Create Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_create_failure_total{sensor_type=\"MOTION\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 52 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 22, + "title": "Temperature Sensor Create Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_create_success_total{sensor_type=\"TEMPERATURE\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 52 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 23, + "title": "Temperature Sensor Create Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_create_failure_total{sensor_type=\"TEMPERATURE\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 58 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 24, + "title": "Light Sensor Update Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_update_success_total{sensor_type=\"LIGHT\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 58 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 25, + "title": "Light Sensor Update Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_update_failure_total{sensor_type=\"LIGHT\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 58 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 26, + "title": "Motion Sensor Update Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_update_success_total{sensor_type=\"MOTION\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 64 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 27, + "title": "Motion Sensor Update Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_update_failure_total{sensor_type=\"MOTION\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 64 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 28, + "title": "Temperature Sensor Update Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_update_success_total{sensor_type=\"TEMPERATURE\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 64 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 29, + "title": "Temperature Sensor Update Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_update_failure_total{sensor_type=\"TEMPERATURE\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 70 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 30, + "title": "Light Sensor Delete Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_delete_success_total{sensor_type=\"LIGHT\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 70 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 31, + "title": "Light Sensor Delete Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_delete_failure_total{sensor_type=\"LIGHT\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 70 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 32, + "title": "Motion Sensor Delete Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_delete_success_total{sensor_type=\"MOTION\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 76 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 33, + "title": "Motion Sensor Delete Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_delete_failure_total{sensor_type=\"MOTION\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 76 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + }, + { + "id": 34, + "title": "Temperature Sensor Delete Success", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_delete_success_total{sensor_type=\"TEMPERATURE\"}", + "legendFormat": "Success" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 76 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + } + }, + { + "id": 35, + "title": "Temperature Sensor Delete Failure", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "targets": [ + { + "expr": "sensor_delete_failure_total{sensor_type=\"TEMPERATURE\"}", + "legendFormat": "Failure" + } + ], + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 82 + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 1 + } + ] + } + } + } + } + ], + "time": { + "from": "now-15m", + "to": "now" + }, + "refresh": "5s" + } diff --git a/owasp-suppressions.xml b/owasp-suppressions.xml index 398fcf5..410be39 100644 --- a/owasp-suppressions.xml +++ b/owasp-suppressions.xml @@ -1,9 +1,11 @@ - + CVE-2023-36415 + + CVE-2024-35255 @@ -11,77 +13,30 @@ CVE-2025-48924 - - - - CVE-2024-57699 - - - + - CVE-2024-12798 - CVE-2024-12801 - - - - - CVE-2024-35255 - - - - - CVE-2024-47535 + CVE-2025-55163 + CVE-2025-58056 + CVE-2025-58057 CVE-2025-24970 - - - CVE-2025-53864 - - - - - CVE-2025-22227 - - - + + CVE-2022-30187 CVE-2025-25193 - - - - CVE-2025-22233 - - - - - CVE-2024-38820 - - - - - CVE-2024-38809 - CVE-2024-38820 - CVE-2025-41234 - - - - - CVE-2024-38816 - CVE-2024-38820 - CVE-2025-26791 - + CVE-2024-50379 CVE-2024-52316 @@ -96,9 +51,7 @@ CVE-2025-49125 CVE-2025-52520 CVE-2025-53506 - - - - CVE-2025-22235 + CVE-2025-55668 + CVE-2025-48989 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 51566b9..1aff973 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.1 + 3.3.8 com.onboarding.camera @@ -16,7 +16,7 @@ 21 - 5.15.0 + 5.18.0 @@ -50,6 +50,7 @@ org.projectlombok lombok + 1.18.38 true @@ -61,7 +62,6 @@ com.azure azure-storage-blob - 12.27.1 @@ -97,6 +97,12 @@ compile + + javax.annotation + javax.annotation-api + 1.3.2 + + org.springframework.boot spring-boot-starter-test @@ -182,8 +188,9 @@ org.owasp dependency-check-maven - 12.1.0 + 12.1.5 + false ${project.basedir}/owasp-suppressions.xml false 0 diff --git a/src/main/java/com/onboarding/camera/cameraonboarding/service/CameraMetricService.java b/src/main/java/com/onboarding/camera/cameraonboarding/service/CameraMetricService.java new file mode 100644 index 0000000..ed5448e --- /dev/null +++ b/src/main/java/com/onboarding/camera/cameraonboarding/service/CameraMetricService.java @@ -0,0 +1,96 @@ +package com.onboarding.camera.cameraonboarding.service; + +public interface CameraMetricService { + + /** + * Increments the counter for successful camera onboarding. + */ + void incrementCameraOnboardingSuccess(); + + /** + * Increments the counter for failed camera onboarding. + */ + void incrementCameraOnboardingFailure(); + + /** + * Increments the counter for successful camera initialization. + */ + void incrementCameraInitializationSuccess(); + + /** + * Increments the counter for failed camera initialization. + */ + void incrementCameraInitializationFailure(); + + /** + * Increments the counter for successful image uploads. + */ + void incrementImageUploadSuccess(); + + /** + * Increments the counter for failed image uploads. + */ + void incrementImageUploadFailure(); + + /** + * Increments the counter for successful image downloads. + */ + void incrementImageDownloadSuccess(); + + /** + * Increments the counter for failed image downloads. + */ + void incrementImageDownloadFailure(); + + /** + * Increments the counter for successfully adding a location. + */ + void incrementLocationAddSuccess(); + + /** + * Increments the counter for failed attempts at adding a location. + */ + void incrementLocationAddFailure(); + + /** + * Increments the counter for successful sensor creation. + * + * @param sensorType the type of the sensor + */ + void incrementSensorCreateSuccess(String sensorType); + + /** + * Increments the counter for failed sensor creation. + * + * @param sensorType the type of the sensor + */ + void incrementSensorCreateFailure(String sensorType); + + /** + * Increments the counter for successful sensor updates. + * + * @param sensorType the type of the sensor + */ + void incrementSensorUpdateSuccess(String sensorType); + + /** + * Increments the counter for failed sensor updates. + * + * @param sensorType the type of the sensor + */ + void incrementSensorUpdateFailure(String sensorType); + + /** + * Increments the counter for successful sensor deletions. + * + * @param sensorType the type of the sensor + */ + void incrementSensorDeleteSuccess(String sensorType); + + /** + * Increments the counter for failed sensor deletions. + * + * @param sensorType the type of the sensor + */ + void incrementSensorDeleteFailure(String sensorType); +} diff --git a/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/CameraMetricServiceImpl.java b/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/CameraMetricServiceImpl.java new file mode 100644 index 0000000..0415e81 --- /dev/null +++ b/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/CameraMetricServiceImpl.java @@ -0,0 +1,177 @@ +package com.onboarding.camera.cameraonboarding.service.impl; + +import com.onboarding.camera.cameraonboarding.service.CameraMetricService; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.HashMap; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class CameraMetricServiceImpl implements CameraMetricService { + + private final MeterRegistry meterRegistry; + + private Counter cameraOnboardingSuccessCounter; + private Counter cameraOnboardingFailureCounter; + + private Counter cameraInitializationSuccessCounter; + private Counter cameraInitializationFailureCounter; + + private Counter imageUploadSuccessCounter; + private Counter imageUploadFailureCounter; + + private Counter imageDownloadSuccessCounter; + private Counter imageDownloadFailureCounter; + + private Counter locationAddSuccessCounter; + private Counter locationAddFailureCounter; + + private final Map sensorCreateSuccessCounters = new HashMap<>(); + private final Map sensorCreateFailureCounters = new HashMap<>(); + private final Map sensorUpdateSuccessCounters = new HashMap<>(); + private final Map sensorUpdateFailureCounters = new HashMap<>(); + private final Map sensorDeleteSuccessCounters = new HashMap<>(); + private final Map sensorDeleteFailureCounters = new HashMap<>(); + + + @PostConstruct + public void init() { + cameraOnboardingSuccessCounter = Counter.builder("camera.onboarding.success") + .description("Number of successful camera onboarding operations") + .register(meterRegistry); + cameraOnboardingFailureCounter = Counter.builder("camera.onboarding.failure") + .description("Number of failed camera onboarding operations") + .register(meterRegistry); + + cameraInitializationSuccessCounter = Counter.builder("camera.initialization.success") + .description("Number of successful camera initialization operations") + .register(meterRegistry); + cameraInitializationFailureCounter = Counter.builder("camera.initialization.failure") + .description("Number of failed camera initialization operations") + .register(meterRegistry); + + imageUploadSuccessCounter = Counter.builder("image.upload.success") + .description("Number of successful image upload operations") + .register(meterRegistry); + imageUploadFailureCounter = Counter.builder("image.upload.failure") + .description("Number of failed image upload operations") + .register(meterRegistry); + + imageDownloadSuccessCounter = Counter.builder("image.download.success") + .description("Number of successful image download operations") + .register(meterRegistry); + imageDownloadFailureCounter = Counter.builder("image.download.failure") + .description("Number of failed image download operations") + .register(meterRegistry); + + locationAddSuccessCounter = Counter.builder("location.add.success") + .description("Number of successful location add operations") + .register(meterRegistry); + locationAddFailureCounter = Counter.builder("location.add.failure") + .description("Number of failed location add operations") + .register(meterRegistry); + } + + @Override + public void incrementCameraOnboardingSuccess() { + cameraOnboardingSuccessCounter.increment(); + } + + @Override + public void incrementCameraOnboardingFailure() { + cameraOnboardingFailureCounter.increment(); + } + + @Override + public void incrementCameraInitializationSuccess() { + cameraInitializationSuccessCounter.increment(); + } + + @Override + public void incrementCameraInitializationFailure() { + cameraInitializationFailureCounter.increment(); + } + + @Override + public void incrementImageUploadSuccess() { + imageUploadSuccessCounter.increment(); + } + + @Override + public void incrementImageUploadFailure() { + imageUploadFailureCounter.increment(); + } + + @Override + public void incrementImageDownloadSuccess() { + imageDownloadSuccessCounter.increment(); + } + + @Override + public void incrementImageDownloadFailure() { + imageDownloadFailureCounter.increment(); + } + + @Override + public void incrementLocationAddSuccess() { + locationAddSuccessCounter.increment(); + } + + @Override + public void incrementLocationAddFailure() { + locationAddFailureCounter.increment(); + } + + @Override + public void incrementSensorCreateSuccess(String sensorType) { + sensorCreateSuccessCounters.computeIfAbsent(sensorType, k -> Counter.builder("sensor.create.success") + .tag("sensor.type", k) + .description("Number of successful sensor creation operations") + .register(meterRegistry)).increment(); + } + + @Override + public void incrementSensorCreateFailure(String sensorType) { + sensorCreateFailureCounters.computeIfAbsent(sensorType, k -> Counter.builder("sensor.create.failure") + .tag("sensor.type", k) + .description("Number of failed sensor creation operations") + .register(meterRegistry)).increment(); + } + + @Override + public void incrementSensorUpdateSuccess(String sensorType) { + sensorUpdateSuccessCounters.computeIfAbsent(sensorType, k -> Counter.builder("sensor.update.success") + .tag("sensor.type", k) + .description("Number of successful sensor update operations") + .register(meterRegistry)).increment(); + } + + @Override + public void incrementSensorUpdateFailure(String sensorType) { + sensorUpdateFailureCounters.computeIfAbsent(sensorType, k -> Counter.builder("sensor.update.failure") + .tag("sensor.type", k) + .description("Number of failed sensor update operations") + .register(meterRegistry)).increment(); + } + + @Override + public void incrementSensorDeleteSuccess(String sensorType) { + sensorDeleteSuccessCounters.computeIfAbsent(sensorType, k -> Counter.builder("sensor.delete.success") + .tag("sensor.type", k) + .description("Number of successful sensor deletion operations") + .register(meterRegistry)).increment(); + } + + @Override + public void incrementSensorDeleteFailure(String sensorType) { + sensorDeleteFailureCounters.computeIfAbsent(sensorType, k -> Counter.builder("sensor.delete.failure") + .tag("sensor.type", k) + .description("Number of failed sensor deletion operations") + .register(meterRegistry)).increment(); + } +} diff --git a/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/CameraServiceImpl.java b/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/CameraServiceImpl.java index efce22b..c392d1b 100644 --- a/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/CameraServiceImpl.java +++ b/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/CameraServiceImpl.java @@ -14,8 +14,10 @@ import com.onboarding.camera.cameraonboarding.exception.LocationNotAddedException; import com.onboarding.camera.cameraonboarding.repository.CameraRepository; import com.onboarding.camera.cameraonboarding.service.BlobStorageService; +import com.onboarding.camera.cameraonboarding.service.CameraMetricService; import com.onboarding.camera.cameraonboarding.service.CameraService; import com.onboarding.camera.cameraonboarding.util.DateTimeFactory; +import io.micrometer.core.annotation.Timed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -36,7 +38,10 @@ public class CameraServiceImpl implements CameraService { private final BlobStorageService blobStorageService; + private final CameraMetricService cameraMetricService; + @Override + @Timed("camera.onboarding") public Camera handleSaveCamera(Camera camera) { try { @@ -44,14 +49,17 @@ public Camera handleSaveCamera(Camera camera) { camera.setOnboardedAt(dateTimeFactory.now()); Camera savedCamera = cameraRepository.save(camera); log.info("Camera saved with ID: {}", savedCamera.getCamId()); + cameraMetricService.incrementCameraOnboardingSuccess(); return savedCamera; } catch (Exception ex) { log.error("Exception occurred while saving camera: {}", ex.getMessage()); + cameraMetricService.incrementCameraOnboardingFailure(); throw new CameraNotCreatedException(String.format("Error occurred while saving camera: %s", ex.getMessage())); } } @Override + @Timed("camera.initialization") public void handleInitializeCamera(UUID cameraId) { Camera camera = getCameraById(cameraId); if (camera.getInitializedAt() != null && !camera.getInitializedAt().toString().isBlank()) { @@ -62,8 +70,10 @@ public void handleInitializeCamera(UUID cameraId) { camera.setInitializedAt(dateTimeFactory.now()); cameraRepository.save(camera); log.info("Camera initialized with ID: {}", cameraId); + cameraMetricService.incrementCameraInitializationSuccess(); } catch (Exception ex) { log.error("Exception occurred while initializing camera with ID: {}", cameraId, ex); + cameraMetricService.incrementCameraInitializationFailure(); throw new CameraNotInitializedException(String.format("Error occurred while initializing camera: %s", ex.getMessage())); } } @@ -75,6 +85,7 @@ public Camera getCameraById(UUID cameraId) { } @Override + @Timed("image.upload") public void handleUploadImage(UUID cameraId, UUID imageId, byte[] imageData) { Camera camera = getCameraById(cameraId); validateCameraImage(camera); @@ -88,16 +99,20 @@ public void handleUploadImage(UUID cameraId, UUID imageId, byte[] imageData) { log.info("Uploading image with ID: {}", imageId); blobStorageService.uploadFile(blobStorageService.getContainerName(), imageId.toString(), imageData); cameraRepository.save(camera); + cameraMetricService.incrementImageUploadSuccess(); } catch (ImageAlreadyUploadedException ex) { log.error("Exception occurred while uploading image"); + cameraMetricService.incrementImageUploadFailure(); throw new ImageAlreadyUploadedException(String.format("Camera already have image with id: %s", camera.getImageId())); } catch (Exception ex) { log.error("Exception occurred while uploading image:{}:ex:{}", imageId, ex.getMessage()); + cameraMetricService.incrementImageUploadFailure(); throw new ImageNotUploadedException(String.format("Error occurred while uploading image: %s", ex.getMessage())); } } @Override + @Timed("image.download") public byte[] handleDownloadImage(UUID cameraId) { Camera camera = getCameraById(cameraId); validateCameraImage(camera); @@ -111,18 +126,22 @@ public byte[] handleDownloadImage(UUID cameraId) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); blobStorageService.getBlob(outputStream, blobStorageService.getContainerName(), camera.getImageId().toString()); + cameraMetricService.incrementImageDownloadSuccess(); return outputStream.toByteArray(); } catch (ImageNotFoundException ex) { log.error("Image is not found by given cameraId: '{}'", cameraId); + cameraMetricService.incrementImageDownloadFailure(); throw new ImageNotFoundException(String.format("Image is not found by given cameraId: %s", cameraId)); } catch (Exception ex) { log.error("Exception occurred while downloading image, camera:{}:ex:{}", cameraId, ex.getMessage()); + cameraMetricService.incrementImageDownloadFailure(); throw new ImageNotDownloadedException(String.format("Error occurred while downloading image: %s", ex.getMessage())); } } @Override @Transactional + @Timed("location.add") public Camera handleAddLocation(UUID cameraId, LocationDto locationDto) { Camera camera = getCameraById(cameraId); @@ -137,9 +156,11 @@ public Camera handleAddLocation(UUID cameraId, LocationDto locationDto) { cameraRepository.save(camera); log.info("Location added/updated successfully for Camera ID: {}", cameraId); + cameraMetricService.incrementLocationAddSuccess(); return camera; } catch (Exception ex) { log.error("Exception occurred while adding location, camera:{}:ex:{}", cameraId, ex.getMessage()); + cameraMetricService.incrementLocationAddFailure(); throw new LocationNotAddedException(String.format("Error occurred while adding location: %s", ex.getMessage())); } } diff --git a/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/LightSensorService.java b/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/LightSensorService.java index 3926a2d..8865f0c 100644 --- a/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/LightSensorService.java +++ b/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/LightSensorService.java @@ -8,8 +8,10 @@ import com.onboarding.camera.cameraonboarding.exception.SensorNotFoundException; import com.onboarding.camera.cameraonboarding.exception.SensorNotUpdatedException; import com.onboarding.camera.cameraonboarding.repository.LightSensorRepository; +import com.onboarding.camera.cameraonboarding.service.CameraMetricService; import com.onboarding.camera.cameraonboarding.service.CameraService; import com.onboarding.camera.cameraonboarding.service.SensorService; +import io.micrometer.core.annotation.Timed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -27,19 +29,25 @@ public class LightSensorService implements SensorService { private final CameraService cameraService; + private final CameraMetricService cameraMetricService; + @Override @Transactional + @Timed("sensor.create") public LightSensor handleCreateSensor(UUID cameraId, LightSensor sensor) { try { sensor.setCamera(cameraService.getCameraById(cameraId)); LightSensor createdSensor = lightSensorRepository.save(sensor); log.info("Creating sensor: {}", createdSensor); + cameraMetricService.incrementSensorCreateSuccess(SensorType.LIGHT.name()); return createdSensor; } catch (CameraNotFoundException ex) { log.error("Camera not found, cameraId:{}", cameraId); + cameraMetricService.incrementSensorCreateFailure(SensorType.LIGHT.name()); throw ex; } catch (Exception ex) { log.error("Failed to create sensor, camera:{}:ex:{}", cameraId, ex.getMessage()); + cameraMetricService.incrementSensorCreateFailure(SensorType.LIGHT.name()); throw new SensorNotCreatedException(String.format("Failed to create sensor: %s", ex.getMessage())); } } @@ -61,6 +69,7 @@ public List handleGetSensorsByCameraId(UUID cameraId) { @Override @Transactional + @Timed("sensor.update") public LightSensor handleUpdateSensor(UUID cameraId, UUID sensorId, LightSensor sensor) { try { Camera camera = cameraService.getCameraById(cameraId); @@ -74,15 +83,19 @@ public LightSensor handleUpdateSensor(UUID cameraId, UUID sensorId, LightSensor existingSensor.setVersion(sensor.getVersion()); existingSensor.setData(sensor.getData()); log.info("Updating sensor: {}", existingSensor); + cameraMetricService.incrementSensorUpdateSuccess(SensorType.LIGHT.name()); return lightSensorRepository.save(existingSensor); } catch (SensorNotFoundException ex) { log.error("Sensor not found while updating, sensorId: {}", sensorId); + cameraMetricService.incrementSensorUpdateFailure(SensorType.LIGHT.name()); throw ex; } catch (CameraNotFoundException ex) { log.error("Camera not found, cameraId:{}", cameraId); + cameraMetricService.incrementSensorUpdateFailure(SensorType.LIGHT.name()); throw ex; } catch (Exception ex) { log.error("Exception occurred while updating sensor, sensorId:{}", sensorId); + cameraMetricService.incrementSensorUpdateFailure(SensorType.LIGHT.name()); throw new SensorNotUpdatedException(String.format("Error occurred while updating sensors: %s", ex.getMessage())); } } @@ -95,6 +108,7 @@ public LightSensor getSensorById(UUID sensorId) { } @Override + @Timed("sensor.delete") public void handleDeleteSensor(UUID cameraId, UUID sensorId) { try { Camera camera = cameraService.getCameraById(cameraId); @@ -107,14 +121,18 @@ public void handleDeleteSensor(UUID cameraId, UUID sensorId) { camera.getSensors().remove(sensor); lightSensorRepository.delete(sensor); log.info("Deleted sensor: {}", sensorId); + cameraMetricService.incrementSensorDeleteSuccess(SensorType.LIGHT.name()); } catch (CameraNotFoundException ex) { log.error("Camera not found, cameraId:{}", cameraId); + cameraMetricService.incrementSensorDeleteFailure(SensorType.LIGHT.name()); throw ex; } catch (SensorNotFoundException ex) { log.error("Sensor not found while deleting, sensorId: {}", sensorId); + cameraMetricService.incrementSensorDeleteFailure(SensorType.LIGHT.name()); throw ex; } catch (Exception ex) { log.error("Exception occurred while deleting sensor, sensorId:{}", sensorId); + cameraMetricService.incrementSensorDeleteFailure(SensorType.LIGHT.name()); throw new SensorNotUpdatedException(String.format("Error occurred while deleting sensor: %s", ex.getMessage())); } } diff --git a/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/MotionSensorService.java b/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/MotionSensorService.java index c7be36f..3c72087 100644 --- a/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/MotionSensorService.java +++ b/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/MotionSensorService.java @@ -8,8 +8,10 @@ import com.onboarding.camera.cameraonboarding.exception.SensorNotFoundException; import com.onboarding.camera.cameraonboarding.exception.SensorNotUpdatedException; import com.onboarding.camera.cameraonboarding.repository.MotionSensorRepository; +import com.onboarding.camera.cameraonboarding.service.CameraMetricService; import com.onboarding.camera.cameraonboarding.service.CameraService; import com.onboarding.camera.cameraonboarding.service.SensorService; +import io.micrometer.core.annotation.Timed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -27,19 +29,25 @@ public class MotionSensorService implements SensorService { private final CameraService cameraService; + private final CameraMetricService cameraMetricService; + @Override @Transactional + @Timed("sensor.create") public MotionSensor handleCreateSensor(UUID cameraId, MotionSensor sensor) { try { sensor.setCamera(cameraService.getCameraById(cameraId)); MotionSensor createdSensor = motionSensorRepository.save(sensor); log.info("Creating sensor: {}", createdSensor); + cameraMetricService.incrementSensorCreateSuccess(SensorType.MOTION.name()); return createdSensor; } catch (CameraNotFoundException ex) { log.error("Camera not found, cameraId:{}", cameraId); + cameraMetricService.incrementSensorCreateFailure(SensorType.MOTION.name()); throw ex; } catch (Exception ex) { log.error("Failed to create sensor, camera:{}:ex:{}", cameraId, ex.getMessage()); + cameraMetricService.incrementSensorCreateFailure(SensorType.MOTION.name()); throw new SensorNotCreatedException(String.format("Failed to create sensor: %s", ex.getMessage())); } } @@ -61,6 +69,7 @@ public List handleGetSensorsByCameraId(UUID cameraId) { @Override @Transactional + @Timed("sensor.update") public MotionSensor handleUpdateSensor(UUID cameraId, UUID sensorId, MotionSensor sensor) { try { Camera camera = cameraService.getCameraById(cameraId); @@ -74,15 +83,19 @@ public MotionSensor handleUpdateSensor(UUID cameraId, UUID sensorId, MotionSenso existingSensor.setVersion(sensor.getVersion()); existingSensor.setData(sensor.getData()); log.info("Updating sensor: {}", existingSensor); + cameraMetricService.incrementSensorUpdateSuccess(SensorType.MOTION.name()); return motionSensorRepository.save(existingSensor); } catch (CameraNotFoundException ex) { log.error("Camera not found, cameraId:{}", cameraId); + cameraMetricService.incrementSensorUpdateFailure(SensorType.MOTION.name()); throw ex; } catch (SensorNotFoundException ex) { log.error("Sensor not found while updating, sensorId: {}", sensorId); + cameraMetricService.incrementSensorUpdateFailure(SensorType.MOTION.name()); throw ex; } catch (Exception ex) { log.error("Exception occurred while updating sensor, sensorId:{}", sensorId); + cameraMetricService.incrementSensorUpdateFailure(SensorType.MOTION.name()); throw new SensorNotUpdatedException(String.format("Error occurred while updating sensors: %s", ex.getMessage())); } } @@ -95,6 +108,7 @@ public MotionSensor getSensorById(UUID sensorId) { } @Override + @Timed("sensor.delete") public void handleDeleteSensor(UUID cameraId, UUID sensorId) { try { Camera camera = cameraService.getCameraById(cameraId); @@ -107,14 +121,18 @@ public void handleDeleteSensor(UUID cameraId, UUID sensorId) { camera.getSensors().remove(sensor); motionSensorRepository.delete(sensor); log.info("Deleted sensor: {}", sensorId); + cameraMetricService.incrementSensorDeleteSuccess(SensorType.MOTION.name()); } catch (CameraNotFoundException ex) { log.error("Camera not found, cameraId:{}", cameraId); + cameraMetricService.incrementSensorDeleteFailure(SensorType.MOTION.name()); throw ex; } catch (SensorNotFoundException ex) { log.error("Sensor not found while deleting, sensorId: {}", sensorId); + cameraMetricService.incrementSensorDeleteFailure(SensorType.MOTION.name()); throw ex; } catch (Exception ex) { log.error("Exception occurred while deleting sensor, sensorId:{}", sensorId); + cameraMetricService.incrementSensorDeleteFailure(SensorType.MOTION.name()); throw new SensorNotUpdatedException(String.format("Error occurred while deleting sensor: %s", ex.getMessage())); } } diff --git a/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/TemperatureSensorService.java b/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/TemperatureSensorService.java index f1a0835..9da17e9 100644 --- a/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/TemperatureSensorService.java +++ b/src/main/java/com/onboarding/camera/cameraonboarding/service/impl/TemperatureSensorService.java @@ -8,8 +8,10 @@ import com.onboarding.camera.cameraonboarding.exception.SensorNotFoundException; import com.onboarding.camera.cameraonboarding.exception.SensorNotUpdatedException; import com.onboarding.camera.cameraonboarding.repository.TemperatureSensorRepository; +import com.onboarding.camera.cameraonboarding.service.CameraMetricService; import com.onboarding.camera.cameraonboarding.service.CameraService; import com.onboarding.camera.cameraonboarding.service.SensorService; +import io.micrometer.core.annotation.Timed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -27,19 +29,25 @@ public class TemperatureSensorService implements SensorService handleGetSensorsByCameraId(UUID cameraId) { @Override @Transactional + @Timed("sensor.update") public TemperatureSensor handleUpdateSensor(UUID cameraId, UUID sensorId, TemperatureSensor sensor) { try { Camera camera = cameraService.getCameraById(cameraId); @@ -74,15 +83,19 @@ public TemperatureSensor handleUpdateSensor(UUID cameraId, UUID sensorId, Temper existingSensor.setVersion(sensor.getVersion()); existingSensor.setData(sensor.getData()); log.info("Updating sensor: {}", existingSensor); + cameraMetricService.incrementSensorUpdateSuccess(SensorType.TEMPERATURE.name()); return temperatureSensorRepository.save(existingSensor); } catch (CameraNotFoundException ex) { log.error("Camera not found, cameraId:{}", cameraId); + cameraMetricService.incrementSensorUpdateFailure(SensorType.TEMPERATURE.name()); throw ex; } catch (SensorNotFoundException ex) { log.error("Sensor not found while updating, sensorId: {}", sensorId); + cameraMetricService.incrementSensorUpdateFailure(SensorType.TEMPERATURE.name()); throw ex; } catch (Exception ex) { log.error("Exception occurred while updating sensor, sensorId:{}", sensorId); + cameraMetricService.incrementSensorUpdateFailure(SensorType.TEMPERATURE.name()); throw new SensorNotUpdatedException(String.format("Error occurred while updating sensors: %s", ex.getMessage())); } } @@ -95,6 +108,7 @@ public TemperatureSensor getSensorById(UUID sensorId) { } @Override + @Timed("sensor.delete") public void handleDeleteSensor(UUID cameraId, UUID sensorId) { try { Camera camera = cameraService.getCameraById(cameraId); @@ -107,14 +121,18 @@ public void handleDeleteSensor(UUID cameraId, UUID sensorId) { camera.getSensors().remove(sensor); temperatureSensorRepository.delete(sensor); log.info("Deleted sensor: {}", sensorId); + cameraMetricService.incrementSensorDeleteSuccess(SensorType.TEMPERATURE.name()); } catch (CameraNotFoundException ex) { log.error("Camera not found, cameraId:{}", cameraId); + cameraMetricService.incrementSensorDeleteFailure(SensorType.TEMPERATURE.name()); throw ex; } catch (SensorNotFoundException ex) { log.error("Sensor not found while deleting, sensorId: {}", sensorId); + cameraMetricService.incrementSensorDeleteFailure(SensorType.TEMPERATURE.name()); throw ex; } catch (Exception ex) { log.error("Exception occurred while deleting sensor, sensorId:{}", sensorId); + cameraMetricService.incrementSensorDeleteFailure(SensorType.TEMPERATURE.name()); throw new SensorNotUpdatedException(String.format("Error occurred while deleting sensor: %s", ex.getMessage())); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 570ca71..32aa3d9 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,6 +6,7 @@ spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.SQLServerDialect spring.liquibase.change-log=classpath:db/changelog/changelog-master.xml api.version=api/v1 + # Azure blob storage configuration spring.cloud.azure.storage.account-name=${AZURE_STORAGE_ACCOUNT_NAME} spring.cloud.azure.storage.connection-string=${AZURE_STORAGE_CONNECTION_STRING} @@ -14,14 +15,16 @@ spring.servlet.multipart.enabled=true spring.servlet.multipart.max-file-size=4MB spring.servlet.multipart.max-request-size=40MB spring.cloud.azure.profile.environment.storage-endpoint-suffix=core.windows.net + +# Management endpoints configuration management.server.port=8080 management.endpoints.web.exposure.include=health,info,metrics,prometheus management.endpoint.metrics.enabled=true management.endpoint.prometheus.enabled=true -management.prometheus.metrics.export.enabled=true -# JVM metrics -management.metrics.enable.jvm=true -management.metrics.enable.system=true -management.metrics.enable.web=true management.endpoint.health.probes.enabled=true -management.endpoint.health.show-details=always \ No newline at end of file +management.endpoint.health.show-details=always + +# Micrometer metrics configuration +management.metrics.distribution.percentiles-histogram.http.server.requests=true +# Custom SLO boundaries for http.server.requests histogram in milliseconds +management.metrics.distribution.slo.http.server.requests=50ms,100ms,200ms,500ms,1s,2s diff --git a/src/test/java/com/onboarding/camera/cameraonboarding/service/CameraServiceImplTest.java b/src/test/java/com/onboarding/camera/cameraonboarding/service/CameraServiceImplTest.java index f8149a0..54d7610 100644 --- a/src/test/java/com/onboarding/camera/cameraonboarding/service/CameraServiceImplTest.java +++ b/src/test/java/com/onboarding/camera/cameraonboarding/service/CameraServiceImplTest.java @@ -45,6 +45,9 @@ class CameraServiceImplTest { @Mock private BlobStorageServiceImpl blobStorageService; + @Mock + private CameraMetricService cameraMetricService; + @InjectMocks private CameraServiceImpl cameraService; diff --git a/src/test/java/com/onboarding/camera/cameraonboarding/service/impl/LightSensorServiceTest.java b/src/test/java/com/onboarding/camera/cameraonboarding/service/impl/LightSensorServiceTest.java index aa47bac..e2315c7 100644 --- a/src/test/java/com/onboarding/camera/cameraonboarding/service/impl/LightSensorServiceTest.java +++ b/src/test/java/com/onboarding/camera/cameraonboarding/service/impl/LightSensorServiceTest.java @@ -7,6 +7,7 @@ import com.onboarding.camera.cameraonboarding.exception.SensorNotFoundException; import com.onboarding.camera.cameraonboarding.exception.SensorNotUpdatedException; import com.onboarding.camera.cameraonboarding.repository.LightSensorRepository; +import com.onboarding.camera.cameraonboarding.service.CameraMetricService; import com.onboarding.camera.cameraonboarding.service.CameraService; import jakarta.transaction.Transactional; import org.assertj.core.api.Assertions; @@ -34,6 +35,9 @@ class LightSensorServiceTest { @Mock private Camera camera; + @Mock + private CameraMetricService cameraMetricService; + @InjectMocks private LightSensorService lightSensorService; diff --git a/src/test/java/com/onboarding/camera/cameraonboarding/service/impl/MotionSensorServiceTest.java b/src/test/java/com/onboarding/camera/cameraonboarding/service/impl/MotionSensorServiceTest.java index 46eaa8e..0dc2341 100644 --- a/src/test/java/com/onboarding/camera/cameraonboarding/service/impl/MotionSensorServiceTest.java +++ b/src/test/java/com/onboarding/camera/cameraonboarding/service/impl/MotionSensorServiceTest.java @@ -7,6 +7,7 @@ import com.onboarding.camera.cameraonboarding.exception.SensorNotFoundException; import com.onboarding.camera.cameraonboarding.exception.SensorNotUpdatedException; import com.onboarding.camera.cameraonboarding.repository.MotionSensorRepository; +import com.onboarding.camera.cameraonboarding.service.CameraMetricService; import com.onboarding.camera.cameraonboarding.service.CameraService; import jakarta.transaction.Transactional; import org.assertj.core.api.Assertions; @@ -34,6 +35,9 @@ class MotionSensorServiceTest { @Mock private Camera camera; + @Mock + private CameraMetricService cameraMetricService; + @InjectMocks private MotionSensorService motionSensorService; diff --git a/src/test/java/com/onboarding/camera/cameraonboarding/service/impl/TemperatureSensorServiceTest.java b/src/test/java/com/onboarding/camera/cameraonboarding/service/impl/TemperatureSensorServiceTest.java index dea4b04..2214a0d 100644 --- a/src/test/java/com/onboarding/camera/cameraonboarding/service/impl/TemperatureSensorServiceTest.java +++ b/src/test/java/com/onboarding/camera/cameraonboarding/service/impl/TemperatureSensorServiceTest.java @@ -7,6 +7,7 @@ import com.onboarding.camera.cameraonboarding.exception.SensorNotFoundException; import com.onboarding.camera.cameraonboarding.exception.SensorNotUpdatedException; import com.onboarding.camera.cameraonboarding.repository.TemperatureSensorRepository; +import com.onboarding.camera.cameraonboarding.service.CameraMetricService; import com.onboarding.camera.cameraonboarding.service.CameraService; import jakarta.transaction.Transactional; import org.assertj.core.api.Assertions; @@ -34,6 +35,9 @@ class TemperatureSensorServiceTest { @Mock private Camera camera; + @Mock + private CameraMetricService cameraMetricService; + @InjectMocks private TemperatureSensorService temperatureSensorService;