From d276792607a9956c6a987780d7290e02f6c0d078 Mon Sep 17 00:00:00 2001 From: cato447 Date: Sat, 21 Feb 2026 15:00:49 +0100 Subject: [PATCH] added grafana and prometheus --- docker-compose.yml | 40 + .../provisioning/dashboards/dashboards.yml | 11 + .../provisioning/dashboards/tutor-tool.json | 685 ++++++++++++++++++ .../provisioning/datasources/datasources.yml | 26 + prometheus/blackbox.yml | 14 + prometheus/prometheus.yml | 49 ++ 6 files changed, 825 insertions(+) create mode 100644 grafana/provisioning/dashboards/dashboards.yml create mode 100644 grafana/provisioning/dashboards/tutor-tool.json create mode 100644 grafana/provisioning/datasources/datasources.yml create mode 100644 prometheus/blackbox.yml create mode 100644 prometheus/prometheus.yml diff --git a/docker-compose.yml b/docker-compose.yml index 9e73088..f370b6c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,3 +34,43 @@ services: redis: image: redis:alpine restart: unless-stopped + + prometheus: + image: prom/prometheus:latest + restart: unless-stopped + ports: + - "127.0.0.1:9090:9090" + volumes: + - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + - ./data/prometheus:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.retention.time=15d' + - '--storage.tsdb.retention.size=512MB' + depends_on: + - blackbox-exporter + + blackbox-exporter: + image: prom/blackbox-exporter:latest + restart: unless-stopped + volumes: + - ./prometheus/blackbox.yml:/etc/blackbox-exporter/config.yml + + grafana: + image: grafana/grafana:latest + restart: unless-stopped + env_file: ./app/.env.db + ports: + - "127.0.0.1:3000:3000" + volumes: + - ./data/grafana:/var/lib/grafana + - ./grafana/provisioning:/etc/grafana/provisioning + environment: + - GF_SECURITY_ADMIN_PASSWORD=changeme + - GF_SECURITY_ADMIN_USER=admin + - GF_USERS_ALLOW_SIGN_UP=false + # Only allow access from localhost/reverse proxy + - GF_SERVER_ROOT_URL=http://localhost:3000 + depends_on: + - prometheus + - db diff --git a/grafana/provisioning/dashboards/dashboards.yml b/grafana/provisioning/dashboards/dashboards.yml new file mode 100644 index 0000000..b2a90d9 --- /dev/null +++ b/grafana/provisioning/dashboards/dashboards.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: 'default' + orgId: 1 + folder: '' + type: file + disableDeletion: false + updateIntervalSeconds: 30 + options: + path: /etc/grafana/provisioning/dashboards diff --git a/grafana/provisioning/dashboards/tutor-tool.json b/grafana/provisioning/dashboards/tutor-tool.json new file mode 100644 index 0000000..4cb9403 --- /dev/null +++ b/grafana/provisioning/dashboards/tutor-tool.json @@ -0,0 +1,685 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "panels": [ + { + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "0": { + "color": "red", + "text": "DOWN" + }, + "1": { + "color": "green", + "text": "UP" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": 0 + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.3", + "targets": [ + { + "datasource": "Prometheus", + "expr": "probe_success{job=\"blackbox_http\"}", + "legendFormat": "{{ instance }}", + "refId": "A" + }, + { + "datasource": "Prometheus", + "expr": "probe_success{job=\"blackbox_tcp\"}", + "legendFormat": "{{ instance }}", + "refId": "B" + } + ], + "title": "Service Health", + "type": "stat" + }, + { + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 4 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.3", + "targets": [ + { + "datasource": "Prometheus", + "expr": "probe_duration_seconds{job=\"blackbox_http\"} * 1000", + "legendFormat": "{{ instance }}", + "refId": "A" + } + ], + "title": "HTTP Response Time (ms)", + "type": "timeseries" + }, + { + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": 0 + }, + { + "color": "yellow", + "value": 95 + }, + { + "color": "green", + "value": 99 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 4 + }, + "id": 3, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.3", + "targets": [ + { + "datasource": "Prometheus", + "expr": "avg_over_time(probe_success{job=\"blackbox_http\", instance=\"http://tutortool:5000/grafana/health\"}[24h]) * 100", + "legendFormat": "Flask App", + "refId": "A" + } + ], + "title": "Uptime % (24h)", + "type": "stat" + }, + { + "datasource": "PostgreSQL", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 12 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.3", + "targets": [ + { + "datasource": "PostgreSQL", + "format": "table", + "rawSql": "SELECT COUNT(*) as value FROM token", + "refId": "A" + } + ], + "title": "Total Registered Users", + "type": "stat" + }, + { + "datasource": "PostgreSQL", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 12 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.3", + "targets": [ + { + "datasource": "PostgreSQL", + "format": "table", + "rawSql": "SELECT COUNT(*) as value FROM deck", + "refId": "A" + } + ], + "title": "Total Decks", + "type": "stat" + }, + { + "datasource": "PostgreSQL", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 12 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.3", + "targets": [ + { + "datasource": "PostgreSQL", + "format": "table", + "rawSql": "SELECT COUNT(*) as value FROM course", + "refId": "A" + } + ], + "title": "Active Courses", + "type": "stat" + }, + { + "datasource": "PostgreSQL", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 12 + }, + "id": 7, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.3", + "targets": [ + { + "datasource": "PostgreSQL", + "format": "table", + "rawSql": "SELECT c.name as \"metric\", COUNT(t.id) as \"value\" FROM course c LEFT JOIN token t ON c.id = t.active_course_id GROUP BY c.name", + "refId": "A" + } + ], + "title": "Users per Active Course", + "type": "stat" + }, + { + "datasource": "PostgreSQL", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 8, + "options": { + "barRadius": 0, + "barWidth": 0.7, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "never", + "stacking": "none", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "12.3.3", + "targets": [ + { + "datasource": "PostgreSQL", + "format": "table", + "rawSql": "SELECT d.name AS \"Deck\", COUNT(dt.token_id) AS \"Users\" FROM deck d JOIN course c ON d.course_id = c.id LEFT JOIN deck_token dt ON d.id = dt.deck_id WHERE c.name = 'GRNVS Tutorium SS 25' GROUP BY d.name ORDER BY regexp_replace(d.name, '\\D', '', 'g')::integer ASC NULLS LAST", + "refId": "A" + } + ], + "title": "Users with Access per Deck — GRNVS Tutorium SS 25", + "type": "barchart" + }, + { + "datasource": "PostgreSQL", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Users with Access", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 26 + }, + "id": 9, + "options": { + "barRadius": 0, + "barWidth": 0.7, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "never", + "stacking": "none", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + }, + "xField": "Deck", + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "12.3.3", + "targets": [ + { + "datasource": "PostgreSQL", + "format": "table", + "rawSql": "SELECT d.name AS \"Deck\", COUNT(dt.token_id) AS \"Users\" FROM deck d JOIN course c ON d.course_id = c.id LEFT JOIN deck_token dt ON d.id = dt.deck_id WHERE c.name = 'IT-Sec Tutorium WS 25/26' GROUP BY d.name ORDER BY regexp_replace(d.name, '\\D', '', 'g')::integer ASC NULLS LAST", + "refId": "A" + } + ], + "title": "Users with Access per Deck — IT-Sec Tutorium WS 25/26", + "type": "barchart" + } + ], + "preload": false, + "refresh": "1m", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Tutor Tool", + "uid": "tutor-tool", + "version": 3 +} diff --git a/grafana/provisioning/datasources/datasources.yml b/grafana/provisioning/datasources/datasources.yml new file mode 100644 index 0000000..1d46d6c --- /dev/null +++ b/grafana/provisioning/datasources/datasources.yml @@ -0,0 +1,26 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: false + + - name: PostgreSQL + type: postgres + access: proxy + url: db:5432 + # These must match your .env.db values + user: $POSTGRES_USER + secureJsonData: + password: $POSTGRES_PASSWORD + jsonData: + database: $POSTGRES_DB + sslmode: disable + maxOpenConns: 5 + maxIdleConns: 2 + connMaxLifetime: 14400 + postgresVersion: 1700 + editable: false diff --git a/prometheus/blackbox.yml b/prometheus/blackbox.yml new file mode 100644 index 0000000..7f2c875 --- /dev/null +++ b/prometheus/blackbox.yml @@ -0,0 +1,14 @@ +modules: + http_2xx: + prober: http + timeout: 5s + http: + valid_http_versions: ["HTTP/1.1", "HTTP/2.0"] + valid_status_codes: [200] + method: GET + fail_if_ssl: false + fail_if_not_ssl: false + + tcp_connect: + prober: tcp + timeout: 5s diff --git a/prometheus/prometheus.yml b/prometheus/prometheus.yml new file mode 100644 index 0000000..7219553 --- /dev/null +++ b/prometheus/prometheus.yml @@ -0,0 +1,49 @@ +global: + scrape_interval: 30s + evaluation_interval: 30s + +scrape_configs: + + # --- Health checks via Blackbox Exporter --- + - job_name: 'blackbox_http' + metrics_path: /probe + params: + module: [http_2xx] + static_configs: + - targets: + - http://tutortool:5000/grafana/health + - http://tutortool:5000/ + relabel_configs: + - source_labels: [__address__] + target_label: __param_target + - source_labels: [__param_target] + target_label: instance + - target_label: __address__ + replacement: blackbox-exporter:9115 + + # --- TCP check for Postgres --- + - job_name: 'blackbox_tcp' + metrics_path: /probe + params: + module: [tcp_connect] + static_configs: + - targets: + - db:5432 + - redis:6379 + relabel_configs: + - source_labels: [__address__] + target_label: __param_target + - source_labels: [__param_target] + target_label: instance + - target_label: __address__ + replacement: blackbox-exporter:9115 + + # --- Blackbox exporter itself --- + - job_name: 'blackbox_exporter' + static_configs: + - targets: ['blackbox-exporter:9115'] + + # --- Prometheus itself --- + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090']