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;